From 6cc59e28938c4c422239e377890dfef50ce87e69 Mon Sep 17 00:00:00 2001 From: youjin09222 Date: Fri, 10 Jan 2025 19:35:12 +0900 Subject: [PATCH 01/20] =?UTF-8?q?#17=20[ADD]=20Kakao=20API=20Key=20?= =?UTF-8?q?=EC=88=A8=EA=B8=B0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1599634..805db48 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,4 +1,3 @@ - import java.util.Properties plugins { @@ -30,6 +29,8 @@ android { useSupportLibrary = true } buildConfigField("String", "BASE_URL", properties["base.url"].toString()) + buildConfigField("String", "KAKAO_API_KEY", properties["KAKAO_API_KEY"].toString()) + manifestPlaceholders["KAKAO_NATIVE_APP_KEY"] = properties["KAKAO_NATIVE_APP_KEY"].toString() } buildTypes { From 0a85b39436759cbcccd8b7440d242eb916a40ddf Mon Sep 17 00:00:00 2001 From: youjin09222 Date: Fri, 10 Jan 2025 19:36:54 +0900 Subject: [PATCH 02/20] =?UTF-8?q?#17=20[ADD]=20Kakao=20Redirect=20URI=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index efb91d4..589d36f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,6 +27,8 @@ + From 75d90c32071940b89d4001a24106d29b07fd3e1f Mon Sep 17 00:00:00 2001 From: youjin09222 Date: Fri, 10 Jan 2025 19:40:43 +0900 Subject: [PATCH 03/20] =?UTF-8?q?#17=20[FEAT]=20Kakao=20SDK=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/sopt/noostak/MyApp.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/com/sopt/noostak/MyApp.kt b/app/src/main/java/com/sopt/noostak/MyApp.kt index 30d6058..db483bb 100644 --- a/app/src/main/java/com/sopt/noostak/MyApp.kt +++ b/app/src/main/java/com/sopt/noostak/MyApp.kt @@ -1,6 +1,7 @@ package com.sopt.noostak import android.app.Application +import com.kakao.sdk.common.KakaoSdk import dagger.hilt.android.HiltAndroidApp import timber.log.Timber @@ -9,9 +10,14 @@ class MyApp : Application() { override fun onCreate() { super.onCreate() setTimber() + initKakao() } private fun setTimber() { Timber.plant(Timber.DebugTree()) } + + private fun initKakao() { + KakaoSdk.init(this, BuildConfig.KAKAO_API_KEY) + } } From 947d5c1bdd794679bd7b7c2e8158dc0fb13e100e Mon Sep 17 00:00:00 2001 From: youjin09222 Date: Fri, 10 Jan 2025 19:41:12 +0900 Subject: [PATCH 04/20] =?UTF-8?q?#17=20[ADD]=20LoginSideEffect=EC=97=90=20?= =?UTF-8?q?ShowToast=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sopt/presentation/auth/login/LoginSideEffect.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/presentation/src/main/java/com/sopt/presentation/auth/login/LoginSideEffect.kt b/presentation/src/main/java/com/sopt/presentation/auth/login/LoginSideEffect.kt index dbed59c..849d43e 100644 --- a/presentation/src/main/java/com/sopt/presentation/auth/login/LoginSideEffect.kt +++ b/presentation/src/main/java/com/sopt/presentation/auth/login/LoginSideEffect.kt @@ -1,6 +1,12 @@ package com.sopt.presentation.auth.login +import androidx.annotation.StringRes + sealed class LoginSideEffect { data object NavigateToHome : LoginSideEffect() data class NavigateSignUp(val authId: String) : LoginSideEffect() + data class ShowToast( + @StringRes val message: Int, + val args: String? = null + ) : LoginSideEffect() } From d9aeacb8ef5c777ca068582af03dab3ede6158eb Mon Sep 17 00:00:00 2001 From: youjin09222 Date: Fri, 10 Jan 2025 19:43:09 +0900 Subject: [PATCH 05/20] =?UTF-8?q?#17=20[REFACTOR]=20authId=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=EA=B0=92=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/sopt/presentation/auth/login/LoginViewModel.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt b/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt index 74066c6..4d31dcb 100644 --- a/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt +++ b/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt @@ -9,8 +9,8 @@ import javax.inject.Inject @HiltViewModel class LoginViewModel @Inject constructor() : BaseViewModel() { - private val _authId = MutableStateFlow(null) - val authId: StateFlow = _authId + private val _authId = MutableStateFlow("") + val authId: StateFlow = _authId fun kakaoLogin() { // TODO: 카카오 로그인 @@ -20,7 +20,7 @@ class LoginViewModel @Inject constructor() : BaseViewModel() { fun googleLogin() { // TODO: 구글 로그인 _authId.value = "google_access_token" - navigateToSignup(_authId.value.orEmpty()) + navigateToSignup(_authId.value) } private fun navigateToSignup(authId: String) { From 1877ed80f6b95010c79bbb4857f4a1f99ee4178c Mon Sep 17 00:00:00 2001 From: youjin09222 Date: Fri, 10 Jan 2025 19:44:21 +0900 Subject: [PATCH 06/20] =?UTF-8?q?#17=20[ADD]=20=EC=B9=B4=EC=B9=B4=EC=98=A4?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20string=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- presentation/src/main/res/values/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 72ba89c..70415f6 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -29,6 +29,9 @@ 이름을 입력해주세요 %1$d / %2$d 갤러리 권한이 필요합니다. + 카카오 로그인 성공 + 카카오 로그인 실패: %s + 로그인이 취소되었습니다. 그룹 초대 설명 이미지 From 4d5e1b428c8e8997f9d196d181e9b28043a79a9e Mon Sep 17 00:00:00 2001 From: youjin09222 Date: Fri, 10 Jan 2025 19:45:32 +0900 Subject: [PATCH 07/20] =?UTF-8?q?#17=20[FEAT]=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20=EB=A1=9C=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 --- .../presentation/auth/login/LoginViewModel.kt | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt b/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt index 4d31dcb..da53b28 100644 --- a/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt +++ b/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt @@ -1,6 +1,12 @@ package com.sopt.presentation.auth.login +import android.content.Context +import com.kakao.sdk.auth.model.OAuthToken +import com.kakao.sdk.common.model.ClientError +import com.kakao.sdk.common.model.ClientErrorCause +import com.kakao.sdk.user.UserApiClient import com.sopt.core.util.BaseViewModel +import com.sopt.presentation.R import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -12,9 +18,46 @@ class LoginViewModel @Inject constructor() : BaseViewModel() { private val _authId = MutableStateFlow("") val authId: StateFlow = _authId - fun kakaoLogin() { - // TODO: 카카오 로그인 - navigateToHome() + fun kakaoLogin(context: Context) { + if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) { + UserApiClient.instance.loginWithKakaoTalk(context) { token, error -> + handleKakaoLoginResult(token, error) { + UserApiClient.instance.loginWithKakaoAccount( + context, + callback = ::handleKakaoLoginResult + ) + } + } + } else { + UserApiClient.instance.loginWithKakaoAccount( + context, + callback = ::handleKakaoLoginResult + ) + } + } + + private fun handleKakaoLoginResult( + token: OAuthToken?, + error: Throwable?, + fallback: (() -> Unit)? = null + ) { + if (error != null) { + if (error is ClientError && error.reason == ClientErrorCause.Cancelled) { + emitSideEffect(LoginSideEffect.ShowToast(R.string.toast_login_cancelled)) + } else { + emitSideEffect( + LoginSideEffect.ShowToast( + R.string.toast_kakao_login_failed, + error.localizedMessage + ) + ) + fallback?.invoke() + } + } else if (token != null) { + _authId.value = token.accessToken + emitSideEffect(LoginSideEffect.ShowToast(R.string.toast_kakao_login_success)) + navigateToSignup(_authId.value) + } } fun googleLogin() { From 02e14d53e80ac2861e12d4c47af917c111b40a43 Mon Sep 17 00:00:00 2001 From: youjin09222 Date: Fri, 10 Jan 2025 19:49:39 +0900 Subject: [PATCH 08/20] =?UTF-8?q?#17=20[FEAT]=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/sopt/presentation/auth/login/LogInRoute.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/presentation/src/main/java/com/sopt/presentation/auth/login/LogInRoute.kt b/presentation/src/main/java/com/sopt/presentation/auth/login/LogInRoute.kt index 0b6eeff..6670855 100644 --- a/presentation/src/main/java/com/sopt/presentation/auth/login/LogInRoute.kt +++ b/presentation/src/main/java/com/sopt/presentation/auth/login/LogInRoute.kt @@ -13,6 +13,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -21,6 +22,7 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.sopt.core.designsystem.theme.NoostakAndroidTheme import com.sopt.core.designsystem.theme.NoostakTheme +import com.sopt.core.extension.toast import com.sopt.presentation.R import com.sopt.presentation.auth.component.LoginButton @@ -30,17 +32,20 @@ fun LoginRoute( navigateToSignUp: (String) -> Unit, loginViewModel: LoginViewModel = hiltViewModel() ) { + val context = LocalContext.current + LaunchedEffect(loginViewModel.sideEffects) { loginViewModel.sideEffects.collect { sideEffect -> when (sideEffect) { is LoginSideEffect.NavigateToHome -> navigateToHome() is LoginSideEffect.NavigateSignUp -> navigateToSignUp(sideEffect.authId) + is LoginSideEffect.ShowToast -> context.toast(sideEffect.message) } } } LoginScreen( - onKakaoLoginClick = loginViewModel::kakaoLogin, + onKakaoLoginClick = { loginViewModel.kakaoLogin(context) }, onGoogleLoginClick = loginViewModel::googleLogin ) } From 7a94617ef949cbe9f1eda23656a90e6e373a8692 Mon Sep 17 00:00:00 2001 From: youjin09222 Date: Fri, 10 Jan 2025 21:41:15 +0900 Subject: [PATCH 09/20] =?UTF-8?q?#17=20[FIX]=20=EC=95=B1=20=EC=84=A4?= =?UTF-8?q?=EC=B9=98=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 589d36f..5be0f34 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,10 +2,11 @@ - - + - + - + + + + + + + + + From c1636499b688023e54806fa60621994473050e25 Mon Sep 17 00:00:00 2001 From: youjin09222 Date: Sun, 12 Jan 2025 07:51:48 +0900 Subject: [PATCH 10/20] =?UTF-8?q?#17=20[REFACTOR]=20Kakao=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/auth/login/LoginViewModel.kt | 57 ++++++++----------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt b/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt index da53b28..d557abc 100644 --- a/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt +++ b/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt @@ -18,45 +18,38 @@ class LoginViewModel @Inject constructor() : BaseViewModel() { private val _authId = MutableStateFlow("") val authId: StateFlow = _authId + // Kakao Login fun kakaoLogin(context: Context) { + val loginCallback: (OAuthToken?, Throwable?) -> Unit = { token, error -> + handleKakaoLoginResult(token, error) + } + if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) { - UserApiClient.instance.loginWithKakaoTalk(context) { token, error -> - handleKakaoLoginResult(token, error) { - UserApiClient.instance.loginWithKakaoAccount( - context, - callback = ::handleKakaoLoginResult - ) - } - } + UserApiClient.instance.loginWithKakaoTalk(context, callback = loginCallback) } else { - UserApiClient.instance.loginWithKakaoAccount( - context, - callback = ::handleKakaoLoginResult - ) + UserApiClient.instance.loginWithKakaoAccount(context, callback = loginCallback) } } - private fun handleKakaoLoginResult( - token: OAuthToken?, - error: Throwable?, - fallback: (() -> Unit)? = null - ) { - if (error != null) { - if (error is ClientError && error.reason == ClientErrorCause.Cancelled) { - emitSideEffect(LoginSideEffect.ShowToast(R.string.toast_login_cancelled)) - } else { - emitSideEffect( - LoginSideEffect.ShowToast( - R.string.toast_kakao_login_failed, - error.localizedMessage - ) - ) - fallback?.invoke() + private fun handleKakaoLoginResult(token: OAuthToken?, error: Throwable?) { + when { + error != null -> { + handleKakaoError(error) + } + + token != null -> { + _authId.value = token.accessToken + emitSideEffect(LoginSideEffect.ShowToast(R.string.toast_kakao_login_success)) + navigateToSignup(_authId.value) } - } else if (token != null) { - _authId.value = token.accessToken - emitSideEffect(LoginSideEffect.ShowToast(R.string.toast_kakao_login_success)) - navigateToSignup(_authId.value) + } + } + + private fun handleKakaoError(error: Throwable) { + if (error is ClientError && error.reason == ClientErrorCause.Cancelled) { + emitSideEffect(LoginSideEffect.ShowToast(R.string.toast_login_cancelled)) + } else { + emitSideEffect(LoginSideEffect.ShowToast(R.string.toast_kakao_login_failed)) } } From d6fcf784c043127148fee13eec990ad248d3f58e Mon Sep 17 00:00:00 2001 From: youjin09222 Date: Sun, 12 Jan 2025 08:07:34 +0900 Subject: [PATCH 11/20] =?UTF-8?q?#17=20[ADD]=20Google=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 4 ++++ gradle/libs.versions.toml | 8 ++++++++ presentation/build.gradle.kts | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 805db48..29e79b2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -131,4 +131,8 @@ dependencies { // Kakao implementation(libs.kakao.all) implementation(libs.kakao.user) + + // Google + implementation(libs.play.services.auth) + implementation(libs.google.id) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9112c35..fc741a9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,6 +53,10 @@ landscapist = "2.3.6" # Kakao kakao = "2.20.1" +# Google +google-id = "1.1.1" +play-services-auth = "21.1.0" + viewpager-indicator = "5.0" # Hilt @@ -155,6 +159,10 @@ landscapist-animation = { group = "com.github.skydoves", name = "landscapist-ani kakao-all = { group = "com.kakao.sdk", name = "v2-all", version.ref = "kakao" } # Kakao SDK 라이브러리 kakao-user = { group = "com.kakao.sdk", name = "v2-user", version.ref = "kakao" } # Kakao 로그인 라이브러리 +# Google +play-services-auth = { group = "com.google.android.gms", name = "play-services-auth", version.ref = "play-services-auth" } +google-id = { group = "com.google.android.libraries.identity.googleid", name = "googleid", version.ref = "google-id" } + # Hilt hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } # Hilt Android 라이브러리 hilt-core = { group = "com.google.dagger", name = "hilt-core", version.ref = "hilt" } # Hilt Core 라이브러리 diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts index 5d4324a..beb9d82 100644 --- a/presentation/build.gradle.kts +++ b/presentation/build.gradle.kts @@ -111,4 +111,8 @@ dependencies { // Kakao implementation(libs.kakao.all) implementation(libs.kakao.user) + + // Google + implementation(libs.play.services.auth) + implementation(libs.google.id) } From 63cb4b78584bbfd8888e0e47c3fbb58a9dddb4f3 Mon Sep 17 00:00:00 2001 From: youjin09222 Date: Sun, 12 Jan 2025 08:09:03 +0900 Subject: [PATCH 12/20] =?UTF-8?q?#17=20[ADD]=20GoogleClientId=20=EC=88=A8?= =?UTF-8?q?=EA=B8=B0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 29e79b2..a39498f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -30,6 +30,7 @@ android { } buildConfigField("String", "BASE_URL", properties["base.url"].toString()) buildConfigField("String", "KAKAO_API_KEY", properties["KAKAO_API_KEY"].toString()) + buildConfigField("String", "GOOGLE_CLIENT_ID", properties["GOOGLE_CLIENT_ID"].toString()) manifestPlaceholders["KAKAO_NATIVE_APP_KEY"] = properties["KAKAO_NATIVE_APP_KEY"].toString() } From a5e8114fac357def082df35d91411332326a2fc3 Mon Sep 17 00:00:00 2001 From: youjin09222 Date: Sun, 12 Jan 2025 08:17:39 +0900 Subject: [PATCH 13/20] =?UTF-8?q?#17=20[ADD]=20GoogleWebClient=20ID=20?= =?UTF-8?q?=EC=A0=9C=EA=B3=B5=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/sopt/noostak/di/AuthModule.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 app/src/main/java/com/sopt/noostak/di/AuthModule.kt diff --git a/app/src/main/java/com/sopt/noostak/di/AuthModule.kt b/app/src/main/java/com/sopt/noostak/di/AuthModule.kt new file mode 100644 index 0000000..b06adc9 --- /dev/null +++ b/app/src/main/java/com/sopt/noostak/di/AuthModule.kt @@ -0,0 +1,20 @@ +package com.sopt.noostak.di + +import com.sopt.noostak.BuildConfig +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Named +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object AuthModule { + @Provides + @Singleton + @Named("GoogleClientId") + fun provideGoogleClientId(): String { + return BuildConfig.GOOGLE_CLIENT_ID + } +} From f2c6643f218eb9c5b53f267e4ea2461dae158621 Mon Sep 17 00:00:00 2001 From: youjin09222 Date: Sun, 12 Jan 2025 08:24:15 +0900 Subject: [PATCH 14/20] =?UTF-8?q?#17=20[FEAT]=20Google=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=ED=81=B4=EB=9D=BC=EC=9D=B4=EC=96=B8?= =?UTF-8?q?=ED=8A=B8=20=EC=B4=88=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sopt/presentation/auth/login/LoginViewModel.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt b/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt index d557abc..0156deb 100644 --- a/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt +++ b/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt @@ -1,6 +1,8 @@ package com.sopt.presentation.auth.login import android.content.Context +import com.google.android.gms.auth.api.identity.Identity +import com.google.android.gms.auth.api.identity.SignInClient import com.kakao.sdk.auth.model.OAuthToken import com.kakao.sdk.common.model.ClientError import com.kakao.sdk.common.model.ClientErrorCause @@ -17,6 +19,11 @@ class LoginViewModel @Inject constructor() : BaseViewModel() { private val _authId = MutableStateFlow("") val authId: StateFlow = _authId + private lateinit var oneTapClient: SignInClient + + fun initializeGoogleSignIn(context: Context) { + oneTapClient = Identity.getSignInClient(context) + } // Kakao Login fun kakaoLogin(context: Context) { From a0a3caeea58558b34d1ba21dd17ff43b3dd03a52 Mon Sep 17 00:00:00 2001 From: youjin09222 Date: Sun, 12 Jan 2025 08:34:57 +0900 Subject: [PATCH 15/20] =?UTF-8?q?#17=20[ADD]=20Google=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20string=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- presentation/src/main/res/values/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 70415f6..c6b9ff6 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -32,6 +32,8 @@ 카카오 로그인 성공 카카오 로그인 실패: %s 로그인이 취소되었습니다. + 구글 로그인 성공 + 구글 로그인 실패: %s 그룹 초대 설명 이미지 From 266ea84c8558f830fa8ca0a297f9166316ebee28 Mon Sep 17 00:00:00 2001 From: youjin09222 Date: Sun, 12 Jan 2025 08:37:19 +0900 Subject: [PATCH 16/20] =?UTF-8?q?#17=20[FEAT]=20=EA=B5=AC=EA=B8=80=20?= =?UTF-8?q?=EB=A1=9C=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 --- .../presentation/auth/login/LoginViewModel.kt | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt b/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt index 0156deb..191df16 100644 --- a/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt +++ b/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt @@ -1,8 +1,11 @@ package com.sopt.presentation.auth.login import android.content.Context +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.IntentSenderRequest import com.google.android.gms.auth.api.identity.Identity import com.google.android.gms.auth.api.identity.SignInClient +import com.google.android.gms.auth.api.identity.SignInCredential import com.kakao.sdk.auth.model.OAuthToken import com.kakao.sdk.common.model.ClientError import com.kakao.sdk.common.model.ClientErrorCause @@ -13,10 +16,12 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject +import javax.inject.Named @HiltViewModel -class LoginViewModel @Inject constructor() : BaseViewModel() { - +class LoginViewModel @Inject constructor( + @Named("GoogleClientId") private val googleClientId: String +) : BaseViewModel() { private val _authId = MutableStateFlow("") val authId: StateFlow = _authId private lateinit var oneTapClient: SignInClient @@ -60,10 +65,40 @@ class LoginViewModel @Inject constructor() : BaseViewModel() { } } - fun googleLogin() { - // TODO: 구글 로그인 - _authId.value = "google_access_token" - navigateToSignup(_authId.value) + // Google Login + fun googleLogin(launcher: ActivityResultLauncher) { + val signInRequest = com.google.android.gms.auth.api.identity.BeginSignInRequest.builder() + .setGoogleIdTokenRequestOptions( + com.google.android.gms.auth.api.identity.BeginSignInRequest.GoogleIdTokenRequestOptions.builder() + .setSupported(true) + .setServerClientId(googleClientId) + .setFilterByAuthorizedAccounts(false) + .build() + ) + .build() + + oneTapClient.beginSignIn(signInRequest) + .addOnSuccessListener { result -> + launcher.launch(IntentSenderRequest.Builder(result.pendingIntent).build()) + } + .addOnFailureListener { exception -> + emitSideEffect( + LoginSideEffect.ShowToast( + R.string.toast_google_login_failed, + exception.localizedMessage + ) + ) + } + } + + fun handleGoogleLoginResult(credential: SignInCredential) { + if (!credential.googleIdToken.isNullOrEmpty()) { + _authId.value = credential.googleIdToken.toString() + emitSideEffect(LoginSideEffect.ShowToast(R.string.toast_google_login_success)) + navigateToSignup(_authId.value) + } else { + emitSideEffect(LoginSideEffect.ShowToast(R.string.toast_google_login_failed)) + } } private fun navigateToSignup(authId: String) { From 51cc6f7a6a06b9914e0126abee48ed0321b3cb61 Mon Sep 17 00:00:00 2001 From: youjin09222 Date: Sun, 12 Jan 2025 08:49:29 +0900 Subject: [PATCH 17/20] =?UTF-8?q?#17=20[REFACTOR]=20Toast=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=ED=95=A8=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/auth/login/LoginViewModel.kt | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt b/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt index 191df16..d35b38d 100644 --- a/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt +++ b/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt @@ -3,6 +3,7 @@ package com.sopt.presentation.auth.login import android.content.Context import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.IntentSenderRequest +import androidx.annotation.StringRes import com.google.android.gms.auth.api.identity.Identity import com.google.android.gms.auth.api.identity.SignInClient import com.google.android.gms.auth.api.identity.SignInCredential @@ -45,13 +46,10 @@ class LoginViewModel @Inject constructor( private fun handleKakaoLoginResult(token: OAuthToken?, error: Throwable?) { when { - error != null -> { - handleKakaoError(error) - } - + error != null -> handleKakaoError(error) token != null -> { _authId.value = token.accessToken - emitSideEffect(LoginSideEffect.ShowToast(R.string.toast_kakao_login_success)) + showToast(R.string.toast_kakao_login_success) navigateToSignup(_authId.value) } } @@ -59,9 +57,9 @@ class LoginViewModel @Inject constructor( private fun handleKakaoError(error: Throwable) { if (error is ClientError && error.reason == ClientErrorCause.Cancelled) { - emitSideEffect(LoginSideEffect.ShowToast(R.string.toast_login_cancelled)) + showToast(R.string.toast_login_cancelled) } else { - emitSideEffect(LoginSideEffect.ShowToast(R.string.toast_kakao_login_failed)) + showToast(R.string.toast_kakao_login_failed, error.localizedMessage) } } @@ -82,22 +80,17 @@ class LoginViewModel @Inject constructor( launcher.launch(IntentSenderRequest.Builder(result.pendingIntent).build()) } .addOnFailureListener { exception -> - emitSideEffect( - LoginSideEffect.ShowToast( - R.string.toast_google_login_failed, - exception.localizedMessage - ) - ) + showToast(R.string.toast_google_login_failed, exception.localizedMessage) } } fun handleGoogleLoginResult(credential: SignInCredential) { if (!credential.googleIdToken.isNullOrEmpty()) { _authId.value = credential.googleIdToken.toString() - emitSideEffect(LoginSideEffect.ShowToast(R.string.toast_google_login_success)) + showToast(R.string.toast_google_login_success) navigateToSignup(_authId.value) } else { - emitSideEffect(LoginSideEffect.ShowToast(R.string.toast_google_login_failed)) + showToast(R.string.toast_google_login_failed) } } @@ -108,4 +101,13 @@ class LoginViewModel @Inject constructor( private fun navigateToHome() { emitSideEffect(LoginSideEffect.NavigateToHome) } + + private fun showToast(@StringRes messageResId: Int, vararg formatArgs: String) { + emitSideEffect( + LoginSideEffect.ShowToast( + message = messageResId, + args = formatArgs.joinToString(separator = ", ") + ) + ) + } } From f061f87f0cd2a4e29d3545a3ea69a4748188cbc9 Mon Sep 17 00:00:00 2001 From: youjin09222 Date: Sun, 12 Jan 2025 09:29:27 +0900 Subject: [PATCH 18/20] =?UTF-8?q?#17=20[FEAT]=20Google=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/auth/login/LogInRoute.kt | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/presentation/src/main/java/com/sopt/presentation/auth/login/LogInRoute.kt b/presentation/src/main/java/com/sopt/presentation/auth/login/LogInRoute.kt index 6670855..43461a1 100644 --- a/presentation/src/main/java/com/sopt/presentation/auth/login/LogInRoute.kt +++ b/presentation/src/main/java/com/sopt/presentation/auth/login/LogInRoute.kt @@ -1,5 +1,8 @@ package com.sopt.presentation.auth.login +import android.app.Activity +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.core.Animatable import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column @@ -20,6 +23,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import com.google.android.gms.auth.api.identity.Identity import com.sopt.core.designsystem.theme.NoostakAndroidTheme import com.sopt.core.designsystem.theme.NoostakTheme import com.sopt.core.extension.toast @@ -34,6 +38,22 @@ fun LoginRoute( ) { val context = LocalContext.current + val launcher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartIntentSenderForResult() + ) { result -> + if (result.resultCode == Activity.RESULT_OK && result.data != null) { + val credential = Identity.getSignInClient(context) + .getSignInCredentialFromIntent(result.data) + loginViewModel.handleGoogleLoginResult(credential) + } else { + context.toast(R.string.toast_login_cancelled) + } + } + + LaunchedEffect(Unit) { + loginViewModel.initializeGoogleSignIn(context) + } + LaunchedEffect(loginViewModel.sideEffects) { loginViewModel.sideEffects.collect { sideEffect -> when (sideEffect) { @@ -46,7 +66,7 @@ fun LoginRoute( LoginScreen( onKakaoLoginClick = { loginViewModel.kakaoLogin(context) }, - onGoogleLoginClick = loginViewModel::googleLogin + onGoogleLoginClick = { loginViewModel.googleLogin(launcher) } ) } From 2b3d78325ed4d1eee5d1ab642b670d9656830c19 Mon Sep 17 00:00:00 2001 From: youjin09222 Date: Sun, 12 Jan 2025 09:38:35 +0900 Subject: [PATCH 19/20] =?UTF-8?q?#17=20[REFACTOR]=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EA=B2=B0=EA=B3=BC=20=EC=B2=98=EB=A6=AC=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/auth/login/LoginViewModel.kt | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt b/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt index d35b38d..2e26b40 100644 --- a/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt +++ b/presentation/src/main/java/com/sopt/presentation/auth/login/LoginViewModel.kt @@ -34,7 +34,11 @@ class LoginViewModel @Inject constructor( // Kakao Login fun kakaoLogin(context: Context) { val loginCallback: (OAuthToken?, Throwable?) -> Unit = { token, error -> - handleKakaoLoginResult(token, error) + if (error != null) { + handleError(error, R.string.toast_kakao_login_failed) + } else if (token != null) { + handleSuccess(token.accessToken, R.string.toast_kakao_login_success) + } } if (UserApiClient.instance.isKakaoTalkLoginAvailable(context)) { @@ -44,25 +48,6 @@ class LoginViewModel @Inject constructor( } } - private fun handleKakaoLoginResult(token: OAuthToken?, error: Throwable?) { - when { - error != null -> handleKakaoError(error) - token != null -> { - _authId.value = token.accessToken - showToast(R.string.toast_kakao_login_success) - navigateToSignup(_authId.value) - } - } - } - - private fun handleKakaoError(error: Throwable) { - if (error is ClientError && error.reason == ClientErrorCause.Cancelled) { - showToast(R.string.toast_login_cancelled) - } else { - showToast(R.string.toast_kakao_login_failed, error.localizedMessage) - } - } - // Google Login fun googleLogin(launcher: ActivityResultLauncher) { val signInRequest = com.google.android.gms.auth.api.identity.BeginSignInRequest.builder() @@ -80,20 +65,32 @@ class LoginViewModel @Inject constructor( launcher.launch(IntentSenderRequest.Builder(result.pendingIntent).build()) } .addOnFailureListener { exception -> - showToast(R.string.toast_google_login_failed, exception.localizedMessage) + handleError(exception, R.string.toast_google_login_failed) } } fun handleGoogleLoginResult(credential: SignInCredential) { if (!credential.googleIdToken.isNullOrEmpty()) { - _authId.value = credential.googleIdToken.toString() - showToast(R.string.toast_google_login_success) - navigateToSignup(_authId.value) + handleSuccess(credential.googleIdToken.toString(), R.string.toast_google_login_success) } else { showToast(R.string.toast_google_login_failed) } } + private fun handleSuccess(authId: String, successMessageResId: Int) { + _authId.value = authId + showToast(successMessageResId) + navigateToSignup(authId) + } + + private fun handleError(error: Throwable, @StringRes errorMessageResId: Int) { + if (error is ClientError && error.reason == ClientErrorCause.Cancelled) { + showToast(R.string.toast_login_cancelled) + } else { + showToast(errorMessageResId, error.localizedMessage.orEmpty()) + } + } + private fun navigateToSignup(authId: String) { emitSideEffect(LoginSideEffect.NavigateSignUp(authId)) } From 97393e4759f0a229f3f16b2aec7a0c5572f498be Mon Sep 17 00:00:00 2001 From: youjin09222 Date: Sun, 12 Jan 2025 23:52:38 +0900 Subject: [PATCH 20/20] =?UTF-8?q?#17=20[MOD]=20=ED=8C=A8=EB=94=A9=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/sopt/presentation/auth/login/LogInRoute.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/presentation/src/main/java/com/sopt/presentation/auth/login/LogInRoute.kt b/presentation/src/main/java/com/sopt/presentation/auth/login/LogInRoute.kt index 43461a1..09df110 100644 --- a/presentation/src/main/java/com/sopt/presentation/auth/login/LogInRoute.kt +++ b/presentation/src/main/java/com/sopt/presentation/auth/login/LogInRoute.kt @@ -8,8 +8,10 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -80,7 +82,9 @@ fun LoginScreen( Column( modifier = Modifier .fillMaxSize() - .padding(dimensionResource(R.dimen.horizontal_padding)), + .padding(dimensionResource(R.dimen.horizontal_padding)) + .statusBarsPadding() + .navigationBarsPadding(), horizontalAlignment = Alignment.CenterHorizontally ) { Spacer(modifier = Modifier.weight(1f))