diff --git a/HappyAnding/HappyAnding.xcodeproj/project.pbxproj b/HappyAnding/HappyAnding.xcodeproj/project.pbxproj index e68c9c0a..ef190551 100644 --- a/HappyAnding/HappyAnding.xcodeproj/project.pbxproj +++ b/HappyAnding/HappyAnding.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 4D061BB82A47531800F76835 /* ExploreShortcutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D061BB72A47531800F76835 /* ExploreShortcutView.swift */; }; + 4D061BBA2A475EE800F76835 /* ExploreShortcutViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D061BB92A475EE800F76835 /* ExploreShortcutViewModel.swift */; }; 4D3DBB88292E67E600DE8160 /* EditNicknameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D3DBB87292E67E500DE8160 /* EditNicknameView.swift */; }; 4D3DBB962934E31A00DE8160 /* ShowProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D3DBB952934E31A00DE8160 /* ShowProfileView.swift */; }; 4D61A767291E1EE8000EF531 /* NavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D61A766291E1EE8000EF531 /* NavigationViewModel.swift */; }; @@ -15,7 +17,11 @@ 4D778A34290A53BA00C15AC4 /* UIApplication+Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D778A33290A53BA00C15AC4 /* UIApplication+Keyboard.swift */; }; 4D7D16072986BBD7008B3332 /* TextLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D7D16062986BBD7008B3332 /* TextLiteral.swift */; }; 4D7D16082986BBDE008B3332 /* TextLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D7D16062986BBD7008B3332 /* TextLiteral.swift */; }; + 4D93D06F2A5956E60042CBA8 /* ShowProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D93D06E2A5956E60042CBA8 /* ShowProfileViewModel.swift */; }; + 4D93D0752A61D0D10042CBA8 /* ReadShortcutViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D93D0742A61D0D10042CBA8 /* ReadShortcutViewModel.swift */; }; 4DAD635E292AB61700ABF8C1 /* UpdateShortcutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DAD635D292AB61700ABF8C1 /* UpdateShortcutView.swift */; }; + 4DF15D732A4ECC7D0014F854 /* ListShortcutViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DF15D722A4ECC7D0014F854 /* ListShortcutViewModel.swift */; }; + 4DF15D752A4ECE1F0014F854 /* ListCategoryShortcutViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DF15D742A4ECE1F0014F854 /* ListCategoryShortcutViewModel.swift */; }; 4DF62DD52A0550ED00A8B377 /* UIScreen+Size.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8795A16F292AB945004B765F /* UIScreen+Size.swift */; }; 87276C382933F6AB00C92F4C /* CustomTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87276C372933F6AB00C92F4C /* CustomTextEditor.swift */; }; 872A7D8F2918393B004A05B8 /* PrivacyPolicyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 872A7D8E2918393B004A05B8 /* PrivacyPolicyView.swift */; }; @@ -33,9 +39,12 @@ 8786B33C29ABA588000B46A1 /* View+Gesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8786B33B29ABA588000B46A1 /* View+Gesture.swift */; }; 8786B33E29ABA5A9000B46A1 /* View+Shape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8786B33D29ABA5A9000B46A1 /* View+Shape.swift */; }; 8788374A2920D549009B3F54 /* Binding+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 878837492920D549009B3F54 /* Binding+Extension.swift */; }; + 8788E19D2A475AB3007C3852 /* ListCurationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8788E19C2A475AB3007C3852 /* ListCurationViewModel.swift */; }; + 8788E1A02A48408F007C3852 /* ExploreCurationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8788E19F2A48408F007C3852 /* ExploreCurationViewModel.swift */; }; 8792478D2918CE450040D5C3 /* UINavigationContoller+Gesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8792478C2918CE450040D5C3 /* UINavigationContoller+Gesture.swift */; }; 8792479B291BDF820040D5C3 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8792479A291BDF820040D5C3 /* SearchView.swift */; }; 8795A170292AB945004B765F /* UIScreen+Size.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8795A16F292AB945004B765F /* UIScreen+Size.swift */; }; + 87B47F3B2A3DC2740009E75F /* ReadCurationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87B47F3A2A3DC2740009E75F /* ReadCurationViewModel.swift */; }; 87CFD8492939187200F97B86 /* NicknameTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CFD8482939187200F97B86 /* NicknameTextField.swift */; }; 87DBFB062A2127C0000CC442 /* CheckVersionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87DBFB052A2127C0000CC442 /* CheckVersionView.swift */; }; 87E606B0291062F900C3DA13 /* AppleAuthCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87E606AF291062F900C3DA13 /* AppleAuthCoordinator.swift */; }; @@ -52,7 +61,6 @@ 87E99CA128FFF225009B691F /* MyPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87E99CA028FFF225009B691F /* MyPageView.swift */; }; 87E99CA328FFF22E009B691F /* ExploreCurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87E99CA228FFF22E009B691F /* ExploreCurationView.swift */; }; 87E99CA728FFF23E009B691F /* ListCurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87E99CA628FFF23E009B691F /* ListCurationView.swift */; }; - 87E99CA928FFF24F009B691F /* ExploreShortcutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87E99CA828FFF24F009B691F /* ExploreShortcutView.swift */; }; 87E99CAD28FFF261009B691F /* ReadShortcutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87E99CAC28FFF261009B691F /* ReadShortcutView.swift */; }; 87E99CB128FFF273009B691F /* WriteCurationSetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87E99CB028FFF273009B691F /* WriteCurationSetView.swift */; }; 87E99CB528FFF282009B691F /* WriteCurationInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87E99CB428FFF282009B691F /* WriteCurationInfoView.swift */; }; @@ -78,7 +86,6 @@ A31F1844292A637300AF4A82 /* Date+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9CAEF822914855900224B0A /* Date+String.swift */; }; A31F1846292A638700AF4A82 /* (null) in Sources */ = {isa = PBXBuildFile; }; A31F1848292A64D700AF4A82 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 87E99C7128F94EA8009B691F /* Assets.xcassets */; }; - A33F74AE2908D8C800B8D0D0 /* CheckBoxShortcutCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A33F74AD2908D8C800B8D0D0 /* CheckBoxShortcutCell.swift */; }; A3439AF529395A100043E273 /* UserAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87E606B729114FB200C3DA13 /* UserAuth.swift */; }; A3439AF629395A3A0043E273 /* CustomTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87276C372933F6AB00C92F4C /* CustomTextEditor.swift */; }; A3439AFB2939B0E80043E273 /* UserDefaults+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3439AFA2939B0E80043E273 /* UserDefaults+Extension.swift */; }; @@ -124,6 +131,7 @@ F94B435F2907B19A00987819 /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = F94B435E2907B19A00987819 /* FirebaseAuth */; }; F94B43612907B19A00987819 /* FirebaseFirestore in Frameworks */ = {isa = PBXBuildFile; productRef = F94B43602907B19A00987819 /* FirebaseFirestore */; }; F94B43632907B19A00987819 /* FirebaseFirestoreCombine-Community in Frameworks */ = {isa = PBXBuildFile; productRef = F94B43622907B19A00987819 /* FirebaseFirestoreCombine-Community */; }; + F95F26542A45C89400B534EB /* WriteCurationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F95F26532A45C89400B534EB /* WriteCurationViewModel.swift */; }; F96D45B72980301F000C2441 /* SubtitleTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F96D45B62980301F000C2441 /* SubtitleTextView.swift */; }; F96D45BB29804057000C2441 /* EnvironmentValues+Alerter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F96D45BA29804057000C2441 /* EnvironmentValues+Alerter.swift */; }; F96D45BD29816578000C2441 /* StickyHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = F96D45BC29816578000C2441 /* StickyHeader.swift */; }; @@ -183,13 +191,19 @@ /* Begin PBXFileReference section */ 3D41EE06290A458B008BE986 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 3D41EE07290A4C18008BE986 /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; + 4D061BB72A47531800F76835 /* ExploreShortcutView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExploreShortcutView.swift; sourceTree = ""; }; + 4D061BB92A475EE800F76835 /* ExploreShortcutViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreShortcutViewModel.swift; sourceTree = ""; }; 4D3DBB87292E67E500DE8160 /* EditNicknameView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditNicknameView.swift; sourceTree = ""; }; 4D3DBB952934E31A00DE8160 /* ShowProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShowProfileView.swift; sourceTree = ""; }; 4D61A766291E1EE8000EF531 /* NavigationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewModel.swift; sourceTree = ""; }; 4D6A9F0029A3A92E00D02522 /* wrappinghstack+license.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "wrappinghstack+license.txt"; sourceTree = ""; }; 4D778A33290A53BA00C15AC4 /* UIApplication+Keyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Keyboard.swift"; sourceTree = ""; }; 4D7D16062986BBD7008B3332 /* TextLiteral.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TextLiteral.swift; path = HappyAnding/TextLiteral.swift; sourceTree = SOURCE_ROOT; }; + 4D93D06E2A5956E60042CBA8 /* ShowProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShowProfileViewModel.swift; sourceTree = ""; }; + 4D93D0742A61D0D10042CBA8 /* ReadShortcutViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadShortcutViewModel.swift; sourceTree = ""; }; 4DAD635D292AB61700ABF8C1 /* UpdateShortcutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateShortcutView.swift; sourceTree = ""; }; + 4DF15D722A4ECC7D0014F854 /* ListShortcutViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListShortcutViewModel.swift; sourceTree = ""; }; + 4DF15D742A4ECE1F0014F854 /* ListCategoryShortcutViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCategoryShortcutViewModel.swift; sourceTree = ""; }; 87276C372933F6AB00C92F4C /* CustomTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextEditor.swift; sourceTree = ""; }; 872A7D8E2918393B004A05B8 /* PrivacyPolicyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyPolicyView.swift; sourceTree = ""; }; 872B5D3C2A2E0FF9008DCC57 /* CurationType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurationType.swift; sourceTree = ""; }; @@ -203,9 +217,12 @@ 8786B33B29ABA588000B46A1 /* View+Gesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Gesture.swift"; sourceTree = ""; }; 8786B33D29ABA5A9000B46A1 /* View+Shape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Shape.swift"; sourceTree = ""; }; 878837492920D549009B3F54 /* Binding+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+Extension.swift"; sourceTree = ""; }; + 8788E19C2A475AB3007C3852 /* ListCurationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCurationViewModel.swift; sourceTree = ""; }; + 8788E19F2A48408F007C3852 /* ExploreCurationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreCurationViewModel.swift; sourceTree = ""; }; 8792478C2918CE450040D5C3 /* UINavigationContoller+Gesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationContoller+Gesture.swift"; sourceTree = ""; }; 8792479A291BDF820040D5C3 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = ""; }; 8795A16F292AB945004B765F /* UIScreen+Size.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScreen+Size.swift"; sourceTree = ""; }; + 87B47F3A2A3DC2740009E75F /* ReadCurationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadCurationViewModel.swift; sourceTree = ""; }; 87CFD8482939187200F97B86 /* NicknameTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameTextField.swift; sourceTree = ""; }; 87DBFB052A2127C0000CC442 /* CheckVersionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckVersionView.swift; sourceTree = ""; }; 87E606AD2910623C00C3DA13 /* HappyAnding.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = HappyAnding.entitlements; sourceTree = ""; }; @@ -227,7 +244,6 @@ 87E99CA028FFF225009B691F /* MyPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageView.swift; sourceTree = ""; }; 87E99CA228FFF22E009B691F /* ExploreCurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreCurationView.swift; sourceTree = ""; }; 87E99CA628FFF23E009B691F /* ListCurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCurationView.swift; sourceTree = ""; }; - 87E99CA828FFF24F009B691F /* ExploreShortcutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ExploreShortcutView.swift; path = ExploreShortcutView/ExploreShortcutView.swift; sourceTree = ""; }; 87E99CAC28FFF261009B691F /* ReadShortcutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadShortcutView.swift; sourceTree = ""; }; 87E99CB028FFF273009B691F /* WriteCurationSetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteCurationSetView.swift; sourceTree = ""; }; 87E99CB428FFF282009B691F /* WriteCurationInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteCurationInfoView.swift; sourceTree = ""; }; @@ -250,7 +266,6 @@ A0DD085629276608008177BB /* URL+DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+DeepLink.swift"; sourceTree = ""; }; A0F822AB2910B8F100AF4448 /* ShortcutsZipViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsZipViewModel.swift; sourceTree = ""; }; A0F822B629164D2300AF4448 /* ListCategoryShortcutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListCategoryShortcutView.swift; sourceTree = ""; }; - A33F74AD2908D8C800B8D0D0 /* CheckBoxShortcutCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckBoxShortcutCell.swift; sourceTree = ""; }; A3439AFA2939B0E80043E273 /* UserDefaults+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Extension.swift"; sourceTree = ""; }; A34BF82729AF3D55009BC946 /* AnnouncementCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnouncementCell.swift; sourceTree = ""; }; A34BF82C29AFC34F009BC946 /* AboutShortcutGradeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutShortcutGradeView.swift; sourceTree = ""; }; @@ -277,6 +292,7 @@ F91F09DC29AE012600E04FA0 /* ShortcutGrade.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutGrade.swift; sourceTree = ""; }; F91F09DE29AE0B5E00E04FA0 /* GradeAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradeAlertView.swift; sourceTree = ""; }; F94B432D2907088400987819 /* UserCurationListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserCurationListView.swift; sourceTree = ""; }; + F95F26532A45C89400B534EB /* WriteCurationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteCurationViewModel.swift; sourceTree = ""; }; F96D45B62980301F000C2441 /* SubtitleTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubtitleTextView.swift; sourceTree = ""; }; F96D45BA29804057000C2441 /* EnvironmentValues+Alerter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EnvironmentValues+Alerter.swift"; sourceTree = ""; }; F96D45BC29816578000C2441 /* StickyHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyHeader.swift; sourceTree = ""; }; @@ -356,6 +372,56 @@ path = View; sourceTree = ""; }; + 8788E19E2A483FDF007C3852 /* ExploreCurationViewModels */ = { + isa = PBXGroup; + children = ( + 87B47F3A2A3DC2740009E75F /* ReadCurationViewModel.swift */, + 8788E19C2A475AB3007C3852 /* ListCurationViewModel.swift */, + 8788E19F2A48408F007C3852 /* ExploreCurationViewModel.swift */, + ); + path = ExploreCurationViewModels; + sourceTree = ""; + }; + 8788E1A12A484518007C3852 /* ExploreShortcutViewModels */ = { + isa = PBXGroup; + children = ( + 4D061BB92A475EE800F76835 /* ExploreShortcutViewModel.swift */, + 4DF15D722A4ECC7D0014F854 /* ListShortcutViewModel.swift */, + 4DF15D742A4ECE1F0014F854 /* ListCategoryShortcutViewModel.swift */, + ); + path = ExploreShortcutViewModels; + sourceTree = ""; + }; + 8788E1A22A484528007C3852 /* WriteShortcutViewModels */ = { + isa = PBXGroup; + children = ( + ); + path = WriteShortcutViewModels; + sourceTree = ""; + }; + 8788E1A32A484533007C3852 /* ReadShortcutViewModels */ = { + isa = PBXGroup; + children = ( + 4D93D0742A61D0D10042CBA8 /* ReadShortcutViewModel.swift */, + 4D93D06E2A5956E60042CBA8 /* ShowProfileViewModel.swift */, + ); + path = ReadShortcutViewModels; + sourceTree = ""; + }; + 8788E1A42A484542007C3852 /* WriteCurationViewModels */ = { + isa = PBXGroup; + children = ( + ); + path = WriteCurationViewModels; + sourceTree = ""; + }; + 8788E1A52A48456E007C3852 /* MyPageViewModels */ = { + isa = PBXGroup; + children = ( + ); + path = MyPageViewModels; + sourceTree = ""; + }; 87E606AE291062D300C3DA13 /* SignInViews */ = { isa = PBXGroup; children = ( @@ -471,7 +537,6 @@ children = ( 87E99CB028FFF273009B691F /* WriteCurationSetView.swift */, 87E99CB428FFF282009B691F /* WriteCurationInfoView.swift */, - A33F74AD2908D8C800B8D0D0 /* CheckBoxShortcutCell.swift */, ); path = WriteCurationViews; sourceTree = ""; @@ -489,7 +554,7 @@ 87E99C9B28FFF1F4009B691F /* ExploreShortcutViews */ = { isa = PBXGroup; children = ( - 87E99CA828FFF24F009B691F /* ExploreShortcutView.swift */, + 4D061BB72A47531800F76835 /* ExploreShortcutView.swift */, 87E99CC8290145B8009B691F /* ListShortcutView.swift */, A0F822B629164D2300AF4448 /* ListCategoryShortcutView.swift */, ); @@ -593,9 +658,16 @@ A0F822AA2910B8B900AF4448 /* ViewModel */ = { isa = PBXGroup; children = ( + 8788E1A12A484518007C3852 /* ExploreShortcutViewModels */, + 8788E1A22A484528007C3852 /* WriteShortcutViewModels */, + 8788E1A32A484533007C3852 /* ReadShortcutViewModels */, + 8788E19E2A483FDF007C3852 /* ExploreCurationViewModels */, + 8788E1A42A484542007C3852 /* WriteCurationViewModels */, + 8788E1A52A48456E007C3852 /* MyPageViewModels */, A0F822AB2910B8F100AF4448 /* ShortcutsZipViewModel.swift */, 4D61A766291E1EE8000EF531 /* NavigationViewModel.swift */, F9AC2BB52935201C00165820 /* CheckUpdateVersion.swift */, + F95F26532A45C89400B534EB /* WriteCurationViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -852,9 +924,11 @@ F9131B6B2922D38D00868A0E /* Keyword.swift in Sources */, F94B432E2907088400987819 /* UserCurationListView.swift in Sources */, A3FF0183291648A300384211 /* MailView.swift in Sources */, + 4D061BBA2A475EE800F76835 /* ExploreShortcutViewModel.swift in Sources */, A0F822AC2910B8F100AF4448 /* ShortcutsZipViewModel.swift in Sources */, 87276C382933F6AB00C92F4C /* CustomTextEditor.swift in Sources */, A34BF82D29AFC34F009BC946 /* AboutShortcutGradeView.swift in Sources */, + 4D93D06F2A5956E60042CBA8 /* ShowProfileViewModel.swift in Sources */, 87E99CC429014572009B691F /* Color+Extension.swift in Sources */, 87CFD8492939187200F97B86 /* NicknameTextField.swift in Sources */, 4D3DBB962934E31A00DE8160 /* ShowProfileView.swift in Sources */, @@ -863,8 +937,10 @@ 87E99CD929042536009B691F /* SectionType.swift in Sources */, 872A7D8F2918393B004A05B8 /* PrivacyPolicyView.swift in Sources */, A38115BA292B447D0043E8B8 /* ShortcutCardCell.swift in Sources */, + F95F26542A45C89400B534EB /* WriteCurationViewModel.swift in Sources */, 8792479B291BDF820040D5C3 /* SearchView.swift in Sources */, A3FF018E291ACFA500384211 /* WithdrawalView.swift in Sources */, + 4DF15D732A4ECC7D0014F854 /* ListShortcutViewModel.swift in Sources */, 4D778A34290A53BA00C15AC4 /* UIApplication+Keyboard.swift in Sources */, A3766B232904330300708F83 /* ReadCurationView.swift in Sources */, 87E606B0291062F900C3DA13 /* AppleAuthCoordinator.swift in Sources */, @@ -872,6 +948,7 @@ 8795A170292AB945004B765F /* UIScreen+Size.swift in Sources */, 8786B2E629A7F987000B46A1 /* String+Date.swift in Sources */, F96D45B72980301F000C2441 /* SubtitleTextView.swift in Sources */, + 4DF15D752A4ECE1F0014F854 /* ListCategoryShortcutViewModel.swift in Sources */, 87E99CDB29042CCA009B691F /* Category.swift in Sources */, 876B4F6F299E3D91009672D9 /* NavigationRouter.swift in Sources */, A04ACB062903D0B2004A85A6 /* MyShortcutCardView.swift in Sources */, @@ -881,13 +958,13 @@ 4D7D16072986BBD7008B3332 /* TextLiteral.swift in Sources */, 87E99CC128FFF2B5009B691F /* CategoryModalView.swift in Sources */, 87E606B829114FB200C3DA13 /* UserAuth.swift in Sources */, + 8788E1A02A48408F007C3852 /* ExploreCurationViewModel.swift in Sources */, 8786B33E29ABA5A9000B46A1 /* View+Shape.swift in Sources */, A3C404D62A23D0E800C3BA75 /* UpdateInfoView.swift in Sources */, F91A72C1299915C500CA135A /* MoreCaptionTextView.swift in Sources */, 4DAD635E292AB61700ABF8C1 /* UpdateShortcutView.swift in Sources */, 87E99C9F28FFF21B009B691F /* SettingView.swift in Sources */, A34BF82829AF3D55009BC946 /* AnnouncementCell.swift in Sources */, - 87E99CA928FFF24F009B691F /* ExploreShortcutView.swift in Sources */, A04ACB082903DECC004A85A6 /* MyShortcutCardListView.swift in Sources */, 8786B2EC29A7FAD2000B46A1 /* UIColor+Extension.swift in Sources */, 87E99CA128FFF225009B691F /* MyPageView.swift in Sources */, @@ -899,13 +976,15 @@ F99569182901DC4D0060AAEF /* UIFont+Extension.swift in Sources */, F91A72C32999160E00CA135A /* Alerter.swift in Sources */, 87E99CAD28FFF261009B691F /* ReadShortcutView.swift in Sources */, - A33F74AE2908D8C800B8D0D0 /* CheckBoxShortcutCell.swift in Sources */, 87E606B22910649B00C3DA13 /* SignInWithAppleView.swift in Sources */, F91F09DF29AE0B5E00E04FA0 /* GradeAlertView.swift in Sources */, + 4D061BB82A47531800F76835 /* ExploreShortcutView.swift in Sources */, 87E99CEC29080C30009B691F /* Curation.swift in Sources */, + 8788E19D2A475AB3007C3852 /* ListCurationViewModel.swift in Sources */, F9136EB6293612310034AAB2 /* ShortcutsZipView.swift in Sources */, 87E99CB128FFF273009B691F /* WriteCurationSetView.swift in Sources */, 4D61A767291E1EE8000EF531 /* NavigationViewModel.swift in Sources */, + 4D93D0752A61D0D10042CBA8 /* ReadShortcutViewModel.swift in Sources */, F96D45BD29816578000C2441 /* StickyHeader.swift in Sources */, 87E99CEE29080D33009B691F /* User.swift in Sources */, F976E82C29368E0D0088BBA1 /* Version.swift in Sources */, @@ -927,6 +1006,7 @@ F9724BBF292755E400860F8A /* Comment.swift in Sources */, 87E99CA328FFF22E009B691F /* ExploreCurationView.swift in Sources */, A0F822B729164D2300AF4448 /* ListCategoryShortcutView.swift in Sources */, + 87B47F3B2A3DC2740009E75F /* ReadCurationViewModel.swift in Sources */, 872B5D3D2A2E0FF9008DCC57 /* CurationType.swift in Sources */, 4D3DBB88292E67E600DE8160 /* EditNicknameView.swift in Sources */, 87E99CE82907C6E6009B691F /* Shortcuts.swift in Sources */, diff --git a/HappyAnding/HappyAnding/Extensions/View/View+Navigation.swift b/HappyAnding/HappyAnding/Extensions/View/View+Navigation.swift index 4e6df8e1..76888014 100644 --- a/HappyAnding/HappyAnding/Extensions/View/View+Navigation.swift +++ b/HappyAnding/HappyAnding/Extensions/View/View+Navigation.swift @@ -67,8 +67,8 @@ extension View { @ViewBuilder func getDestination(data: T, isPresented: Binding) -> some View { switch data { - case is WriteCurationInfoType: - WriteCurationInfoView(data: data as! WriteCurationInfoType, isWriting: isPresented) + case is WriteCurationViewModel: + WriteCurationInfoView(viewModel: data as! WriteCurationViewModel, isWriting: isPresented) default: EmptyView() } @@ -77,20 +77,20 @@ extension View { @ViewBuilder func getDestination(data: T) -> some View { switch data { - case is NavigationListShortcutType: - ListShortcutView(data: data as! NavigationListShortcutType) - case is NavigationReadShortcutType: - ReadShortcutView(data: data as! NavigationReadShortcutType) - case is NavigationReadCurationType: - ReadCurationView(data: data as! NavigationReadCurationType) + case is SectionType: + ListShortcutView(viewModel: ListShortcutViewModel(data: data as! SectionType)) + case is Shortcuts: + ReadShortcutView(viewModel: ReadShortcutViewModel(data: data as! Shortcuts)) + case is Curation: + ReadCurationView(viewModel: ReadCurationViewModel(data: data as! Curation)) case is CurationType: - ListCurationView(curationType: data as! CurationType) - case is NavigationProfile: - ShowProfileView(data: data as! NavigationProfile) + ListCurationView(viewModel: ListCurationViewModel(data: data as! CurationType)) + case is User: + ShowProfileView(viewModel: ShowProfileViewModel(data: data as! User)) case is NavigationSearch: SearchView() - case is NavigationListCategoryShortcutType: - ListCategoryShortcutView(data: data as! NavigationListCategoryShortcutType) + case is Category: + ListCategoryShortcutView(viewModel: ListCategoryShortcutViewModel(data: data as! Category)) case is NavigationNicknameView: EditNicknameView() case is NavigationSettingView: @@ -115,23 +115,23 @@ struct NavigationViewModifier: ViewModifier { func body(content: Content) -> some View { content - .navigationDestination(for: NavigationProfile.self) { data in - ShowProfileView(data: data) + .navigationDestination(for: User.self) { data in + ShowProfileView(viewModel: ShowProfileViewModel(data: data)) } - .navigationDestination(for: NavigationReadCurationType.self) { data in - ReadCurationView(data: data) + .navigationDestination(for: Curation.self) { data in + ReadCurationView(viewModel: ReadCurationViewModel(data: data)) } .navigationDestination(for: CurationType.self) { data in - ListCurationView(curationType: data) + ListCurationView(viewModel: ListCurationViewModel(data: data)) } - .navigationDestination(for: NavigationReadShortcutType.self) { data in - ReadShortcutView(data: data) + .navigationDestination(for: Shortcuts.self) { data in + ReadShortcutView(viewModel: ReadShortcutViewModel(data: data)) } - .navigationDestination(for: NavigationListShortcutType.self) { data in - ListShortcutView(data: data) + .navigationDestination(for: SectionType.self) { data in + ListShortcutView(viewModel: ListShortcutViewModel(data: data)) } - .navigationDestination(for: NavigationListCategoryShortcutType.self) { data in - ListCategoryShortcutView(data: data) + .navigationDestination(for: Category.self) { data in + ListCategoryShortcutView(viewModel: ListCategoryShortcutViewModel(data: data)) } .navigationDestination(for: NavigationLisence.self) { value in LicenseView() diff --git a/HappyAnding/HappyAnding/Model/Comment.swift b/HappyAnding/HappyAnding/Model/Comment.swift index 898d0c82..fb3ed8a6 100644 --- a/HappyAnding/HappyAnding/Model/Comment.swift +++ b/HappyAnding/HappyAnding/Model/Comment.swift @@ -15,6 +15,16 @@ struct Comments: Identifiable, Codable, Equatable { let data = (try? JSONEncoder().encode(self)) ?? Data() return (try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any]) ?? [:] } + + init() { + self.id = "" + self.comments = [] + } + + init(id: String, comments: [Comment]) { + self.id = id + self.comments = comments + } } struct Comment: Identifiable, Codable, Hashable { @@ -25,6 +35,22 @@ struct Comment: Identifiable, Codable, Hashable { var date: String //처음 작성한 날짜만 저장 var depth: Int //0이면 원댓글, 1이면 대댓글 var contents: String + + init() { + self.user_nickname = "" + self.user_id = "" + self.date = "" + self.depth = 0 + self.contents = "" + } + + init(user_nickname: String, user_id: String, date: String, depth: Int, contents: String) { + self.user_nickname = user_nickname + self.user_id = user_id + self.date = date + self.depth = depth + self.contents = contents + } } extension Comments { diff --git a/HappyAnding/HappyAnding/Model/Curation.swift b/HappyAnding/HappyAnding/Model/Curation.swift index 664a3445..1ef2833c 100644 --- a/HappyAnding/HappyAnding/Model/Curation.swift +++ b/HappyAnding/HappyAnding/Model/Curation.swift @@ -22,6 +22,28 @@ struct Curation: Identifiable, Equatable, Codable, Hashable { let data = (try? JSONEncoder().encode(self)) ?? Data() return (try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any]) ?? [:] } + + init(title: String, subtitle: String, isAdmin: Bool, background: String, author: String, shortcuts: [ShortcutCellModel]) { + self.id = UUID().uuidString + self.title = title + self.subtitle = subtitle + self.dateTime = Date().getDate() + self.isAdmin = isAdmin + self.background = background + self.author = author + self.shortcuts = shortcuts + } + + init() { + self.id = UUID().uuidString + self.title = "" + self.subtitle = "" + self.dateTime = Date().getDate() + self.isAdmin = false + self.background = "" + self.author = "" + self.shortcuts = [] + } } struct ShortcutCellModel: Identifiable, Codable, Equatable, Hashable { diff --git a/HappyAnding/HappyAnding/Model/NavigationStackModel.swift b/HappyAnding/HappyAnding/Model/NavigationStackModel.swift index 0a4d41a7..f0927503 100644 --- a/HappyAnding/HappyAnding/Model/NavigationStackModel.swift +++ b/HappyAnding/HappyAnding/Model/NavigationStackModel.swift @@ -7,61 +7,6 @@ import SwiftUI -struct NavigationListShortcutType: Identifiable, Hashable { - var id = UUID().uuidString - - var sectionType: SectionType - var shortcuts: [Shortcuts]? - let navigationParentView: NavigationParentView -} - -struct NavigationReadShortcutType: Identifiable, Hashable { - var id = UUID().uuidString - - var shortcut: Shortcuts? - let shortcutID: String - let navigationParentView: NavigationParentView -} - -struct NavigationReadCurationType: Identifiable, Hashable { - var id = UUID().uuidString - - var isAdmin: Bool = false - let curation: Curation - let navigationParentView: NavigationParentView -} - -struct NavigationListCurationType: Identifiable, Hashable { - var id = UUID().uuidString - - var type: CurationType - let navigationParentView: NavigationParentView -} - -struct NavigationProfile: Identifiable, Hashable { - var id = UUID().uuidString - - var userInfo: User? -} - -struct NavigationListCategoryShortcutType: Identifiable, Hashable { - - var id = UUID().uuidString - - var shortcuts: [Shortcuts] - var categoryName: Category - var navigationParentView: NavigationParentView -} - -struct WriteCurationInfoType: Identifiable, Hashable { - - var id = UUID().uuidString - - var curation: Curation - var deletedShortcutCells: [ShortcutCellModel] - var isEdit: Bool -} - enum NavigationSearch: Hashable, Equatable { case first } @@ -82,7 +27,6 @@ enum NavigationNicknameView: Hashable, Equatable { case first } - enum NavigationLisence: Hashable, Equatable { case first } diff --git a/HappyAnding/HappyAnding/Model/Shortcuts.swift b/HappyAnding/HappyAnding/Model/Shortcuts.swift index 696163fd..8847812d 100644 --- a/HappyAnding/HappyAnding/Model/Shortcuts.swift +++ b/HappyAnding/HappyAnding/Model/Shortcuts.swift @@ -31,4 +31,36 @@ struct Shortcuts: Identifiable, Codable, Equatable, Hashable { let data = (try? JSONEncoder().encode(self)) ?? Data() return (try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any]) ?? [:] } + + init() { + self.sfSymbol = "" + self.color = "" + self.title = "" + self.subtitle = "" + self.description = "" + self.category = [] + self.requiredApp = [] + self.numberOfLike = 0 + self.numberOfDownload = 0 + self.author = "" + self.shortcutRequirements = "" + self.downloadLink = [] + self.curationIDs = [] + } + + init(sfSymbol: String, color: String, title: String, subtitle: String, description: String, category: [String], requiredApp: [String], numberOfLike: Int, numberOfDownload: Int, author: String, shortcutRequirements: String, downloadLink: [String], curationIDs: [String]) { + self.sfSymbol = sfSymbol + self.color = color + self.title = title + self.subtitle = subtitle + self.description = description + self.category = category + self.requiredApp = requiredApp + self.numberOfLike = numberOfLike + self.numberOfDownload = numberOfDownload + self.author = author + self.shortcutRequirements = shortcutRequirements + self.downloadLink = downloadLink + self.curationIDs = curationIDs + } } diff --git a/HappyAnding/HappyAnding/Model/User.swift b/HappyAnding/HappyAnding/Model/User.swift index 7c482f69..6ce01633 100644 --- a/HappyAnding/HappyAnding/Model/User.swift +++ b/HappyAnding/HappyAnding/Model/User.swift @@ -17,6 +17,20 @@ struct User: Identifiable, Codable, Hashable { let data = (try? JSONEncoder().encode(self)) ?? Data() return (try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any]) ?? [:] } + + init() { + self.id = "" + self.nickname = "" + self.likedShortcuts = [] + self.downloadedShortcuts = [] + } + + init(id: String, nickname: String, likedShortcuts: [String], downloadedShortcuts: [DownloadedShortcut]) { + self.id = id + self.nickname = nickname + self.likedShortcuts = likedShortcuts + self.downloadedShortcuts = downloadedShortcuts + } } struct DownloadedShortcut: Identifiable, Codable, Hashable { diff --git a/HappyAnding/HappyAnding/ViewModel/ExploreCurationViewModels/ExploreCurationViewModel.swift b/HappyAnding/HappyAnding/ViewModel/ExploreCurationViewModels/ExploreCurationViewModel.swift new file mode 100644 index 00000000..e7f57a8c --- /dev/null +++ b/HappyAnding/HappyAnding/ViewModel/ExploreCurationViewModels/ExploreCurationViewModel.swift @@ -0,0 +1,36 @@ +// +// ExploreCurationViewModel.swift +// HappyAnding +// +// Created by 이지원 on 2023/06/25. +// + +import Foundation + +final class ExploreCurationViewModel: ObservableObject { + private let shortcutsZipViewModel = ShortcutsZipViewModel.share + @Published var adminCurationList = [Curation]() + @Published var personalCurationList = [Curation]() + @Published var userCurationList = [Curation]() + + init() { + fetchAdminCurationList() + } + + private func fetchAdminCurationList() { + self.adminCurationList = shortcutsZipViewModel.adminCurations + } + + func getCurationList(with curationType: CurationType) -> [Curation] { + curationType.filterCuration(from: shortcutsZipViewModel) + } + + func getSectionTitle(with curationType: CurationType) -> String { + switch curationType { + case .personalCuration: + return (shortcutsZipViewModel.userInfo?.nickname ?? "") + curationType.title + default: + return curationType.title + } + } +} diff --git a/HappyAnding/HappyAnding/ViewModel/ExploreCurationViewModels/ListCurationViewModel.swift b/HappyAnding/HappyAnding/ViewModel/ExploreCurationViewModels/ListCurationViewModel.swift new file mode 100644 index 00000000..23082377 --- /dev/null +++ b/HappyAnding/HappyAnding/ViewModel/ExploreCurationViewModels/ListCurationViewModel.swift @@ -0,0 +1,36 @@ +// +// ListCurationViewModel.swift +// HappyAnding +// +// Created by 이지원 on 2023/06/25. +// + +import Foundation + +final class ListCurationViewModel: ObservableObject { + private let shortcutsZipViewModel = ShortcutsZipViewModel.share + + @Published var curationType: CurationType + @Published private(set) var curationList = [Curation]() + @Published private(set) var sectionTitle: String = "" + + init(data: CurationType) { + self.curationType = data + self.curationList = curationType.filterCuration(from: shortcutsZipViewModel) + self.sectionTitle = fetchSectionTitle() + print("new viewModel: ", curationType, curationList) + } + + private func fetchSectionTitle() -> String { + switch curationType { + case .personalCuration: + return (shortcutsZipViewModel.userInfo?.nickname ?? "") + curationType.title + default: + return curationType.title + } + } + + func getEmptyContentsWording() -> String { + "아직 \(sectionTitle)\(sectionTitle.contains("단축어") ? "가" : "이") 없어요" + } +} diff --git a/HappyAnding/HappyAnding/ViewModel/ExploreCurationViewModels/ReadCurationViewModel.swift b/HappyAnding/HappyAnding/ViewModel/ExploreCurationViewModels/ReadCurationViewModel.swift new file mode 100644 index 00000000..fe2a8b64 --- /dev/null +++ b/HappyAnding/HappyAnding/ViewModel/ExploreCurationViewModels/ReadCurationViewModel.swift @@ -0,0 +1,67 @@ +// +// ReadCurationViewModel.swift +// HappyAnding +// +// Created by 이지원 on 2023/06/17. +// + +import SwiftUI + + +final class ReadCurationViewModel: ObservableObject { + private let shortcutsZipViewModel = ShortcutsZipViewModel.share + + @Published var isWriting = false + @Published var isTappedDeleteButton = false + @Published var curation: Curation + @Published private(set) var authInformation: User + @Published private(set) var gradeImage = Image(systemName: "person.crop.circle.fill") + @Published private(set) var isAdmin = false + + init(data: Curation) { + self.curation = data + self.authInformation = User() + self.isAdmin = curation.isAdmin + fetchUserGrade() + } + + private func fetchUserGrade() { + shortcutsZipViewModel.fetchUser(userID: curation.author, + isCurrentUser: false) { user in + self.authInformation = user + let grade = self.shortcutsZipViewModel.checkShortcutGrade(userID: self.authInformation.id) + let image = self.shortcutsZipViewModel.fetchShortcutGradeImage(isBig: false, shortcutGrade: grade) + self.gradeImage = image + } + } + + func checkAuthor() -> Bool { + return curation.author == shortcutsZipViewModel.currentUser() + } + + func deleteCuration() { + shortcutsZipViewModel.deleteData(model: curation) + shortcutsZipViewModel.curationsMadeByUser = shortcutsZipViewModel.curationsMadeByUser.filter { $0.id != curation.id } + } + + func shareCuration() { + guard let deepLink = URL(string: "ShortcutsZip://myPage/CurationDetailView?curationID=\(curation.id)") else { return } + + let activityVC = UIActivityViewController(activityItems: [deepLink], applicationActivities: nil) + let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene + guard let window = windowScene?.windows.first else { return } + window.rootViewController?.present(activityVC, animated: true, completion: nil) + } + + func fetchCuration() { + if let index = shortcutsZipViewModel.userCurations.firstIndex(where: {$0.id == self.curation.id}) { + self.curation = shortcutsZipViewModel.userCurations[index] + print("curation", self.curation) + } + } + + func fetchShortcut(from shortcutCellModel: ShortcutCellModel) -> Shortcuts { + shortcutsZipViewModel.fetchShortcutDetail(id: shortcutCellModel.id) ?? Shortcuts() + } +} + diff --git a/HappyAnding/HappyAnding/ViewModel/ExploreShortcutViewModels/ExploreShortcutViewModel.swift b/HappyAnding/HappyAnding/ViewModel/ExploreShortcutViewModels/ExploreShortcutViewModel.swift new file mode 100644 index 00000000..dca00d12 --- /dev/null +++ b/HappyAnding/HappyAnding/ViewModel/ExploreShortcutViewModels/ExploreShortcutViewModel.swift @@ -0,0 +1,34 @@ +// +// ExploreShortcutViewModel.swift +// HappyAnding +// +// Created by kimjimin on 2023/06/25. +// + +import SwiftUI + +final class ExploreShortcutViewModel: ObservableObject { + + private let shortcutsZipViewModel = ShortcutsZipViewModel.share + + @Published private(set) var isCategoryCellViewFolded = true + @Published var isAnnouncementCellShowing = false + @Published private(set) var numberOfDisplayedCategories = 6 + + func changeDisplayedCategories() { + self.isCategoryCellViewFolded.toggle() + self.numberOfDisplayedCategories = isCategoryCellViewFolded ? 6 : 12 + } + + func announcementCellDidTap() { + self.isAnnouncementCellShowing = true + } + + func fetchShortcuts(by category: Category) -> [Shortcuts] { + self.shortcutsZipViewModel.shortcutsInCategory[category.index] + } + + func fetchShortcuts(by sectionType: SectionType) -> [Shortcuts] { + sectionType.filterShortcuts(from: self.shortcutsZipViewModel) + } +} diff --git a/HappyAnding/HappyAnding/ViewModel/ExploreShortcutViewModels/ListCategoryShortcutViewModel.swift b/HappyAnding/HappyAnding/ViewModel/ExploreShortcutViewModels/ListCategoryShortcutViewModel.swift new file mode 100644 index 00000000..e029f62e --- /dev/null +++ b/HappyAnding/HappyAnding/ViewModel/ExploreShortcutViewModels/ListCategoryShortcutViewModel.swift @@ -0,0 +1,22 @@ +// +// ListCategoryShortcutViewModel.swift +// HappyAnding +// +// Created by kimjimin on 2023/06/30. +// + +import SwiftUI + +final class ListCategoryShortcutViewModel: ObservableObject { + + private let shortcutsZipViewModel = ShortcutsZipViewModel.share + + @Published private(set) var shortcuts: [Shortcuts] = [] + @Published private(set) var category: Category = .business + + init(data: Category) { + self.shortcuts = shortcutsZipViewModel.shortcutsInCategory[data.index] + self.category = data + } + +} diff --git a/HappyAnding/HappyAnding/ViewModel/ExploreShortcutViewModels/ListShortcutViewModel.swift b/HappyAnding/HappyAnding/ViewModel/ExploreShortcutViewModels/ListShortcutViewModel.swift new file mode 100644 index 00000000..06e1b292 --- /dev/null +++ b/HappyAnding/HappyAnding/ViewModel/ExploreShortcutViewModels/ListShortcutViewModel.swift @@ -0,0 +1,32 @@ +// +// ListShortcutViewModel.swift +// HappyAnding +// +// Created by kimjimin on 2023/06/30. +// + +import SwiftUI + +final class ListShortcutViewModel: ObservableObject { + + private let shortcutsZipViewModel = ShortcutsZipViewModel.share + + @Published private(set) var shortcuts: [Shortcuts] = [] + @Published private(set) var sectionType: SectionType = .download + + init(data: SectionType) { + self.sectionType = data + self.shortcuts = fetchShortcutsBySectionType() + } + + func fetchShortcutsBySectionType() -> [Shortcuts] { + switch self.sectionType { + case .recent: return shortcutsZipViewModel.allShortcuts + case .download: return shortcutsZipViewModel.sortedShortcutsByDownload + case .popular: return shortcutsZipViewModel.sortedShortcutsByLike + case .myDownloadShortcut: return shortcutsZipViewModel.shortcutsUserDownloaded + case .myLovingShortcut: return shortcutsZipViewModel.shortcutsUserLiked + case .myShortcut: return shortcutsZipViewModel.shortcutsMadeByUser + } + } +} diff --git a/HappyAnding/HappyAnding/ViewModel/ReadShortcutViewModels/ReadShortcutViewModel.swift b/HappyAnding/HappyAnding/ViewModel/ReadShortcutViewModels/ReadShortcutViewModel.swift new file mode 100644 index 00000000..eb945867 --- /dev/null +++ b/HappyAnding/HappyAnding/ViewModel/ReadShortcutViewModels/ReadShortcutViewModel.swift @@ -0,0 +1,180 @@ +// +// ReadShortcutViewModel.swift +// HappyAnding +// +// Created by kimjimin on 2023/07/15. +// + +import SwiftUI + +final class ReadShortcutViewModel: ObservableObject { + + private let shortcutsZipViewModel = ShortcutsZipViewModel.share + + @Published private(set) var shortcut: Shortcuts + + // ReadShortcutView + @Published var isDeletingShortcut = false + @Published var isEditingShortcut = false + @Published var isUpdatingShortcut = false + + @Published var isMyLike = false + @Published private var isMyFirstLike = false + @Published var isDownloadingShortcut = false + @Published private(set) var isDowngradingUserLevel = false + + @Published var currentTab: Int = 0 + @Published private(set) var comments: Comments = Comments() + @Published var comment: Comment = Comment() + @Published var nestedCommentTarget: String = "" + @Published var commentText = "" + + @Published var isEditingComment = false + @Published var isUndoingCommentEdit = false + + // ReadShortcutViewHeader + @Published private(set) var author: User + @Published var numberOfLike = 0 + @Published private(set) var userGrade = Image(systemName: "person.crop.circle.fill") + + // ReadShortcutCommentView + @Published var isDeletingComment = false + @Published var deletedComment = Comment() + + // UpdateShortcutView + @Published var updatedLink = "" + @Published var updateDescription = "" + @Published var isLinkValid = false + @Published var isDescriptionValid = false + + var isUpdateValid: Bool { + isLinkValid && isDescriptionValid + } + + init(data: Shortcuts) { + self.author = User() + self.shortcut = shortcutsZipViewModel.fetchShortcutDetail(id: data.id) ?? data + self.isMyLike = shortcutsZipViewModel.checkLikedShortrcut(shortcutID: data.id) + self.isMyFirstLike = isMyLike + self.comments = shortcutsZipViewModel.fetchComment(shortcutID: data.id) + self.numberOfLike = data.numberOfLike + fetchAuthor() + } + + private func fetchAuthor() { + shortcutsZipViewModel.fetchUser(userID: shortcut.author, + isCurrentUser: false) { user in + self.author = user + let grade = self.shortcutsZipViewModel.checkShortcutGrade(userID: self.author.id) + let image = self.shortcutsZipViewModel.fetchShortcutGradeImage(isBig: false, shortcutGrade: grade) + self.userGrade = image + } + } + + func moveTab(to tab: Int) { + self.currentTab = tab + } + + func setReply(to comment: Comment) { + self.nestedCommentTarget = comment.user_nickname + self.comment.bundle_id = comment.bundle_id + self.comment.depth = 1 + } + + func checkIfDownloaded() { + if (shortcutsZipViewModel.userInfo?.downloadedShortcuts.firstIndex(where: { $0.id == shortcut.id })) == nil { + shortcut.numberOfDownload += 1 + } + } + + func updateNumberOfDownload(index: Int) { + shortcutsZipViewModel.updateNumberOfDownload(shortcut: shortcut, downloadlinkIndex: index) + } + + func onViewDisappear() { + if isMyLike != isMyFirstLike { + shortcutsZipViewModel.updateNumberOfLike(isMyLike: isMyLike, shortcut: shortcut) + } + } + + func deleteShortcut() { + shortcutsZipViewModel.deleteShortcutIDInUser(shortcutID: shortcut.id) + shortcutsZipViewModel.deleteShortcutInCuration(curationsIDs: shortcut.curationIDs, shortcutID: shortcut.id) + shortcutsZipViewModel.deleteData(model: shortcut) + shortcutsZipViewModel.shortcutsMadeByUser = shortcutsZipViewModel.shortcutsMadeByUser.filter { $0.id != shortcut.id } + shortcutsZipViewModel.updateShortcutGrade() + } + + func cancelEditingComment() { + self.isEditingComment.toggle() + self.comment = self.comment.resetComment() + self.commentText = "" + } + + func postComment() { + if !isEditingComment { + comment.contents = commentText + comment.date = Date().getDate() + comment.user_id = shortcutsZipViewModel.userInfo!.id + comment.user_nickname = shortcutsZipViewModel.userInfo!.nickname + comments.comments.append(comment) + } else { + if let index = comments.comments.firstIndex(where: { $0.id == comment.id }) { + comments.comments[index].contents = commentText + } + isEditingComment = false + } + shortcutsZipViewModel.setData(model: comments) + commentText = "" + comment = comment.resetComment() + self.comments.comments = comments.fetchSortedComment() + } + + func cancelNestedComment() { + comment.bundle_id = "\(Date().getDate())_\(UUID().uuidString)" + comment.depth = 0 + } + + func checkAuthor() -> Bool { + return self.shortcut.author == shortcutsZipViewModel.currentUser() + } + + func shareShortcut() { + guard let deepLink = URL(string: "ShortcutsZip://myPage/detailView?shortcutID=\(shortcut.id)") else { return } + let activityVC = UIActivityViewController(activityItems: [deepLink], applicationActivities: nil) + let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene + guard let window = windowScene?.windows.first else { return } + window.rootViewController?.present(activityVC, animated: true, completion: nil) + } + + func checkDowngrading() { + isDeletingShortcut.toggle() + isDowngradingUserLevel = shortcutsZipViewModel.isShortcutDowngrade() + } + + func updateShortcut() { + shortcutsZipViewModel.updateShortcutVersion(shortcut: shortcut, + updateDescription: updateDescription, + updateLink: updatedLink) + self.shortcut = shortcutsZipViewModel.fetchShortcutDetail(id: shortcut.id) ?? shortcut + isUpdatingShortcut.toggle() + } + + func deleteComment() { + if deletedComment.depth == 0 { + comments.comments.removeAll(where: { $0.bundle_id == deletedComment.bundle_id}) + } else { + comments.comments.removeAll(where: { $0.id == deletedComment.id}) + } + + shortcutsZipViewModel.setData(model: comments) + } + + func fetchUserGrade(id: String) -> Image { + shortcutsZipViewModel.fetchShortcutGradeImage(isBig: false, shortcutGrade: shortcutsZipViewModel.checkShortcutGrade(userID: id)) + } + + func refreshShortcut() { + self.shortcut = shortcutsZipViewModel.fetchShortcutDetail(id: shortcut.id) ?? shortcut + } +} diff --git a/HappyAnding/HappyAnding/ViewModel/ReadShortcutViewModels/ShowProfileViewModel.swift b/HappyAnding/HappyAnding/ViewModel/ReadShortcutViewModels/ShowProfileViewModel.swift new file mode 100644 index 00000000..8b780d99 --- /dev/null +++ b/HappyAnding/HappyAnding/ViewModel/ReadShortcutViewModels/ShowProfileViewModel.swift @@ -0,0 +1,35 @@ +// +// ShowProfileViewModel.swift +// HappyAnding +// +// Created by kimjimin on 2023/07/08. +// + +import SwiftUI + +final class ShowProfileViewModel: ObservableObject { + + private let shortcutsZipViewModel = ShortcutsZipViewModel.share + + @Published var user: User + @Published private(set) var shortcuts: [Shortcuts] = [] + @Published private(set) var curations: [Curation] = [] + @Published private(set) var userGrade = Image(systemName: "person.crop.circle.fill") + @Published var currentTab: Int = 0 + @Published private(set) var animationAmount = 0.0 + + init(data: User) { + self.user = data + self.userGrade = shortcutsZipViewModel.fetchShortcutGradeImage(isBig: true, shortcutGrade: shortcutsZipViewModel.checkShortcutGrade(userID: data.id)) + self.shortcuts = shortcutsZipViewModel.allShortcuts.filter { $0.author == data.id } + self.curations = shortcutsZipViewModel.fetchCurationByAuthor(author: data.id) + } + + func profileDidTap() { + self.animationAmount += 360 + } + + func moveTab(to tab: Int) { + self.currentTab = tab + } +} diff --git a/HappyAnding/HappyAnding/ViewModel/ShortcutsZipViewModel.swift b/HappyAnding/HappyAnding/ViewModel/ShortcutsZipViewModel.swift index 61208df4..9ca66768 100644 --- a/HappyAnding/HappyAnding/ViewModel/ShortcutsZipViewModel.swift +++ b/HappyAnding/HappyAnding/ViewModel/ShortcutsZipViewModel.swift @@ -32,7 +32,11 @@ class ShortcutsZipViewModel: ObservableObject { @Published var shortcutsInCategory: [[Shortcuts]] = [[Shortcuts]].init(repeating: [], count: Category.allCases.count) // Category에서 사용할 숏컷 배열 @Published var curationsMadeByUser: [Curation] = [] // 유저가 만든 큐레이션배열 - @Published var userCurations: [Curation] = [] + @Published var userCurations: [Curation] = [] { + didSet { + self.refreshPersonalCurations() + } + } @Published var personalCurations: [Curation] = [] // "땡땡님을 위한 모음집" 큐레이션배열 @Published var adminCurations: [Curation] = [] @@ -913,8 +917,9 @@ extension ShortcutsZipViewModel { func fetchComment(shortcutID: String) -> Comments { if let index = allComments.firstIndex(where: {$0.id == shortcutID}) { - allComments[index].comments = allComments[index].fetchSortedComment() - return allComments[index] + var allComments = self.allComments[index] + allComments.comments = allComments.fetchSortedComment() + return allComments } return Comments(id: shortcutID, comments: []) } diff --git a/HappyAnding/HappyAnding/ViewModel/WriteCurationViewModel.swift b/HappyAnding/HappyAnding/ViewModel/WriteCurationViewModel.swift new file mode 100644 index 00000000..5b410068 --- /dev/null +++ b/HappyAnding/HappyAnding/ViewModel/WriteCurationViewModel.swift @@ -0,0 +1,86 @@ +// +// WriteCurationViewModel.swift +// HappyAnding +// +// Created by JeonJimin on 2023/06/23. +// + +import SwiftUI + +final class WriteCurationViewModel: ObservableObject, Hashable { + + static func == (lhs: WriteCurationViewModel, rhs: WriteCurationViewModel) -> Bool { + return false + } + func hash(into hasher: inout Hasher) { + hasher.combine(curation) + } + + private var shortcutsZipViewModel = ShortcutsZipViewModel.share + + //WriteCurationSet + @Published var isEdit = false + + //좋아요 + 내가 작성한 단축어 목록 + @Published var shortcutCells = [ShortcutCellModel]() + //CheckboxShortcutCell 선택 여부 저장 + @Published var isShortcutsTapped: [Bool] = [] + + //모음집 편집 시 전달받는 기존 모음집 정보 + @Published var curation = Curation(title: "", subtitle: "", isAdmin: false, background: "", author: "", shortcuts: [ShortcutCellModel]()) + + //기존 선택 -> 편집 시 선택 해제 되어 기존 모음집 정보에서 삭제해야할 단축어 배열 + @Published var deletedShortcutCells = [ShortcutCellModel]() + + + //WriteCurationInfo +// @Published var writeCurationNavigation = WriteCurationNavigation() + @Published var isValidTitle = false + @Published var isValidDescription = false + + var isIncomplete: Bool { + !(isValidTitle && isValidDescription) + } + + init() { } + + init(data: Curation) { + self.curation = data + } + + func fetchMakeCuration() { + shortcutCells = shortcutsZipViewModel.fetchShortcutMakeCuration().sorted { $0.title < $1.title } + if isEdit { + deletedShortcutCells = curation.shortcuts + } + + //isShortcutsTapped 초기화 + isShortcutsTapped = [Bool](repeating: false, count: shortcutCells.count) + for shortcut in curation.shortcuts { + if let index = shortcutCells.firstIndex(of: shortcut) { + isShortcutsTapped[index] = true + } + } + } + + func addCuration() { + shortcutsZipViewModel.addCuration(curation: curation, isEdit: isEdit, deletedShortcutCells: deletedShortcutCells) + } + + func checkboxCellTapGesture(index: Int) { + if isShortcutsTapped[index] { + isShortcutsTapped[index] = false + // TODO: 현재는 name을 기준으로 검색중, id로 검색해서 삭제해야함 / Shortcuts 자체를 배열에 저장해야함 + + if let firstIndex = curation.shortcuts.firstIndex(of: shortcutCells[index]) { + curation.shortcuts.remove(at: firstIndex) + } + } + else { + if curation.shortcuts.count < 10 { + curation.shortcuts.append(shortcutCells[index]) + isShortcutsTapped[index] = true + } + } + } +} diff --git a/HappyAnding/HappyAnding/Views/Components/CustomTextEditor.swift b/HappyAnding/HappyAnding/Views/Components/CustomTextEditor.swift index f37218d4..8b935ee5 100644 --- a/HappyAnding/HappyAnding/Views/Components/CustomTextEditor.swift +++ b/HappyAnding/HappyAnding/Views/Components/CustomTextEditor.swift @@ -54,7 +54,9 @@ struct CustomTextEditor: UIViewRepresentable { } func textViewDidChangeSelection(_ textView: UITextView) { - self.text = textView.text ?? "" + DispatchQueue.main.async { + self.text = textView.text ?? "" + } } func textViewDidBeginEditing(_ textView: UITextView) { diff --git a/HappyAnding/HappyAnding/Views/Components/MyShortcutCardListView.swift b/HappyAnding/HappyAnding/Views/Components/MyShortcutCardListView.swift index 70d5634b..ceced5ec 100644 --- a/HappyAnding/HappyAnding/Views/Components/MyShortcutCardListView.swift +++ b/HappyAnding/HappyAnding/Views/Components/MyShortcutCardListView.swift @@ -21,11 +21,6 @@ struct MyShortcutCardListView: View { @State var isGradeAlertPresented = false var shortcuts: [Shortcuts]? - var data: NavigationListShortcutType { - NavigationListShortcutType(sectionType: .myShortcut, - shortcuts: self.shortcuts, - navigationParentView: self.navigationParentView) - } let navigationParentView: NavigationParentView @@ -37,7 +32,7 @@ struct MyShortcutCardListView: View { Spacer() MoreCaptionTextView(text: TextLiteral.more) - .navigationLinkRouter(data: data) + .navigationLinkRouter(data: SectionType.myShortcut) } .padding(.horizontal, 16) @@ -56,13 +51,10 @@ struct MyShortcutCardListView: View { if let shortcuts { ForEach(Array((shortcuts.enumerated())), id: \.offset) { index, shortcut in if index < 7 { - let data = NavigationReadShortcutType(shortcutID: shortcut.id, - navigationParentView: self.navigationParentView) - MyShortcutCardView(myShortcutIcon: shortcut.sfSymbol, myShortcutName: shortcut.title, myShortcutColor: shortcut.color) - .navigationLinkRouter(data: data) + .navigationLinkRouter(data: shortcut) } } } diff --git a/HappyAnding/HappyAnding/Views/Components/UserCurationCell.swift b/HappyAnding/HappyAnding/Views/Components/UserCurationCell.swift index bddd709b..b978fefe 100644 --- a/HappyAnding/HappyAnding/Views/Components/UserCurationCell.swift +++ b/HappyAnding/HappyAnding/Views/Components/UserCurationCell.swift @@ -13,8 +13,7 @@ struct UserCurationCell: View { @EnvironmentObject var shortcutsZipViewModel: ShortcutsZipViewModel - @State var curation: Curation - @State var index = 0 + @Binding var curation: Curation var lineLimit: Int? let navigationParentView: NavigationParentView @@ -25,7 +24,7 @@ struct UserCurationCell: View { //MARK: - 단축어 아이콘 배열 HStack { - ForEach(shortcutsZipViewModel.userCurations[index].shortcuts.prefix(4), id: \.self) { shortcut in + ForEach(curation.shortcuts.prefix(4), id: \.self) { shortcut in ZStack { Rectangle() .fill(Color.fetchGradient(color: shortcut.color)) @@ -38,7 +37,7 @@ struct UserCurationCell: View { } //단축어가 4개 이상인 경우에만 그리는 아이콘 - if shortcutsZipViewModel.userCurations[index].shortcuts.count > 4 { + if curation.shortcuts.count > 4 { ZStack(alignment: .center) { Rectangle() .fill(Color.gray2) @@ -47,7 +46,7 @@ struct UserCurationCell: View { HStack(spacing: 0) { Image(systemName: "plus") .smallIcon() - Text("\(shortcutsZipViewModel.userCurations[index].shortcuts.count-4)") + Text("\(curation.shortcuts.count-4)") .shortcutsZipFootnote() } .foregroundColor(.gray5) @@ -59,11 +58,11 @@ struct UserCurationCell: View { //MARK: - curation title, subtitle - Text(shortcutsZipViewModel.userCurations[index].title) + Text(curation.title) .shortcutsZipHeadline() .foregroundColor(Color.gray5) .frame(maxWidth: .infinity, alignment: .leading) - Text(shortcutsZipViewModel.userCurations[index].subtitle.lineBreaking) + Text(curation.subtitle.lineBreaking) .shortcutsZipBody2() .multilineTextAlignment(.leading) .lineLimit(lineLimit) @@ -71,11 +70,6 @@ struct UserCurationCell: View { .padding(.bottom, 20) .fixedSize(horizontal: false, vertical: true) } - .onAppear() { - if let index = shortcutsZipViewModel.userCurations.firstIndex(of: curation) { - self.index = index - } - } .padding(.horizontal, 24) .background(Color.backgroudList) .overlay( diff --git a/HappyAnding/HappyAnding/Views/Components/UserCurationListView.swift b/HappyAnding/HappyAnding/Views/Components/UserCurationListView.swift index 936f4c45..8e289728 100644 --- a/HappyAnding/HappyAnding/Views/Components/UserCurationListView.swift +++ b/HappyAnding/HappyAnding/Views/Components/UserCurationListView.swift @@ -18,11 +18,12 @@ struct UserCurationListView: View { @State var isWriting = false @State var data: CurationType + @State var curation = Curation() var body: some View { VStack(spacing: 0) { HStack(alignment: .bottom) { - SubtitleTextView(text: data.title ?? "") + SubtitleTextView(text: data.title ) .onTapGesture { } Spacer() @@ -58,12 +59,10 @@ struct UserCurationListView: View { ForEach(Array(shortcutsZipViewModel.curationsMadeByUser.enumerated()), id: \.offset) { index, curation in if index < 2 { - let data = NavigationReadCurationType(curation: curation, - navigationParentView: .curations) - UserCurationCell(curation: curation, + UserCurationCell(curation: .constant(curation), lineLimit: 2, navigationParentView: .curations) - .navigationLinkRouter(data: data) + .navigationLinkRouter(data: curation) } } @@ -78,12 +77,15 @@ struct UserCurationListView: View { @ViewBuilder private func writeCurationView() -> some View { - WriteCurationSetView(isWriting: $isWriting - , isEdit: false - ) - .navigationDestination(for: WriteCurationInfoType.self) { data in - WriteCurationInfoView(data: data, isWriting: $isWriting) - } + WriteCurationSetView(isWriting: $isWriting, viewModel: WriteCurationViewModel()) + .navigationDestination(for: WriteCurationViewModel.self) { data in + WriteCurationInfoView(viewModel: data, isWriting: $isWriting) + .onDisappear(){ + if #available(iOS 16.1, *) { + writeCurationNavigation.navigationPath = .init() + } + } + } } } diff --git a/HappyAnding/HappyAnding/Views/Components/UserNameCell.swift b/HappyAnding/HappyAnding/Views/Components/UserNameCell.swift index af160989..b0da346c 100644 --- a/HappyAnding/HappyAnding/Views/Components/UserNameCell.swift +++ b/HappyAnding/HappyAnding/Views/Components/UserNameCell.swift @@ -18,11 +18,11 @@ import SwiftUI - description: - 해당 뷰는 넓이가 부모 프레임에 꽉차도록 만들어졌습니다. 여백이 필요한 경우 부모프레임 또는 해당 뷰를 불러온 후 설정해주세요. - + */ struct UserNameCell: View { - var userInformation: User? - var gradeImage: Image? + var userInformation: User + var gradeImage: Image var body: some View { HStack { @@ -31,13 +31,19 @@ struct UserNameCell: View { .frame(width: 24, height: 24) .foregroundColor(.gray3) - Text(userInformation?.nickname ?? TextLiteral.withdrawnUser) - .shortcutsZipBody2() - .foregroundColor(.gray4) + if !userInformation.nickname.isEmpty { + Text(userInformation.nickname) + .shortcutsZipBody2() + .foregroundColor(.gray4) + } else { + Text(TextLiteral.withdrawnUser) + .shortcutsZipBody2() + .foregroundColor(.gray4) + } Spacer() - if userInformation?.nickname != nil { + if !userInformation.id.isEmpty { Image(systemName: "chevron.right") .shortcutsZipFootnote() .foregroundColor(.gray4) @@ -50,13 +56,13 @@ struct UserNameCell: View { RoundedRectangle(cornerRadius: 12) .foregroundColor(.gray1) ) - .navigationLinkRouter(data: NavigationProfile(userInfo: self.userInformation)) - .disabled(self.userInformation == nil) + .navigationLinkRouter(data: self.userInformation) + .disabled(self.userInformation.id.isEmpty) } } struct UserNameCell_Previews: PreviewProvider { static var previews: some View { - UserNameCell() + UserNameCell(userInformation: User(), gradeImage: Image(systemName: "person.crop.circle.fill")) } } diff --git a/HappyAnding/HappyAnding/Views/ExploreCurationViews/ExploreCurationView.swift b/HappyAnding/HappyAnding/Views/ExploreCurationViews/ExploreCurationView.swift index 78014dd2..93a5f20f 100644 --- a/HappyAnding/HappyAnding/Views/ExploreCurationViews/ExploreCurationView.swift +++ b/HappyAnding/HappyAnding/Views/ExploreCurationViews/ExploreCurationView.swift @@ -9,8 +9,7 @@ import SwiftUI struct ExploreCurationView: View { - @EnvironmentObject var shortcutsZipViewModel: ShortcutsZipViewModel - + @StateObject var viewModel: ExploreCurationViewModel @AppStorage("useWithoutSignIn") var useWithoutSignIn = false var body: some View { @@ -27,9 +26,6 @@ struct ExploreCurationView: View { } .padding(.top, 20) .padding(.bottom, 44) - .onChange(of: shortcutsZipViewModel.userCurations) { _ in - shortcutsZipViewModel.refreshPersonalCurations() - } } .navigationBarTitle(TextLiteral.exploreCurationViewTitle) .navigationBarTitleDisplayMode(.large) @@ -48,11 +44,9 @@ struct ExploreCurationView: View { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 0) { - ForEach(shortcutsZipViewModel.adminCurations, id: \.id) { curation in + ForEach(viewModel.adminCurationList, id: \.id) { curation in AdminCurationCell(adminCuration: curation) - .navigationLinkRouter(data: NavigationReadCurationType(isAdmin: true, - curation: curation, - navigationParentView: .curations)) + .navigationLinkRouter(data: curation) } } .padding(.trailing, 8) @@ -64,19 +58,10 @@ struct ExploreCurationView: View { @ViewBuilder private func sectionView(with sectionType: CurationType) -> some View { - let curations = sectionType.filterCuration(from: shortcutsZipViewModel) - var sectionTitle: String { - if sectionType == .personalCuration { - return (shortcutsZipViewModel.userInfo?.nickname ?? "") + sectionType.title - } else { - return sectionType.title - } - } - VStack(spacing: 0) { HStack(alignment: .bottom) { - SubtitleTextView(text: sectionTitle) + SubtitleTextView(text: viewModel.getSectionTitle(with: sectionType)) Spacer() @@ -86,14 +71,12 @@ struct ExploreCurationView: View { .padding(.bottom, 12) .padding(.horizontal, 16) - ForEach(curations.prefix(2), id: \.self) { curation in - let data = NavigationReadCurationType(curation: curation, - navigationParentView: .curations) + ForEach(viewModel.getCurationList(with:sectionType).prefix(2), id: \.self) { curation in - UserCurationCell(curation: curation, + UserCurationCell(curation: .constant(curation), lineLimit: 2, navigationParentView: .curations) - .navigationLinkRouter(data: data) + .navigationLinkRouter(data: curation) } } } diff --git a/HappyAnding/HappyAnding/Views/ExploreCurationViews/ListCurationView.swift b/HappyAnding/HappyAnding/Views/ExploreCurationViews/ListCurationView.swift index 08cd740e..fcfdaa34 100644 --- a/HappyAnding/HappyAnding/Views/ExploreCurationViews/ListCurationView.swift +++ b/HappyAnding/HappyAnding/Views/ExploreCurationViews/ListCurationView.swift @@ -15,23 +15,12 @@ import SwiftUI struct ListCurationView: View { - @EnvironmentObject var shortcutsZipViewModel: ShortcutsZipViewModel - - @State var curationType: CurationType - @State var curations = [Curation]() - - private var sectionTitle: String { - if curationType == .personalCuration { - return (shortcutsZipViewModel.userInfo?.nickname ?? "") + curationType.title - } else { - return curationType.title - } - } + @StateObject var viewModel: ListCurationViewModel var body: some View { VStack { - if curations.isEmpty { - Text("아직 \(sectionTitle)\(sectionTitle.contains("단축어") ? "가" : "이") 없어요") + if viewModel.curationList.isEmpty { + Text(viewModel.getEmptyContentsWording()) .shortcutsZipBody2() .foregroundColor(Color.gray4) .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -41,7 +30,7 @@ struct ListCurationView: View { LazyVStack(spacing: 0) { Spacer() .frame(height: 20) - makeCurationCellList(curations) + makeCurationCellList(viewModel.curationList) Spacer() .frame(height: 32) @@ -55,23 +44,17 @@ struct ListCurationView: View { .background(Color.shortcutsZipBackground.ignoresSafeArea(.all, edges: .all)) .navigationBarBackground ({ Color.shortcutsZipBackground }) .navigationBarTitleDisplayMode(.inline) - .navigationTitle(sectionTitle) - .onAppear { - curations = curationType.filterCuration(from: shortcutsZipViewModel) - } + .navigationTitle(viewModel.sectionTitle) } @ViewBuilder private func makeCurationCellList(_ curations: [Curation]) -> some View { ForEach(Array(curations.enumerated()), id: \.offset) { index, curation in - let data = NavigationReadCurationType(curation: curation, - navigationParentView: .curations) - - UserCurationCell(curation: curation, + UserCurationCell(curation: .constant(curation), lineLimit: 2, navigationParentView: .curations) - .navigationLinkRouter(data: data) + .navigationLinkRouter(data: curation) .listRowInsets(EdgeInsets()) .listRowSeparator(.hidden) .listRowBackground(Color.shortcutsZipBackground) diff --git a/HappyAnding/HappyAnding/Views/ExploreCurationViews/ReadCurationView.swift b/HappyAnding/HappyAnding/Views/ExploreCurationViews/ReadCurationView.swift index adf759c0..f9662d69 100644 --- a/HappyAnding/HappyAnding/Views/ExploreCurationViews/ReadCurationView.swift +++ b/HappyAnding/HappyAnding/Views/ExploreCurationViews/ReadCurationView.swift @@ -9,34 +9,24 @@ import SwiftUI struct ReadCurationView: View { @Environment(\.presentationMode) var presentation: Binding - - @EnvironmentObject var shortcutsZipViewModel: ShortcutsZipViewModel @StateObject var writeCurationNavigation = WriteCurationNavigation() - - @State var authorInformation: User? = nil - @State var isWriting = false - @State var isTappedEditButton = false - @State var isTappedShareButton = false - @State var isTappedDeleteButton = false - @State var data: NavigationReadCurationType - @State var index = 0 + @StateObject var viewModel: ReadCurationViewModel var body: some View { ScrollView(showsIndicators: false) { - if data.isAdmin { + if viewModel.isAdmin { adminCuration } else { userCuration } VStack(spacing: 0) { - ForEach(shortcutsZipViewModel.userCurations[index].shortcuts, id: \.self) { shortcut in - let data = NavigationReadShortcutType(shortcutID: shortcut.id, - navigationParentView: self.data.navigationParentView) - ShortcutCell(shortcutCell: shortcut, - navigationParentView: self.data.navigationParentView) - .navigationLinkRouter(data: data) + ForEach(viewModel.curation.shortcuts, id: \.self) { shortcutCellModel in + + ShortcutCell(shortcutCell: shortcutCellModel, + navigationParentView: .curations) + .navigationLinkRouter(data: viewModel.fetchShortcut(from: shortcutCellModel)) } } @@ -48,20 +38,22 @@ struct ReadCurationView: View { .edgesIgnoringSafeArea(.top) .navigationBarTitleDisplayMode(.inline) .navigationBarItems(trailing: readCurationViewButtonByUser()) - .fullScreenCover(isPresented: $isWriting) { + .fullScreenCover(isPresented: $viewModel.isWriting) { NavigationRouter(content: editView, path: $writeCurationNavigation.navigationPath) .environmentObject(writeCurationNavigation) + .onDisappear() { + viewModel.fetchCuration() + } } - .alert(TextLiteral.readCurationViewDeletionTitle, isPresented: $isTappedDeleteButton) { + .alert(TextLiteral.readCurationViewDeletionTitle, isPresented: $viewModel.isTappedDeleteButton) { Button(role: .cancel) { - self.isTappedDeleteButton.toggle() + viewModel.isTappedDeleteButton.toggle() } label: { Text(TextLiteral.cancel) } Button(role: .destructive) { - shortcutsZipViewModel.deleteData(model: self.data.curation) - shortcutsZipViewModel.curationsMadeByUser = shortcutsZipViewModel.curationsMadeByUser.filter { $0.id != self.data.curation.id } + viewModel.deleteCuration() presentation.wrappedValue.dismiss() } label: { Text(TextLiteral.delete) @@ -77,10 +69,10 @@ struct ReadCurationView: View { StickyHeader(height: 100) VStack(spacing: 16) { - userInformation + UserNameCell(userInformation: viewModel.authInformation, gradeImage: viewModel.gradeImage) .padding(EdgeInsets(top: 103, leading: 16, bottom: 0, trailing: 16)) - UserCurationCell(curation: data.curation, navigationParentView: data.navigationParentView) + UserCurationCell(curation: $viewModel.curation, navigationParentView: .curations) } } .padding(.bottom, 8) @@ -90,13 +82,13 @@ struct ReadCurationView: View { var adminCuration: some View { VStack { - StickyHeader(height: 304, image: data.curation.background) + StickyHeader(height: 304, image: viewModel.curation.background) .padding(.bottom, 20) HStack { VStack(alignment: .leading, spacing: 4) { - SubtitleTextView(text: data.curation.title) - Text(data.curation.subtitle.replacingOccurrences(of: "\\n", with: "\n")) + SubtitleTextView(text: viewModel.curation.title) + Text(viewModel.curation.subtitle.replacingOccurrences(of: "\\n", with: "\n")) .shortcutsZipBody2() .foregroundColor(.gray4) } @@ -106,20 +98,6 @@ struct ReadCurationView: View { .padding(.bottom, 8) } } - var userInformation: some View { - ZStack { - UserNameCell(userInformation: self.authorInformation, gradeImage: shortcutsZipViewModel.fetchShortcutGradeImage(isBig: false, shortcutGrade: shortcutsZipViewModel.checkShortcutGrade(userID: authorInformation?.id ?? "!"))) - } - .onAppear { - shortcutsZipViewModel.fetchUser(userID: self.data.curation.author, - isCurrentUser: false) { user in - authorInformation = user - } - if let index = shortcutsZipViewModel.userCurations.firstIndex(where: { $0.id == data.curation.id}) { - self.index = index - } - } - } } @@ -127,18 +105,15 @@ extension ReadCurationView { @ViewBuilder private func editView() -> some View { - WriteCurationSetView(isWriting: $isWriting, - curation: shortcutsZipViewModel.userCurations[index] - ,isEdit: true - ) - .navigationDestination(for: WriteCurationInfoType.self) { data in - WriteCurationInfoView(data: data, isWriting: $isWriting) + WriteCurationSetView(isWriting: $viewModel.isWriting, viewModel: WriteCurationViewModel(data: viewModel.curation)) + .navigationDestination(for: WriteCurationViewModel.self) { data in + WriteCurationInfoView(viewModel: data, isWriting: $viewModel.isWriting) } } @ViewBuilder private func readCurationViewButtonByUser() -> some View { - if self.data.curation.author == shortcutsZipViewModel.currentUser() { + if viewModel.checkAuthor() { myCurationMenu } else { shareButton @@ -146,30 +121,30 @@ extension ReadCurationView { } private var myCurationMenu: some View { - Menu(content: { + Menu { Section { editButton shareButton deleteButton } - }, label: { + } label: { Image(systemName: "ellipsis") .foregroundColor(.gray4) - }) + } } private var editButton: some View { Button { - self.isWriting.toggle() + self.viewModel.isWriting.toggle() } label: { Label(TextLiteral.edit, systemImage: "square.and.pencil") } } private var shareButton: some View { - Button(action: { - shareCuration() - }) { + Button { + viewModel.shareCuration() + } label: { Label(TextLiteral.share, systemImage: "square.and.arrow.up") .foregroundColor(.gray4) .fontWeight(.medium) @@ -177,20 +152,10 @@ extension ReadCurationView { } private var deleteButton: some View { - Button(role: .destructive, action: { - isTappedDeleteButton.toggle() - }) { + Button(role: .destructive) { + viewModel.isTappedDeleteButton.toggle() + } label: { Label(TextLiteral.delete, systemImage: "trash.fill") } } - - private func shareCuration() { - guard let deepLink = URL(string: "ShortcutsZip://myPage/CurationDetailView?curationID=\(data.curation.id)") else { return } - - let activityVC = UIActivityViewController(activityItems: [deepLink], applicationActivities: nil) - let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene - guard let window = windowScene?.windows.first else { return } - window.rootViewController?.present(activityVC, animated: true, completion: nil) - } } - diff --git a/HappyAnding/HappyAnding/Views/ExploreShortcutViews/ExploreShortcutView.swift b/HappyAnding/HappyAnding/Views/ExploreShortcutViews/ExploreShortcutView.swift index 8a2fe41b..73741917 100644 --- a/HappyAnding/HappyAnding/Views/ExploreShortcutViews/ExploreShortcutView.swift +++ b/HappyAnding/HappyAnding/Views/ExploreShortcutViews/ExploreShortcutView.swift @@ -9,27 +9,177 @@ import SwiftUI struct ExploreShortcutView: View { - let firebase = FirebaseService() - @State var shortcutsArray: [Shortcuts] = [] + @StateObject var viewModel: ExploreShortcutViewModel + + // TODO: 추후 UpdateInfoView 제작 시 true로 변경해서 cell 보이게 하기 + @AppStorage("isUpdateAnnnouncementShow") var isUpdateAnnnouncementShow: Bool = false + + let randomCategories: [Category] var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) - .onAppear { - firebase.fetchShortcut(model: "Shortcut") { shortcuts in - shortcutsArray = shortcuts + ScrollViewReader { proxy in + ScrollView { + VStack(spacing: 32) { + if isUpdateAnnnouncementShow { + Button { + viewModel.announcementCellDidTap() + } label: { + AnnouncementCell(icon: "updateAppIcon", + tagName: TextLiteral.updateTag, + discription: TextLiteral.updateCellDescription, + isAnnouncementShow: $isUpdateAnnnouncementShow) + } + .id(000) + } + + sectionView(with: .recent) + .id(111) + + categoryCardView(with: randomCategories[0]) + + sectionView(with: .download) + + categoryCardView(with: randomCategories[1]) + + sectionView(with: .popular) + + VStack { + HStack { + SubtitleTextView(text: TextLiteral.categoryViewTitle) + + Spacer() + + Button { + viewModel.changeDisplayedCategories() + } label: { + MoreCaptionTextView(text: viewModel.isCategoryCellViewFolded ? TextLiteral.categoryViewUnfold : TextLiteral.categoryViewFold) + } + } + .padding(.horizontal, 16) + + LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())]) { + ForEach(Array(Category.allCases.enumerated()), id: \.offset) { index, value in + + if index < viewModel.numberOfDisplayedCategories { + + categoryCellView(with: value.translateName()) + .navigationLinkRouter(data: value) + + } + } + } + .padding(.horizontal, 16) + .padding(.bottom, 16) + .id(999) + } } + .padding(.top, 20) + .padding(.bottom, 44) } - .onTapGesture { - firebase.fetchCategoryLikedList(category: "education") { shortcuts in - shortcutsArray = shortcuts - print(shortcutsArray) + .onChange(of: viewModel.isCategoryCellViewFolded) { _ in + withAnimation { + proxy.scrollTo(999, anchor: .bottom) } } + } + .scrollIndicators(.hidden) + .navigationBarTitle(TextLiteral.exploreShortcutViewTitle) + .navigationBarTitleDisplayMode(.large) + .background(Color.shortcutsZipBackground) + .toolbar { + ToolbarItem { + Image(systemName: "magnifyingglass") + .shortcutsZipHeadline() + .foregroundColor(.gray5) + .navigationLinkRouter(data: NavigationSearch.first) + } + } + .navigationBarBackground ({ Color.shortcutsZipBackground }) + .sheet(isPresented: $viewModel.isAnnouncementCellShowing) { + UpdateInfoView() + .presentationDetents([.large]) + .presentationDragIndicator(.visible) + } } + } -struct ExploreShortcutView_Previews: PreviewProvider { - static var previews: some View { - ExploreShortcutView() +// MARK: ViewBuilder +extension ExploreShortcutView { + + @ViewBuilder + private func sectionView(with sectionType: SectionType) -> some View { + + let shortcuts = viewModel.fetchShortcuts(by: sectionType) + + VStack(spacing: 0) { + HStack { + SubtitleTextView(text: sectionType.title) + + Spacer() + + MoreCaptionTextView(text: TextLiteral.more) + .navigationLinkRouter(data: sectionType) + } + .padding(.horizontal, 16) + .padding(.bottom, 12) + + ForEach(Array(shortcuts.enumerated()), id:\.offset) { index, shortcut in + if index < 3 { + + ShortcutCell(shortcut: shortcut, + rankNumber: index + 1, + navigationParentView: .shortcuts) + .navigationLinkRouter(data: shortcut) + } + } + .background(Color.shortcutsZipBackground) + } + } + + @ViewBuilder + private func categoryCardView(with category: Category) -> some View { + + VStack(spacing: 0) { + HStack { + + SubtitleTextView(text: category.translateName()) + + Spacer() + + MoreCaptionTextView(text: TextLiteral.more) + .navigationLinkRouter(data: category) + } + .padding(.horizontal, 16) + .padding(.bottom, 12) + + ScrollView(.horizontal, showsIndicators: false) { + HStack { + ForEach(viewModel.fetchShortcuts(by: category).prefix(7), id: \.self) { shortcut in + + ShortcutCardCell(categoryShortcutIcon: shortcut.sfSymbol, + categoryShortcutName: shortcut.title, + categoryShortcutColor: shortcut.color) + .navigationLinkRouter(data: shortcut) + } + } + .padding(.horizontal, 16) + } + } + } + + @ViewBuilder + private func categoryCellView(with categoryName: String) -> some View { + RoundedRectangle(cornerSize: CGSize(width: 12, height: 12)) + .strokeBorder(Color.gray1, lineWidth: 1) + .background(Color.shortcutsZipWhite) + .cornerRadius(12) + .frame(maxWidth: .infinity, minHeight:48, maxHeight: 48) + .overlay { + Text(categoryName) + .shortcutsZipBody2() + .foregroundColor(Color.gray5) + } } } + diff --git a/HappyAnding/HappyAnding/Views/ExploreShortcutViews/ListCategoryShortcutView.swift b/HappyAnding/HappyAnding/Views/ExploreShortcutViews/ListCategoryShortcutView.swift index 2cb26e76..edc780c0 100644 --- a/HappyAnding/HappyAnding/Views/ExploreShortcutViews/ListCategoryShortcutView.swift +++ b/HappyAnding/HappyAnding/Views/ExploreShortcutViews/ListCategoryShortcutView.swift @@ -1,5 +1,5 @@ // -// ShortcutsListView.swift +// ListCategoryShortcutView.swift // HappyAnding // // Created by KiWoong Hong on 2022/11/05. @@ -9,44 +9,34 @@ import SwiftUI struct ListCategoryShortcutView: View { - @EnvironmentObject var shortcutsZipViewModel: ShortcutsZipViewModel - - @State var navigationTitle = "" - @State var isLastShortcut: Bool = false - @State var data: NavigationListCategoryShortcutType + @StateObject var viewModel: ListCategoryShortcutViewModel var body: some View { + ScrollView { - scrollHeader + categoryHeader LazyVStack(spacing: 0) { - ForEach(data.shortcuts, id: \.self) { shortcut in - let data = NavigationReadShortcutType(shortcut: shortcut, - shortcutID: shortcut.id, - navigationParentView: self.data.navigationParentView) - ShortcutCell(shortcut: shortcut, - navigationParentView: self.data.navigationParentView) - .navigationLinkRouter(data: data) - .listRowInsets(EdgeInsets()) - .listRowSeparator(.hidden) + ForEach(viewModel.shortcuts, id: \.self) { shortcut in + + ShortcutCell(shortcut: shortcut, navigationParentView: .shortcuts) + .navigationLinkRouter(data: shortcut) + .listRowInsets(EdgeInsets()) + .listRowSeparator(.hidden) + } } .padding(.bottom, 44) } - .navigationBarTitle(data.categoryName.translateName()) + .navigationBarTitle(viewModel.category.translateName()) .navigationBarTitleDisplayMode(.inline) .background(Color.shortcutsZipBackground) .navigationBarBackground ({ Color.shortcutsZipBackground }) - .onAppear { - self.data.shortcuts = shortcutsZipViewModel.shortcutsInCategory[data.categoryName.index] - } } - var scrollHeader: some View { - VStack { - Text(data.categoryName.fetchDescription().lineBreaking) - } + var categoryHeader: some View { + Text(viewModel.category.fetchDescription().lineBreaking) .foregroundColor(.gray5) .shortcutsZipBody2() .padding(16) diff --git a/HappyAnding/HappyAnding/Views/ExploreShortcutViews/ListShortcutView.swift b/HappyAnding/HappyAnding/Views/ExploreShortcutViews/ListShortcutView.swift index 5d21a150..df6fe15c 100644 --- a/HappyAnding/HappyAnding/Views/ExploreShortcutViews/ListShortcutView.swift +++ b/HappyAnding/HappyAnding/Views/ExploreShortcutViews/ListShortcutView.swift @@ -11,85 +11,51 @@ import SwiftUI /// sectionType: 다운로드 순위에서 접근할 시, .download를, 사랑받는 앱에서 접근시 .popular를 넣어주세요. struct ListShortcutView: View { - @EnvironmentObject var shortcutsZipViewModel: ShortcutsZipViewModel - - @State var data: NavigationListShortcutType - @State private var isLastItem = false + @StateObject var viewModel: ListShortcutViewModel var body: some View { - if let shortcuts = data.shortcuts { - if shortcuts.count == 0 { - Text("아직 \(data.sectionType.title)가 없어요") - .shortcutsZipBody2() - .foregroundColor(Color.gray4) - .frame(maxWidth: .infinity, maxHeight: .infinity) - - .background(Color.shortcutsZipBackground.ignoresSafeArea(.all, edges: .all)) - .navigationTitle(data.sectionType.title) - .navigationBarTitleDisplayMode(.inline) - } else { - ScrollView { - LazyVStack(spacing: 0) { - - //TODO: 무한 스크롤을 위한 업데이트 함수 필요 - switch data.sectionType { - case .recent: - makeShortcutCellList(shortcutsZipViewModel.allShortcuts) - case .download: - makeIndexShortcutCellList(shortcutsZipViewModel.sortedShortcutsByDownload) - case .popular: - makeShortcutCellList(shortcutsZipViewModel.sortedShortcutsByLike) - case .myDownloadShortcut: - makeShortcutCellList(shortcutsZipViewModel.shortcutsUserDownloaded) - case .myLovingShortcut: - makeShortcutCellList(shortcutsZipViewModel.shortcutsUserLiked) - case .myShortcut: - makeShortcutCellList(shortcutsZipViewModel.shortcutsMadeByUser) - } - Rectangle() - .fill(Color.shortcutsZipBackground) - .frame(height: 44) - .listRowInsets(EdgeInsets()) - .listRowSeparator(.hidden) - } - } - .listRowBackground(Color.shortcutsZipBackground) - .listStyle(.plain) + if viewModel.shortcuts.count == 0 { + Text("아직 \(viewModel.sectionType.title)가 없어요") + .shortcutsZipBody2() + .foregroundColor(Color.gray4) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color.shortcutsZipBackground.ignoresSafeArea(.all, edges: .all)) - .scrollContentBackground(.hidden) - .navigationTitle(data.sectionType.title) + .navigationTitle(viewModel.sectionType.title) .navigationBarTitleDisplayMode(.inline) - .navigationBarBackground ({ Color.shortcutsZipBackground }) + } else { + ScrollView { + LazyVStack(spacing: 0) { + + //TODO: 무한 스크롤을 위한 업데이트 함수 필요 + makeShortcutCellList(viewModel.shortcuts) + + Rectangle() + .fill(Color.shortcutsZipBackground) + .frame(height: 44) + .listRowInsets(EdgeInsets()) + .listRowSeparator(.hidden) + } } + .listRowBackground(Color.shortcutsZipBackground) + .listStyle(.plain) + .background(Color.shortcutsZipBackground.ignoresSafeArea(.all, edges: .all)) + .scrollContentBackground(.hidden) + .navigationTitle(viewModel.sectionType.title) + .navigationBarTitleDisplayMode(.inline) + .navigationBarBackground ({ Color.shortcutsZipBackground }) } } @ViewBuilder private func makeShortcutCellList(_ shortcuts: [Shortcuts]) -> some View { ForEach(shortcuts, id: \.self) { shortcut in - let navigationData = NavigationReadShortcutType(shortcut: shortcut, - shortcutID: shortcut.id, - navigationParentView: self.data.navigationParentView) - ShortcutCell(shortcut: shortcut, - sectionType: data.sectionType, - navigationParentView: data.navigationParentView) - .navigationLinkRouter(data: navigationData) - } - } - - @ViewBuilder - private func makeIndexShortcutCellList(_ shortcuts: [Shortcuts]) -> some View { - ForEach(Array(shortcuts.enumerated()), id: \.offset) { index, shortcut in - let navigationData = NavigationReadShortcutType(shortcut: shortcut, - shortcutID: shortcut.id, - navigationParentView: self.data.navigationParentView) ShortcutCell(shortcut: shortcut, - rankNumber: index + 1, - navigationParentView: data.navigationParentView) - .navigationLinkRouter(data: navigationData) - .listRowInsets(EdgeInsets()) - .listRowSeparator(.hidden) + sectionType: viewModel.sectionType, + navigationParentView: .shortcuts) + .navigationLinkRouter(data: shortcut) + } } } diff --git a/HappyAnding/HappyAnding/Views/HappyAndingApp.swift b/HappyAnding/HappyAnding/Views/HappyAndingApp.swift index b086a81b..918638ef 100644 --- a/HappyAnding/HappyAnding/Views/HappyAndingApp.swift +++ b/HappyAnding/HappyAnding/Views/HappyAndingApp.swift @@ -16,7 +16,7 @@ struct HappyAndingApp: App { @Environment(\.scenePhase) var scenePhase @Environment(\.openURL) private var openURL - @StateObject var shortcutsZipViewModel = ShortcutsZipViewModel() + @StateObject var shortcutsZipViewModel = ShortcutsZipViewModel.share @StateObject var loginAlerter = Alerter() @StateObject var gradeAlerter = Alerter() diff --git a/HappyAnding/HappyAnding/Views/MyPageViews/MyPageView.swift b/HappyAnding/HappyAnding/Views/MyPageViews/MyPageView.swift index 24d32177..8189d867 100644 --- a/HappyAnding/HappyAnding/Views/MyPageViews/MyPageView.swift +++ b/HappyAnding/HappyAnding/Views/MyPageViews/MyPageView.swift @@ -88,11 +88,6 @@ struct MyPageShortcutListCell: View { var type: SectionType let shortcuts: [Shortcuts] - var data: NavigationListShortcutType { - NavigationListShortcutType(sectionType: self.type, - shortcuts: self.shortcuts, - navigationParentView: .myPage) - } var body: some View { HStack() { Text(type == .myLovingShortcut ? TextLiteral.myPageViewLikedShortcuts : TextLiteral.myPageViewDownloadedShortcuts) @@ -119,6 +114,6 @@ struct MyPageShortcutListCell: View { } .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 16) - .navigationLinkRouter(data: data) + .navigationLinkRouter(data: self.type) } } diff --git a/HappyAnding/HappyAnding/Views/ReadShortcutViews/ReadShortcutView.swift b/HappyAnding/HappyAnding/Views/ReadShortcutViews/ReadShortcutView.swift index 367bbe9a..fe8d264d 100644 --- a/HappyAnding/HappyAnding/Views/ReadShortcutViews/ReadShortcutView.swift +++ b/HappyAnding/HappyAnding/Views/ReadShortcutViews/ReadShortcutView.swift @@ -14,29 +14,9 @@ struct ReadShortcutView: View { @Environment(\.openURL) private var openURL @Environment(\.loginAlertKey) var loginAlerter - @EnvironmentObject var shortcutsZipViewModel: ShortcutsZipViewModel - + @StateObject var viewModel: ReadShortcutViewModel @StateObject var writeNavigation = WriteShortcutNavigation() - @State var isDeletingShortcut = false - @State var isEditingShortcut = false - @State var isUpdatingShortcut = false - - @State var isMyLike = false - @State var isMyFirstLike = false - @State var isDownloadingShortcut = false - @State var isDowngradingUserLevel = false - - @State var currentTab: Int = 0 - @State var data: NavigationReadShortcutType - @State var comments: Comments = Comments(id: "", comments: []) - @State var comment: Comment = Comment(user_nickname: "", user_id: "", date: "", depth: 0, contents: "") - @State var nestedCommentTarget: String = "" - @State var commentText = "" - - @State var isEditingComment = false - @State var isUndoingCommentEdit = false - @AppStorage("useWithoutSignIn") var useWithoutSignIn: Bool = false @FocusState private var isFocused: Bool @Namespace var namespace @@ -49,49 +29,41 @@ struct ReadShortcutView: View { ScrollViewReader { proxy in ScrollView { VStack(spacing: 0) { - if data.shortcut != nil { - - StickyHeader(height: 40) - - /// 단축어 타이틀 - ReadShortcutViewHeader(shortcut: $data.shortcut.unwrap()!, isMyLike: $isMyLike) - - /// 탭뷰 (기본 정보, 버전 정보, 댓글) - LazyVStack(pinnedViews: [.sectionHeaders]) { - Section(header: tabBarView) { - ZStack { - TabView(selection: self.$currentTab) { - Color.clear - .tag(0) - Color.clear - .tag(1) - Color.clear - .tag(2) - } - .tabViewStyle(.page(indexDisplayMode: .never)) - .frame(minHeight: UIScreen.screenHeight / 2) - - switch currentTab { - case 0: - ReadShortcutContentView(shortcut: $data.shortcut.unwrap()!) - case 1: - ReadShortcutVersionView(shortcut: $data.shortcut.unwrap()!, isUpdating: $isUpdatingShortcut) - case 2: - ReadShortcutCommentView(isFocused: _isFocused, - newComment: $comment, - comments: $comments, - nestedCommentTarget: $nestedCommentTarget, - isEditingComment: $isEditingComment, - shortcutID: data.shortcutID) + StickyHeader(height: 40) + + /// 단축어 타이틀 + ReadShortcutViewHeader(viewModel: self.viewModel) + + /// 탭뷰 (기본 정보, 버전 정보, 댓글) + LazyVStack(pinnedViews: [.sectionHeaders]) { + Section(header: tabBarView) { + ZStack { + TabView(selection: $viewModel.currentTab) { + Color.clear + .tag(0) + Color.clear + .tag(1) + Color.clear + .tag(2) + } + .tabViewStyle(.page(indexDisplayMode: .never)) + .frame(minHeight: UIScreen.screenHeight / 2) + + switch viewModel.currentTab { + case 0: + ReadShortcutContentView(viewModel: self.viewModel) + case 1: + ReadShortcutVersionView(viewModel: self.viewModel) + case 2: + ReadShortcutCommentView(viewModel: self.viewModel) .id(bottomID) - default: - EmptyView() - } + default: + EmptyView() } - .animation(.easeInOut, value: currentTab) - .padding(.top, 4) - .padding(.horizontal, 16) } + .animation(.easeInOut, value: viewModel.currentTab) + .padding(.top, 4) + .padding(.horizontal, 16) } } } @@ -100,14 +72,14 @@ struct ReadShortcutView: View { NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidShowNotification, object: nil, queue: .main) { notification in withAnimation { - if currentTab == 2 && !isEditingComment && comment.depth == 0 { + if viewModel.currentTab == 2 && !viewModel.isEditingComment && viewModel.comment.depth == 0 { proxy.scrollTo(bottomID, anchor: .bottom) } } } } } - .scrollDisabled(isEditingComment) + .scrollDisabled(viewModel.isEditingComment) .background(Color.shortcutsZipBackground) .navigationBarBackground ({ Color.shortcutsZipWhite }) .navigationBarTitleDisplayMode(NavigationBarItem.TitleDisplayMode.inline) @@ -117,33 +89,29 @@ struct ReadShortcutView: View { /// Safe Area에 고정된 댓글창, 다운로드 버튼 VStack { - if !isEditingComment { - if currentTab == 2 { + if !viewModel.isEditingComment { + if viewModel.currentTab == 2 { commentTextField } if !isFocused { - if let shortcut = data.shortcut { - Button { - if !useWithoutSignIn { - if let url = URL(string: shortcut.downloadLink[0]) { - if (shortcutsZipViewModel.userInfo?.downloadedShortcuts.firstIndex(where: { $0.id == data.shortcutID })) == nil { - data.shortcut?.numberOfDownload += 1 - } - isDownloadingShortcut = true - openURL(url) - } - shortcutsZipViewModel.updateNumberOfDownload(shortcut: shortcut, downloadlinkIndex: 0) - } else { - loginAlerter.isPresented = true + Button { + if !useWithoutSignIn { + if let url = URL(string: viewModel.shortcut.downloadLink[0]) { + viewModel.checkIfDownloaded() + viewModel.isDownloadingShortcut = true + openURL(url) } - } label: { - Text("다운로드 | \(Image(systemName: "arrow.down.app.fill")) \(shortcut.numberOfDownload)") - .shortcutsZipBody1() - .foregroundColor(Color.textIcon) - .padding() - .frame(maxWidth: .infinity) - .background(Color.shortcutsZipPrimary) + viewModel.updateNumberOfDownload(index: 0) + } else { + loginAlerter.isPresented = true } + } label: { + Text("다운로드 | \(Image(systemName: "arrow.down.app.fill")) \(viewModel.shortcut.numberOfDownload)") + .shortcutsZipBody1() + .foregroundColor(Color.textIcon) + .padding() + .frame(maxWidth: .infinity) + .background(Color.shortcutsZipPrimary) } } } @@ -152,58 +120,39 @@ struct ReadShortcutView: View { } .onAppear { UINavigationBar.appearance().standardAppearance.configureWithTransparentBackground() - data.shortcut = shortcutsZipViewModel.fetchShortcutDetail(id: data.shortcutID) - isMyLike = shortcutsZipViewModel.checkLikedShortrcut(shortcutID: data.shortcutID) - isMyFirstLike = isMyLike - self.comments = shortcutsZipViewModel.fetchComment(shortcutID: data.shortcutID) - } - .onChange(of: isEditingShortcut || isUpdatingShortcut) { _ in - if !isEditingShortcut || !isUpdatingShortcut { - data.shortcut = shortcutsZipViewModel.fetchShortcutDetail(id: data.shortcutID) - } - } - .onChange(of: shortcutsZipViewModel.allComments) { _ in - self.comments = shortcutsZipViewModel.fetchComment(shortcutID: data.shortcutID) } .onDisappear { - if let shortcut = data.shortcut { - if isMyLike != isMyFirstLike { - shortcutsZipViewModel.updateNumberOfLike(isMyLike: isMyLike, shortcut: shortcut) - } - } + viewModel.onViewDisappear() } - .alert(TextLiteral.readShortcutViewDeletionTitle, isPresented: $isDeletingShortcut) { + .alert(TextLiteral.readShortcutViewDeletionTitle, isPresented: $viewModel.isDeletingShortcut) { Button(role: .cancel) { } label: { Text(TextLiteral.cancel) } Button(role: .destructive) { - if let shortcut = data.shortcut { - shortcutsZipViewModel.deleteShortcutIDInUser(shortcutID: shortcut.id) - shortcutsZipViewModel.deleteShortcutInCuration(curationsIDs: shortcut.curationIDs, shortcutID: shortcut.id) - shortcutsZipViewModel.deleteData(model: shortcut) - shortcutsZipViewModel.shortcutsMadeByUser = shortcutsZipViewModel.shortcutsMadeByUser.filter { $0.id != shortcut.id } - shortcutsZipViewModel.updateShortcutGrade() - self.presentation.wrappedValue.dismiss() - } + viewModel.deleteShortcut() + self.presentation.wrappedValue.dismiss() } label: { Text(TextLiteral.delete) } } message: { - Text(isDowngradingUserLevel ? TextLiteral.readShortcutViewDeletionMessageDowngrade : TextLiteral.readShortcutViewDeletionMessage) + Text(viewModel.isDowngradingUserLevel ? TextLiteral.readShortcutViewDeletionMessageDowngrade : TextLiteral.readShortcutViewDeletionMessage) } - .fullScreenCover(isPresented: $isEditingShortcut) { + .fullScreenCover(isPresented: $viewModel.isEditingShortcut) { NavigationRouter(content: writeShortcutView, path: $writeNavigation.navigationPath) .environmentObject(writeNavigation) + .onDisappear { + viewModel.refreshShortcut() + } } - .fullScreenCover(isPresented: $isUpdatingShortcut) { - UpdateShortcutView(isUpdating: $isUpdatingShortcut, shortcut: $data.shortcut) + .fullScreenCover(isPresented: $viewModel.isUpdatingShortcut) { + UpdateShortcutView(viewModel: self.viewModel) } /// 댓글 수정할 때 뒷 배경을 어둡게 만들기 위한 뷰 - if isEditingComment { + if viewModel.isEditingComment { Color.black .ignoresSafeArea() .opacity(0.4) @@ -218,13 +167,13 @@ struct ReadShortcutView: View { } } .onAppear { - commentText = comment.contents + viewModel.commentText = viewModel.comment.contents } .onTapGesture(count: 1) { isFocused.toggle() - isUndoingCommentEdit.toggle() + viewModel.isUndoingCommentEdit.toggle() } - .alert(TextLiteral.readShortcutViewDeleteFixesTitle, isPresented: $isUndoingCommentEdit) { + .alert(TextLiteral.readShortcutViewDeleteFixesTitle, isPresented: $viewModel.isUndoingCommentEdit) { Button(role: .cancel) { isFocused.toggle() } label: { @@ -233,9 +182,7 @@ struct ReadShortcutView: View { Button(role: .destructive) { withAnimation(.easeInOut) { - isEditingComment.toggle() - comment = comment.resetComment() - commentText = "" + viewModel.cancelEditingComment() } } label: { Text(TextLiteral.delete) @@ -250,11 +197,9 @@ struct ReadShortcutView: View { @ViewBuilder private func writeShortcutView() -> some View { - if let shortcut = data.shortcut { - WriteShortcutView(isWriting: $isEditingShortcut, - shortcut: shortcut, - isEdit: true) - } + WriteShortcutView(isWriting: $viewModel.isEditingShortcut, + shortcut: viewModel.shortcut, + isEdit: true) } } @@ -265,57 +210,43 @@ extension ReadShortcutView { private var commentTextField: some View { VStack(spacing: 0) { - if comment.depth == 1 && !isEditingComment { + if viewModel.comment.depth == 1 && !viewModel.isEditingComment { nestedCommentTargetView } HStack { - if comment.depth == 1 && !isEditingComment { + if viewModel.comment.depth == 1 && !viewModel.isEditingComment { Image(systemName: "arrow.turn.down.right") .smallIcon() .foregroundColor(.gray4) } - TextField(useWithoutSignIn ? TextLiteral.readShortcutViewCommentDescriptionBeforeLogin : TextLiteral.readShortcutViewCommentDescription, text: $commentText, axis: .vertical) + TextField(useWithoutSignIn ? TextLiteral.readShortcutViewCommentDescriptionBeforeLogin : TextLiteral.readShortcutViewCommentDescription, text: $viewModel.commentText, axis: .vertical) .keyboardType(.twitter) .disabled(useWithoutSignIn) .disableAutocorrection(true) .textInputAutocapitalization(.never) .shortcutsZipBody2() - .lineLimit(comment.depth == 1 ? 2 : 4) + .lineLimit(viewModel.comment.depth == 1 ? 2 : 4) .focused($isFocused) .onAppear { UIApplication.shared.hideKeyboard() } Button { - if !isEditingComment { - comment.contents = commentText - comment.date = Date().getDate() - comment.user_id = shortcutsZipViewModel.userInfo!.id - comment.user_nickname = shortcutsZipViewModel.userInfo!.nickname - comments.comments.append(comment) - } else { - if let index = comments.comments.firstIndex(where: { $0.id == comment.id }) { - comments.comments[index].contents = commentText - } - isEditingComment = false - } - shortcutsZipViewModel.setData(model: comments) - commentText = "" - comment = comment.resetComment() + viewModel.postComment() isFocused.toggle() } label: { Image(systemName: "paperplane.fill") .mediumIcon() - .foregroundColor(commentText == "" ? Color.gray2 : Color.gray5) + .foregroundColor(viewModel.commentText == "" ? Color.gray2 : Color.gray5) } - .disabled(commentText == "" ? true : false) + .disabled(viewModel.commentText == "" ? true : false) } .padding(.vertical, 12) .padding(.horizontal, 16) .background( Rectangle() .fill(Color.gray1) - .cornerRadius(12 ,corners: (comment.depth == 1) && (!isEditingComment) ? [.bottomLeft, .bottomRight] : .allCorners) + .cornerRadius(12 ,corners: (viewModel.comment.depth == 1) && (!viewModel.isEditingComment) ? [.bottomLeft, .bottomRight] : .allCorners) ) .padding(.horizontal, 16) .padding(.bottom, 20) @@ -325,15 +256,14 @@ extension ReadShortcutView { private var nestedCommentTargetView: some View { HStack { - Text("@ \(nestedCommentTarget)") + Text("@ \(viewModel.nestedCommentTarget)") .shortcutsZipFootnote() .foregroundColor(.gray5) Spacer() Button { - comment.bundle_id = "\(Date().getDate())_\(UUID().uuidString)" - comment.depth = 0 + viewModel.cancelNestedComment() } label: { Image(systemName: "xmark") .smallIcon() @@ -354,7 +284,7 @@ extension ReadShortcutView { @ViewBuilder private func readShortcutViewNavigationBarItems() -> some View { - if self.data.shortcut?.author == shortcutsZipViewModel.currentUser() { + if viewModel.checkAuthor() { Menu { Section { editButton @@ -374,7 +304,7 @@ extension ReadShortcutView { private var editButton: some View { Button { - isEditingShortcut.toggle() + viewModel.isEditingShortcut.toggle() } label: { Label(TextLiteral.edit, systemImage: "square.and.pencil") } @@ -382,7 +312,7 @@ extension ReadShortcutView { private var updateButton: some View { Button { - isUpdatingShortcut.toggle() + viewModel.isUpdatingShortcut.toggle() } label: { Label(TextLiteral.update, systemImage: "clock.arrow.circlepath") } @@ -390,13 +320,7 @@ extension ReadShortcutView { private var shareButton: some View { Button { - if let shortcut = data.shortcut { - guard let deepLink = URL(string: "ShortcutsZip://myPage/detailView?shortcutID=\(shortcut.id)") else { return } - let activityVC = UIActivityViewController(activityItems: [deepLink], applicationActivities: nil) - let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene - guard let window = windowScene?.windows.first else { return } - window.rootViewController?.present(activityVC, animated: true, completion: nil) - } + viewModel.shareShortcut() } label: { Label(TextLiteral.share, systemImage: "square.and.arrow.up") .foregroundColor(.gray4) @@ -406,8 +330,7 @@ extension ReadShortcutView { private var deleteButton: some View { Button(role: .destructive) { - isDeletingShortcut.toggle() - isDowngradingUserLevel = shortcutsZipViewModel.isShortcutDowngrade() + viewModel.checkDowngrading() } label: { Label(TextLiteral.delete, systemImage: "trash.fill") } @@ -418,7 +341,7 @@ extension ReadShortcutView { private var tabBarView: some View { HStack(spacing: 20) { ForEach(Array(zip(self.tabItems.indices, self.tabItems)), id: \.0) { index, name in - tabBarItem(string: name, tab: index) + tabBarItem(title: name, tabID: index) } } .padding(.horizontal, 16) @@ -426,13 +349,13 @@ extension ReadShortcutView { .background(Color.shortcutsZipWhite) } - private func tabBarItem(string: String, tab: Int) -> some View { + private func tabBarItem(title: String, tabID: Int) -> some View { Button { - self.currentTab = tab + viewModel.moveTab(to: tabID) } label: { VStack { - if self.currentTab == tab { - Text(string) + if viewModel.currentTab == tabID { + Text(title) .shortcutsZipHeadline() .foregroundColor(.gray5) Color.gray5 @@ -440,13 +363,13 @@ extension ReadShortcutView { .matchedGeometryEffect(id: "underline", in: namespace, properties: .frame) } else { - Text(string) + Text(title) .shortcutsZipBody1() .foregroundColor(.gray3) Color.clear.frame(height: 2) } } - .animation(.spring(), value: currentTab) + .animation(.spring(), value: viewModel.currentTab) } .buttonStyle(.plain) } @@ -460,13 +383,9 @@ extension ReadShortcutView { @Environment(\.loginAlertKey) var loginAlerter @EnvironmentObject var shortcutsZipViewModel: ShortcutsZipViewModel - @AppStorage("useWithoutSignIn") var useWithoutSignIn: Bool = false - - @Binding var shortcut: Shortcuts - @Binding var isMyLike: Bool + @StateObject var viewModel: ReadShortcutViewModel - @State var userInformation: User? = nil - @State var numberOfLike = 0 + @AppStorage("useWithoutSignIn") var useWithoutSignIn: Bool = false var body: some View { VStack(alignment: .leading, spacing: 16) { @@ -474,27 +393,27 @@ extension ReadShortcutView { /// 단축어 아이콘 VStack { - Image(systemName: shortcut.sfSymbol) + Image(systemName: viewModel.shortcut.sfSymbol) .mediumShortcutIcon() .foregroundColor(Color.textIcon) } .frame(width: 52, height: 52) - .background(Color.fetchGradient(color: shortcut.color)) + .background(Color.fetchGradient(color: viewModel.shortcut.color)) .cornerRadius(8) Spacer() /// 좋아요 버튼 - Text("\(isMyLike ? Image(systemName: "heart.fill") : Image(systemName: "heart")) \(numberOfLike)") + Text("\(viewModel.isMyLike ? Image(systemName: "heart.fill") : Image(systemName: "heart")) \(viewModel.numberOfLike)") .shortcutsZipBody2() .padding(10) - .foregroundColor(isMyLike ? Color.textIcon : Color.gray4) - .background(isMyLike ? Color.shortcutsZipPrimary : Color.gray1) + .foregroundColor(viewModel.isMyLike ? Color.textIcon : Color.gray4) + .background(viewModel.isMyLike ? Color.shortcutsZipPrimary : Color.gray1) .cornerRadius(12) .onTapGesture { if !useWithoutSignIn { - isMyLike.toggle() - numberOfLike += isMyLike ? 1 : -1 + viewModel.isMyLike.toggle() + viewModel.numberOfLike += viewModel.isMyLike ? 1 : -1 } else { loginAlerter.isPresented = true } @@ -503,34 +422,24 @@ extension ReadShortcutView { /// 단축어 이름, 한 줄 설명 VStack(alignment: .leading, spacing: 4) { - Text("\(shortcut.title)") + Text("\(viewModel.shortcut.title)") .shortcutsZipTitle1() .foregroundColor(Color.gray5) .fixedSize(horizontal: false, vertical: true) - Text("\(shortcut.subtitle)") + Text("\(viewModel.shortcut.subtitle)") .shortcutsZipBody1() .foregroundColor(Color.gray3) .fixedSize(horizontal: false, vertical: true) } /// 단축어 작성자 닉네임 - UserNameCell(userInformation: userInformation, gradeImage: shortcutsZipViewModel.fetchShortcutGradeImage(isBig: false, shortcutGrade: shortcutsZipViewModel.checkShortcutGrade(userID: userInformation?.id ?? "!"))) + UserNameCell(userInformation: viewModel.author, gradeImage: viewModel.userGrade) } .frame(maxWidth: .infinity, minHeight: 160, alignment: .leading) .padding(.bottom, 20) .padding(.horizontal, 16) .background(Color.shortcutsZipWhite) - .onAppear { - shortcutsZipViewModel.fetchUser(userID: shortcut.author, - isCurrentUser: false) { user in - userInformation = user - } - numberOfLike = shortcut.numberOfLike - } - .onDisappear { - self.shortcut.numberOfLike = numberOfLike - } } } @@ -538,8 +447,7 @@ extension ReadShortcutView { struct ReadShortcutContentView: View { - @Binding var shortcut: Shortcuts - + @StateObject var viewModel: ReadShortcutViewModel var body: some View { VStack(alignment: .leading, spacing: 24) { @@ -548,16 +456,16 @@ extension ReadShortcutView { Text(TextLiteral.readShortcutContentViewDescription) .shortcutsZipBody2() .foregroundColor(Color.gray4) - Text(shortcut.description) + Text(viewModel.shortcut.description) .shortcutsZipBody2() .foregroundColor(Color.gray5) .lineLimit(nil) } - splitList(title: TextLiteral.readShortcutContentViewCategory, content: shortcut.category) + splitList(title: TextLiteral.readShortcutContentViewCategory, content: viewModel.shortcut.category) - if !shortcut.requiredApp.isEmpty { - splitList(title: TextLiteral.readShortcutContentViewRequiredApps, content: shortcut.requiredApp) + if !viewModel.shortcut.requiredApp.isEmpty { + splitList(title: TextLiteral.readShortcutContentViewRequiredApps, content: viewModel.shortcut.requiredApp) } Spacer() @@ -603,15 +511,14 @@ extension ReadShortcutView { @Environment(\.loginAlertKey) var loginAlerter @EnvironmentObject var shortcutsZipViewModel: ShortcutsZipViewModel - @AppStorage("useWithoutSignIn") var useWithoutSignIn: Bool = false + @StateObject var viewModel: ReadShortcutViewModel - @Binding var shortcut: Shortcuts - @Binding var isUpdating: Bool + @AppStorage("useWithoutSignIn") var useWithoutSignIn: Bool = false var body: some View { VStack(alignment: .leading, spacing: 16) { - if shortcut.updateDescription.count == 1 { + if viewModel.shortcut.updateDescription.count == 1 { Text(TextLiteral.readShortcutVersionViewNoUpdates) .shortcutsZipBody2() .foregroundColor(.gray4) @@ -632,16 +539,16 @@ extension ReadShortcutView { private var versionView: some View { - ForEach(Array(zip(shortcut.updateDescription, shortcut.updateDescription.indices)), id: \.0) { data, index in + ForEach(Array(zip(viewModel.shortcut.updateDescription, viewModel.shortcut.updateDescription.indices)), id: \.0) { data, index in VStack(alignment: .leading, spacing: 12) { HStack { - Text("Ver \(shortcut.updateDescription.count - index).0") + Text("Ver \(viewModel.shortcut.updateDescription.count - index).0") .shortcutsZipBody2() .foregroundColor(.gray5) Spacer() - Text(shortcut.date[index].getVersionUpdateDateFormat()) + Text(viewModel.shortcut.date[index].getVersionUpdateDateFormat()) .shortcutsZipBody2() .foregroundColor(.gray3) } @@ -655,11 +562,9 @@ extension ReadShortcutView { if index != 0 { Button { if !useWithoutSignIn { - if let url = URL(string: shortcut.downloadLink[index]) { - if (shortcutsZipViewModel.userInfo?.downloadedShortcuts.firstIndex(where: { $0.id == shortcut.id })) == nil { - shortcut.numberOfDownload += 1 - } - shortcutsZipViewModel.updateNumberOfDownload(shortcut: shortcut, downloadlinkIndex: index) + if let url = URL(string: viewModel.shortcut.downloadLink[index]) { + viewModel.checkIfDownloaded() + viewModel.updateNumberOfDownload(index: index) openURL(url) } } else { @@ -685,24 +590,16 @@ extension ReadShortcutView { @EnvironmentObject var shortcutsZipViewModel: ShortcutsZipViewModel + @StateObject var viewModel: ReadShortcutViewModel + @AppStorage("useWithoutSignIn") var useWithoutSignIn: Bool = false @FocusState var isFocused: Bool - @Binding var newComment: Comment /// 추가되는 댓글 - @Binding var comments: Comments /// 화면에 나타나는 모든 댓글 - @Binding var nestedCommentTarget: String /// 대댓글 작성 시 텍스트필드 위에 뜨는 작성자 정보 - @Binding var isEditingComment: Bool - - @State var isDeletingComment = false - @State var deletedComment = Comment(user_nickname: "", user_id: "", date: "", depth: 0, contents: "") - - let shortcutID: String - var body: some View { VStack(alignment: .leading) { - if comments.comments.isEmpty { + if viewModel.comments.comments.isEmpty { Text(TextLiteral.readShortcutCommentViewNoComments) .shortcutsZipBody2() .foregroundColor(.gray4) @@ -715,7 +612,7 @@ extension ReadShortcutView { .frame(maxHeight: .infinity) } .padding(.top, 16) - .alert(TextLiteral.readShortcutCommentViewDeletionTitle, isPresented: $isDeletingComment) { + .alert(TextLiteral.readShortcutCommentViewDeletionTitle, isPresented: $viewModel.isDeletingComment) { Button(role: .cancel) { } label: { @@ -723,13 +620,7 @@ extension ReadShortcutView { } Button(role: .destructive) { - if deletedComment.depth == 0 { - comments.comments.removeAll(where: { $0.bundle_id == deletedComment.bundle_id}) - } else { - comments.comments.removeAll(where: { $0.id == deletedComment.id}) - } - - shortcutsZipViewModel.setData(model: comments) + viewModel.deleteComment() } label: { Text(TextLiteral.delete) } @@ -740,7 +631,7 @@ extension ReadShortcutView { private var commentView: some View { - ForEach(comments.comments, id: \.self) { comment in + ForEach(viewModel.comments.comments, id: \.self) { comment in HStack(alignment: .top, spacing: 8) { if comment.depth == 1 { @@ -754,7 +645,7 @@ extension ReadShortcutView { /// 유저 정보 HStack(spacing: 8) { - shortcutsZipViewModel.fetchShortcutGradeImage(isBig: false, shortcutGrade: shortcutsZipViewModel.checkShortcutGrade(userID: comment.user_id )) + viewModel.fetchUserGrade(id: comment.user_id) .font(.system(size: 24, weight: .medium)) .frame(width: 24, height: 24) .foregroundColor(.gray3) @@ -765,7 +656,6 @@ extension ReadShortcutView { } .padding(.bottom, 4) - /// 댓글 내용 Text(comment.contents) .shortcutsZipBody2() @@ -776,9 +666,7 @@ extension ReadShortcutView { HStack(spacing: 0) { if !useWithoutSignIn { Button { - nestedCommentTarget = comment.user_nickname - newComment.bundle_id = comment.bundle_id - newComment.depth = 1 + viewModel.setReply(to: comment) isFocused = true } label: { Text(TextLiteral.readShortcutCommentViewReply) @@ -792,8 +680,8 @@ extension ReadShortcutView { if user.id == comment.user_id { Button { withAnimation(.easeInOut) { - isEditingComment.toggle() - newComment = comment + viewModel.isEditingComment.toggle() + viewModel.comment = comment } } label: { Text(TextLiteral.readShortcutCommentViewEdit) @@ -803,8 +691,8 @@ extension ReadShortcutView { } Button { - isDeletingComment.toggle() - deletedComment = comment + viewModel.isDeletingComment.toggle() + viewModel.deletedComment = comment } label: { Text(TextLiteral.delete) .shortcutsZipFootnote() diff --git a/HappyAnding/HappyAnding/Views/ReadShortcutViews/ShowProfileView.swift b/HappyAnding/HappyAnding/Views/ReadShortcutViews/ShowProfileView.swift index 652b1a46..6a6f6ff4 100644 --- a/HappyAnding/HappyAnding/Views/ReadShortcutViews/ShowProfileView.swift +++ b/HappyAnding/HappyAnding/Views/ReadShortcutViews/ShowProfileView.swift @@ -9,15 +9,7 @@ import SwiftUI struct ShowProfileView: View { - @EnvironmentObject var shortcutsZipViewModel: ShortcutsZipViewModel - - @State var data: NavigationProfile - - @State var shortcuts: [Shortcuts] = [] - @State var curations: [Curation] = [] - @State var currentTab: Int = 0 - - @State private var animationAmount = 0.0 + @StateObject var viewModel: ShowProfileViewModel @Namespace var namespace @@ -26,29 +18,37 @@ struct ShowProfileView: View { var body: some View { ScrollView { VStack(spacing: 0) { - StickyHeader(height: 24) //MARK: 프로필이미지 및 닉네임 VStack(spacing: 8) { Button { withAnimation(.interpolatingSpring(stiffness: 10, damping: 3)) { - self.animationAmount += 360 + viewModel.profileDidTap() } } label: { - ZStack(alignment: .center) { - Circle() + if viewModel.shortcuts.isEmpty { + viewModel.userGrade + .resizable() .frame(width: 72, height: 72) - .foregroundColor(.gray1) - shortcutsZipViewModel.fetchShortcutGradeImage(isBig: true, shortcutGrade: shortcutsZipViewModel.checkShortcutGrade(userID: data.userInfo?.id ?? "")) - .rotation3DEffect( - .degrees(animationAmount), axis: (x: 0.0, y: 1.0, z: 0.0)) + .foregroundColor(.gray3) + } else { + ZStack(alignment: .center) { + Circle() + .frame(width: 72, height: 72) + .foregroundColor(.gray1) + viewModel.userGrade + .rotation3DEffect( + .degrees(viewModel.animationAmount), axis: (x: 0.0, y: 1.0, z: 0.0)) + } } } - Text(data.userInfo?.nickname ?? TextLiteral.defaultUser) + + Text(viewModel.user.nickname) .shortcutsZipTitle1() .foregroundColor(.gray5) } + .disabled(viewModel.shortcuts.isEmpty) .frame(maxWidth: .infinity) .padding(.bottom, 35) .background(Color.shortcutsZipWhite) @@ -66,22 +66,17 @@ struct ShowProfileView: View { .background(Color.shortcutsZipBackground) .toolbar(.visible, for: .tabBar) .onAppear { - shortcuts = shortcutsZipViewModel.allShortcuts.filter { $0.author == self.data.userInfo?.id } - curations = shortcutsZipViewModel.fetchCurationByAuthor(author: data.userInfo?.id ?? "") withAnimation(.interpolatingSpring(stiffness: 10, damping: 3)) { - self.animationAmount += 360 + viewModel.profileDidTap() } } } -} - -extension ShowProfileView { //MARK: 탭바 var tabBarView: some View { HStack(spacing: 20) { ForEach(Array(zip(self.tabItems.indices, self.tabItems)), id: \.0) { index, name in - tabBarItem(string: name, tab: index) + tabBarItem(title: name, tabID: index) } } .padding(.horizontal, 16) @@ -89,13 +84,13 @@ extension ShowProfileView { .background(Color.shortcutsZipWhite) } - private func tabBarItem(string: String, tab: Int) -> some View { + private func tabBarItem(title: String, tabID: Int) -> some View { Button { - self.currentTab = tab + viewModel.moveTab(to: tabID) } label: { VStack { - if self.currentTab == tab { - Text(string) + if viewModel.currentTab == tabID { + Text(title) .shortcutsZipHeadline() .foregroundColor(.gray5) Color.gray5 @@ -103,14 +98,14 @@ extension ShowProfileView { .matchedGeometryEffect(id: "underline", in: namespace, properties: .frame) } else { - Text(string) + Text(title) .shortcutsZipBody1() .foregroundColor(.gray3) Color.clear .frame(height: 2) } } - .animation(.spring(), value: currentTab) + .animation(.spring(), value: viewModel.currentTab) } .buttonStyle(.plain) } @@ -119,16 +114,16 @@ extension ShowProfileView { var profileContentView: some View { VStack { ZStack { - TabView(selection: self.$currentTab) { + TabView(selection: $viewModel.currentTab) { Color.clear.tag(0) Color.clear.tag(1) } .frame(minHeight: UIScreen.screenHeight / 2) .tabViewStyle(.page(indexDisplayMode: .never)) - switch(currentTab) { + switch(viewModel.currentTab) { case 0: - if shortcuts.isEmpty { + if viewModel.shortcuts.isEmpty { VStack { Text(TextLiteral.showProfileViewNoShortcuts) .padding(.top, 16) @@ -139,13 +134,10 @@ extension ShowProfileView { } VStack(spacing: 0) { - ForEach(shortcuts, id:\.self) { shortcut in - let data = NavigationReadShortcutType(shortcutID:shortcut.id, - navigationParentView: .shortcuts) - + ForEach(viewModel.shortcuts, id:\.self) { shortcut in ShortcutCell(shortcut: shortcut, - navigationParentView: data.navigationParentView) - .navigationLinkRouter(data: data) + navigationParentView: .shortcuts) + .navigationLinkRouter(data: shortcut) } Spacer() @@ -153,7 +145,7 @@ extension ShowProfileView { } .padding(.bottom, 44) case 1: - if curations.isEmpty { + if viewModel.curations.isEmpty { VStack{ Text(TextLiteral.showProfileViewNoCurations) .padding(.top, 16) @@ -164,13 +156,12 @@ extension ShowProfileView { } VStack(spacing: 0) { - ForEach(curations, id: \.self) { curation in - let data = NavigationReadCurationType(curation: curation, - navigationParentView: .shortcuts) - UserCurationCell(curation: curation, + ForEach(viewModel.curations, id: \.self) { curation in + // TODO: navigation parent view 삭제 + UserCurationCell(curation: .constant(curation), lineLimit: 2, - navigationParentView: data.navigationParentView) - .navigationLinkRouter(data: data) + navigationParentView: .curations) + .navigationLinkRouter(data: curation) } Spacer() @@ -180,7 +171,7 @@ extension ShowProfileView { EmptyView() } } - .animation(.easeInOut, value: currentTab) + .animation(.easeInOut, value: viewModel.currentTab) } } } diff --git a/HappyAnding/HappyAnding/Views/ReadShortcutViews/UpdateShortcutView.swift b/HappyAnding/HappyAnding/Views/ReadShortcutViews/UpdateShortcutView.swift index 8b6a72ba..ebd438de 100644 --- a/HappyAnding/HappyAnding/Views/ReadShortcutViews/UpdateShortcutView.swift +++ b/HappyAnding/HappyAnding/Views/ReadShortcutViews/UpdateShortcutView.swift @@ -9,27 +9,19 @@ import SwiftUI struct UpdateShortcutView: View { - @EnvironmentObject var shortcutsZipViewModel: ShortcutsZipViewModel + @StateObject var viewModel: ReadShortcutViewModel @FocusState var isDescriptionFieldFocused: Bool - @Binding var isUpdating: Bool - @Binding var shortcut: Shortcuts? - - @State var updatedLink = "" - @State var updateDescription = "" - @State var isLinkValid = false - @State var isDescriptionValid = false - var body: some View { VStack { HStack { - Button(action: { - isUpdating.toggle() - }, label: { + Button { + viewModel.isUpdatingShortcut.toggle() + } label: { Text(TextLiteral.cancel) .foregroundColor(.gray5) - }) + } .frame(maxWidth: .infinity, alignment: .leading) Text(TextLiteral.update) @@ -48,14 +40,14 @@ struct UpdateShortcutView: View { placeholder: TextLiteral.updateShortcutViewLinkPlaceholder, lengthLimit: 100, isDownloadLinkTextField: true, - content: $updatedLink, - isValid: $isLinkValid + content: $viewModel.updatedLink, + isValid: $viewModel.isLinkValid ) .onSubmit { isDescriptionFieldFocused = true } .submitLabel(.next) - .onAppear(perform : UIApplication.shared.hideKeyboard) + .onAppear(perform: UIApplication.shared.hideKeyboard) .padding(.top, 30) ValidationCheckTextField(textType: .mandatory, @@ -64,29 +56,26 @@ struct UpdateShortcutView: View { placeholder: TextLiteral.updateShortcutViewDescriptionPlaceholder, lengthLimit: 50, isDownloadLinkTextField: false, - content: $updateDescription, - isValid: $isDescriptionValid + content: $viewModel.updateDescription, + isValid: $viewModel.isDescriptionValid ) .focused($isDescriptionFieldFocused) Spacer() - Button(action: { - if let shortcut { - shortcutsZipViewModel.updateShortcutVersion(shortcut: shortcut, updateDescription: updateDescription, updateLink: updatedLink) - } - isUpdating.toggle() - }, label: { + Button { + viewModel.updateShortcut() + } label: { ZStack { RoundedRectangle(cornerRadius: 12) - .foregroundColor(isLinkValid && isDescriptionValid ? .shortcutsZipPrimary : .shortcutsZipPrimary.opacity(0.13)) + .foregroundColor(viewModel.isUpdateValid ? .shortcutsZipPrimary : .shortcutsZipPrimary.opacity(0.13)) .frame(maxWidth: .infinity, maxHeight: 52) Text(TextLiteral.update) - .foregroundColor(isLinkValid && isDescriptionValid ? .textButton : .textButtonDisable) + .foregroundColor(viewModel.isUpdateValid ? .textButton : .textButtonDisable) .shortcutsZipBody1() } - }) - .disabled(!isLinkValid || !isDescriptionValid) + } + .disabled(!viewModel.isUpdateValid) .padding(.horizontal, 16) .padding(.bottom, 24) } diff --git a/HappyAnding/HappyAnding/Views/SearchView.swift b/HappyAnding/HappyAnding/Views/SearchView.swift index 614746ea..2657da96 100644 --- a/HappyAnding/HappyAnding/Views/SearchView.swift +++ b/HappyAnding/HappyAnding/Views/SearchView.swift @@ -37,12 +37,9 @@ struct SearchView: View { VStack(spacing: 0) { ForEach(shortcutResults.sorted(by: { $0.title < $1.title }), id: \.self) { shortcut in - let data = NavigationReadShortcutType(shortcutID: shortcut.id, - navigationParentView: .shortcuts) - ShortcutCell(shortcut: shortcut, navigationParentView: NavigationParentView.shortcuts) - .navigationLinkRouter(data: data) + .navigationLinkRouter(data: shortcut) .listRowInsets(EdgeInsets()) .listRowSeparator(.hidden) } diff --git a/HappyAnding/HappyAnding/Views/TabView/ShortcutTabView.swift b/HappyAnding/HappyAnding/Views/TabView/ShortcutTabView.swift index f6750129..c7ccac52 100644 --- a/HappyAnding/HappyAnding/Views/TabView/ShortcutTabView.swift +++ b/HappyAnding/HappyAnding/Views/TabView/ShortcutTabView.swift @@ -28,7 +28,6 @@ struct ShortcutTabView: View { @State private var tempCurationId = "" @State private var randomCategories = Category.allCases.shuffled().prefix(2) - @State var isFolded = true @State private var twiceTappedTab = 0 @State private var firstTabID = UUID() @@ -72,11 +71,6 @@ struct ShortcutTabView: View { } self.twiceTappedTab = 0 } - .onChange(of: isFolded) { _ in - withAnimation { - proxy.scrollTo(999, anchor: .bottom) - } - } .environmentObject(shortcutNavigation) .tabItem { Label("단축어", systemImage: "square.stack.3d.up.fill") @@ -113,7 +107,7 @@ struct ShortcutTabView: View { @ViewBuilder private func firstTab() -> some View { - ExploreShortcutView(isCategoryCellViewFolded: $isFolded, randomCategories: Array(randomCategories)) + ExploreShortcutView(viewModel: ExploreShortcutViewModel(), randomCategories: Array(randomCategories)) .modifierNavigation() .navigationBarBackground ({ Color.shortcutsZipBackground }) .id(firstTabID) @@ -121,7 +115,7 @@ struct ShortcutTabView: View { @ViewBuilder private func secondTab() -> some View { - ExploreCurationView() + ExploreCurationView(viewModel: ExploreCurationViewModel()) .modifierNavigation() .navigationBarBackground ({ Color.shortcutsZipBackground }) .id(secondTabID) @@ -152,8 +146,7 @@ struct ShortcutTabView: View { tempShortcutId = shortcutIDfromURL isShortcutDeeplink = true - let data = NavigationReadShortcutType(shortcutID: self.tempShortcutId, - navigationParentView: .myPage) + let data = shortcutsZipViewModel.fetchShortcutDetail(id: tempShortcutId) navigateLink(data: data) } @@ -175,9 +168,7 @@ struct ShortcutTabView: View { isCurationDeeplink = true if let curation = shortcutsZipViewModel.fetchCurationDetail(curationID: tempCurationId) { - let data = NavigationReadCurationType(curation: curation, - navigationParentView: .myPage) - navigateLink(data: data) + navigateLink(data: curation) } } diff --git a/HappyAnding/HappyAnding/Views/WriteCurationViews/CheckBoxShortcutCell.swift b/HappyAnding/HappyAnding/Views/WriteCurationViews/CheckBoxShortcutCell.swift deleted file mode 100644 index 5a2dedf8..00000000 --- a/HappyAnding/HappyAnding/Views/WriteCurationViews/CheckBoxShortcutCell.swift +++ /dev/null @@ -1,106 +0,0 @@ -// -// CheckBoxShortcutCell.swift -// HappyAnding -// -// Created by HanGyeongjun on 2022/10/26. -// - -import SwiftUI - -struct CheckBoxShortcutCell: View { - - @Binding var selectedShortcutCells: [ShortcutCellModel] - - @State var isShortcutTapped: Bool = false - - let shortcutCell: ShortcutCellModel - - var body: some View { - - ZStack { - Color.shortcutsZipBackground - - HStack { - toggle - icon - shortcutInfo - Spacer() - } - .padding(.vertical, 20) - .background( background ) - .padding(.horizontal, 16) - } - .onTapGesture { - if isShortcutTapped { - isShortcutTapped = false - - // TODO: 현재는 name을 기준으로 검색중, id로 검색해서 삭제해야함 / Shortcuts 자체를 배열에 저장해야함 - - if let index = selectedShortcutCells.firstIndex(of: shortcutCell) { - selectedShortcutCells.remove(at: index) - } - } - else { - if selectedShortcutCells.count < 10 { - isShortcutTapped = true - selectedShortcutCells.append(shortcutCell) - } - } - } - .padding(.top, 0) - .background(Color.shortcutsZipBackground) - } - - var toggle: some View { - Image(systemName: isShortcutTapped ? "checkmark.square.fill" : "square") - .smallIcon() - .foregroundColor(isShortcutTapped ? .shortcutsZipPrimary : .gray3) - .padding(.leading, 20) - } - - var icon: some View { - - ZStack(alignment: .center) { - Rectangle() - .fill(Color.fetchGradient(color: shortcutCell.color)) - .cornerRadius(8) - .frame(width: 52, height: 52) - - Image(systemName: shortcutCell.sfSymbol) - .mediumShortcutIcon() - .foregroundColor(.white) - } - .padding(.leading, 12) - } - - var shortcutInfo: some View { - - VStack(alignment: .leading, spacing: 4) { - Text(shortcutCell.title) - .shortcutsZipHeadline() - .foregroundColor(.gray5) - .lineLimit(1) - Text(shortcutCell.subtitle) - .shortcutsZipFootnote() - .foregroundColor(.gray3) - .lineLimit(2) - } - .padding(.leading, 12) - .padding(.trailing, 20) - } - - var background: some View { - - RoundedRectangle(cornerRadius: 12) - .fill(isShortcutTapped ? Color.shortcutsZipWhite : Color.backgroudList) - .overlay( - RoundedRectangle(cornerRadius: 12) - .strokeBorder(isShortcutTapped ? Color.shortcutsZipPrimary : Color.backgroudListBorder) - ) - }} - -//struct CheckBoxShortcutCell_Previews: PreviewProvider { -// static var previews: some View { -// CheckBoxShortcutCell(color: "Blue", sfSymbol: "books.vertical.fill", name: "ShortcutsTitle", description: "DescriptionDescription") -// } -//} diff --git a/HappyAnding/HappyAnding/Views/WriteCurationViews/WriteCurationInfoView.swift b/HappyAnding/HappyAnding/Views/WriteCurationViews/WriteCurationInfoView.swift index 52a023ae..f3b71817 100644 --- a/HappyAnding/HappyAnding/Views/WriteCurationViews/WriteCurationInfoView.swift +++ b/HappyAnding/HappyAnding/Views/WriteCurationViews/WriteCurationInfoView.swift @@ -8,21 +8,10 @@ import SwiftUI struct WriteCurationInfoView: View { - - @EnvironmentObject var shortcutsZipViewModel: ShortcutsZipViewModel - @EnvironmentObject var writeCurationNavigation: WriteCurationNavigation - - @FocusState var isDescriptionFieldFocused: Bool - - @State var data: WriteCurationInfoType + @StateObject var viewModel: WriteCurationViewModel @Binding var isWriting: Bool - - @State var isValidTitle = false - @State var isValidDescription = false - - private var isIncomplete: Bool { - !(isValidTitle && isValidDescription) - } + + @FocusState var isDescriptionFieldFocused: Bool var body: some View { VStack(spacing: 24) { @@ -34,8 +23,8 @@ struct WriteCurationInfoView: View { placeholder: TextLiteral.writeCurationInfoViewNamePlaceholder, lengthLimit: 20, isDownloadLinkTextField: false, - content: $data.curation.title, - isValid: $isValidTitle) + content: $viewModel.curation.title, + isValid: $viewModel.isValidTitle) .padding(.top, 12) .onSubmit { isDescriptionFieldFocused = true @@ -49,9 +38,8 @@ struct WriteCurationInfoView: View { lengthLimit: 40, isDownloadLinkTextField: false, inputHeight: 72, - content: Binding(get: {data.curation.subtitle}, - set: {data.curation.subtitle = $0}), - isValid: $isValidDescription) + content: $viewModel.curation.subtitle, + isValid: $viewModel.isValidDescription) .focused($isDescriptionFieldFocused) Spacer() @@ -60,23 +48,19 @@ struct WriteCurationInfoView: View { .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button { - - shortcutsZipViewModel.addCuration(curation: data.curation, isEdit: data.isEdit, deletedShortcutCells: data.deletedShortcutCells) - + isDescriptionFieldFocused = false + viewModel.addCuration() self.isWriting.toggle() - if #available(iOS 16.1, *) { - writeCurationNavigation.navigationPath = .init() - } } label: { Text(TextLiteral.upload) .shortcutsZipHeadline() - .foregroundColor(isIncomplete ? .shortcutsZipPrimary.opacity(0.3) : .shortcutsZipPrimary) + .foregroundColor(viewModel.isIncomplete ? .shortcutsZipPrimary.opacity(0.3) : .shortcutsZipPrimary) } - .disabled(isIncomplete) + .disabled(viewModel.isIncomplete) } } .background(Color.shortcutsZipBackground) - .navigationBarTitle(data.isEdit ? TextLiteral.writeCurationInfoViewEdit : TextLiteral.wrietCurationInfoViewPost) + .navigationBarTitle(viewModel.isEdit ? TextLiteral.writeCurationInfoViewEdit : TextLiteral.wrietCurationInfoViewPost) .onAppear(perform : UIApplication.shared.hideKeyboard) } } diff --git a/HappyAnding/HappyAnding/Views/WriteCurationViews/WriteCurationSetView.swift b/HappyAnding/HappyAnding/Views/WriteCurationViews/WriteCurationSetView.swift index 54624019..abe30425 100644 --- a/HappyAnding/HappyAnding/Views/WriteCurationViews/WriteCurationSetView.swift +++ b/HappyAnding/HappyAnding/Views/WriteCurationViews/WriteCurationSetView.swift @@ -9,24 +9,8 @@ import SwiftUI struct WriteCurationSetView: View { - @EnvironmentObject var shortcutsZipViewModel: ShortcutsZipViewModel - @EnvironmentObject var writeCurationNavigation: WriteCurationNavigation - @Binding var isWriting: Bool - - @State var shortcutCells = [ShortcutCellModel]() - @State var isSelected = false - @State var curation = Curation(title: "", - subtitle: "", - isAdmin: false, - background: "White", - author: "", - shortcuts: [ShortcutCellModel]()) - @State var isTappedQuestionMark: Bool = false - @State var deletedShortcutCells = [ShortcutCellModel]() - - let isEdit: Bool - + @StateObject var viewModel: WriteCurationViewModel var body: some View { VStack { ProgressView(value: 1, total: 2) @@ -34,7 +18,7 @@ struct WriteCurationSetView: View { listHeader infomation - if shortcutCells.isEmpty { + if viewModel.shortcutCells.isEmpty { Spacer() Text(TextLiteral.writeCurationSetViewNoShortcuts) .shortcutsZipBody2() @@ -47,13 +31,10 @@ struct WriteCurationSetView: View { } } .background(Color.shortcutsZipBackground) - .navigationTitle(isEdit ? TextLiteral.writeCurationSetViewEdit : TextLiteral.writeCurationSetViewPost) + .navigationTitle(viewModel.isEdit ? TextLiteral.writeCurationSetViewEdit : TextLiteral.writeCurationSetViewPost) .navigationBarTitleDisplayMode(.inline) .onAppear { - self.shortcutCells = shortcutsZipViewModel.fetchShortcutMakeCuration().sorted { $0.title < $1.title } - if isEdit { - deletedShortcutCells = curation.shortcuts - } + viewModel.fetchMakeCuration() } .toolbar { ToolbarItem(placement: .navigationBarLeading) { @@ -65,13 +46,13 @@ struct WriteCurationSetView: View { .foregroundColor(.gray4) } } - + ToolbarItem(placement: .navigationBarTrailing) { Text(TextLiteral.next) - .navigationLinkRouter(data: WriteCurationInfoType(curation: curation, deletedShortcutCells: deletedShortcutCells, isEdit: isEdit), isPresented: $isWriting) + .navigationLinkRouter(data: viewModel, isPresented: $isWriting) .shortcutsZipHeadline() - .foregroundColor(curation.shortcuts.isEmpty ? .shortcutsZipPrimary.opacity(0.3) : .shortcutsZipPrimary) - .disabled(curation.shortcuts.isEmpty) + .foregroundColor(viewModel.curation.shortcuts.isEmpty ? .shortcutsZipPrimary.opacity(0.3) : .shortcutsZipPrimary) + .disabled(viewModel.curation.shortcuts.isEmpty) } } } @@ -86,7 +67,7 @@ struct WriteCurationSetView: View { .shortcutsZipFootnote() .foregroundColor(.gray3) Spacer() - Text("\(curation.shortcuts.count)개") + Text("\(viewModel.curation.shortcuts.count)개") .shortcutsZipBody2() .foregroundColor(.shortcutsZipPrimary) } @@ -97,11 +78,8 @@ struct WriteCurationSetView: View { var shortcutList: some View { ScrollView { - ForEach(Array(shortcutCells)) { shortcut in - CheckBoxShortcutCell( - selectedShortcutCells: $curation.shortcuts, isShortcutTapped: curation.shortcuts.contains(shortcut), - shortcutCell: shortcut - ) + ForEach(Array(viewModel.shortcutCells.enumerated()), id: \.offset) { index, shortcut in + checkBoxShortcutCell(viewModel: viewModel, index: index) } } .frame(maxWidth: .infinity) @@ -122,10 +100,61 @@ struct WriteCurationSetView: View { .padding(.horizontal, 16) .padding(.bottom, 20) } -} - -struct WriteCurationSetView_Previews: PreviewProvider { - static var previews: some View { - WriteCurationSetView(isWriting: .constant(false), isEdit: false) + + @ViewBuilder + private func checkBoxShortcutCell(viewModel: WriteCurationViewModel, index: Int) -> some View { + + ZStack { + Color.shortcutsZipBackground + + HStack { + Image(systemName: viewModel.isShortcutsTapped[index] ? "checkmark.square.fill" : "square") + .smallIcon() + .foregroundColor(viewModel.isShortcutsTapped[index] ? .shortcutsZipPrimary : .gray3) + .padding(.leading, 20) + + ZStack(alignment: .center) { + Rectangle() + .fill(Color.fetchGradient(color: viewModel.shortcutCells[index].color)) + .cornerRadius(8) + .frame(width: 52, height: 52) + + Image(systemName: viewModel.shortcutCells[index].sfSymbol) + .mediumShortcutIcon() + .foregroundColor(.white) + } + .padding(.leading, 12) + + VStack(alignment: .leading, spacing: 4) { + Text(viewModel.shortcutCells[index].title) + .shortcutsZipHeadline() + .foregroundColor(.gray5) + .lineLimit(1) + Text(viewModel.shortcutCells[index].subtitle) + .shortcutsZipFootnote() + .foregroundColor(.gray3) + .lineLimit(2) + } + .padding(.leading, 12) + .padding(.trailing, 20) + + Spacer() + } + .padding(.vertical, 20) + .background( + RoundedRectangle(cornerRadius: 12) + .fill(viewModel.isShortcutsTapped[index] ? Color.shortcutsZipWhite : Color.backgroudList) + .overlay( + RoundedRectangle(cornerRadius: 12) + .strokeBorder(viewModel.isShortcutsTapped[index] ? Color.shortcutsZipPrimary : Color.backgroudListBorder) + ) + ) + .padding(.horizontal, 16) + } + .onTapGesture { + viewModel.checkboxCellTapGesture(index: index) + } + .padding(.top, 0) + .background(Color.shortcutsZipBackground) } }