diff --git a/Podfile b/Podfile index e4799e2..4e63794 100644 --- a/Podfile +++ b/Podfile @@ -8,6 +8,8 @@ target 'SodaLive' do # Pods for SodaLive pod 'BootpayUI', '4.4.10' pod 'AgoraRtm', '2.2.4' + pod 'GoogleSignIn' + pod 'GoogleSignInSwiftSupport' end @@ -18,6 +20,8 @@ target 'SodaLive-dev' do # Pods for SodaLive-dev pod 'BootpayUI', '4.4.10' pod 'AgoraRtm', '2.2.4' + pod 'GoogleSignIn' + pod 'GoogleSignInSwiftSupport' end diff --git a/Podfile.lock b/Podfile.lock index 9cbddd8..3e57a70 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -5,6 +5,16 @@ PODS: - AgoraRtm/RtmBasic (2.2.4) - AgoraRtm/RtmKit (2.2.4) - Alamofire (5.10.2) + - AppAuth (2.0.0): + - AppAuth/Core (= 2.0.0) + - AppAuth/ExternalUserAgent (= 2.0.0) + - AppAuth/Core (2.0.0) + - AppAuth/ExternalUserAgent (2.0.0): + - AppAuth/Core + - AppCheckCore (11.2.0): + - GoogleUtilities/Environment (~> 8.0) + - GoogleUtilities/UserDefaults (~> 8.0) + - PromisesObjC (~> 2.4) - Bootpay (4.4.6): - CryptoSwift - NVActivityIndicatorView @@ -17,40 +27,79 @@ PODS: - SnapKit - SwiftyJSON - CryptoSwift (1.8.4) + - GoogleSignIn (9.1.0): + - AppAuth (~> 2.0) + - AppCheckCore (~> 11.0) + - GTMAppAuth (~> 5.0) + - GTMSessionFetcher/Core (~> 3.3) + - GoogleSignInSwiftSupport (9.1.0): + - GoogleSignIn (~> 9.0) + - GoogleUtilities/Environment (8.1.0): + - GoogleUtilities/Privacy + - GoogleUtilities/Logger (8.1.0): + - GoogleUtilities/Environment + - GoogleUtilities/Privacy + - GoogleUtilities/Privacy (8.1.0) + - GoogleUtilities/UserDefaults (8.1.0): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GTMAppAuth (5.0.0): + - AppAuth/Core (~> 2.0) + - GTMSessionFetcher/Core (< 4.0, >= 3.3) + - GTMSessionFetcher/Core (3.5.0) - NVActivityIndicatorView (5.2.0): - NVActivityIndicatorView/Base (= 5.2.0) - NVActivityIndicatorView/Base (5.2.0) - ObjectMapper (4.4.2) + - PromisesObjC (2.4.0) - SnapKit (5.7.1) - SwiftyJSON (5.0.2) DEPENDENCIES: - AgoraRtm (= 2.2.4) - BootpayUI (= 4.4.10) + - GoogleSignIn + - GoogleSignInSwiftSupport SPEC REPOS: trunk: - AgoraRtm - Alamofire + - AppAuth + - AppCheckCore - Bootpay - BootpayUI - CryptoSwift + - GoogleSignIn + - GoogleSignInSwiftSupport + - GoogleUtilities + - GTMAppAuth + - GTMSessionFetcher - NVActivityIndicatorView - ObjectMapper + - PromisesObjC - SnapKit - SwiftyJSON SPEC CHECKSUMS: AgoraRtm: 534144434383d41b3b0ebfae2a961ef0f51b0645 Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496 + AppAuth: 1c1a8afa7e12f2ec3a294d9882dfa5ab7d3cb063 + AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f Bootpay: cd7f0542b096ab0af0b09a6e12a6b87f2cbbb531 BootpayUI: beec5b0bba002b4dbced8c0ecace571ed6a017bc CryptoSwift: e64e11850ede528a02a0f3e768cec8e9d92ecb90 + GoogleSignIn: fcee2257188d5eda57a5e2b6a715550ffff9206d + GoogleSignInSwiftSupport: aca902e4e15b234611ecac74ef5c8f61278f774e + GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 + GTMAppAuth: 217a876b249c3c585a54fd6f73e6b58c4f5c4238 + GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 NVActivityIndicatorView: fe52a6a68664c2df8991d7d9e3d86d8d19453c53 ObjectMapper: e6e4d91ff7f2861df7aecc536c92d8363f4c9677 + PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a SwiftyJSON: f5b1bf1cd8dd53cd25887ac0eabcfd92301c6a5a -PODFILE CHECKSUM: 197d8c8b434dbcc335438281fc68e94718f6a8e1 +PODFILE CHECKSUM: 70c5639090824ff26cfad959985347579609e1e6 COCOAPODS: 1.16.2 diff --git a/SodaLive/Resources/Debug/SodaLive-dev-Info.plist b/SodaLive/Resources/Debug/SodaLive-dev-Info.plist index 528a4f7..e5ffe0e 100644 --- a/SodaLive/Resources/Debug/SodaLive-dev-Info.plist +++ b/SodaLive/Resources/Debug/SodaLive-dev-Info.plist @@ -2,12 +2,15 @@ + GIDClientID + 758414412471-3cf403jb4s405eu17qrfrcbs9ofhq369.apps.googleusercontent.com CFBundleURLTypes CFBundleURLSchemes fb608674328645232 + com.googleusercontent.apps.758414412471-3cf403jb4s405eu17qrfrcbs9ofhq369 diff --git a/SodaLive/Resources/Info.plist b/SodaLive/Resources/Info.plist index 36db44e..f45e800 100644 --- a/SodaLive/Resources/Info.plist +++ b/SodaLive/Resources/Info.plist @@ -2,12 +2,15 @@ + GIDClientID + 983594297130-m6bv7lvc1lsetsvv3rk92etqc98uopqj.apps.googleusercontent.com CFBundleURLTypes CFBundleURLSchemes - fb612448298237287 + fb612448298237287 + com.googleusercontent.apps.983594297130-m6bv7lvc1lsetsvv3rk92etqc98uopqj diff --git a/SodaLive/Sources/App/SodaLiveApp.swift b/SodaLive/Sources/App/SodaLiveApp.swift index 36f1ce4..f8a986c 100644 --- a/SodaLive/Sources/App/SodaLiveApp.swift +++ b/SodaLive/Sources/App/SodaLiveApp.swift @@ -10,6 +10,7 @@ import Kingfisher import FBSDKCoreKit import AppsFlyerLib +import GoogleSignIn @main struct SodaLiveApp: App { @@ -76,6 +77,7 @@ struct SodaLiveApp: App { } else { ApplicationDelegate.shared.application(UIApplication.shared, open: url, options: [:]) AppsFlyerLib.shared().handleOpen(url) + GIDSignIn.sharedInstance.handle(url) } } } diff --git a/SodaLive/Sources/Debug/Utils/Constants.swift b/SodaLive/Sources/Debug/Utils/Constants.swift index 61bb709..f4d47dd 100644 --- a/SodaLive/Sources/Debug/Utils/Constants.swift +++ b/SodaLive/Sources/Debug/Utils/Constants.swift @@ -22,3 +22,6 @@ let NOTIFLY_PASSWORD = "c6c585db0aaa4189be44d0467c7d66b6@A" let APPSCHEME = "voiceon-test" let PAYVERSE_HTML_RESOURCE = "payverse_starter_debug" + +let GID_CLIENT_ID = "758414412471-3cf403jb4s405eu17qrfrcbs9ofhq369.apps.googleusercontent.com" +let GID_SERVER_CLIENT_ID = "758414412471-mosodbj2chno7l1j0iihldh6edmk0gk9.apps.googleusercontent.com" diff --git a/SodaLive/Sources/I18n/I18n.swift b/SodaLive/Sources/I18n/I18n.swift index de7ef87..e3aa15e 100644 --- a/SodaLive/Sources/I18n/I18n.swift +++ b/SodaLive/Sources/I18n/I18n.swift @@ -1101,6 +1101,42 @@ enum I18n { } } + enum Login { + enum Google { + static var openFailed: String { + pick( + ko: "구글 로그인 화면을 열 수 없습니다.\n다시 시도해 주세요.", + en: "Unable to open Google sign-in.\nPlease try again.", + ja: "Googleログイン画面を開けません。\nもう一度お試しください。" + ) + } + + static var configMissing: String { + pick( + ko: "구글 로그인 설정을 확인할 수 없습니다.\n다시 시도해 주세요.", + en: "Unable to find Google sign-in settings.\nPlease try again.", + ja: "Googleログイン設定を確認できません。\nもう一度お試しください。" + ) + } + + static var signInFailed: String { + pick( + ko: "구글 로그인에 실패했습니다.\n다시 시도해 주세요.", + en: "Google sign-in failed.\nPlease try again.", + ja: "Googleログインに失敗しました。\nもう一度お試しください。" + ) + } + + static var tokenMissing: String { + pick( + ko: "구글 인증 토큰을 가져오지 못했습니다.", + en: "Failed to retrieve Google token.", + ja: "Google認証トークンを取得できませんでした。" + ) + } + } + } + // 문자 메시지(Text Message) 관련 문자열 enum TextMessage { // 전송 버튼 라벨 diff --git a/SodaLive/Sources/User/Login/LoginView.swift b/SodaLive/Sources/User/Login/LoginView.swift index a0e8d0b..6461dfc 100644 --- a/SodaLive/Sources/User/Login/LoginView.swift +++ b/SodaLive/Sources/User/Login/LoginView.swift @@ -132,7 +132,12 @@ struct LoginView: View { Image("ic_login_kakao") .onTapGesture { hideKeyboard() - AppState.shared.setAppStep(step: .signUp) + } + + Image("ic_login_google") + .onTapGesture { + hideKeyboard() + viewModel.loginWithGoogle() } Image("ic_login_apple") diff --git a/SodaLive/Sources/User/Login/LoginViewModel.swift b/SodaLive/Sources/User/Login/LoginViewModel.swift index 0467209..903d542 100644 --- a/SodaLive/Sources/User/Login/LoginViewModel.swift +++ b/SodaLive/Sources/User/Login/LoginViewModel.swift @@ -12,6 +12,7 @@ import AuthenticationServices import CryptoKit import Security import UIKit +import GoogleSignIn final class LoginViewModel: NSObject, ObservableObject { private let appViewModel = AppViewModel() @@ -68,6 +69,35 @@ final class LoginViewModel: NSObject, ObservableObject { controller.presentationContextProvider = self controller.performRequests() } + + func loginWithGoogle() { + guard let presentingViewController = presentingViewController() else { + self.errorMessage = I18n.Login.Google.openFailed + self.isShowPopup = true + return + } + + GIDSignIn.sharedInstance.configuration = GIDConfiguration(clientID: GID_CLIENT_ID, serverClientID: GID_SERVER_CLIENT_ID) + + GIDSignIn.sharedInstance.signIn(withPresenting: presentingViewController) { result, error in + DispatchQueue.main.async { + if let error = error { + ERROR_LOG(error.localizedDescription) + self.errorMessage = I18n.Login.Google.signInFailed + self.isShowPopup = true + return + } + + guard let idToken = result?.user.idToken?.tokenString else { + self.errorMessage = I18n.Login.Google.tokenMissing + self.isShowPopup = true + return + } + + self.loginWithGoogle(idToken: idToken) + } + } + } private func loginWithApple(identityToken: String, nonce: String) { let pushToken = UserDefaults.string(forKey: .pushToken) @@ -95,6 +125,41 @@ final class LoginViewModel: NSObject, ObservableObject { } .store(in: &subscription) } + + private func loginWithGoogle(idToken: String) { + let pushToken = UserDefaults.string(forKey: .pushToken) + let marketingPid = UserDefaults.string(forKey: .marketingPid) + let request = SocialLoginRequest( + container: "ios", + pushToken: pushToken.isEmpty ? nil : pushToken, + marketingPid: marketingPid.isEmpty ? nil : marketingPid, + identityToken: nil, + nonce: nil + ) + + isLoading = true + repository.loginGoogle(request: request, idToken: idToken) + .sink { result in + switch result { + case .finished: + DEBUG_LOG("finish") + case .failure(let error): + self.isLoading = false + ERROR_LOG(error.localizedDescription) + } + } receiveValue: { response in + self.handleLoginResponse(response) + } + .store(in: &subscription) + } + + private func presentingViewController() -> UIViewController? { + return UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .flatMap { $0.windows } + .first { $0.isKeyWindow }? + .rootViewController + } private func handleLoginResponse(_ response: Response) { self.isLoading = false diff --git a/SodaLive/Sources/User/UserApi.swift b/SodaLive/Sources/User/UserApi.swift index 4d981ec..dbd8487 100644 --- a/SodaLive/Sources/User/UserApi.swift +++ b/SodaLive/Sources/User/UserApi.swift @@ -11,6 +11,7 @@ import Moya enum UserApi { case login(request: LoginRequest) case loginApple(request: SocialLoginRequest) + case loginGoogle(request: SocialLoginRequest, idToken: String) case signUp(request: SignUpRequest) case findPassword(request: ForgotPasswordRequest) case searchUser(nickname: String) @@ -50,6 +51,9 @@ extension UserApi: TargetType { case .loginApple: return "/member/login/apple" + + case .loginGoogle: + return "/member/login/google" case .signUp: return "/member/signup/v2" @@ -124,7 +128,7 @@ extension UserApi: TargetType { var method: Moya.Method { switch self { - case .login, .loginApple, .signUp, .findPassword, .notification, .logout, .logoutAllDevice, .signOut, .creatorFollow, .creatorUnFollow, .memberBlock, .memberUnBlock, + case .login, .loginApple, .loginGoogle, .signUp, .findPassword, .notification, .logout, .logoutAllDevice, .signOut, .creatorFollow, .creatorUnFollow, .memberBlock, .memberUnBlock, .profileImageUpdate: return .post @@ -143,6 +147,9 @@ extension UserApi: TargetType { case .loginApple(let request): return .requestJSONEncodable(request) + + case .loginGoogle(let request, _): + return .requestJSONEncodable(request) case .signUp(let request): return .requestJSONEncodable(request) @@ -208,6 +215,9 @@ extension UserApi: TargetType { switch self { case .login, .loginApple, .signUp, .findPassword: return nil + + case .loginGoogle(_, let idToken): + return ["Authorization": "Bearer \(idToken)"] default: return ["Authorization": "Bearer \(UserDefaults.string(forKey: UserDefaultsKey.token))"] diff --git a/SodaLive/Sources/User/UserRepository.swift b/SodaLive/Sources/User/UserRepository.swift index d707b1c..93cd542 100644 --- a/SodaLive/Sources/User/UserRepository.swift +++ b/SodaLive/Sources/User/UserRepository.swift @@ -20,6 +20,10 @@ final class UserRepository { func loginApple(request: SocialLoginRequest) -> AnyPublisher { return api.requestPublisher(.loginApple(request: request)) } + + func loginGoogle(request: SocialLoginRequest, idToken: String) -> AnyPublisher { + return api.requestPublisher(.loginGoogle(request: request, idToken: idToken)) + } func signUp(request: SignUpRequest) -> AnyPublisher { return api.requestPublisher(.signUp(request: request)) diff --git a/SodaLive/Sources/Utils/Constants.swift b/SodaLive/Sources/Utils/Constants.swift index 459d37f..2c63aaf 100644 --- a/SodaLive/Sources/Utils/Constants.swift +++ b/SodaLive/Sources/Utils/Constants.swift @@ -22,3 +22,6 @@ let NOTIFLY_PASSWORD = "c6c585db0aaa4189be44d0467c7d66b6@A" let APPSCHEME = "voiceon" let PAYVERSE_HTML_RESOURCE = "payverse_starter" + +let GID_CLIENT_ID = "983594297130-m6bv7lvc1lsetsvv3rk92etqc98uopqj.apps.googleusercontent.com" +let GID_SERVER_CLIENT_ID = "983594297130-5hrmkh6vpskeq6v34350kmilf74574h2.apps.googleusercontent.com"