From e876c08a773e583be9fb2f61068f1cedfe984546 Mon Sep 17 00:00:00 2001 From: hooni Date: Fri, 19 Jul 2024 23:14:40 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix/=EC=9E=90=EB=8F=99=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KkuMulKum/Application/SceneDelegate.swift | 29 ++- .../Model/Auth/SocialLoginResponseModel.swift | 10 + KkuMulKum/Network/Service/AuthService.swift | 2 - .../TargetType/Auth/AuthTargetType.swift | 11 +- .../Login/VIewModel/LoginViewModel.swift | 186 ++++++++++-------- .../ViewController/LoginViewController.swift | 34 ++-- .../ViewModel/ProfileSetupViewModel.swift | 48 +---- 7 files changed, 158 insertions(+), 162 deletions(-) diff --git a/KkuMulKum/Application/SceneDelegate.swift b/KkuMulKum/Application/SceneDelegate.swift index 7514337d..1b3c8a38 100644 --- a/KkuMulKum/Application/SceneDelegate.swift +++ b/KkuMulKum/Application/SceneDelegate.swift @@ -6,18 +6,13 @@ // import UIKit - import KakaoSDKAuth class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? let loginViewModel = LoginViewModel() - func scene( - _ scene: UIScene, - willConnectTo session: UISceneSession, - options connectionOptions: UIScene.ConnectionOptions - ) { + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } self.window = UIWindow(windowScene: windowScene) @@ -31,19 +26,19 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } private func performAutoLogin() { - print("Performing auto login") - loginViewModel.autoLogin { [weak self] success in - DispatchQueue.main.async { - if success { - print("Auto login successful, showing main screen") - self?.showMainScreen() - } else { - print("Auto login failed, showing login screen") - self?.showLoginScreen() - } + print("Performing auto login") + loginViewModel.autoLogin { [weak self] success in + DispatchQueue.main.async { + if success { + print("Auto login successful, showing main screen") + self?.showMainScreen() + } else { + print("Auto login failed, showing login screen") + self?.showLoginScreen() } } } + } func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { if let url = URLContexts.first?.url { @@ -73,7 +68,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } private func showLoginScreen() { - let loginViewController = LoginViewController() + let loginViewController = LoginViewController(viewModel: loginViewModel) animateRootViewControllerChange(to: loginViewController) } diff --git a/KkuMulKum/Network/DTO/Model/Auth/SocialLoginResponseModel.swift b/KkuMulKum/Network/DTO/Model/Auth/SocialLoginResponseModel.swift index b7c4c2f4..efc74846 100644 --- a/KkuMulKum/Network/DTO/Model/Auth/SocialLoginResponseModel.swift +++ b/KkuMulKum/Network/DTO/Model/Auth/SocialLoginResponseModel.swift @@ -27,3 +27,13 @@ struct RefreshTokenResponseModel: ResponseModelType { let accessToken: String let refreshToken: String } + +struct UserInfoModel: ResponseModelType { + let userId: Int + let name: String + let level: Int + let promiseCount: Int + let tardyCount: Int + let tardySum: Int + let profileImg: String? +} diff --git a/KkuMulKum/Network/Service/AuthService.swift b/KkuMulKum/Network/Service/AuthService.swift index 8f422767..10220178 100644 --- a/KkuMulKum/Network/Service/AuthService.swift +++ b/KkuMulKum/Network/Service/AuthService.swift @@ -4,9 +4,7 @@ // // Created by 이지훈 on 7/14/24. // - import Foundation - import Moya protocol AuthServiceType { diff --git a/KkuMulKum/Network/TargetType/Auth/AuthTargetType.swift b/KkuMulKum/Network/TargetType/Auth/AuthTargetType.swift index 589fadb8..2dfd76be 100644 --- a/KkuMulKum/Network/TargetType/Auth/AuthTargetType.swift +++ b/KkuMulKum/Network/TargetType/Auth/AuthTargetType.swift @@ -4,9 +4,7 @@ // // Created by 이지훈 on 7/18/24. // - import Foundation - import Moya enum AuthTargetType { @@ -15,6 +13,7 @@ enum AuthTargetType { case refreshToken(refreshToken: String) case updateProfileImage(image: Data, fileName: String, mimeType: String) case updateName(name: String) + case getUserInfo } extension AuthTargetType: TargetType { @@ -37,6 +36,8 @@ extension AuthTargetType: TargetType { return "/api/v1/users/me/image" case .updateName: return "/api/v1/users/me/name" + case .getUserInfo: + return "/api/v1/users/me" } } @@ -46,6 +47,8 @@ extension AuthTargetType: TargetType { return .post case .updateProfileImage, .updateName: return .patch + case .getUserInfo: + return .get } } @@ -65,6 +68,8 @@ extension AuthTargetType: TargetType { parameters: ["name": name], encoding: JSONEncoding.default ) + case .getUserInfo: + return .requestPlain } } @@ -84,7 +89,7 @@ extension AuthTargetType: TargetType { "Authorization": "Bearer \(token)", "Content-Type": "multipart/form-data" ] - case .updateName: + case .updateName, .getUserInfo: guard let token = DefaultKeychainService.shared.accessToken else { fatalError("No access token available") } diff --git a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift index 1412d369..343a5782 100644 --- a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift +++ b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift @@ -7,7 +7,6 @@ import UIKit import AuthenticationServices - import KakaoSDKUser import KakaoSDKAuth import Moya @@ -22,6 +21,7 @@ enum LoginState { class LoginViewModel: NSObject { var loginState: ObservablePattern = ObservablePattern(.notLogin) var error: ObservablePattern = ObservablePattern("") + var userName: ObservablePattern = ObservablePattern(nil) private let provider: MoyaProvider private var authService: AuthServiceType @@ -41,7 +41,6 @@ class LoginViewModel: NSObject { self.keychainAccessible = keychainAccessible super.init() - // 초기화 시 FCM 토큰 출력 print("Initial FCM Token: \(getFCMToken())") } @@ -111,7 +110,6 @@ class LoginViewModel: NSObject { } private func loginToServer(with loginTarget: AuthTargetType) { - // FCM 토큰 출력 switch loginTarget { case .appleLogin(_, let fcmToken), .kakaoLogin(_, let fcmToken): print("Sending FCM Token to server: \(fcmToken)") @@ -143,76 +141,109 @@ class LoginViewModel: NSObject { } private func handleLoginResponse(_ response: ResponseBodyDTO) { - print("Handling login response") - if response.success, let data = response.data { - saveTokens( + 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 - } - } + ) + userName.value = data.name + if data.name != nil { + print("Login successful, user has a name") + loginState.value = .login + } else { + print("Login successful, but user 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") - 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") - } + 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?.fetchUserInfo { success in + if success { + completion(true) + } else { + self?.clearTokensAndHandleError() + completion(false) + } + } + } 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 fetchUserInfo(completion: @escaping (Bool) -> Void) { + provider.request(.getUserInfo) { [weak self] result in + switch result { + case .success(let response): + do { + let userInfoResponse = try response.map(ResponseBodyDTO.self) + if userInfoResponse.success, let data = userInfoResponse.data { + self?.userName.value = data.name + self?.loginState.value = .login // 이름이 있으므로 항상 .login 상태로 설정 + completion(true) + } else { + self?.clearTokensAndHandleError() + completion(false) + } + } catch { + print("Failed to decode user info: \(error)") + self?.clearTokensAndHandleError() + completion(false) + } + case .failure(let error): + print("Failed to fetch user info: \(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") @@ -229,12 +260,8 @@ class LoginViewModel: NSObject { } } -extension LoginViewModel: ASAuthorizationControllerDelegate, - ASAuthorizationControllerPresentationContextProviding { - func authorizationController( - controller: ASAuthorizationController, - didCompleteWithAuthorization authorization: ASAuthorization - ) { +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, @@ -249,13 +276,8 @@ extension LoginViewModel: ASAuthorizationControllerDelegate, } } - func authorizationController( - controller: ASAuthorizationController, - didCompleteWithError error: Error - ) { - print( - "Apple authorization error: \(error.localizedDescription)" - ) + func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { + print("Apple authorization error: \(error.localizedDescription)") self.error.value = error.localizedDescription } diff --git a/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift b/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift index 0e8fafc6..0c87385e 100644 --- a/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift +++ b/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift @@ -11,7 +11,7 @@ class LoginViewController: BaseViewController { private let loginView = LoginView() private let loginViewModel: LoginViewModel - init(viewModel: LoginViewModel = LoginViewModel()) { + init(viewModel: LoginViewModel) { self.loginViewModel = viewModel super.init(nibName: nil, bundle: nil) } @@ -35,19 +35,13 @@ class LoginViewController: BaseViewController { let appleTapGesture = UITapGestureRecognizer( target: self, - action: #selector( - appleLoginTapped - ) - ) - loginView.appleLoginImageView.addGestureRecognizer( - appleTapGesture + action: #selector(appleLoginTapped) ) + loginView.appleLoginImageView.addGestureRecognizer(appleTapGesture) let kakaoTapGesture = UITapGestureRecognizer( target: self, - action: #selector( - kakaoLoginTapped - ) + action: #selector(kakaoLoginTapped) ) loginView.kakaoLoginImageView.addGestureRecognizer(kakaoTapGesture) } @@ -58,14 +52,22 @@ class LoginViewController: BaseViewController { case .notLogin: print("Login State: Not logged in") case .login: - print("Login State: Logged in with user info: ") - owner.navigateToOnboardingScreen() + print("Login State: Logged in") + owner.navigateToMainScreen() case .needOnboarding: print("Login State: Need onboarding") owner.navigateToOnboardingScreen() } } + loginViewModel.userName.bind(with: self) { owner, name in + if name != nil { + owner.navigateToMainScreen() + } else { + owner.navigateToOnboardingScreen() + } + } + loginViewModel.error.bind(with: self) { owner, error in if !error.isEmpty { print("Login Error: \(error)") @@ -74,7 +76,6 @@ class LoginViewController: BaseViewController { } } - @objc private func appleLoginTapped() { loginViewModel.performAppleLogin(presentationAnchor: view.window!) } @@ -83,13 +84,6 @@ class LoginViewController: BaseViewController { loginViewModel.performKakaoLogin() } - @objc private func dummyNextButtonTapped() { - let viewController = UINavigationController(rootViewController: MainTabBarController()) - viewController.isNavigationBarHidden = true - viewController.modalPresentationStyle = .fullScreen - present(viewController, animated: true) - } - private func navigateToMainScreen() { DispatchQueue.main.async { let mainTabBarController = MainTabBarController() diff --git a/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileSetupViewModel.swift b/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileSetupViewModel.swift index 912b36a0..cc2c083c 100644 --- a/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileSetupViewModel.swift +++ b/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileSetupViewModel.swift @@ -14,7 +14,7 @@ class ProfileSetupViewModel { let serverResponse = ObservablePattern(nil) private let authService: AuthServiceType - private var compressedImageData: Data? + private var imageData: Data? init(nickname: String, authService: AuthServiceType = AuthService()) { self.nickname = nickname @@ -23,31 +23,32 @@ class ProfileSetupViewModel { func updateProfileImage(_ image: UIImage?) { profileImage.value = image - if let image = image { - compressImage(image, maxSizeInBytes: 2 * 1024 * 1024) // 2MB로 변경 + if let image = image, let data = image.jpegData(compressionQuality: 1.0) { + imageData = data + print("이미지 크기: \(data.count) bytes") } else { - compressedImageData = nil + imageData = nil } - isConfirmButtonEnabled.value = compressedImageData != nil + isConfirmButtonEnabled.value = imageData != nil } func uploadProfileImage(completion: @escaping (Bool) -> Void) { print("uploadProfileImage 함수 호출됨") - guard let compressedImageData = compressedImageData else { - print("압축된 이미지 데이터가 없습니다.") + guard let imageData = imageData else { + print("이미지 데이터가 없습니다.") serverResponse.value = "이미지 데이터가 없습니다." completion(false) return } - print("업로드할 이미지 데이터 크기: \(compressedImageData.count) bytes") + print("업로드할 이미지 데이터 크기: \(imageData.count) bytes") let fileName = "profile_image.jpg" let mimeType = "image/jpeg" authService.performRequest( .updateProfileImage( - image: compressedImageData, + image: imageData, fileName: fileName, mimeType: mimeType ) @@ -69,33 +70,4 @@ class ProfileSetupViewModel { serverResponse.value = error.message print("프로필 이미지 업로드 실패: \(error.message)") } - - private func compressImage(_ image: UIImage, maxSizeInBytes: Int) { - guard let originalImageData = image.jpegData(compressionQuality: 1.0) else { - print("원본 이미지 데이터를 생성할 수 없습니다.") - return - } - - print("원본 이미지 크기: \(originalImageData.count) bytes") - - if originalImageData.count <= maxSizeInBytes { - print("원본 이미지가 이미 \(maxSizeInBytes) bytes 이하입니다. 압축이 필요하지 않습니다.") - compressedImageData = originalImageData - return - } - - var compression: CGFloat = 1.0 - var imageData = originalImageData - - while imageData.count > maxSizeInBytes && compression > 0.0 { - compression -= 0.1 - if let compressedData = image.jpegData(compressionQuality: compression) { - imageData = compressedData - print("압축 시도: 크기 \(imageData.count) bytes (압축률: \(compression))") - } - } - - compressedImageData = imageData - print("최종 압축 결과: \(imageData.count) bytes (원본 대비 \(Int((Double(imageData.count) / Double(originalImageData.count)) * 100))% 크기)") - } } From 00aead1ccb30578e2607c47ecaae7e28baf84028 Mon Sep 17 00:00:00 2001 From: hooni Date: Fri, 19 Jul 2024 23:32:36 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix/=EB=84=A4=ED=8A=B8=EC=9B=8C=ED=81=AC=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- KkuMulKum/Network/DTO/Model/Promises/TardyInfoModel.swift | 2 +- KkuMulKum/Network/TargetType/MeetingTargetType.swift | 8 +++++++- .../Tardy/ViewController/TardyViewController.swift | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/KkuMulKum/Network/DTO/Model/Promises/TardyInfoModel.swift b/KkuMulKum/Network/DTO/Model/Promises/TardyInfoModel.swift index 08dff31f..227c93a7 100644 --- a/KkuMulKum/Network/DTO/Model/Promises/TardyInfoModel.swift +++ b/KkuMulKum/Network/DTO/Model/Promises/TardyInfoModel.swift @@ -18,7 +18,7 @@ struct TardyInfoModel: ResponseModelType { struct Comer: Codable { let participantId: Int - let name, profileImageURL: String + let name, profileImageURL: String? enum CodingKeys: String, CodingKey { case participantId diff --git a/KkuMulKum/Network/TargetType/MeetingTargetType.swift b/KkuMulKum/Network/TargetType/MeetingTargetType.swift index 0fbadcf0..b4b5b328 100644 --- a/KkuMulKum/Network/TargetType/MeetingTargetType.swift +++ b/KkuMulKum/Network/TargetType/MeetingTargetType.swift @@ -60,11 +60,17 @@ extension MeetingTargetType: TargetType { return .requestJSONEncodable(request) case .joinMeeting(let request): return .requestJSONEncodable(request) - case .fetchMeetingList, .fetchMeetingInfo, .fetchMeetingMember, .fetchMeetingPromiseList: + case .fetchMeetingList, .fetchMeetingInfo, .fetchMeetingMember: return .requestPlain + case .fetchMeetingPromiseList: + return .requestParameters( + parameters: ["done": "false"], + encoding: URLEncoding.queryString + ) } } + var headers: [String : String]? { guard let token = DefaultKeychainService.shared.accessToken else { return ["Content-Type" : "application/json"] diff --git a/KkuMulKum/Source/Promise/Tardy/ViewController/TardyViewController.swift b/KkuMulKum/Source/Promise/Tardy/ViewController/TardyViewController.swift index 41c62287..e8282cba 100644 --- a/KkuMulKum/Source/Promise/Tardy/ViewController/TardyViewController.swift +++ b/KkuMulKum/Source/Promise/Tardy/ViewController/TardyViewController.swift @@ -115,9 +115,9 @@ extension TardyViewController: UICollectionViewDataSource { guard let data = tardyViewModel.comers.value?[indexPath.row] else { return cell } - cell.nameLabel.setText(data.name, style: .body06, color: .gray6) + cell.nameLabel.setText(data.name ?? " " , style: .body06, color: .gray6) - guard let image = URL(string: data.profileImageURL) else { + guard let image = URL(string: data.profileImageURL ?? " ") else { cell.profileImageView.image = .imgProfile return cell From 5f60d2c4a6bac9c832efa02cf2815ac4da83f71c Mon Sep 17 00:00:00 2001 From: hooni Date: Fri, 19 Jul 2024 23:37:26 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix/=20=EC=A7=80=EA=B0=81=EA=BE=B8=EB=AC=BC?= =?UTF-8?q?=EC=9D=B4=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Tardy/ViewController/TardyViewController.swift | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/KkuMulKum/Source/Promise/Tardy/ViewController/TardyViewController.swift b/KkuMulKum/Source/Promise/Tardy/ViewController/TardyViewController.swift index e8282cba..ad29b287 100644 --- a/KkuMulKum/Source/Promise/Tardy/ViewController/TardyViewController.swift +++ b/KkuMulKum/Source/Promise/Tardy/ViewController/TardyViewController.swift @@ -11,27 +11,27 @@ class TardyViewController: BaseViewController { // MARK: Property - + private let tardyViewModel: TardyViewModel let tardyView: TardyView = TardyView() let arriveView: ArriveView = ArriveView() // MARK: Initialize - + init(tardyViewModel: TardyViewModel) { self.tardyViewModel = tardyViewModel super.init(nibName: nil, bundle: nil) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - Setup - + override func loadView() { view = tardyViewModel.isPastDue.value ? arriveView : tardyView } @@ -117,13 +117,11 @@ extension TardyViewController: UICollectionViewDataSource { cell.nameLabel.setText(data.name ?? " " , style: .body06, color: .gray6) - guard let image = URL(string: data.profileImageURL ?? " ") else { + guard let imageURL = URL(string: data.profileImageURL ?? "") else { cell.profileImageView.image = .imgProfile - return cell } - - cell.profileImageView.kf.setImage(with: image) + cell.profileImageView.kf.setImage(with: imageURL) return cell }