diff --git a/FakeNFT.xcodeproj/project.pbxproj b/FakeNFT.xcodeproj/project.pbxproj index 160cc2dcb7..e1ee3e6796 100644 --- a/FakeNFT.xcodeproj/project.pbxproj +++ b/FakeNFT.xcodeproj/project.pbxproj @@ -47,6 +47,10 @@ 79D0C7562DE3973200D53241 /* TabBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79D0C7552DE3973200D53241 /* TabBarView.swift */; }; 79D0C7592DE39C3800D53241 /* NavigationBarStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79D0C7582DE39C3800D53241 /* NavigationBarStyle.swift */; }; 79D0C75D2DE39F5900D53241 /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79D0C75C2DE39F5900D53241 /* LoadingView.swift */; }; + 79D9E0402DF0316F005D5DB1 /* ShowWebViewButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79D9E03F2DF0316F005D5DB1 /* ShowWebViewButton.swift */; }; + 79D9E0422DF03260005D5DB1 /* ShowCollectionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79D9E0412DF03260005D5DB1 /* ShowCollectionButton.swift */; }; + 79D9E0442DF03392005D5DB1 /* UserCardViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79D9E0432DF03392005D5DB1 /* UserCardViewModel.swift */; }; + 79D9E0472DF03E41005D5DB1 /* UserCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79D9E0462DF03E41005D5DB1 /* UserCollectionView.swift */; }; E1CD40DC2A96BECC00BE7FE8 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E1CD40DB2A96BECC00BE7FE8 /* Localizable.strings */; }; /* End PBXBuildFile section */ @@ -110,6 +114,10 @@ 79D0C7552DE3973200D53241 /* TabBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarView.swift; sourceTree = ""; }; 79D0C7582DE39C3800D53241 /* NavigationBarStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarStyle.swift; sourceTree = ""; }; 79D0C75C2DE39F5900D53241 /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = ""; }; + 79D9E03F2DF0316F005D5DB1 /* ShowWebViewButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShowWebViewButton.swift; sourceTree = ""; }; + 79D9E0412DF03260005D5DB1 /* ShowCollectionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShowCollectionButton.swift; sourceTree = ""; }; + 79D9E0432DF03392005D5DB1 /* UserCardViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UserCardViewModel.swift; path = FakeNFT/UserCardViewModel.swift; sourceTree = SOURCE_ROOT; }; + 79D9E0462DF03E41005D5DB1 /* UserCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserCollectionView.swift; sourceTree = ""; }; E1CD40DB2A96BECC00BE7FE8 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = ""; }; /* End PBXFileReference section */ @@ -316,6 +324,7 @@ children = ( 793BCC9A2DEF1BC500DF1252 /* RatingView */, 79641C262DEF292A004C970D /* UserCardView */, + 79D9E0452DF03E34005D5DB1 /* UserCollectionView */, 79641C192DEF1D63004C970D /* Common */, ); path = Statistics; @@ -335,6 +344,8 @@ 79641C1F2DEF1ECB004C970D /* List */, 79641C1A2DEF1DF4004C970D /* Row */, 79641C1D2DEF1E63004C970D /* UserAvatar.swift */, + 79D9E03F2DF0316F005D5DB1 /* ShowWebViewButton.swift */, + 79D9E0412DF03260005D5DB1 /* ShowCollectionButton.swift */, ); path = Common; sourceTree = ""; @@ -376,6 +387,7 @@ isa = PBXGroup; children = ( 79641C222DEF2081004C970D /* RatingViewModel.swift */, + 79D9E0432DF03392005D5DB1 /* UserCardViewModel.swift */, ); path = ViewModels; sourceTree = ""; @@ -420,6 +432,14 @@ path = LoadingView; sourceTree = ""; }; + 79D9E0452DF03E34005D5DB1 /* UserCollectionView */ = { + isa = PBXGroup; + children = ( + 79D9E0462DF03E41005D5DB1 /* UserCollectionView.swift */, + ); + path = UserCollectionView; + sourceTree = ""; + }; E1CD40DA2A96BE9B00BE7FE8 /* Resources */ = { isa = PBXGroup; children = ( @@ -582,8 +602,10 @@ 79D0C75D2DE39F5900D53241 /* LoadingView.swift in Sources */, 797AAE8A2DEF316100745B0D /* UsersRequest.swift in Sources */, 0CF2C2DD2A783CE600FDC837 /* ErrorView.swift in Sources */, + 79D9E0402DF0316F005D5DB1 /* ShowWebViewButton.swift in Sources */, 79641C212DEF1ED7004C970D /* RatingList.swift in Sources */, 5019A4CE2DE1E899009A5AF8 /* FakeNftApp.swift in Sources */, + 79D9E0442DF03392005D5DB1 /* UserCardViewModel.swift in Sources */, 79641C1C2DEF1E17004C970D /* RatingRow.swift in Sources */, 79641C282DEF293A004C970D /* UserCardView.swift in Sources */, 0CFCB7402A78002A0009A829 /* ExamplePutService.swift in Sources */, @@ -596,8 +618,10 @@ 0CFCB74E2A7817DC0009A829 /* NftStorage.swift in Sources */, 3FC8C39129D2453B0081F015 /* ExamplePutRequest.swift in Sources */, 5019A4D02DE1E8E0009A5AF8 /* ContentView.swift in Sources */, + 79D9E0422DF03260005D5DB1 /* ShowCollectionButton.swift in Sources */, 79641C232DEF2081004C970D /* RatingViewModel.swift in Sources */, 0C79EE612A76DCD600EE90EA /* NftByIdRequest.swift in Sources */, + 79D9E0472DF03E41005D5DB1 /* UserCollectionView.swift in Sources */, 3F6806D329CBBE9600B4F915 /* NetworkRequest.swift in Sources */, 3F6806D129CBBE6B00B4F915 /* NetworkClient.swift in Sources */, 79D0C7562DE3973200D53241 /* TabBarView.swift in Sources */, diff --git a/FakeNFT/Docs/Statistics.md b/FakeNFT/Docs/Statistics.md index b0f501694b..19fe607bba 100644 --- a/FakeNFT/Docs/Statistics.md +++ b/FakeNFT/Docs/Statistics.md @@ -29,14 +29,14 @@ ## Модуль 2: #### Верстка -- Экран информации о пользователе (аватар, имя, описание) (est: 30 минут; fact: x часов). -- Кнопка перехода на сайт пользователя (est: 30 минут; fact: x часов). -- Кнопка перехода на экран коллекции пользователя (est: 30 минут; fact: x часов). -- Создать WebView (est: 1 час; fact: x часов). +- Экран информации о пользователе (аватар, имя, описание) (est: 30 минут; fact: 30 минут). +- Кнопка перехода на сайт пользователя (est: 30 минут; fact: 20 минут). +- Кнопка перехода на экран коллекции пользователя (est: 30 минут; fact: 30 минут). +- Создать WebView (est: 1 час; fact: 1 час). #### Логика -- Открытие сайта пользователя в WebView (est: 30 минут; fact: x часов). -- Навигация на экран коллекции пользователя (est: 20 минут; fact: x часов). +- Открытие сайта пользователя в WebView (est: 30 минут; fact: 10 минут). +- Навигация на экран коллекции пользователя (est: 20 минут; fact: 15 минут). ## Module 3: diff --git a/FakeNFT/Scenes /Statistics/Common/ShowCollectionButton.swift b/FakeNFT/Scenes /Statistics/Common/ShowCollectionButton.swift new file mode 100644 index 0000000000..c08a0be7d3 --- /dev/null +++ b/FakeNFT/Scenes /Statistics/Common/ShowCollectionButton.swift @@ -0,0 +1,31 @@ +// +// ShowCollectionButton.swift +// FakeNFT +// +// Created by Anastasia on 04.06.2025. +// + +import SwiftUI + +struct ShowCollectionButton: View { + + let nftsCount: Int + + var body: some View { + HStack { + Group { + Text("Коллекция NFT") + Text("(\(nftsCount))") + Spacer() + Image(systemName: "chevron.forward") + } + .font(.bold17) + .foregroundColor(Color.blackDay) + } + .frame(height: StatisticsConstants.buttonHeightLarge) + } +} + +#Preview { + ShowCollectionButton(nftsCount: 122) +} diff --git a/FakeNFT/Scenes /Statistics/Common/ShowWebViewButton.swift b/FakeNFT/Scenes /Statistics/Common/ShowWebViewButton.swift new file mode 100644 index 0000000000..3b5ec8a73c --- /dev/null +++ b/FakeNFT/Scenes /Statistics/Common/ShowWebViewButton.swift @@ -0,0 +1,41 @@ +// +// ShowWebViewButton.swift +// FakeNFT +// +// Created by Anastasia on 04.06.2025. +// + +import SwiftUI + +struct ShowWebViewButton: View { + + let url: String + + var body: some View { + NavigationLink( + destination: WebView(url: URL(string: url)) + .toolbar(.hidden, for: .tabBar) + ) { + Text("Перейти на сайт пользователя") + .font(.regular15) + .foregroundStyle(Color.blackDay) + .frame( + maxWidth: .infinity, + maxHeight: StatisticsConstants.buttonHeightMedium + ) + .background( + RoundedRectangle(cornerRadius: StatisticsConstants.cornerRadiusMedium) + .stroke( + Color.blackDay, + lineWidth: StatisticsConstants.borderLineWidth + ) + ) + } + } +} + +#Preview { + NavigationStack { + ShowWebViewButton(url: "") + } +} diff --git a/FakeNFT/Scenes /Statistics/RatingView/RatingView.swift b/FakeNFT/Scenes /Statistics/RatingView/RatingView.swift index d67a901edf..f94d4b9264 100644 --- a/FakeNFT/Scenes /Statistics/RatingView/RatingView.swift +++ b/FakeNFT/Scenes /Statistics/RatingView/RatingView.swift @@ -69,7 +69,10 @@ struct RatingView: View { VStack(spacing: .zero) { RatingList(isLoading: viewModel.isLoading) { ForEach(viewModel.filteredUsers) { user in - NavigationLink(destination: UserCardView(isTabBarHidden: $isTabBarHidden)) { + NavigationLink(destination: UserCardView( + user: user, + isTabBarHidden: $isTabBarHidden + )) { RatingRow(user: user) } } diff --git a/FakeNFT/Scenes /Statistics/UserCardView/UserCardView.swift b/FakeNFT/Scenes /Statistics/UserCardView/UserCardView.swift index d73a4e3d22..3bf5c6b50b 100644 --- a/FakeNFT/Scenes /Statistics/UserCardView/UserCardView.swift +++ b/FakeNFT/Scenes /Statistics/UserCardView/UserCardView.swift @@ -9,29 +9,83 @@ import SwiftUI struct UserCardView: View { + // MARK: - Properties + @Binding var isTabBarHidden: Bool + @ObservedObject var viewModel: UserCardViewModel + + // MARK: - Initializers - // TODO: 2/3 Statistics + init(user: User, isTabBarHidden: Binding) { + self.viewModel = UserCardViewModel(user: user) + self._isTabBarHidden = isTabBarHidden + } + + // MARK: - Content var body: some View { - VStack { - Text("Hello, World!") + content + .modifier(NavigationBarStyle( + title: nil, + backButtonHidden: false, + filterButtonHidden: true, + isTabBarHidden: $isTabBarHidden, + filterButtonTapHandler: { } + )) + .onAppear { + isTabBarHidden = true + } + } + + // MARK: - View + + private var content: some View { + VStack(alignment: .leading) { + userInfo + Text(viewModel.user.description ?? "") + .font(.footnote) + .foregroundStyle(Color.blackDay) + .padding(.top, StatisticsConstants.topAnchorSmall) + ShowWebViewButton(url: viewModel.user.website) + .padding(.top, StatisticsConstants.topAnchorMedium) + NavigationLink(destination: UserCollectionView()) { + ShowCollectionButton(nftsCount: viewModel.user.nfts.count) + .padding(.top, StatisticsConstants.topAnchorLarge) + } + Spacer() } - .modifier(NavigationBarStyle( - title: nil, - backButtonHidden: false, - filterButtonHidden: true, - isTabBarHidden: $isTabBarHidden, - filterButtonTapHandler: { } - )) - .onAppear { - isTabBarHidden = true + .padding(.top, StatisticsConstants.topAnchorSmall) + .padding(.horizontal) + } + + private var userInfo: some View { + HStack(spacing: .zero) { + UserAvatar( + url: viewModel.user.avatar, + size: StatisticsConstants.avatarSizeLarge + ) + Text(viewModel.user.name) + .font(.bold22) + .foregroundStyle(Color.blackDay) + .padding(.leading) + .frame(maxWidth: .infinity, alignment: .leading) } } } #Preview { + let userTest = User(id: "1", + name: "Joaquin Phoenix", + avatar: "", + description: "Дизайнер из Казани, люблю цифровое искусство и бейглы. В моей коллекции уже 100+ NFT, и еще больше — на моём сайте. Открыт к коллаборациям.", + website: "", + nfts: ["1", "2", "3", "3", "3", "3"], + rating: "1" + ) NavigationView { - UserCardView(isTabBarHidden: .constant(true)) + UserCardView( + user: userTest, + isTabBarHidden: .constant(true) + ) } } diff --git a/FakeNFT/Scenes /Statistics/UserCollectionView/UserCollectionView.swift b/FakeNFT/Scenes /Statistics/UserCollectionView/UserCollectionView.swift new file mode 100644 index 0000000000..2abf89a8aa --- /dev/null +++ b/FakeNFT/Scenes /Statistics/UserCollectionView/UserCollectionView.swift @@ -0,0 +1,32 @@ +// +// UserCollectionView.swift +// FakeNFT +// +// Created by Anastasia on 04.06.2025. +// + +import SwiftUI + +struct UserCollectionView: View { + + // TODO: 3/3 Statistics + + var body: some View { + VStack { + Text("Hello, World!") + } + .modifier(NavigationBarStyle( + title: "Коллекция NFT", + backButtonHidden: false, + filterButtonHidden: true, + filterButtonTapHandler: { } + )) + .toolbar(.hidden, for: .tabBar) + } +} + +#Preview { + NavigationStack { + UserCollectionView() + } +} diff --git a/FakeNFT/StatisticsConstants.swift b/FakeNFT/StatisticsConstants.swift index fa7c8e02e2..c29d1e4c41 100644 --- a/FakeNFT/StatisticsConstants.swift +++ b/FakeNFT/StatisticsConstants.swift @@ -20,4 +20,7 @@ struct StatisticsConstants { static let avatarSizeSmall: CGFloat = 28.0 static let avatarSizeLarge: CGFloat = 70.0 static let ratingLabelWidth: CGFloat = 27.0 + static let buttonHeightMedium: CGFloat = 40.0 + static let buttonHeightLarge: CGFloat = 54.0 + static let borderLineWidth: CGFloat = 1.0 } diff --git a/FakeNFT/UserCardViewModel.swift b/FakeNFT/UserCardViewModel.swift new file mode 100644 index 0000000000..78af3f0478 --- /dev/null +++ b/FakeNFT/UserCardViewModel.swift @@ -0,0 +1,17 @@ +// +// UserCardViewModel.swift +// FakeNFT +// +// Created by Anastasia on 04.06.2025. +// + +import Foundation + +@MainActor +final class UserCardViewModel: ObservableObject { + @Published var user: User + + init(user: User) { + self.user = user + } +}