diff --git a/FakeNFT.xcodeproj/project.pbxproj b/FakeNFT.xcodeproj/project.pbxproj index e9ad82fe6f..e5b5e44415 100644 --- a/FakeNFT.xcodeproj/project.pbxproj +++ b/FakeNFT.xcodeproj/project.pbxproj @@ -9,42 +9,76 @@ /* Begin PBXBuildFile section */ 0C79EE612A76DCD600EE90EA /* NftByIdRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C79EE602A76DCD600EE90EA /* NftByIdRequest.swift */; }; 0C79EE632A76DD1900EE90EA /* RequestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C79EE622A76DD1900EE90EA /* RequestConstants.swift */; }; - 0C79EE662A76DDFF00EE90EA /* NftDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C79EE652A76DDFF00EE90EA /* NftDetailViewController.swift */; }; - 0C79EE682A76DE0900EE90EA /* NftDetailPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C79EE672A76DE0900EE90EA /* NftDetailPresenter.swift */; }; - 0C79EE6A2A76DE1000EE90EA /* NftDetailAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C79EE692A76DE1000EE90EA /* NftDetailAssembly.swift */; }; 0C79EE6C2A76DE2E00EE90EA /* ServicesAssemly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C79EE6B2A76DE2E00EE90EA /* ServicesAssemly.swift */; }; - 0CF2C2DB2A783C1B00FDC837 /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CF2C2DA2A783C1B00FDC837 /* LoadingView.swift */; }; 0CF2C2DD2A783CE600FDC837 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CF2C2DC2A783CE600FDC837 /* ErrorView.swift */; }; - 0CF2C2E12A784C7000FDC837 /* LinePageControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CF2C2E02A784C7000FDC837 /* LinePageControl.swift */; }; 0CFCB7402A78002A0009A829 /* ExamplePutService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CFCB73F2A78002A0009A829 /* ExamplePutService.swift */; }; 0CFCB7422A78013E0009A829 /* Nft.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CFCB7412A78013E0009A829 /* Nft.swift */; }; - 0CFCB7442A7802440009A829 /* NftDetailInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CFCB7432A7802440009A829 /* NftDetailInput.swift */; }; - 0CFCB7462A78064B0009A829 /* NftDetailCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CFCB7452A78064B0009A829 /* NftDetailCellModel.swift */; }; - 0CFCB7492A7808900009A829 /* TestCatalogController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CFCB7482A7808900009A829 /* TestCatalogController.swift */; }; - 0CFCB74B2A780EA80009A829 /* UIView+Constraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CFCB74A2A780EA80009A829 /* UIView+Constraints.swift */; }; 0CFCB74E2A7817DC0009A829 /* NftStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CFCB74D2A7817DC0009A829 /* NftStorage.swift */; }; 3F478ECF29DB474E00F6D39E /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F478ECE29DB474E00F6D39E /* Colors.swift */; }; 3F478ED129DB476500F6D39E /* Fonts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F478ED029DB476500F6D39E /* Fonts.swift */; }; 3F603CCD29DB4A53000C43D7 /* ProgressHUD in Frameworks */ = {isa = PBXBuildFile; productRef = 3F603CCC29DB4A53000C43D7 /* ProgressHUD */; }; 3F603CD029DB4A74000C43D7 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 3F603CCF29DB4A74000C43D7 /* Kingfisher */; }; - 3F68069729CBBAF100B4F915 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F68069629CBBAF100B4F915 /* AppDelegate.swift */; }; - 3F68069929CBBAF100B4F915 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F68069829CBBAF100B4F915 /* SceneDelegate.swift */; }; - 3F68069B29CBBAF100B4F915 /* ProductDetailsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F68069A29CBBAF100B4F915 /* ProductDetailsTableViewController.swift */; }; - 3F68069E29CBBAF100B4F915 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3F68069C29CBBAF100B4F915 /* Main.storyboard */; }; 3F6806A029CBBAF200B4F915 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3F68069F29CBBAF200B4F915 /* Assets.xcassets */; }; - 3F6806A329CBBAF200B4F915 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3F6806A129CBBAF200B4F915 /* LaunchScreen.storyboard */; }; 3F6806AE29CBBAF200B4F915 /* ExampleUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F6806AD29CBBAF200B4F915 /* ExampleUnitTests.swift */; }; 3F6806B829CBBAF200B4F915 /* FakeNFTUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F6806B729CBBAF200B4F915 /* FakeNFTUITests.swift */; }; 3F6806D129CBBE6B00B4F915 /* NetworkClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F6806D029CBBE6B00B4F915 /* NetworkClient.swift */; }; 3F6806D329CBBE9600B4F915 /* NetworkRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F6806D229CBBE9600B4F915 /* NetworkRequest.swift */; }; 3F6806D529CBBEC700B4F915 /* NetworkTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F6806D429CBBEC700B4F915 /* NetworkTask.swift */; }; - 3F6806D729CBC50A00B4F915 /* CellsReusingUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F6806D629CBC50A00B4F915 /* CellsReusingUtils.swift */; }; - 3FC8C38B29D242E90081F015 /* ProductDetailsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FC8C38A29D242E90081F015 /* ProductDetailsTableViewCell.swift */; }; 3FC8C39129D2453B0081F015 /* ExamplePutRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FC8C39029D2453B0081F015 /* ExamplePutRequest.swift */; }; 3FC8C39329D246BA0081F015 /* DateFormatters+Presets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FC8C39229D246BA0081F015 /* DateFormatters+Presets.swift */; }; + 50160EAF2DF84E6700D6A608 /* Profile.md in Resources */ = {isa = PBXBuildFile; fileRef = 50160EAE2DF84E6700D6A608 /* Profile.md */; }; + 5019A4CE2DE1E899009A5AF8 /* FakeNftApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5019A4CD2DE1E899009A5AF8 /* FakeNftApp.swift */; }; + 5019A4D02DE1E8E0009A5AF8 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5019A4CF2DE1E8E0009A5AF8 /* ContentView.swift */; }; + 505B25102DF9797C001FE06C /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505B250F2DF9797C001FE06C /* ProfileView.swift */; }; + 505B25122DFB09D3001FE06C /* ProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505B25112DFB09D3001FE06C /* ProfileViewModel.swift */; }; + 505B25142DFCA6A1001FE06C /* NftsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505B25132DFCA6A1001FE06C /* NftsButton.swift */; }; + 505B25162DFCA987001FE06C /* AboutButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505B25152DFCA987001FE06C /* AboutButton.swift */; }; + 505B25182DFD73E3001FE06C /* FullScreenModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505B25172DFD73E3001FE06C /* FullScreenModalView.swift */; }; + 5086615A2E096A9800C30955 /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508661592E096A9800C30955 /* UserProfile.swift */; }; + 5086615C2E0988AE00C30955 /* UserData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5086615B2E0988A600C30955 /* UserData.swift */; }; + 509263F32E02FACA00D35F08 /* NftRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 509263F22E02FAC900D35F08 /* NftRow.swift */; }; + 509717FF2E04256D00C847F2 /* FavouriteNft.swift in Sources */ = {isa = PBXBuildFile; fileRef = 509717FE2E04256D00C847F2 /* FavouriteNft.swift */; }; + 509EF9782E016D0400E67749 /* UserNft.swift in Sources */ = {isa = PBXBuildFile; fileRef = 509EF9772E016D0400E67749 /* UserNft.swift */; }; + 509EF97A2E016D1C00E67749 /* Favourites.swift in Sources */ = {isa = PBXBuildFile; fileRef = 509EF9792E016D1C00E67749 /* Favourites.swift */; }; + 509EF9822E016F5100E67749 /* AboutUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 509EF9812E016F5100E67749 /* AboutUser.swift */; }; + 509EF9842E019FD800E67749 /* NftCollectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 509EF9832E019FD800E67749 /* NftCollectionViewModel.swift */; }; + 509EF9862E02D92F00E67749 /* UserNftViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 509EF9852E02D92F00E67749 /* UserNftViewModel.swift */; }; + 50E45DA12E094A3800E982D0 /* SFProText-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 50E45DA02E094A3700E982D0 /* SFProText-Regular.ttf */; }; 558E39E72C68CE0A00FB86AC /* NftService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 558E39E62C68CE0900FB86AC /* NftService.swift */; }; - E19CD5AB2A98B56600CA39A5 /* NftImageCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E19CD5AA2A98B56600CA39A5 /* NftImageCollectionViewCell.swift */; }; - E1A1B9DA2AA01CE400C3AFBC /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A1B9D92AA01CE400C3AFBC /* TabBarController.swift */; }; + 793BCC8E2DEEF05000DF1252 /* WebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 793BCC8D2DEEF05000DF1252 /* WebView.swift */; }; + 793BCC902DEEF06800DF1252 /* WebViewRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 793BCC8F2DEEF06800DF1252 /* WebViewRepresentable.swift */; }; + 793BCC922DEEF46600DF1252 /* Statistics.md in Resources */ = {isa = PBXBuildFile; fileRef = 793BCC912DEEF46600DF1252 /* Statistics.md */; }; + 793BCC942DEEF47200DF1252 /* CartAnisimov.md in Resources */ = {isa = PBXBuildFile; fileRef = 793BCC932DEEF47200DF1252 /* CartAnisimov.md */; }; + 793BCC972DEF12F500DF1252 /* RatingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 793BCC962DEF12F500DF1252 /* RatingView.swift */; }; + 793BCC992DEF1B8800DF1252 /* StatisticsConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 793BCC982DEF1B8800DF1252 /* StatisticsConstants.swift */; }; + 79641C182DEF1D33004C970D /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79641C172DEF1D33004C970D /* User.swift */; }; + 79641C1C2DEF1E17004C970D /* RatingRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79641C1B2DEF1E17004C970D /* RatingRow.swift */; }; + 79641C1E2DEF1E63004C970D /* UserAvatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79641C1D2DEF1E63004C970D /* UserAvatar.swift */; }; + 79641C212DEF1ED7004C970D /* RatingList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79641C202DEF1ED7004C970D /* RatingList.swift */; }; + 79641C232DEF2081004C970D /* RatingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79641C222DEF2081004C970D /* RatingViewModel.swift */; }; + 79641C282DEF293A004C970D /* UserCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79641C272DEF293A004C970D /* UserCardView.swift */; }; + 796CB7962DF4269F0028631B /* CollectionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 796CB7952DF4269F0028631B /* CollectionRow.swift */; }; + 796CB7982DF42A620028631B /* UserNFTCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 796CB7972DF42A620028631B /* UserNFTCollection.swift */; }; + 796CB79A2DF42DB80028631B /* UserCollectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 796CB7992DF42DB80028631B /* UserCollectionViewModel.swift */; }; + 796CB79C2DF43B000028631B /* NftInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 796CB79B2DF43B000028631B /* NftInfo.swift */; }; + 796CB79E2DF43B770028631B /* NftInfoService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 796CB79D2DF43B770028631B /* NftInfoService.swift */; }; + 796CB7A02DF43C4E0028631B /* NftInfoRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 796CB79F2DF43C4E0028631B /* NftInfoRequest.swift */; }; + 796CB7A22DF4658D0028631B /* LikesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 796CB7A12DF4658D0028631B /* LikesService.swift */; }; + 796CB7A42DF467060028631B /* LikesRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 796CB7A32DF467060028631B /* LikesRequest.swift */; }; + 796CB7A62DF46D500028631B /* UserLikes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 796CB7A52DF46D500028631B /* UserLikes.swift */; }; + 796CB7A82DF474330028631B /* UserOrdersService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 796CB7A72DF474330028631B /* UserOrdersService.swift */; }; + 796CB7AA2DF474A30028631B /* UserOrders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 796CB7A92DF474A30028631B /* UserOrders.swift */; }; + 796CB7AC2DF474F70028631B /* UserOrdersRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 796CB7AB2DF474F70028631B /* UserOrdersRequest.swift */; }; + 797AAE882DEF30E400745B0D /* UsersService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 797AAE872DEF30E400745B0D /* UsersService.swift */; }; + 797AAE8A2DEF316100745B0D /* UsersRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 797AAE892DEF316100745B0D /* UsersRequest.swift */; }; + 79D0C7532DE396AC00D53241 /* Tab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79D0C7522DE396AC00D53241 /* Tab.swift */; }; + 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 */ @@ -68,29 +102,15 @@ /* Begin PBXFileReference section */ 0C79EE602A76DCD600EE90EA /* NftByIdRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftByIdRequest.swift; sourceTree = ""; }; 0C79EE622A76DD1900EE90EA /* RequestConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestConstants.swift; sourceTree = ""; }; - 0C79EE652A76DDFF00EE90EA /* NftDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftDetailViewController.swift; sourceTree = ""; }; - 0C79EE672A76DE0900EE90EA /* NftDetailPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftDetailPresenter.swift; sourceTree = ""; }; - 0C79EE692A76DE1000EE90EA /* NftDetailAssembly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftDetailAssembly.swift; sourceTree = ""; }; 0C79EE6B2A76DE2E00EE90EA /* ServicesAssemly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesAssemly.swift; sourceTree = ""; }; - 0CF2C2DA2A783C1B00FDC837 /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = ""; }; 0CF2C2DC2A783CE600FDC837 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; - 0CF2C2E02A784C7000FDC837 /* LinePageControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinePageControl.swift; sourceTree = ""; }; 0CFCB73F2A78002A0009A829 /* ExamplePutService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExamplePutService.swift; sourceTree = ""; }; 0CFCB7412A78013E0009A829 /* Nft.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Nft.swift; sourceTree = ""; }; - 0CFCB7432A7802440009A829 /* NftDetailInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftDetailInput.swift; sourceTree = ""; }; - 0CFCB7452A78064B0009A829 /* NftDetailCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftDetailCellModel.swift; sourceTree = ""; }; - 0CFCB7482A7808900009A829 /* TestCatalogController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCatalogController.swift; sourceTree = ""; }; - 0CFCB74A2A780EA80009A829 /* UIView+Constraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Constraints.swift"; sourceTree = ""; }; 0CFCB74D2A7817DC0009A829 /* NftStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftStorage.swift; sourceTree = ""; }; 3F478ECE29DB474E00F6D39E /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; }; 3F478ED029DB476500F6D39E /* Fonts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fonts.swift; sourceTree = ""; }; 3F68069329CBBAF100B4F915 /* FakeNFT.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FakeNFT.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 3F68069629CBBAF100B4F915 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 3F68069829CBBAF100B4F915 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - 3F68069A29CBBAF100B4F915 /* ProductDetailsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductDetailsTableViewController.swift; sourceTree = ""; }; - 3F68069D29CBBAF100B4F915 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 3F68069F29CBBAF200B4F915 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 3F6806A229CBBAF200B4F915 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 3F6806A429CBBAF200B4F915 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3F6806A929CBBAF200B4F915 /* FakeNFTTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FakeNFTTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3F6806AD29CBBAF200B4F915 /* ExampleUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleUnitTests.swift; sourceTree = ""; }; @@ -99,15 +119,61 @@ 3F6806D029CBBE6B00B4F915 /* NetworkClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkClient.swift; sourceTree = ""; }; 3F6806D229CBBE9600B4F915 /* NetworkRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkRequest.swift; sourceTree = ""; }; 3F6806D429CBBEC700B4F915 /* NetworkTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkTask.swift; sourceTree = ""; }; - 3F6806D629CBC50A00B4F915 /* CellsReusingUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellsReusingUtils.swift; sourceTree = ""; }; - 3FC8C38A29D242E90081F015 /* ProductDetailsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductDetailsTableViewCell.swift; sourceTree = ""; }; 3FC8C39029D2453B0081F015 /* ExamplePutRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExamplePutRequest.swift; sourceTree = ""; }; 3FC8C39229D246BA0081F015 /* DateFormatters+Presets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatters+Presets.swift"; sourceTree = ""; }; + 50160EAE2DF84E6700D6A608 /* Profile.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Profile.md; sourceTree = ""; }; + 5019A4CD2DE1E899009A5AF8 /* FakeNftApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeNftApp.swift; sourceTree = ""; }; + 5019A4CF2DE1E8E0009A5AF8 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 505B250F2DF9797C001FE06C /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = ""; }; + 505B25112DFB09D3001FE06C /* ProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ProfileViewModel.swift; path = FakeNFT/ProfileViewModel.swift; sourceTree = SOURCE_ROOT; }; + 505B25132DFCA6A1001FE06C /* NftsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftsButton.swift; sourceTree = ""; }; + 505B25152DFCA987001FE06C /* AboutButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutButton.swift; sourceTree = ""; }; + 505B25172DFD73E3001FE06C /* FullScreenModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenModalView.swift; sourceTree = ""; }; + 508661592E096A9800C30955 /* UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfile.swift; sourceTree = ""; }; + 5086615B2E0988A600C30955 /* UserData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserData.swift; sourceTree = ""; }; + 509263F22E02FAC900D35F08 /* NftRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftRow.swift; sourceTree = ""; }; + 509717FE2E04256D00C847F2 /* FavouriteNft.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavouriteNft.swift; sourceTree = ""; }; + 509EF9772E016D0400E67749 /* UserNft.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNft.swift; sourceTree = ""; }; + 509EF9792E016D1C00E67749 /* Favourites.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Favourites.swift; sourceTree = ""; }; + 509EF9812E016F5100E67749 /* AboutUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutUser.swift; sourceTree = ""; }; + 509EF9832E019FD800E67749 /* NftCollectionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftCollectionViewModel.swift; sourceTree = ""; }; + 509EF9852E02D92F00E67749 /* UserNftViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNftViewModel.swift; sourceTree = ""; }; + 50E45DA02E094A3700E982D0 /* SFProText-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SFProText-Regular.ttf"; sourceTree = ""; }; 558E39E62C68CE0900FB86AC /* NftService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftService.swift; sourceTree = ""; }; - E19CD5AA2A98B56600CA39A5 /* NftImageCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftImageCollectionViewCell.swift; sourceTree = ""; }; - E1A1B9D92AA01CE400C3AFBC /* TabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; - E1CD40D82A96BE7D00BE7FE8 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Main.strings; sourceTree = ""; }; - E1CD40D92A96BE7D00BE7FE8 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/LaunchScreen.strings; sourceTree = ""; }; + 793BCC8D2DEEF05000DF1252 /* WebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebView.swift; sourceTree = ""; }; + 793BCC8F2DEEF06800DF1252 /* WebViewRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewRepresentable.swift; sourceTree = ""; }; + 793BCC912DEEF46600DF1252 /* Statistics.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Statistics.md; sourceTree = ""; }; + 793BCC932DEEF47200DF1252 /* CartAnisimov.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CartAnisimov.md; sourceTree = ""; }; + 793BCC962DEF12F500DF1252 /* RatingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RatingView.swift; sourceTree = ""; }; + 793BCC982DEF1B8800DF1252 /* StatisticsConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StatisticsConstants.swift; path = FakeNFT/StatisticsConstants.swift; sourceTree = SOURCE_ROOT; }; + 79641C172DEF1D33004C970D /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; + 79641C1B2DEF1E17004C970D /* RatingRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RatingRow.swift; sourceTree = ""; }; + 79641C1D2DEF1E63004C970D /* UserAvatar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAvatar.swift; sourceTree = ""; }; + 79641C202DEF1ED7004C970D /* RatingList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RatingList.swift; sourceTree = ""; }; + 79641C222DEF2081004C970D /* RatingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RatingViewModel.swift; path = FakeNFT/RatingViewModel.swift; sourceTree = SOURCE_ROOT; }; + 79641C272DEF293A004C970D /* UserCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserCardView.swift; sourceTree = ""; }; + 796CB7952DF4269F0028631B /* CollectionRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionRow.swift; sourceTree = ""; }; + 796CB7972DF42A620028631B /* UserNFTCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNFTCollection.swift; sourceTree = ""; }; + 796CB7992DF42DB80028631B /* UserCollectionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UserCollectionViewModel.swift; path = FakeNFT/UserCollectionViewModel.swift; sourceTree = SOURCE_ROOT; }; + 796CB79B2DF43B000028631B /* NftInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftInfo.swift; sourceTree = ""; }; + 796CB79D2DF43B770028631B /* NftInfoService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftInfoService.swift; sourceTree = ""; }; + 796CB79F2DF43C4E0028631B /* NftInfoRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NftInfoRequest.swift; sourceTree = ""; }; + 796CB7A12DF4658D0028631B /* LikesService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LikesService.swift; sourceTree = ""; }; + 796CB7A32DF467060028631B /* LikesRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LikesRequest.swift; sourceTree = ""; }; + 796CB7A52DF46D500028631B /* UserLikes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserLikes.swift; sourceTree = ""; }; + 796CB7A72DF474330028631B /* UserOrdersService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserOrdersService.swift; sourceTree = ""; }; + 796CB7A92DF474A30028631B /* UserOrders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserOrders.swift; sourceTree = ""; }; + 796CB7AB2DF474F70028631B /* UserOrdersRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserOrdersRequest.swift; sourceTree = ""; }; + 797AAE872DEF30E400745B0D /* UsersService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsersService.swift; sourceTree = ""; }; + 797AAE892DEF316100745B0D /* UsersRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsersRequest.swift; sourceTree = ""; }; + 79D0C7522DE396AC00D53241 /* Tab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tab.swift; sourceTree = ""; }; + 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 */ @@ -138,22 +204,11 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 0C79EE642A76DDCF00EE90EA /* NftDetails */ = { - isa = PBXGroup; - children = ( - E19CD5A92A98B55900CA39A5 /* Cell */, - 0C79EE652A76DDFF00EE90EA /* NftDetailViewController.swift */, - 0C79EE672A76DE0900EE90EA /* NftDetailPresenter.swift */, - 0C79EE692A76DE1000EE90EA /* NftDetailAssembly.swift */, - 0CFCB7432A7802440009A829 /* NftDetailInput.swift */, - ); - path = NftDetails; - sourceTree = ""; - }; 0CF2C2D92A783C1600FDC837 /* Common */ = { isa = PBXGroup; children = ( - 0CF2C2DF2A784C5600FDC837 /* Views */, + 793BCC8C2DEEF03300DF1252 /* WebView */, + 79D0C75B2DE39F3900D53241 /* LoadingView */, 0CF2C2DE2A784C4C00FDC837 /* Protocols */, ); path = Common; @@ -162,28 +217,11 @@ 0CF2C2DE2A784C4C00FDC837 /* Protocols */ = { isa = PBXGroup; children = ( - 0CF2C2DA2A783C1B00FDC837 /* LoadingView.swift */, 0CF2C2DC2A783CE600FDC837 /* ErrorView.swift */, ); path = Protocols; sourceTree = ""; }; - 0CF2C2DF2A784C5600FDC837 /* Views */ = { - isa = PBXGroup; - children = ( - 0CF2C2E02A784C7000FDC837 /* LinePageControl.swift */, - ); - path = Views; - sourceTree = ""; - }; - 0CFCB7472A7808870009A829 /* Catalog */ = { - isa = PBXGroup; - children = ( - 0CFCB7482A7808900009A829 /* TestCatalogController.swift */, - ); - path = Catalog; - sourceTree = ""; - }; 0CFCB74C2A7817C30009A829 /* MemoryStorage */ = { isa = PBXGroup; children = ( @@ -197,6 +235,7 @@ children = ( 3F478ECE29DB474E00F6D39E /* Colors.swift */, 3F478ED029DB476500F6D39E /* Fonts.swift */, + 50E45DA02E094A3700E982D0 /* SFProText-Regular.ttf */, ); path = DesignSystem; sourceTree = ""; @@ -224,15 +263,17 @@ 3F68069529CBBAF100B4F915 /* FakeNFT */ = { isa = PBXGroup; children = ( - 3F68069629CBBAF100B4F915 /* AppDelegate.swift */, - 3F68069829CBBAF100B4F915 /* SceneDelegate.swift */, E1CD40DA2A96BE9B00BE7FE8 /* Resources */, 3F478ECD29DB473000F6D39E /* DesignSystem */, 3F6806CE29CBBD1B00B4F915 /* Foundation */, 3F6806C929CBBCAF00B4F915 /* Models */, + 79D0C7502DE3940800D53241 /* ViewModels */, 3F6806C729CBBC5B00B4F915 /* Scenes */, 3F6806C629CBBC4E00B4F915 /* Services */, + 79D0C75A2DE39E1100D53241 /* Constants */, + 79D0C74F2DE393F600D53241 /* Docs */, 3F6806A429CBBAF200B4F915 /* Info.plist */, + 5019A4CD2DE1E899009A5AF8 /* FakeNftApp.swift */, ); path = FakeNFT; sourceTree = ""; @@ -259,6 +300,10 @@ 3FC8C38F29D245250081F015 /* Requests */, 0C79EE6B2A76DE2E00EE90EA /* ServicesAssemly.swift */, 558E39E62C68CE0900FB86AC /* NftService.swift */, + 797AAE872DEF30E400745B0D /* UsersService.swift */, + 796CB79D2DF43B770028631B /* NftInfoService.swift */, + 796CB7A12DF4658D0028631B /* LikesService.swift */, + 796CB7A72DF474330028631B /* UserOrdersService.swift */, 0CFCB73F2A78002A0009A829 /* ExamplePutService.swift */, ); path = Services; @@ -267,27 +312,20 @@ 3F6806C729CBBC5B00B4F915 /* Scenes */ = { isa = PBXGroup; children = ( - E1A1B9D82AA01C9700C3AFBC /* TabBarController */, + 505B250D2DF9795B001FE06C /* Profile */, + 5019A4CF2DE1E8E0009A5AF8 /* ContentView.swift */, + 793BCC952DEF12D900DF1252 /* Statistics */, 0CF2C2D92A783C1600FDC837 /* Common */, - 0CFCB7472A7808870009A829 /* Catalog */, - 0C79EE642A76DDCF00EE90EA /* NftDetails */, - 3F6806C829CBBC8100B4F915 /* ProductDetails */, + 79D0C7572DE39C1100D53241 /* ViewModifier */, + 79D0C7542DE396D400D53241 /* TabBar */, ); path = "Scenes "; sourceTree = ""; }; - 3F6806C829CBBC8100B4F915 /* ProductDetails */ = { - isa = PBXGroup; - children = ( - 3F68069A29CBBAF100B4F915 /* ProductDetailsTableViewController.swift */, - 3FC8C38A29D242E90081F015 /* ProductDetailsTableViewCell.swift */, - ); - path = ProductDetails; - sourceTree = ""; - }; 3F6806C929CBBCAF00B4F915 /* Models */ = { isa = PBXGroup; children = ( + 79D0C7512DE3968600D53241 /* Tab */, 3F6806D829CC979D00B4F915 /* Network */, ); path = Models; @@ -298,9 +336,7 @@ children = ( 0CFCB74C2A7817C30009A829 /* MemoryStorage */, 3F6806CF29CBBDB100B4F915 /* NetworkClient */, - 3F6806D629CBC50A00B4F915 /* CellsReusingUtils.swift */, 3FC8C39229D246BA0081F015 /* DateFormatters+Presets.swift */, - 0CFCB74A2A780EA80009A829 /* UIView+Constraints.swift */, ); path = Foundation; sourceTree = ""; @@ -319,6 +355,10 @@ isa = PBXGroup; children = ( 0CFCB7412A78013E0009A829 /* Nft.swift */, + 79641C172DEF1D33004C970D /* User.swift */, + 796CB79B2DF43B000028631B /* NftInfo.swift */, + 796CB7A52DF46D500028631B /* UserLikes.swift */, + 796CB7A92DF474A30028631B /* UserOrders.swift */, ); path = Network; sourceTree = ""; @@ -329,33 +369,173 @@ 0C79EE622A76DD1900EE90EA /* RequestConstants.swift */, 3FC8C39029D2453B0081F015 /* ExamplePutRequest.swift */, 0C79EE602A76DCD600EE90EA /* NftByIdRequest.swift */, + 797AAE892DEF316100745B0D /* UsersRequest.swift */, + 796CB79F2DF43C4E0028631B /* NftInfoRequest.swift */, + 796CB7A32DF467060028631B /* LikesRequest.swift */, + 796CB7AB2DF474F70028631B /* UserOrdersRequest.swift */, ); path = Requests; sourceTree = ""; }; - E19CD5A92A98B55900CA39A5 /* Cell */ = { + 505B250D2DF9795B001FE06C /* Profile */ = { + isa = PBXGroup; + children = ( + 5086615B2E0988A600C30955 /* UserData.swift */, + 505B250F2DF9797C001FE06C /* ProfileView.swift */, + 505B25132DFCA6A1001FE06C /* NftsButton.swift */, + 505B25152DFCA987001FE06C /* AboutButton.swift */, + 505B25172DFD73E3001FE06C /* FullScreenModalView.swift */, + 509EF9772E016D0400E67749 /* UserNft.swift */, + 509EF9792E016D1C00E67749 /* Favourites.swift */, + 505B25112DFB09D3001FE06C /* ProfileViewModel.swift */, + 509EF9812E016F5100E67749 /* AboutUser.swift */, + 509EF9832E019FD800E67749 /* NftCollectionViewModel.swift */, + 509EF9852E02D92F00E67749 /* UserNftViewModel.swift */, + 509263F22E02FAC900D35F08 /* NftRow.swift */, + 509717FE2E04256D00C847F2 /* FavouriteNft.swift */, + 508661592E096A9800C30955 /* UserProfile.swift */, + ); + path = Profile; + sourceTree = ""; + }; + 793BCC8C2DEEF03300DF1252 /* WebView */ = { + isa = PBXGroup; + children = ( + 793BCC8D2DEEF05000DF1252 /* WebView.swift */, + 793BCC8F2DEEF06800DF1252 /* WebViewRepresentable.swift */, + ); + path = WebView; + sourceTree = ""; + }; + 793BCC952DEF12D900DF1252 /* Statistics */ = { + isa = PBXGroup; + children = ( + 793BCC9A2DEF1BC500DF1252 /* RatingView */, + 79641C262DEF292A004C970D /* UserCardView */, + 79D9E0452DF03E34005D5DB1 /* UserCollectionView */, + 79641C192DEF1D63004C970D /* Common */, + ); + path = Statistics; + sourceTree = ""; + }; + 793BCC9A2DEF1BC500DF1252 /* RatingView */ = { + isa = PBXGroup; + children = ( + 793BCC962DEF12F500DF1252 /* RatingView.swift */, + ); + path = RatingView; + sourceTree = ""; + }; + 79641C192DEF1D63004C970D /* Common */ = { + isa = PBXGroup; + children = ( + 79641C1F2DEF1ECB004C970D /* List */, + 79641C1A2DEF1DF4004C970D /* Row */, + 79641C1D2DEF1E63004C970D /* UserAvatar.swift */, + 79D9E03F2DF0316F005D5DB1 /* ShowWebViewButton.swift */, + 79D9E0412DF03260005D5DB1 /* ShowCollectionButton.swift */, + ); + path = Common; + sourceTree = ""; + }; + 79641C1A2DEF1DF4004C970D /* Row */ = { + isa = PBXGroup; + children = ( + 79641C1B2DEF1E17004C970D /* RatingRow.swift */, + 796CB7952DF4269F0028631B /* CollectionRow.swift */, + ); + path = Row; + sourceTree = ""; + }; + 79641C1F2DEF1ECB004C970D /* List */ = { + isa = PBXGroup; + children = ( + 79641C202DEF1ED7004C970D /* RatingList.swift */, + 796CB7972DF42A620028631B /* UserNFTCollection.swift */, + ); + path = List; + sourceTree = ""; + }; + 79641C262DEF292A004C970D /* UserCardView */ = { + isa = PBXGroup; + children = ( + 79641C272DEF293A004C970D /* UserCardView.swift */, + ); + path = UserCardView; + sourceTree = ""; + }; + 79D0C74F2DE393F600D53241 /* Docs */ = { + isa = PBXGroup; + children = ( + 793BCC912DEEF46600DF1252 /* Statistics.md */, + 793BCC932DEEF47200DF1252 /* CartAnisimov.md */, + 50160EAE2DF84E6700D6A608 /* Profile.md */, + ); + path = Docs; + sourceTree = ""; + }; + 79D0C7502DE3940800D53241 /* ViewModels */ = { + isa = PBXGroup; + children = ( + 79641C222DEF2081004C970D /* RatingViewModel.swift */, + 79D9E0432DF03392005D5DB1 /* UserCardViewModel.swift */, + 796CB7992DF42DB80028631B /* UserCollectionViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + 79D0C7512DE3968600D53241 /* Tab */ = { + isa = PBXGroup; + children = ( + 79D0C7522DE396AC00D53241 /* Tab.swift */, + ); + path = Tab; + sourceTree = ""; + }; + 79D0C7542DE396D400D53241 /* TabBar */ = { isa = PBXGroup; children = ( - 0CFCB7452A78064B0009A829 /* NftDetailCellModel.swift */, - E19CD5AA2A98B56600CA39A5 /* NftImageCollectionViewCell.swift */, + 79D0C7552DE3973200D53241 /* TabBarView.swift */, ); - path = Cell; + path = TabBar; sourceTree = ""; }; - E1A1B9D82AA01C9700C3AFBC /* TabBarController */ = { + 79D0C7572DE39C1100D53241 /* ViewModifier */ = { isa = PBXGroup; children = ( - E1A1B9D92AA01CE400C3AFBC /* TabBarController.swift */, + 79D0C7582DE39C3800D53241 /* NavigationBarStyle.swift */, ); - path = TabBarController; + path = ViewModifier; + sourceTree = ""; + }; + 79D0C75A2DE39E1100D53241 /* Constants */ = { + isa = PBXGroup; + children = ( + 793BCC982DEF1B8800DF1252 /* StatisticsConstants.swift */, + ); + path = Constants; + sourceTree = ""; + }; + 79D0C75B2DE39F3900D53241 /* LoadingView */ = { + isa = PBXGroup; + children = ( + 79D0C75C2DE39F5900D53241 /* LoadingView.swift */, + ); + path = LoadingView; + sourceTree = ""; + }; + 79D9E0452DF03E34005D5DB1 /* UserCollectionView */ = { + isa = PBXGroup; + children = ( + 79D9E0462DF03E41005D5DB1 /* UserCollectionView.swift */, + ); + path = UserCollectionView; sourceTree = ""; }; E1CD40DA2A96BE9B00BE7FE8 /* Resources */ = { isa = PBXGroup; children = ( - 3F68069C29CBBAF100B4F915 /* Main.storyboard */, 3F68069F29CBBAF200B4F915 /* Assets.xcassets */, - 3F6806A129CBBAF200B4F915 /* LaunchScreen.storyboard */, E1CD40DB2A96BECC00BE7FE8 /* Localizable.strings */, ); path = Resources; @@ -429,7 +609,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1420; - LastUpgradeCheck = 1420; + LastUpgradeCheck = 1630; TargetAttributes = { 3F68069229CBBAF100B4F915 = { CreatedOnToolsVersion = 14.2; @@ -474,10 +654,12 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3F6806A329CBBAF200B4F915 /* LaunchScreen.storyboard in Resources */, + 50160EAF2DF84E6700D6A608 /* Profile.md in Resources */, + 50E45DA12E094A3800E982D0 /* SFProText-Regular.ttf in Resources */, E1CD40DC2A96BECC00BE7FE8 /* Localizable.strings in Resources */, 3F6806A029CBBAF200B4F915 /* Assets.xcassets in Resources */, - 3F68069E29CBBAF100B4F915 /* Main.storyboard in Resources */, + 793BCC942DEEF47200DF1252 /* CartAnisimov.md in Resources */, + 793BCC922DEEF46600DF1252 /* Statistics.md in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -502,37 +684,69 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 3FC8C38B29D242E90081F015 /* ProductDetailsTableViewCell.swift in Sources */, + 79641C182DEF1D33004C970D /* User.swift in Sources */, + 796CB7A62DF46D500028631B /* UserLikes.swift in Sources */, 3F478ECF29DB474E00F6D39E /* Colors.swift in Sources */, - 0C79EE662A76DDFF00EE90EA /* NftDetailViewController.swift in Sources */, - E1A1B9DA2AA01CE400C3AFBC /* TabBarController.swift in Sources */, + 505B25182DFD73E3001FE06C /* FullScreenModalView.swift in Sources */, 3F478ED129DB476500F6D39E /* Fonts.swift in Sources */, - 0C79EE6A2A76DE1000EE90EA /* NftDetailAssembly.swift in Sources */, + 793BCC992DEF1B8800DF1252 /* StatisticsConstants.swift in Sources */, 3FC8C39329D246BA0081F015 /* DateFormatters+Presets.swift in Sources */, + 509263F32E02FACA00D35F08 /* NftRow.swift in Sources */, + 79641C1E2DEF1E63004C970D /* UserAvatar.swift in Sources */, + 796CB79E2DF43B770028631B /* NftInfoService.swift in Sources */, + 793BCC972DEF12F500DF1252 /* RatingView.swift in Sources */, + 505B25162DFCA987001FE06C /* AboutButton.swift in Sources */, + 796CB7A02DF43C4E0028631B /* NftInfoRequest.swift in Sources */, 0CFCB7422A78013E0009A829 /* Nft.swift in Sources */, + 796CB7962DF4269F0028631B /* CollectionRow.swift in Sources */, + 79D0C7592DE39C3800D53241 /* NavigationBarStyle.swift in Sources */, + 509EF9842E019FD800E67749 /* NftCollectionViewModel.swift in Sources */, + 79D0C75D2DE39F5900D53241 /* LoadingView.swift in Sources */, + 797AAE8A2DEF316100745B0D /* UsersRequest.swift in Sources */, 0CF2C2DD2A783CE600FDC837 /* ErrorView.swift in Sources */, - 0C79EE682A76DE0900EE90EA /* NftDetailPresenter.swift in Sources */, - 3F68069B29CBBAF100B4F915 /* ProductDetailsTableViewController.swift in Sources */, + 509EF9822E016F5100E67749 /* AboutUser.swift in Sources */, + 79D9E0402DF0316F005D5DB1 /* ShowWebViewButton.swift in Sources */, + 79641C212DEF1ED7004C970D /* RatingList.swift in Sources */, + 796CB79A2DF42DB80028631B /* UserCollectionViewModel.swift in Sources */, + 796CB7AC2DF474F70028631B /* UserOrdersRequest.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 */, + 797AAE882DEF30E400745B0D /* UsersService.swift in Sources */, + 796CB7AA2DF474A30028631B /* UserOrders.swift in Sources */, 3F6806D529CBBEC700B4F915 /* NetworkTask.swift in Sources */, - 0CF2C2DB2A783C1B00FDC837 /* LoadingView.swift in Sources */, 558E39E72C68CE0A00FB86AC /* NftService.swift in Sources */, - 3F68069729CBBAF100B4F915 /* AppDelegate.swift in Sources */, - 3F68069929CBBAF100B4F915 /* SceneDelegate.swift in Sources */, + 796CB7A42DF467060028631B /* LikesRequest.swift in Sources */, + 505B25122DFB09D3001FE06C /* ProfileViewModel.swift in Sources */, + 5086615C2E0988AE00C30955 /* UserData.swift in Sources */, 0C79EE6C2A76DE2E00EE90EA /* ServicesAssemly.swift in Sources */, + 793BCC902DEEF06800DF1252 /* WebViewRepresentable.swift in Sources */, + 505B25102DF9797C001FE06C /* ProfileView.swift in Sources */, + 796CB7A82DF474330028631B /* UserOrdersService.swift in Sources */, + 79D0C7532DE396AC00D53241 /* Tab.swift in Sources */, + 509EF9862E02D92F00E67749 /* UserNftViewModel.swift in Sources */, 0CFCB74E2A7817DC0009A829 /* NftStorage.swift in Sources */, 3FC8C39129D2453B0081F015 /* ExamplePutRequest.swift in Sources */, - 0CFCB7462A78064B0009A829 /* NftDetailCellModel.swift in Sources */, - 0CFCB74B2A780EA80009A829 /* UIView+Constraints.swift in Sources */, - 3F6806D729CBC50A00B4F915 /* CellsReusingUtils.swift in Sources */, + 5019A4D02DE1E8E0009A5AF8 /* ContentView.swift in Sources */, + 796CB79C2DF43B000028631B /* NftInfo.swift in Sources */, + 5086615A2E096A9800C30955 /* UserProfile.swift in Sources */, + 79D9E0422DF03260005D5DB1 /* ShowCollectionButton.swift in Sources */, + 509717FF2E04256D00C847F2 /* FavouriteNft.swift in Sources */, + 509EF97A2E016D1C00E67749 /* Favourites.swift in Sources */, + 796CB7A22DF4658D0028631B /* LikesService.swift in Sources */, + 79641C232DEF2081004C970D /* RatingViewModel.swift in Sources */, + 796CB7982DF42A620028631B /* UserNFTCollection.swift in Sources */, + 509EF9782E016D0400E67749 /* UserNft.swift in Sources */, 0C79EE612A76DCD600EE90EA /* NftByIdRequest.swift in Sources */, - E19CD5AB2A98B56600CA39A5 /* NftImageCollectionViewCell.swift in Sources */, - 0CF2C2E12A784C7000FDC837 /* LinePageControl.swift in Sources */, + 79D9E0472DF03E41005D5DB1 /* UserCollectionView.swift in Sources */, + 505B25142DFCA6A1001FE06C /* NftsButton.swift in Sources */, 3F6806D329CBBE9600B4F915 /* NetworkRequest.swift in Sources */, - 0CFCB7492A7808900009A829 /* TestCatalogController.swift in Sources */, 3F6806D129CBBE6B00B4F915 /* NetworkClient.swift in Sources */, - 0CFCB7442A7802440009A829 /* NftDetailInput.swift in Sources */, + 79D0C7562DE3973200D53241 /* TabBarView.swift in Sources */, 0C79EE632A76DD1900EE90EA /* RequestConstants.swift in Sources */, + 793BCC8E2DEEF05000DF1252 /* WebView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -567,27 +781,6 @@ }; /* End PBXTargetDependency section */ -/* Begin PBXVariantGroup section */ - 3F68069C29CBBAF100B4F915 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 3F68069D29CBBAF100B4F915 /* Base */, - E1CD40D82A96BE7D00BE7FE8 /* ru */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 3F6806A129CBBAF200B4F915 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 3F6806A229CBBAF200B4F915 /* Base */, - E1CD40D92A96BE7D00BE7FE8 /* ru */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - /* Begin XCBuildConfiguration section */ 3F6806BB29CBBAF200B4F915 /* Debug */ = { isa = XCBuildConfiguration; @@ -626,6 +819,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -640,7 +834,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -687,6 +881,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -695,7 +890,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -712,22 +907,24 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 6H2GQX47PU; + DEVELOPMENT_TEAM = A89A5GBX7T; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = FakeNFT/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen.storyboard; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.practicum.FakeNFT; + PRODUCT_BUNDLE_IDENTIFIER = com.practicum.FakeNFTMax; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -741,22 +938,24 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 6H2GQX47PU; + DEVELOPMENT_TEAM = A89A5GBX7T; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = FakeNFT/Info.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen.storyboard; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.practicum.FakeNFT; + PRODUCT_BUNDLE_IDENTIFIER = com.practicum.FakeNFTMax; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -766,7 +965,6 @@ 3F6806C129CBBAF200B4F915 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -785,7 +983,6 @@ 3F6806C229CBBAF200B4F915 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -804,7 +1001,6 @@ 3F6806C429CBBAF200B4F915 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; @@ -821,7 +1017,6 @@ 3F6806C529CBBAF200B4F915 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; diff --git a/FakeNFT.xcodeproj/xcshareddata/xcschemes/FakeNFT.xcscheme b/FakeNFT.xcodeproj/xcshareddata/xcschemes/FakeNFT.xcscheme index d682c47de1..f107b1a687 100644 --- a/FakeNFT.xcodeproj/xcshareddata/xcschemes/FakeNFT.xcscheme +++ b/FakeNFT.xcodeproj/xcshareddata/xcschemes/FakeNFT.xcscheme @@ -1,6 +1,6 @@ Bool { - return true - } - - // MARK: UISceneSession Lifecycle - - func application( - _: UIApplication, - configurationForConnecting connectingSceneSession: UISceneSession, - options _: UIScene.ConnectionOptions - ) -> UISceneConfiguration { - return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) - } -} diff --git a/FakeNFT/DesignSystem/Colors.swift b/FakeNFT/DesignSystem/Colors.swift index 8f95ac024d..7abdc93916 100644 --- a/FakeNFT/DesignSystem/Colors.swift +++ b/FakeNFT/DesignSystem/Colors.swift @@ -1,67 +1,55 @@ -import UIKit - -extension UIColor { - // Creates color from a hex string - convenience init(hexString: String) { - let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) - var int = UInt64() +import SwiftUI + +extension Color { + + // MARK: - Day/Night Theme + static let blackDay = Color("blackDay") + static let whiteDay = Color("whiteDay") + static let lightGrayDay = Color("lightGrayDay") + + // MARK: - Universal Colors + static let yaGrayUniversal = Color(hex: "#625C5C") + static let yaRedUniversal = Color(hex: "#F56B6C") + static let yaBackgroundUniversal = Color(hex: "#1A1B2280") + static let yaGreenUniversal = Color(hex: "#1C9F00") + static let yaBlueUniversal = Color(hex: "#0A84FF") + static let yaBlackUniversal = Color(hex: "#1A1B22") + static let yaWhiteUniversal = Color(hex: "#FFFFFF") + static let yaYellowUniversal = Color(hex: "#FEEF0D") + + // MARK: - Init with HEX + init(hex: String) { + let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) + var int: UInt64 = 0 Scanner(string: hex).scanHexInt64(&int) - let alpha, red, green, blue: UInt64 + + let a, r, g, b: UInt64 switch hex.count { case 3: // RGB (12-bit) - (alpha, red, green, blue) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) + (a, r, g, b) = (255, + (int >> 8) * 17, + (int >> 4 & 0xF) * 17, + (int & 0xF) * 17) case 6: // RGB (24-bit) - (alpha, red, green, blue) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) + (a, r, g, b) = (255, + int >> 16, + int >> 8 & 0xFF, + int & 0xFF) case 8: // ARGB (32-bit) - (alpha, red, green, blue) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) + (a, r, g, b) = (int >> 24, + int >> 16 & 0xFF, + int >> 8 & 0xFF, + int & 0xFF) default: - (alpha, red, green, blue) = (255, 0, 0, 0) + (a, r, g, b) = (255, 0, 0, 0) } + self.init( - red: CGFloat(red) / 255, - green: CGFloat(green) / 255, - blue: CGFloat(blue) / 255, - alpha: CGFloat(alpha) / 255 + .sRGB, + red: Double(r) / 255, + green: Double(g) / 255, + blue: Double(b) / 255, + opacity: Double(a) / 255 ) } - - // Ниже приведены примеры цветов, настоящие цвета надо взять из фигмы - - // Primary Colors - static let primary = UIColor(red: 0 / 255, green: 122 / 255, blue: 255 / 255, alpha: 1.0) - - // Secondary Colors - static let secondary = UIColor(red: 255 / 255, green: 193 / 255, blue: 7 / 255, alpha: 1.0) - - // Background Colors - static let background = UIColor.white - - // Text Colors - static let textPrimary = UIColor.black - static let textSecondary = UIColor.gray - static let textOnPrimary = UIColor.white - static let textOnSecondary = UIColor.black - - private static let yaBlackLight = UIColor(hexString: "1A1B22") - private static let yaBlackDark = UIColor.white - private static let yaLightGrayLight = UIColor(hexString: "#F7F7F8") - private static let yaLightGrayDark = UIColor(hexString: "#2C2C2E") - - static let segmentActive = UIColor { traits in - return traits.userInterfaceStyle == .dark - ? .yaBlackDark - : .yaBlackLight - } - - static let segmentInactive = UIColor { traits in - return traits.userInterfaceStyle == .dark - ? .yaLightGrayDark - : .yaLightGrayLight - } - - static let closeButton = UIColor { traits in - return traits.userInterfaceStyle == .dark - ? .yaBlackDark - : .yaBlackLight - } } diff --git a/FakeNFT/DesignSystem/Fonts.swift b/FakeNFT/DesignSystem/Fonts.swift index d8e1f6657d..1cf327e9c3 100644 --- a/FakeNFT/DesignSystem/Fonts.swift +++ b/FakeNFT/DesignSystem/Fonts.swift @@ -1,19 +1,10 @@ -import UIKit - -extension UIFont { - // Ниже приведены примеры шрифтов, настоящие шрифты надо взять из фигмы - - // Headline Fonts - static var headline1 = UIFont.systemFont(ofSize: 34, weight: .bold) - static var headline2 = UIFont.systemFont(ofSize: 28, weight: .bold) - static var headline3 = UIFont.systemFont(ofSize: 22, weight: .bold) - static var headline4 = UIFont.systemFont(ofSize: 20, weight: .bold) - - // Body Fonts - static var bodyRegular = UIFont.systemFont(ofSize: 17, weight: .regular) - static var bodyBold = UIFont.systemFont(ofSize: 17, weight: .bold) - - // Caption Fonts - static var caption1 = UIFont.systemFont(ofSize: 15, weight: .regular) - static var caption2 = UIFont.systemFont(ofSize: 13, weight: .regular) +import SwiftUI + +extension Font { + static let regular13 = Font.system(size: 13, weight: .regular) + static let regular15 = Font.system(size: 15, weight: .regular) + static let regular17 = Font.system(size: 17, weight: .regular) + static let medium10 = Font.system(size: 10, weight: .medium) + static let bold17 = Font.system(size: 17, weight: .bold) + static let bold22 = Font.system(size: 22, weight: .bold) } diff --git a/FakeNFT/DesignSystem/SFProText-Regular.ttf b/FakeNFT/DesignSystem/SFProText-Regular.ttf new file mode 100644 index 0000000000..b63615e93d Binary files /dev/null and b/FakeNFT/DesignSystem/SFProText-Regular.ttf differ diff --git a/FakeNFT/Docs/CartAnisimov.md b/FakeNFT/Docs/CartAnisimov.md new file mode 100644 index 0000000000..47edd948f2 --- /dev/null +++ b/FakeNFT/Docs/CartAnisimov.md @@ -0,0 +1,44 @@ +Анисимов Максим Сергеевич +Когорта: 22 +Группа: 2 +Эпик: Корзина + +Ссылка на доску: [ссылка](https://github.com/users/Maxoscaro/projects/1/views/4) + + +# Декомпозиция эпика Корзина + +## Модуль 1: + +#### Верстка +- Основной экран корзины и состояние пустой корзины (est: 1 час; fact: x часов). +- Ячейка с картинкой и ценой (est: 1 час; fact: x часов). +- Верстка кнопки покупки с ценой - (est: 1 час; fact: х часов) + +#### Логика +- Сортировка NFT (по названию, рейтингу, цене) (est: 1 час; fact: x часов). +- Сохранение выбранного способа сортировки (est: 1 час; fact: x часов). +- Заполнение ячеек данными из сервиса NFTService - (est: 2 часа; fact: х часов) +- Навигация на экран выбора способа оплаты (est: 40 минут; fact: x часов). + +## Модуль 2: + +#### Верстка +- Экран выбора способа оплаты (est: 1 час; fact: x часов). +- Ячейка с картинкой и криптовалютами (est: 50 минут; fact: x часов). +- Кнопка оплатить (est: 30 минут; fact: x часов). +- Ссылка на пользовательское соглашение (est: 30 минут; fact: x часов). + +#### Логика +- Открытие сайта пользователя в WebView (est: 30 минут; fact: x часов). +- Навигация на экран успешной покупки (est: 20 минут; fact: x часов). + +## Module 3: + +#### Верстка +- Экран удаления товара из корзины (est: 50 минут; fact: x часов). +- Экран успешной покупки (est: 1 час; fact: x часов). + +#### Логика +- Логика покупки: удаление из корзины и добавление в аккаунт пользователя (est: 2 часа; fact: x часов). +- Обработка возврата в каталог (est: 1 час; fact: x часов). diff --git a/FakeNFT/Docs/Profile.md b/FakeNFT/Docs/Profile.md new file mode 100644 index 0000000000..c8670fcae6 --- /dev/null +++ b/FakeNFT/Docs/Profile.md @@ -0,0 +1,44 @@ +Егиазаров Вадим Каренович + +Когорта: 22 + +Группа: 2 + +Эпик: Профиль +Ссылка на доску: [ссылка](https://github.com/users/Maxoscaro/projects/1/views/6) + + +# Декомпозиция эпика Профиль + +## Модуль 1: + +#### Верстка +- Экран профиля и редактирование экрана профиля (est: 1 час; fact: 5 часов). +- Фото профиля (est: 1 час; fact: 2 часа). +- Кнопки для перехода на страницы "Мои NFT", "Избранные NFT", "О разработчике"- (est: 1 час; fact: 2 часа) + +#### Логика +- Редактирование профиля (имя пользователя, описание, сайт и ссылка на изображение) (est: 1 час; fact: 6 часов). + + +## Модуль 2: + +#### Верстка +- Экраны Мои NFT, Фильтрация NFT, Нет NFT (est: 1 час; fact: x часов). +- Ячейки с добавленными NFT и их ценой (est: 50 минут; fact: x часов). + + + +#### Логика +- Сортировка по (цене, рейтингу, названию) (est: 30 минут; fact: x часов). +- Сохранение выбранного способа сортировки (est: 20 минут; fact: x часов). + +## Module 3: + +#### Верстка +- Экраны Favoriets, No favoriets (est: 50 минут; fact: x часов). +- Экран успешной покупки (est: 1 час; fact: x часов). + +#### Логика +- Добавление NFT в избранное при нажатии на сердечко (est: 1 часа; fact: x часов). +- Удаление NFT из избранного при нажатии на сердечко (est: 1 часа; fact: x часов). diff --git a/FakeNFT/Docs/Statistics.md b/FakeNFT/Docs/Statistics.md new file mode 100644 index 0000000000..1210404aae --- /dev/null +++ b/FakeNFT/Docs/Statistics.md @@ -0,0 +1,55 @@ +Кирпинева Анастасия Валерьевна + +Когорта: 22 + +Группа: 2 + +Эпик: Статистика + +Ссылка на доску: [ссылка](https://github.com/users/Maxoscaro/projects/1/views/3) + + +# Декомпозиция эпика Статистика + +## Модуль 1: + +#### Верстка +- Экран рейтинга (кнопка сортировки, список пользователей) (est: 30 минут; fact: 30 минут). +- Ячейка для списка пользователей на экране рейтинга (est: 1 час; fact: 30 минут). + +#### Логика +- Сортировка списка пользователей (по имени, рейтингу) (est: 1 час; fact: 20 минут). +- Сохранение выбранного способа сортировки (est: 1 час; fact: 25 минут). +- Навигация на экран информации о пользователе (est: 20 минут; fact: 15 минут). + +#### Работа с сетью +- Создать запрос на получение списка пользователей (est: 2 часа; fact: 1 час). +- Подключить запрос (est: 1 час; fact: 30 минут). + +## Модуль 2: + +#### Верстка +- Экран информации о пользователе (аватар, имя, описание) (est: 30 минут; fact: 30 минут). +- Кнопка перехода на сайт пользователя (est: 30 минут; fact: 20 минут). +- Кнопка перехода на экран коллекции пользователя (est: 30 минут; fact: 30 минут). +- Создать WebView (est: 1 час; fact: 1 час). + +#### Логика +- Открытие сайта пользователя в WebView (est: 30 минут; fact: 10 минут). +- Навигация на экран коллекции пользователя (est: 20 минут; fact: 15 минут). + +## Module 3: + +#### Верстка +- Экран коллекции пользователя (est: 20 минут; fact: 20 минут). +- Ячейка с информацией об NFT (est: 1 час; fact: 50 минут). + +#### Логика +- Обработка нажатия на сердечко (est: 1 час; fact: 1 час). +- Обработка нажатия на кнопку корзины (добавить/удалить NFT) (est: 1 час; fact: 1 час). + +#### Работа с сетью +- Загрузка и отображение изображений NFT (est: 1 час; fact: 30 минут). +- Запрос на добавление/удаление лайка (est: 1 час; fact: 2 часа). +- Запрос на добавление NFT в корзину (est: 1 час; fact: 30 минут). +- Запрос на удаление NFT из корзины (est: 1 час; fact: 30 минут). diff --git a/FakeNFT/FakeNftApp.swift b/FakeNFT/FakeNftApp.swift new file mode 100644 index 0000000000..b47c6f0dff --- /dev/null +++ b/FakeNFT/FakeNftApp.swift @@ -0,0 +1,22 @@ +// +// FakeNftApp.swift +// FakeNFT +// +// Created by Max on 24.05.2025. +// + +import SwiftUI + +@main +struct FakeNftApp: App { + + @StateObject var service = ServicesAssembly() + + var body: some Scene { + WindowGroup { + ContentView() + .environmentObject(service) + } + } +} + diff --git a/FakeNFT/Info.plist b/FakeNFT/Info.plist index dd3c9afdae..bca2780ea9 100644 --- a/FakeNFT/Info.plist +++ b/FakeNFT/Info.plist @@ -2,6 +2,10 @@ + UIAppFonts + + SFProText-Regular + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes @@ -13,10 +17,6 @@ UISceneConfigurationName Default Configuration - UISceneDelegateClassName - $(PRODUCT_MODULE_NAME).SceneDelegate - UISceneStoryboardFile - Main diff --git a/FakeNFT/Models/Network/Nft.swift b/FakeNFT/Models/Network/Nft.swift index 59e761a821..a3b08ce7a6 100644 --- a/FakeNFT/Models/Network/Nft.swift +++ b/FakeNFT/Models/Network/Nft.swift @@ -1,6 +1,11 @@ -import Foundation +import SwiftUI -struct Nft: Decodable { +struct Nft: Identifiable, Hashable, Codable { let id: String - let images: [URL] + let name: String + let images: [String] + let description: String + let rating: Int + let price: Double + let author: String } diff --git a/FakeNFT/Models/Network/NftInfo.swift b/FakeNFT/Models/Network/NftInfo.swift new file mode 100644 index 0000000000..69ec6d5390 --- /dev/null +++ b/FakeNFT/Models/Network/NftInfo.swift @@ -0,0 +1,16 @@ +// +// NftInfo.swift +// FakeNFT +// +// Created by Anastasia on 07.06.2025. +// + +import Foundation + +struct NftInfo: Decodable, Hashable { + let id: String + let name: String + let images: [String] + let rating: Int + let price: Double +} diff --git a/FakeNFT/Models/Network/User.swift b/FakeNFT/Models/Network/User.swift new file mode 100644 index 0000000000..44e25e2e2a --- /dev/null +++ b/FakeNFT/Models/Network/User.swift @@ -0,0 +1,18 @@ +// +// User.swift +// FakeNFT +// +// Created by Anastasia on 03.06.2025. +// + +import Foundation + +struct User: Decodable, Identifiable { + let id: String + let name: String + let avatar: String + let description: String? + let website: String + let nfts: [String] + let rating: String +} diff --git a/FakeNFT/Models/Network/UserLikes.swift b/FakeNFT/Models/Network/UserLikes.swift new file mode 100644 index 0000000000..4fcea953fe --- /dev/null +++ b/FakeNFT/Models/Network/UserLikes.swift @@ -0,0 +1,12 @@ +// +// UserLikes.swift +// FakeNFT +// +// Created by Anastasia on 07.06.2025. +// + +import Foundation + +struct UserLikes: Decodable { + let likes: [String] +} diff --git a/FakeNFT/Models/Network/UserOrders.swift b/FakeNFT/Models/Network/UserOrders.swift new file mode 100644 index 0000000000..b9ec939d5f --- /dev/null +++ b/FakeNFT/Models/Network/UserOrders.swift @@ -0,0 +1,12 @@ +// +// UserOrders.swift +// FakeNFT +// +// Created by Anastasia on 07.06.2025. +// + +import Foundation + +struct UserOrders: Decodable { + let nfts: [String] +} diff --git a/FakeNFT/Models/Tab/Tab.swift b/FakeNFT/Models/Tab/Tab.swift new file mode 100644 index 0000000000..28df738582 --- /dev/null +++ b/FakeNFT/Models/Tab/Tab.swift @@ -0,0 +1,41 @@ +// +// Tab.swift +// FakeNFT +// +// Created by Anastasia on 25.05.2025. +// + +import Foundation + +enum Tab: String, CaseIterable { + case profile + case catalog + case cart + case statistics + + var title: String { + switch self { + case .profile: + return "Профиль" + case .catalog: + return "Каталог" + case .cart: + return "Корзина" + case .statistics: + return "Статистика" + } + } + + var imageName: String { + switch self { + case .profile: + return "profile" + case .catalog: + return "catalog" + case .cart: + return "cart" + case .statistics: + return "statistics" + } + } +} diff --git a/FakeNFT/ProfileViewModel.swift b/FakeNFT/ProfileViewModel.swift new file mode 100644 index 0000000000..af4e419855 --- /dev/null +++ b/FakeNFT/ProfileViewModel.swift @@ -0,0 +1,48 @@ +// +// ProfileViewModel.swift +// FakeNFT +// +// Created by Mac on 12.06.2025. +// + +import Foundation +import SwiftUI +@MainActor + +final class ProfileViewModel: ObservableObject { + @AppStorage("userData") private var storedData: Data = Data() + + @Published var name: String = "Joaquin Phoenix" + + @Published var description: String = "Дизайнер из Казани, люблю цифровое искусство и бейглы. В моей коллекции уже 100+ NFT, 
и еще больше — на моём сайте. Открыт к коллаборациям." + + @Published var link: String = "link" + + @Published var imageData: Data? + + init() { + load() + } + + func load() { + if let loaded = try? JSONDecoder().decode(UserData.self, from: storedData) { + self.name = loaded.name + self.description = loaded.description + self.link = loaded.link + self.imageData = loaded.imageData + } + } + + func save(name: String, description: String, link: String, imageData: Data?) { + let newData = UserData(name: name, description: description, link: link, imageData: imageData) + if let encoded = try? JSONEncoder().encode(newData) { + storedData = encoded + self.name = name + self.description = description + self.link = link + self.imageData = imageData + } + } +} + + diff --git a/FakeNFT/RatingViewModel.swift b/FakeNFT/RatingViewModel.swift new file mode 100644 index 0000000000..e47d2d7c44 --- /dev/null +++ b/FakeNFT/RatingViewModel.swift @@ -0,0 +1,59 @@ +// +// RatingViewModel.swift +// FakeNFT +// +// Created by Anastasia on 03.06.2025. +// + +import SwiftUI + +@MainActor +final class RatingViewModel: ObservableObject { + @Published var filteredUsers: [User] = [] + @Published var isLoading: Bool = false + @Published var isShowingFilterSheet: Bool = false + @Published var isShowingErrorAlert: Bool = false + private var users: [User] = [] + private let usersService: UsersService + + @AppStorage("ratingSort") private var currentSorting: RatingFilterType = .rating + + init(usersService: UsersService) { + self.usersService = usersService + } + + func loadUsers() { + if !users.isEmpty { return } + isLoading = true + + usersService.loadUsers { [weak self] result in + DispatchQueue.main.async { + guard let self else { return } + self.isLoading = false + switch result { + case .success(let users): + self.users = users + self.filterUsers(by: self.currentSorting) + case .failure(_): + self.isShowingErrorAlert = true + } + } + } + } + + func filterUsers(by type: RatingFilterType) { + switch type { + case .name: + currentSorting = .name + filteredUsers = users.sorted { $0.name.lowercased() < $1.name.lowercased() } + case .rating: + currentSorting = .rating + filteredUsers = users.sorted { Int($0.rating) ?? 0 < Int($1.rating) ?? 0 } + } + } +} + +enum RatingFilterType: String { + case name + case rating +} diff --git a/FakeNFT/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/FakeNFT/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json index 13613e3ee1..9ef292d7df 100644 --- a/FakeNFT/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/FakeNFT/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,6 +1,7 @@ { "images" : [ { + "filename" : "store.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" diff --git a/FakeNFT/Resources/Assets.xcassets/AppIcon.appiconset/store.png b/FakeNFT/Resources/Assets.xcassets/AppIcon.appiconset/store.png new file mode 100644 index 0000000000..a6f6980e3a Binary files /dev/null and b/FakeNFT/Resources/Assets.xcassets/AppIcon.appiconset/store.png differ diff --git a/FakeNFT/Resources/Assets.xcassets/Colors/Contents.json b/FakeNFT/Resources/Assets.xcassets/Colors/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Colors/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Colors/blackDay.colorset/Contents.json b/FakeNFT/Resources/Assets.xcassets/Colors/blackDay.colorset/Contents.json new file mode 100644 index 0000000000..f72aca9951 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Colors/blackDay.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x22", + "green" : "0x1B", + "red" : "0x1A" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Colors/lightGrayDay.colorset/Contents.json b/FakeNFT/Resources/Assets.xcassets/Colors/lightGrayDay.colorset/Contents.json new file mode 100644 index 0000000000..7f62711051 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Colors/lightGrayDay.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF8", + "green" : "0xF7", + "red" : "0xF7" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x2E", + "green" : "0x2C", + "red" : "0x2C" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Colors/whiteDay.colorset/Contents.json b/FakeNFT/Resources/Assets.xcassets/Colors/whiteDay.colorset/Contents.json new file mode 100644 index 0000000000..dee192ff04 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Colors/whiteDay.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x22", + "green" : "0x1B", + "red" : "0x1A" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/Contents.json b/FakeNFT/Resources/Assets.xcassets/Icons/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/backButton.imageset/Contents.json b/FakeNFT/Resources/Assets.xcassets/Icons/backButton.imageset/Contents.json new file mode 100644 index 0000000000..b462291b3b --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/backButton.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "backButton.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/backButton.imageset/backButton.svg b/FakeNFT/Resources/Assets.xcassets/Icons/backButton.imageset/backButton.svg new file mode 100644 index 0000000000..85965564bf --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/backButton.imageset/backButton.svg @@ -0,0 +1,3 @@ + + + diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/cart.imageset/Contents.json b/FakeNFT/Resources/Assets.xcassets/Icons/cart.imageset/Contents.json new file mode 100644 index 0000000000..0478608aef --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/cart.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "cart.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/cart.imageset/cart.svg b/FakeNFT/Resources/Assets.xcassets/Icons/cart.imageset/cart.svg new file mode 100644 index 0000000000..faf33ca1e5 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/cart.imageset/cart.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/cartAdd.imageset/Contents.json b/FakeNFT/Resources/Assets.xcassets/Icons/cartAdd.imageset/Contents.json new file mode 100644 index 0000000000..7fe1b7a939 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/cartAdd.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "cartAdd.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/cartAdd.imageset/cartAdd.svg b/FakeNFT/Resources/Assets.xcassets/Icons/cartAdd.imageset/cartAdd.svg new file mode 100644 index 0000000000..cf69dcc80b --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/cartAdd.imageset/cartAdd.svg @@ -0,0 +1,4 @@ + + + + diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/cartDelete.imageset/Contents.json b/FakeNFT/Resources/Assets.xcassets/Icons/cartDelete.imageset/Contents.json new file mode 100644 index 0000000000..0bcb2e25b3 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/cartDelete.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "cartDelete.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/cartDelete.imageset/cartDelete.svg b/FakeNFT/Resources/Assets.xcassets/Icons/cartDelete.imageset/cartDelete.svg new file mode 100644 index 0000000000..65af140646 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/cartDelete.imageset/cartDelete.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/catalog.imageset/Contents.json b/FakeNFT/Resources/Assets.xcassets/Icons/catalog.imageset/Contents.json new file mode 100644 index 0000000000..22549f1e3e --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/catalog.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "catalog.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/catalog.imageset/catalog.svg b/FakeNFT/Resources/Assets.xcassets/Icons/catalog.imageset/catalog.svg new file mode 100644 index 0000000000..0168876437 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/catalog.imageset/catalog.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/filterButton.imageset/Contents.json b/FakeNFT/Resources/Assets.xcassets/Icons/filterButton.imageset/Contents.json new file mode 100644 index 0000000000..e7e405ad8b --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/filterButton.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "filterButton.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/filterButton.imageset/filterButton.svg b/FakeNFT/Resources/Assets.xcassets/Icons/filterButton.imageset/filterButton.svg new file mode 100644 index 0000000000..de994be362 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/filterButton.imageset/filterButton.svg @@ -0,0 +1,3 @@ + + + diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/likeActive.imageset/Contents.json b/FakeNFT/Resources/Assets.xcassets/Icons/likeActive.imageset/Contents.json new file mode 100644 index 0000000000..3b79ba0161 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/likeActive.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "likeActive.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/likeActive.imageset/likeActive.svg b/FakeNFT/Resources/Assets.xcassets/Icons/likeActive.imageset/likeActive.svg new file mode 100644 index 0000000000..0df1256881 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/likeActive.imageset/likeActive.svg @@ -0,0 +1,3 @@ + + + diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/likeNoActive.imageset/Contents.json b/FakeNFT/Resources/Assets.xcassets/Icons/likeNoActive.imageset/Contents.json new file mode 100644 index 0000000000..49466f01a2 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/likeNoActive.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "likeNoActive.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/likeNoActive.imageset/likeNoActive.svg b/FakeNFT/Resources/Assets.xcassets/Icons/likeNoActive.imageset/likeNoActive.svg new file mode 100644 index 0000000000..07735df20b --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/likeNoActive.imageset/likeNoActive.svg @@ -0,0 +1,3 @@ + + + diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/profile.imageset/Contents.json b/FakeNFT/Resources/Assets.xcassets/Icons/profile.imageset/Contents.json new file mode 100644 index 0000000000..e52494b52d --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/profile.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "profile.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/profile.imageset/profile.svg b/FakeNFT/Resources/Assets.xcassets/Icons/profile.imageset/profile.svg new file mode 100644 index 0000000000..05db0cadf6 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/profile.imageset/profile.svg @@ -0,0 +1,3 @@ + + + diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/starActive.imageset/Contents.json b/FakeNFT/Resources/Assets.xcassets/Icons/starActive.imageset/Contents.json new file mode 100644 index 0000000000..db3a4ad6d5 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/starActive.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "starActive.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/starActive.imageset/starActive.svg b/FakeNFT/Resources/Assets.xcassets/Icons/starActive.imageset/starActive.svg new file mode 100644 index 0000000000..3f0f70ed74 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/starActive.imageset/starActive.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/starNoActive.imageset/Contents.json b/FakeNFT/Resources/Assets.xcassets/Icons/starNoActive.imageset/Contents.json new file mode 100644 index 0000000000..a0db275696 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/starNoActive.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "starNoActive.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/starNoActive.imageset/starNoActive.svg b/FakeNFT/Resources/Assets.xcassets/Icons/starNoActive.imageset/starNoActive.svg new file mode 100644 index 0000000000..766c252ba1 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/starNoActive.imageset/starNoActive.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/statistics.imageset/Contents.json b/FakeNFT/Resources/Assets.xcassets/Icons/statistics.imageset/Contents.json new file mode 100644 index 0000000000..20b5bdcf51 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/statistics.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "statistics.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/statistics.imageset/statistics.svg b/FakeNFT/Resources/Assets.xcassets/Icons/statistics.imageset/statistics.svg new file mode 100644 index 0000000000..34edbe13d0 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/statistics.imageset/statistics.svg @@ -0,0 +1,3 @@ + + + diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/userpick.imageset/Contents.json b/FakeNFT/Resources/Assets.xcassets/Icons/userpick.imageset/Contents.json new file mode 100644 index 0000000000..98723393bc --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/userpick.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "userpick.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Icons/userpick.imageset/userpick.svg b/FakeNFT/Resources/Assets.xcassets/Icons/userpick.imageset/userpick.svg new file mode 100644 index 0000000000..f23ca4fc32 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Icons/userpick.imageset/userpick.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/FakeNFT/Resources/Assets.xcassets/Images/Contents.json b/FakeNFT/Resources/Assets.xcassets/Images/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Images/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Images/blueSomething.imageset/6ae4f0cf45e2cd5c9240b70315e8ba1b67613527.png b/FakeNFT/Resources/Assets.xcassets/Images/blueSomething.imageset/6ae4f0cf45e2cd5c9240b70315e8ba1b67613527.png new file mode 100644 index 0000000000..5eaa9ea1da Binary files /dev/null and b/FakeNFT/Resources/Assets.xcassets/Images/blueSomething.imageset/6ae4f0cf45e2cd5c9240b70315e8ba1b67613527.png differ diff --git a/FakeNFT/Resources/Assets.xcassets/Images/blueSomething.imageset/Contents.json b/FakeNFT/Resources/Assets.xcassets/Images/blueSomething.imageset/Contents.json new file mode 100644 index 0000000000..edc6ccc700 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Images/blueSomething.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "6ae4f0cf45e2cd5c9240b70315e8ba1b67613527.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Images/digitalArt.imageset/Contents.json b/FakeNFT/Resources/Assets.xcassets/Images/digitalArt.imageset/Contents.json new file mode 100644 index 0000000000..2e123661b9 --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/Images/digitalArt.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "digitalArt@1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "digitalArt@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "digitalArt@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/Images/digitalArt.imageset/digitalArt@1x.png b/FakeNFT/Resources/Assets.xcassets/Images/digitalArt.imageset/digitalArt@1x.png new file mode 100644 index 0000000000..ec17a4791a Binary files /dev/null and b/FakeNFT/Resources/Assets.xcassets/Images/digitalArt.imageset/digitalArt@1x.png differ diff --git a/FakeNFT/Resources/Assets.xcassets/Images/digitalArt.imageset/digitalArt@2x.png b/FakeNFT/Resources/Assets.xcassets/Images/digitalArt.imageset/digitalArt@2x.png new file mode 100644 index 0000000000..c36c6f3375 Binary files /dev/null and b/FakeNFT/Resources/Assets.xcassets/Images/digitalArt.imageset/digitalArt@2x.png differ diff --git a/FakeNFT/Resources/Assets.xcassets/Images/digitalArt.imageset/digitalArt@3x.png b/FakeNFT/Resources/Assets.xcassets/Images/digitalArt.imageset/digitalArt@3x.png new file mode 100644 index 0000000000..59917f2b8d Binary files /dev/null and b/FakeNFT/Resources/Assets.xcassets/Images/digitalArt.imageset/digitalArt@3x.png differ diff --git a/FakeNFT/Resources/Assets.xcassets/ProfilePhoto.imageset/Contents.json b/FakeNFT/Resources/Assets.xcassets/ProfilePhoto.imageset/Contents.json new file mode 100644 index 0000000000..66fae2e8aa --- /dev/null +++ b/FakeNFT/Resources/Assets.xcassets/ProfilePhoto.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "ProfilePhoto.jpg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FakeNFT/Resources/Assets.xcassets/ProfilePhoto.imageset/ProfilePhoto.jpg b/FakeNFT/Resources/Assets.xcassets/ProfilePhoto.imageset/ProfilePhoto.jpg new file mode 100644 index 0000000000..6ef1925513 Binary files /dev/null and b/FakeNFT/Resources/Assets.xcassets/ProfilePhoto.imageset/ProfilePhoto.jpg differ diff --git a/FakeNFT/Resources/Base.lproj/LaunchScreen.storyboard b/FakeNFT/Resources/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 865e9329f3..0000000000 --- a/FakeNFT/Resources/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/FakeNFT/Resources/Base.lproj/Main.storyboard b/FakeNFT/Resources/Base.lproj/Main.storyboard deleted file mode 100644 index 264407c712..0000000000 --- a/FakeNFT/Resources/Base.lproj/Main.storyboard +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/FakeNFT/Resources/ru.lproj/LaunchScreen.strings b/FakeNFT/Resources/ru.lproj/LaunchScreen.strings deleted file mode 100644 index 8b13789179..0000000000 --- a/FakeNFT/Resources/ru.lproj/LaunchScreen.strings +++ /dev/null @@ -1 +0,0 @@ - diff --git a/FakeNFT/Resources/ru.lproj/Main.strings b/FakeNFT/Resources/ru.lproj/Main.strings deleted file mode 100644 index 6d61671411..0000000000 --- a/FakeNFT/Resources/ru.lproj/Main.strings +++ /dev/null @@ -1,30 +0,0 @@ - -/* Class = "UITabBarItem"; title = "Корзина"; ObjectID = "4N1-bS-JmX"; */ -"4N1-bS-JmX.title" = "Корзина"; - -/* Class = "UITabBarItem"; title = "Профиль"; ObjectID = "Bq6-vk-Wno"; */ -"Bq6-vk-Wno.title" = "Профиль"; - -/* Class = "UITabBarItem"; title = "Каталог"; ObjectID = "X74-Ak-ux8"; */ -"X74-Ak-ux8.title" = "Каталог"; - -/* Class = "UILabel"; text = "Статистика"; ObjectID = "YLD-QU-GF5"; */ -"YLD-QU-GF5.text" = "Статистика"; - -/* Class = "UILabel"; text = "Профиль"; ObjectID = "cJe-qj-ep5"; */ -"cJe-qj-ep5.text" = "Профиль"; - -/* Class = "UIButton"; configuration.title = "Show nft id = 22"; ObjectID = "co5-kx-oQ8"; */ -"co5-kx-oQ8.configuration.title" = "Show nft id = 22"; - -/* Class = "UIButton"; normalTitle = "Button"; ObjectID = "co5-kx-oQ8"; */ -"co5-kx-oQ8.normalTitle" = "Button"; - -/* Class = "UILabel"; text = "Корзина"; ObjectID = "hE3-IK-5YF"; */ -"hE3-IK-5YF.text" = "Корзина"; - -/* Class = "UILabel"; text = "Каталог"; ObjectID = "qWk-zT-AG8"; */ -"qWk-zT-AG8.text" = "Каталог"; - -/* Class = "UITabBarItem"; title = "Корзина"; ObjectID = "xgD-9Y-EGT"; */ -"xgD-9Y-EGT.title" = "Корзина"; diff --git a/FakeNFT/SceneDelegate.swift b/FakeNFT/SceneDelegate.swift deleted file mode 100644 index 5cc4b031b3..0000000000 --- a/FakeNFT/SceneDelegate.swift +++ /dev/null @@ -1,15 +0,0 @@ -import UIKit - -final class SceneDelegate: UIResponder, UIWindowSceneDelegate { - var window: UIWindow? - - let servicesAssembly = ServicesAssembly( - networkClient: DefaultNetworkClient(), - nftStorage: NftStorageImpl() - ) - - func scene(_: UIScene, willConnectTo _: UISceneSession, options _: UIScene.ConnectionOptions) { - let tabBarController = window?.rootViewController as? TabBarController - tabBarController?.servicesAssembly = servicesAssembly - } -} diff --git a/FakeNFT/Scenes /CartView/CartView.swift b/FakeNFT/Scenes /CartView/CartView.swift new file mode 100644 index 0000000000..8242348794 --- /dev/null +++ b/FakeNFT/Scenes /CartView/CartView.swift @@ -0,0 +1,18 @@ +// +// CartView.swift +// FakeNFT +// +// Created by Max on 24.05.2025. +// + +import SwiftUI + +struct CartView: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +#Preview { + CartView() +} diff --git a/FakeNFT/Scenes /Catalog/TestCatalogController.swift b/FakeNFT/Scenes /Catalog/TestCatalogController.swift index 201fdc7e73..98d5ab237e 100644 --- a/FakeNFT/Scenes /Catalog/TestCatalogController.swift +++ b/FakeNFT/Scenes /Catalog/TestCatalogController.swift @@ -1,41 +1,41 @@ -import UIKit - -final class TestCatalogViewController: UIViewController { - - let servicesAssembly: ServicesAssembly - let testNftButton = UIButton() - - init(servicesAssembly: ServicesAssembly) { - self.servicesAssembly = servicesAssembly - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - view.backgroundColor = .systemBackground - - view.addSubview(testNftButton) - testNftButton.constraintCenters(to: view) - testNftButton.setTitle(Constants.openNftTitle, for: .normal) - testNftButton.addTarget(self, action: #selector(showNft), for: .touchUpInside) - testNftButton.setTitleColor(.systemBlue, for: .normal) - } - - @objc - func showNft() { - let assembly = NftDetailAssembly(servicesAssembler: servicesAssembly) - let nftInput = NftDetailInput(id: Constants.testNftId) - let nftViewController = assembly.build(with: nftInput) - present(nftViewController, animated: true) - } -} - -private enum Constants { - static let openNftTitle = NSLocalizedString("Catalog.openNft", comment: "") - static let testNftId = "7773e33c-ec15-4230-a102-92426a3a6d5a" -} +//import UIKit +// +//final class TestCatalogViewController: UIViewController { +// +// let servicesAssembly: ServicesAssembly +// let testNftButton = UIButton() +// +// init(servicesAssembly: ServicesAssembly) { +// self.servicesAssembly = servicesAssembly +// super.init(nibName: nil, bundle: nil) +// } +// +// required init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +// +// override func viewDidLoad() { +// super.viewDidLoad() +// +// view.backgroundColor = .systemBackground +// +// view.addSubview(testNftButton) +// testNftButton.constraintCenters(to: view) +// testNftButton.setTitle(Constants.openNftTitle, for: .normal) +// testNftButton.addTarget(self, action: #selector(showNft), for: .touchUpInside) +// testNftButton.setTitleColor(.systemBlue, for: .normal) +// } +// +// @objc +// func showNft() { +// let assembly = NftDetailAssembly(servicesAssembler: servicesAssembly) +// let nftInput = NftDetailInput(id: Constants.testNftId) +// let nftViewController = assembly.build(with: nftInput) +// present(nftViewController, animated: true) +// } +//} +// +//private enum Constants { +// static let openNftTitle = NSLocalizedString("Catalog.openNft", comment: "") +// static let testNftId = "7773e33c-ec15-4230-a102-92426a3a6d5a" +//} diff --git a/FakeNFT/Scenes /CatalogView/CatalogView.swift b/FakeNFT/Scenes /CatalogView/CatalogView.swift new file mode 100644 index 0000000000..ea8fd0ae6b --- /dev/null +++ b/FakeNFT/Scenes /CatalogView/CatalogView.swift @@ -0,0 +1,18 @@ +// +// CatalogView.swift +// FakeNFT +// +// Created by Max on 24.05.2025. +// + +import SwiftUI + +struct CatalogView: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +#Preview { + CatalogView() +} diff --git a/FakeNFT/Scenes /Common/LoadingView/LoadingView.swift b/FakeNFT/Scenes /Common/LoadingView/LoadingView.swift new file mode 100644 index 0000000000..7bcd7b7687 --- /dev/null +++ b/FakeNFT/Scenes /Common/LoadingView/LoadingView.swift @@ -0,0 +1,25 @@ +// +// LoadingView.swift +// FakeNFT +// +// Created by Anastasia on 25.05.2025. +// + +import SwiftUI + +struct LoadingView: View { + var body: some View { + RoundedRectangle(cornerRadius: 8) + .fill(Color.lightGrayDay) + .frame(width: 82, height: 82) + .overlay { + ProgressView() + .tint(.blackDay) + .scaleEffect(1.5) + } + } +} + +#Preview { + LoadingView() +} diff --git a/FakeNFT/Scenes /Common/Protocols/LoadingView.swift b/FakeNFT/Scenes /Common/Protocols/LoadingView.swift deleted file mode 100644 index 4b5b24cc50..0000000000 --- a/FakeNFT/Scenes /Common/Protocols/LoadingView.swift +++ /dev/null @@ -1,18 +0,0 @@ -import ProgressHUD -import UIKit - -protocol LoadingView { - var activityIndicator: UIActivityIndicatorView { get } - func showLoading() - func hideLoading() -} - -extension LoadingView { - func showLoading() { - activityIndicator.startAnimating() - } - - func hideLoading() { - activityIndicator.stopAnimating() - } -} diff --git a/FakeNFT/Scenes /Common/Views/LinePageControl.swift b/FakeNFT/Scenes /Common/Views/LinePageControl.swift index 2a27b4c91a..75e0908023 100644 --- a/FakeNFT/Scenes /Common/Views/LinePageControl.swift +++ b/FakeNFT/Scenes /Common/Views/LinePageControl.swift @@ -4,17 +4,17 @@ final class LinePageControl: UIView { // MARK: - Properties - var numberOfItems: Int = 0 { - didSet { - setupStackView() - } - } - - var selectedItem: Int = 0 { - didSet { - selectedSegmentChanged() - } - } +// var numberOfItems: Int = 0 { +// didSet { +// setupStackView() +// } +// } + +// var selectedItem: Int = 0 { +// didSet { +// selectedSegmentChanged() +// } +// } override var intrinsicContentSize: CGSize { return CGSize(width: UIView.noIntrinsicMetric, height: height) @@ -45,25 +45,25 @@ final class LinePageControl: UIView { // MARK: - Functions - func setupStackView() { - stackView.arrangedSubviews.forEach { stackView.removeArrangedSubview($0) } - - for _ in (0.. WKWebView { + WKWebView() + } + + func updateUIView(_ webView: WKWebView, context: Context) { + webView.navigationDelegate = context.coordinator + webView.isOpaque = false + webView.backgroundColor = .white + + if let url = url { + webView.load(URLRequest(url: url)) + } + } + + func makeCoordinator() -> Coordinator { + Coordinator(parent: self) + } + + final class Coordinator: NSObject, WKNavigationDelegate { + var parent: WebViewRepresentable + + init(parent: WebViewRepresentable) { + self.parent = parent + } + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + parent.isLoading = false + } + } +} diff --git a/FakeNFT/Scenes /ContentView.swift b/FakeNFT/Scenes /ContentView.swift new file mode 100644 index 0000000000..ef0a1353df --- /dev/null +++ b/FakeNFT/Scenes /ContentView.swift @@ -0,0 +1,44 @@ +// +// ContentView.swift +// FakeNFT +// +// Created by Max on 24.05.2025. +// + +import SwiftUI + +struct ContentView: View { + + @EnvironmentObject var service: ServicesAssembly + @State private var isTabBarHidden = false + @State private var selectedTab = Tab.profile + + var body: some View { + ZStack(alignment: .bottom) { + TabView(selection: $selectedTab) { + ProfileView() + .tag(Tab.profile) + + Text("Catalog") + .tag(Tab.catalog) + + Text("Cart") + .tag(Tab.catalog) + + RatingView( + viewModel: RatingViewModel(usersService: service.usersService), + isTabBarHidden: $isTabBarHidden + ) + .tag(Tab.statistics) + } + if !isTabBarHidden { + TabBarView(selectedTab: $selectedTab) + } + } + } +} + +#Preview { + ContentView() + .environmentObject(ServicesAssembly()) +} diff --git a/FakeNFT/Scenes /NftDetails/Cell/NftDetailCellModel.swift b/FakeNFT/Scenes /NftDetails/Cell/NftDetailCellModel.swift deleted file mode 100644 index cc35909943..0000000000 --- a/FakeNFT/Scenes /NftDetails/Cell/NftDetailCellModel.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -struct NftDetailCellModel { - let url: URL -} diff --git a/FakeNFT/Scenes /NftDetails/Cell/NftImageCollectionViewCell.swift b/FakeNFT/Scenes /NftDetails/Cell/NftImageCollectionViewCell.swift deleted file mode 100644 index 77448fb1f2..0000000000 --- a/FakeNFT/Scenes /NftDetails/Cell/NftImageCollectionViewCell.swift +++ /dev/null @@ -1,57 +0,0 @@ -import UIKit - -final class NftImageCollectionViewCell: UICollectionViewCell, ReuseIdentifying { - - // MARK: - Properties - - private lazy var scrollView: UIScrollView = { - let scrollView = UIScrollView() - scrollView.delegate = self - scrollView.minimumZoomScale = 1.0 - scrollView.maximumZoomScale = 3.0 - return scrollView - }() - - private lazy var imageView: UIImageView = { - let imageView = UIImageView() - imageView.contentMode = .scaleAspectFit - return imageView - }() - - // MARK: - Init - - override init(frame: CGRect) { - super.init(frame: frame) - - contentView.addSubview(scrollView) - scrollView.constraintEdges(to: contentView) - - scrollView.addSubview(imageView) - imageView.constraintCenters(to: scrollView) - imageView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true - imageView.heightAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Functions - - func configure(with cellModel: NftDetailCellModel) { - imageView.kf.setImage(with: cellModel.url) - } -} - -// MARK: - UIScrollViewDelegate - -extension NftImageCollectionViewCell: UIScrollViewDelegate { - - func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) { - scrollView.setZoomScale(scrollView.minimumZoomScale, animated: true) - } - - func viewForZooming(in scrollView: UIScrollView) -> UIView? { - imageView - } -} diff --git a/FakeNFT/Scenes /NftDetails/NftDetailAssembly.swift b/FakeNFT/Scenes /NftDetails/NftDetailAssembly.swift index 1a76e6cd50..2a70c89728 100644 --- a/FakeNFT/Scenes /NftDetails/NftDetailAssembly.swift +++ b/FakeNFT/Scenes /NftDetails/NftDetailAssembly.swift @@ -1,20 +1,20 @@ -import UIKit - -public final class NftDetailAssembly { - - private let servicesAssembler: ServicesAssembly - - init(servicesAssembler: ServicesAssembly) { - self.servicesAssembler = servicesAssembler - } - - public func build(with input: NftDetailInput) -> UIViewController { - let presenter = NftDetailPresenterImpl( - input: input, - service: servicesAssembler.nftService - ) - let viewController = NftDetailViewController(presenter: presenter) - presenter.view = viewController - return viewController - } -} +//import UIKit +// +//public final class NftDetailAssembly { +// +// private let servicesAssembler: ServicesAssembly +// +// init(servicesAssembler: ServicesAssembly) { +// self.servicesAssembler = servicesAssembler +// } +// +// public func build(with input: NftDetailInput) -> UIViewController { +// let presenter = NftDetailPresenterImpl( +// input: input, +// service: servicesAssembler.nftService +// ) +// let viewController = NftDetailViewController(presenter: presenter) +// presenter.view = viewController +// return viewController +// } +//} diff --git a/FakeNFT/Scenes /NftDetails/NftDetailPresenter.swift b/FakeNFT/Scenes /NftDetails/NftDetailPresenter.swift index 11d2df4f1d..bab9f4e4d7 100644 --- a/FakeNFT/Scenes /NftDetails/NftDetailPresenter.swift +++ b/FakeNFT/Scenes /NftDetails/NftDetailPresenter.swift @@ -1,84 +1,84 @@ -import Foundation - -// MARK: - Protocol - -protocol NftDetailPresenter { - func viewDidLoad() -} - -// MARK: - State - -enum NftDetailState { - case initial, loading, failed(Error), data(Nft) -} - -final class NftDetailPresenterImpl: NftDetailPresenter { - - // MARK: - Properties - - weak var view: NftDetailView? - private let input: NftDetailInput - private let service: NftService - private var state = NftDetailState.initial { - didSet { - stateDidChanged() - } - } - - // MARK: - Init - - init(input: NftDetailInput, service: NftService) { - self.input = input - self.service = service - } - - // MARK: - Functions - - func viewDidLoad() { - state = .loading - } - - private func stateDidChanged() { - switch state { - case .initial: - assertionFailure("can't move to initial state") - case .loading: - view?.showLoading() - loadNft() - case .data(let nft): - view?.hideLoading() - let cellModels = nft.images.map { NftDetailCellModel(url: $0) } - view?.displayCells(cellModels) - case .failed(let error): - let errorModel = makeErrorModel(error) - view?.hideLoading() - view?.showError(errorModel) - } - } - - private func loadNft() { - service.loadNft(id: input.id) { [weak self] result in - switch result { - case .success(let nft): - self?.state = .data(nft) - case .failure(let error): - self?.state = .failed(error) - } - } - } - - private func makeErrorModel(_ error: Error) -> ErrorModel { - let message: String - switch error { - case is NetworkClientError: - message = NSLocalizedString("Error.network", comment: "") - default: - message = NSLocalizedString("Error.unknown", comment: "") - } - - let actionText = NSLocalizedString("Error.repeat", comment: "") - return ErrorModel(message: message, actionText: actionText) { [weak self] in - self?.state = .loading - } - } -} +//import Foundation +// +//// MARK: - Protocol +// +//protocol NftDetailPresenter { +// func viewDidLoad() +//} +// +//// MARK: - State +// +//enum NftDetailState { +// case initial, loading, failed(Error), data(Nft) +//} +// +//final class NftDetailPresenterImpl: NftDetailPresenter { +// +// // MARK: - Properties +// +// weak var view: NftDetailView? +// private let input: NftDetailInput +// private let service: NftService +// private var state = NftDetailState.initial { +// didSet { +// stateDidChanged() +// } +// } +// +// // MARK: - Init +// +// init(input: NftDetailInput, service: NftService) { +// self.input = input +// self.service = service +// } +// +// // MARK: - Functions +// +// func viewDidLoad() { +// state = .loading +// } +// +// private func stateDidChanged() { +// switch state { +// case .initial: +// assertionFailure("can't move to initial state") +// case .loading: +// view?.showLoading() +// loadNft() +// case .data(let nft): +// view?.hideLoading() +// let cellModels = nft.images.map { NftDetailCellModel(url: $0) } +// view?.displayCells(cellModels) +// case .failed(let error): +// let errorModel = makeErrorModel(error) +// view?.hideLoading() +// view?.showError(errorModel) +// } +// } +// +// private func loadNft() { +// service.loadNft(id: input.id) { [weak self] result in +// switch result { +// case .success(let nft): +// self?.state = .data(nft) +// case .failure(let error): +// self?.state = .failed(error) +// } +// } +// } +// +// private func makeErrorModel(_ error: Error) -> ErrorModel { +// let message: String +// switch error { +// case is NetworkClientError: +// message = NSLocalizedString("Error.network", comment: "") +// default: +// message = NSLocalizedString("Error.unknown", comment: "") +// } +// +// let actionText = NSLocalizedString("Error.repeat", comment: "") +// return ErrorModel(message: message, actionText: actionText) { [weak self] in +// self?.state = .loading +// } +// } +//} diff --git a/FakeNFT/Scenes /NftDetails/NftDetailViewController.swift b/FakeNFT/Scenes /NftDetails/NftDetailViewController.swift deleted file mode 100644 index 6bb84a9b14..0000000000 --- a/FakeNFT/Scenes /NftDetails/NftDetailViewController.swift +++ /dev/null @@ -1,133 +0,0 @@ -import UIKit -import Kingfisher - -protocol NftDetailView: AnyObject, ErrorView, LoadingView { - func displayCells(_ cellModels: [NftDetailCellModel]) -} - -final class NftDetailViewController: UIViewController { - - private let presenter: NftDetailPresenter - - private lazy var collectionView: UICollectionView = { - let layout = UICollectionViewFlowLayout() - layout.scrollDirection = .horizontal - layout.minimumInteritemSpacing = 0 - layout.minimumLineSpacing = 0 - let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) - collectionView.register(NftImageCollectionViewCell.self) - collectionView.dataSource = self - collectionView.delegate = self - - collectionView.showsHorizontalScrollIndicator = false - collectionView.isPagingEnabled = true - return collectionView - }() - - private lazy var closeButton: UIButton = { - let button = UIButton() - button.tintColor = .closeButton - button.setImage(UIImage(named: "close"), for: .normal) - button.addTarget(self, action: #selector(close), for: .touchUpInside) - return button - }() - - private lazy var pageControl = LinePageControl() - internal lazy var activityIndicator = UIActivityIndicatorView() - - private var cellModels: [NftDetailCellModel] = [] - - // MARK: - Init - - init(presenter: NftDetailPresenter) { - self.presenter = presenter - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Functions - - override func viewDidLoad() { - super.viewDidLoad() - - view.backgroundColor = .white - setupLayout() - presenter.viewDidLoad() - } - - // MARK: - private functions - - private func setupLayout() { - collectionView.addSubview(activityIndicator) - activityIndicator.constraintCenters(to: collectionView) - - view.addSubview(collectionView) - collectionView.constraintEdges(to: view) - - view.addSubview(pageControl) - pageControl.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - pageControl.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -32), - pageControl.centerXAnchor.constraint(equalTo: view.centerXAnchor), - pageControl.bottomAnchor.constraint(equalTo: collectionView.safeAreaLayoutGuide.bottomAnchor) - ]) - - view.addSubview(closeButton) - closeButton.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - closeButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 30), - closeButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16) - ]) - } - - @objc - private func close() { - dismiss(animated: true) - } -} - -// MARK: - NftDetailView - -extension NftDetailViewController: NftDetailView { - func displayCells(_ cellModels: [NftDetailCellModel]) { - self.cellModels = cellModels - collectionView.reloadData() - pageControl.numberOfItems = cellModels.count - } -} - -// MARK: - UICollectionViewDataSource - -extension NftDetailViewController: UICollectionViewDataSource { - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - cellModels.count - } - - func collectionView(_ collectionView: UICollectionView, - cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let cell: NftImageCollectionViewCell = collectionView.dequeueReusableCell(indexPath: indexPath) - - let cellModel = cellModels[indexPath.row] - cell.configure(with: cellModel) - - return cell - } -} - -// MARK: - UICollectionViewDelegateFlowLayout - -extension NftDetailViewController: UICollectionViewDelegateFlowLayout { - func collectionView(_ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - sizeForItemAt indexPath: IndexPath) -> CGSize { - collectionView.bounds.size - } - - func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { - let selectedItem = Int(scrollView.contentOffset.x / scrollView.frame.size.width) - pageControl.selectedItem = selectedItem - } -} diff --git a/FakeNFT/Scenes /ProductDetails/ProductDetailsTableViewController.swift b/FakeNFT/Scenes /ProductDetails/ProductDetailsTableViewController.swift deleted file mode 100644 index baa2a0ffb1..0000000000 --- a/FakeNFT/Scenes /ProductDetails/ProductDetailsTableViewController.swift +++ /dev/null @@ -1,21 +0,0 @@ -import UIKit - -final class ProductDetailsTableViewController: UITableViewController { - override func viewDidLoad() { - super.viewDidLoad() - - tableView.register(ProductDetailsTableViewCell.self) - } - - override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { - return 10 - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell: ProductDetailsTableViewCell = tableView.dequeueReusableCell() - - cell.textLabel?.text = "Ячейка номер \(indexPath.row)" - - return cell - } -} diff --git a/FakeNFT/Scenes /Profile/AboutButton.swift b/FakeNFT/Scenes /Profile/AboutButton.swift new file mode 100644 index 0000000000..b54af5d31e --- /dev/null +++ b/FakeNFT/Scenes /Profile/AboutButton.swift @@ -0,0 +1,32 @@ +// +// SwiftUIView.swift +// FakeNFT +// +// Created by Mac on 13.06.2025. +// + +import SwiftUI + + +struct AboutButton: View { + + let name: String + var body: some View { + HStack{ + Group{ + Text("\(name)") + Spacer() + Image(systemName: "chevron.forward") + } + .font(.bold17) + .foregroundColor(Color.blackDay) + + } + .frame(height: StatisticsConstants.buttonHeightLarge) + + } +} + +#Preview { + AboutButton(name: "О разработчике") +} diff --git a/FakeNFT/Scenes /Profile/AboutUser.swift b/FakeNFT/Scenes /Profile/AboutUser.swift new file mode 100644 index 0000000000..8d0dcc9b19 --- /dev/null +++ b/FakeNFT/Scenes /Profile/AboutUser.swift @@ -0,0 +1,18 @@ +// +// AboutUser.swift +// FakeNFT +// +// Created by Mac on 17.06.2025. +// + +import SwiftUI + +struct AboutUser: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +#Preview { + AboutUser() +} diff --git a/FakeNFT/Scenes /Profile/FavouriteNft.swift b/FakeNFT/Scenes /Profile/FavouriteNft.swift new file mode 100644 index 0000000000..67992c2646 --- /dev/null +++ b/FakeNFT/Scenes /Profile/FavouriteNft.swift @@ -0,0 +1,116 @@ +// +// FavouriteNft.swift +// FakeNFT +// +// Created by Mac on 19.06.2025. +// + +import SwiftUI + +struct FavouriteNft: View { + + // MARK: - Properties + let name: String + let nft: NftInfo + let userLikes: UserLikes + let userOrders: UserOrders + var likeTapHandler: (NftInfo) -> Void + var cartTapHandler: (NftInfo) -> Void + + // MARK: - Content + + var body: some View { + HStack(){ + + image + .frame(width: 80) + + + + HStack(){ + + ratingView + .padding() + + } + + }.padding() + + + } + + // MARK: - View + + + + private var image: some View { + ZStack(alignment: .top) { + AsyncImage(url: URL(string: nft.images.first ?? "")) { phase in + switch phase { + case .success(let image): + image + .resizable() + .frame( + width: StatisticsConstants.collectionRowSize, + height: StatisticsConstants.collectionRowSize + ) + .aspectRatio(contentMode: .fill) + .clipShape(.rect(cornerRadius: StatisticsConstants.cornerRadiusSmall)) + case .failure, .empty: + RoundedRectangle(cornerRadius: StatisticsConstants.cornerRadiusSmall) + .fill(Color.lightGrayDay) + .frame( + width: 100, + height: 100 + ) + default: + EmptyView() + } + } + HStack { + Spacer() + Button { + likeTapHandler(nft) + } label: { + Image(userLikes.likes.contains(nft.id) ? "likeActive" : "likeNoActive") + } + } + } + } + + private var ratingView: some View { + VStack(alignment: .leading, spacing: 6){ + Text("\(name)") + .font(.headline) + HStack(spacing: 2) { + let rating = nft.rating + ForEach(1..<6) { index in + Image(index <= rating ? "starActive" : "starNoActive") + } + + } + Text("\(nft.price, specifier: "%.2f") ETH").font(.system(size: 16)) + + } + + + } + + +} + + + +#Preview { + FavouriteNft(name: "Lilo", nft: NftInfo( + id: "", + name: "", + images: [""], + rating: 4, + price: 1.79 + ), + userLikes: UserLikes(likes: []), + userOrders: UserOrders(nfts: []), + likeTapHandler: {_ in }, + cartTapHandler: {_ in}) +} diff --git a/FakeNFT/Scenes /Profile/Favourites.swift b/FakeNFT/Scenes /Profile/Favourites.swift new file mode 100644 index 0000000000..083309e212 --- /dev/null +++ b/FakeNFT/Scenes /Profile/Favourites.swift @@ -0,0 +1,61 @@ +// +// Favourites.swift +// FakeNFT +// +// Created by Mac on 17.06.2025. +// + +import SwiftUI + + + +struct Favourites: View { + let columns = [ + GridItem(.flexible()), + GridItem(.flexible()) + ] + var body: some View { + NavigationView { + content + .modifier(NavigationBarStyle( + title: "Избранные NFT", + backButtonHidden: false, + filterButtonHidden: true, + filterButtonTapHandler: {} + )) + } + + + } + + var content: some View { + ScrollView{ + LazyVGrid(columns: columns, spacing: .zero) { + nft + nft + nft + nft + nft + nft + }.padding(13) + + } + } + var nft: some View{ + FavouriteNft(name: "Lilo", nft: NftInfo( + id: "", + name: "", + images: [""], + rating: 4, + price: 1.79 + ), + userLikes: UserLikes(likes: []), + userOrders: UserOrders(nfts: []), + likeTapHandler: {_ in }, + cartTapHandler: {_ in}) + } +} + +#Preview { + Favourites() +} diff --git a/FakeNFT/Scenes /Profile/FullScreenModalView.swift b/FakeNFT/Scenes /Profile/FullScreenModalView.swift new file mode 100644 index 0000000000..2b29cde158 --- /dev/null +++ b/FakeNFT/Scenes /Profile/FullScreenModalView.swift @@ -0,0 +1,190 @@ +// +// FullScreenModalView.swift +// FakeNFT +// +// Created by Mac on 14.06.2025. +// + +import SwiftUI +import PhotosUI +struct FullScreenModalView: View { + + @ObservedObject var viewModel: ProfileViewModel + + + + @Environment(\.presentationMode) var presentationMode + @State var description: String = "" + @State var name: String = "" + @State var link: String = "" + @State private var selectedItem: PhotosPickerItem? + @State private var tempImageData: Data? + + + + var body: some View { + HStack{ + Spacer() + Button("", systemImage: "xmark") { + viewModel.save(name: name, description: description, link: link, imageData: tempImageData) + presentationMode.wrappedValue.dismiss() + } + .padding(.top, 16) + .foregroundStyle(Color.blackDay) + .frame(width: 42,height: 42) + .font(.system(size: 19, weight: .bold)) + + + } + VStack(spacing: 20){ + ZStack(alignment: .center){ + if let data = tempImageData, let uiImage = UIImage(data: data){ + + Image(uiImage: uiImage) + .resizable() + .scaledToFit() + .frame(width: 100,height: 100) + .frame(width: 70, height: 70) + .clipShape(.circle) + } else { + Image(.profilePhoto) + .resizable() + .scaledToFit() + .frame(width: 100,height: 100) + .frame(width: 70, height: 70) + .clipShape(.circle) + .overlay( + Circle().fill(Color.black.opacity(0.4)) + ) + + + } + PhotosPicker( + selection: $selectedItem, matching: .images, + photoLibrary: .shared() + ) { + + Text("Сменить \nфото") + .foregroundColor(.white) + .multilineTextAlignment(.center) + .font(.system(size: 10, weight: .semibold)) + .lineLimit(2) + .minimumScaleFactor(0.5) + .frame(width: 100, height: 100, alignment: .center) + } + + } + + + + + } + .interactiveDismissDisabled(true) + .onAppear{ + name = viewModel.name + link = viewModel.link + description = viewModel.description + tempImageData = viewModel.imageData + } + .onChange(of: selectedItem) { newItem in + Task{ + if let data = try? await newItem?.loadTransferable(type: Data.self){ + tempImageData = data + } + } + + } + + VStack(alignment: .leading){ + + + + Text("Имя") + .font(.bold22) + .foregroundColor(Color.blackDay) + .padding(.leading) + ZStack{ + TextField(name, text: $name) + .padding() + .background(Color.gray.opacity(0.1)) + .cornerRadius(8) + .clearButton(text: $name) + + }.padding(.leading) + .padding(.trailing) + + Text("Описание") + .font(.bold22) + .foregroundColor(Color.blackDay) + .padding(.leading) + ZStack{ + TextField(description, text: $description) + .padding() + .background(Color.gray.opacity(0.1)) + .cornerRadius(8) + .clearButton(text: $description) + }.padding(.leading) + .padding(.trailing) + + Text("Сайт") + .font(.bold22) + .foregroundColor(Color.blackDay) + .padding(.leading) + ZStack{ + TextField(link, text: $link) + .padding() + .background(Color.gray.opacity(0.1)) + .cornerRadius(8) + .clearButton(text: $link) + }.padding(.leading) + .padding(.trailing) + + + + + + Spacer() + + } + + + } +} + +struct ClearButtonModitifier: ViewModifier { + @Binding var text: String + + func body(content: Content) -> some View{ + content + .overlay( + HStack{ + Spacer() + if !text.isEmpty { + Button(action: { + text = "" + }) { + Image(systemName: "xmark.circle.fill") + .foregroundColor(.gray) + } + .padding(.trailing, 8) + } + } + + ) + + } +} +extension View{ + func clearButton(text: Binding) -> some View { + self.modifier(ClearButtonModitifier(text: text)) + } +} + +#Preview("FullScreenModalView MVVM") { + let vm = ProfileViewModel() + vm.name = "N" + vm.description = "D" + vm.link = "L" + vm.imageData = UIImage(systemName: "photo")?.jpegData(compressionQuality: 1.0) + return FullScreenModalView(viewModel: vm) +} diff --git a/FakeNFT/Scenes /Profile/NftCollectionViewModel.swift b/FakeNFT/Scenes /Profile/NftCollectionViewModel.swift new file mode 100644 index 0000000000..14de706401 --- /dev/null +++ b/FakeNFT/Scenes /Profile/NftCollectionViewModel.swift @@ -0,0 +1,18 @@ +// +// NftCollectionViewModel.swift +// FakeNFT +// +// Created by Mac on 17.06.2025. +// + +import SwiftUI + +struct NftCollectionViewModel: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +#Preview { + NftCollectionViewModel() +} diff --git a/FakeNFT/Scenes /Profile/NftRow.swift b/FakeNFT/Scenes /Profile/NftRow.swift new file mode 100644 index 0000000000..8eeaf1d731 --- /dev/null +++ b/FakeNFT/Scenes /Profile/NftRow.swift @@ -0,0 +1,130 @@ +// +// NftRow.swift +// FakeNFT +// +// Created by Mac on 18.06.2025. +// + +import SwiftUI + +struct NftRow: View { + + // MARK: - Properties + let name: String + let nft: NftInfo + let userLikes: UserLikes + let userOrders: UserOrders + var likeTapHandler: (NftInfo) -> Void + var cartTapHandler: (NftInfo) -> Void + + // MARK: - Content + + var body: some View { + HStack(){ + + image + .frame(width: 109) + + + + HStack(){ + + ratingView + .padding() + Spacer() + VStack(alignment: .leading){ + Text("Цена") + + Text("\(nft.price, specifier: "%.2f") ETH") + .font(.headline) + } + .padding() + + + + + + + } + + }.padding() + + + } + + // MARK: - View + + + + private var image: some View { + ZStack(alignment: .top) { + AsyncImage(url: URL(string: nft.images.first ?? "")) { phase in + switch phase { + case .success(let image): + image + .resizable() + .frame( + width: StatisticsConstants.collectionRowSize, + height: StatisticsConstants.collectionRowSize + ) + .aspectRatio(contentMode: .fill) + .clipShape(.rect(cornerRadius: StatisticsConstants.cornerRadiusSmall)) + case .failure, .empty: + RoundedRectangle(cornerRadius: StatisticsConstants.cornerRadiusSmall) + .fill(Color.lightGrayDay) + .frame( + width: StatisticsConstants.collectionRowSize, + height: StatisticsConstants.collectionRowSize + ) + default: + EmptyView() + } + } + HStack { + Spacer() + Button { + likeTapHandler(nft) + } label: { + Image(userLikes.likes.contains(nft.id) ? "likeActive" : "likeNoActive") + } + } + } + } + + private var ratingView: some View { + VStack(alignment: .leading, spacing: 6){ + Text("\(name)") + .font(.headline) + HStack(spacing: 2) { + let rating = nft.rating + ForEach(1..<6) { index in + Image(index <= rating ? "starActive" : "starNoActive") + } + + } + Text("от Jhon Doe") + + } + + + } + + +} + + +#Preview { + NftRow( + name: "Lilo", nft: NftInfo( + id: "", + name: "", + images: [""], + rating: 4, + price: 1.79 + ), + userLikes: UserLikes(likes: []), + userOrders: UserOrders(nfts: []), + likeTapHandler: {_ in }, + cartTapHandler: {_ in} + ) +} diff --git a/FakeNFT/Scenes /Profile/NftsButton.swift b/FakeNFT/Scenes /Profile/NftsButton.swift new file mode 100644 index 0000000000..63dea7f24b --- /dev/null +++ b/FakeNFT/Scenes /Profile/NftsButton.swift @@ -0,0 +1,30 @@ +// +// NftsButton.swift +// FakeNFT +// +// Created by Mac on 13.06.2025. +// + +import SwiftUI + +struct NftsButton: View { + + let nfts: Int + let name: String + var body: some View { + HStack{ + Group{ + Text("\(name)") + Text("(\(nfts))") + Spacer() + Image(systemName: "chevron.forward") + } + .font(.bold17) + .foregroundColor(Color.blackDay) + } + .frame(height: StatisticsConstants.buttonHeightLarge) + } +} +#Preview { + NftsButton(nfts: 122, name: "Мои NFT") +} diff --git a/FakeNFT/Scenes /Profile/ProfileView.swift b/FakeNFT/Scenes /Profile/ProfileView.swift new file mode 100644 index 0000000000..32f72d6bef --- /dev/null +++ b/FakeNFT/Scenes /Profile/ProfileView.swift @@ -0,0 +1,121 @@ +// +// ProfileView.swift +// FakeNFT +// +// Created by Mac on 11.06.2025. +// + +import SwiftUI + +struct ProfileView: View { + @StateObject private var viewModel = ProfileViewModel() + + @State private var isPresenting = false + + + + func send(){ + + } + var body: some View { + + content + + } + + var content: some View { + NavigationView{ + VStack(alignment: .leading) { + HStack{ + Spacer() + Button("",systemImage: "square.and.pencil"){ + isPresenting = true + } + .foregroundStyle(Color.blackDay) + .frame(width: 42,height: 42) + .font(.system(size: 26, weight: .semibold)) + .sheet(isPresented: $isPresenting) { + FullScreenModalView(viewModel: viewModel) + + } + + + } + + userInfo + Text("\(viewModel.description)") + .font(.custom("SFProText-Regular", size: 13)) + .foregroundStyle(Color.blackDay) + .padding(.top) + Button(action: send) { + Text("\(viewModel.link)") + .padding(.top, 6) + .padding(.bottom) + + } + + NavigationLink(destination: UserNft()){ + NftsButton(nfts: 122, name: "Мои NFT") + } + + + NavigationLink(destination: Favourites()){ + NftsButton(nfts: 11, name: "Избранные NFT") + } + NavigationLink(destination: AboutUser()){ + AboutButton(name: "О разработчике") + } + + + + + + Spacer() + } + .padding(.top, 20) + .padding(.horizontal) + + } + + + } + + + var userInfo: some View { + + + HStack(spacing: .zero){ + if let data = viewModel.imageData, let uiImage = UIImage(data: data) { + Image(uiImage: uiImage) + .resizable() + .scaledToFit() + .frame(width: 120,height: 120) + .frame(width: 70, height: 70) + .clipShape(.circle) + + } else { + Image(.profilePhoto) + .resizable() + .scaledToFit() + .frame(width: 100,height: 100) + .frame(width: 70, height: 70) + .clipShape(.circle) + } + Text("\(viewModel.name)") + .font(.bold22) + .foregroundStyle(Color.blackDay) + .padding(.leading) + .frame(maxWidth: .infinity, alignment: .leading) + + Spacer() + } + + + + } + +} + +#Preview { + ProfileView() +} diff --git a/FakeNFT/Scenes /Profile/UserData.swift b/FakeNFT/Scenes /Profile/UserData.swift new file mode 100644 index 0000000000..bc6518c19f --- /dev/null +++ b/FakeNFT/Scenes /Profile/UserData.swift @@ -0,0 +1,15 @@ +// +// ProfileModel.swift +// FakeNFT +// +// Created by Mac on 23.06.2025. +// + +import SwiftUI + +struct UserData: Codable{ + var name: String + var description: String + var link: String + var imageData: Data? +} diff --git a/FakeNFT/Scenes /Profile/UserNft.swift b/FakeNFT/Scenes /Profile/UserNft.swift new file mode 100644 index 0000000000..cbf9ba9540 --- /dev/null +++ b/FakeNFT/Scenes /Profile/UserNft.swift @@ -0,0 +1,85 @@ +// +// UserNft.swift +// FakeNFT +// +// Created by Mac on 17.06.2025. +// + +import SwiftUI + +struct UserNft: View { + + var body: some View { + NavigationView { + content + .modifier(NavigationBarStyle( + title: "Мои NFT", + backButtonHidden: false, + filterButtonHidden: false, + filterButtonTapHandler: {} + )) + } + .navigationBarBackButtonHidden(true) + + + } + + var content: some View{ + VStack(spacing: .zero){ + + NftRow( + name: "Lilo", nft: NftInfo( + id: "", + name: "", + images: [""], + rating: 4, + price: 1.79 + ), + userLikes: UserLikes(likes: []), + userOrders: UserOrders(nfts: []), + likeTapHandler: {_ in }, + cartTapHandler: {_ in} + ) + NftRow( + name: "Lilo", nft: NftInfo( + id: "", + name: "", + images: [""], + rating: 4, + price: 1.79 + ), + userLikes: UserLikes(likes: []), + userOrders: UserOrders(nfts: []), + likeTapHandler: {_ in }, + cartTapHandler: {_ in} + ) + NftRow( + name: "Lilo", nft: NftInfo( + id: "", + name: "", + images: [""], + rating: 4, + price: 1.79 + ), + userLikes: UserLikes(likes: []), + userOrders: UserOrders(nfts: []), + likeTapHandler: {_ in }, + cartTapHandler: {_ in} + ) + + + } + + + + + + + } + + +} + +#Preview { + UserNft() +} diff --git a/FakeNFT/Scenes /Profile/UserNftViewModel.swift b/FakeNFT/Scenes /Profile/UserNftViewModel.swift new file mode 100644 index 0000000000..395656a082 --- /dev/null +++ b/FakeNFT/Scenes /Profile/UserNftViewModel.swift @@ -0,0 +1,14 @@ +// +// UserNftViewModel.swift +// FakeNFT +// +// Created by Mac on 18.06.2025. +// + +import SwiftUI + +@MainActor +final class UserNftViewModel: ObservableObject { + + +} diff --git a/FakeNFT/Scenes /Profile/UserProfile.swift b/FakeNFT/Scenes /Profile/UserProfile.swift new file mode 100644 index 0000000000..0221cc201f --- /dev/null +++ b/FakeNFT/Scenes /Profile/UserProfile.swift @@ -0,0 +1,32 @@ +// +// UserProfile.swift +// FakeNFT +// +// Created by Mac on 23.06.2025. +// + +import Foundation +import SwiftUI + +struct UserProfile: Codable { + var name: String + var description: String + var link: String + var imageData: Data? + + var avatarImage: UIImage? { + if let data = imageData { + return UIImage(data: data) + + } + return nil + } + + var avatar: Image { + if let uiImage = avatarImage { + return Image(uiImage: uiImage) + } else { + return Image(.profilePhoto) + } + } +} diff --git a/FakeNFT/Scenes /ProfileView/ProfileView.swift b/FakeNFT/Scenes /ProfileView/ProfileView.swift new file mode 100644 index 0000000000..f809ff49b9 --- /dev/null +++ b/FakeNFT/Scenes /ProfileView/ProfileView.swift @@ -0,0 +1,20 @@ +// +// ProfileView.swift +// FakeNFT +// +// Created by Max on 24.05.2025. +// + +import SwiftUI + +struct ProfileView: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + .font(Font.regular17) + .foregroundStyle(Color.blackDay) + } +} + +#Preview { + ProfileView() +} diff --git a/FakeNFT/Scenes /Statistics/Common/List/RatingList.swift b/FakeNFT/Scenes /Statistics/Common/List/RatingList.swift new file mode 100644 index 0000000000..bca32dfa66 --- /dev/null +++ b/FakeNFT/Scenes /Statistics/Common/List/RatingList.swift @@ -0,0 +1,34 @@ +// +// RatingList.swift +// FakeNFT +// +// Created by Anastasia on 03.06.2025. +// + +import SwiftUI + +struct RatingList: View { + + // MARK: - Properties + + let isLoading: Bool + @ViewBuilder var content: Content + + // MARK: - Content + + var body: some View { + if isLoading { + LoadingView() + } else { + ScrollView(showsIndicators: false) { + LazyVStack(spacing: StatisticsConstants.anchorSmall) { + content + } + } + } + } +} + +#Preview { + RatingList(isLoading: true) {} +} diff --git a/FakeNFT/Scenes /Statistics/Common/List/UserNFTCollection.swift b/FakeNFT/Scenes /Statistics/Common/List/UserNFTCollection.swift new file mode 100644 index 0000000000..0804c2c424 --- /dev/null +++ b/FakeNFT/Scenes /Statistics/Common/List/UserNFTCollection.swift @@ -0,0 +1,57 @@ +// +// NFTCollection.swift +// FakeNFT +// +// Created by Anastasia on 07.06.2025. +// + +import SwiftUI + +struct UserNFTCollection: View { + + let nftInfo: [NftInfo] + let userLikes: UserLikes + let userOrders: UserOrders + var likeTapHandler: (NftInfo) -> Void + var cartTapHandler: (NftInfo) -> Void + + let columns = [ + GridItem(.flexible(), alignment: .top), + GridItem(.flexible(), alignment: .top), + GridItem(.flexible(), alignment: .top) + ] + + var body: some View { + ScrollView { + LazyVGrid(columns: columns) { + ForEach(nftInfo, id: \.self) { nft in + CollectionRow( + nft: nft, + userLikes: userLikes, + userOrders: userOrders, + likeTapHandler: likeTapHandler, + cartTapHandler: cartTapHandler + ) + } + } + .padding(.horizontal) + } + } +} + +#Preview { + UserNFTCollection( + nftInfo: [ + NftInfo( + id: "", + name: "", + images: [""], + rating: 1, + price: 3.98 + )], + userLikes: UserLikes(likes: []), + userOrders: UserOrders(nfts: []), + likeTapHandler: {_ in }, + cartTapHandler: {_ in } + ) +} diff --git a/FakeNFT/Scenes /Statistics/Common/Row/CollectionRow.swift b/FakeNFT/Scenes /Statistics/Common/Row/CollectionRow.swift new file mode 100644 index 0000000000..bad1b06fcc --- /dev/null +++ b/FakeNFT/Scenes /Statistics/Common/Row/CollectionRow.swift @@ -0,0 +1,121 @@ +// +// CollectionRow.swift +// FakeNFT +// +// Created by Anastasia on 07.06.2025. +// + +import SwiftUI + +struct CollectionRow: View { + + // MARK: - Properties + + let nft: NftInfo + let userLikes: UserLikes + let userOrders: UserOrders + var likeTapHandler: (NftInfo) -> Void + var cartTapHandler: (NftInfo) -> Void + + // MARK: - Content + + var body: some View { + content + } + + // MARK: - View + + private var content: some View { + HStack() { + VStack(spacing: .zero) { + image + ratingView + .padding(.top, StatisticsConstants.anchorSmall) + nftInfo + .padding(.top, StatisticsConstants.rowAnchorSmall) + .padding(.bottom, StatisticsConstants.rowAnchorMedium) + } + .frame(width: StatisticsConstants.collectionRowSize) + } + + } + + private var image: some View { + ZStack(alignment: .top) { + AsyncImage(url: URL(string: nft.images.first ?? "")) { phase in + switch phase { + case .success(let image): + image + .resizable() + .frame( + width: StatisticsConstants.collectionRowSize, + height: StatisticsConstants.collectionRowSize + ) + .aspectRatio(contentMode: .fill) + .clipShape(.rect(cornerRadius: StatisticsConstants.cornerRadiusSmall)) + case .failure, .empty: + RoundedRectangle(cornerRadius: StatisticsConstants.cornerRadiusSmall) + .fill(Color.lightGrayDay) + .frame( + width: StatisticsConstants.collectionRowSize, + height: StatisticsConstants.collectionRowSize + ) + default: + EmptyView() + } + } + HStack { + Spacer() + Button { + likeTapHandler(nft) + } label: { + Image(userLikes.likes.contains(nft.id) ? "likeActive" : "likeNoActive") + } + } + } + } + + private var ratingView: some View { + HStack(spacing: 2) { + let rating = nft.rating + ForEach(1..<6) { index in + Image(index <= rating ? "starActive" : "starNoActive") + } + Spacer() + } + } + + private var nftInfo: some View { + HStack(spacing: .zero) { + VStack(alignment: .leading) { + Text(nft.name) + .font(.bold17) + Text("\(nft.price, specifier: "%.2f") ETH") + .font(.medium10) + } + .foregroundStyle(Color.blackDay) + Spacer() + Button { + cartTapHandler(nft) + } label: { + Image(userOrders.nfts.contains(nft.id) ? "cartDelete" : "cartAdd") + } + } + } +} + +#Preview { + CollectionRow( + nft: NftInfo( + id: "", + name: "", + images: [""], + rating: 4, + price: 1.79 + ), + userLikes: UserLikes(likes: []), + userOrders: UserOrders(nfts: []), + likeTapHandler: {_ in }, + cartTapHandler: {_ in} + ) +} diff --git a/FakeNFT/Scenes /Statistics/Common/Row/RatingRow.swift b/FakeNFT/Scenes /Statistics/Common/Row/RatingRow.swift new file mode 100644 index 0000000000..85d512cde6 --- /dev/null +++ b/FakeNFT/Scenes /Statistics/Common/Row/RatingRow.swift @@ -0,0 +1,65 @@ +// +// RatingRow.swift +// FakeNFT +// +// Created by Anastasia on 03.06.2025. +// + +import SwiftUI + +struct RatingRow: View { + + // MARK: - Properties + + let user: User + + // MARK: - View + + var body: some View { + HStack(spacing: StatisticsConstants.anchorSmall) { + Text(user.rating) + .font(.regular15) + .foregroundStyle(Color.blackDay) + .frame(width: StatisticsConstants.ratingLabelWidth) + RoundedRectangle(cornerRadius: StatisticsConstants.cornerRadiusSmall) + .fill(Color.lightGrayDay) + .frame(height: StatisticsConstants.ratingRowHeight) + .overlay { + userInfo() + } + } + } + + private func userInfo() -> some View { + HStack(spacing: .zero) { + UserAvatar( + url: user.avatar, + size: StatisticsConstants.avatarSizeSmall + ) + .padding(.leading) + Group { + Text(user.name) + .multilineTextAlignment(.leading) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.leading, StatisticsConstants.anchorSmall) + Text("\(user.nfts.count)") + .padding(.horizontal) + } + .font(.bold22) + .foregroundStyle(Color.blackDay) + } + } +} + +#Preview { + let user = User( + id: "1", + name: "Joaquin Phoenix", + avatar: "", + description: "", + website: "", + nfts: ["1", "2", "3"], + rating: "1" + ) + RatingRow(user: user) +} 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/Common/UserAvatar.swift b/FakeNFT/Scenes /Statistics/Common/UserAvatar.swift new file mode 100644 index 0000000000..960ec5638c --- /dev/null +++ b/FakeNFT/Scenes /Statistics/Common/UserAvatar.swift @@ -0,0 +1,37 @@ +// +// UserAvatar.swift +// FakeNFT +// +// Created by Anastasia on 03.06.2025. +// + +import SwiftUI + +struct UserAvatar: View { + + let url: String + let size: CGFloat + + var body: some View { + AsyncImage(url: URL(string: url)) { phase in + switch phase { + case .success(let image): + image + .resizable() + .frame(width: size, height: size) + .aspectRatio(contentMode: .fill) + .clipShape(Circle()) + case .failure, .empty: + Image("userpick") + .resizable() + .frame(width: size, height: size) + default: + EmptyView() + } + } + } +} + +#Preview { + UserAvatar(url: "", size: 28.0) +} diff --git a/FakeNFT/Scenes /Statistics/RatingView/RatingView.swift b/FakeNFT/Scenes /Statistics/RatingView/RatingView.swift new file mode 100644 index 0000000000..f94d4b9264 --- /dev/null +++ b/FakeNFT/Scenes /Statistics/RatingView/RatingView.swift @@ -0,0 +1,92 @@ +// +// RatingView.swift +// FakeNFT +// +// Created by Anastasia on 03.06.2025. +// + +import SwiftUI + +struct RatingView: View { + + // MARK: - Properties + + @StateObject var viewModel: RatingViewModel + @Binding var isTabBarHidden: Bool + + // MARK: - Content + + var body: some View { + NavigationStack { + content + .onAppear { + viewModel.loadUsers() + } + } + } + + // MARK: - View + + private var content: some View { + ratingList + .modifier(NavigationBarStyle( + title: nil, + backButtonHidden: true, + filterButtonHidden: false, + filterButtonTapHandler: { + viewModel.isShowingFilterSheet = true + } + )) + .confirmationDialog( + "Сортировка", + isPresented: $viewModel.isShowingFilterSheet, + titleVisibility: .visible + ) { + Button("По имени") { + viewModel.filterUsers(by: .name) + } + Button("По рейтингу") { + viewModel.filterUsers(by: .rating) + } + } + .alert(isPresented: $viewModel.isShowingErrorAlert) { + Alert( + title: Text("Не удалось получить данные"), + primaryButton: .default( + Text("Отмена") + ), + secondaryButton: .default( + Text("Повторить"), + action: { + viewModel.loadUsers() + } + ) + ) + } + } + + private var ratingList: some View { + VStack(spacing: .zero) { + RatingList(isLoading: viewModel.isLoading) { + ForEach(viewModel.filteredUsers) { user in + NavigationLink(destination: UserCardView( + user: user, + isTabBarHidden: $isTabBarHidden + )) { + RatingRow(user: user) + } + } + } + } + .padding(.horizontal) + .padding(.top, StatisticsConstants.topAnchorSmall) + } +} + +#Preview { + let services = ServicesAssembly() + RatingView( + viewModel: RatingViewModel(usersService: services.usersService), + isTabBarHidden: .constant(true) + ) +} diff --git a/FakeNFT/Scenes /Statistics/UserCardView/UserCardView.swift b/FakeNFT/Scenes /Statistics/UserCardView/UserCardView.swift new file mode 100644 index 0000000000..7f3e292108 --- /dev/null +++ b/FakeNFT/Scenes /Statistics/UserCardView/UserCardView.swift @@ -0,0 +1,95 @@ +// +// UserCardView.swift +// FakeNFT +// +// Created by Anastasia on 03.06.2025. +// + +import SwiftUI + +struct UserCardView: View { + + // MARK: - Properties + + @Binding var isTabBarHidden: Bool + @ObservedObject var viewModel: UserCardViewModel + @EnvironmentObject var service: ServicesAssembly + + // MARK: - Initializers + + init(user: User, isTabBarHidden: Binding) { + self.viewModel = UserCardViewModel(user: user) + self._isTabBarHidden = isTabBarHidden + } + + // MARK: - Content + + var body: some View { + 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(nftIds: viewModel.user.nfts, service: service) + ) { + ShowCollectionButton(nftsCount: viewModel.user.nfts.count) + .padding(.top, StatisticsConstants.topAnchorLarge) + } + Spacer() + } + .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( + 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..6ae97da891 --- /dev/null +++ b/FakeNFT/Scenes /Statistics/UserCollectionView/UserCollectionView.swift @@ -0,0 +1,81 @@ +// +// UserCollectionView.swift +// FakeNFT +// +// Created by Anastasia on 04.06.2025. +// + +import SwiftUI + +struct UserCollectionView: View { + + // MARK: - Properties + + @ObservedObject var viewModel: UserCollectionViewModel + + // MARK: - Initializers + + init(nftIds: [String], service: ServicesAssembly) { + self.viewModel = UserCollectionViewModel(nftIds: nftIds, service: service) + } + + // MARK: - Content + + var body: some View { + content + .modifier(NavigationBarStyle( + title: "Коллекция NFT", + backButtonHidden: false, + filterButtonHidden: true, + filterButtonTapHandler: {} + )) + .onAppear { + viewModel.loadData() + } + .alert(isPresented: $viewModel.isShowingErrorAlert) { + Alert( + title: Text("Не удалось получить данные"), + primaryButton: .default( + Text("Отмена") + ), + secondaryButton: .default( + Text("Повторить"), + action: { + viewModel.loadData() + } + ) + ) + } + .toolbar(.hidden, for: .tabBar) + } + + // MARK: - View + + private var content: some View { + ZStack { + UserNFTCollection( + nftInfo: viewModel.nftInfo, + userLikes: viewModel.userLikes, + userOrders: viewModel.userOrders, + likeTapHandler: { nft in + viewModel.updateLike(nftId: nft.id) + }, + cartTapHandler: { nft in + viewModel.updateUserOrder(nftId: nft.id) + } + ) + .padding(.top, StatisticsConstants.topAnchorSmall) + Text("Пусто") + .font(.bold17) + .opacity(viewModel.nftIds.isEmpty ? 1 : 0) + LoadingView() + .opacity(viewModel.isLoading ? 1 : 0) + } + } +} + +#Preview { + NavigationStack { + UserCollectionView(nftIds: [""], service: ServicesAssembly()) + } +} diff --git a/FakeNFT/Scenes /TabBar/TabBarView.swift b/FakeNFT/Scenes /TabBar/TabBarView.swift new file mode 100644 index 0000000000..01ef9788ed --- /dev/null +++ b/FakeNFT/Scenes /TabBar/TabBarView.swift @@ -0,0 +1,52 @@ +// +// TabBarView.swift +// FakeNFT +// +// Created by Anastasia on 25.05.2025. +// + +import SwiftUI + +struct TabBarView: View { + + @Binding var selectedTab: Tab + + var body: some View { + HStack { + ForEach(Tab.allCases.prefix(4), id: \.self) { tab in + Button { + self.selectedTab = tab + } label: { + tabItemView(for: tab) + } + .tint(selectedTab == tab ? .yaBlueUniversal : .blackDay) + } + } + .padding(.horizontal) + .onAppear { + setupTabBarAppearance() + } + } + + private func tabItemView(for tab: Tab) -> some View { + VStack { + Image("\(tab.imageName)") + .resizable() + .frame(width: 30, height: 30) + Text(tab.title) + .font(.medium10) + } + .frame(maxWidth: .infinity) + } + private func setupTabBarAppearance() { + let tabBarAppearance = UITabBarAppearance() + tabBarAppearance.configureWithOpaqueBackground() + tabBarAppearance.shadowColor = .clear + UITabBar.appearance().standardAppearance = tabBarAppearance + } +} + +#Preview { + Spacer() + TabBarView(selectedTab: .constant(.profile)) +} diff --git a/FakeNFT/Scenes /TabBarController/TabBarController.swift b/FakeNFT/Scenes /TabBarController/TabBarController.swift deleted file mode 100644 index 99931a214a..0000000000 --- a/FakeNFT/Scenes /TabBarController/TabBarController.swift +++ /dev/null @@ -1,25 +0,0 @@ -import UIKit - -final class TabBarController: UITabBarController { - - var servicesAssembly: ServicesAssembly! - - private let catalogTabBarItem = UITabBarItem( - title: NSLocalizedString("Tab.catalog", comment: ""), - image: UIImage(systemName: "square.stack.3d.up.fill"), - tag: 0 - ) - - override func viewDidLoad() { - super.viewDidLoad() - - let catalogController = TestCatalogViewController( - servicesAssembly: servicesAssembly - ) - catalogController.tabBarItem = catalogTabBarItem - - viewControllers = [catalogController] - - view.backgroundColor = .systemBackground - } -} diff --git a/FakeNFT/Scenes /TabBarView/TabBarView.swift b/FakeNFT/Scenes /TabBarView/TabBarView.swift new file mode 100644 index 0000000000..0deccf93d8 --- /dev/null +++ b/FakeNFT/Scenes /TabBarView/TabBarView.swift @@ -0,0 +1,18 @@ +// +// TabBarView.swift +// FakeNFT +// +// Created by Max on 24.05.2025. +// + +import SwiftUI + +struct TabBarView: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +#Preview { + TabBarView() +} diff --git a/FakeNFT/Scenes /ViewModifier/NavigationBarStyle.swift b/FakeNFT/Scenes /ViewModifier/NavigationBarStyle.swift new file mode 100644 index 0000000000..43ef95d464 --- /dev/null +++ b/FakeNFT/Scenes /ViewModifier/NavigationBarStyle.swift @@ -0,0 +1,124 @@ +// +// NavigationBarStyle.swift +// FakeNFT +// +// Created by Anastasia on 25.05.2025. +// + +import SwiftUI + +struct NavigationBarStyle: ViewModifier { + + // MARK: - Properties + + @Environment(\.dismiss) var dismiss + + let title: String? + let backButtonHidden: Bool + let filterButtonHidden: Bool + let isTabBarHidden: Binding? + var filterButtonTapHandler: () -> Void? + + // MARK: - Initializers + + init(title: String?, + backButtonHidden: Bool, + filterButtonHidden: Bool, + isTabBarHidden: Binding? = nil, + filterButtonTapHandler: @escaping () -> Void? + ) { + self.title = title + self.backButtonHidden = backButtonHidden + self.filterButtonHidden = filterButtonHidden + self.isTabBarHidden = isTabBarHidden + self.filterButtonTapHandler = filterButtonTapHandler + setupNavigationBarAppearance() + } + + // MARK: - Content + + func body(content: Content) -> some View { + content + .navigationTitle(title ?? "") + .navigationBarTitleDisplayMode(.inline) + .navigationBarBackButtonHidden(true) + .toolbar { + ToolbarItem(placement: .topBarLeading) { + backButton + .opacity(backButtonHidden ? 0 : 1) + } + ToolbarItem(placement: .topBarTrailing) { + filterButton + .opacity(filterButtonHidden ? 0 : 1) + } + } + } + + // MARK: - View + + private var backButton: some View { + Button { + isTabBarHidden?.wrappedValue = false + dismiss() + } label: { + Image("backButton") + .resizable() + .scaledToFit() + .frame(width: 24, height: 24) + .tint(.blackDay) + } + } + + private var filterButton: some View { + Button { + filterButtonTapHandler() + } label: { + Image("filterButton") + .resizable() + .scaledToFit() + .frame(width: 42, height: 42) + .tint(.blackDay) + } + } + + // MARK: - Private Methods + + private func setupNavigationBarAppearance() { + let navBarAppearance = UINavigationBarAppearance() + navBarAppearance.configureWithOpaqueBackground() + navBarAppearance.shadowColor = .clear + UINavigationBar.appearance().standardAppearance = navBarAppearance + } +} + +#Preview { + NavigationView { + Text("Пример использования NavigationBarStyle с разными настройками") + .modifier(NavigationBarStyle( + title: "Мои NFT", + backButtonHidden: false, + filterButtonHidden: false, + filterButtonTapHandler: {} + )) + } + + NavigationView { + Text("Пример использования NavigationBarStyle с разными настройками") + .modifier(NavigationBarStyle( + title: "Коллекция NFT", + backButtonHidden: false, + filterButtonHidden: true, + filterButtonTapHandler: {} + )) + } + + NavigationView { + Text("Пример использования NavigationBarStyle с разными настройками") + .modifier(NavigationBarStyle( + title: nil, + backButtonHidden: false, + filterButtonHidden: true, + filterButtonTapHandler: {} + )) + } +} diff --git a/FakeNFT/Services/LikesService.swift b/FakeNFT/Services/LikesService.swift new file mode 100644 index 0000000000..1e4228bf2f --- /dev/null +++ b/FakeNFT/Services/LikesService.swift @@ -0,0 +1,45 @@ +// +// LikesService.swift +// FakeNFT +// +// Created by Anastasia on 07.06.2025. +// + +import Foundation + +typealias LikesCompletion = (Result) -> Void +typealias LikesPutCompletion = (Result) -> Void + +protocol LikesService { + func loadLikes(completion: @escaping LikesCompletion) + func updateLikes(likes: UserLikes, completion: @escaping LikesPutCompletion) +} + +final class LikesServiceImpl: LikesService { + + private let networkClient: NetworkClient + + init(networkClient: NetworkClient) { + self.networkClient = networkClient + } + + func loadLikes(completion: @escaping LikesCompletion) { + let request = LikesRequest() + networkClient.send(request: request, type: UserLikes.self) { result in + completion(result) + } + } + + func updateLikes(likes: UserLikes, completion: @escaping LikesPutCompletion) { + let dto = LikesDtoObject(likes: likes.likes) + let request = LikesPutRequest(dto: dto) + networkClient.send(request: request) { result in + switch result { + case .success: + completion(.success(())) + case .failure(let error): + completion(.failure(error)) + } + } + } +} diff --git a/FakeNFT/Services/NftInfoService.swift b/FakeNFT/Services/NftInfoService.swift new file mode 100644 index 0000000000..0e3f873ebd --- /dev/null +++ b/FakeNFT/Services/NftInfoService.swift @@ -0,0 +1,30 @@ +// +// NftInfoService.swift +// FakeNFT +// +// Created by Anastasia on 07.06.2025. +// + +import Foundation + +typealias NftInfoCompletion = (Result) -> Void + +protocol NftInfoService { + func loadNftInfo(id: String, completion: @escaping NftInfoCompletion) +} + +final class NftInfoServiceImpl: NftInfoService { + + private let networkClient: NetworkClient + + init(networkClient: NetworkClient) { + self.networkClient = networkClient + } + + func loadNftInfo(id: String, completion: @escaping NftInfoCompletion) { + let request = NftInfoRequest(id: id) + networkClient.send(request: request, type: NftInfo.self) { result in + completion(result) + } + } +} diff --git a/FakeNFT/Services/Requests/LikesRequest.swift b/FakeNFT/Services/Requests/LikesRequest.swift new file mode 100644 index 0000000000..4f0affe24c --- /dev/null +++ b/FakeNFT/Services/Requests/LikesRequest.swift @@ -0,0 +1,31 @@ +// +// LikesRequest.swift +// FakeNFT +// +// Created by Anastasia on 07.06.2025. +// + +import Foundation + +struct LikesRequest: NetworkRequest { + var endpoint: URL? { + URL(string: "\(RequestConstants.baseURL)/api/v1/profile/1") + } + var dto: Dto? +} + +struct LikesPutRequest: NetworkRequest { + var endpoint: URL? { + URL(string: "\(RequestConstants.baseURL)/api/v1/profile/1") + } + var httpMethod: HttpMethod = .put + var dto: Dto? +} + +struct LikesDtoObject: Dto { + let likes: [String] + + func asDictionary() -> [String : String] { + ["likes" : likes.joined(separator: ",")] + } +} diff --git a/FakeNFT/Services/Requests/NftInfoRequest.swift b/FakeNFT/Services/Requests/NftInfoRequest.swift new file mode 100644 index 0000000000..4071071d89 --- /dev/null +++ b/FakeNFT/Services/Requests/NftInfoRequest.swift @@ -0,0 +1,16 @@ +// +// NftInfoRequest.swift +// FakeNFT +// +// Created by Anastasia on 07.06.2025. +// + +import Foundation + +struct NftInfoRequest: NetworkRequest { + let id: String + var endpoint: URL? { + URL(string: "\(RequestConstants.baseURL)/api/v1/nft/\(id)") + } + var dto: Dto? +} diff --git a/FakeNFT/Services/Requests/RequestConstants.swift b/FakeNFT/Services/Requests/RequestConstants.swift index 9d3529893b..abd529c431 100644 --- a/FakeNFT/Services/Requests/RequestConstants.swift +++ b/FakeNFT/Services/Requests/RequestConstants.swift @@ -1,5 +1,4 @@ enum RequestConstants { static let baseURL = "https://d5dn3j2ouj72b0ejucbl.apigw.yandexcloud.net" - #warning("Instert your token here") - static let token = "" + static let token = "aa87dab8-01e5-4aa8-957e-951e3edcb363" } diff --git a/FakeNFT/Services/Requests/UserOrdersRequest.swift b/FakeNFT/Services/Requests/UserOrdersRequest.swift new file mode 100644 index 0000000000..0b682a321f --- /dev/null +++ b/FakeNFT/Services/Requests/UserOrdersRequest.swift @@ -0,0 +1,31 @@ +// +// UserOrdersRequest.swift +// FakeNFT +// +// Created by Anastasia on 07.06.2025. +// + +import Foundation + +struct UserOrdersRequest: NetworkRequest { + var endpoint: URL? { + URL(string: "\(RequestConstants.baseURL)/api/v1/orders/1") + } + var dto: Dto? +} + +struct UserOrdersPutRequest: NetworkRequest { + var endpoint: URL? { + URL(string: "\(RequestConstants.baseURL)/api/v1/orders/1") + } + var httpMethod: HttpMethod = .put + var dto: Dto? +} + +struct UserOrdersDtoObject: Dto { + let nfts: [String] + + func asDictionary() -> [String : String] { + ["nfts" : nfts.joined(separator: ",")] + } +} diff --git a/FakeNFT/Services/Requests/UsersRequest.swift b/FakeNFT/Services/Requests/UsersRequest.swift new file mode 100644 index 0000000000..d9e8bf878f --- /dev/null +++ b/FakeNFT/Services/Requests/UsersRequest.swift @@ -0,0 +1,15 @@ +// +// UsersRequest.swift +// FakeNFT +// +// Created by Anastasia on 03.06.2025. +// + +import Foundation + +struct UsersRequest: NetworkRequest { + var endpoint: URL? { + URL(string: "\(RequestConstants.baseURL)/api/v1/users") + } + var dto: Dto? +} diff --git a/FakeNFT/Services/ServicesAssemly.swift b/FakeNFT/Services/ServicesAssemly.swift index 033f8bdf61..511a2ed148 100644 --- a/FakeNFT/Services/ServicesAssemly.swift +++ b/FakeNFT/Services/ServicesAssemly.swift @@ -1,11 +1,13 @@ -final class ServicesAssembly { +import Foundation + +final class ServicesAssembly: ObservableObject { private let networkClient: NetworkClient private let nftStorage: NftStorage init( - networkClient: NetworkClient, - nftStorage: NftStorage + networkClient: NetworkClient = DefaultNetworkClient(), + nftStorage: NftStorage = NftStorageImpl() ) { self.networkClient = networkClient self.nftStorage = nftStorage @@ -17,4 +19,20 @@ final class ServicesAssembly { storage: nftStorage ) } + + var usersService: UsersService { + UsersServiceImpl(networkClient: networkClient) + } + + var nftInfoService: NftInfoService { + NftInfoServiceImpl(networkClient: networkClient) + } + + var likesService: LikesService { + LikesServiceImpl(networkClient: networkClient) + } + + var userOrdersService: UserOrdersService { + UserOrdersImpl(networkClient: networkClient) + } } diff --git a/FakeNFT/Services/UserOrdersService.swift b/FakeNFT/Services/UserOrdersService.swift new file mode 100644 index 0000000000..f59007b418 --- /dev/null +++ b/FakeNFT/Services/UserOrdersService.swift @@ -0,0 +1,45 @@ +// +// UserOrdersService.swift +// FakeNFT +// +// Created by Anastasia on 07.06.2025. +// + +import Foundation + +typealias UserOrdersCompletion = (Result) -> Void +typealias UserOrdersPutCompletion = (Result) -> Void + +protocol UserOrdersService { + func loadUserOrders(completion: @escaping UserOrdersCompletion) + func updateUserOrders(nfts: UserOrders, completion: @escaping UserOrdersPutCompletion) +} + +final class UserOrdersImpl: UserOrdersService { + + private let networkClient: NetworkClient + + init(networkClient: NetworkClient) { + self.networkClient = networkClient + } + + func loadUserOrders(completion: @escaping UserOrdersCompletion) { + let request = UserOrdersRequest() + networkClient.send(request: request, type: UserOrders.self) { result in + completion(result) + } + } + + func updateUserOrders(nfts: UserOrders, completion: @escaping UserOrdersPutCompletion) { + let dto = UserOrdersDtoObject(nfts: nfts.nfts) + let request = UserOrdersPutRequest(dto: dto) + networkClient.send(request: request) { result in + switch result { + case .success: + completion(.success(())) + case .failure(let error): + completion(.failure(error)) + } + } + } +} diff --git a/FakeNFT/Services/UsersService.swift b/FakeNFT/Services/UsersService.swift new file mode 100644 index 0000000000..af1b104957 --- /dev/null +++ b/FakeNFT/Services/UsersService.swift @@ -0,0 +1,30 @@ +// +// UsersService.swift +// FakeNFT +// +// Created by Anastasia on 03.06.2025. +// + +import Foundation + +typealias UsersCompletion = (Result<[User], Error>) -> Void + +protocol UsersService { + func loadUsers(completion: @escaping UsersCompletion) +} + +final class UsersServiceImpl: UsersService { + + private let networkClient: NetworkClient + + init(networkClient: NetworkClient) { + self.networkClient = networkClient + } + + func loadUsers(completion: @escaping UsersCompletion) { + let request = UsersRequest() + networkClient.send(request: request, type: [User].self) { result in + completion(result) + } + } +} diff --git a/FakeNFT/StatisticsConstants.swift b/FakeNFT/StatisticsConstants.swift new file mode 100644 index 0000000000..89d92feb02 --- /dev/null +++ b/FakeNFT/StatisticsConstants.swift @@ -0,0 +1,30 @@ +// +// StatisticsConstants.swift +// FakeNFT +// +// Created by Anastasia on 03.06.2025. +// + +import Foundation + +struct StatisticsConstants { + static let anchorSmall: CGFloat = 8.0 + static let topAnchorSmall: CGFloat = 20.0 + static let topAnchorMedium: CGFloat = 28.0 + static let topAnchorLarge: CGFloat = 40.0 + + static let cornerRadiusSmall: CGFloat = 12.0 + static let cornerRadiusMedium: CGFloat = 16.0 + + static let ratingRowHeight: CGFloat = 80.0 + 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 + + static let collectionRowSize: CGFloat = 108.0 + static let rowAnchorSmall: CGFloat = 5.0 + static let rowAnchorMedium: CGFloat = 20.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 + } +} diff --git a/FakeNFT/UserCollectionViewModel.swift b/FakeNFT/UserCollectionViewModel.swift new file mode 100644 index 0000000000..ab4cfe3356 --- /dev/null +++ b/FakeNFT/UserCollectionViewModel.swift @@ -0,0 +1,145 @@ +// +// UserCollectionViewModel.swift +// FakeNFT +// +// Created by Anastasia on 07.06.2025. +// + +import Foundation + +@MainActor +final class UserCollectionViewModel: ObservableObject { + + // MARK: - Properties + + @Published var nftIds: [String] + @Published var nftInfo: [NftInfo] = [] + @Published var userLikes = UserLikes(likes: []) + @Published var userOrders = UserOrders(nfts: []) + @Published var isLoading: Bool = false + @Published var isShowingErrorAlert: Bool = false + + private let nftInfoService: NftInfoService + private let likesService: LikesService + private let userOrdersService: UserOrdersService + + // MARK: - Initializers + + init(nftIds: [String], service: ServicesAssembly) { + self.nftIds = nftIds + self.nftInfoService = service.nftInfoService + self.likesService = service.likesService + self.userOrdersService = service.userOrdersService + } + + // MARK: - Public Methods + + func loadData() { + if nftIds.isEmpty { return } + if !nftInfo.isEmpty { return } + isLoading = true + + let group = DispatchGroup() + + loadNftInfo(group) + loadLikes(group) + loadUserOrders(group) + + group.notify(queue: .main) { + self.isLoading = false + } + } + + func updateLike(nftId: String) { + userLikes = UserLikes( + likes: updateData( + array: userLikes.likes, + nftId: nftId + )) + + likesService.updateLikes(likes: userLikes) { result in + switch result { + case .success(): + print("Likes updated") + case .failure(let error): + print("Error update: \(error)") + } + } + } + + func updateUserOrder(nftId: String) { + userOrders = UserOrders( + nfts: updateData( + array: userOrders.nfts, + nftId: nftId + )) + + userOrdersService.updateUserOrders(nfts: userOrders) { result in + switch result { + case .success(): + print("Orders updated") + case .failure(let error): + print("Error update: \(error)") + } + } + } + + // MARK: - Private Methods + + private func loadNftInfo(_ group: DispatchGroup) { + for id in nftIds { + group.enter() + nftInfoService.loadNftInfo(id: id) { [weak self] result in + guard let self else { return } + switch result { + case .success(let nftInfo): + self.nftInfo.append(nftInfo) + case .failure(let error): + print("Error loading nft info: \(error)") + self.isShowingErrorAlert = true + } + group.leave() + } + } + } + + private func loadLikes(_ group: DispatchGroup) { + group.enter() + likesService.loadLikes() { [weak self] result in + guard let self else { return } + switch result { + case .success(let likes): + self.userLikes = likes + case .failure(let error): + print("Error loading likes: \(error)") + self.isShowingErrorAlert = true + } + group.leave() + } + } + + private func loadUserOrders(_ group: DispatchGroup) { + group.enter() + userOrdersService.loadUserOrders() { [weak self] result in + guard let self else { return } + switch result { + case .success(let orders): + self.userOrders = orders + case .failure(let error): + print("Error loading user orders: \(error)") + self.isShowingErrorAlert = true + } + group.leave() + } + } + + private func updateData(array: [T], nftId: T) -> [T] { + var updatedArray = array + if array.contains(nftId) { + updatedArray.removeAll { $0 == nftId } + } else { + updatedArray.append(nftId) + } + return updatedArray + } +}