From a7e1b3f237e858e4ecdcbca3e953483942ee228b Mon Sep 17 00:00:00 2001 From: hooni Date: Wed, 17 Jul 2024 22:01:46 +0900 Subject: [PATCH 1/8] =?UTF-8?q?chore:=20appName=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20fcm=20print=20=EB=AC=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KkuMulKum.xcodeproj/project.pbxproj | 17 ++++---- KkuMulKum/Application/AppDelegate.swift | 5 +++ .../DTO/TargetType/LoginTargetType.swift | 13 +----- KkuMulKum/Resource/Service/HomeService.swift | 10 ++--- .../Login/VIewModel/LoginViewModel.swift | 40 +++++++++---------- .../ViewController/LoginViewController.swift | 2 +- 6 files changed, 39 insertions(+), 48 deletions(-) diff --git a/KkuMulKum.xcodeproj/project.pbxproj b/KkuMulKum.xcodeproj/project.pbxproj index 7945d68b..434d36c6 100644 --- a/KkuMulKum.xcodeproj/project.pbxproj +++ b/KkuMulKum.xcodeproj/project.pbxproj @@ -56,6 +56,7 @@ 789D73AD2C46C19500C7077D /* ProfileModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73AC2C46C19500C7077D /* ProfileModel.swift */; }; 789D73AF2C46D99B00C7077D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 789D73AE2C46D99B00C7077D /* GoogleService-Info.plist */; }; 789D73B32C47CC6D00C7077D /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73B22C47CC6D00C7077D /* LocalNotificationManager.swift */; }; + 789D73BB2C47F1A100C7077D /* TardyService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73BA2C47F1A100C7077D /* TardyService.swift */; }; 78AED1342C3D951F000AD80A /* NicknameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AED1332C3D951F000AD80A /* NicknameViewController.swift */; }; 78AED1372C3D98D1000AD80A /* NicknameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AED1362C3D98D1000AD80A /* NicknameView.swift */; }; 78B9286C2C29402C006D9942 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B9286B2C29402C006D9942 /* AppDelegate.swift */; }; @@ -236,6 +237,7 @@ 789D73AE2C46D99B00C7077D /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 789D73B02C46DACD00C7077D /* KkuMulKum.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KkuMulKum.entitlements; sourceTree = ""; }; 789D73B22C47CC6D00C7077D /* LocalNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationManager.swift; sourceTree = ""; }; + 789D73BA2C47F1A100C7077D /* TardyService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TardyService.swift; sourceTree = ""; }; 78AED1332C3D951F000AD80A /* NicknameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameViewController.swift; sourceTree = ""; }; 78AED1362C3D98D1000AD80A /* NicknameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameView.swift; sourceTree = ""; }; 78B928682C29402C006D9942 /* KkuMulKum.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KkuMulKum.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -295,7 +297,6 @@ DD43937E2C41357800EC1799 /* InviteCodeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteCodeViewModel.swift; sourceTree = ""; }; DD4909952C440CDC003ED304 /* ArriveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArriveView.swift; sourceTree = ""; }; DD4909972C441215003ED304 /* TardyViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TardyViewModel.swift; sourceTree = ""; }; - DD49099B2C441719003ED304 /* TardyService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TardyService.swift; sourceTree = ""; }; DD8626522C4606A300E4F980 /* SetReadyInfoViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetReadyInfoViewModel.swift; sourceTree = ""; }; DD8626532C4606A300E4F980 /* ReadyStatusViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadyStatusViewModel.swift; sourceTree = ""; }; DD8626552C4606A300E4F980 /* EnterReadyInfoButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnterReadyInfoButtonView.swift; sourceTree = ""; }; @@ -933,7 +934,7 @@ DD49099A2C441709003ED304 /* Service */ = { isa = PBXGroup; children = ( - DD49099B2C441719003ED304 /* TardyService.swift */, + 789D73BA2C47F1A100C7077D /* TardyService.swift */, ); path = Service; sourceTree = ""; @@ -1650,7 +1651,6 @@ DE6D4D112C3F14D80005584B /* MeetingInfoBannerView.swift in Sources */, DE6D4D122C3F14D80005584B /* MeetingInfoView.swift in Sources */, DD30721E2C3C0CC800416D9F /* PromiseInfoResponseModel.swift in Sources */, - DD931B722C3DA92700526452 /* EnterReadyInfoButtonView.swift in Sources */, DD41BEFF2C41DAA40095A068 /* TardyEmptyView.swift in Sources */, A3FB18512C3BF531001483E5 /* RegisterMeetingsResquestModel.swift in Sources */, DD8626632C4606A300E4F980 /* EnterReadyInfoButtonView.swift in Sources */, @@ -1675,7 +1675,6 @@ DE9E18842C3BA84500DB76B4 /* CustomTextField.swift in Sources */, A3FB184F2C3BF4BC001483E5 /* MakeMeetingsResponseModel.swift in Sources */, DDE7D2C42C470A58005A921F /* NicknameTargetType.swift in Sources */, - DD3976852C41C2AD00E2A4C4 /* UpcomingPromiseModel.swift in Sources */, DEF725DB2C3F3BBF008C87C7 /* Toast.swift in Sources */, DD43937A2C412F4500EC1799 /* FinishCreateViewController.swift in Sources */, DE254AAC2C31192400A4015E /* UILabel+.swift in Sources */, @@ -1707,7 +1706,7 @@ DD3976832C41C2AD00E2A4C4 /* HomeViewModel.swift in Sources */, DE9E189A2C3BCCBE00DB76B4 /* PlaceModel.swift in Sources */, DD41BF012C41DE4F0095A068 /* TardyCollectionViewCell.swift in Sources */, - DE9E189A2C3BCCBE00DB76B4 /* UtilsTemp.swift in Sources */, + DE9E189A2C3BCCBE00DB76B4 /* PlaceModel.swift in Sources */, A3DD9C682C45C78300E58A13 /* HomeService.swift in Sources */, 782B407B2C3E395A008B0CA7 /* WelcomeView.swift in Sources */, DD8626612C4606A300E4F980 /* SetReadyInfoViewModel.swift in Sources */, @@ -1747,11 +1746,6 @@ DD41BEFC2C41D54D0095A068 /* TardyPenaltyView.swift in Sources */, 789873322C3D1A7B00435E96 /* LoginViewController.swift in Sources */, 78BD61342C45B4A7005752FD /* AuthService.swift in Sources */, - 782B40722C3DBFA3008B0CA7 /* ProfileSetupViewModel.swift in Sources */, - DDAF1C8F2C3D6E3D008A37D3 /* PagePromiseSegmentedControl.swift in Sources */, - DD49099C2C441719003ED304 /* TardyService.swift in Sources */, - 782B40722C3DBFA3008B0CA7 /* ProfileSetupViewModel.swift in Sources */, - DDAF1C8F2C3D6E3D008A37D3 /* PagePromiseSegmentedControl.swift in Sources */, DE32D1D22C3BF703006848DF /* LoginUserResponseModel.swift in Sources */, DD8626692C4606A300E4F980 /* ReadyStatusProgressView.swift in Sources */, DE9E18892C3BC91000DB76B4 /* ResponseBodyDTO.swift in Sources */, @@ -1767,6 +1761,7 @@ DD931B6E2C3DA27F00526452 /* ParticipantCollectionViewCell.swift in Sources */, DD3976882C41C2AD00E2A4C4 /* UpcomingEmptyView.swift in Sources */, DECB84642C44655A0022A003 /* PlaceListCell.swift in Sources */, + 789D73BB2C47F1A100C7077D /* TardyService.swift in Sources */, DD931B762C3DC16100526452 /* PromiseInfoView.swift in Sources */, DEA89B832C45C5CA00AF4924 /* SelectMemberViewModel.swift in Sources */, DD3072242C3C0EB200416D9F /* MyPromiseReadyInfoRequestModel.swift in Sources */, @@ -1948,6 +1943,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = D2DRA3F792; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = KkuMulKum/Resource/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "꾸물꿈"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; @@ -1981,6 +1977,7 @@ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = D2DRA3F792; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = KkuMulKum/Resource/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "꾸물꿈"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; diff --git a/KkuMulKum/Application/AppDelegate.swift b/KkuMulKum/Application/AppDelegate.swift index 24d74838..975e12a8 100644 --- a/KkuMulKum/Application/AppDelegate.swift +++ b/KkuMulKum/Application/AppDelegate.swift @@ -77,6 +77,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { extension AppDelegate: MessagingDelegate, UNUserNotificationCenterDelegate { + static func getFCMToken() -> String? { + return UserDefaults.standard.string(forKey: "FCMToken") + } + func setupFirebase(application: UIApplication) { FirebaseApp.configure() FirebaseConfiguration.shared.setLoggerLevel(.min) @@ -104,6 +108,7 @@ extension AppDelegate: MessagingDelegate, UNUserNotificationCenterDelegate { } } + func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { print("Firebase registration token: \(String(describing: fcmToken))") diff --git a/KkuMulKum/Network/DTO/TargetType/LoginTargetType.swift b/KkuMulKum/Network/DTO/TargetType/LoginTargetType.swift index 0ea276f7..a4d20196 100644 --- a/KkuMulKum/Network/DTO/TargetType/LoginTargetType.swift +++ b/KkuMulKum/Network/DTO/TargetType/LoginTargetType.swift @@ -4,7 +4,6 @@ // // Created by 이지훈 on 7/15/24. // - import Foundation import Moya @@ -16,7 +15,6 @@ enum LoginTargetType { } extension LoginTargetType: TargetType { - var method: Moya.Method { .post } @@ -42,15 +40,9 @@ extension LoginTargetType: TargetType { var task: Task { switch self { case let .appleLogin(_, fcmToken): - return .requestParameters( - parameters: ["provider": "APPLE", "fcmToken": fcmToken], - encoding: JSONEncoding.default - ) + return .requestJSONEncodable(SocialLoginRequestModel(provider: "APPLE", fcmToken: fcmToken)) case let .kakaoLogin(_, fcmToken): - return .requestParameters( - parameters: ["provider": "KAKAO", "fcmToken": fcmToken], - encoding: JSONEncoding.default - ) + return .requestJSONEncodable(SocialLoginRequestModel(provider: "KAKAO", fcmToken: fcmToken)) case .refreshToken: return .requestPlain } @@ -67,4 +59,3 @@ extension LoginTargetType: TargetType { } } } - diff --git a/KkuMulKum/Resource/Service/HomeService.swift b/KkuMulKum/Resource/Service/HomeService.swift index 2ccbb036..2956a94d 100644 --- a/KkuMulKum/Resource/Service/HomeService.swift +++ b/KkuMulKum/Resource/Service/HomeService.swift @@ -34,7 +34,7 @@ final class MockHomeService: HomeServiceType { let mockData = ResponseBodyDTO( success: true, data: NearestPromiseModel( - id: 1, + promiseID: 1, dDay: 0, name: "꾸물이들 대환장 파티", meetingName: "꾸물이들", @@ -54,7 +54,7 @@ final class MockHomeService: HomeServiceType { data: UpcomingPromiseListModel( promises: [ UpcomingPromise( - id: 1, + promiseID: 1, dDay: 1, name: "누가 코코볼 다 먹었어?", meetingName: "우마우스", @@ -64,7 +64,7 @@ final class MockHomeService: HomeServiceType { placeName: "가자하우스" ), UpcomingPromise( - id: 2, + promiseID: 2, dDay: 1, name: "누가 코코볼 다 먹었어?", meetingName: "우마우스", @@ -74,7 +74,7 @@ final class MockHomeService: HomeServiceType { placeName: "가자하우스" ), UpcomingPromise( - id: 3, + promiseID: 3, dDay: 1, name: "누가 코코볼 다 먹었어?", meetingName: "우마우스", @@ -84,7 +84,7 @@ final class MockHomeService: HomeServiceType { placeName: "가자하우스" ), UpcomingPromise( - id: 4, + promiseID: 4, dDay: 1, name: "누가 코코볼 다 먹었어?", meetingName: "우마우스", diff --git a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift index d64a9068..66ba0637 100644 --- a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift +++ b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift @@ -60,6 +60,12 @@ class LoginViewModel: NSObject { } } } + + private func getFCMToken() -> String { + let fcmToken = UserDefaults.standard.string(forKey: "FCMToken") ?? "fcm_token_not_available" + print("FCM Token: \(fcmToken)") + return fcmToken + } private func handleKakaoLoginResult(oauthToken: OAuthToken?, error: Error?) { if let error = error { @@ -70,28 +76,26 @@ class LoginViewModel: NSObject { if let token = oauthToken?.accessToken { print("Kakao Login Successful, access token: \(token)") - loginToServer(with: .kakaoLogin(accessToken: token, fcmToken: "dummy_fcm_token")) + let fcmToken = getFCMToken() + loginToServer(with: .kakaoLogin(accessToken: token, fcmToken: fcmToken)) } else { print("Kakao Login Error: No access token") self.error.value = "No access token received" } } - + private func loginToServer(with loginTarget: LoginTargetType) { provider.request(loginTarget) { [weak self] result in switch result { case .success(let response): print("Received response from server: \(response)") + print("Response body: \(String(data: response.data, encoding: .utf8) ?? "")") do { let loginResponse = try response.map( ResponseBodyDTO.self ) - print( - "Successfully mapped response: \(loginResponse)" - ) - self?.handleLoginResponse( - loginResponse - ) + print("Successfully mapped response: \(loginResponse)") + self?.handleLoginResponse(loginResponse) } catch { print("Failed to decode response: \(error)") self?.error.value = "Failed to decode response: \(error.localizedDescription)" @@ -103,7 +107,7 @@ class LoginViewModel: NSObject { } } } - + private func handleLoginResponse(_ response: ResponseBodyDTO) { print("Handling login response") if response.success { @@ -150,29 +154,23 @@ class LoginViewModel: NSObject { } } -extension LoginViewModel: ASAuthorizationControllerDelegate, +extension LoginViewModel: ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding { func authorizationController( controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization ) { - print( - "Apple authorization completed" - ) + print("Apple authorization completed") guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential, let identityToken = appleIDCredential.identityToken, - let tokenString = String( - data: identityToken, - encoding: .utf8 - ) else { - print( - "Failed to get Apple ID Credential or identity token" - ) + let tokenString = String(data: identityToken, encoding: .utf8) else { + print("Failed to get Apple ID Credential or identity token") return } print("Apple Login Successful, identity token: \(tokenString)") - loginToServer(with: .appleLogin(identityToken: tokenString, fcmToken: "dummy_fcm_token")) + let fcmToken = getFCMToken() + loginToServer(with: .appleLogin(identityToken: tokenString, fcmToken: fcmToken)) } func authorizationController( diff --git a/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift b/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift index d8430bf5..0173fffd 100644 --- a/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift +++ b/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift @@ -65,7 +65,7 @@ class LoginViewController: BaseViewController { print("Login State: Not logged in") case .login: print("Login State: Logged in with user info: ") - owner.navigateToOnboardingScreen() + owner.navigateToMainScreen() case .needOnboarding: print("Login State: Need onboarding") owner.navigateToOnboardingScreen() From db202253813bfebe57d5b7bf457c470f93a6a927 Mon Sep 17 00:00:00 2001 From: hooni Date: Wed, 17 Jul 2024 22:14:18 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat/#205=20FCM=20=EC=95=8C=EB=9E=8C=20get?= =?UTF-8?q?=EB=B0=9B=EC=95=84=EC=98=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KkuMulKum/Application/AppDelegate.swift | 2 + .../Login/VIewModel/LoginViewModel.swift | 63 +++++++++++-------- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/KkuMulKum/Application/AppDelegate.swift b/KkuMulKum/Application/AppDelegate.swift index 975e12a8..ae044024 100644 --- a/KkuMulKum/Application/AppDelegate.swift +++ b/KkuMulKum/Application/AppDelegate.swift @@ -114,6 +114,8 @@ extension AppDelegate: MessagingDelegate, UNUserNotificationCenterDelegate { if let token = fcmToken { UserDefaults.standard.set(token, forKey: "FCMToken") + UserDefaults.standard.synchronize() + print("New FCM token saved: \(token)") } } diff --git a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift index 66ba0637..6bffed7b 100644 --- a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift +++ b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift @@ -11,6 +11,7 @@ import AuthenticationServices import KakaoSDKUser import KakaoSDKAuth import Moya +import FirebaseMessaging enum LoginState { case notLogin @@ -60,11 +61,21 @@ class LoginViewModel: NSObject { } } } - - private func getFCMToken() -> String { - let fcmToken = UserDefaults.standard.string(forKey: "FCMToken") ?? "fcm_token_not_available" - print("FCM Token: \(fcmToken)") - return fcmToken + + private func getFCMToken(completion: @escaping (String) -> Void) { + Messaging.messaging().token { token, error in + if let error = error { + print("Error fetching FCM registration token: \(error)") + completion("fcm_token_not_available") + } else if let token = token { + print("Current FCM Token: \(token)") + UserDefaults.standard.set(token, forKey: "FCMToken") + UserDefaults.standard.synchronize() + completion(token) + } else { + completion("fcm_token_not_available") + } + } } private func handleKakaoLoginResult(oauthToken: OAuthToken?, error: Error?) { @@ -76,14 +87,15 @@ class LoginViewModel: NSObject { if let token = oauthToken?.accessToken { print("Kakao Login Successful, access token: \(token)") - let fcmToken = getFCMToken() - loginToServer(with: .kakaoLogin(accessToken: token, fcmToken: fcmToken)) + getFCMToken { [weak self] fcmToken in + self?.loginToServer(with: .kakaoLogin(accessToken: token, fcmToken: fcmToken)) + } } else { print("Kakao Login Error: No access token") self.error.value = "No access token received" } } - + private func loginToServer(with loginTarget: LoginTargetType) { provider.request(loginTarget) { [weak self] result in switch result { @@ -107,7 +119,7 @@ class LoginViewModel: NSObject { } } } - + private func handleLoginResponse(_ response: ResponseBodyDTO) { print("Handling login response") if response.success { @@ -155,23 +167,24 @@ class LoginViewModel: NSObject { } extension LoginViewModel: ASAuthorizationControllerDelegate, - ASAuthorizationControllerPresentationContextProviding { + ASAuthorizationControllerPresentationContextProviding { func authorizationController( - controller: ASAuthorizationController, - didCompleteWithAuthorization authorization: ASAuthorization - ) { - print("Apple authorization completed") - guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential, - let identityToken = appleIDCredential.identityToken, - let tokenString = String(data: identityToken, encoding: .utf8) else { - print("Failed to get Apple ID Credential or identity token") - return - } + controller: ASAuthorizationController, + didCompleteWithAuthorization authorization: ASAuthorization + ) { + print("Apple authorization completed") + guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential, + let identityToken = appleIDCredential.identityToken, + let tokenString = String(data: identityToken, encoding: .utf8) else { + print("Failed to get Apple ID Credential or identity token") + return + } - print("Apple Login Successful, identity token: \(tokenString)") - let fcmToken = getFCMToken() - loginToServer(with: .appleLogin(identityToken: tokenString, fcmToken: fcmToken)) - } + print("Apple Login Successful, identity token: \(tokenString)") + getFCMToken { [weak self] fcmToken in + self?.loginToServer(with: .appleLogin(identityToken: tokenString, fcmToken: fcmToken)) + } + } func authorizationController( controller: ASAuthorizationController, @@ -182,7 +195,7 @@ extension LoginViewModel: ASAuthorizationControllerDelegate, ) self.error.value = error.localizedDescription } - + func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { print("Providing presentation anchor for Apple Login") let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene From 73c3cf9cde95d3dbf8a6da58ec5b9cd38cbc8c8f Mon Sep 17 00:00:00 2001 From: hooni Date: Wed, 17 Jul 2024 23:09:05 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat/#205=20=EC=97=91=EC=84=B8=EC=8A=A4=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=EB=94=B0=EB=9D=BC=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EB=B6=84=EA=B8=B0=20=EC=84=B1=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KkuMulKum.xcodeproj/project.pbxproj | 12 ++ KkuMulKum/Application/AppDelegate.swift | 7 -- KkuMulKum/Application/SceneDelegate.swift | 44 +++++-- .../Source/Core/Auth/AuthInterceptor.swift | 69 +++++++++++ .../Login/VIewModel/LoginViewModel.swift | 114 +++++++++++------- 5 files changed, 185 insertions(+), 61 deletions(-) create mode 100644 KkuMulKum/Source/Core/Auth/AuthInterceptor.swift diff --git a/KkuMulKum.xcodeproj/project.pbxproj b/KkuMulKum.xcodeproj/project.pbxproj index 434d36c6..635fa4ea 100644 --- a/KkuMulKum.xcodeproj/project.pbxproj +++ b/KkuMulKum.xcodeproj/project.pbxproj @@ -57,6 +57,7 @@ 789D73AF2C46D99B00C7077D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 789D73AE2C46D99B00C7077D /* GoogleService-Info.plist */; }; 789D73B32C47CC6D00C7077D /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73B22C47CC6D00C7077D /* LocalNotificationManager.swift */; }; 789D73BB2C47F1A100C7077D /* TardyService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73BA2C47F1A100C7077D /* TardyService.swift */; }; + 789D73BE2C47FE0F00C7077D /* AuthInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73BD2C47FE0F00C7077D /* AuthInterceptor.swift */; }; 78AED1342C3D951F000AD80A /* NicknameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AED1332C3D951F000AD80A /* NicknameViewController.swift */; }; 78AED1372C3D98D1000AD80A /* NicknameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AED1362C3D98D1000AD80A /* NicknameView.swift */; }; 78B9286C2C29402C006D9942 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78B9286B2C29402C006D9942 /* AppDelegate.swift */; }; @@ -238,6 +239,7 @@ 789D73B02C46DACD00C7077D /* KkuMulKum.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KkuMulKum.entitlements; sourceTree = ""; }; 789D73B22C47CC6D00C7077D /* LocalNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationManager.swift; sourceTree = ""; }; 789D73BA2C47F1A100C7077D /* TardyService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TardyService.swift; sourceTree = ""; }; + 789D73BD2C47FE0F00C7077D /* AuthInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthInterceptor.swift; sourceTree = ""; }; 78AED1332C3D951F000AD80A /* NicknameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameViewController.swift; sourceTree = ""; }; 78AED1362C3D98D1000AD80A /* NicknameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameView.swift; sourceTree = ""; }; 78B928682C29402C006D9942 /* KkuMulKum.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KkuMulKum.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -524,6 +526,14 @@ path = Notification; sourceTree = ""; }; + 789D73BC2C47FDEE00C7077D /* Auth */ = { + isa = PBXGroup; + children = ( + 789D73BD2C47FE0F00C7077D /* AuthInterceptor.swift */, + ); + path = Auth; + sourceTree = ""; + }; 78AED1322C3D9514000AD80A /* Nickname */ = { isa = PBXGroup; children = ( @@ -1026,6 +1036,7 @@ DDA2EE7E2C3860B2007C6059 /* Core */ = { isa = PBXGroup; children = ( + 789D73BC2C47FDEE00C7077D /* Auth */, DDA2EE722C385EB9007C6059 /* MainTabBarController.swift */, ); path = Core; @@ -1695,6 +1706,7 @@ DE32D1D42C3BFB56006848DF /* UpdateProfileNameModel.swift in Sources */, DE6D4D132C3F14D80005584B /* MeetingMemberCell.swift in Sources */, DDAF1C932C3D6E3D008A37D3 /* PagePromiseViewController.swift in Sources */, + 789D73BE2C47FE0F00C7077D /* AuthInterceptor.swift in Sources */, 789D73B32C47CC6D00C7077D /* LocalNotificationManager.swift in Sources */, DE9E18922C3BCC9D00DB76B4 /* SocialLoginRequestModel.swift in Sources */, DE254AA82C3118EA00A4015E /* UIView+.swift in Sources */, diff --git a/KkuMulKum/Application/AppDelegate.swift b/KkuMulKum/Application/AppDelegate.swift index ae044024..24d74838 100644 --- a/KkuMulKum/Application/AppDelegate.swift +++ b/KkuMulKum/Application/AppDelegate.swift @@ -77,10 +77,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { extension AppDelegate: MessagingDelegate, UNUserNotificationCenterDelegate { - static func getFCMToken() -> String? { - return UserDefaults.standard.string(forKey: "FCMToken") - } - func setupFirebase(application: UIApplication) { FirebaseApp.configure() FirebaseConfiguration.shared.setLoggerLevel(.min) @@ -108,14 +104,11 @@ extension AppDelegate: MessagingDelegate, UNUserNotificationCenterDelegate { } } - func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { print("Firebase registration token: \(String(describing: fcmToken))") if let token = fcmToken { UserDefaults.standard.set(token, forKey: "FCMToken") - UserDefaults.standard.synchronize() - print("New FCM token saved: \(token)") } } diff --git a/KkuMulKum/Application/SceneDelegate.swift b/KkuMulKum/Application/SceneDelegate.swift index e9b59113..3b3bd9d4 100644 --- a/KkuMulKum/Application/SceneDelegate.swift +++ b/KkuMulKum/Application/SceneDelegate.swift @@ -6,10 +6,12 @@ // import UIKit + import KakaoSDKAuth class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? + let loginViewModel = LoginViewModel() func scene( _ scene: UIScene, @@ -18,8 +20,35 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { ) { guard let windowScene = (scene as? UIWindowScene) else { return } self.window = UIWindow(windowScene: windowScene) - self.window?.rootViewController = LoginViewController() - self.window?.makeKeyAndVisible() + performAutoLogin() + } + + private func performAutoLogin() { + loginViewModel.autoLogin { [weak self] success in + DispatchQueue.main.async { + if success { + self?.showMainScreen() + print("showMainScreen") + } else { + self?.showLoginScreen() + print("showLoginScreen") + } + } + } + } + + private func showMainScreen() { + let mainTabBarController = MainTabBarController() + let navigationController = UINavigationController(rootViewController: mainTabBarController) + navigationController.isNavigationBarHidden = true + window?.rootViewController = navigationController + window?.makeKeyAndVisible() + } + + private func showLoginScreen() { + let loginViewController = LoginViewController() + window?.rootViewController = loginViewController + window?.makeKeyAndVisible() } func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { @@ -30,17 +59,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } } - func application( - _ app: UIApplication, - open url: URL, - options: [UIApplication.OpenURLOptionsKey : Any] = [:] - ) -> Bool { - if (AuthApi.isKakaoTalkLoginUrl(url)) { - return AuthController.handleOpenUrl(url: url) - } - return false - } - func sceneDidDisconnect(_ scene: UIScene) {} func sceneDidBecomeActive(_ scene: UIScene) {} func sceneWillResignActive(_ scene: UIScene) {} diff --git a/KkuMulKum/Source/Core/Auth/AuthInterceptor.swift b/KkuMulKum/Source/Core/Auth/AuthInterceptor.swift new file mode 100644 index 00000000..f9f48d27 --- /dev/null +++ b/KkuMulKum/Source/Core/Auth/AuthInterceptor.swift @@ -0,0 +1,69 @@ +// +// AuthInterceptor.swift +// KkuMulKum +// +// Created by 이지훈 on 7/17/24. +// + +import Foundation + +import Moya +import Alamofire + +class AuthInterceptor: RequestInterceptor { + let authService: AuthServiceType + let provider: MoyaProvider + + init(authService: AuthServiceType, provider: MoyaProvider) { + self.authService = authService + self.provider = provider + } + + func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result) -> Void) { + guard let accessToken = authService.getAccessToken() else { + completion(.success(urlRequest)) + return + } + + var urlRequest = urlRequest + urlRequest.addValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") + completion(.success(urlRequest)) + } + + func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) { + guard let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 else { + completion(.doNotRetry) + return + } + + guard let refreshToken = authService.getRefreshToken() else { + completion(.doNotRetry) + return + } + + provider.request(.refreshToken(refreshToken: refreshToken)) { [weak self] result in + switch result { + case .success(let response): + do { + let reissueResponse = try response.map(ResponseBodyDTO.self) + if reissueResponse.success, let data = reissueResponse.data, + let newAccessToken = data.accessToken, + let newRefreshToken = data.refreshToken { + self?.authService.saveAccessToken(newAccessToken) + self?.authService.saveRefreshToken(newRefreshToken) + completion(.retry) + } else { + self?.authService.clearTokens() + completion(.doNotRetry) + } + } catch { + self?.authService.clearTokens() + completion(.doNotRetry) + } + case .failure: + self?.authService.clearTokens() + completion(.doNotRetry) + } + } + } +} diff --git a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift index 6bffed7b..a294f3b8 100644 --- a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift +++ b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift @@ -25,6 +25,8 @@ class LoginViewModel: NSObject { private let provider: MoyaProvider private var authService: AuthServiceType + private let authInterceptor: AuthInterceptor + init( provider: MoyaProvider = MoyaProvider( @@ -34,6 +36,7 @@ class LoginViewModel: NSObject { ) { self.provider = provider self.authService = authService + self.authInterceptor = AuthInterceptor(authService: authService, provider: provider) super.init() } @@ -62,6 +65,8 @@ class LoginViewModel: NSObject { } } + + private func getFCMToken(completion: @escaping (String) -> Void) { Messaging.messaging().token { token, error in if let error = error { @@ -121,32 +126,59 @@ class LoginViewModel: NSObject { } private func handleLoginResponse(_ response: ResponseBodyDTO) { - print("Handling login response") - if response.success { - if let data = response.data { - if data.name != nil { - print("Login successful") - loginState.value = .login - } else { - print("Login successful, but needs onboarding.") - loginState.value = .needOnboarding + print("Handling login response") + if response.success, let data = response.data { + saveTokens(accessToken: data.jwtTokenDTO.accessToken, refreshToken: data.jwtTokenDTO.refreshToken) + if data.name != nil { + print("Login successful") + loginState.value = .login + } else { + print("Login successful, but needs onboarding.") + loginState.value = .needOnboarding + } + } else { + if let error = response.error { + print("Login failed: \(error.message)") + self.error.value = error.message + } else { + print("Login failed: Unknown error") + self.error.value = "Unknown error occurred" + } + loginState.value = .notLogin + } + } + + func autoLogin(completion: @escaping (Bool) -> Void) { + guard let refreshToken = authService.getRefreshToken() else { + print("No refresh token found") + completion(false) + return + } + + provider.request(.refreshToken(refreshToken: refreshToken)) { [weak self] result in + switch result { + case .success(let response): + do { + let reissueResponse = try response.map(ResponseBodyDTO.self) + if reissueResponse.success, let data = reissueResponse.data, + let newAccessToken = data.accessToken, + let newRefreshToken = data.refreshToken { + self?.saveTokens(accessToken: newAccessToken, refreshToken: newRefreshToken) + completion(true) + } else { + print("Token refresh failed: \(reissueResponse.error?.message ?? "Unknown error")") + self?.authService.clearTokens() + completion(false) + } + } catch { + print("Token refresh failed: \(error)") + self?.authService.clearTokens() + completion(false) } - - saveTokens( - accessToken: data.jwtTokenDTO.accessToken, - refreshToken: data.jwtTokenDTO.refreshToken - ) - } else { - print("Warning: No data received in response") - error.value = "No data received" - } - } else { - if let error = response.error { - print("Login failed: \(error.message)") - self.error.value = error.message - } else { - print("Login failed: Unknown error") - self.error.value = "Unknown error occurred" + case .failure(let error): + print("Token refresh failed: \(error)") + self?.authService.clearTokens() + completion(false) } } } @@ -169,23 +201,23 @@ class LoginViewModel: NSObject { extension LoginViewModel: ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding { func authorizationController( - controller: ASAuthorizationController, - didCompleteWithAuthorization authorization: ASAuthorization - ) { - print("Apple authorization completed") - guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential, - let identityToken = appleIDCredential.identityToken, - let tokenString = String(data: identityToken, encoding: .utf8) else { - print("Failed to get Apple ID Credential or identity token") - return - } - - print("Apple Login Successful, identity token: \(tokenString)") - getFCMToken { [weak self] fcmToken in - self?.loginToServer(with: .appleLogin(identityToken: tokenString, fcmToken: fcmToken)) - } + controller: ASAuthorizationController, + didCompleteWithAuthorization authorization: ASAuthorization + ) { + print("Apple authorization completed") + guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential, + let identityToken = appleIDCredential.identityToken, + let tokenString = String(data: identityToken, encoding: .utf8) else { + print("Failed to get Apple ID Credential or identity token") + return } - + + print("Apple Login Successful, identity token: \(tokenString)") + getFCMToken { [weak self] fcmToken in + self?.loginToServer(with: .appleLogin(identityToken: tokenString, fcmToken: fcmToken)) + } + } + func authorizationController( controller: ASAuthorizationController, didCompleteWithError error: Error From 0fda755aa09cd0b2a0ac632d4e63d0ae64e4390b Mon Sep 17 00:00:00 2001 From: hooni Date: Thu, 18 Jul 2024 02:54:09 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat/#205=20LunchScreen=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=ED=99=94=EB=A9=B4=20=EB=B6=88=EB=9F=AC=EC=98=A4=EA=B8=B0=20?= =?UTF-8?q?=EB=8F=99=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sleep(1) 삭제 화면 초기화 이후 런치스크린에서 메인 화면으로 이동 --- KkuMulKum/Application/SceneDelegate.swift | 69 ++++++++++++++++------- 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/KkuMulKum/Application/SceneDelegate.swift b/KkuMulKum/Application/SceneDelegate.swift index 3b3bd9d4..4da0c0df 100644 --- a/KkuMulKum/Application/SceneDelegate.swift +++ b/KkuMulKum/Application/SceneDelegate.swift @@ -20,43 +20,70 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { ) { guard let windowScene = (scene as? UIWindowScene) else { return } self.window = UIWindow(windowScene: windowScene) - performAutoLogin() + + let launchScreenStoryboard = UIStoryboard(name: "LaunchScreen", bundle: nil) + let launchScreenViewController = launchScreenStoryboard.instantiateInitialViewController() + + self.window?.rootViewController = launchScreenViewController + self.window?.makeKeyAndVisible() + + DispatchQueue.global(qos: .userInitiated).async { + self.loginViewModel.autoLogin { success in + DispatchQueue.main.async { + if success { + self.showMainScreen() + } else { + self.showLoginScreen() + } + } + } + } } - private func performAutoLogin() { - loginViewModel.autoLogin { [weak self] success in - DispatchQueue.main.async { - if success { - self?.showMainScreen() - print("showMainScreen") - } else { - self?.showLoginScreen() - print("showLoginScreen") - } + func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + if let url = URLContexts.first?.url { + if (AuthApi.isKakaoTalkLoginUrl(url)) { + _ = AuthController.handleOpenUrl(url: url) } } } + func application( + _ app: UIApplication, + open url: URL, + options: [UIApplication.OpenURLOptionsKey : Any] = [:] + ) -> Bool { + if (AuthApi.isKakaoTalkLoginUrl(url)) { + return AuthController.handleOpenUrl(url: url) + } + return false + } + private func showMainScreen() { let mainTabBarController = MainTabBarController() let navigationController = UINavigationController(rootViewController: mainTabBarController) navigationController.isNavigationBarHidden = true - window?.rootViewController = navigationController - window?.makeKeyAndVisible() + + animateRootViewControllerChange(to: navigationController) } private func showLoginScreen() { let loginViewController = LoginViewController() - window?.rootViewController = loginViewController - window?.makeKeyAndVisible() + animateRootViewControllerChange(to: loginViewController) } - func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { - if let url = URLContexts.first?.url { - if (AuthApi.isKakaoTalkLoginUrl(url)) { - _ = AuthController.handleOpenUrl(url: url) - } - } + private func animateRootViewControllerChange(to newRootViewController: UIViewController) { + guard let window = self.window else { return } + + UIView.transition(with: window, + duration: 0.3, + options: .transitionCrossDissolve, + animations: { + let oldState = UIView.areAnimationsEnabled + UIView.setAnimationsEnabled(false) + window.rootViewController = newRootViewController + UIView.setAnimationsEnabled(oldState) + }) } func sceneDidDisconnect(_ scene: UIScene) {} From 1ab9dd5768cbbeb8c1af9adc0ee34a46ddb9354f Mon Sep 17 00:00:00 2001 From: hooni Date: Thu, 18 Jul 2024 03:17:06 +0900 Subject: [PATCH 5/8] =?UTF-8?q?fix/#205=20=EC=BB=A8=EB=B2=A4=EC=85=98=20?= =?UTF-8?q?=EB=A7=9E=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/Core/Auth/AuthInterceptor.swift | 21 ++++++++++++++++--- .../Login/VIewModel/LoginViewModel.swift | 8 +++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/KkuMulKum/Source/Core/Auth/AuthInterceptor.swift b/KkuMulKum/Source/Core/Auth/AuthInterceptor.swift index f9f48d27..7c021d5c 100644 --- a/KkuMulKum/Source/Core/Auth/AuthInterceptor.swift +++ b/KkuMulKum/Source/Core/Auth/AuthInterceptor.swift @@ -19,7 +19,13 @@ class AuthInterceptor: RequestInterceptor { self.provider = provider } - func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result) -> Void) { + func adapt( + _ urlRequest: URLRequest, + for session: Session, + completion: @escaping ( + Result + ) -> Void + ) { guard let accessToken = authService.getAccessToken() else { completion(.success(urlRequest)) return @@ -30,9 +36,18 @@ class AuthInterceptor: RequestInterceptor { completion(.success(urlRequest)) } - func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) { + func retry( + _ request: Request, + for session: Session, + dueTo error: Error, + completion: @escaping ( + RetryResult + ) -> Void + ) { guard let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 else { - completion(.doNotRetry) + completion( + .doNotRetry + ) return } diff --git a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift index a294f3b8..2f60bb30 100644 --- a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift +++ b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift @@ -128,7 +128,10 @@ class LoginViewModel: NSObject { private func handleLoginResponse(_ response: ResponseBodyDTO) { print("Handling login response") if response.success, let data = response.data { - saveTokens(accessToken: data.jwtTokenDTO.accessToken, refreshToken: data.jwtTokenDTO.refreshToken) + saveTokens( + accessToken: data.jwtTokenDTO.accessToken, + refreshToken: data.jwtTokenDTO.refreshToken + ) if data.name != nil { print("Login successful") loginState.value = .login @@ -167,17 +170,14 @@ class LoginViewModel: NSObject { completion(true) } else { print("Token refresh failed: \(reissueResponse.error?.message ?? "Unknown error")") - self?.authService.clearTokens() completion(false) } } catch { print("Token refresh failed: \(error)") - self?.authService.clearTokens() completion(false) } case .failure(let error): print("Token refresh failed: \(error)") - self?.authService.clearTokens() completion(false) } } From f8ba66db38cf0451dd59d87dcab8aaf0f7ddb3ca Mon Sep 17 00:00:00 2001 From: hooni Date: Thu, 18 Jul 2024 05:30:08 +0900 Subject: [PATCH 6/8] =?UTF-8?q?fix/#205=20=EC=9E=90=EB=8F=99=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KkuMulKum.xcodeproj/project.pbxproj | 12 ++- KkuMulKum/Application/AppDelegate.swift | 1 - KkuMulKum/Application/SceneDelegate.swift | 15 ++-- .../Source/Core/Auth/AuthInterceptor.swift | 38 ++++----- .../Login/VIewModel/LoginViewModel.swift | 77 +++++++++++-------- .../ViewController/LoginViewController.swift | 12 +++ 6 files changed, 91 insertions(+), 64 deletions(-) diff --git a/KkuMulKum.xcodeproj/project.pbxproj b/KkuMulKum.xcodeproj/project.pbxproj index 6a6ffd54..550c471c 100644 --- a/KkuMulKum.xcodeproj/project.pbxproj +++ b/KkuMulKum.xcodeproj/project.pbxproj @@ -55,7 +55,6 @@ 789D73A72C46AF4900C7077D /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73A62C46AF4900C7077D /* KeychainService.swift */; }; 789D73AF2C46D99B00C7077D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 789D73AE2C46D99B00C7077D /* GoogleService-Info.plist */; }; 789D73B32C47CC6D00C7077D /* LocalNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73B22C47CC6D00C7077D /* LocalNotificationManager.swift */; }; - 789D73BB2C47F1A100C7077D /* TardyService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73BA2C47F1A100C7077D /* TardyService.swift */; }; 789D73BE2C47FE0F00C7077D /* AuthInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789D73BD2C47FE0F00C7077D /* AuthInterceptor.swift */; }; 78AED1342C3D951F000AD80A /* NicknameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AED1332C3D951F000AD80A /* NicknameViewController.swift */; }; 78AED1372C3D98D1000AD80A /* NicknameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78AED1362C3D98D1000AD80A /* NicknameView.swift */; }; @@ -544,6 +543,14 @@ path = Auth; sourceTree = ""; }; + 789D73C02C48441E00C7077D /* Recovered References */ = { + isa = PBXGroup; + children = ( + 789D73BA2C47F1A100C7077D /* TardyService.swift */, + ); + name = "Recovered References"; + sourceTree = ""; + }; 78AED1322C3D9514000AD80A /* Nickname */ = { isa = PBXGroup; children = ( @@ -575,6 +582,7 @@ children = ( 78B9286A2C29402C006D9942 /* KkuMulKum */, 78B928692C29402C006D9942 /* Products */, + 789D73C02C48441E00C7077D /* Recovered References */, ); sourceTree = ""; }; @@ -1734,7 +1742,6 @@ DD4393792C412F4500EC1799 /* JoinButtonView.swift in Sources */, DD3976832C41C2AD00E2A4C4 /* HomeViewModel.swift in Sources */, DD41BF012C41DE4F0095A068 /* TardyCollectionViewCell.swift in Sources */, - DE9E189A2C3BCCBE00DB76B4 /* PlaceModel.swift in Sources */, A3DD9C682C45C78300E58A13 /* HomeService.swift in Sources */, 782B407B2C3E395A008B0CA7 /* WelcomeView.swift in Sources */, DD8626612C4606A300E4F980 /* SetReadyInfoViewModel.swift in Sources */, @@ -1792,7 +1799,6 @@ A39F2B192C47BF83008DA5F5 /* SetReadyCompletedView.swift in Sources */, DD3976882C41C2AD00E2A4C4 /* UpcomingEmptyView.swift in Sources */, DECB84642C44655A0022A003 /* PlaceListCell.swift in Sources */, - 789D73BB2C47F1A100C7077D /* TardyService.swift in Sources */, DD931B762C3DC16100526452 /* PromiseInfoView.swift in Sources */, DEA89B832C45C5CA00AF4924 /* SelectMemberViewModel.swift in Sources */, DD3072242C3C0EB200416D9F /* MyPromiseReadyInfoRequestModel.swift in Sources */, diff --git a/KkuMulKum/Application/AppDelegate.swift b/KkuMulKum/Application/AppDelegate.swift index 24d74838..467001bc 100644 --- a/KkuMulKum/Application/AppDelegate.swift +++ b/KkuMulKum/Application/AppDelegate.swift @@ -19,7 +19,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - sleep(1) //런치스크린 작동용 // KakaoSDK 초기화 과정에서 앱 키를 동적으로 불러오기 if let kakaoAppKey = fetchKakaoAppKeyFromPrivacyInfo() { KakaoSDK.initSDK(appKey: kakaoAppKey) diff --git a/KkuMulKum/Application/SceneDelegate.swift b/KkuMulKum/Application/SceneDelegate.swift index 4da0c0df..bd9b87d7 100644 --- a/KkuMulKum/Application/SceneDelegate.swift +++ b/KkuMulKum/Application/SceneDelegate.swift @@ -27,18 +27,23 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { self.window?.rootViewController = launchScreenViewController self.window?.makeKeyAndVisible() - DispatchQueue.global(qos: .userInitiated).async { - self.loginViewModel.autoLogin { success in + performAutoLogin() + } + + private func performAutoLogin() { + print("Performing auto login") + loginViewModel.autoLogin { [weak self] success in DispatchQueue.main.async { if success { - self.showMainScreen() + print("Auto login successful, showing main screen") + self?.showMainScreen() } else { - self.showLoginScreen() + print("Auto login failed, showing login screen") + self?.showLoginScreen() } } } } - } func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { if let url = URLContexts.first?.url { diff --git a/KkuMulKum/Source/Core/Auth/AuthInterceptor.swift b/KkuMulKum/Source/Core/Auth/AuthInterceptor.swift index 7c021d5c..51e18711 100644 --- a/KkuMulKum/Source/Core/Auth/AuthInterceptor.swift +++ b/KkuMulKum/Source/Core/Auth/AuthInterceptor.swift @@ -10,6 +10,10 @@ import Foundation import Moya import Alamofire +enum AuthError: Error { + case tokenRefreshFailed +} + class AuthInterceptor: RequestInterceptor { let authService: AuthServiceType let provider: MoyaProvider @@ -19,13 +23,7 @@ class AuthInterceptor: RequestInterceptor { self.provider = provider } - func adapt( - _ urlRequest: URLRequest, - for session: Session, - completion: @escaping ( - Result - ) -> Void - ) { + func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result) -> Void) { guard let accessToken = authService.getAccessToken() else { completion(.success(urlRequest)) return @@ -36,22 +34,14 @@ class AuthInterceptor: RequestInterceptor { completion(.success(urlRequest)) } - func retry( - _ request: Request, - for session: Session, - dueTo error: Error, - completion: @escaping ( - RetryResult - ) -> Void - ) { + func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) { guard let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 else { - completion( - .doNotRetry - ) + completion(.doNotRetry) return } guard let refreshToken = authService.getRefreshToken() else { + authService.clearTokens() completion(.doNotRetry) return } @@ -61,21 +51,25 @@ class AuthInterceptor: RequestInterceptor { case .success(let response): do { let reissueResponse = try response.map(ResponseBodyDTO.self) - if reissueResponse.success, let data = reissueResponse.data, - let newAccessToken = data.accessToken, - let newRefreshToken = data.refreshToken { + if reissueResponse.success, let data = reissueResponse.data { + let newAccessToken = data.accessToken + let newRefreshToken = data.refreshToken self?.authService.saveAccessToken(newAccessToken) self?.authService.saveRefreshToken(newRefreshToken) + print("Token refreshed successfully in interceptor") completion(.retry) } else { + print("Token refresh failed in interceptor: \(reissueResponse.error?.message ?? "Unknown error")") self?.authService.clearTokens() completion(.doNotRetry) } } catch { + print("Token refresh failed in interceptor: \(error)") self?.authService.clearTokens() completion(.doNotRetry) } - case .failure: + case .failure(let error): + print("Network error during token refresh in interceptor: \(error)") self?.authService.clearTokens() completion(.doNotRetry) } diff --git a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift index 2f60bb30..e34b6209 100644 --- a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift +++ b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift @@ -27,7 +27,6 @@ class LoginViewModel: NSObject { private var authService: AuthServiceType private let authInterceptor: AuthInterceptor - init( provider: MoyaProvider = MoyaProvider( plugins: [NetworkLoggerPlugin(configuration: .init(logOptions: .verbose))] @@ -65,8 +64,6 @@ class LoginViewModel: NSObject { } } - - private func getFCMToken(completion: @escaping (String) -> Void) { Messaging.messaging().token { token, error in if let error = error { @@ -152,36 +149,50 @@ class LoginViewModel: NSObject { } func autoLogin(completion: @escaping (Bool) -> Void) { - guard let refreshToken = authService.getRefreshToken() else { - print("No refresh token found") - completion(false) - return - } - - provider.request(.refreshToken(refreshToken: refreshToken)) { [weak self] result in - switch result { - case .success(let response): - do { - let reissueResponse = try response.map(ResponseBodyDTO.self) - if reissueResponse.success, let data = reissueResponse.data, - let newAccessToken = data.accessToken, - let newRefreshToken = data.refreshToken { - self?.saveTokens(accessToken: newAccessToken, refreshToken: newRefreshToken) - completion(true) - } else { - print("Token refresh failed: \(reissueResponse.error?.message ?? "Unknown error")") - completion(false) - } - } catch { - print("Token refresh failed: \(error)") - completion(false) - } - case .failure(let error): - print("Token refresh failed: \(error)") - completion(false) - } - } - } + guard let refreshToken = authService.getRefreshToken() else { + print("No refresh token found") + loginState.value = .notLogin + completion(false) + return + } + + print("Attempting auto login with refresh token") + provider.request(.refreshToken(refreshToken: refreshToken)) { [weak self] result in + switch result { + case .success(let response): + do { + let reissueResponse = try response.map(ResponseBodyDTO.self) + if reissueResponse.success, let data = reissueResponse.data { + let newAccessToken = data.accessToken + let newRefreshToken = data.refreshToken + self?.saveTokens(accessToken: newAccessToken, refreshToken: newRefreshToken) + self?.loginState.value = .login + print("Auto login successful") + completion(true) + } else { + print("Token refresh failed: \(reissueResponse.error?.message ?? "Unknown error")") + self?.clearTokensAndHandleError() + completion(false) + } + } catch { + print("Token refresh failed: \(error)") + self?.clearTokensAndHandleError() + completion(false) + } + case .failure(let error): + print("Network error during auto login: \(error)") + self?.clearTokensAndHandleError() + completion(false) + } + } + } + + private func clearTokensAndHandleError() { + authService.clearTokens() + loginState.value = .notLogin + error.value = "자동 로그인 실패. 다시 로그인해주세요." + print("Tokens cleared, login state set to notLogin") + } private func saveTokens(accessToken: String, refreshToken: String) { print("Attempting to save tokens") diff --git a/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift b/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift index 0173fffd..074f30d0 100644 --- a/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift +++ b/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift @@ -63,6 +63,7 @@ class LoginViewController: BaseViewController { switch state { case .notLogin: print("Login State: Not logged in") + owner.showLoginScreen() case .login: print("Login State: Logged in with user info: ") owner.navigateToMainScreen() @@ -79,6 +80,17 @@ class LoginViewController: BaseViewController { } } } + + private func showLoginScreen() { + DispatchQueue.main.async { + // 현재 화면이 이미 LoginViewController라면 아무것도 하지 않습니다. + if !(self.presentedViewController is LoginViewController) { + let loginViewController = LoginViewController() + loginViewController.modalPresentationStyle = .fullScreen + self.present(loginViewController, animated: true, completion: nil) + } + } + } @objc private func appleLoginTapped() { loginViewModel.performAppleLogin(presentationAnchor: view.window!) From 9464cbd6bee44b831528fb419d78a8f584b0443e Mon Sep 17 00:00:00 2001 From: hooni Date: Thu, 18 Jul 2024 05:42:49 +0900 Subject: [PATCH 7/8] =?UTF-8?q?fix/#205=20=EC=BD=94=EB=93=9C=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KkuMulKum.xcodeproj/project.pbxproj | 10 ---------- .../Login/ViewController/LoginViewController.swift | 13 +------------ 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/KkuMulKum.xcodeproj/project.pbxproj b/KkuMulKum.xcodeproj/project.pbxproj index 1072371a..36e4a98a 100644 --- a/KkuMulKum.xcodeproj/project.pbxproj +++ b/KkuMulKum.xcodeproj/project.pbxproj @@ -246,7 +246,6 @@ 789D73AE2C46D99B00C7077D /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 789D73B02C46DACD00C7077D /* KkuMulKum.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KkuMulKum.entitlements; sourceTree = ""; }; 789D73B22C47CC6D00C7077D /* LocalNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationManager.swift; sourceTree = ""; }; - 789D73BA2C47F1A100C7077D /* TardyService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TardyService.swift; sourceTree = ""; }; 789D73BD2C47FE0F00C7077D /* AuthInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthInterceptor.swift; sourceTree = ""; }; 78AED1332C3D951F000AD80A /* NicknameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameViewController.swift; sourceTree = ""; }; 78AED1362C3D98D1000AD80A /* NicknameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameView.swift; sourceTree = ""; }; @@ -553,14 +552,6 @@ path = Auth; sourceTree = ""; }; - 789D73C02C48441E00C7077D /* Recovered References */ = { - isa = PBXGroup; - children = ( - 789D73BA2C47F1A100C7077D /* TardyService.swift */, - ); - name = "Recovered References"; - sourceTree = ""; - }; 78AED1322C3D9514000AD80A /* Nickname */ = { isa = PBXGroup; children = ( @@ -592,7 +583,6 @@ children = ( 78B9286A2C29402C006D9942 /* KkuMulKum */, 78B928692C29402C006D9942 /* Products */, - 789D73C02C48441E00C7077D /* Recovered References */, ); sourceTree = ""; }; diff --git a/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift b/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift index 074f30d0..e26a0d17 100644 --- a/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift +++ b/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift @@ -63,7 +63,6 @@ class LoginViewController: BaseViewController { switch state { case .notLogin: print("Login State: Not logged in") - owner.showLoginScreen() case .login: print("Login State: Logged in with user info: ") owner.navigateToMainScreen() @@ -81,17 +80,7 @@ class LoginViewController: BaseViewController { } } - private func showLoginScreen() { - DispatchQueue.main.async { - // 현재 화면이 이미 LoginViewController라면 아무것도 하지 않습니다. - if !(self.presentedViewController is LoginViewController) { - let loginViewController = LoginViewController() - loginViewController.modalPresentationStyle = .fullScreen - self.present(loginViewController, animated: true, completion: nil) - } - } - } - + @objc private func appleLoginTapped() { loginViewModel.performAppleLogin(presentationAnchor: view.window!) } From a87ad304bd060f8640615950b1ed9cd4d4f6e1b1 Mon Sep 17 00:00:00 2001 From: hooni Date: Thu, 18 Jul 2024 05:47:08 +0900 Subject: [PATCH 8/8] =?UTF-8?q?refactor/#205=20=EB=8D=94=EB=AF=B8=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KkuMulKum/Application/SceneDelegate.swift | 2 +- .../Onboarding/Login/View/LoginView.swift | 18 ++---------------- .../ViewController/LoginViewController.swift | 6 ------ 3 files changed, 3 insertions(+), 23 deletions(-) diff --git a/KkuMulKum/Application/SceneDelegate.swift b/KkuMulKum/Application/SceneDelegate.swift index bd9b87d7..2af0dffc 100644 --- a/KkuMulKum/Application/SceneDelegate.swift +++ b/KkuMulKum/Application/SceneDelegate.swift @@ -36,7 +36,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { DispatchQueue.main.async { if success { print("Auto login successful, showing main screen") - self?.showMainScreen() + self?.showLoginScreen() } else { print("Auto login failed, showing login screen") self?.showLoginScreen() diff --git a/KkuMulKum/Source/Onboarding/Login/View/LoginView.swift b/KkuMulKum/Source/Onboarding/Login/View/LoginView.swift index 78f438b0..c130e98f 100644 --- a/KkuMulKum/Source/Onboarding/Login/View/LoginView.swift +++ b/KkuMulKum/Source/Onboarding/Login/View/LoginView.swift @@ -17,14 +17,6 @@ class LoginView: BaseView { $0.image = .imgLogin } - // TODO: 서버 연결후 삭제예정 - let dummyNextButton = UIButton().then { - $0.setTitle("다음 화면으로 (서버연결후 삭제예정)", for: .normal) - $0.setTitleColor(.white, for: .normal) - $0.backgroundColor = .blue - $0.layer.cornerRadius = 8 - } - let appleLoginImageView = UIImageView().then { $0.contentMode = .scaleAspectFit $0.image = UIImage(named: "appleLogin") @@ -38,7 +30,7 @@ class LoginView: BaseView { } override func setupView() { - addSubviews(backgroundImageView, appleLoginImageView, kakaoLoginImageView, dummyNextButton) + addSubviews(backgroundImageView, appleLoginImageView, kakaoLoginImageView) } override func setupAutoLayout() { @@ -59,12 +51,6 @@ class LoginView: BaseView { $0.horizontalEdges.equalToSuperview().inset(14) $0.height.equalTo(Screen.height(54)) } - - dummyNextButton.snp.makeConstraints { - $0.centerX.equalToSuperview() - $0.top.equalTo(kakaoLoginImageView.snp.bottom).offset(20) - $0.width.equalTo(200) - $0.height.equalTo(44) - } + } } diff --git a/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift b/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift index e26a0d17..5c80efc1 100644 --- a/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift +++ b/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift @@ -50,12 +50,6 @@ class LoginViewController: BaseViewController { ) ) loginView.kakaoLoginImageView.addGestureRecognizer(kakaoTapGesture) - - loginView.dummyNextButton.addTarget( - self, - action: #selector(dummyNextButtonTapped), - for: .touchUpInside - ) } private func bindViewModel() {