diff --git a/iOS-NOTTODO/iOS-NOTTODO.xcodeproj/project.pbxproj b/iOS-NOTTODO/iOS-NOTTODO.xcodeproj/project.pbxproj index f8905f3d..52b81443 100644 --- a/iOS-NOTTODO/iOS-NOTTODO.xcodeproj/project.pbxproj +++ b/iOS-NOTTODO/iOS-NOTTODO.xcodeproj/project.pbxproj @@ -30,11 +30,32 @@ 0960C0D42A38BC6500A3D8DB /* KeychainUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0960C0D32A38BC6500A3D8DB /* KeychainUtil.swift */; }; 0960C0D62A38BC8100A3D8DB /* DefaultKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0960C0D52A38BC8100A3D8DB /* DefaultKeys.swift */; }; 0964BA4A2B0F6BFB00A8984B /* AuthInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0964BA492B0F6BFB00A8984B /* AuthInterceptor.swift */; }; + 096980712BA40FCB00D101B9 /* MyPageAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096980702BA40FCB00D101B9 /* MyPageAccountModel.swift */; }; + 0969809C2BA4198500D101B9 /* MyPageAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C16015B29C56DBA005AE3F5 /* MyPageAccountViewController.swift */; }; + 096980A32BA41A6000D101B9 /* MyPageAccountViewModelImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096C88F12BA3F7A800FE01D4 /* MyPageAccountViewModelImpl.swift */; }; + 096980AA2BA41AB000D101B9 /* MyPageViewModelImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096980A92BA41AB000D101B9 /* MyPageViewModelImpl.swift */; }; + 096980AC2BA41AC100D101B9 /* MyPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096980AB2BA41AC100D101B9 /* MyPageViewModel.swift */; }; + 096980AE2BA41ACC00D101B9 /* MyPageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096980AD2BA41ACC00D101B9 /* MyPageModel.swift */; }; + 096980B22BA41AF600D101B9 /* MyPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096980B12BA41AF600D101B9 /* MyPageViewController.swift */; }; + 096980B42BA41B1200D101B9 /* InfoCollecitonViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096980B32BA41B1200D101B9 /* InfoCollecitonViewCell.swift */; }; + 096980B62BA41B2000D101B9 /* MyPageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096980B52BA41B2000D101B9 /* MyPageHeaderView.swift */; }; + 096980B82BA41B2D00D101B9 /* MyProfileCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096980B72BA41B2D00D101B9 /* MyProfileCollectionViewCell.swift */; }; + 096C88EB2BA3E5E500FE01D4 /* MyPageAccountCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096C88EA2BA3E5E500FE01D4 /* MyPageAccountCollectionViewCell.swift */; }; + 096C88ED2BA3E6B700FE01D4 /* MyInfoAccountSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096C88EC2BA3E6B700FE01D4 /* MyInfoAccountSection.swift */; }; + 096C88EF2BA3F71E00FE01D4 /* MyInfoAccountViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096C88EE2BA3F71E00FE01D4 /* MyInfoAccountViewModel.swift */; }; + 096C88F42BA3F89D00FE01D4 /* MyPageManger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096C88F32BA3F89D00FE01D4 /* MyPageManger.swift */; }; + 096C88F62BA3F8A600FE01D4 /* MyPageManagerImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096C88F52BA3F8A600FE01D4 /* MyPageManagerImpl.swift */; }; 097568362A2FEF630001EC46 /* String+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097568352A2FEF3F0001EC46 /* String+.swift */; }; 097C003629AB8270008CAEF3 /* MissionListCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097C003529AB8270008CAEF3 /* MissionListCollectionViewCell.swift */; }; 0982DE5429ADCCE000D933D2 /* HomeEmptyCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0982DE5329ADCCE000D933D2 /* HomeEmptyCollectionViewCell.swift */; }; 0982DE5829AE40FB00D933D2 /* UITabBar+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0982DE5729AE40FB00D933D2 /* UITabBar+.swift */; }; 0982DE5A29AE5E6000D933D2 /* CompositionalLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0982DE5929AE5E6000D933D2 /* CompositionalLayout.swift */; }; + 0982E25F2BAB2B0F0002B060 /* API_KEY.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0982E25E2BAB2B0F0002B060 /* API_KEY.plist */; }; + 0982E2612BAB2FC90002B060 /* NottodoNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0982E2602BAB2FC90002B060 /* NottodoNavigationView.swift */; }; + 0982E2632BAB3E080002B060 /* ModalViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0982E2622BAB3E080002B060 /* ModalViewModel.swift */; }; + 0982E2652BAB3E4D0002B060 /* ModalViewModelImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0982E2642BAB3E4D0002B060 /* ModalViewModelImpl.swift */; }; + 0982E2702BAC00BA0002B060 /* UIApplication+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0982E26F2BAC00BA0002B060 /* UIApplication+.swift */; }; + 0982E2722BAC01220002B060 /* NotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0982E2712BAC01220002B060 /* NotificationCenter.swift */; }; 0987C8402B9DD4DC007EE8DE /* MissionAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0987C83F2B9DD242007EE8DE /* MissionAPI.swift */; }; 098904302B81BB3A004AAD3C /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0989042F2B81BB3A004AAD3C /* Coordinator.swift */; }; 098904322B81BB43004AAD3C /* CoordinatorDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098904312B81BB43004AAD3C /* CoordinatorDelegate.swift */; }; @@ -53,10 +74,6 @@ 098904542B81CA47004AAD3C /* UpdateCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098904532B81CA47004AAD3C /* UpdateCoordinator.swift */; }; 098A23A22B833C6700265955 /* AuthCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098A23A12B833C6700265955 /* AuthCoordinator.swift */; }; 098A23A42B833F0300265955 /* AuthCoordinatorImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098A23A32B833F0300265955 /* AuthCoordinatorImpl.swift */; }; - 098BFD5929B7999E008E80F9 /* MyProfileCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098BFD5829B7999E008E80F9 /* MyProfileCollectionViewCell.swift */; }; - 098BFD5B29B79B6A008E80F9 /* MyInfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098BFD5A29B79B6A008E80F9 /* MyInfoModel.swift */; }; - 098BFD5D29B79CE3008E80F9 /* InfoCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098BFD5C29B79CE3008E80F9 /* InfoCollectionViewCell.swift */; }; - 098BFD5F29B7AECF008E80F9 /* MyInfoHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098BFD5E29B7AECF008E80F9 /* MyInfoHeaderView.swift */; }; 099FC98129B3094F005B37E6 /* WeekMonthFSCalendar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099FC98029B3094F005B37E6 /* WeekMonthFSCalendar.swift */; }; 099FC98329B30A2E005B37E6 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099FC98229B30A2E005B37E6 /* Utils.swift */; }; 099FC98929B3233D005B37E6 /* CalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099FC98829B3233D005B37E6 /* CalendarView.swift */; }; @@ -117,7 +134,6 @@ 3B027A9E299C34DA00BEB65C /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B027A9D299C34DA00BEB65C /* HomeViewController.swift */; }; 3B027AA0299C353700BEB65C /* AddMissionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B027A9F299C353700BEB65C /* AddMissionViewController.swift */; }; 3B027AA2299C355800BEB65C /* AchievementViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B027AA1299C355800BEB65C /* AchievementViewController.swift */; }; - 3B027AA4299C357000BEB65C /* MyInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B027AA3299C357000BEB65C /* MyInfoViewController.swift */; }; 3B027AAC299C35E500BEB65C /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3B027AAB299C35E500BEB65C /* Colors.xcassets */; }; 3B03D0D62B0F15AA00302872 /* NotificationDialogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B03D0D52B0F15AA00302872 /* NotificationDialogViewController.swift */; }; 3B03D0D82B0F5EF300302872 /* CGSize+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B03D0D72B0F5EF300302872 /* CGSize+.swift */; }; @@ -169,9 +185,6 @@ 3BEEBE972A4B048A0081C936 /* NottodoToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BEEBE962A4B048A0081C936 /* NottodoToastView.swift */; }; 6C049A312A595C670085E40B /* logo.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 6C049A302A595C670085E40B /* logo.mp4 */; }; 6C16015829C40112005AE3F5 /* AuthButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C16015729C40112005AE3F5 /* AuthButtonView.swift */; }; - 6C16015C29C56DBA005AE3F5 /* MyInfoAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C16015B29C56DBA005AE3F5 /* MyInfoAccountViewController.swift */; }; - 6C16016229C59EFD005AE3F5 /* MyInfoAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C16016129C59EFD005AE3F5 /* MyInfoAccountModel.swift */; }; - 6C16016429C5E37D005AE3F5 /* MyInfoAccountStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C16016329C5E37D005AE3F5 /* MyInfoAccountStackView.swift */; }; 6C44127129A35A1000313C3F /* KakaoSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 6C44127029A35A1000313C3F /* KakaoSDK */; }; 6C44127329A35A1000313C3F /* KakaoSDKAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 6C44127229A35A1000313C3F /* KakaoSDKAuth */; }; 6C44127529A35A1000313C3F /* KakaoSDKCommon in Frameworks */ = {isa = PBXBuildFile; productRef = 6C44127429A35A1000313C3F /* KakaoSDKCommon */; }; @@ -243,11 +256,31 @@ 0960C0D32A38BC6500A3D8DB /* KeychainUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainUtil.swift; sourceTree = ""; }; 0960C0D52A38BC8100A3D8DB /* DefaultKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultKeys.swift; sourceTree = ""; }; 0964BA492B0F6BFB00A8984B /* AuthInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthInterceptor.swift; sourceTree = ""; }; + 096980702BA40FCB00D101B9 /* MyPageAccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageAccountModel.swift; sourceTree = ""; }; + 096980A92BA41AB000D101B9 /* MyPageViewModelImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageViewModelImpl.swift; sourceTree = ""; }; + 096980AB2BA41AC100D101B9 /* MyPageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageViewModel.swift; sourceTree = ""; }; + 096980AD2BA41ACC00D101B9 /* MyPageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageModel.swift; sourceTree = ""; }; + 096980B12BA41AF600D101B9 /* MyPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageViewController.swift; sourceTree = ""; }; + 096980B32BA41B1200D101B9 /* InfoCollecitonViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoCollecitonViewCell.swift; sourceTree = ""; }; + 096980B52BA41B2000D101B9 /* MyPageHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageHeaderView.swift; sourceTree = ""; }; + 096980B72BA41B2D00D101B9 /* MyProfileCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyProfileCollectionViewCell.swift; sourceTree = ""; }; + 096C88EA2BA3E5E500FE01D4 /* MyPageAccountCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageAccountCollectionViewCell.swift; sourceTree = ""; }; + 096C88EC2BA3E6B700FE01D4 /* MyInfoAccountSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoAccountSection.swift; sourceTree = ""; }; + 096C88EE2BA3F71E00FE01D4 /* MyInfoAccountViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoAccountViewModel.swift; sourceTree = ""; }; + 096C88F12BA3F7A800FE01D4 /* MyPageAccountViewModelImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageAccountViewModelImpl.swift; sourceTree = ""; }; + 096C88F32BA3F89D00FE01D4 /* MyPageManger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageManger.swift; sourceTree = ""; }; + 096C88F52BA3F8A600FE01D4 /* MyPageManagerImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageManagerImpl.swift; sourceTree = ""; }; 097568352A2FEF3F0001EC46 /* String+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+.swift"; sourceTree = ""; }; 097C003529AB8270008CAEF3 /* MissionListCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MissionListCollectionViewCell.swift; sourceTree = ""; }; 0982DE5329ADCCE000D933D2 /* HomeEmptyCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeEmptyCollectionViewCell.swift; sourceTree = ""; }; 0982DE5729AE40FB00D933D2 /* UITabBar+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITabBar+.swift"; sourceTree = ""; }; 0982DE5929AE5E6000D933D2 /* CompositionalLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionalLayout.swift; sourceTree = ""; }; + 0982E25E2BAB2B0F0002B060 /* API_KEY.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = API_KEY.plist; path = ../../../../Desktop/API_KEY.plist; sourceTree = ""; }; + 0982E2602BAB2FC90002B060 /* NottodoNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NottodoNavigationView.swift; sourceTree = ""; }; + 0982E2622BAB3E080002B060 /* ModalViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalViewModel.swift; sourceTree = ""; }; + 0982E2642BAB3E4D0002B060 /* ModalViewModelImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalViewModelImpl.swift; sourceTree = ""; }; + 0982E26F2BAC00BA0002B060 /* UIApplication+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+.swift"; sourceTree = ""; }; + 0982E2712BAC01220002B060 /* NotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationCenter.swift; sourceTree = ""; }; 0987C83F2B9DD242007EE8DE /* MissionAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MissionAPI.swift; sourceTree = ""; }; 0989042F2B81BB3A004AAD3C /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; 098904312B81BB43004AAD3C /* CoordinatorDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinatorDelegate.swift; sourceTree = ""; }; @@ -266,10 +299,6 @@ 098904532B81CA47004AAD3C /* UpdateCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateCoordinator.swift; sourceTree = ""; }; 098A23A12B833C6700265955 /* AuthCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthCoordinator.swift; sourceTree = ""; }; 098A23A32B833F0300265955 /* AuthCoordinatorImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthCoordinatorImpl.swift; sourceTree = ""; }; - 098BFD5829B7999E008E80F9 /* MyProfileCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyProfileCollectionViewCell.swift; sourceTree = ""; }; - 098BFD5A29B79B6A008E80F9 /* MyInfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoModel.swift; sourceTree = ""; }; - 098BFD5C29B79CE3008E80F9 /* InfoCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoCollectionViewCell.swift; sourceTree = ""; }; - 098BFD5E29B7AECF008E80F9 /* MyInfoHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoHeaderView.swift; sourceTree = ""; }; 099FC98029B3094F005B37E6 /* WeekMonthFSCalendar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeekMonthFSCalendar.swift; sourceTree = ""; }; 099FC98229B30A2E005B37E6 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; 099FC98829B3233D005B37E6 /* CalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarView.swift; sourceTree = ""; }; @@ -330,7 +359,6 @@ 3B027A9D299C34DA00BEB65C /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; 3B027A9F299C353700BEB65C /* AddMissionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddMissionViewController.swift; sourceTree = ""; }; 3B027AA1299C355800BEB65C /* AchievementViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementViewController.swift; sourceTree = ""; }; - 3B027AA3299C357000BEB65C /* MyInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoViewController.swift; sourceTree = ""; }; 3B027AAB299C35E500BEB65C /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; 3B03D0D52B0F15AA00302872 /* NotificationDialogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationDialogViewController.swift; sourceTree = ""; }; 3B03D0D72B0F5EF300302872 /* CGSize+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGSize+.swift"; sourceTree = ""; }; @@ -381,9 +409,7 @@ 3BEEBE962A4B048A0081C936 /* NottodoToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NottodoToastView.swift; sourceTree = ""; }; 6C049A302A595C670085E40B /* logo.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = logo.mp4; sourceTree = ""; }; 6C16015729C40112005AE3F5 /* AuthButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthButtonView.swift; sourceTree = ""; }; - 6C16015B29C56DBA005AE3F5 /* MyInfoAccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoAccountViewController.swift; sourceTree = ""; }; - 6C16016129C59EFD005AE3F5 /* MyInfoAccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoAccountModel.swift; sourceTree = ""; }; - 6C16016329C5E37D005AE3F5 /* MyInfoAccountStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyInfoAccountStackView.swift; sourceTree = ""; }; + 6C16015B29C56DBA005AE3F5 /* MyPageAccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyPageAccountViewController.swift; sourceTree = ""; }; 6C9628A82A22209E003ADE25 /* LogoOnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoOnboardingViewController.swift; sourceTree = ""; }; 6CA208222A18FE78001C4247 /* RecommendService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendService.swift; sourceTree = ""; }; 6CA208242A18FEEA001C4247 /* RecommendAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendAPI.swift; sourceTree = ""; }; @@ -540,6 +566,98 @@ path = ViewModel; sourceTree = ""; }; + 0969806F2BA40FBF00D101B9 /* Model */ = { + isa = PBXGroup; + children = ( + 096980702BA40FCB00D101B9 /* MyPageAccountModel.swift */, + 096C88EC2BA3E6B700FE01D4 /* MyInfoAccountSection.swift */, + ); + path = Model; + sourceTree = ""; + }; + 096980A42BA41A8A00D101B9 /* MyPage */ = { + isa = PBXGroup; + children = ( + 096980A82BA41AA100D101B9 /* Cells */, + 096980A72BA41A9B00D101B9 /* ViewControllers */, + 096980A62BA41A9700D101B9 /* ViewModel */, + 096980A52BA41A9000D101B9 /* Model */, + ); + path = MyPage; + sourceTree = ""; + }; + 096980A52BA41A9000D101B9 /* Model */ = { + isa = PBXGroup; + children = ( + 096980AD2BA41ACC00D101B9 /* MyPageModel.swift */, + ); + path = Model; + sourceTree = ""; + }; + 096980A62BA41A9700D101B9 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 096980A92BA41AB000D101B9 /* MyPageViewModelImpl.swift */, + 096980AB2BA41AC100D101B9 /* MyPageViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 096980A72BA41A9B00D101B9 /* ViewControllers */ = { + isa = PBXGroup; + children = ( + 096980B12BA41AF600D101B9 /* MyPageViewController.swift */, + ); + path = ViewControllers; + sourceTree = ""; + }; + 096980A82BA41AA100D101B9 /* Cells */ = { + isa = PBXGroup; + children = ( + 096980B32BA41B1200D101B9 /* InfoCollecitonViewCell.swift */, + 096980B72BA41B2D00D101B9 /* MyProfileCollectionViewCell.swift */, + 096980B52BA41B2000D101B9 /* MyPageHeaderView.swift */, + ); + path = Cells; + sourceTree = ""; + }; + 096C88F02BA3F79A00FE01D4 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 096C88EE2BA3F71E00FE01D4 /* MyInfoAccountViewModel.swift */, + 096C88F12BA3F7A800FE01D4 /* MyPageAccountViewModelImpl.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 0982E2662BAB44B20002B060 /* ViewModel */ = { + isa = PBXGroup; + children = ( + 0982E2622BAB3E080002B060 /* ModalViewModel.swift */, + 0982E2642BAB3E4D0002B060 /* ModalViewModelImpl.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + 0982E2672BAB44C20002B060 /* View */ = { + isa = PBXGroup; + children = ( + 3B4E12F52A27C0BE001D1EC1 /* QuitModalView.swift */, + 092C09B62A48596500E9B06B /* DeleteModalView.swift */, + 3B4E12F72A27C12F001D1EC1 /* WithdrawModalView.swift */, + ); + path = View; + sourceTree = ""; + }; + 0982E2682BAB44CB0002B060 /* ViewControllers */ = { + isa = PBXGroup; + children = ( + 3B4E12F12A27B621001D1EC1 /* NottodoModalViewController.swift */, + 09ED941A2B2ABAB7001864EF /* CommonNotificationViewController.swift */, + ); + path = ViewControllers; + sourceTree = ""; + }; 0987C83E2B9DD234007EE8DE /* Mission */ = { isa = PBXGroup; children = ( @@ -619,32 +737,6 @@ path = Factory; sourceTree = ""; }; - 098BFD6029B80137008E80F9 /* Cell */ = { - isa = PBXGroup; - children = ( - 098BFD5E29B7AECF008E80F9 /* MyInfoHeaderView.swift */, - 098BFD5829B7999E008E80F9 /* MyProfileCollectionViewCell.swift */, - 098BFD5C29B79CE3008E80F9 /* InfoCollectionViewCell.swift */, - ); - path = Cell; - sourceTree = ""; - }; - 098BFD6129B80149008E80F9 /* Model */ = { - isa = PBXGroup; - children = ( - 098BFD5A29B79B6A008E80F9 /* MyInfoModel.swift */, - ); - path = Model; - sourceTree = ""; - }; - 098BFD6229B8015B008E80F9 /* Viewcontrollers */ = { - isa = PBXGroup; - children = ( - 3B027AA3299C357000BEB65C /* MyInfoViewController.swift */, - ); - path = Viewcontrollers; - sourceTree = ""; - }; 099CFA1729AB8C260003E5D5 /* ViewControllers */ = { isa = PBXGroup; children = ( @@ -713,6 +805,7 @@ children = ( 09DCB85C2BA0316A00B6BB74 /* ManagerInterface */, 09DCB8612BA031F600B6BB74 /* AchieveManagerImpl.swift */, + 096C88F52BA3F8A600FE01D4 /* MyPageManagerImpl.swift */, ); path = Manager; sourceTree = ""; @@ -721,6 +814,7 @@ isa = PBXGroup; children = ( 09DCB85F2BA031E000B6BB74 /* AchieveManager.swift */, + 096C88F32BA3F89D00FE01D4 /* MyPageManger.swift */, ); path = ManagerInterface; sourceTree = ""; @@ -901,6 +995,8 @@ 0943A9F82A53239200614761 /* Bundle+.swift */, 3B03D0D72B0F5EF300302872 /* CGSize+.swift */, 3B80B5D42B7F304D00697250 /* adjust+.swift */, + 0982E26F2BAC00BA0002B060 /* UIApplication+.swift */, + 0982E2712BAC01220002B060 /* NotificationCenter.swift */, ); path = Extensions; sourceTree = ""; @@ -939,8 +1035,8 @@ 3B027A98299C347400BEB65C /* Auth */, 6CF4707729A7AAD1008D145C /* Common */, 3B027A99299C347A00BEB65C /* Home */, - 3B027A9A299C348200BEB65C /* MyInfo */, - 6C16015929C56CE8005AE3F5 /* MyInfoAccount */, + 096980A42BA41A8A00D101B9 /* MyPage */, + 6C16015929C56CE8005AE3F5 /* MyPageAccount */, 6CF4706B29A737E8008D145C /* Recommend */, 6CD4F8B529AA48D900CCC740 /* RecommendAction */, 3B027AA5299C358300BEB65C /* TabBar */, @@ -966,16 +1062,6 @@ path = Home; sourceTree = ""; }; - 3B027A9A299C348200BEB65C /* MyInfo */ = { - isa = PBXGroup; - children = ( - 098BFD6229B8015B008E80F9 /* Viewcontrollers */, - 098BFD6029B80137008E80F9 /* Cell */, - 098BFD6129B80149008E80F9 /* Model */, - ); - path = MyInfo; - sourceTree = ""; - }; 3B027A9B299C348800BEB65C /* Achievement */ = { isa = PBXGroup; children = ( @@ -1012,7 +1098,7 @@ 3B027AA6299C359900BEB65C /* Resource */ = { isa = PBXGroup; children = ( - 09DC0EED2BAEC1790075AAC9 /* API_KEY.plist */, + 0982E25E2BAB2B0F0002B060 /* API_KEY.plist */, 3B3EF2F72AF35C90001F79BC /* GoogleService-Info.plist */, 3B027A85299C31B600BEB65C /* Info.plist */, 3B027AAA299C35D000BEB65C /* Assets */, @@ -1086,12 +1172,10 @@ 3B4E12F02A27B60B001D1EC1 /* Modal */ = { isa = PBXGroup; children = ( + 0982E2682BAB44CB0002B060 /* ViewControllers */, + 0982E2672BAB44C20002B060 /* View */, 3B9532F22A284CAD006510F8 /* Protocol */, - 3B4E12F12A27B621001D1EC1 /* NottodoModalViewController.swift */, - 09ED941A2B2ABAB7001864EF /* CommonNotificationViewController.swift */, - 3B4E12F52A27C0BE001D1EC1 /* QuitModalView.swift */, - 3B4E12F72A27C12F001D1EC1 /* WithdrawModalView.swift */, - 092C09B62A48596500E9B06B /* DeleteModalView.swift */, + 0982E2662BAB44B20002B060 /* ViewModel */, ); path = Modal; sourceTree = ""; @@ -1228,32 +1312,25 @@ path = Toast; sourceTree = ""; }; - 6C16015929C56CE8005AE3F5 /* MyInfoAccount */ = { + 6C16015929C56CE8005AE3F5 /* MyPageAccount */ = { isa = PBXGroup; children = ( - 6C16016029C59EE0005AE3F5 /* Models */, + 0969806F2BA40FBF00D101B9 /* Model */, + 096C88F02BA3F79A00FE01D4 /* ViewModel */, 6C16015A29C56D90005AE3F5 /* ViewControllers */, ); - path = MyInfoAccount; + path = MyPageAccount; sourceTree = ""; }; 6C16015A29C56D90005AE3F5 /* ViewControllers */ = { isa = PBXGroup; children = ( - 6C16015B29C56DBA005AE3F5 /* MyInfoAccountViewController.swift */, - 6C16016329C5E37D005AE3F5 /* MyInfoAccountStackView.swift */, + 6C16015B29C56DBA005AE3F5 /* MyPageAccountViewController.swift */, + 096C88EA2BA3E5E500FE01D4 /* MyPageAccountCollectionViewCell.swift */, ); path = ViewControllers; sourceTree = ""; }; - 6C16016029C59EE0005AE3F5 /* Models */ = { - isa = PBXGroup; - children = ( - 6C16016129C59EFD005AE3F5 /* MyInfoAccountModel.swift */, - ); - path = Models; - sourceTree = ""; - }; 6C9628AA2A2220E2003ADE25 /* Lottie */ = { isa = PBXGroup; children = ( @@ -1348,6 +1425,7 @@ 09022D4529C44BC300DE6E49 /* MissionCalendarCell.swift */, 0917F2B129C979A400009324 /* StackView */, 0917F2AF29C9798800009324 /* Calendar */, + 0982E2602BAB2FC90002B060 /* NottodoNavigationView.swift */, ); path = Common; sourceTree = ""; @@ -1457,7 +1535,7 @@ 3B710A5C2A62D4AB00E95620 /* Settings.bundle in Resources */, 3B027A84299C31B600BEB65C /* LaunchScreen.storyboard in Resources */, 6CC54C1A2A28C3AE00AAD76D /* value.json in Resources */, - 09DC0EEE2BAEC1790075AAC9 /* API_KEY.plist in Resources */, + 0982E25F2BAB2B0F0002B060 /* API_KEY.plist in Resources */, 3B3EF2F82AF35C90001F79BC /* GoogleService-Info.plist in Resources */, 6C049A312A595C670085E40B /* logo.mp4 in Resources */, 3B027A81299C31B600BEB65C /* Assets.xcassets in Resources */, @@ -1495,6 +1573,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 0969809C2BA4198500D101B9 /* MyPageAccountViewController.swift in Sources */, 3B03D0D62B0F15AA00302872 /* NotificationDialogViewController.swift in Sources */, 09F6719729CC81B500708725 /* DetailAchievementCollectionViewCell.swift in Sources */, 0960C0D62A38BC8100A3D8DB /* DefaultKeys.swift in Sources */, @@ -1504,14 +1583,13 @@ 09A146652A1964B500DDC308 /* AddAnotherDayResponseDTO.swift in Sources */, 3B027A7C299C31B500BEB65C /* AuthViewController.swift in Sources */, 098904382B81BC16004AAD3C /* AppCoordinator.swift in Sources */, - 6C16016229C59EFD005AE3F5 /* MyInfoAccountModel.swift in Sources */, 3B027A92299C33FE00BEB65C /* UIColor+.swift in Sources */, 092C09B52A484DD900E9B06B /* HomeDeleteViewController.swift in Sources */, - 3B027AA4299C357000BEB65C /* MyInfoViewController.swift in Sources */, 0989043B2B81BCFA004AAD3C /* AppCoordinatorImpl.swift in Sources */, 097568362A2FEF630001EC46 /* String+.swift in Sources */, 6CA2B7BB2A222D2300A9E549 /* ValueOnboardingViewController.swift in Sources */, 3B5F8F8129BF90190063A7F8 /* NottodoCollectionViewCell.swift in Sources */, + 0982E2702BAC00BA0002B060 /* UIApplication+.swift in Sources */, 3B027A94299C340600BEB65C /* UIFont+.swift in Sources */, 6CA208292A191185001C4247 /* UIImageView+.swift in Sources */, 0982DE5A29AE5E6000D933D2 /* CompositionalLayout.swift in Sources */, @@ -1520,26 +1598,29 @@ 098904442B81C18A004AAD3C /* HomeCoordinator.swift in Sources */, 095FEE122B9ED15600FF44C0 /* DetailAchieveHeaderView.swift in Sources */, 3B482FA3299EA9CB00BCF424 /* TabBarItem.swift in Sources */, + 0982E2652BAB3E4D0002B060 /* ModalViewModelImpl.swift in Sources */, 3B027AA0299C353700BEB65C /* AddMissionViewController.swift in Sources */, - 098BFD5929B7999E008E80F9 /* MyProfileCollectionViewCell.swift in Sources */, 09DCB84D2BA0146800B6BB74 /* DetailAchievementViewModel.swift in Sources */, 3B5F8F7A29BF8E8D0063A7F8 /* AddMissionProtocol.swift in Sources */, 09DC0F012BAEC27A0075AAC9 /* AuthViewControllerFactory.swift in Sources */, 09DCB8512BA0156400B6BB74 /* DetailAchievementViewModelImpl.swift in Sources */, 09F6718C29CB4AB700708725 /* SubOnboardingCollectionViewCell.swift in Sources */, 0921611D2A57D0920019CC8C /* AmplitudeAnalyticsService.swift in Sources */, + 0982E2612BAB2FC90002B060 /* NottodoNavigationView.swift in Sources */, + 096980712BA40FCB00D101B9 /* MyPageAccountModel.swift in Sources */, 3B027A96299C340C00BEB65C /* UIImage+.swift in Sources */, 0921611F2A57D7BF0019CC8C /* AnalyticsEvent.swift in Sources */, - 098BFD5B29B79B6A008E80F9 /* MyInfoModel.swift in Sources */, 09DCCD1F2A18ED76003DCF8A /* DailyMissionResponseDTO.swift in Sources */, 6CF4707A29A7AAFF008D145C /* PaddingLabel.swift in Sources */, 09DC0EFD2BAEC2710075AAC9 /* UpdateViewControllerFactory.swift in Sources */, 6CA208272A18FFCF001C4247 /* RecommendResponseDTO.swift in Sources */, + 0982E2722BAC01220002B060 /* NotificationCenter.swift in Sources */, 3B4E12F22A27B621001D1EC1 /* NottodoModalViewController.swift in Sources */, 3B027A78299C31B500BEB65C /* AppDelegate.swift in Sources */, 3B50CB212A40E75400F2E761 /* AddMissionResponseDTO.swift in Sources */, 3B37AE2929C8821600AB7587 /* GoalCollectionViewCell.swift in Sources */, 098904322B81BB43004AAD3C /* CoordinatorDelegate.swift in Sources */, + 096980A32BA41A6000D101B9 /* MyPageAccountViewModelImpl.swift in Sources */, 3B14A14329A6FEE400F92897 /* UITextField+.swift in Sources */, 098904482B81C197004AAD3C /* AchieveCoordinator.swift in Sources */, 09022D4629C44BC300DE6E49 /* MissionCalendarCell.swift in Sources */, @@ -1555,6 +1636,7 @@ 092E04B129BD9C86008A5892 /* MissionDetailCollectionViewCell.swift in Sources */, 6CF4706D29A739D9008D145C /* RecommendViewController.swift in Sources */, 3BC1A27429C9AF500088376B /* MissionHistoryCollectionViewCell.swift in Sources */, + 0982E2632BAB3E080002B060 /* ModalViewModel.swift in Sources */, 3B027A9E299C34DA00BEB65C /* HomeViewController.swift in Sources */, 09F6719029CB6AB400708725 /* OnboardingFooterView.swift in Sources */, 3B482FA9299EB95400BCF424 /* UIScreen+.swift in Sources */, @@ -1563,10 +1645,10 @@ 3B892ABB2A2FBD4C00A316BC /* RecommendSituationResponseDTO.swift in Sources */, 3B14A13B29A694C000F92897 /* UITextView+.swift in Sources */, 093DB0372A146BF900ECA5F6 /* MyInfoURL.swift in Sources */, - 098BFD5F29B7AECF008E80F9 /* MyInfoHeaderView.swift in Sources */, 09A8E48E2B9DBEC700C0F48F /* BaseService.swift in Sources */, 3B482FA7299EB8FD00BCF424 /* UIViewController+.swift in Sources */, 0960C0D42A38BC6500A3D8DB /* KeychainUtil.swift in Sources */, + 096980B42BA41B1200D101B9 /* InfoCollecitonViewCell.swift in Sources */, 3B03D0D82B0F5EF300302872 /* CGSize+.swift in Sources */, 098904502B81C21D004AAD3C /* AchieveCoordinatorImpl.swift in Sources */, 6CA208302A1925EE001C4247 /* RecommendActionResponseDTO.swift in Sources */, @@ -1575,6 +1657,8 @@ 3B11740D2A4B574B0033DDF3 /* CALayer+.swift in Sources */, 155E456D2B62B1A1008628E7 /* UpdateCheckViewController.swift in Sources */, 098904302B81BB3A004AAD3C /* Coordinator.swift in Sources */, + 096980B22BA41AF600D101B9 /* MyPageViewController.swift in Sources */, + 096980B82BA41B2D00D101B9 /* MyProfileCollectionViewCell.swift in Sources */, 3B14A13D29A6FBD300F92897 /* UIView+.swift in Sources */, 09F6719529CBFCD200708725 /* GradientView.swift in Sources */, 09DC0F052BAEC2850075AAC9 /* AchieveViewControllerFactory.swift in Sources */, @@ -1588,6 +1672,8 @@ 09582B4829BDA7F600EF3207 /* DetailStackView.swift in Sources */, 09F6718429CADB1100708725 /* OnboardingModel.swift in Sources */, 095FEE132B9ED15600FF44C0 /* StatisticsView.swift in Sources */, + 096C88F42BA3F89D00FE01D4 /* MyPageManger.swift in Sources */, + 096980AC2BA41AC100D101B9 /* MyPageViewModel.swift in Sources */, 09DC0EF32BAEC1F10075AAC9 /* UpdateFlowcontrollerFactory.swift in Sources */, 3B027A7A299C31B500BEB65C /* SceneDelegate.swift in Sources */, 0989043E2B81BD50004AAD3C /* CoordinatorFactory.swift in Sources */, @@ -1601,12 +1687,14 @@ 3BC19A9329CA1CA800C02803 /* UICollectionViewCell+.swift in Sources */, 093DB03D2A15FC7800ECA5F6 /* CalendarReponseDTO.swift in Sources */, 098904462B81C191004AAD3C /* MypageCoordinator.swift in Sources */, + 096C88F62BA3F8A600FE01D4 /* MyPageManagerImpl.swift in Sources */, 093DB03D2A15FC7800ECA5F6 /* CalendarReponseDTO.swift in Sources */, 3B0CBA242A461B1C0004F2DB /* RecentMissionResponseDTO.swift in Sources */, 3B5F8F7F29BF900A0063A7F8 /* DateCollectionViewCell.swift in Sources */, 3BC1A27229C9AF310088376B /* MissionHistoryModels.swift in Sources */, 6CD4F8BA29AA493600CCC740 /* RecommendActionHeaderView.swift in Sources */, 0930DE6229B80550007958DE /* MissionDetailViewController.swift in Sources */, + 096C88ED2BA3E6B700FE01D4 /* MyInfoAccountSection.swift in Sources */, 0989044C2B81C210004AAD3C /* HomecoordinatorImpl.swift in Sources */, 3B14A13F29A6FCB300F92897 /* UIStackView+.swift in Sources */, 6CD4F8BC29AA494300CCC740 /* RecommendActionFooterView.swift in Sources */, @@ -1617,7 +1705,6 @@ 6CF4706A29A71D71008D145C /* Strings.swift in Sources */, 6CF4705F29A69025008D145C /* GeneralResponse.swift in Sources */, 097C003629AB8270008CAEF3 /* MissionListCollectionViewCell.swift in Sources */, - 6C16015C29C56DBA005AE3F5 /* MyInfoAccountViewController.swift in Sources */, 09F6718E29CB612B00708725 /* FifthOnboardingViewController.swift in Sources */, 0987C8402B9DD4DC007EE8DE /* MissionAPI.swift in Sources */, 6CD4F8C229AA5AF200CCC740 /* UIButton+.swift in Sources */, @@ -1626,7 +1713,7 @@ 09DC0EF52BAEC2030075AAC9 /* HomeFlowControllerFactory.swift in Sources */, 6CA208362A1957CA001C4247 /* AuthService.swift in Sources */, 099FC98129B3094F005B37E6 /* WeekMonthFSCalendar.swift in Sources */, - 6C16016429C5E37D005AE3F5 /* MyInfoAccountStackView.swift in Sources */, + 096980AE2BA41ACC00D101B9 /* MyPageModel.swift in Sources */, 3B80B5D52B7F304D00697250 /* adjust+.swift in Sources */, 09F6718229CAD86100708725 /* OnboardingCollectionViewCell.swift in Sources */, 6CF4707029A73A15008D145C /* RecommendActionViewController.swift in Sources */, @@ -1634,6 +1721,7 @@ 0982DE5429ADCCE000D933D2 /* HomeEmptyCollectionViewCell.swift in Sources */, 09DCB86B2BA0600600B6BB74 /* AchievementViewModelImpl.swift in Sources */, 3BEEBE972A4B048A0081C936 /* NottodoToastView.swift in Sources */, + 096C88EF2BA3F71E00FE01D4 /* MyInfoAccountViewModel.swift in Sources */, 3B027AA2299C355800BEB65C /* AchievementViewController.swift in Sources */, 3B5F8F8929BF9EFE0063A7F8 /* AddMissionLabel.swift in Sources */, 09582B4F29BEBAFA00EF3207 /* DetailCalendarViewController.swift in Sources */, @@ -1646,18 +1734,20 @@ 09F6718029CAD76C00708725 /* SecondOnboardingViewController.swift in Sources */, 09DC0EFB2BAEC2340075AAC9 /* TabBarFlowControllerFactory.swift in Sources */, 098A23A42B833F0300265955 /* AuthCoordinatorImpl.swift in Sources */, - 098BFD5D29B79CE3008E80F9 /* InfoCollectionViewCell.swift in Sources */, 3B0CBA222A45FC170004F2DB /* UpdateMissionResponseDTO.swift in Sources */, 092C09B72A48596500E9B06B /* DeleteModalView.swift in Sources */, 155E45692B5FF2EE008628E7 /* FirebaseUtil.swift in Sources */, + 096980B62BA41B2000D101B9 /* MyPageHeaderView.swift in Sources */, 09DCB8562BA0308D00B6BB74 /* APIError.swift in Sources */, 098904402B81BFAF004AAD3C /* ViewControllerFactory.swift in Sources */, 3B9532F42A284CC1006510F8 /* ModalProtocol.swift in Sources */, 09DC0EF72BAEC2140075AAC9 /* MyPageFlowControllerFactory.swift in Sources */, 3B5F8F8329BF90290063A7F8 /* SituationCollectionViewCell.swift in Sources */, + 096980AA2BA41AB000D101B9 /* MyPageViewModelImpl.swift in Sources */, 3B4E12F62A27C0BE001D1EC1 /* QuitModalView.swift in Sources */, 6CA208232A18FE78001C4247 /* RecommendService.swift in Sources */, 6CA208342A1956ED001C4247 /* AuthAPI.swift in Sources */, + 096C88EB2BA3E5E500FE01D4 /* MyPageAccountCollectionViewCell.swift in Sources */, 0921611B2A5727EF0019CC8C /* AnalyticsEventProtocol.swift in Sources */, 09DCB8692BA05F9E00B6BB74 /* AchievementModel.swift in Sources */, 098904542B81CA47004AAD3C /* UpdateCoordinator.swift in Sources */, diff --git a/iOS-NOTTODO/iOS-NOTTODO/Coordinator/Coordinator/MypageCoordinatorImpl.swift b/iOS-NOTTODO/iOS-NOTTODO/Coordinator/Coordinator/MypageCoordinatorImpl.swift index 03bb656d..8dba175a 100644 --- a/iOS-NOTTODO/iOS-NOTTODO/Coordinator/Coordinator/MypageCoordinatorImpl.swift +++ b/iOS-NOTTODO/iOS-NOTTODO/Coordinator/Coordinator/MypageCoordinatorImpl.swift @@ -32,12 +32,12 @@ final class MypageCoordinatorImpl: MypageCoordinator { } func showMyInfoViewController() { - let viewController = viewControllerFactory.makeMyInfoViewController(coordinator: self) + let viewController = viewControllerFactory.makeMyPageViewController(coordinator: self) navigationController.setViewControllers([viewController], animated: true) } func showMyInfoAccountViewController() { - let viewController = viewControllerFactory.makeMyInfoAccountViewController(coordinator: self) + let viewController = viewControllerFactory.makeMyPageAccountViewController(coordinator: self) viewController.hidesBottomBarWhenPushed = true navigationController.pushViewController(viewController, animated: true) } @@ -59,10 +59,11 @@ final class MypageCoordinatorImpl: MypageCoordinator { logoutAlert.addAction(logoutAction) navigationController.present(logoutAlert, animated: true) } - + func connectAuthCoordinator(type: ViewType) { navigationController.dismiss(animated: true) { [weak self] in - self?.finish() + guard let self else { return } + self.finish() switch type { case .quitSurvey: KeychainUtil.removeUserInfo() diff --git a/iOS-NOTTODO/iOS-NOTTODO/Coordinator/Factory/MyPageViewControllerFactory.swift b/iOS-NOTTODO/iOS-NOTTODO/Coordinator/Factory/MyPageViewControllerFactory.swift index 64d8812f..f41f0ebf 100644 --- a/iOS-NOTTODO/iOS-NOTTODO/Coordinator/Factory/MyPageViewControllerFactory.swift +++ b/iOS-NOTTODO/iOS-NOTTODO/Coordinator/Factory/MyPageViewControllerFactory.swift @@ -8,18 +8,45 @@ import Foundation extension ViewControllerFactoryImpl { - func makeMyInfoViewController(coordinator: MypageCoordinator) -> MyInfoViewController { - let viewController = MyInfoViewController(coordinator: coordinator) + + func makeMyPageManager() -> MyPageManger { + let authAPI = DefaultAuthService() + let manager = MyPageManagerImpl(authAPI: authAPI) + return manager + } + + func makeMyPageViewModel(coordinator: MypageCoordinator) -> any MyPageViewModel { + let viewModel = MyPageViewModelImpl(coordinator: coordinator) + return viewModel + } + + func makeMyPageViewController(coordinator: MypageCoordinator) -> MyPageViewController { + let viewModel = self.makeMyPageViewModel(coordinator: coordinator) + let viewController = MyPageViewController(viewModel: viewModel) return viewController } - func makeMyInfoAccountViewController(coordinator: MypageCoordinator) -> MyInfoAccountViewController { - let viewController = MyInfoAccountViewController(coordinator: coordinator) + func makeMyPageAccountViewModel(coordinator: MypageCoordinator) -> any MyPageAccountViewModel { + let manager = self.makeMyPageManager() + let viewModel = MyPageAccountViewModelImpl(coordinator: coordinator, manager: manager) + return viewModel + } + + func makeMyPageAccountViewController(coordinator: MypageCoordinator) -> MyPageAccountViewController { + let viewModel = self.makeMyPageAccountViewModel(coordinator: coordinator) + let viewController = MyPageAccountViewController(viewModel: viewModel) return viewController } + func makeWithdrawViewModel(coordinator: MypageCoordinator) -> any ModalViewModel { + let manager = self.makeMyPageManager() + let viewModel = ModalViewModelImpl(coordinator: coordinator, manager: manager) + return viewModel + } + func makeWithdrawViewController(coordinator: MypageCoordinator) -> NottodoModalViewController { - let viewController = NottodoModalViewController(coordinator: coordinator) + let viewModel = self.makeWithdrawViewModel(coordinator: coordinator) + let viewController = NottodoModalViewController(viewModel: viewModel) return viewController } } diff --git a/iOS-NOTTODO/iOS-NOTTODO/Coordinator/Factory/Protocol/MyPageFlowControllerFactory.swift b/iOS-NOTTODO/iOS-NOTTODO/Coordinator/Factory/Protocol/MyPageFlowControllerFactory.swift index 0ef669c8..a2318f5b 100644 --- a/iOS-NOTTODO/iOS-NOTTODO/Coordinator/Factory/Protocol/MyPageFlowControllerFactory.swift +++ b/iOS-NOTTODO/iOS-NOTTODO/Coordinator/Factory/Protocol/MyPageFlowControllerFactory.swift @@ -8,7 +8,7 @@ import Foundation protocol MyPageFlowControllerFactory { - func makeMyInfoViewController(coordinator: MypageCoordinator) -> MyInfoViewController - func makeMyInfoAccountViewController(coordinator: MypageCoordinator) -> MyInfoAccountViewController + func makeMyPageViewController(coordinator: MypageCoordinator) -> MyPageViewController + func makeMyPageAccountViewController(coordinator: MypageCoordinator) -> MyPageAccountViewController func makeWithdrawViewController(coordinator: MypageCoordinator) -> NottodoModalViewController } diff --git a/iOS-NOTTODO/iOS-NOTTODO/Coordinator/Factory/ViewControllerFactory.swift b/iOS-NOTTODO/iOS-NOTTODO/Coordinator/Factory/ViewControllerFactory.swift index b8381e89..a52a643f 100644 --- a/iOS-NOTTODO/iOS-NOTTODO/Coordinator/Factory/ViewControllerFactory.swift +++ b/iOS-NOTTODO/iOS-NOTTODO/Coordinator/Factory/ViewControllerFactory.swift @@ -7,6 +7,6 @@ import UIKit -protocol ViewControllerFactory: UpdateFlowcontrollerFactory, AuthFlowControllerFactory, HomeFlowControllerFactory, MyPageFlowControllerFactory, AchieveFlowControllerFactory, TabBarControllerFactory, AuthFlowControllerFactory {} +protocol ViewControllerFactory: UpdateFlowcontrollerFactory, AuthFlowControllerFactory, HomeFlowControllerFactory, AchieveFlowControllerFactory, TabBarControllerFactory, AuthFlowControllerFactory, MyPageFlowControllerFactory {} final class ViewControllerFactoryImpl: ViewControllerFactory {} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Global/Extensions/NotificationCenter.swift b/iOS-NOTTODO/iOS-NOTTODO/Global/Extensions/NotificationCenter.swift new file mode 100644 index 00000000..cb474fed --- /dev/null +++ b/iOS-NOTTODO/iOS-NOTTODO/Global/Extensions/NotificationCenter.swift @@ -0,0 +1,34 @@ +// +// NotificationCenter.swift +// iOS-NOTTODO +// +// Created by JEONGEUN KIM on 3/21/24. +// + +import UIKit +import Combine + +extension NotificationCenter { + + enum Notification { + case willEnterForeground + case didEnterBackground + } + + var willEnterForeground: AnyPublisher { + publisher(for: UIApplication.willEnterForegroundNotification) + .map { _ in return .willEnterForeground } + .eraseToAnyPublisher() + } + + var didEnterBackground: AnyPublisher { + publisher(for: UIApplication.didEnterBackgroundNotification) + .map { _ in return .didEnterBackground } + .eraseToAnyPublisher() + } + + var applicationState: AnyPublisher { + Publishers.Merge(willEnterForeground, didEnterBackground) + .eraseToAnyPublisher() + } +} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Global/Extensions/UIApplication+.swift b/iOS-NOTTODO/iOS-NOTTODO/Global/Extensions/UIApplication+.swift new file mode 100644 index 00000000..b457fca7 --- /dev/null +++ b/iOS-NOTTODO/iOS-NOTTODO/Global/Extensions/UIApplication+.swift @@ -0,0 +1,29 @@ +// +// UIApplication+.swift +// iOS-NOTTODO +// +// Created by JEONGEUN KIM on 3/21/24. +// + +import UIKit + +extension UIApplication { + private static let notificationSettingsURL: URL? = { + let settingsString: String + if #available(iOS 16, *) { + settingsString = UIApplication.openNotificationSettingsURLString + } else if #available(iOS 15.4, *) { + settingsString = UIApplicationOpenNotificationSettingsURLString + } else { + settingsString = UIApplication.openSettingsURLString + } + return URL(string: settingsString) + }() + + func openAppNotificationSettings() { + guard + let url = UIApplication.notificationSettingsURL, + self.canOpenURL(url) else { return } + return open(url) + } +} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Network/Manager/ManagerInterface/MyPageManger.swift b/iOS-NOTTODO/iOS-NOTTODO/Network/Manager/ManagerInterface/MyPageManger.swift new file mode 100644 index 00000000..29353be0 --- /dev/null +++ b/iOS-NOTTODO/iOS-NOTTODO/Network/Manager/ManagerInterface/MyPageManger.swift @@ -0,0 +1,16 @@ +// +// MyPageManger.swift +// iOS-NOTTODO +// +// Created by JEONGEUN KIM on 3/15/24. +// + +import Foundation +import Combine + +protocol MyPageManger { + func logout() -> AnyPublisher + func withdrawl() -> AnyPublisher + func kakaoLogout() + func kakaoWithdrawal() +} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Network/Manager/MyPageManagerImpl.swift b/iOS-NOTTODO/iOS-NOTTODO/Network/Manager/MyPageManagerImpl.swift new file mode 100644 index 00000000..39de808d --- /dev/null +++ b/iOS-NOTTODO/iOS-NOTTODO/Network/Manager/MyPageManagerImpl.swift @@ -0,0 +1,59 @@ +// +// MyPageManagerImpl.swift +// iOS-NOTTODO +// +// Created by JEONGEUN KIM on 3/15/24. +// + +import Foundation +import Combine + +import KakaoSDKUser + +final class MyPageManagerImpl: MyPageManger { + + private let authAPI: AuthServiceProtocol + private let cancelBag = Set() + + init(authAPI: AuthServiceProtocol) { + self.authAPI = authAPI + } + + func logout() -> AnyPublisher { + authAPI.logout() + .map { [weak self] _ in + self?.kakaoLogout() + } + .eraseToAnyPublisher() + } + + func withdrawl() -> AnyPublisher { + authAPI.withdrawal() + .map { [weak self] _ in + if !KeychainUtil.getBool(DefaultKeys.isAppleLogin) { + self?.kakaoWithdrawal() + } + } + .eraseToAnyPublisher() + } + + func kakaoLogout() { + UserApi.shared.logout {(error) in + if let error = error { + print(error) + } else { + print("logout() success.") + } + } + } + + func kakaoWithdrawal() { + UserApi.shared.unlink {(error) in + if let error = error { + print(error) + } else { + print("unlink() success.") + } + } + } +} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Network/Service/Auth/AuthService.swift b/iOS-NOTTODO/iOS-NOTTODO/Network/Service/Auth/AuthService.swift index e4b22393..ce36c55f 100644 --- a/iOS-NOTTODO/iOS-NOTTODO/Network/Service/Auth/AuthService.swift +++ b/iOS-NOTTODO/iOS-NOTTODO/Network/Service/Auth/AuthService.swift @@ -6,9 +6,27 @@ // import Foundation +import Combine import Moya +protocol AuthServiceProtocol { + func logout() -> AnyPublisher + func withdrawal() -> AnyPublisher +} + +typealias DefaultAuthService = BaseService + +extension DefaultAuthService: AuthServiceProtocol { + func logout() -> AnyPublisher { + return requestWithCombineNoResult(AuthAPI.logout) + } + + func withdrawal() -> AnyPublisher { + return requestWithCombineNoResult(AuthAPI.withdrawal) + } +} + typealias AuthData = GeneralResponse typealias EmptyData = GeneralResponse diff --git a/iOS-NOTTODO/iOS-NOTTODO/Network/Service/BaseService.swift b/iOS-NOTTODO/iOS-NOTTODO/Network/Service/BaseService.swift index c89f452c..21968f2a 100644 --- a/iOS-NOTTODO/iOS-NOTTODO/Network/Service/BaseService.swift +++ b/iOS-NOTTODO/iOS-NOTTODO/Network/Service/BaseService.swift @@ -79,7 +79,7 @@ extension BaseService { }.eraseToAnyPublisher() } - // status codea만 사용하는 경우 + // status code만 사용하는 경우 func requestWithCombineNoResult(_ target: API) -> AnyPublisher { return Future { promise in self.provider.request(target) { response in diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/Achievement/ViewControllers/AchievementViewController.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/Achievement/ViewControllers/AchievementViewController.swift index 648d1ecf..3201fe1a 100644 --- a/iOS-NOTTODO/iOS-NOTTODO/Presentation/Achievement/ViewControllers/AchievementViewController.swift +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/Achievement/ViewControllers/AchievementViewController.swift @@ -42,14 +42,13 @@ final class AchievementViewController: UIViewController { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + // MARK: - Life Cycle override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) viewWillAppearSubject.send(Date()) - AmplitudeAnalyticsService.shared.send(event: AnalyticsEvent.Achieve.viewAccomplish) } override func viewDidLoad() { diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/Achievement/ViewControllers/DetailAchievementViewController.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/Achievement/ViewControllers/DetailAchievementViewController.swift index b20cf2c4..8f60bd44 100644 --- a/iOS-NOTTODO/iOS-NOTTODO/Presentation/Achievement/ViewControllers/DetailAchievementViewController.swift +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/Achievement/ViewControllers/DetailAchievementViewController.swift @@ -43,7 +43,7 @@ final class DetailAchievementViewController: UIViewController { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/Achievement/ViewModel/AchievementViewModelImpl.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/Achievement/ViewModel/AchievementViewModelImpl.swift index 38244c5b..05b8f4a6 100644 --- a/iOS-NOTTODO/iOS-NOTTODO/Presentation/Achievement/ViewModel/AchievementViewModelImpl.swift +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/Achievement/ViewModel/AchievementViewModelImpl.swift @@ -33,6 +33,12 @@ final class AchievementViewModelImpl: AchievementViewModel { } .store(in: &cancelBag) + input.viewWillAppearSubject + .sink { _ in + AmplitudeAnalyticsService.shared.send(event: AnalyticsEvent.Achieve.viewAccomplish) + } + .store(in: &cancelBag) + input.calendarCellTapped .filter { [weak self] date -> Bool in guard let percentage = self?.dataSource.value[date.formattedString()] else { diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/DeleteModalView.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/View/DeleteModalView.swift similarity index 100% rename from iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/DeleteModalView.swift rename to iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/View/DeleteModalView.swift diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/QuitModalView.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/View/QuitModalView.swift similarity index 100% rename from iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/QuitModalView.swift rename to iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/View/QuitModalView.swift diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/WithdrawModalView.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/View/WithdrawModalView.swift similarity index 100% rename from iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/WithdrawModalView.swift rename to iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/View/WithdrawModalView.swift diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/CommonNotificationViewController.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/ViewControllers/CommonNotificationViewController.swift similarity index 100% rename from iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/CommonNotificationViewController.swift rename to iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/ViewControllers/CommonNotificationViewController.swift diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/NottodoModalViewController.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/ViewControllers/NottodoModalViewController.swift similarity index 70% rename from iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/NottodoModalViewController.swift rename to iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/ViewControllers/NottodoModalViewController.swift index 6df688f8..c34dfce6 100644 --- a/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/NottodoModalViewController.swift +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/ViewControllers/NottodoModalViewController.swift @@ -9,6 +9,7 @@ import UIKit import SnapKit import KakaoSDKUser import SafariServices +import Combine enum ViewType { case quit @@ -20,8 +21,12 @@ final class NottodoModalViewController: UIViewController { // MARK: - Properties - private weak var coordinator: MypageCoordinator? - + private let viewWillAppearSubject = PassthroughSubject() + private let modalViewControllerDismiss = PassthroughSubject() + private let safariDismiss = PassthroughSubject() + private let safariPresent = PassthroughSubject() + private var viewModel: any ModalViewModel + private var viewType: ViewType? = .quit { didSet { setUI() @@ -38,23 +43,30 @@ final class NottodoModalViewController: UIViewController { // MARK: - init - init(coordinator: MypageCoordinator) { - self.coordinator = coordinator + init(viewModel: some ModalViewModel) { + self.viewModel = viewModel super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + // MARK: - Life Cycle + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + viewWillAppearSubject.send(()) + } + override func viewDidLoad() { super.viewDidLoad() - AmplitudeAnalyticsService.shared.send(event: AnalyticsEvent.AccountInfo.appearWithdrawalModal) + setUI() setLayout() setDelegate() + setBindings() } override func touchesBegan(_ touches: Set, with event: UIEvent?) { @@ -63,7 +75,7 @@ final class NottodoModalViewController: UIViewController { let location = touch.location(in: self.view) if !modalView.frame.contains(location) { - coordinator?.dismiss() + modalViewControllerDismiss.send(()) } } } @@ -96,6 +108,15 @@ extension NottodoModalViewController { withdrawView.delegate = self safariViewController.delegate = self } + + private func setBindings() { + let input = ModalViewModelInput(viewWillAppearSubject: viewWillAppearSubject, + modalDismiss: modalViewControllerDismiss, + safariDismiss: safariDismiss, + safariPresent: safariPresent) + + _ = viewModel.transform(input: input) + } } extension NottodoModalViewController: ModalDelegate { @@ -103,7 +124,7 @@ extension NottodoModalViewController: ModalDelegate { switch viewType { case .quitSurvey: self.present(safariViewController, animated: true) { - self.withdrawal() + self.safariPresent.send(()) } case .quit: viewType = .quitSurvey @@ -114,34 +135,13 @@ extension NottodoModalViewController: ModalDelegate { } func modalDismiss() { - coordinator?.dismiss() // 탈퇴 alert 취소 - } -} - -extension NottodoModalViewController { - func withdrawal() { - if !KeychainUtil.getBool(DefaultKeys.isAppleLogin) { - kakaoWithdrawal() - } - AuthService.shared.withdrawalAuth { _ in - AmplitudeAnalyticsService.shared.send(event: AnalyticsEvent.AccountInfo.completeWithdrawal) - } - } - - func kakaoWithdrawal() { - UserApi.shared.unlink {(error) in - if let error = error { - print(error) - } else { - print("unlink() success.") - } - } + modalViewControllerDismiss.send(()) } } extension NottodoModalViewController: SFSafariViewControllerDelegate { func safariViewControllerDidFinish(_ controller: SFSafariViewController) { controller.delegate = nil - coordinator?.connectAuthCoordinator(type: .quitSurvey) + safariDismiss.send(.quitSurvey) } } diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/ViewModel/ModalViewModel.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/ViewModel/ModalViewModel.swift new file mode 100644 index 00000000..967acfea --- /dev/null +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/ViewModel/ModalViewModel.swift @@ -0,0 +1,22 @@ +// +// ModalViewModel.swift +// iOS-NOTTODO +// +// Created by JEONGEUN KIM on 3/21/24. +// + +import Foundation +import Combine + +protocol ModalViewModelPresentable {} + +protocol ModalViewModel: ViewModel where Input == ModalViewModelInput, Output == ModalViewModelOutput {} + +struct ModalViewModelInput { + let viewWillAppearSubject: PassthroughSubject + let modalDismiss: PassthroughSubject + let safariDismiss: PassthroughSubject + let safariPresent: PassthroughSubject +} + +struct ModalViewModelOutput {} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/ViewModel/ModalViewModelImpl.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/ViewModel/ModalViewModelImpl.swift new file mode 100644 index 00000000..f7dff969 --- /dev/null +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/Modal/ViewModel/ModalViewModelImpl.swift @@ -0,0 +1,59 @@ +// +// ModalViewModelImpl.swift +// iOS-NOTTODO +// +// Created by JEONGEUN KIM on 3/21/24. +// + +import Foundation +import Combine + +final class ModalViewModelImpl: ModalViewModel { + + private weak var coordinator: MypageCoordinator? + private var manager: MyPageManger + private var cancelBag = Set() + + init(coordinator: MypageCoordinator, manager: MyPageManger) { + self.coordinator = coordinator + self.manager = manager + } + + func transform(input: ModalViewModelInput) -> ModalViewModelOutput { + + input.viewWillAppearSubject + .sink { _ in + AmplitudeAnalyticsService.shared.send(event: AnalyticsEvent.AccountInfo.appearWithdrawalModal) + } + .store(in: &cancelBag) + + input.modalDismiss + .sink { [weak self] _ in + self?.coordinator?.dismiss() + } + .store(in: &cancelBag) + + input.safariDismiss + .sink { [weak self] type in + self?.coordinator?.connectAuthCoordinator(type: type) + } + .store(in: &cancelBag) + + input.safariPresent + .sink { [weak self] _ in + self?.withdrawal() + } + .store(in: &cancelBag) + return Output() + } + + func withdrawal() { + manager.withdrawl() + .sink(receiveCompletion: { event in + print("completion: \(event)") + }, receiveValue: { _ in + AmplitudeAnalyticsService.shared.send(event: AnalyticsEvent.AccountInfo.completeWithdrawal) + }) + .store(in: &cancelBag) + } +} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/NottodoNavigationView.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/NottodoNavigationView.swift new file mode 100644 index 00000000..b85d844a --- /dev/null +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/Common/NottodoNavigationView.swift @@ -0,0 +1,108 @@ +// +// NOTTODONavigationView.swift +// iOS-NOTTODO +// +// Created by JEONGEUN KIM on 3/20/24. +// + +import UIKit + +import SnapKit +import Then +import Combine + +protocol NavigationDelegate: AnyObject { + func popViewController() +} + +final class NottodoNavigationView: UIView { + + // MARK: - Property + + weak var delegate: NavigationDelegate? + var cancelBag = Set() + var buttonTapped = PassthroughSubject() + + // MARK: - UI Components + + private let backButton = UIButton() + private let navigationTitle = UILabel() + private let seperateView = UIView() + + override init(frame: CGRect) { + super.init(frame: .zero) + + setUI() + setLayout() + setBindings() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension NottodoNavigationView { + + private func setUI() { + + seperateView.do { + $0.backgroundColor = .gray2 + } + backButton.do { + $0.setBackgroundImage(.icBack, for: .normal) + $0.addTarget(self, action: #selector(backButtonTapped), for: .touchUpInside) + } + + navigationTitle.do { + $0.font = .Pretendard(.semiBold, size: 18) + $0.textColor = .white + } + } + + private func setLayout() { + self.addSubviews(backButton, navigationTitle, seperateView) + + self.snp.makeConstraints { + $0.height.equalTo(58) + } + + backButton.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalToSuperview().inset(15) + } + + navigationTitle.snp.makeConstraints { + $0.center.equalToSuperview() + } + + seperateView.snp.makeConstraints { + $0.bottom.horizontalEdges.equalToSuperview() + $0.height.equalTo(0.7) + } + } + + private func setBindings() { + backButton.tapPublisher + .sink { [weak self] _ in + self?.buttonTapped.send(()) + } + .store(in: &cancelBag) + } + + func setTitle(_ text: String) { + navigationTitle.text = text + } + + @objc + private func backButtonTapped() { + delegate?.popViewController() + } +} +extension UIButton { + var tapPublisher: AnyPublisher { + controlPublisher(for: .touchUpInside) + .map { _ in } + .eraseToAnyPublisher() + } +} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Model/MyInfoModel.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Model/MyInfoModel.swift deleted file mode 100644 index 68c5f2f7..00000000 --- a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Model/MyInfoModel.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// MyInfoModel.swift -// iOS-NOTTODO -// -// Created by JEONGEUN KIM on 2023/03/08. -// - -import UIKit - -struct InfoModel: Hashable { - - var image: UIImage? - var user: String? - var email: String? - var title: String? - - static var profile: [InfoModel] = [InfoModel(image: .imgUser, - user: UserDefaults.standard.bool(forKey: DefaultKeys.isAppleLogin) ? KeychainUtil.getAppleUsername() : KeychainUtil.getKakaoNickname(), - email: UserDefaults.standard.bool(forKey: DefaultKeys.isAppleLogin) ? KeychainUtil.getAppleEmail() : KeychainUtil.getKakaoEmail())] - - static let support: [InfoModel] = [InfoModel(image: .icGuide, title: I18N.guide), - InfoModel(image: .icQuestion1, title: I18N.oftenQuestion) - ] - static let info: [InfoModel] = [InfoModel(title: I18N.notice), - InfoModel(title: I18N.sendFeedback), - InfoModel(title: I18N.inquiry), - InfoModel(title: I18N.policies) - ] - static func version() -> [InfoModel] { return [InfoModel(title: I18N.version + " " + (Utils.version ?? "1.0.0"))] } -} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfoAccount/Models/MyInfoAccountModel.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfoAccount/Models/MyInfoAccountModel.swift deleted file mode 100644 index 4712da1d..00000000 --- a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfoAccount/Models/MyInfoAccountModel.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// MyInfoAccountModel.swift -// iOS-NOTTODO -// -// Created by 김민서 on 2023/03/18. -// - -import UIKit - -struct MyInfoAccountModel: Hashable { - var nickname: String - var email: String - var account: String - var notification: Bool -} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfoAccount/ViewControllers/MyInfoAccountStackView.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfoAccount/ViewControllers/MyInfoAccountStackView.swift deleted file mode 100644 index 8a8e8022..00000000 --- a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfoAccount/ViewControllers/MyInfoAccountStackView.swift +++ /dev/null @@ -1,174 +0,0 @@ -// -// MyInfoAccountStackView.swift -// iOS-NOTTODO -// -// Created by 김민서 on 2023/03/18. -// - -import UIKit - -import SnapKit -import Then -import UserNotifications - -final class MyInfoAccountStackView: UIView { - - // MARK: - UI Components - - private let stackView = UIView() - let titleLabel = UILabel() - let contentLabel = UILabel() - let notificationSwitch = UISwitch() - private let lineView = UIView() - var switchClosure: ((_ isTapped: Bool) -> Void)? - private var isNotificationAllowed: Bool { - return KeychainUtil.getBool(DefaultKeys.isNotificationAccepted) - } - - // MARK: - View Life Cycle - - init(title: String, isHidden: Bool) { - super.init(frame: .zero) - setUI(title: title, isHidden: isHidden) - setLayout(isHidden: isHidden) - - NotificationCenter.default.addObserver( - self, - selector: #selector(appWillEnterForeground), - name: UIApplication.willEnterForegroundNotification, - object: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - NotificationCenter.default.removeObserver( - self, - name: UIApplication.willEnterForegroundNotification, - object: nil) - } -} - -// MARK: - Methods - -extension MyInfoAccountStackView { - private func setUI(title: String, isHidden: Bool) { - makeCornerRound(radius: 10) - - titleLabel.do { - $0.text = title - $0.font = .Pretendard(.medium, size: 14) - $0.textColor = .white - $0.numberOfLines = 0 - $0.textAlignment = .left - } - - contentLabel.do { - $0.font = .Pretendard(.regular, size: 14) - $0.textColor = .gray6 - $0.numberOfLines = 0 - $0.textAlignment = .right - } - - notificationSwitch.do { - $0.isOn = isNotificationAllowed - $0.onTintColor = .green2 - $0.addTarget(self, action: #selector(switchTapped), for: .valueChanged) - } - - lineView.do { - $0.backgroundColor = .gray2 - $0.isHidden = isHidden ? true : false - } - } - - private func setLayout(isHidden: Bool) { - addSubviews(lineView, stackView) - stackView.addSubviews(titleLabel, isHidden ? notificationSwitch : contentLabel) - - stackView.snp.makeConstraints { - $0.top.equalToSuperview().offset(10) - $0.directionalHorizontalEdges.equalToSuperview().inset(20) - $0.bottom.equalToSuperview().inset(10) - $0.height.equalTo(30) - } - - titleLabel.snp.makeConstraints { - $0.centerY.equalToSuperview() - } - - if !isHidden { - contentLabel.snp.makeConstraints { - $0.centerY.equalToSuperview() - $0.trailing.equalToSuperview() - } - } else { - notificationSwitch.snp.makeConstraints { - $0.centerY.equalToSuperview() - $0.trailing.equalToSuperview() - $0.height.equalTo(30) - $0.width.equalTo(50) - } - } - - lineView.snp.makeConstraints { - $0.directionalHorizontalEdges.equalToSuperview() - $0.bottom.equalToSuperview() - $0.height.equalTo(0.5) - } - } - - @objc func switchTapped(_ sender: Any) { - - DispatchQueue.main.async { - do { - try self.toggleNotificationPermission() - } catch { - print("Error toggling notification permission: \(error)") - } - } - } - - private func toggleNotificationPermission() throws { - do { - try self.openAppSettings() - } catch { - throw error - } - } - - private func openAppSettings() throws { - let settingsUrl: URL - if #available(iOS 16.0, *) { - settingsUrl = URL(string: UIApplication.openNotificationSettingsURLString) ?? URL(string: "")! - } else { - settingsUrl = URL(string: UIApplication.openSettingsURLString) ?? URL(string: "")! - } - - if UIApplication.shared.canOpenURL(settingsUrl) { - UIApplication.shared.open(settingsUrl, completionHandler: { success in - print("iOS 설정 앱 열기: \(success)") - }) - } - } - - @objc - private func appWillEnterForeground() { - UNUserNotificationCenter.current().getNotificationSettings { [weak self] settings in - DispatchQueue.main.async { - KeychainUtil.setBool(settings.authorizationStatus == .authorized, forKey: DefaultKeys.isNotificationAccepted) - if let isNotificationAllowed = self?.isNotificationAllowed { - self?.switchClosure?(isNotificationAllowed) - } - - if KeychainUtil.getBool(DefaultKeys.isNotificationAccepted) { - AmplitudeAnalyticsService.shared.send(event: AnalyticsEvent.AccountInfo.completePushOn) - } else { - AmplitudeAnalyticsService.shared.send(event: AnalyticsEvent.AccountInfo.completePushOff) - } - } - } - } -} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfoAccount/ViewControllers/MyInfoAccountViewController.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfoAccount/ViewControllers/MyInfoAccountViewController.swift deleted file mode 100644 index 6eeb0588..00000000 --- a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfoAccount/ViewControllers/MyInfoAccountViewController.swift +++ /dev/null @@ -1,204 +0,0 @@ -// -// MyInfoAccountViewController.swift -// iOS-NOTTODO -// -// Created by 김민서 on 2023/03/18. -// - -import UIKit - -import KakaoSDKUser - -final class MyInfoAccountViewController: UIViewController { - - // MARK: - Property - - private weak var coordinator: MypageCoordinator? - - // MARK: - UI Components - - private let navigationView = UIView() - private let backButton = UIButton() - private let navigationTitle = UILabel() - private let seperateView = UIView() - - private let verticalStackView = UIStackView() - private let nicknameView = MyInfoAccountStackView(title: I18N.nickname, isHidden: false) - private let emailView = MyInfoAccountStackView(title: I18N.email, isHidden: false) - private let accountView = MyInfoAccountStackView(title: I18N.account, isHidden: false) - private let notificationView = MyInfoAccountStackView(title: I18N.notification, isHidden: true) - - private let logoutView = UIView() - private let logoutButton = UIButton() - private let withdrawButton = UIButton() - - // MARK: - init - - init(coordinator: MypageCoordinator) { - self.coordinator = coordinator - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Life Cycle - - override func viewDidLoad() { - super.viewDidLoad() - AmplitudeAnalyticsService.shared.send(event: AnalyticsEvent.AccountInfo.viewAccountInfo) - setUI() - setLayout() - configure(model: MyInfoAccountModel(nickname: KeychainUtil.getBool(DefaultKeys.isAppleLogin) ? KeychainUtil.getAppleUsername() : KeychainUtil.getKakaoNickname(), email: KeychainUtil.getBool(DefaultKeys.isAppleLogin) ? KeychainUtil.getAppleEmail() : KeychainUtil.getKakaoEmail(), account: KeychainUtil.getBool(DefaultKeys.isAppleLogin) ? "apple" : "kakao", notification: true)) - } -} - -// MARK: - Methods - -private extension MyInfoAccountViewController { - func setUI() { - self.notificationView.switchClosure = { [weak self] result in - self?.notificationView.notificationSwitch.setOn(result, animated: true) - } - - view.backgroundColor = .ntdBlack - seperateView.backgroundColor = .gray2 - - backButton.do { - $0.setBackgroundImage(.icBack, for: .normal) - $0.addTarget(self, action: #selector(popBackbutton), for: .touchUpInside) - } - - navigationTitle.do { - $0.font = .Pretendard(.semiBold, size: 18) - $0.textColor = .white - $0.text = I18N.myInfoAccount - } - - verticalStackView.do { - $0.addArrangedSubviews(nicknameView, emailView, accountView, notificationView) - $0.axis = .vertical - $0.spacing = 0 - $0.distribution = .equalSpacing - $0.makeCornerRound(radius: 10) - $0.backgroundColor = .gray1 - } - - logoutView.do { - $0.makeCornerRound(radius: 10) - $0.backgroundColor = .gray1 - } - - logoutButton.do { - $0.setTitle(I18N.logout, for: .normal) - $0.setTitleColor(.ntdRed, for: .normal) - $0.titleLabel?.font = .Pretendard(.medium, size: 14) - $0.addTarget(self, action: #selector(tappedLogout), for: .touchUpInside) - } - - withdrawButton.do { - $0.setTitle(I18N.withdraw, for: .normal) - $0.setTitleColor(.gray4, for: .normal) - $0.titleLabel?.font = .Pretendard(.regular, size: 12) - $0.setUnderline() - $0.addTarget(self, action: #selector(presentToWithdraw), for: .touchUpInside) - } - } - - func setLayout() { - view.addSubviews(navigationView, seperateView, verticalStackView, logoutView, withdrawButton) - navigationView.addSubviews(backButton, navigationTitle) - logoutView.addSubview(logoutButton) - - navigationView.snp.makeConstraints { - $0.top.equalTo(view.safeAreaLayoutGuide) - $0.directionalHorizontalEdges.equalToSuperview() - $0.height.equalTo(58) - } - - backButton.snp.makeConstraints { - $0.centerY.equalToSuperview() - $0.leading.equalToSuperview().offset(15) - } - - navigationTitle.snp.makeConstraints { - $0.center.equalToSuperview() - } - - seperateView.snp.makeConstraints { - $0.top.equalTo(navigationView.snp.bottom) - $0.directionalHorizontalEdges.equalToSuperview() - $0.height.equalTo(0.7) - } - - verticalStackView.snp.makeConstraints { - $0.top.equalTo(navigationView.snp.bottom).offset(35) - $0.directionalHorizontalEdges.equalToSuperview().inset(22) - } - - logoutView.snp.makeConstraints { - $0.top.equalTo(verticalStackView.snp.bottom).offset(21) - $0.directionalHorizontalEdges.equalToSuperview().inset(22) - $0.height.equalTo(50) - } - - logoutButton.snp.makeConstraints { - $0.leading.equalToSuperview().offset(20) - $0.centerY.equalToSuperview() - } - - withdrawButton.snp.makeConstraints { - $0.centerX.equalToSuperview() - $0.bottom.equalToSuperview().offset(-119) - } - } - - func configure(model: MyInfoAccountModel) { - nicknameView.contentLabel.text = model.nickname - emailView.contentLabel.text = model.email - accountView.contentLabel.text = model.account - } - - @objc - private func presentToWithdraw() { - coordinator?.showWithdrawViewController() - } - @objc - private func tappedLogout() { - AmplitudeAnalyticsService.shared.send(event: AnalyticsEvent.AccountInfo.appearLogoutModal) - coordinator?.showLogoutAlertController { [weak self] in - self?.logout() - } - } - - @objc - private func popBackbutton() { - coordinator?.popViewController() - } -} - -extension MyInfoAccountViewController { - func logout() { - if !KeychainUtil.getBool(DefaultKeys.isAppleLogin) { - kakaoLogout() - } - - AuthService.shared.deleteAuth { [weak self] _ in - guard let self else { return } - self.coordinator?.connectAuthCoordinator(type: .logout ) - - AmplitudeAnalyticsService.shared.send(event: AnalyticsEvent.AccountInfo.completeLogout) - } - } - - func kakaoLogout() { - UserApi.shared.logout {(error) in - if let error = error { - print(error) - } else { - print("logout() success.") - } - } - } -} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Cell/InfoCollectionViewCell.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Cells/InfoCollecitonViewCell.swift similarity index 78% rename from iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Cell/InfoCollectionViewCell.swift rename to iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Cells/InfoCollecitonViewCell.swift index b667cb6d..d925a8a5 100644 --- a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Cell/InfoCollectionViewCell.swift +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Cells/InfoCollecitonViewCell.swift @@ -1,8 +1,8 @@ // -// InfoCollectionViewCell.swift +// InfoCollecitonViewCell.swift // iOS-NOTTODO // -// Created by JEONGEUN KIM on 2023/03/08. +// Created by JEONGEUN KIM on 3/15/24. // import UIKit @@ -64,24 +64,28 @@ extension InfoCollectionViewCell { private func setLayout() { contentView.addSubviews(horizontalStackView, arrowImage) + titleLabel.snp.makeConstraints { + $0.verticalEdges.equalTo(contentView).inset(15) + } + iconImage.snp.makeConstraints { - $0.size.equalTo(CGSize(width: 30, height: 30)) + $0.size.equalTo(30) + $0.centerY.equalToSuperview() } arrowImage.snp.makeConstraints { - $0.size.equalTo(CGSize(width: 24, height: 24)) + $0.size.equalTo(24) $0.centerY.equalToSuperview() $0.trailing.equalToSuperview().inset(13) } + horizontalStackView.snp.makeConstraints { - $0.top.equalToSuperview().offset(17) - $0.bottom.equalToSuperview().inset(17) + $0.top.bottom.equalToSuperview().inset(10) $0.leading.equalToSuperview().offset(20) - $0.centerY.equalToSuperview() } } - func configureWithIcon(with model: InfoModel) { + func configureWithIcon(with model: MyPageRowData) { iconImage.image = model.image titleLabel.text = model.title @@ -91,10 +95,9 @@ extension InfoCollectionViewCell { } } - func configure(with model: InfoModel, isHidden: Bool) { - horizontalStackView.removeArrangedSubview(iconImage) - iconImage.removeFromSuperview() + func configure(with model: MyPageRowData) { + iconImage.isHidden = true titleLabel.text = model.title - arrowImage.isHidden = isHidden + arrowImage.isHidden = model.isArrowHidden } } diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Cell/MyInfoHeaderView.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Cells/MyPageHeaderView.swift similarity index 84% rename from iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Cell/MyInfoHeaderView.swift rename to iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Cells/MyPageHeaderView.swift index aecc8d6d..30035638 100644 --- a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Cell/MyInfoHeaderView.swift +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Cells/MyPageHeaderView.swift @@ -1,8 +1,8 @@ // -// MyInfoHeaderCollectionReusableView.swift +// MyPageHeaderView.swift // iOS-NOTTODO // -// Created by JEONGEUN KIM on 2023/03/08. +// Created by JEONGEUN KIM on 3/15/24. // import UIKit @@ -10,7 +10,7 @@ import UIKit import Then import SnapKit -final class MyInfoHeaderView: UICollectionReusableView { +final class MyPageHeaderView: UICollectionReusableView { // MARK: - Properties @@ -36,7 +36,7 @@ final class MyInfoHeaderView: UICollectionReusableView { // MARK: - Methods -extension MyInfoHeaderView { +extension MyPageHeaderView { private func setUI() { myInfoLabel.do { diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Cell/MyProfileCollectionViewCell.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Cells/MyProfileCollectionViewCell.swift similarity index 94% rename from iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Cell/MyProfileCollectionViewCell.swift rename to iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Cells/MyProfileCollectionViewCell.swift index 2bdefe65..49a80b41 100644 --- a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Cell/MyProfileCollectionViewCell.swift +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Cells/MyProfileCollectionViewCell.swift @@ -1,8 +1,8 @@ // -// MyInfoCollectionViewCell.swift +// MyProfileCollectionViewCell.swift // iOS-NOTTODO // -// Created by JEONGEUN KIM on 2023/03/08. +// Created by JEONGEUN KIM on 3/15/24. // import UIKit @@ -82,7 +82,7 @@ extension MyProfileCollectionViewCell { } } - func configure(model: InfoModel) { + func configure(model: MyPageRowData) { logoImage.image = model.image userLabel.text = model.user emailLabel.text = model.email diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Model/MyPageModel.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Model/MyPageModel.swift new file mode 100644 index 00000000..093e53d6 --- /dev/null +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/Model/MyPageModel.swift @@ -0,0 +1,84 @@ +// +// MyPageModel.swift +// iOS-NOTTODO +// +// Created by JEONGEUN KIM on 3/15/24. +// + +import UIKit + +struct MyPageModel: Hashable { + let sections: [Section] + + enum Section: Int, CaseIterable { + case profile, support, info, version + + var rows: [MyPageRowData] { + switch self { + case .profile: + return MyPageRowData.profile + case .support: + return MyPageRowData.support + case .info: + return MyPageRowData.info + case .version: + return MyPageRowData.version() + } + } + var events: [AnalyticsEvent.MyInfo] { + switch self { + case .profile: + return [.clickMyInfo] + case .support: + return [.clickGuide, .clickFaq] + case .info: + return [.clickNotice, .clickSuggestion, .clickQuestion, .clickTerms] + case .version: + return [] + } + } + + var urls: [MyInfoURL] { + switch self { + case .profile: + return [] + case .support: + return [.guid, .faq] + case .info: + return [.notice, .suggestoin, .question, .service] + case .version: + return [] + } + } + } +} + +struct MyPageRowData: Hashable { + var image: UIImage? + var user: String? + var email: String? + var title: String? + var isArrowHidden: Bool = false + + static var profile: [MyPageRowData] { + let user = UserDefaults.standard.bool(forKey: DefaultKeys.isAppleLogin) ? KeychainUtil.getAppleUsername() : KeychainUtil.getKakaoNickname() + let email = UserDefaults.standard.bool(forKey: DefaultKeys.isAppleLogin) ? KeychainUtil.getAppleEmail() : KeychainUtil.getKakaoEmail() + return [MyPageRowData(image: .imgUser, user: user, email: email)] + } + + static let support: [MyPageRowData] = [ + MyPageRowData(image: .icGuide, title: I18N.guide), + MyPageRowData(image: .icQuestion1, title: I18N.oftenQuestion) + ] + + static let info: [MyPageRowData] = [ + MyPageRowData(title: I18N.notice), + MyPageRowData(title: I18N.sendFeedback), + MyPageRowData(title: I18N.inquiry), + MyPageRowData(title: I18N.policies) + ] + + static func version() -> [MyPageRowData] { + return [MyPageRowData(title: I18N.version + " " + (Utils.version ?? "1.0.0"), isArrowHidden: true)] + } +} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Viewcontrollers/MyInfoViewController.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/ViewControllers/MyPageViewController.swift similarity index 56% rename from iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Viewcontrollers/MyInfoViewController.swift rename to iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/ViewControllers/MyPageViewController.swift index a257c00a..318da1f6 100644 --- a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyInfo/Viewcontrollers/MyInfoViewController.swift +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/ViewControllers/MyPageViewController.swift @@ -1,38 +1,42 @@ // -// MyInfoViewController.swift +// MyPageViewController.swift // iOS-NOTTODO // -// Created by 강윤서 on 2023/02/15. +// Created by JEONGEUN KIM on 3/15/24. // import UIKit +import Combine import Then import SnapKit -final class MyInfoViewController: UIViewController { +final class MyPageViewController: UIViewController { // MARK: - Properties + typealias Sections = MyPageModel.Section typealias CellRegistration = UICollectionView.CellRegistration typealias HeaderRegistration = UICollectionView.SupplementaryRegistration - typealias DataSource = UICollectionViewDiffableDataSource - typealias SnapShot = NSDiffableDataSourceSnapshot + typealias DataSource = UICollectionViewDiffableDataSource + typealias Snapshot = NSDiffableDataSourceSnapshot - enum Sections: Int, CaseIterable { - case profile, support, info, version - } + private let viewWillAppearSubject = PassthroughSubject() + private let myPageCellTapped = PassthroughSubject() + private var cancelBag = Set() private var dataSource: DataSource? + private let viewModel: any MyPageViewModel - private lazy var safeArea = self.view.safeAreaLayoutGuide + // MARK: - UI Components - private weak var coordinator: MypageCoordinator? + private lazy var safeArea = self.view.safeAreaLayoutGuide + private let collectionView = UICollectionView(frame: .zero, collectionViewLayout: .init()) // MARK: - init - init(coordinator: MypageCoordinator) { - self.coordinator = coordinator + init(viewModel: some MyPageViewModel) { + self.viewModel = viewModel super.init(nibName: nil, bundle: nil) } @@ -40,31 +44,32 @@ final class MyInfoViewController: UIViewController { fatalError("init(coder:) has not been implemented") } - // MARK: - UI Components - - private let myInfoCollectionView = UICollectionView(frame: .zero, collectionViewLayout: .init()) - // MARK: - Life Cycle + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + viewWillAppearSubject.send(()) + } + override func viewDidLoad() { super.viewDidLoad() - AmplitudeAnalyticsService.shared.send(event: AnalyticsEvent.MyInfo.viewMyInfo) setUI() setLayout() setupDataSource() - setSnapShot() + setBindings() } } // MARK: - Methods -extension MyInfoViewController { +extension MyPageViewController { private func setUI() { view.backgroundColor = .ntdBlack - myInfoCollectionView.do { + collectionView.do { $0.collectionViewLayout = layout() $0.backgroundColor = .clear $0.bounces = false @@ -75,9 +80,9 @@ extension MyInfoViewController { } private func setLayout() { - view.addSubview(myInfoCollectionView) + view.addSubview(collectionView) - myInfoCollectionView.snp.makeConstraints { + collectionView.snp.makeConstraints { $0.directionalHorizontalEdges.equalTo(safeArea).inset(22) $0.directionalVerticalEdges.equalTo(safeArea) } @@ -85,28 +90,25 @@ extension MyInfoViewController { private func setupDataSource() { - let profileCellRegistration = CellRegistration {cell, _, item in + let profileCellRegistration = CellRegistration {cell, _, item in cell.configure(model: item) } - let infoCellRegistration = CellRegistration {cell, indexPath, item in + let infoCellRegistration = CellRegistration {cell, indexPath, item in guard let section = Sections(rawValue: indexPath.section) else { return } switch section { case .support: cell.configureWithIcon(with: item) - case .info: - cell.configure(with: item, isHidden: false) default: - cell.configure(with: item, isHidden: true) + cell.configure(with: item) } } - let headerRegistration = HeaderRegistration(elementKind: UICollectionView.elementKindSectionHeader) { _, _, _ in } + let headerRegistration = HeaderRegistration(elementKind: UICollectionView.elementKindSectionHeader) { _, _, _ in } - dataSource = DataSource(collectionView: myInfoCollectionView, cellProvider: { collectionView, indexPath, item in - + dataSource = DataSource(collectionView: collectionView, cellProvider: { collectionView, indexPath, item in guard let section = Sections(rawValue: indexPath.section) else { return UICollectionViewCell() } switch section { @@ -114,7 +116,7 @@ extension MyInfoViewController { return collectionView.dequeueConfiguredReusableCell(using: profileCellRegistration, for: indexPath, item: item) - case .support, .info, .version: + case .support, .info, .version: return collectionView.dequeueConfiguredReusableCell(using: infoCellRegistration, for: indexPath, item: item) @@ -126,24 +128,18 @@ extension MyInfoViewController { } } - private func setSnapShot() { - - var snapShot = SnapShot() + private func applySnapshot(data: MyPageModel) { + var snapshot = Snapshot() - defer { - dataSource?.apply(snapShot, animatingDifferences: false) + data.sections.forEach { section in + snapshot.appendSections([section]) + snapshot.appendItems(section.rows, toSection: section) } - snapShot.appendSections(Sections.allCases) - snapShot.appendItems(InfoModel.profile, toSection: .profile) - snapShot.appendItems(InfoModel.support, toSection: .support) - snapShot.appendItems(InfoModel.info, toSection: .info) - snapShot.appendItems(InfoModel.version(), toSection: .version) - + dataSource?.apply(snapshot, animatingDifferences: false) } private func layout() -> UICollectionViewLayout { - let layout = UICollectionViewCompositionalLayout { sectionIndex, env in guard let section = Sections(rawValue: sectionIndex) else { return nil } @@ -159,54 +155,38 @@ extension MyInfoViewController { return CompositionalLayout.setUpSection(layoutEnvironment: env, topContentInset: 18, bottomContentInset: 60) - } - } return layout } + + func setBindings() { + + let input = MyPageViewModelInput(viewWillAppearSubject: viewWillAppearSubject, myPageCellTapped: myPageCellTapped) + + let output = viewModel.transform(input: input) + output.viewWillAppearSubject + .receive(on: RunLoop.main) + .sink { [weak self] in + guard let self else { return } + self.applySnapshot(data: $0) + } + .store(in: &cancelBag) + + output.openSafariController + .sink { [weak self] url in + guard let self else { return } + Utils.myInfoUrl(vc: self, url: url) + } + .store(in: &cancelBag) + } } // MARK: - CollectionViewDelegate -extension MyInfoViewController: UICollectionViewDelegate { +extension MyPageViewController: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - switch indexPath.section { - case 0: - profileSectionSelection() - case 1: - infoSectionSelection(for: indexPath, - events: [.clickGuide, .clickFaq], - urls: [.guid, .faq]) - case 2: - infoSectionSelection(for: indexPath, - events: [.clickNotice, .clickSuggestion, .clickQuestion, .clickTerms], - urls: [.notice, .suggestoin, .question, .service]) - default: - return - } - } - - private func profileSectionSelection() { - sendAnalyticsEvent(.clickMyInfo) { - coordinator?.showMyInfoAccountViewController() - } - } - - private func infoSectionSelection(for indexPath: IndexPath, - events: [AnalyticsEvent.MyInfo], - urls: [MyInfoURL]) { - guard let item = urls.indices.contains(indexPath.item) ? urls[indexPath.item] : nil, - let event = events.indices.contains(indexPath.item) ? events[indexPath.item] : nil else { return } - - sendAnalyticsEvent(event) { - Utils.myInfoUrl(vc: self, url: item.url) - } - } - - private func sendAnalyticsEvent(_ event: AnalyticsEvent.MyInfo, action: () -> Void) { - AmplitudeAnalyticsService.shared.send(event: event) - action() + self.myPageCellTapped.send(indexPath) } } diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/ViewModel/MyPageViewModel.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/ViewModel/MyPageViewModel.swift new file mode 100644 index 00000000..f2b30a4b --- /dev/null +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/ViewModel/MyPageViewModel.swift @@ -0,0 +1,23 @@ +// +// MyPageViewModel.swift +// iOS-NOTTODO +// +// Created by JEONGEUN KIM on 3/15/24. +// + +import Foundation +import Combine + +protocol MyPageViewModelPresentable {} + +protocol MyPageViewModel: ViewModel where Input == MyPageViewModelInput, Output == MyPageViewModelOutput {} + +struct MyPageViewModelInput { + let viewWillAppearSubject: PassthroughSubject + let myPageCellTapped: PassthroughSubject +} + +struct MyPageViewModelOutput { + let viewWillAppearSubject: AnyPublisher + let openSafariController: AnyPublisher +} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/ViewModel/MyPageViewModelImpl.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/ViewModel/MyPageViewModelImpl.swift new file mode 100644 index 00000000..fa3f29ba --- /dev/null +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPage/ViewModel/MyPageViewModelImpl.swift @@ -0,0 +1,67 @@ +// +// MyPageViewModelImpl.swift +// iOS-NOTTODO +// +// Created by JEONGEUN KIM on 3/15/24. +// + +import Foundation +import Combine + +final class MyPageViewModelImpl: MyPageViewModel { + + private weak var coordinator: MypageCoordinator? + private var cancelBag = Set() + + init(coordinator: MypageCoordinator) { + self.coordinator = coordinator + } + + private let openSafariController = PassthroughSubject() + + func transform(input: MyPageViewModelInput) -> MyPageViewModelOutput { + + let viewWillAppearSubject = input.viewWillAppearSubject + .map { _ -> MyPageModel in + AmplitudeAnalyticsService.shared.send(event: AnalyticsEvent.MyInfo.viewMyInfo) + + return MyPageModel(sections: [ + .profile, + .support, + .info, + .version + ]) + } + .eraseToAnyPublisher() + + input.myPageCellTapped + .sink { [weak self] indexPath in + guard let self = self else { return } + guard let section = MyPageModel.Section(rawValue: indexPath.section), + indexPath.item < section.events.count else { return } + guard let coordinator = self.coordinator else { return } + self.sendAnalyticsEvent(section.events[indexPath.item]) + + switch section { + case .profile: + coordinator.showMyInfoAccountViewController() + case .support, .info: + let url = section.urls[indexPath.item] + self.openSafariController.send(url.url) + case .version: + break + } + } + .store(in: &cancelBag) + + return Output(viewWillAppearSubject: viewWillAppearSubject, openSafariController: openSafariController.eraseToAnyPublisher()) + } + + private func sendAnalyticsEvent(_ event: AnalyticsEvent.MyInfo) { + AmplitudeAnalyticsService.shared.send(event: event) + } + + deinit { + cancelBag.forEach { $0.cancel() } + } +} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPageAccount/Model/MyInfoAccountSection.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPageAccount/Model/MyInfoAccountSection.swift new file mode 100644 index 00000000..ffd0df6a --- /dev/null +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPageAccount/Model/MyInfoAccountSection.swift @@ -0,0 +1,12 @@ +// +// MyInfoAccountSection.swift +// iOS-NOTTODO +// +// Created by JEONGEUN KIM on 3/15/24. +// + +import Foundation + +enum MyInfoAccountSections: Int, CaseIterable { + case account, logout +} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPageAccount/Model/MyPageAccountModel.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPageAccount/Model/MyPageAccountModel.swift new file mode 100644 index 00000000..2a10b0f9 --- /dev/null +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPageAccount/Model/MyPageAccountModel.swift @@ -0,0 +1,45 @@ +// +// MyPageAccountModel.swift +// iOS-NOTTODO +// +// Created by JEONGEUN KIM on 3/15/24. +// + +import UIKit + +struct MyPageAccountModel: Equatable { + let profileData: [AccountRowData] + let logout: [AccountRowData] +} + +struct AccountRowData: Hashable { + var uuid = UUID() + var title: String + var content: String? + var titleColor: UIColor = .white + var isSwitch: Bool = false + var isOn: Bool = false + + static func userInfo() -> [AccountRowData] { + return [AccountRowData(title: I18N.nickname, + content: KeychainUtil.getBool(DefaultKeys.isAppleLogin) ? KeychainUtil.getAppleUsername() : KeychainUtil.getKakaoNickname()), + AccountRowData(title: I18N.email, + content: KeychainUtil.getBool(DefaultKeys.isAppleLogin) ? KeychainUtil.getAppleEmail() : KeychainUtil.getKakaoEmail()), + AccountRowData(title: I18N.account, + content: KeychainUtil.getBool(DefaultKeys.isAppleLogin) ? "apple" : "kakao"), + AccountRowData(title: I18N.notification, isSwitch: true)] + } + + static func logout() -> [AccountRowData] { + return [AccountRowData(title: I18N.logout, + titleColor: .ntdRed!)] + } + + func hash(into hasher: inout Hasher) { + hasher.combine(uuid) + } + + static func == (lhs: AccountRowData, rhs: AccountRowData) -> Bool { + return lhs.uuid == rhs.uuid + } +} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPageAccount/ViewControllers/MyPageAccountCollectionViewCell.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPageAccount/ViewControllers/MyPageAccountCollectionViewCell.swift new file mode 100644 index 00000000..4b50a887 --- /dev/null +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPageAccount/ViewControllers/MyPageAccountCollectionViewCell.swift @@ -0,0 +1,173 @@ +// +// MyInfoAccountCollectionViewCell.swift +// iOS-NOTTODO +// +// Created by JEONGEUN KIM on 3/15/24. +// + +import UIKit +import Combine + +import SnapKit +import Then + +final class MyPageAccountCollectionViewCell: UICollectionViewCell { + + // MARK: - Properties + + static let identifier = "MyPageAccountCollectionViewCell" + + // MARK: - Properties + + var switchTapped = PassthroughSubject() + var cancelBag = Set() + + // MARK: - UI Components + + private let titleLabel = UILabel() + private let contentLabel = UILabel() + private let notificationSwitch = UISwitch() + + // MARK: - init + + override init(frame: CGRect) { + super.init(frame: .zero) + + setUI() + setLayout() + setBindings() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: - Methods + +extension MyPageAccountCollectionViewCell { + + private func setUI() { + backgroundColor = .gray1 + + titleLabel.do { + $0.font = .Pretendard(.medium, size: 14) + $0.numberOfLines = 0 + $0.textAlignment = .left + } + + contentLabel.do { + $0.font = .Pretendard(.regular, size: 14) + $0.textColor = .gray6 + $0.numberOfLines = 0 + $0.textAlignment = .right + } + + notificationSwitch.do { + $0.onTintColor = .green2 + } + } + + private func setLayout() { + contentView.addSubviews(titleLabel, contentLabel, notificationSwitch) + + titleLabel.snp.makeConstraints { + $0.leading.equalToSuperview().inset(20) + $0.centerY.equalToSuperview() + } + + contentLabel.snp.makeConstraints { + $0.trailing.equalToSuperview().inset(18) + $0.centerY.equalToSuperview() + } + + notificationSwitch.snp.makeConstraints { + $0.trailing.equalToSuperview().inset(18) + $0.centerY.equalToSuperview() + $0.height.equalTo(30) + $0.width.equalTo(50) + } + } + + func configure(data: AccountRowData) { + titleLabel.textColor = data.titleColor + titleLabel.text = data.title + contentLabel.text = data.content + notificationSwitch.setOn(data.isOn, animated: true) + + notificationSwitch.isHidden = !data.isSwitch + contentLabel.isHidden = data.isSwitch + } + + func setBindings() { + + notificationSwitch.statePublisher + .receive(on: RunLoop.main) + .sink { [weak self] isOn in + guard let self else { return } + self.switchTapped.send(isOn) + } + .store(in: &cancelBag) + } +} + +// 임시 코드 - 윤서 코드 merge 후 삭제 +extension UIControl { + func controlPublisher(for event: UIControl.Event) -> UIControl.EventPublisher { + return UIControl.EventPublisher(control: self, event: event) + } + + // Publisher + struct EventPublisher: Publisher { + typealias Output = UIControl + typealias Failure = Never + + let control: UIControl + let event: UIControl.Event + + func receive(subscriber: S) + where S: Subscriber, Never == S.Failure, UIControl == S.Input { + let subscription = EventSubscription( + control: control, + subscriber: subscriber, + event: event + ) + subscriber.receive(subscription: subscription) + } + } + + // Subscription + fileprivate class EventSubscription: Subscription + where EventSubscriber.Input == UIControl, EventSubscriber.Failure == Never { + let control: UIControl + let event: UIControl.Event + var subscriber: EventSubscriber? + + init(control: UIControl, subscriber: EventSubscriber, event: UIControl.Event) { + self.control = control + self.subscriber = subscriber + self.event = event + control.addTarget(self, action: #selector(eventDidOccur), for: event) + } + + func request(_ demand: Subscribers.Demand) {} + + func cancel() { + subscriber = nil + control.removeTarget(self, action: #selector(eventDidOccur), for: event) + } + + @objc func eventDidOccur() { + _ = subscriber?.receive(control) + } + } +} + +extension UISwitch { + var statePublisher: AnyPublisher { + controlPublisher(for: .valueChanged) + .map { $0 as! UISwitch } + .map { $0.isOn } + .eraseToAnyPublisher() + } +} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPageAccount/ViewControllers/MyPageAccountViewController.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPageAccount/ViewControllers/MyPageAccountViewController.swift new file mode 100644 index 00000000..d2f866cc --- /dev/null +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPageAccount/ViewControllers/MyPageAccountViewController.swift @@ -0,0 +1,195 @@ +// +// MyInfoAccountViewController.swift +// iOS-NOTTODO +// +// Created by 김민서 on 2023/03/18. +// + +import UIKit +import Combine + +final class MyPageAccountViewController: UIViewController { + + // MARK: - Property + + typealias CellRegistration = UICollectionView.CellRegistration + typealias HeaderRegistration = UICollectionView.SupplementaryRegistration + typealias DataSource = UICollectionViewDiffableDataSource + typealias SnapShot = NSDiffableDataSourceSnapshot + + private let viewWillAppearSubject = PassthroughSubject() + private let withdrawalTapped = PassthroughSubject() + private let logoutTapped = PassthroughSubject() + private let backButtonTapped = PassthroughSubject() + private let switchButtonTapped = PassthroughSubject() + private var cancelBag = Set() + + private var dataSource: DataSource? + private var viewModel: any MyPageAccountViewModel + + // MARK: - UI Components + + private lazy var safeArea = self.view.safeAreaLayoutGuide + private let navigationView = NottodoNavigationView() + private let withdrawButton = UIButton() + private let collectionView = UICollectionView(frame: .zero, collectionViewLayout: .init()) + + // MARK: - init + + init(viewModel: some MyPageAccountViewModel) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Life Cycle + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + viewWillAppearSubject.send(()) + } + + override func viewDidLoad() { + super.viewDidLoad() + + setUI() + setLayout() + setupDataSource() + setBindings() + } +} + +// MARK: - Methods + +private extension MyPageAccountViewController { + + func setUI() { + view.backgroundColor = .ntdBlack + + navigationView.do { + $0.setTitle(I18N.myInfoAccount) + $0.buttonTapped.sink { [weak self] _ in + guard let self else { return } + self.backButtonTapped.send(()) + } + .store(in: &navigationView.cancelBag) + } + + collectionView.do { + $0.collectionViewLayout = layout() + $0.backgroundColor = .clear + $0.bounces = false + $0.autoresizingMask = [.flexibleWidth, .flexibleHeight] + $0.showsVerticalScrollIndicator = false + $0.delegate = self + } + + withdrawButton.do { + $0.setTitle(I18N.withdraw, for: .normal) + $0.setTitleColor(.gray4, for: .normal) + $0.titleLabel?.font = .Pretendard(.regular, size: 12) + $0.setUnderline() + $0.addTarget(self, action: #selector(presentToWithdraw), for: .touchUpInside) + } + } + + func setLayout() { + view.addSubviews(navigationView, collectionView, withdrawButton) + + navigationView.snp.makeConstraints { + $0.top.horizontalEdges.equalTo(safeArea) + } + + collectionView.snp.makeConstraints { + $0.top.equalTo(navigationView.snp.bottom).offset(35) + $0.horizontalEdges.equalTo(safeArea).inset(22) + $0.bottom.equalTo(safeArea) + } + + withdrawButton.snp.makeConstraints { + $0.centerX.equalTo(safeArea) + $0.bottom.equalTo(safeArea).inset(119) + } + } + + private func setupDataSource() { + + let cellRegistration = CellRegistration { [weak self] cell, _, item in + cell.configure(data: item) + cell.switchTapped + .receive(on: RunLoop.main) + .sink { isOn in + self?.switchButtonTapped.send(isOn) + } + .store(in: &cell.cancelBag) + } + + dataSource = DataSource(collectionView: collectionView, cellProvider: { collectionView, indexPath, item in + + return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, + for: indexPath, + item: item) + }) + } + + private func setBindings() { + + let input = MyPageAccountViewModelInput(viewWillAppearSubject: viewWillAppearSubject, + withdrawalTapped: withdrawalTapped, + logoutTapped: logoutTapped, + backButtonTapped: backButtonTapped, + switchButtonTapped: switchButtonTapped) + + let output = viewModel.transform(input: input) + + output.viewWillAppearSubject + .receive(on: RunLoop.main) + .sink { [weak self] in + guard let self else { return } + self.setSnapShot(userInfo: $0.profileData, logout: $0.logout) + } + .store(in: &cancelBag) + + output.openNotificationSettings + .receive(on: RunLoop.main) + .sink { + UIApplication.shared.openAppNotificationSettings() + } + .store(in: &cancelBag) + } + + private func setSnapShot(userInfo: [AccountRowData], logout: [AccountRowData]) { + var snapShot = SnapShot() + + snapShot.appendSections(MyInfoAccountSections.allCases) + snapShot.appendItems(userInfo, toSection: .account) + snapShot.appendItems(logout, toSection: .logout) + + dataSource?.apply(snapShot, animatingDifferences: true) + } + + private func layout() -> UICollectionViewLayout { + UICollectionViewCompositionalLayout { _, env in + return CompositionalLayout.setUpSection(layoutEnvironment: env, topContentInset: 18) + } + } +} + +extension MyPageAccountViewController: UICollectionViewDelegate { + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + if indexPath.section == MyInfoAccountSections.logout.rawValue { logoutTapped.send(()) } + } +} + +extension MyPageAccountViewController { + + @objc + private func presentToWithdraw() { + withdrawalTapped.send(()) + } +} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPageAccount/ViewModel/MyInfoAccountViewModel.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPageAccount/ViewModel/MyInfoAccountViewModel.swift new file mode 100644 index 00000000..0398f69a --- /dev/null +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPageAccount/ViewModel/MyInfoAccountViewModel.swift @@ -0,0 +1,26 @@ +// +// MyInfoAccountViewModel.swift +// iOS-NOTTODO +// +// Created by JEONGEUN KIM on 3/15/24. +// + +import Foundation +import Combine + +protocol MyPageAccountViewModelPresentable {} + +protocol MyPageAccountViewModel: ViewModel where Input == MyPageAccountViewModelInput, Output == MyPageAccountViewModelOutput {} + +struct MyPageAccountViewModelInput { + let viewWillAppearSubject: PassthroughSubject + let withdrawalTapped: PassthroughSubject + let logoutTapped: PassthroughSubject + let backButtonTapped: PassthroughSubject + let switchButtonTapped: PassthroughSubject +} + +struct MyPageAccountViewModelOutput { + let viewWillAppearSubject: AnyPublisher + let openNotificationSettings: AnyPublisher +} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPageAccount/ViewModel/MyPageAccountViewModelImpl.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPageAccount/ViewModel/MyPageAccountViewModelImpl.swift new file mode 100644 index 00000000..73bea7b3 --- /dev/null +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/MyPageAccount/ViewModel/MyPageAccountViewModelImpl.swift @@ -0,0 +1,112 @@ +// +// MyInfoAccountViewModelImpl.swift +// iOS-NOTTODO +// +// Created by JEONGEUN KIM on 3/15/24. +// + +import Foundation +import Combine + +import KakaoSDKUser +import UserNotifications + +final class MyPageAccountViewModelImpl: MyPageAccountViewModel { + + private weak var coordinator: MypageCoordinator? + private var manager: MyPageManger + private var cancelBag = Set() + + init(coordinator: MypageCoordinator, manager: MyPageManger) { + self.coordinator = coordinator + self.manager = manager + } + + private let mypageAccountModel = CurrentValueSubject(MyPageAccountModel(profileData: AccountRowData.userInfo(), logout: AccountRowData.logout())) + private let openNotificationSettings = PassthroughSubject() + + func transform(input: MyPageAccountViewModelInput) -> MyPageAccountViewModelOutput { + + let viewWillAppearAndForeground = Publishers.Merge(input.viewWillAppearSubject, NotificationCenter.default.willEnterForeground.map { _ in }) + + viewWillAppearAndForeground + .flatMap { [weak self] _ in + guard let self = self else { + return Empty().eraseToAnyPublisher() + } + return self.getAuthorizationStatus() + .map { isAuthorized -> MyPageAccountModel in + var profileData = AccountRowData.userInfo() + let logoutData = AccountRowData.logout() + profileData[3].isOn = isAuthorized + KeychainUtil.setBool(isAuthorized, forKey: DefaultKeys.isNotificationAccepted) + return MyPageAccountModel(profileData: profileData, logout: logoutData) + } + .eraseToAnyPublisher() + } + .sink(receiveValue: { [weak self] model in + guard let self = self else { return } + self.mypageAccountModel.send(model) + }) + .store(in: &cancelBag) + + input.switchButtonTapped + .sink { [weak self] _ in + guard let self = self else { return } + self.openNotificationSettings.send(()) + } + .store(in: &cancelBag) + + input.backButtonTapped + .sink(receiveValue: { [weak self] _ in + guard let self else { return } + self.coordinator?.popViewController() + }) + .store(in: &cancelBag) + + input.withdrawalTapped + .sink(receiveValue: { [weak self] _ in + guard let self else { return } + self.coordinator?.showWithdrawViewController() + }) + .store(in: &cancelBag) + + input.logoutTapped + .sink(receiveValue: { [weak self] _ in + guard let self else { return } + AmplitudeAnalyticsService.shared.send(event: AnalyticsEvent.AccountInfo.appearLogoutModal) + + self.coordinator?.showLogoutAlertController { [weak self] in + self?.logout() + } + }) + .store(in: &cancelBag) + + return Output(viewWillAppearSubject: mypageAccountModel.eraseToAnyPublisher(), openNotificationSettings: openNotificationSettings.eraseToAnyPublisher()) + } + + private func logout() { + manager.logout() + .sink(receiveCompletion: { event in + print("completion: \(event)") + }, receiveValue: { [weak self] _ in + guard let self else { return } + self.coordinator?.connectAuthCoordinator(type: .logout) + AmplitudeAnalyticsService.shared.send(event: AnalyticsEvent.AccountInfo.completeLogout) + }) + .store(in: &cancelBag) + } + + private func getAuthorizationStatus() -> AnyPublisher { + return Future { promise in + UNUserNotificationCenter.current().getNotificationSettings { settings in + promise(.success(settings.authorizationStatus == .authorized)) + } + } + .eraseToAnyPublisher() + } + + deinit { + cancelBag.forEach { $0.cancel() } + } +} diff --git a/iOS-NOTTODO/iOS-NOTTODO/Presentation/RecommendAction/ViewControllers/RecommendActionViewController.swift b/iOS-NOTTODO/iOS-NOTTODO/Presentation/RecommendAction/ViewControllers/RecommendActionViewController.swift index d73e836d..85996202 100644 --- a/iOS-NOTTODO/iOS-NOTTODO/Presentation/RecommendAction/ViewControllers/RecommendActionViewController.swift +++ b/iOS-NOTTODO/iOS-NOTTODO/Presentation/RecommendAction/ViewControllers/RecommendActionViewController.swift @@ -43,9 +43,7 @@ final class RecommendActionViewController: UIViewController { // MARK: - UI Components - private let navigationView = UIView() - private let backButton = UIButton() - private let navigationTitle = UILabel() + private let navigationView = NottodoNavigationView() private let nextButton = UIButton() private var isTapped: Bool = false { didSet { @@ -111,15 +109,9 @@ private extension RecommendActionViewController { // $0.allowsMultipleSelection = true 생성뷰 이슈 해결 후 주석 해제 } - backButton.do { - $0.setBackgroundImage(.back, for: .normal) - $0.addTarget(self, action: #selector(backButtonDidTapped), for: .touchUpInside) - } - - navigationTitle.do { - $0.font = .Pretendard(.semiBold, size: 18) - $0.textColor = .white - $0.text = I18N.recommendNavTitle + navigationView.do { + $0.delegate = self + $0.setTitle(I18N.recommendNavTitle) } nextButton.do { @@ -135,23 +127,12 @@ private extension RecommendActionViewController { func setLayout() { view.addSubviews(navigationView, recommendActionCollectionView, nextButton) - navigationView.addSubviews(backButton, navigationTitle) navigationView.snp.makeConstraints { $0.top.equalTo(view.safeAreaLayoutGuide) $0.directionalHorizontalEdges.equalToSuperview() - $0.height.equalTo(58) - } - - backButton.snp.makeConstraints { - $0.centerY.equalToSuperview() - $0.leading.equalToSuperview().offset(15) } - - navigationTitle.snp.makeConstraints { - $0.center.equalToSuperview() - } - + recommendActionCollectionView.snp.makeConstraints { $0.top.equalTo(navigationView.snp.bottom) $0.leading.trailing.equalToSuperview() @@ -186,11 +167,6 @@ private extension RecommendActionViewController { recommendActionCollectionView.delegate = self recommendActionCollectionView.dataSource = self } - - @objc - func backButtonDidTapped() { - coordinator?.popViewController() - } } extension RecommendActionViewController: UICollectionViewDelegateFlowLayout { @@ -299,3 +275,9 @@ extension RecommendActionViewController { } } } + +extension RecommendActionViewController: NavigationDelegate { + func popViewController() { + coordinator?.popViewController() + } +}