diff --git a/KkuMulKum.xcodeproj/project.pbxproj b/KkuMulKum.xcodeproj/project.pbxproj index af44d8bc..a680804a 100644 --- a/KkuMulKum.xcodeproj/project.pbxproj +++ b/KkuMulKum.xcodeproj/project.pbxproj @@ -7,6 +7,18 @@ objects = { /* Begin PBXBuildFile section */ + 780E897A2C5D22B90009D27E /* KakaoSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 780E89792C5D22B90009D27E /* KakaoSDK */; }; + 780E897C2C5D22B90009D27E /* KakaoSDKAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 780E897B2C5D22B90009D27E /* KakaoSDKAuth */; }; + 780E897E2C5D22B90009D27E /* KakaoSDKCert in Frameworks */ = {isa = PBXBuildFile; productRef = 780E897D2C5D22B90009D27E /* KakaoSDKCert */; }; + 780E89802C5D22B90009D27E /* KakaoSDKCertCore in Frameworks */ = {isa = PBXBuildFile; productRef = 780E897F2C5D22B90009D27E /* KakaoSDKCertCore */; }; + 780E89822C5D22B90009D27E /* KakaoSDKCommon in Frameworks */ = {isa = PBXBuildFile; productRef = 780E89812C5D22B90009D27E /* KakaoSDKCommon */; }; + 780E89842C5D22B90009D27E /* KakaoSDKFriend in Frameworks */ = {isa = PBXBuildFile; productRef = 780E89832C5D22B90009D27E /* KakaoSDKFriend */; }; + 780E89862C5D22B90009D27E /* KakaoSDKFriendCore in Frameworks */ = {isa = PBXBuildFile; productRef = 780E89852C5D22B90009D27E /* KakaoSDKFriendCore */; }; + 780E89882C5D22B90009D27E /* KakaoSDKNavi in Frameworks */ = {isa = PBXBuildFile; productRef = 780E89872C5D22B90009D27E /* KakaoSDKNavi */; }; + 780E898A2C5D22B90009D27E /* KakaoSDKShare in Frameworks */ = {isa = PBXBuildFile; productRef = 780E89892C5D22B90009D27E /* KakaoSDKShare */; }; + 780E898C2C5D22B90009D27E /* KakaoSDKTalk in Frameworks */ = {isa = PBXBuildFile; productRef = 780E898B2C5D22B90009D27E /* KakaoSDKTalk */; }; + 780E898E2C5D22B90009D27E /* KakaoSDKTemplate in Frameworks */ = {isa = PBXBuildFile; productRef = 780E898D2C5D22B90009D27E /* KakaoSDKTemplate */; }; + 780E89902C5D22B90009D27E /* KakaoSDKUser in Frameworks */ = {isa = PBXBuildFile; productRef = 780E898F2C5D22B90009D27E /* KakaoSDKUser */; }; 782B407B2C3E395A008B0CA7 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782B407A2C3E395A008B0CA7 /* WelcomeView.swift */; }; 782B407D2C3E3984008B0CA7 /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782B407C2C3E3984008B0CA7 /* WelcomeViewController.swift */; }; 782B407F2C3E44B7008B0CA7 /* WelcomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 782B407E2C3E44B7008B0CA7 /* WelcomeViewModel.swift */; }; @@ -418,23 +430,30 @@ buildActionMask = 2147483647; files = ( 785AE1C02C2E878600677CA0 /* FirebaseVertexAI-Preview in Frameworks */, + 780E897A2C5D22B90009D27E /* KakaoSDK in Frameworks */, 785AE19E2C2E878600677CA0 /* FirebaseDatabaseSwift in Frameworks */, 785AE1AA2C2E878600677CA0 /* FirebaseFunctionsCombine-Community in Frameworks */, 785AE1BC2C2E878600677CA0 /* FirebaseStorage in Frameworks */, 785AE1BA2C2E878600677CA0 /* FirebaseRemoteConfigSwift in Frameworks */, 785AE16E2C2E85E800677CA0 /* Then in Frameworks */, + 780E897C2C5D22B90009D27E /* KakaoSDKAuth in Frameworks */, + 780E89902C5D22B90009D27E /* KakaoSDKUser in Frameworks */, 785AE1662C2E858A00677CA0 /* SnapKit in Frameworks */, 785AE1942C2E878600677CA0 /* FirebaseAppDistribution-Beta in Frameworks */, 785AE1B02C2E878600677CA0 /* FirebaseInstallations in Frameworks */, 784E4D992C3B95A900BC943C /* KeychainAccess in Frameworks */, + 780E89822C5D22B90009D27E /* KakaoSDKCommon in Frameworks */, 785AE1902C2E878600677CA0 /* FirebaseAnalyticsWithoutAdIdSupport in Frameworks */, 785AE18E2C2E878600677CA0 /* FirebaseAnalyticsSwift in Frameworks */, 785AE1962C2E878600677CA0 /* FirebaseAuth in Frameworks */, + 780E89862C5D22B90009D27E /* KakaoSDKFriendCore in Frameworks */, 785AE1A82C2E878600677CA0 /* FirebaseFunctions in Frameworks */, 785AE1602C2E857A00677CA0 /* LookinServer in Frameworks */, 785AE19A2C2E878600677CA0 /* FirebaseCrashlytics in Frameworks */, 785AE16B2C2E85C200677CA0 /* Moya in Frameworks */, 785AE1922C2E878600677CA0 /* FirebaseAppCheck in Frameworks */, + 780E898C2C5D22B90009D27E /* KakaoSDKTalk in Frameworks */, + 780E89882C5D22B90009D27E /* KakaoSDKNavi in Frameworks */, DDAF1C7C2C3D5B86008A37D3 /* RxCocoa in Frameworks */, 785AE18A2C2E878600677CA0 /* FirebaseAnalytics in Frameworks */, 785AE1982C2E878600677CA0 /* FirebaseAuthCombine-Community in Frameworks */, @@ -448,13 +467,18 @@ 784E4D962C3B1C7F00BC943C /* KakaoSDKAuth in Frameworks */, 785AE1A62C2E878600677CA0 /* FirebaseFirestoreSwift in Frameworks */, 785AE1A22C2E878600677CA0 /* FirebaseFirestore in Frameworks */, + 780E897E2C5D22B90009D27E /* KakaoSDKCert in Frameworks */, 785AE1A42C2E878600677CA0 /* FirebaseFirestoreCombine-Community in Frameworks */, 78BD61202C43F557005752FD /* SwiftKeychainWrapper in Frameworks */, 785AE1AC2C2E878600677CA0 /* FirebaseInAppMessaging-Beta in Frameworks */, + 780E89842C5D22B90009D27E /* KakaoSDKFriend in Frameworks */, + 780E898A2C5D22B90009D27E /* KakaoSDKShare in Frameworks */, 785AE1B22C2E878600677CA0 /* FirebaseMLModelDownloader in Frameworks */, 785AE19C2C2E878600677CA0 /* FirebaseDatabase in Frameworks */, + 780E898E2C5D22B90009D27E /* KakaoSDKTemplate in Frameworks */, 785AE1A02C2E878600677CA0 /* FirebaseDynamicLinks in Frameworks */, DDAF1C812C3D5BD5008A37D3 /* Kingfisher in Frameworks */, + 780E89802C5D22B90009D27E /* KakaoSDKCertCore in Frameworks */, 785AE1AE2C2E878600677CA0 /* FirebaseInAppMessagingSwift-Beta in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1594,6 +1618,18 @@ DDAF1C7D2C3D5B86008A37D3 /* RxRelay */, DDAF1C802C3D5BD5008A37D3 /* Kingfisher */, 78BD611F2C43F557005752FD /* SwiftKeychainWrapper */, + 780E89792C5D22B90009D27E /* KakaoSDK */, + 780E897B2C5D22B90009D27E /* KakaoSDKAuth */, + 780E897D2C5D22B90009D27E /* KakaoSDKCert */, + 780E897F2C5D22B90009D27E /* KakaoSDKCertCore */, + 780E89812C5D22B90009D27E /* KakaoSDKCommon */, + 780E89832C5D22B90009D27E /* KakaoSDKFriend */, + 780E89852C5D22B90009D27E /* KakaoSDKFriendCore */, + 780E89872C5D22B90009D27E /* KakaoSDKNavi */, + 780E89892C5D22B90009D27E /* KakaoSDKShare */, + 780E898B2C5D22B90009D27E /* KakaoSDKTalk */, + 780E898D2C5D22B90009D27E /* KakaoSDKTemplate */, + 780E898F2C5D22B90009D27E /* KakaoSDKUser */, ); productName = KkuMulKum; productReference = 78B928682C29402C006D9942 /* KkuMulKum.app */; @@ -1991,10 +2027,13 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = KkuMulKum/Resource/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "꾸물꿈"; + INFOPLIST_KEY_NSCameraUsageDescription = "카메라 사용 권한이 필요합니다."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "사진 라이브러리 접근 권한이 필요합니다."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UIUserInterfaceStyle = Light; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -2025,10 +2064,13 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = KkuMulKum/Resource/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "꾸물꿈"; + INFOPLIST_KEY_NSCameraUsageDescription = "카메라 사용 권한이 필요합니다."; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "사진 라이브러리 접근 권한이 필요합니다."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UIUserInterfaceStyle = Light; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -2152,6 +2194,66 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 780E89792C5D22B90009D27E /* KakaoSDK */ = { + isa = XCSwiftPackageProductDependency; + package = 784E4D922C3B1C7F00BC943C /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDK; + }; + 780E897B2C5D22B90009D27E /* KakaoSDKAuth */ = { + isa = XCSwiftPackageProductDependency; + package = 784E4D922C3B1C7F00BC943C /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKAuth; + }; + 780E897D2C5D22B90009D27E /* KakaoSDKCert */ = { + isa = XCSwiftPackageProductDependency; + package = 784E4D922C3B1C7F00BC943C /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKCert; + }; + 780E897F2C5D22B90009D27E /* KakaoSDKCertCore */ = { + isa = XCSwiftPackageProductDependency; + package = 784E4D922C3B1C7F00BC943C /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKCertCore; + }; + 780E89812C5D22B90009D27E /* KakaoSDKCommon */ = { + isa = XCSwiftPackageProductDependency; + package = 784E4D922C3B1C7F00BC943C /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKCommon; + }; + 780E89832C5D22B90009D27E /* KakaoSDKFriend */ = { + isa = XCSwiftPackageProductDependency; + package = 784E4D922C3B1C7F00BC943C /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKFriend; + }; + 780E89852C5D22B90009D27E /* KakaoSDKFriendCore */ = { + isa = XCSwiftPackageProductDependency; + package = 784E4D922C3B1C7F00BC943C /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKFriendCore; + }; + 780E89872C5D22B90009D27E /* KakaoSDKNavi */ = { + isa = XCSwiftPackageProductDependency; + package = 784E4D922C3B1C7F00BC943C /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKNavi; + }; + 780E89892C5D22B90009D27E /* KakaoSDKShare */ = { + isa = XCSwiftPackageProductDependency; + package = 784E4D922C3B1C7F00BC943C /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKShare; + }; + 780E898B2C5D22B90009D27E /* KakaoSDKTalk */ = { + isa = XCSwiftPackageProductDependency; + package = 784E4D922C3B1C7F00BC943C /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKTalk; + }; + 780E898D2C5D22B90009D27E /* KakaoSDKTemplate */ = { + isa = XCSwiftPackageProductDependency; + package = 784E4D922C3B1C7F00BC943C /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKTemplate; + }; + 780E898F2C5D22B90009D27E /* KakaoSDKUser */ = { + isa = XCSwiftPackageProductDependency; + package = 784E4D922C3B1C7F00BC943C /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; + productName = KakaoSDKUser; + }; 784E4D932C3B1C7F00BC943C /* KakaoSDK */ = { isa = XCSwiftPackageProductDependency; package = 784E4D922C3B1C7F00BC943C /* XCRemoteSwiftPackageReference "kakao-ios-sdk" */; diff --git a/KkuMulKum/Network/Service/AuthService.swift b/KkuMulKum/Network/Service/AuthService.swift index 04d9fcf8..8796140f 100644 --- a/KkuMulKum/Network/Service/AuthService.swift +++ b/KkuMulKum/Network/Service/AuthService.swift @@ -13,7 +13,7 @@ protocol AuthServiceType { func getAccessToken() -> String? func getRefreshToken() -> String? func clearTokens() -> Bool - func performRequest(_ target: AuthTargetType, completion: @escaping (Result) -> Void) + func performRequest(_ target: AuthTargetType) async throws -> T } class AuthService: AuthServiceType { @@ -50,31 +50,33 @@ class AuthService: AuthServiceType { return keychainService.accessToken == nil && keychainService.refreshToken == nil } - func performRequest(_ target: AuthTargetType, completion: @escaping (Result) -> Void) { - provider.request(target) { result in - switch result { - case .success(let response): - print("서버 응답 상태 코드: \(response.statusCode)") - print("서버 응답 데이터: \(String(data: response.data, encoding: .utf8) ?? "디코딩 불가")") - - do { - let decodedResponse = try JSONDecoder().decode(ResponseBodyDTO.self, from: response.data) - if decodedResponse.success { - if let data = decodedResponse.data { - completion(.success(data)) - } else if T.self == EmptyModel.self { - completion(.success(EmptyModel() as! T)) - } else { - completion(.failure(.decodingError)) + func performRequest(_ target: AuthTargetType) async throws -> T { + return try await withCheckedThrowingContinuation { continuation in + provider.request(target) { result in + switch result { + case .success(let response): + print("서버 응답 상태 코드: \(response.statusCode)") + print("서버 응답 데이터: \(String(data: response.data, encoding: .utf8) ?? "디코딩 불가")") + + do { + let decodedResponse = try JSONDecoder().decode(ResponseBodyDTO.self, from: response.data) + guard decodedResponse.success else { + throw decodedResponse.error.map(self.mapErrorResponse) ?? NetworkError.unknownError("Unknown error occurred") } - } else if let error = decodedResponse.error { - completion(.failure(self.mapErrorResponse(error))) - } else { - completion(.failure(.unknownError("Unknown error occurred"))) + guard let data = decodedResponse.data else { + if T.self == EmptyModel.self { + continuation.resume(returning: EmptyModel() as! T) + } else { + throw NetworkError.decodingError + } + return + } + continuation.resume(returning: data) + } catch { + continuation.resume(throwing: error is NetworkError ? error : NetworkError.decodingError) } - } catch { - print("디코딩 오류: \(error)") - completion(.failure(.decodingError)) + case .failure(let error): + continuation.resume(throwing: NetworkError.networkError(error)) } case .failure(let error): if let response = error.response, response.statusCode == 413 { diff --git a/KkuMulKum/Resource/Info.plist b/KkuMulKum/Resource/Info.plist index aa26eb10..8cc36857 100644 --- a/KkuMulKum/Resource/Info.plist +++ b/KkuMulKum/Resource/Info.plist @@ -2,64 +2,56 @@ + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + + FirebaseAppDelegateProxyEnabled No - LSApplicationQueriesSchemes - - kakaotalk - kakaoplus - kakaolink - kakaokompassauth - - NSPhotoLibraryAddUsageDescription - 사진 라이브러리 접근 권한이 필요합니다. - NSCameraUsageDescription - 카메라 사용 권한이 필요합니다. - UIUserInterfaceStyle - Light - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLSchemes - - kakao69aeef4a49d5b6772d62efdf1686994c - - - - KeychainAccessGroups - - $(AppIdentifierPrefix)KkuMulKum.yizihn - - UIAppFonts - - Pretendard-Black.otf - Pretendard-Bold.otf - Pretendard-ExtraBold.otf - Pretendard-ExtraLight.otf - Pretendard-Light.otf - Pretendard-Medium.otf - Pretendard-Regular.otf - Pretendard-SemiBold.otf - Pretendard-Thin.otf - - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - UISceneConfigurations - - UIWindowSceneSessionRoleApplication - - - UISceneConfigurationName - Default Configuration - UISceneDelegateClassName - $(PRODUCT_MODULE_NAME).SceneDelegate - - - - + KeychainAccessGroups + + $(AppIdentifierPrefix)KkuMulKum.yizihn + + LSApplicationQueriesSchemes + + kakaotalk + kakaoplus + kakaolink + kakaokompassauth + + UIAppFonts + + Pretendard-Black.otf + Pretendard-Bold.otf + Pretendard-ExtraBold.otf + Pretendard-ExtraLight.otf + Pretendard-Light.otf + Pretendard-Medium.otf + Pretendard-Regular.otf + Pretendard-SemiBold.otf + Pretendard-Thin.otf + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + diff --git a/KkuMulKum/Source/MyPage/View/MyPageEtcSettingView.swift b/KkuMulKum/Source/MyPage/View/MyPageEtcSettingView.swift index c2868500..5db11575 100644 --- a/KkuMulKum/Source/MyPage/View/MyPageEtcSettingView.swift +++ b/KkuMulKum/Source/MyPage/View/MyPageEtcSettingView.swift @@ -11,6 +11,9 @@ import SnapKit import Then class MyPageEtcSettingView: BaseView { + + let kakaoShareTapped = ObservablePattern(nil) + let stackView = UIStackView(axis: .vertical).then { $0.spacing = 24 $0.distribution = .fillEqually @@ -74,7 +77,7 @@ class MyPageEtcSettingView: BaseView { switch index { case 0: - print("버전정보 탭됨") + kakaoShareTapped.value = () case 1: print("이용약관 탭됨") case 2: diff --git a/KkuMulKum/Source/MyPage/View/MyPageView.swift b/KkuMulKum/Source/MyPage/View/MyPageView.swift index c3bea878..8df2e02c 100644 --- a/KkuMulKum/Source/MyPage/View/MyPageView.swift +++ b/KkuMulKum/Source/MyPage/View/MyPageView.swift @@ -14,7 +14,7 @@ class MyPageView: BaseView { private let topBackgroundView = UIView(backgroundColor: .white) private let contentView = MyPageContentView() private let alarmView = MyPageAlarmSettingView() - private let etcSettingView = MyPageEtcSettingView() + let etcSettingView = MyPageEtcSettingView() override func setupView() { backgroundColor = .green1 diff --git a/KkuMulKum/Source/MyPage/ViewController/MyPageViewController.swift b/KkuMulKum/Source/MyPage/ViewController/MyPageViewController.swift index 0dcf5fda..3d2a3c17 100644 --- a/KkuMulKum/Source/MyPage/ViewController/MyPageViewController.swift +++ b/KkuMulKum/Source/MyPage/ViewController/MyPageViewController.swift @@ -6,6 +6,9 @@ // import UIKit +import KakaoSDKCommon +import KakaoSDKTemplate +import KakaoSDKShare class MyPageViewController: BaseViewController { private let rootView = MyPageView() @@ -14,8 +17,46 @@ class MyPageViewController: BaseViewController { view = rootView } + override func setupView() { - view.backgroundColor = .green1 - setupNavigationBarTitle(with: "마이페이지") - } + view.backgroundColor = .green1 + setupNavigationBarTitle(with: "마이페이지") + + bindViewModel() + } + + private func bindViewModel() { + rootView.etcSettingView.kakaoShareTapped.bind { [weak self] _ in + self?.shareKakaoTemplate() + } + } + + private func shareKakaoTemplate() { + let templateId = 110759 + + if ShareApi.isKakaoTalkSharingAvailable() { + ShareApi.shared.shareCustom(templateId: Int64(templateId)) { [weak self] (sharingResult, error) in + if let error = error { + print("카카오톡 공유 실패: \(error)") + self?.showAlert(title: "공유 실패", message: "카카오톡 공유에 실패했습니다.") + } + else { + print("카카오톡 공유 성공") + if let sharingResult = sharingResult { + UIApplication.shared.open(sharingResult.url, options: [:], completionHandler: nil) + } + } + } + } + else { + print("카카오톡 미설치") + self.showAlert(title: "알림", message: "카카오톡이 설치되어 있지 않습니다.") + } + } + + private func showAlert(title: String, message: String) { + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: "확인", style: .default, handler: nil)) + present(alertController, animated: true, completion: nil) + } } diff --git a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift index 343a5782..cbbab194 100644 --- a/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift +++ b/KkuMulKum/Source/Onboarding/Login/VIewModel/LoginViewModel.swift @@ -238,6 +238,7 @@ class LoginViewModel: NSObject { } } } + private func clearTokensAndHandleError() { _ = authService.clearTokens() loginState.value = .notLogin diff --git a/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift b/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift index 0a6c4aa5..b98ce67b 100644 --- a/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift +++ b/KkuMulKum/Source/Onboarding/Login/ViewController/LoginViewController.swift @@ -10,7 +10,7 @@ import UIKit class LoginViewController: BaseViewController { private let loginView = LoginView() private let loginViewModel: LoginViewModel - + init(viewModel: LoginViewModel) { self.loginViewModel = viewModel super.init(nibName: nil, bundle: nil) @@ -48,44 +48,55 @@ class LoginViewController: BaseViewController { private func bindViewModel() { loginViewModel.loginState.bind(with: self) { owner, state in - switch state { - case .notLogin: - print("Login State: Not logged in") - case .login: - print("Login State: Logged in") - owner.navigateToOnboardingScreen() - case .needOnboarding: - print("Login State: Need onboarding") - owner.navigateToOnboardingScreen() + + Task { + switch state { + case .notLogin: + print("Login State: Not logged in") + case .login: + print("Login State: Logged in") + await owner.navigateToMainScreen() + case .needOnboarding: + print("Login State: Need onboarding") + await owner.navigateToOnboardingScreen() + } } } loginViewModel.userName.bind(with: self) { owner, name in - if name != nil { - owner.navigateToOnboardingScreen() - } else { - owner.navigateToOnboardingScreen() + Task { + if name != nil { + await owner.navigateToOnboardingScreen() + } else { + await owner.navigateToOnboardingScreen() + } } } loginViewModel.error.bind(with: self) { owner, error in - if !error.isEmpty { - print("Login Error: \(error)") - owner.showErrorAlert(message: error) + Task { + if !error.isEmpty { + print("Login Error: \(error)") + await owner.showErrorAlert(message: error) + } } } } - + @objc private func appleLoginTapped() { - loginViewModel.performAppleLogin(presentationAnchor: view.window!) + Task { + loginViewModel.performAppleLogin(presentationAnchor: view.window!) + } } @objc private func kakaoLoginTapped() { - loginViewModel.performKakaoLogin() + Task { + loginViewModel.performKakaoLogin() + } } - - private func navigateToMainScreen() { - DispatchQueue.main.async { + + private func navigateToMainScreen() async { + await MainActor.run { let mainTabBarController = MainTabBarController() let navigationController = UINavigationController(rootViewController: mainTabBarController) navigationController.isNavigationBarHidden = true @@ -95,8 +106,9 @@ class LoginViewController: BaseViewController { } } - private func navigateToOnboardingScreen() { - DispatchQueue.main.async { + private func navigateToOnboardingScreen() async { + await MainActor.run { + let nicknameViewController = NicknameViewController() if let navigationController = self.navigationController { navigationController.pushViewController(nicknameViewController, animated: true) @@ -109,10 +121,12 @@ class LoginViewController: BaseViewController { } } - private func showErrorAlert(message: String) { - print("Showing error alert with message: \(message)") - let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default)) - present(alert, animated: true) + private func showErrorAlert(message: String) async { + await MainActor.run { + print("Showing error alert with message: \(message)") + let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default)) + present(alert, animated: true) + } } } diff --git a/KkuMulKum/Source/Onboarding/Profile/VIewController/ProfileSetupViewController.swift b/KkuMulKum/Source/Onboarding/Profile/VIewController/ProfileSetupViewController.swift index c7be34b2..aa5086a4 100644 --- a/KkuMulKum/Source/Onboarding/Profile/VIewController/ProfileSetupViewController.swift +++ b/KkuMulKum/Source/Onboarding/Profile/VIewController/ProfileSetupViewController.swift @@ -50,18 +50,19 @@ class ProfileSetupViewController: BaseViewController { } @objc private func confirmButtonTapped() { - viewModel.uploadProfileImage { [weak self] success in - if success { - DispatchQueue.main.async { - let welcomeVC = WelcomeViewController( - viewModel: WelcomeViewModel(nickname: self?.viewModel.nickname ?? "") - ) - welcomeVC.modalPresentationStyle = .fullScreen - self?.present(welcomeVC, animated: true, completion: nil) - } - } - } - } + Task { + let success = await viewModel.uploadProfileImage() + if success { + DispatchQueue.main.async { + let welcomeVC = WelcomeViewController( + viewModel: WelcomeViewModel(nickname: self.viewModel.nickname) + ) + welcomeVC.modalPresentationStyle = .fullScreen + self.present(welcomeVC, animated: true, completion: nil) + } + } + } + } @objc private func skipButtonTapped() { let welcomeVC = WelcomeViewController( diff --git a/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileSetupViewModel.swift b/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileSetupViewModel.swift index d464b78f..c3ea45bc 100644 --- a/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileSetupViewModel.swift +++ b/KkuMulKum/Source/Onboarding/Profile/ViewModel/ProfileSetupViewModel.swift @@ -24,94 +24,45 @@ class ProfileSetupViewModel { } func updateProfileImage(_ image: UIImage?) { - guard let image = image else { - profileImage.value = nil + profileImage.value = image + if let image = image, let data = image.jpegData(compressionQuality: 1.0) { + imageData = data + print("이미지 크기: \(data.count) bytes") + } else { imageData = nil - isConfirmButtonEnabled.value = false - return } - guard let compressedData = compressImage(image, maxSizeInBytes: maxImageSizeBytes) else { - imageData = nil - profileImage.value = nil - isConfirmButtonEnabled.value = false - print("이미지 압축 실패") - return - } - imageData = compressedData - profileImage.value = UIImage(data: compressedData) - print("압축된 이미지 크기: \(compressedData.count) bytes") - isConfirmButtonEnabled.value = true + isConfirmButtonEnabled.value = imageData != nil } - - private func compressImage(_ image: UIImage, maxSizeInBytes: Int) -> Data? { - let resizedImage = image.kf.resize(to: image.size, for: .aspectFit) - - guard let data = resizedImage.jpegData(compressionQuality: 0.8) else { - return nil - } - if data.count <= maxSizeInBytes { - return data - } - var quality: CGFloat = 0.8 - - while data.count > maxSizeInBytes && quality > 0.1 { - quality -= 0.1 - guard let reducedData = resizedImage.jpegData(compressionQuality: quality) else { - continue - } - if reducedData.count <= maxSizeInBytes { - return reducedData - } - } - - var newSize = image.size - while data.count > maxSizeInBytes { - newSize = CGSize(width: newSize.width * 0.9, height: newSize.height * 0.9) - let reducedImage = image.kf.resize(to: newSize, for: .aspectFit) - if let reducedData = reducedImage.jpegData(compressionQuality: quality) { - if reducedData.count <= maxSizeInBytes { - return reducedData - } - } - } - return nil - } - - func uploadProfileImage(completion: @escaping (Bool) -> Void) { - guard let imageData = imageData else { - print("이미지 데이터가 없습니다.") - serverResponse.value = "이미지 데이터가 없습니다." - completion(false) - return - } - - print("업로드할 이미지 데이터 크기: \(imageData.count) bytes") - - let fileName = "profile_image.jpg" - let mimeType = "image/jpeg" - - authService.performRequest( - .updateProfileImage( - image: imageData, - fileName: fileName, - mimeType: mimeType - ) - ) { [weak self] (result: Result) in - print("네트워크 요청 완료") - DispatchQueue.main.async { - switch result { - case .success(_): - self?.serverResponse.value = "프로필 이미지가 성공적으로 업로드되었습니다." - print("프로필 이미지 업로드 성공") - completion(true) - case .failure(let error): - self?.handleError(error) - completion(false) - } - } - } - } + func uploadProfileImage() async -> Bool { + print("uploadProfileImage 함수 호출됨") + guard let imageData = imageData else { + print("이미지 데이터가 없습니다.") + serverResponse.value = "이미지 데이터가 없습니다." + return false + } + + print("업로드할 이미지 데이터 크기: \(imageData.count) bytes") + + let fileName = "profile_image.jpg" + let mimeType = "image/jpeg" + + do { + let _: EmptyModel = try await authService.performRequest( + .updateProfileImage( + image: imageData, + fileName: fileName, + mimeType: mimeType + ) + ) + serverResponse.value = "프로필 이미지가 성공적으로 업로드되었습니다." + print("프로필 이미지 업로드 성공") + return true + } catch { + handleError(error as? NetworkError ?? .unknownError("알 수 없는 오류가 발생했습니다.")) + return false + } + } private func handleError(_ error: NetworkError) { switch error {