diff --git a/Enums/OnboardingStage.swift b/Enums/OnboardingStage.swift new file mode 100644 index 00000000..3a2f99ee --- /dev/null +++ b/Enums/OnboardingStage.swift @@ -0,0 +1,15 @@ +// +// OnboardingStage.swift +// onboard-iOS +// +// Created by 윤다예 on 12/2/23. +// + +import Foundation + +enum OnboardingStage: String { + case terms = "TERMS" + case updateTerms = "UPDATE_TERMS" + case nickname = "NICKNAME" + case joinGroup = "JOIN_GROUP" +} diff --git a/onboard-iOS.xcodeproj/project.pbxproj b/onboard-iOS.xcodeproj/project.pbxproj index 08ed6691..a5b1998b 100644 --- a/onboard-iOS.xcodeproj/project.pbxproj +++ b/onboard-iOS.xcodeproj/project.pbxproj @@ -37,14 +37,13 @@ 61342DAC2B1612AA00AE0268 /* NicknameCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61342DAB2B1612AA00AE0268 /* NicknameCoordinator.swift */; }; 61342DAE2B16170E00AE0268 /* OBRequestInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61342DAD2B16170E00AE0268 /* OBRequestInterceptor.swift */; }; 61342DB02B1618EB00AE0268 /* TermsAgreementUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61342DAF2B1618EB00AE0268 /* TermsAgreementUseCase.swift */; }; - 61342DB22B1619BE00AE0268 /* TermsAgreementEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61342DB12B1619BE00AE0268 /* TermsAgreementEntity.swift */; }; - 61342DB42B1619DD00AE0268 /* TermsAgreementDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61342DB32B1619DD00AE0268 /* TermsAgreementDTO.swift */; }; + 61342DB22B1619BE00AE0268 /* TermsEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61342DB12B1619BE00AE0268 /* TermsEntity.swift */; }; + 61342DB42B1619DD00AE0268 /* TermsDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61342DB32B1619DD00AE0268 /* TermsDTO.swift */; }; 61342DB62B161A5900AE0268 /* TermsAgreementRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61342DB52B161A5900AE0268 /* TermsAgreementRepository.swift */; }; 6151A06F2AECFE92001D74AB /* TermsAgreementReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6151A06E2AECFE92001D74AB /* TermsAgreementReactor.swift */; }; 618175F52AC5722500B5E2A0 /* UILabel+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618175F42AC5722500B5E2A0 /* UILabel+Extension.swift */; }; 618175F92AC5961500B5E2A0 /* TermsAgreementModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618175F82AC5961500B5E2A0 /* TermsAgreementModalView.swift */; }; 618176162AD2C21000B5E2A0 /* AuthEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618176152AD2C21000B5E2A0 /* AuthEntity.swift */; }; - 618176182AD2C22A00B5E2A0 /* AuthDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618176172AD2C22A00B5E2A0 /* AuthDTO.swift */; }; 6181761E2AD2C66500B5E2A0 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6181761D2AD2C66500B5E2A0 /* LoginViewController.swift */; }; 618176202AD2C6EE00B5E2A0 /* LoginReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6181761F2AD2C6EE00B5E2A0 /* LoginReactor.swift */; }; 618176232AD2C87C00B5E2A0 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618176222AD2C87C00B5E2A0 /* LoginView.swift */; }; @@ -84,6 +83,13 @@ 61D15BC92B1218F80060D089 /* TermsAgreementCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D15BC82B1218F80060D089 /* TermsAgreementCoordinator.swift */; }; 61D15BCB2B12EE390060D089 /* TermsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D15BCA2B12EE390060D089 /* TermsViewController.swift */; }; 61D15BCD2B1301BB0060D089 /* TermsReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D15BCC2B1301BB0060D089 /* TermsReactor.swift */; }; + 61D95E272B1AE82D007F93D7 /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D95E262B1AE82D007F93D7 /* KeychainService.swift */; }; + 61D95E292B1B062B007F93D7 /* OnboardingEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D95E282B1B062B007F93D7 /* OnboardingEntity.swift */; }; + 61D95E2B2B1B06A5007F93D7 /* AuthDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D95E2A2B1B06A5007F93D7 /* AuthDTO.swift */; }; + 61D95E2D2B1B06BB007F93D7 /* OnboardingDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D95E2C2B1B06BB007F93D7 /* OnboardingDTO.swift */; }; + 61D95E322B1B0B5C007F93D7 /* OnboardingStage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D95E312B1B0B5C007F93D7 /* OnboardingStage.swift */; }; + 61D95E342B1B1417007F93D7 /* GroupSearchCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D95E332B1B1417007F93D7 /* GroupSearchCoordinator.swift */; }; + 61D95E602B1C2B2A007F93D7 /* GoogleLoginUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61D95E5F2B1C2B2A007F93D7 /* GoogleLoginUseCase.swift */; }; AA09C27A2ACBE9BC00F51A96 /* KakaoSDKAuth in Frameworks */ = {isa = PBXBuildFile; productRef = AA09C2792ACBE9BC00F51A96 /* KakaoSDKAuth */; }; AA09C27C2ACBE9BC00F51A96 /* KakaoSDKCommon in Frameworks */ = {isa = PBXBuildFile; productRef = AA09C27B2ACBE9BC00F51A96 /* KakaoSDKCommon */; }; AA09C27E2ACBE9BC00F51A96 /* KakaoSDKUser in Frameworks */ = {isa = PBXBuildFile; productRef = AA09C27D2ACBE9BC00F51A96 /* KakaoSDKUser */; }; @@ -106,7 +112,6 @@ 1C1F51AC2AE1640B0020AA71 /* JoinPopupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinPopupView.swift; sourceTree = ""; }; 1C1F51AE2AE16B600020AA71 /* ImagePopupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePopupView.swift; sourceTree = ""; }; 1C1F51B42AE2B7960020AA71 /* BottomSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheetView.swift; sourceTree = ""; }; - 1C5369432AC69C9600AC9AF0 /* AuthDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthDTO.swift; sourceTree = ""; }; 1CA1BBAE2ACF109A000C5F7E /* SpoqaHanSansNeo-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SpoqaHanSansNeo-Medium.ttf"; sourceTree = ""; }; 1CA1BBAF2ACF109A000C5F7E /* SpoqaHanSansNeo-Light.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SpoqaHanSansNeo-Light.ttf"; sourceTree = ""; }; 1CA1BBB02ACF109B000C5F7E /* SpoqaHanSansNeo-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SpoqaHanSansNeo-Regular.ttf"; sourceTree = ""; }; @@ -135,14 +140,13 @@ 61342DAB2B1612AA00AE0268 /* NicknameCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameCoordinator.swift; sourceTree = ""; }; 61342DAD2B16170E00AE0268 /* OBRequestInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OBRequestInterceptor.swift; sourceTree = ""; }; 61342DAF2B1618EB00AE0268 /* TermsAgreementUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsAgreementUseCase.swift; sourceTree = ""; }; - 61342DB12B1619BE00AE0268 /* TermsAgreementEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsAgreementEntity.swift; sourceTree = ""; }; - 61342DB32B1619DD00AE0268 /* TermsAgreementDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsAgreementDTO.swift; sourceTree = ""; }; + 61342DB12B1619BE00AE0268 /* TermsEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsEntity.swift; sourceTree = ""; }; + 61342DB32B1619DD00AE0268 /* TermsDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsDTO.swift; sourceTree = ""; }; 61342DB52B161A5900AE0268 /* TermsAgreementRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsAgreementRepository.swift; sourceTree = ""; }; 6151A06E2AECFE92001D74AB /* TermsAgreementReactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsAgreementReactor.swift; sourceTree = ""; }; 618175F42AC5722500B5E2A0 /* UILabel+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Extension.swift"; sourceTree = ""; }; 618175F82AC5961500B5E2A0 /* TermsAgreementModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsAgreementModalView.swift; sourceTree = ""; }; 618176152AD2C21000B5E2A0 /* AuthEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthEntity.swift; sourceTree = ""; }; - 618176172AD2C22A00B5E2A0 /* AuthDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthDTO.swift; sourceTree = ""; }; 6181761D2AD2C66500B5E2A0 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 6181761F2AD2C6EE00B5E2A0 /* LoginReactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginReactor.swift; sourceTree = ""; }; 618176222AD2C87C00B5E2A0 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; @@ -179,6 +183,13 @@ 61D15BC82B1218F80060D089 /* TermsAgreementCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsAgreementCoordinator.swift; sourceTree = ""; }; 61D15BCA2B12EE390060D089 /* TermsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsViewController.swift; sourceTree = ""; }; 61D15BCC2B1301BB0060D089 /* TermsReactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsReactor.swift; sourceTree = ""; }; + 61D95E262B1AE82D007F93D7 /* KeychainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainService.swift; sourceTree = ""; }; + 61D95E282B1B062B007F93D7 /* OnboardingEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingEntity.swift; sourceTree = ""; }; + 61D95E2A2B1B06A5007F93D7 /* AuthDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthDTO.swift; sourceTree = ""; }; + 61D95E2C2B1B06BB007F93D7 /* OnboardingDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingDTO.swift; sourceTree = ""; }; + 61D95E312B1B0B5C007F93D7 /* OnboardingStage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStage.swift; sourceTree = ""; }; + 61D95E332B1B1417007F93D7 /* GroupSearchCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupSearchCoordinator.swift; sourceTree = ""; }; + 61D95E5F2B1C2B2A007F93D7 /* GoogleLoginUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleLoginUseCase.swift; sourceTree = ""; }; AA09C27F2ACBEB0100F51A96 /* KakaoLoginUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KakaoLoginUseCase.swift; sourceTree = ""; }; AA09C2812ACBF18C00F51A96 /* KakaoLoginManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KakaoLoginManager.swift; sourceTree = ""; }; AA7035FA2ADB81C700B26537 /* GroupRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupRepository.swift; sourceTree = ""; }; @@ -299,6 +310,7 @@ 611D8CA12AB886B10074A55A /* AppleLoginUseCase.swift */, AA09C27F2ACBEB0100F51A96 /* KakaoLoginUseCase.swift */, 61342DAF2B1618EB00AE0268 /* TermsAgreementUseCase.swift */, + 61D95E5F2B1C2B2A007F93D7 /* GoogleLoginUseCase.swift */, ); path = Login; sourceTree = ""; @@ -375,6 +387,7 @@ 61AC7F922AB6D4A300B0B6B5 = { isa = PBXGroup; children = ( + 61D95E302B1B0B4E007F93D7 /* Enums */, 61AC7F9D2AB6D4A300B0B6B5 /* onboard-iOS */, 61AC7F9C2AB6D4A300B0B6B5 /* Products */, 61D15BC22B1215AC0060D089 /* Recovered References */, @@ -396,7 +409,6 @@ 6181762B2AD3D64F00B5E2A0 /* BaseComponents */, 611D8CA52ABECB910074A55A /* onboard-iOS.entitlements */, 61AC80032AB73A0F00B0B6B5 /* Extensions */, - 61AC7FE82AB72E5100B0B6B5 /* Enum */, 61AC7FDE2AB6E6C800B0B6B5 /* Utils */, 61AC7FB52AB6D69D00B0B6B5 /* Presentation */, 61AC7FB42AB6D68B00B0B6B5 /* Domain */, @@ -499,10 +511,10 @@ isa = PBXGroup; children = ( 61AC7FCD2AB6DDE300B0B6B5 /* TestDTO.swift */, - 618176172AD2C22A00B5E2A0 /* AuthDTO.swift */, - 1C5369432AC69C9600AC9AF0 /* AuthDTO.swift */, AA7036012ADB8A1400B26537 /* GroupDTO.swift */, - 61342DB32B1619DD00AE0268 /* TermsAgreementDTO.swift */, + 61342DB32B1619DD00AE0268 /* TermsDTO.swift */, + 61D95E2A2B1B06A5007F93D7 /* AuthDTO.swift */, + 61D95E2C2B1B06BB007F93D7 /* OnboardingDTO.swift */, ); path = DTO; sourceTree = ""; @@ -513,7 +525,8 @@ 61AC7FD02AB6DFBF00B0B6B5 /* TestEntity.swift */, 618176152AD2C21000B5E2A0 /* AuthEntity.swift */, AA7035FF2ADB85A500B26537 /* GroupListEntity.swift */, - 61342DB12B1619BE00AE0268 /* TermsAgreementEntity.swift */, + 61342DB12B1619BE00AE0268 /* TermsEntity.swift */, + 61D95E282B1B062B007F93D7 /* OnboardingEntity.swift */, ); path = Entity; sourceTree = ""; @@ -523,17 +536,11 @@ children = ( 611D8C9F2AB884C10074A55A /* AppleLoginManager.swift */, AA09C2812ACBF18C00F51A96 /* KakaoLoginManager.swift */, + 61D95E262B1AE82D007F93D7 /* KeychainService.swift */, ); path = Utils; sourceTree = ""; }; - 61AC7FE82AB72E5100B0B6B5 /* Enum */ = { - isa = PBXGroup; - children = ( - ); - path = Enum; - sourceTree = ""; - }; 61AC80032AB73A0F00B0B6B5 /* Extensions */ = { isa = PBXGroup; children = ( @@ -564,10 +571,19 @@ 61D15BC62B1217E70060D089 /* LoginCoordinator.swift */, 61D15BC82B1218F80060D089 /* TermsAgreementCoordinator.swift */, 61342DAB2B1612AA00AE0268 /* NicknameCoordinator.swift */, + 61D95E332B1B1417007F93D7 /* GroupSearchCoordinator.swift */, ); path = Coordinator; sourceTree = ""; }; + 61D95E302B1B0B4E007F93D7 /* Enums */ = { + isa = PBXGroup; + children = ( + 61D95E312B1B0B5C007F93D7 /* OnboardingStage.swift */, + ); + path = Enums; + sourceTree = ""; + }; AA7035F92ADB81B600B26537 /* Group */ = { isa = PBXGroup; children = ( @@ -721,12 +737,15 @@ files = ( 618176352AD5902300B5E2A0 /* TermsAgreementViewController.swift in Sources */, 61342DAC2B1612AA00AE0268 /* NicknameCoordinator.swift in Sources */, - 61342DB42B1619DD00AE0268 /* TermsAgreementDTO.swift in Sources */, + 61D95E342B1B1417007F93D7 /* GroupSearchCoordinator.swift in Sources */, + 61342DB42B1619DD00AE0268 /* TermsDTO.swift in Sources */, 618175F52AC5722500B5E2A0 /* UILabel+Extension.swift in Sources */, 611D8CEE2AC44EEA0074A55A /* UIView+SwiftUI+Extension.swift in Sources */, 611D8CEC2AC44D230074A55A /* TermsView.swift in Sources */, 1CA1BBCA2ADABFF9000C5F7E /* PopupState.swift in Sources */, 61AC7FCE2AB6DDE300B0B6B5 /* TestDTO.swift in Sources */, + 61D95E272B1AE82D007F93D7 /* KeychainService.swift in Sources */, + 61D95E292B1B062B007F93D7 /* OnboardingEntity.swift in Sources */, 61342DAA2B1611FF00AE0268 /* NicknameReactor.swift in Sources */, 61AC7FEA2AB7309400B0B6B5 /* APIEventLogger.swift in Sources */, 1CA1BBCC2ADAC091000C5F7E /* PopupView.swift in Sources */, @@ -737,14 +756,14 @@ 61D15BCD2B1301BB0060D089 /* TermsReactor.swift in Sources */, 61AC7FC42AB6DA0200B0B6B5 /* OBRouter.swift in Sources */, 61AC7FE72AB6EEB700B0B6B5 /* APIConstants.swift in Sources */, - 618176182AD2C22A00B5E2A0 /* AuthDTO.swift in Sources */, 61AC7F9F2AB6D4A300B0B6B5 /* AppDelegate.swift in Sources */, 618176202AD2C6EE00B5E2A0 /* LoginReactor.swift in Sources */, 1CA1BBC42ADABEBD000C5F7E /* TextField.swift in Sources */, - 61342DB22B1619BE00AE0268 /* TermsAgreementEntity.swift in Sources */, + 61342DB22B1619BE00AE0268 /* TermsEntity.swift in Sources */, AACCA91B2AE19A09000A4CDA /* NewGroupButton.swift in Sources */, 61CC3C242AEE876500AE8599 /* GoogleLoginManager.swift in Sources */, 61AC7FBC2AB6D72C00B0B6B5 /* TestView.swift in Sources */, + 61D95E2D2B1B06BB007F93D7 /* OnboardingDTO.swift in Sources */, 61AC7FC42AB6DA0200B0B6B5 /* OBRouter.swift in Sources */, 61AC7FE72AB6EEB700B0B6B5 /* APIConstants.swift in Sources */, 61342DAE2B16170E00AE0268 /* OBRequestInterceptor.swift in Sources */, @@ -759,6 +778,7 @@ AA9105642ADA242800DA2E96 /* GroupSearchReactor.swift in Sources */, 611D8CA22AB886B10074A55A /* AppleLoginUseCase.swift in Sources */, 1CA1BBD82AE0337E000C5F7E /* BaseButton.swift in Sources */, + 61D95E2B2B1B06A5007F93D7 /* AuthDTO.swift in Sources */, 61D15BC72B1217E70060D089 /* LoginCoordinator.swift in Sources */, 1C1F51B52AE2B7960020AA71 /* BottomSheetView.swift in Sources */, 1CA1BBBF2AD439BA000C5F7E /* IconImage.swift in Sources */, @@ -787,11 +807,13 @@ 618176312AD3D89200B5E2A0 /* UITableView+Extension.swift in Sources */, 618176282AD3C7E500B5E2A0 /* TermsAgreementItemView.swift in Sources */, 61342DA62B160D6400AE0268 /* NicknameView.swift in Sources */, + 61D95E322B1B0B5C007F93D7 /* OnboardingStage.swift in Sources */, 61AC7FC92AB6DB1800B0B6B5 /* OBNetworkManager.swift in Sources */, 61AC7FA12AB6D4A300B0B6B5 /* SceneDelegate.swift in Sources */, 6181762D2AD3D66D00B5E2A0 /* BaseTableViewCell.swift in Sources */, 1CA1BBC72ADABFA3000C5F7E /* LinkButtonState.swift in Sources */, 61AC80052AB73A2800B0B6B5 /* Reactive+Extension.swift in Sources */, + 61D95E602B1C2B2A007F93D7 /* GoogleLoginUseCase.swift in Sources */, 611D8CA02AB884C10074A55A /* AppleLoginManager.swift in Sources */, AACCA9192AE193D3000A4CDA /* GroupSearchBar.swift in Sources */, AA7035FE2ADB820300B26537 /* GroupSearchUseCase.swift in Sources */, diff --git a/onboard-iOS/Coordinator/Coordinator.swift b/onboard-iOS/Coordinator/Coordinator.swift index 61226236..ebe1a0ee 100644 --- a/onboard-iOS/Coordinator/Coordinator.swift +++ b/onboard-iOS/Coordinator/Coordinator.swift @@ -21,7 +21,9 @@ final class AppCoordinator: Coordinator { } func start() { - self.showLoginViewController() + DispatchQueue.main.async { + self.showLoginViewController() + } } private func showLoginViewController() { diff --git a/onboard-iOS/Coordinator/GroupSearchCoordinator.swift b/onboard-iOS/Coordinator/GroupSearchCoordinator.swift new file mode 100644 index 00000000..4b42ea5a --- /dev/null +++ b/onboard-iOS/Coordinator/GroupSearchCoordinator.swift @@ -0,0 +1,31 @@ +// +// GroupSearchCoordinator.swift +// onboard-iOS +// +// Created by 윤다예 on 12/2/23. +// + +import UIKit + +final class GroupSearchCoordinator: Coordinator { + + var childCoordinators: [Coordinator] = [] + + private var navigationController: UINavigationController? + + init(navigationController: UINavigationController?) { + self.navigationController = navigationController + } + + func start() { + DispatchQueue.main.async { + let repository = GroupRepositoryImpl() + let useCase = GroupSearchUseCaseImpl(groupRepository: repository) + let reactor = GroupSearchReactor(useCase: useCase) + let viewController = GroupSearchViewController(reactor: reactor) + + + self.navigationController?.pushViewController(viewController, animated: true) + } + } +} diff --git a/onboard-iOS/Coordinator/LoginCoordinator.swift b/onboard-iOS/Coordinator/LoginCoordinator.swift index b6ff6031..dec310f1 100644 --- a/onboard-iOS/Coordinator/LoginCoordinator.swift +++ b/onboard-iOS/Coordinator/LoginCoordinator.swift @@ -9,6 +9,8 @@ import UIKit protocol LoginCoordinatorNavigateDelegate: AnyObject { func showTermsAgreementView() + func showNicknameSetting() + func showGroupSearch() } final class LoginCoordinator: Coordinator { @@ -22,26 +24,37 @@ final class LoginCoordinator: Coordinator { } func start() { - let appleLoginManager = AppleLoginManagerImpl() - let authRepository = AuthRepositoryImpl() - let appleLoginUseCase = AppleLoginUseCaseImpl( - appleLoginManager: appleLoginManager, - authRepository: authRepository - ) - - let kakaoLoginManager = KakaoLoginManagerImpl() - let kakaoLoginUseCase = KakaoLoginUseCaseImpl( - kakaoLoginManager: kakaoLoginManager, - authRepository: authRepository - ) - let reactor = LoginReactor( - appleUseCase: appleLoginUseCase, - kakaoUseCase: kakaoLoginUseCase, - coordinator: self - ) - let viewController = LoginViewController(reactor: reactor) - - self.navigationController?.pushViewController(viewController, animated: true) + DispatchQueue.main.async { + let appleLoginManager = AppleLoginManagerImpl() + let authRepository = AuthRepositoryImpl() + let keychainService = KeychainServiceImpl() + let appleLoginUseCase = AppleLoginUseCaseImpl( + appleLoginManager: appleLoginManager, + authRepository: authRepository, + keychainService: keychainService + ) + + let kakaoLoginManager = KakaoLoginManagerImpl() + let kakaoLoginUseCase = KakaoLoginUseCaseImpl( + kakaoLoginManager: kakaoLoginManager, + authRepository: authRepository + ) + + let googleLoginUseCase = GoogleLoginUseCaseImpl( + authRepository: authRepository, + keychainService: keychainService + ) + + let reactor = LoginReactor( + appleUseCase: appleLoginUseCase, + kakaoUseCase: kakaoLoginUseCase, + googleUseCase: googleLoginUseCase, + coordinator: self + ) + let viewController = LoginViewController(reactor: reactor) + + self.navigationController?.pushViewController(viewController, animated: true) + } } } @@ -53,4 +66,18 @@ extension LoginCoordinator: LoginCoordinatorNavigateDelegate { ) coordinator.start() } + + func showNicknameSetting() { + let coordinator = NicknameCoordinator( + navigationController: self.navigationController + ) + coordinator.start() + } + + func showGroupSearch() { + let coordinator = GroupSearchCoordinator( + navigationController: self.navigationController + ) + coordinator.start() + } } diff --git a/onboard-iOS/Coordinator/NicknameCoordinator.swift b/onboard-iOS/Coordinator/NicknameCoordinator.swift index e2e93326..7b7560db 100644 --- a/onboard-iOS/Coordinator/NicknameCoordinator.swift +++ b/onboard-iOS/Coordinator/NicknameCoordinator.swift @@ -7,6 +7,10 @@ import UIKit +protocol NicknameCoordinatorNavigateDelegate: AnyObject { + func showGroupSearch() +} + final class NicknameCoordinator: Coordinator { var childCoordinators: [Coordinator] = [] @@ -18,9 +22,21 @@ final class NicknameCoordinator: Coordinator { } func start() { - let reactor = NicknameReactor(coordinator: self) - let viewController = NicknameViewController(reactor: reactor) + DispatchQueue.main.async { + let reactor = NicknameReactor(coordinator: self) + let viewController = NicknameViewController(reactor: reactor) + + self.navigationController?.pushViewController(viewController, animated: true) + } + } +} + +extension NicknameCoordinator: NicknameCoordinatorNavigateDelegate { + func showGroupSearch() { + let coordinator = GroupSearchCoordinator( + navigationController: self.navigationController + ) - self.navigationController?.pushViewController(viewController, animated: true) + coordinator.start() } } diff --git a/onboard-iOS/Coordinator/TermsAgreementCoordinator.swift b/onboard-iOS/Coordinator/TermsAgreementCoordinator.swift index 8900917f..2543e282 100644 --- a/onboard-iOS/Coordinator/TermsAgreementCoordinator.swift +++ b/onboard-iOS/Coordinator/TermsAgreementCoordinator.swift @@ -22,16 +22,17 @@ final class TermsAgreementCoordinator: Coordinator { } func start() { - let repository = TermsAgreementRepositoryImpl() - let useCase = TermsAgreementUseCaseImpl(repository: repository) - let reactor = TermsAgreementReactor(coordinator: self, useCase: useCase) - let viewController = UINavigationController( - rootViewController: TermsAgreementViewController(reactor: reactor) - ) - viewController.modalPresentationStyle = .overFullScreen - - self.navigationController?.present(viewController, animated: false) - self.navigationController = viewController + DispatchQueue.main.async { + let repository = TermsAgreementRepositoryImpl() + let useCase = TermsAgreementUseCaseImpl(repository: repository) + let reactor = TermsAgreementReactor(coordinator: self, useCase: useCase) + let viewController = UINavigationController( + rootViewController: TermsAgreementViewController(reactor: reactor) + ) + viewController.modalPresentationStyle = .overFullScreen + self.navigationController?.present(viewController, animated: false) + self.navigationController = viewController + } } func showTerms(url: String) { diff --git a/onboard-iOS/DesignSystem/Values/BaseButton.swift b/onboard-iOS/DesignSystem/Values/BaseButton.swift index 64de25ee..fbd85f2e 100644 --- a/onboard-iOS/DesignSystem/Values/BaseButton.swift +++ b/onboard-iOS/DesignSystem/Values/BaseButton.swift @@ -53,10 +53,14 @@ class BaseButton: UIButton { self.bgColor = Colors.Orange_10 self.textColor = Colors.Gray_2 self.typo = Font.Typography.label3_B + self.isEnabled = true + case .disabled: self.bgColor = Colors.Gray_4 self.textColor = Colors.Gray_7 self.typo = Font.Typography.label3_M + self.isEnabled = false + case .pressed: self.bgColor = Colors.Gray_15 self.textColor = Colors.Gray_1 diff --git a/onboard-iOS/Domain/Entity/OnboardingEntity.swift b/onboard-iOS/Domain/Entity/OnboardingEntity.swift new file mode 100644 index 00000000..58f45553 --- /dev/null +++ b/onboard-iOS/Domain/Entity/OnboardingEntity.swift @@ -0,0 +1,13 @@ +// +// OnboardingEntity.swift +// onboard-iOS +// +// Created by 윤다예 on 12/2/23. +// + +import Foundation + +struct OnboardingEntity { + let stages: [String] + let groupId: Int? +} diff --git a/onboard-iOS/Domain/Entity/TermsAgreementEntity.swift b/onboard-iOS/Domain/Entity/TermsEntity.swift similarity index 59% rename from onboard-iOS/Domain/Entity/TermsAgreementEntity.swift rename to onboard-iOS/Domain/Entity/TermsEntity.swift index 07024eda..f3053b5d 100644 --- a/onboard-iOS/Domain/Entity/TermsAgreementEntity.swift +++ b/onboard-iOS/Domain/Entity/TermsEntity.swift @@ -1,5 +1,5 @@ // -// TermsAgreementEntity.swift +// TermsEntity.swift // onboard-iOS // // Created by 윤다예 on 11/28/23. @@ -7,7 +7,7 @@ import Foundation -struct TermsAgreementEntity { +struct TermsEntity { let terms: [Term] struct Term { @@ -17,3 +17,10 @@ struct TermsAgreementEntity { let isReuired: Bool } } + +enum TermsAgreementEntity { + struct Req { + let agreeList: [String] + let disagreeList: [String] + } +} diff --git a/onboard-iOS/Domain/Login/AppleLoginUseCase.swift b/onboard-iOS/Domain/Login/AppleLoginUseCase.swift index 597f1316..f687dd40 100644 --- a/onboard-iOS/Domain/Login/AppleLoginUseCase.swift +++ b/onboard-iOS/Domain/Login/AppleLoginUseCase.swift @@ -10,7 +10,7 @@ import Foundation import RxSwift protocol AppleLoginUseCase { - var result: Observable { get } + var result: Observable { get } func signIn() async } @@ -22,28 +22,38 @@ protocol AppleLoginDelegate: AnyObject { protocol AuthRepository { // api 호출 및 토큰 가져오기 func signIn(req: AuthEntity.Req) async throws -> AuthEntity.Res + func onboarding() async throws -> OnboardingEntity } final class AppleLoginUseCaseImpl: AppleLoginUseCase { private let appleLoginManager: AppleLoginManager private let authRepository: AuthRepository + private let keychainService: KeychainService - var result: Observable - private let _result: PublishSubject = .init() + var result: Observable + private let _result: PublishSubject = .init() init( appleLoginManager: AppleLoginManager, - authRepository: AuthRepository + authRepository: AuthRepository, + keychainService: KeychainService ) { self.appleLoginManager = appleLoginManager self.authRepository = authRepository + self.keychainService = keychainService self.result = self._result } func signIn() async { + self.removeKeychainData() self.appleLoginManager.excute(delegate: self) } + + private func removeKeychainData() { + self.keychainService.remove(forKey: .accessToken) + self.keychainService.remove(forKey: .refreshToken) + } } // MARK: - Apple login delegate @@ -52,18 +62,15 @@ extension AppleLoginUseCaseImpl: AppleLoginDelegate { func success(token: String) { Task { - let result = try await self.authRepository.signIn( + let authResult = try await self.authRepository.signIn( req: AuthEntity.Req(type: .apple, token: token) ) - - // TODO: 온보딩 진행정보 받아오기 호출 구현 - // 임시로 false 처리 - let isExisted = false - print(result.accessToken) - print("-=-----==========") - print(result.refreshToken) - - self._result.onNext(isExisted) + self.keychainService.set(authResult.accessToken, forKey: .accessToken) + self.keychainService.set(authResult.refreshToken, forKey: .refreshToken) + + let onboardingResult = try await self.authRepository.onboarding() + + self._result.onNext(onboardingResult) } } } diff --git a/onboard-iOS/Domain/Login/GoogleLoginUseCase.swift b/onboard-iOS/Domain/Login/GoogleLoginUseCase.swift new file mode 100644 index 00000000..6ec9ae94 --- /dev/null +++ b/onboard-iOS/Domain/Login/GoogleLoginUseCase.swift @@ -0,0 +1,57 @@ +// +// GoogleLoginUseCase.swift +// onboard-iOS +// +// Created by 윤다예 on 12/3/23. +// + +import Foundation + +import RxSwift + +protocol GoogleLoginUseCase { + var result: Observable { get } + func signIn(token: String) async throws +} + +protocol GoogleLoginDelegate: AnyObject { + // 구글로그인 토큰 전달 + func success(token: String) +} + +final class GoogleLoginUseCaseImpl: GoogleLoginUseCase { + + private let authRepository: AuthRepository + private let keychainService: KeychainService + + var result: Observable + private let _result: PublishSubject = .init() + + init( + authRepository: AuthRepository, + keychainService: KeychainService + ) { + self.authRepository = authRepository + self.keychainService = keychainService + self.result = self._result + } + + func signIn(token: String) async throws { + self.removeKeychainData() + + let authResult = try await self.authRepository.signIn( + req: AuthEntity.Req(type: .google, token: token) + ) + self.keychainService.set(authResult.accessToken, forKey: .accessToken) + self.keychainService.set(authResult.refreshToken, forKey: .refreshToken) + + let onboardingResult = try await self.authRepository.onboarding() + + self._result.onNext(onboardingResult) + } + + private func removeKeychainData() { + self.keychainService.remove(forKey: .accessToken) + self.keychainService.remove(forKey: .refreshToken) + } +} diff --git a/onboard-iOS/Domain/Login/KakaoLoginUseCase.swift b/onboard-iOS/Domain/Login/KakaoLoginUseCase.swift index 6532c946..f17b2baa 100644 --- a/onboard-iOS/Domain/Login/KakaoLoginUseCase.swift +++ b/onboard-iOS/Domain/Login/KakaoLoginUseCase.swift @@ -9,7 +9,7 @@ import Foundation import RxSwift protocol KakaoLoginUseCase { - var result: Observable { get } + var result: Observable { get } func signIn() async } @@ -22,9 +22,10 @@ final class KakaoLoginUseCaseImpl: KakaoLoginUseCase { private let kakaoLoginManager: KakaoLoginManager private let authRepository: AuthRepository + private let keychainService = KeychainServiceImpl() - var result: Observable - private let _result: PublishSubject = .init() + var result: Observable + private let _result: PublishSubject = .init() init( kakaoLoginManager: KakaoLoginManager, @@ -36,8 +37,14 @@ final class KakaoLoginUseCaseImpl: KakaoLoginUseCase { } func signIn() async { + self.removeKeychainData() self.kakaoLoginManager.excute(delegate: self) } + + private func removeKeychainData() { + self.keychainService.remove(forKey: .accessToken) + self.keychainService.remove(forKey: .refreshToken) + } } // MARK: - Kakao Login delegate @@ -45,17 +52,16 @@ final class KakaoLoginUseCaseImpl: KakaoLoginUseCase { extension KakaoLoginUseCaseImpl: KakaoLoginDelegate { func sendOAuthToken(_ token: String) { Task { - let result = try await self.authRepository.signIn( + let authResult = try await self.authRepository.signIn( req: AuthEntity.Req(type: .kakao, token: token) ) - // TODO: 온보딩 진행정보 받아오기 호출 구현 - // 임시로 false 처리 - let isExisted = false - print(result.accessToken) - print(result.refreshToken) + self.keychainService.set(authResult.accessToken, forKey: .accessToken) + self.keychainService.set(authResult.refreshToken, forKey: .refreshToken) + + let onboardingResult = try await self.authRepository.onboarding() - self._result.onNext(isExisted) + self._result.onNext(onboardingResult) } } } diff --git a/onboard-iOS/Domain/Login/TermsAgreementUseCase.swift b/onboard-iOS/Domain/Login/TermsAgreementUseCase.swift index 1481b9b8..0a945a0d 100644 --- a/onboard-iOS/Domain/Login/TermsAgreementUseCase.swift +++ b/onboard-iOS/Domain/Login/TermsAgreementUseCase.swift @@ -10,30 +10,44 @@ import Foundation import RxSwift protocol TermsAgreementUseCase { - var result: Observable<[TermsAgreementEntity.Term]> { get set } + var termsList: Observable<[TermsEntity.Term]> { get set } + var agreeResult: Observable { get set } func fetch() async + func agreeTerms(req: TermsAgreementEntity.Req) async } protocol TermsAgreementRepository { - func fetch() async throws -> [TermsAgreementEntity.Term] + func fetch() async throws -> [TermsEntity.Term] + func agreeTerms(req: TermsAgreementEntity.Req) async throws -> Bool } final class TermsAgreementUseCaseImpl: TermsAgreementUseCase { private let repository: TermsAgreementRepository - var result: Observable<[TermsAgreementEntity.Term]> - private let _result: PublishSubject<[TermsAgreementEntity.Term]> = .init() + var termsList: Observable<[TermsEntity.Term]> + private let _termsList: PublishSubject<[TermsEntity.Term]> = .init() + + var agreeResult: Observable + private let _agreeResult: PublishSubject = .init() init(repository: TermsAgreementRepository) { self.repository = repository - self.result = _result + self.termsList = _termsList + self.agreeResult = _agreeResult } func fetch() async { Task { let result = try await self.repository.fetch() - self._result.onNext(result) + self._termsList.onNext(result) + } + } + + func agreeTerms(req: TermsAgreementEntity.Req) async { + Task { + let result = try await self.repository.agreeTerms(req: req) + self._agreeResult.onNext(result) } } } diff --git a/onboard-iOS/Info.plist b/onboard-iOS/Info.plist index 67f05dfe..23a4201e 100644 --- a/onboard-iOS/Info.plist +++ b/onboard-iOS/Info.plist @@ -2,38 +2,39 @@ - UIAppFonts - - SpoqaHanSansNeo-Bold.ttf - SpoqaHanSansNeo-Medium.ttf - SpoqaHanSansNeo-Regular.ttf - SpoqaHanSansNeo-Light.ttf - SpoqaHanSansNeo-Thin.ttf - + GIDClientID + 407255596267-ug3kch0n6tnc8bmdjj87q0amckjkp7jt.apps.googleusercontent.com CFBundleURLTypes - CFBundleURLName - - CFBundleURLSchemes - - com.googleusercontent.apps.60961186546-rtf4h136vnreuooj193b19jhsgjsoba9 - - - GIDClientID - 60961186546-rtf4h136vnreuooj193b19jhsgjsoba9.apps.googleusercontent.com CFBundleTypeRole Editor + CFBundleURLName + CFBundleURLSchemes - kakao0fc9af67a72e49041aa31ec49dcd8bc0 + com.googleusercontent.apps.407255596267-ug3kch0n6tnc8bmdjj87q0amckjkp7jt + + + LSApplicationQueriesSchemes + + kakaokompassauth + kakaolink NSAppTransportSecurity NSAllowsArbitraryLoads + UIAppFonts + + SpoqaHanSansNeo-Bold.ttf + SpoqaHanSansNeo-Medium.ttf + SpoqaHanSansNeo-Regular.ttf + SpoqaHanSansNeo-Light.ttf + SpoqaHanSansNeo-Thin.ttf + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes @@ -53,10 +54,5 @@ - LSApplicationQueriesSchemes - - kakaokompassauth - kakaolink - diff --git a/onboard-iOS/Network/APIConstants.swift b/onboard-iOS/Network/APIConstants.swift index d4dcab9e..935800ee 100644 --- a/onboard-iOS/Network/APIConstants.swift +++ b/onboard-iOS/Network/APIConstants.swift @@ -9,7 +9,7 @@ import Foundation import Alamofire enum API { - static let BASE_URL = "http://sandbox-api.onboardgame.co.kr/" + static let BASE_URL = "http://sandbox-api.onboardgame.co.kr/api/" } class APISession { diff --git a/onboard-iOS/Network/OBNetworkManager.swift b/onboard-iOS/Network/OBNetworkManager.swift index 2d838b59..83777486 100644 --- a/onboard-iOS/Network/OBNetworkManager.swift +++ b/onboard-iOS/Network/OBNetworkManager.swift @@ -15,7 +15,9 @@ final class OBNetworkManager { private var session: Session - private let interceptor = OBRequestInterceptor() + private let interceptor = OBRequestInterceptor( + keychainService: KeychainServiceImpl() + ) private let apiLogger = APIEventLogger() private init() { @@ -37,32 +39,7 @@ final class OBNetworkManager { .validate(statusCode: 200..<300) .serializingDecodable(object) .response - - // if let afError = response.error as? AFError { - // if let data = response.data, let dataString = String(data: data, encoding: .utf8) { - // print(dataString) - // } - // } return response } - - func googleLoginRequest(token: String) throws { - let googleLoginAPI = try OBRouter.auth( - body: AuthRequest.Body( - type: AuthEntity.Req.AuthType.google.rawValue, - token: token - ).encode() - ) - - APISession.session.request(googleLoginAPI) - .responseDecodable { (response: AFDataResponse) in - switch response.result { - case .success(let data): - print("Response Data: \(data)") - case .failure(let error): - print("Response Error: \(error)") - } - } - } } diff --git a/onboard-iOS/Network/OBRequestInterceptor.swift b/onboard-iOS/Network/OBRequestInterceptor.swift index a4aadd27..44877fa7 100644 --- a/onboard-iOS/Network/OBRequestInterceptor.swift +++ b/onboard-iOS/Network/OBRequestInterceptor.swift @@ -11,6 +11,12 @@ import Alamofire final class OBRequestInterceptor: RequestInterceptor { + private var keychainService: KeychainService + + init(keychainService: KeychainService) { + self.keychainService = keychainService + } + func adapt( _ urlRequest: URLRequest, for session: Session, @@ -19,15 +25,13 @@ final class OBRequestInterceptor: RequestInterceptor { var request = urlRequest - // TODO: - 키체인, 소셜로그인 구현 후 삭제할 것 - let sugarToken = "77+9bVoF77+9ZjHvv71gDe+/vRNDKe+/vT1NZO+/" - - guard urlRequest.url?.absoluteString.hasPrefix(API.BASE_URL) == true else { + guard urlRequest.url?.absoluteString.hasPrefix(API.BASE_URL) == true, + let accessToken = self.keychainService.value(forKey: .accessToken) else { completion(.success(urlRequest)) return } - request.setValue("Bearer " + sugarToken, forHTTPHeaderField: "Authorization") + request.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization") completion(.success(request)) } diff --git a/onboard-iOS/Network/OBRouter.swift b/onboard-iOS/Network/OBRouter.swift index 9012d101..b5143653 100644 --- a/onboard-iOS/Network/OBRouter.swift +++ b/onboard-iOS/Network/OBRouter.swift @@ -26,6 +26,8 @@ enum OBRouter: URLRequestConvertible { case testAPI case auth(body: Body) case terms + case agreementTerms(body: Body) + case onboarding case groupList(params: Params) case addGroup(body: Body) @@ -33,10 +35,37 @@ enum OBRouter: URLRequestConvertible { var method: HTTPMethod { switch self { - case .testAPI, .groupList, .terms: + // Group + case .groupList, .addGroup: + return .get + + // Match + + // Terms + case .terms: return .get - case .auth, .addGroup: + + case .agreementTerms: + return .post + + // Auth + case .auth: return .post + + // Setting + + // User + case .onboarding: + return .get + + // Game + + // Member + + // Etc + case .testAPI: + return .get + } } @@ -44,14 +73,34 @@ enum OBRouter: URLRequestConvertible { var path: String { switch self { - case .testAPI: - return "v1/test" - case .auth: - return "v1/auth/login" - case .terms: - return "api/v1/terms" - case .groupList, .addGroup: - return "v1/group" + // Group + case .groupList, .addGroup: + return "v1/group" + + // Match + + // Terms + case .terms, .agreementTerms: + return "v1/terms" + + // Auth + case .auth: + return "v1/auth/login" + + // Setting + + // User + case .onboarding: + return "v1/user/me/onboarding" + + // Game + + // Member + + // Etc + case .testAPI: + return "v1/test" + } } @@ -59,7 +108,7 @@ enum OBRouter: URLRequestConvertible { var header: Header? { switch self { - case .testAPI, .auth, .addGroup, .groupList, .terms: + default: return nil } } @@ -68,11 +117,13 @@ enum OBRouter: URLRequestConvertible { var body: Body? { switch self { - case .testAPI, .groupList, .terms: - return nil - - case let .auth(body), let .addGroup(body): + case let .auth(body), + let .agreementTerms(body), + let .addGroup(body): return body + + default: + return nil } } @@ -80,10 +131,10 @@ enum OBRouter: URLRequestConvertible { var params: Params? { switch self { - case .testAPI, .auth, .addGroup, .terms: - return nil case let .groupList(params): return params + default: + return nil } } diff --git a/onboard-iOS/Presentation/Login/LoginReactor.swift b/onboard-iOS/Presentation/Login/LoginReactor.swift index c8b6c9e1..fc962bb4 100644 --- a/onboard-iOS/Presentation/Login/LoginReactor.swift +++ b/onboard-iOS/Presentation/Login/LoginReactor.swift @@ -18,26 +18,28 @@ final class LoginReactor: Reactor { case google case kakao } - - enum Mutation { - case setLoginResult(result: String) - } + + enum Mutation { } struct State { var result: String = "" + var stage: [OnboardingStage] = [] } private let coordinator: LoginCoordinator private let appleUseCase: AppleLoginUseCase private let kakaoUseCase: KakaoLoginUseCase + private let googleUseCase: GoogleLoginUseCase init( appleUseCase: AppleLoginUseCase, kakaoUseCase: KakaoLoginUseCase, + googleUseCase: GoogleLoginUseCase, coordinator: LoginCoordinator ) { self.appleUseCase = appleUseCase self.kakaoUseCase = kakaoUseCase + self.googleUseCase = googleUseCase self.coordinator = coordinator } @@ -47,44 +49,63 @@ final class LoginReactor: Reactor { return self.excuteAppleLogin() case .google: - return self.googleLoginResult() + return .empty() case .kakao: - // TODO: Kakao Login return self.excuteKakaoLogin() } } - - func reduce(state: State, mutation: Mutation) -> State { - var state = state - - switch mutation { - case let .setLoginResult(token): - state.result = token - } - - return state - } - + func transform(mutation: Observable) -> Observable { - let loginMutation = self.mutation( + let appleMutation = self.mutation( result: self.appleUseCase.result ) + + let kakaoMutation = self.mutation( + result: self.kakaoUseCase.result + ) + + let googleMutation = self.mutation( + result: self.googleUseCase.result + ) - return Observable.merge(mutation, loginMutation) + return Observable.merge([ + mutation, + appleMutation, + kakaoMutation, + googleMutation + ]) } - } extension LoginReactor { - private func mutation(result: Observable) -> Observable { + private func mutation(result: Observable) -> Observable { return result.flatMap { response -> Observable in - if response { - return .just(.setLoginResult(result: "success")) + + guard let firstStage = response.stages.first, + let stage = OnboardingStage(rawValue: firstStage) else { + + // TODO: - 온보딩 남은 스테이지 없음 -> home으로 이동 +// if response.stages.isEmpty { +// return self.coordinator.showHome() +// } + return .empty() + } + + switch stage { + case .terms, .updateTerms: + self.coordinator.showTermsAgreementView() + + case .nickname: + self.coordinator.showNicknameSetting() + + case .joinGroup: + self.coordinator.showGroupSearch() } - return .just(.setLoginResult(result: "fail")) + + return .empty() } } @@ -104,6 +125,7 @@ extension LoginReactor { private func excuteKakaoLogin() -> Observable { return Observable.create { [weak self] observer in guard let self else { return Disposables.create() } + Task { do { await self.kakaoUseCase.signIn() @@ -113,13 +135,30 @@ extension LoginReactor { } } - private func googleLoginResult() -> Observable { + private func googleLoginResult(token: String) -> Observable { return Observable.create { [weak self] observer in guard let self else { return Disposables.create() } + +// Task { +// do { +// try await self.googleUseCase.signIn(token: token) +// } +// } + return Disposables.create() + } + } +} - self.coordinator.showTermsAgreementView() +// MARK: - Google login delegate - return Disposables.create() +extension LoginReactor: GoogleLoginDelegate { + + func success(token: String) { + Task { + do { + try await self.googleUseCase.signIn(token: token) + } } } } + diff --git a/onboard-iOS/Presentation/Login/LoginViewController.swift b/onboard-iOS/Presentation/Login/LoginViewController.swift index b572a820..db283870 100644 --- a/onboard-iOS/Presentation/Login/LoginViewController.swift +++ b/onboard-iOS/Presentation/Login/LoginViewController.swift @@ -43,6 +43,14 @@ final class LoginViewController: UIViewController, View { private func bindAction(reactor: LoginReactor) { self.loginView.didTapGoogleButton = { + + let googleLoginManager = GoogleLoginManagerImpl() + + googleLoginManager.signIn( + presentingViewController: self, + delegate: self.reactor ?? nil + ) + reactor.action.onNext(.google) } diff --git a/onboard-iOS/Presentation/SignUp/NicknameReactor.swift b/onboard-iOS/Presentation/SignUp/NicknameReactor.swift index 0afafd6f..2cff48d4 100644 --- a/onboard-iOS/Presentation/SignUp/NicknameReactor.swift +++ b/onboard-iOS/Presentation/SignUp/NicknameReactor.swift @@ -35,6 +35,7 @@ final class NicknameReactor: Reactor { switch action { case .confirm: + self.coordinator.showGroupSearch() return .empty() } } diff --git a/onboard-iOS/Presentation/SignUp/TermsAgreementReactor.swift b/onboard-iOS/Presentation/SignUp/TermsAgreementReactor.swift index cbccbfbe..a0a60fc9 100644 --- a/onboard-iOS/Presentation/SignUp/TermsAgreementReactor.swift +++ b/onboard-iOS/Presentation/SignUp/TermsAgreementReactor.swift @@ -36,6 +36,7 @@ final class TermsAgreementReactor: Reactor { let isRequired: Bool let url: String let isChecked: Bool + let code: String } } @@ -70,9 +71,7 @@ final class TermsAgreementReactor: Reactor { return .just(.updateAllAgreement) case .selectRegister: - self.coordinator.showNicknameSetting() - - return .empty() + return self.agreeTerms() } } @@ -100,11 +99,19 @@ final class TermsAgreementReactor: Reactor { func transform(mutation: Observable) -> Observable { - let loginMutation = self.mutation( - result: self.useCase.result + let termsListMutation = self.mutation( + termsList: self.useCase.termsList + ) + + let termsAgreementMutation = self.mutation( + agreeResult: self.useCase.agreeResult ) - return Observable.merge(mutation, loginMutation) + return Observable.merge([ + mutation, + termsListMutation, + termsAgreementMutation + ]) } } @@ -112,13 +119,23 @@ final class TermsAgreementReactor: Reactor { extension TermsAgreementReactor { private func mutation( - result: Observable<[TermsAgreementEntity.Term]> + termsList: Observable<[TermsEntity.Term]> ) -> Observable { - return result.flatMap { response -> Observable in + return termsList.flatMap { response -> Observable in return .just(.setTerms(self.toState(response))) } } + private func mutation( + agreeResult: Observable + ) -> Observable { + return agreeResult.flatMap { response -> Observable in + self.coordinator.showNicknameSetting() + + return .empty() + } + } + private func fetch() -> Observable { return Observable.create { [weak self] observer in guard let self else { return Disposables.create() } @@ -131,14 +148,42 @@ extension TermsAgreementReactor { } } + private func agreeTerms() -> Observable { + return Observable.create { [weak self] observer in + guard let self else { return Disposables.create() } + Task { + do { + + let agreeList = self.currentState.terms + .filter { $0.isChecked } + .map { $0.code } + + let disagreeList = self.currentState.terms + .filter { !$0.isChecked } + .map { $0.code } + + await self.useCase.agreeTerms( + req: TermsAgreementEntity.Req( + agreeList: agreeList, + disagreeList: disagreeList + ) + ) + } + } + return Disposables.create() + } + } + + private func toState( - _ terms: [TermsAgreementEntity.Term] + _ terms: [TermsEntity.Term] ) -> [TermsAgreementReactor.State.Term] { return terms.map { TermsAgreementReactor.State.Term( title: $0.title, isRequired: $0.isReuired, url: $0.url, - isChecked: false + isChecked: false, + code: $0.code )} } @@ -154,7 +199,8 @@ extension TermsAgreementReactor { title: term.title, isRequired: term.isRequired, url: term.url, - isChecked: !term.isChecked + isChecked: !term.isChecked, + code: term.code ) let isAllAgreement = !terms.contains(where: { $0.isChecked == false }) @@ -171,7 +217,8 @@ extension TermsAgreementReactor { title: $0.title, isRequired: $0.isRequired, url: $0.url, - isChecked: !state.isAllAgreemented + isChecked: !state.isAllAgreemented, + code: $0.code ) }, isAllAgreemented: !state.isAllAgreemented diff --git a/onboard-iOS/Presentation/SignUp/Views/NicknameView.swift b/onboard-iOS/Presentation/SignUp/Views/NicknameView.swift index 1c59c459..d3a72f7c 100644 --- a/onboard-iOS/Presentation/SignUp/Views/NicknameView.swift +++ b/onboard-iOS/Presentation/SignUp/Views/NicknameView.swift @@ -48,6 +48,8 @@ final class NicknameView: UIView { button.setTitle("확인", for: .normal) return button }() + + var selectConfirm: (() -> Void)? // MARK: - Initialize @@ -68,6 +70,14 @@ final class NicknameView: UIView { self.stackView.axis = .vertical self.stackView.spacing = 4 self.makeConstraints() + self.setConfirmButtonAction() + } + + private func setConfirmButtonAction() { + let action = UIAction(handler: { _ in + self.selectConfirm?() + }) + self.confirmButton.addAction(action, for: .touchUpInside) } private func makeConstraints() { diff --git a/onboard-iOS/Presentation/SignUp/Views/NicknameViewController.swift b/onboard-iOS/Presentation/SignUp/Views/NicknameViewController.swift index 2ad6a064..a375dd06 100644 --- a/onboard-iOS/Presentation/SignUp/Views/NicknameViewController.swift +++ b/onboard-iOS/Presentation/SignUp/Views/NicknameViewController.swift @@ -36,6 +36,9 @@ final class NicknameViewController: UIViewController, View { } private func bindAction(reactor: Reactor) { + self.nicknameView.selectConfirm = { + self.reactor?.action.onNext(.confirm) + } } private func bindState(reactor: Reactor) { diff --git a/onboard-iOS/Presentation/Test/TestReactor.swift b/onboard-iOS/Presentation/Test/TestReactor.swift index d4a14cd9..23686bc5 100644 --- a/onboard-iOS/Presentation/Test/TestReactor.swift +++ b/onboard-iOS/Presentation/Test/TestReactor.swift @@ -72,15 +72,6 @@ final class TestReactor: Reactor { return state } - func transform(mutation: Observable) -> Observable { - - let loginMutation = self.mutation( - result: self.appleUseCase.result - ) - - return Observable.merge(mutation, loginMutation) - } - } extension TestReactor { diff --git a/onboard-iOS/Presentation/Test/TestViewController.swift b/onboard-iOS/Presentation/Test/TestViewController.swift index 8d29e8c6..33825e8b 100644 --- a/onboard-iOS/Presentation/Test/TestViewController.swift +++ b/onboard-iOS/Presentation/Test/TestViewController.swift @@ -134,8 +134,4 @@ final class TestViewController: UIViewController, View { }) .disposed(by: self.disposeBag) } - - @objc private func buttonAction() { - GoogleLoginManager.shared.signIn(withPresenting: self) - } } diff --git a/onboard-iOS/Repository/Auth/AuthRepository.swift b/onboard-iOS/Repository/Auth/AuthRepository.swift index 11454f6d..183b190a 100644 --- a/onboard-iOS/Repository/Auth/AuthRepository.swift +++ b/onboard-iOS/Repository/Auth/AuthRepository.swift @@ -36,6 +36,28 @@ final class AuthRepositoryImpl: AuthRepository { throw error } } + + func onboarding() async throws -> OnboardingEntity { + do { + let result = try await OBNetworkManager + .shared + .asyncRequest( + object: OnboardingDTO.self, + router: OBRouter.onboarding + ) + + guard let data = result.value else { + throw NetworkError.noExist + } + + return data.toDomain + + } catch { + print(error.localizedDescription) + + throw error + } + } } extension AuthDTO { @@ -46,3 +68,12 @@ extension AuthDTO { ) } } + +extension OnboardingDTO { + var toDomain: OnboardingEntity { + return OnboardingEntity( + stages: self.onboarding, + groupId: self.mainGroupId + ) + } +} diff --git a/onboard-iOS/Repository/Auth/TermsAgreementRepository.swift b/onboard-iOS/Repository/Auth/TermsAgreementRepository.swift index 8e8abfd7..da8e2787 100644 --- a/onboard-iOS/Repository/Auth/TermsAgreementRepository.swift +++ b/onboard-iOS/Repository/Auth/TermsAgreementRepository.swift @@ -9,13 +9,13 @@ import Foundation final class TermsAgreementRepositoryImpl: TermsAgreementRepository { - func fetch() async throws -> [TermsAgreementEntity.Term] { + func fetch() async throws -> [TermsEntity.Term] { do { let result = try await OBNetworkManager .shared .asyncRequest( - object: TermsAgreementDTO.self, + object: TermsDTO.self, router: OBRouter.terms ) @@ -31,12 +31,39 @@ final class TermsAgreementRepositoryImpl: TermsAgreementRepository { throw error } } + + func agreeTerms(req: TermsAgreementEntity.Req) async throws -> Bool { + do { + let result = try await OBNetworkManager + .shared + .asyncRequest( + object: TermsAgreementDTO.self, + router: OBRouter.agreementTerms( + body: TermsAgreementRequest.Body( + agree: req.agreeList, + disagree: req.disagreeList + ).encode() + ) + ) + + guard let data = result.value?.result else { + throw NetworkError.noExist + } + + return data + + } catch { + print(error.localizedDescription) + + throw error + } + } } -extension TermsAgreementDTO { - var toDomain: [TermsAgreementEntity.Term] { +extension TermsDTO { + var toDomain: [TermsEntity.Term] { return self.contents.map { - TermsAgreementEntity.Term( + TermsEntity.Term( code: $0.code, title: $0.title, url: $0.url, diff --git a/onboard-iOS/Repository/DTO/OnboardingDTO.swift b/onboard-iOS/Repository/DTO/OnboardingDTO.swift new file mode 100644 index 00000000..754c50ad --- /dev/null +++ b/onboard-iOS/Repository/DTO/OnboardingDTO.swift @@ -0,0 +1,13 @@ +// +// OnboardingDTO.swift +// onboard-iOS +// +// Created by 윤다예 on 12/2/23. +// + +import Foundation + +struct OnboardingDTO: Decodable { + let onboarding: [String] + let mainGroupId: Int? +} diff --git a/onboard-iOS/Repository/DTO/TermsAgreementDTO.swift b/onboard-iOS/Repository/DTO/TermsDTO.swift similarity index 64% rename from onboard-iOS/Repository/DTO/TermsAgreementDTO.swift rename to onboard-iOS/Repository/DTO/TermsDTO.swift index ef08b469..4c63cd9f 100644 --- a/onboard-iOS/Repository/DTO/TermsAgreementDTO.swift +++ b/onboard-iOS/Repository/DTO/TermsDTO.swift @@ -7,7 +7,7 @@ import Foundation -struct TermsAgreementDTO: Decodable { +struct TermsDTO: Decodable { let contents: [Term] struct Term: Decodable { @@ -17,3 +17,14 @@ struct TermsAgreementDTO: Decodable { let isRequired: Bool } } + +enum TermsAgreementRequest { + struct Body: Encodable { + let agree: [String] + let disagree: [String] + } +} + +struct TermsAgreementDTO: Decodable { + let result: Bool? +} diff --git a/onboard-iOS/Repository/SocialLogin/GoogleLoginManager.swift b/onboard-iOS/Repository/SocialLogin/GoogleLoginManager.swift index 3e18e634..b53e66a9 100644 --- a/onboard-iOS/Repository/SocialLogin/GoogleLoginManager.swift +++ b/onboard-iOS/Repository/SocialLogin/GoogleLoginManager.swift @@ -8,31 +8,39 @@ import Foundation import GoogleSignIn -class GoogleLoginManager { +protocol GoogleLoginManager { + func signIn( + presentingViewController: UIViewController, + delegate: GoogleLoginDelegate? + ) +} + +class GoogleLoginManagerImpl: GoogleLoginManager { + + private var token: String = "" { + didSet { + self.delegate?.success(token: self.token) + } + } - static let shared = GoogleLoginManager() + weak var delegate: GoogleLoginDelegate? - func signIn(withPresenting presentingViewController: UIViewController) { - GIDSignIn.sharedInstance.signIn(withPresenting: presentingViewController) { signInResult, error in + func signIn( + presentingViewController: UIViewController, + delegate: GoogleLoginDelegate? + ) { + + self.delegate = delegate + + GIDSignIn.sharedInstance.signIn( + withPresenting: presentingViewController + ) { signInResult, error in + + guard error == nil, + let result = signInResult, + let idToken = result.user.idToken?.tokenString else { return } - if error == nil { - guard let signInResult = signInResult else { return } - let user = signInResult.user - - let idTokenString: GIDToken? = user.idToken - - if let idToken = idTokenString { - let networkManager = OBNetworkManager.shared - - do { - try networkManager.googleLoginRequest(token: idToken.tokenString) - } catch { - print(error.localizedDescription) - } - } - } else { - print("Google 로그인 실패: \(error?.localizedDescription ?? "Unknown error")") - } + self.token = idToken } } } diff --git a/onboard-iOS/SceneDelegate.swift b/onboard-iOS/SceneDelegate.swift index f07baf27..48c7a1eb 100644 --- a/onboard-iOS/SceneDelegate.swift +++ b/onboard-iOS/SceneDelegate.swift @@ -8,6 +8,7 @@ import UIKit import KakaoSDKAuth import KakaoSDKCommon +import GoogleSignIn class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -50,6 +51,12 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { _ = AuthController.handleOpenUrl(url: url) } } + + guard let url = URLContexts.first?.url, + let scheme = url.scheme, + scheme.contains("com.googleusercontent.apps") else { return } + + GIDSignIn.sharedInstance.handle(url) } } diff --git a/onboard-iOS/Utils/KeychainService.swift b/onboard-iOS/Utils/KeychainService.swift new file mode 100644 index 00000000..363102f8 --- /dev/null +++ b/onboard-iOS/Utils/KeychainService.swift @@ -0,0 +1,44 @@ +// +// KeychainService.swift +// onboard-iOS +// +// Created by 윤다예 on 12/2/23. +// + +import Foundation + +import SwiftKeychainWrapper + +enum KeychainKey: String { + case accessToken + case refreshToken +} + +protocol KeychainService { + func set(_ value: String, forKey keychainKey: KeychainKey) + func value(forKey keychainKey: KeychainKey) -> String? + func remove(forKey keychainKey: KeychainKey) +} + +final class KeychainServiceImpl: KeychainService { + + private var keychain: KeychainWrapper { + return KeychainWrapper.standard + } + + /// 저장 + func set(_ value: String, forKey keychainKey: KeychainKey) { + self.keychain.set(value, forKey: keychainKey.rawValue) + } + + /// 불러오기 + func value(forKey keychainKey: KeychainKey) -> String? { + return self.keychain.string(forKey: keychainKey.rawValue) + } + + /// 삭제 + func remove(forKey keychainKey: KeychainKey) { + self.keychain.removeObject(forKey: keychainKey.rawValue) + } + +}