diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bebec272..00580221 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -32,6 +32,7 @@ android { getByName("debug") { isDebuggable = true applicationIdSuffix = ".dev" + buildConfigField("String", "GOOGLE_WEB_CLIENT_ID", getApiKey("DEBUG_GOOGLE_WEB_CLIENT_ID")) manifestPlaceholders += mapOf( "appName" to "@string/app_name_dev", ) @@ -42,6 +43,7 @@ android { isMinifyEnabled = true isShrinkResources = true signingConfig = signingConfigs.getByName("release") + buildConfigField("String", "GOOGLE_WEB_CLIENT_ID", getApiKey("RELEASE_GOOGLE_WEB_CLIENT_ID")) manifestPlaceholders += mapOf( "appName" to "@string/app_name", ) diff --git a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/constants/BookStatus.kt b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/constants/BookStatus.kt index 6745d507..2a5fe6c4 100644 --- a/core/common/src/main/kotlin/com/ninecraft/booket/core/common/constants/BookStatus.kt +++ b/core/common/src/main/kotlin/com/ninecraft/booket/core/common/constants/BookStatus.kt @@ -16,7 +16,7 @@ enum class BookStatus(val value: String) { } } - companion object Companion { + companion object { fun fromValue(value: String): BookStatus? { return entries.find { it.value == value } } diff --git a/core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/AuthRepository.kt b/core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/AuthRepository.kt index c8943668..a79b2f21 100644 --- a/core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/AuthRepository.kt +++ b/core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/AuthRepository.kt @@ -5,7 +5,10 @@ import com.ninecraft.booket.core.model.UserState import kotlinx.coroutines.flow.Flow interface AuthRepository { - suspend fun login(accessToken: String): Result + suspend fun login( + providerType: String, + token: String, + ): Result suspend fun logout(): Result diff --git a/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultAuthRepository.kt b/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultAuthRepository.kt index 4172d9e0..73e53d8b 100644 --- a/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultAuthRepository.kt +++ b/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultAuthRepository.kt @@ -12,19 +12,20 @@ import com.ninecraft.booket.core.di.DataScope import dev.zacsweers.metro.SingleIn import kotlinx.coroutines.flow.map -private const val KAKAO_PROVIDER_TYPE = "KAKAO" - @SingleIn(DataScope::class) @Inject class DefaultAuthRepository( private val service: ReedService, private val tokenDataSource: TokenDataSource, ) : AuthRepository { - override suspend fun login(accessToken: String) = runSuspendCatching { + override suspend fun login( + providerType: String, + token: String, + ) = runSuspendCatching { val response = service.login( LoginRequest( - providerType = KAKAO_PROVIDER_TYPE, - oauthToken = accessToken, + providerType = providerType, + oauthToken = token, ), ) saveTokens(response.accessToken, response.refreshToken) diff --git a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultNotificationDataSource.kt b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultNotificationDataSource.kt index 579426c8..1e8d2879 100644 --- a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultNotificationDataSource.kt +++ b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultNotificationDataSource.kt @@ -48,7 +48,7 @@ class DefaultNotificationDataSource( } } - companion object Companion { + companion object { private val USER_NOTIFICATION_ENABLED = booleanPreferencesKey("USER_NOTIFICATION_ENABLED") private val LAST_SYNCED_NOTIFICATION_ENABLED = booleanPreferencesKey("LAST_SYNCED_NOTIFICATION_ENABLED") } diff --git a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultTokenDataSource.kt b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultTokenDataSource.kt index c4b179f2..b492fc6e 100644 --- a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultTokenDataSource.kt +++ b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultTokenDataSource.kt @@ -64,7 +64,7 @@ class DefaultTokenDataSource( }.orEmpty() } - companion object Companion { + companion object { private val ACCESS_TOKEN = stringPreferencesKey("ACCESS_TOKEN") private val REFRESH_TOKEN = stringPreferencesKey("REFRESH_TOKEN") } diff --git a/core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/button/ButtonColorStyle.kt b/core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/button/ButtonColorStyle.kt index b4ddfb90..d3550498 100644 --- a/core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/button/ButtonColorStyle.kt +++ b/core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/component/button/ButtonColorStyle.kt @@ -8,16 +8,17 @@ import com.ninecraft.booket.core.designsystem.theme.Kakao import com.ninecraft.booket.core.designsystem.theme.ReedTheme enum class ReedButtonColorStyle { - PRIMARY, SECONDARY, TERTIARY, STROKE, TEXT, KAKAO; + PRIMARY, SECONDARY, TERTIARY, STROKE, TEXT, KAKAO, GOOGLE; @Composable fun containerColor(isPressed: Boolean) = when (this) { PRIMARY -> if (isPressed) ReedTheme.colors.bgPrimaryPressed else ReedTheme.colors.bgPrimary SECONDARY -> if (isPressed) ReedTheme.colors.bgSecondaryPressed else ReedTheme.colors.bgSecondary TERTIARY -> if (isPressed) ReedTheme.colors.bgTertiaryPressed else ReedTheme.colors.bgTertiary - STROKE -> if (isPressed) ReedTheme.colors.basePrimary else ReedTheme.colors.basePrimary + STROKE -> ReedTheme.colors.basePrimary TEXT -> Color.Transparent KAKAO -> Kakao + GOOGLE -> ReedTheme.colors.basePrimary } @Composable @@ -26,8 +27,9 @@ enum class ReedButtonColorStyle { SECONDARY -> ReedTheme.colors.contentPrimary TERTIARY -> ReedTheme.colors.contentBrand STROKE -> ReedTheme.colors.contentBrand - TEXT -> ReedTheme.colors.borderBrand + TEXT -> ReedTheme.colors.contentTertiary KAKAO -> ReedTheme.colors.contentPrimary + GOOGLE -> ReedTheme.colors.contentPrimary } @Composable @@ -42,6 +44,7 @@ enum class ReedButtonColorStyle { @Composable fun borderStroke() = when (this) { STROKE -> BorderStroke(1.dp, ReedTheme.colors.borderBrand) + GOOGLE -> BorderStroke(1.dp, ReedTheme.colors.borderPrimary) else -> null } } diff --git a/core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/theme/Color.kt b/core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/theme/Color.kt index 61997cc9..3d2d035e 100644 --- a/core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/theme/Color.kt +++ b/core/designsystem/src/main/kotlin/com/ninecraft/booket/core/designsystem/theme/Color.kt @@ -63,7 +63,7 @@ val Blue700 = Color(0xFF007BFF) val Blue800 = Color(0xFF1269EC) val Blue900 = Color(0xFF1F47CD) -val Kakao = Color(0xFFFBD300) +val Kakao = Color(0xFFFFEB00) val Blank = Color(0xFFD6D6D6) val HomeBg = Color(0xFFF0F9E8) diff --git a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUiState.kt b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUiState.kt index 550d0ee1..95343ae1 100644 --- a/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUiState.kt +++ b/feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUiState.kt @@ -98,7 +98,7 @@ enum class RecordSort(val value: String) { } } - companion object Companion { + companion object { fun fromValue(value: String): RecordSort? { return entries.find { it.value == value } } diff --git a/feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUiState.kt b/feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUiState.kt index cac40f41..4ab0366b 100644 --- a/feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUiState.kt +++ b/feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUiState.kt @@ -85,7 +85,7 @@ enum class LibraryFilterOption(val value: String) { } } - companion object Companion { + companion object { fun fromValue(value: String): LibraryFilterOption? { return entries.find { it.value == value } } diff --git a/feature/login/build.gradle.kts b/feature/login/build.gradle.kts index fa25f74d..31ab2096 100644 --- a/feature/login/build.gradle.kts +++ b/feature/login/build.gradle.kts @@ -1,5 +1,7 @@ @file:Suppress("INLINE_FROM_HIGHER_PLATFORM") +import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties + plugins { alias(libs.plugins.booket.android.feature) alias(libs.plugins.kotlin.serialization) @@ -8,11 +10,32 @@ plugins { android { namespace = "com.ninecraft.booket.feature.login" + + buildTypes { + getByName("debug") { + buildConfigField("String", "GOOGLE_WEB_CLIENT_ID", getApiKey("DEBUG_GOOGLE_WEB_CLIENT_ID")) + } + + getByName("release") { + buildConfigField("String", "GOOGLE_WEB_CLIENT_ID", getApiKey("RELEASE_GOOGLE_WEB_CLIENT_ID")) + } + } + + buildFeatures { + buildConfig = true + } } dependencies { implementations( libs.logger, libs.kakao.auth, + libs.androidx.credentials, + libs.androidx.credentials.play.services.auth, + libs.googleid, ) } + +fun getApiKey(propertyKey: String): String { + return gradleLocalProperties(rootDir, providers).getProperty(propertyKey) +} diff --git a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/HandleLoginSideEffects.kt b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/HandleLoginSideEffects.kt index 2e7b48f1..01251b43 100644 --- a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/HandleLoginSideEffects.kt +++ b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/HandleLoginSideEffects.kt @@ -3,8 +3,12 @@ package com.ninecraft.booket.feature.login import android.widget.Toast import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalContext +import com.ninecraft.booket.feature.login.client.GoogleLoginClient +import com.ninecraft.booket.feature.login.client.KakaoLoginClient import com.skydoves.compose.effects.RememberedEffect +import kotlinx.coroutines.launch @Composable internal fun HandleLoginSideEffects( @@ -12,7 +16,9 @@ internal fun HandleLoginSideEffects( eventSink: (LoginUiEvent) -> Unit, ) { val context = LocalContext.current + val scope = rememberCoroutineScope() val kakaoLoginClient = remember { KakaoLoginClient() } + val googleLoginClient = remember { GoogleLoginClient() } RememberedEffect(state.sideEffect) { when (state.sideEffect) { @@ -20,7 +26,12 @@ internal fun HandleLoginSideEffects( kakaoLoginClient.loginWithKakao( context = context, onSuccess = { token -> - eventSink(LoginUiEvent.Login(token)) + eventSink( + LoginUiEvent.Login( + providerType = LoginUiEvent.PROVIDER_TYPE_KAKAO, + token = token, + ), + ) }, onFailure = { errorMessage -> eventSink(LoginUiEvent.LoginFailure(errorMessage)) @@ -28,6 +39,26 @@ internal fun HandleLoginSideEffects( ) } + is LoginSideEffect.GoogleLogin -> { + scope.launch { + googleLoginClient.loginWithGoogle( + context = context, + webClientId = BuildConfig.GOOGLE_WEB_CLIENT_ID, + onSuccess = { idToken -> + eventSink( + LoginUiEvent.Login( + providerType = LoginUiEvent.PROVIDER_TYPE_GOOGLE, + token = idToken, + ), + ) + }, + onFailure = { errorMessage -> + eventSink(LoginUiEvent.LoginFailure(errorMessage)) + }, + ) + } + } + is LoginSideEffect.ShowToast -> { Toast.makeText(context, state.sideEffect.message, Toast.LENGTH_SHORT).show() } diff --git a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt index 6a717c49..9a7a21a0 100644 --- a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt +++ b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt @@ -85,6 +85,11 @@ class LoginPresenter( sideEffect = LoginSideEffect.KakaoLogin() } + is LoginUiEvent.OnGoogleLoginButtonClick -> { + isLoading = true + sideEffect = LoginSideEffect.GoogleLogin() + } + is LoginUiEvent.LoginFailure -> { isLoading = false analyticsHelper.logEvent(EVENT_ERROR_LOGIN) @@ -95,7 +100,7 @@ class LoginPresenter( scope.launch { try { isLoading = true - authRepository.login(event.accessToken) + authRepository.login(event.providerType, event.token) .onSuccess { userRepository.syncFcmToken() navigateAfterLogin() diff --git a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUi.kt b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUi.kt index 2b0d8f83..82cd656c 100644 --- a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUi.kt +++ b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUi.kt @@ -113,8 +113,30 @@ internal fun LoginUi( ) }, ) + Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing2)) + ReedButton( + onClick = { + state.eventSink(LoginUiEvent.OnGoogleLoginButtonClick) + }, + sizeStyle = largeButtonStyle, + colorStyle = ReedButtonColorStyle.GOOGLE, + modifier = Modifier + .fillMaxWidth() + .padding( + start = ReedTheme.spacing.spacing5, + end = ReedTheme.spacing.spacing5, + ), + text = stringResource(id = R.string.google_login), + leadingIcon = { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_google), + contentDescription = "Google Icon", + tint = Color.Unspecified, + ) + }, + ) Spacer( - modifier = Modifier.height(if (state.returnToScreen == null) ReedTheme.spacing.spacing2 else ReedTheme.spacing.spacing8), + modifier = Modifier.height(if (state.returnToScreen == null) ReedTheme.spacing.spacing3 else ReedTheme.spacing.spacing8), ) if (state.returnToScreen == null) { ReedTextButton( diff --git a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUiState.kt b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUiState.kt index 1f58e248..8c49aea8 100644 --- a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUiState.kt +++ b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUiState.kt @@ -16,6 +16,7 @@ data class LoginUiState( @Immutable sealed interface LoginSideEffect { data class KakaoLogin(private val key: String = UUID.randomUUID().toString()) : LoginSideEffect + data class GoogleLogin(private val key: String = UUID.randomUUID().toString()) : LoginSideEffect data class ShowToast( val message: String, private val key: String = UUID.randomUUID().toString(), @@ -24,8 +25,17 @@ sealed interface LoginSideEffect { sealed interface LoginUiEvent : CircuitUiEvent { data object OnKakaoLoginButtonClick : LoginUiEvent - data class Login(val accessToken: String) : LoginUiEvent + data object OnGoogleLoginButtonClick : LoginUiEvent + data class Login( + val providerType: String, + val token: String, + ) : LoginUiEvent data class LoginFailure(val message: String) : LoginUiEvent data object OnGuestLoginButtonClick : LoginUiEvent data object OnCloseButtonClick : LoginUiEvent + + companion object { + const val PROVIDER_TYPE_KAKAO = "KAKAO" + const val PROVIDER_TYPE_GOOGLE = "GOOGLE" + } } diff --git a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/client/GoogleLoginClient.kt b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/client/GoogleLoginClient.kt new file mode 100644 index 00000000..b3712be3 --- /dev/null +++ b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/client/GoogleLoginClient.kt @@ -0,0 +1,82 @@ +package com.ninecraft.booket.feature.login.client + +import android.content.Context +import androidx.credentials.CredentialManager +import androidx.credentials.CustomCredential +import androidx.credentials.GetCredentialRequest +import androidx.credentials.GetCredentialResponse +import androidx.credentials.exceptions.GetCredentialCancellationException +import androidx.credentials.exceptions.GetCredentialException +import androidx.credentials.exceptions.NoCredentialException +import com.google.android.libraries.identity.googleid.GetGoogleIdOption +import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential +import com.ninecraft.booket.core.designsystem.R +import com.orhanobut.logger.Logger +import dev.zacsweers.metro.Inject + +@Inject +internal class GoogleLoginClient { + suspend fun loginWithGoogle( + context: Context, + webClientId: String, + onSuccess: (String) -> Unit, + onFailure: (String) -> Unit, + ) { + val credentialManager = CredentialManager.create(context) + + val googleIdOption = GetGoogleIdOption.Builder() + .setFilterByAuthorizedAccounts(false) + .setServerClientId(webClientId) + .setAutoSelectEnabled(true) + .build() + + val credentialRequest = GetCredentialRequest.Builder() + .addCredentialOption(googleIdOption) + .build() + + try { + val result = credentialManager.getCredential( + request = credentialRequest, + context = context, + ) + handleSignIn(result, onSuccess, onFailure, context) + } catch (e: GetCredentialCancellationException) { + Logger.e("Google 로그인 취소됨, ${e.message}") + onFailure(context.getString(R.string.unknown_error_message)) + } catch (e: NoCredentialException) { + Logger.e("Google 계정을 찾을 수 없음, ${e.message}") + onFailure(context.getString(R.string.unknown_error_message)) + } catch (e: GetCredentialException) { + Logger.e("Google 로그인 실패, ${e.message}") + onFailure(context.getString(R.string.unknown_error_message)) + } catch (e: Exception) { + Logger.e("알 수 없는 오류: ${e.message}") + onFailure(context.getString(R.string.unknown_error_message)) + } + } + + private fun handleSignIn( + result: GetCredentialResponse, + onSuccess: (String) -> Unit, + onFailure: (String) -> Unit, + context: Context, + ) { + val credential = result.credential + + if (credential is CustomCredential && + credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) { + try { + val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.data) + val idToken = googleIdTokenCredential.idToken + Logger.d("Google 로그인 성공: ${googleIdTokenCredential.id}") + onSuccess(idToken) + } catch (e: Exception) { + Logger.e("Google ID Token 파싱 실패: ${e.message}") + onFailure(context.getString(R.string.unknown_error_message)) + } + } else { + Logger.e("예상치 못한 credential type: ${credential.type}") + onFailure(context.getString(R.string.unknown_error_message)) + } + } +} diff --git a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/KakaoLoginClient.kt b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/client/KakaoLoginClient.kt similarity index 79% rename from feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/KakaoLoginClient.kt rename to feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/client/KakaoLoginClient.kt index 6f1b7d22..9334d1a6 100644 --- a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/KakaoLoginClient.kt +++ b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/client/KakaoLoginClient.kt @@ -1,12 +1,12 @@ -package com.ninecraft.booket.feature.login +package com.ninecraft.booket.feature.login.client import android.content.Context import com.kakao.sdk.auth.model.OAuthToken import com.kakao.sdk.common.model.AuthError import com.kakao.sdk.user.UserApiClient -import com.ninecraft.booket.core.designsystem.R as designR -import dev.zacsweers.metro.Inject +import com.ninecraft.booket.core.designsystem.R import com.orhanobut.logger.Logger +import dev.zacsweers.metro.Inject @Inject internal class KakaoLoginClient { @@ -19,7 +19,7 @@ internal class KakaoLoginClient { when { error != null -> handleLoginError(context, error, onFailure) token != null -> handleLoginSuccess(token, onSuccess, onFailure, context) - else -> onFailure(context.getString(designR.string.unknown_error_message)) + else -> onFailure(context.getString(R.string.unknown_error_message)) } } @@ -38,12 +38,12 @@ internal class KakaoLoginClient { when { (error is AuthError && error.response.error == "ProtocolError") -> { Logger.e("로그인 실패: ${error.response.error}, ${error.response.errorDescription}") - onFailure(context.getString(designR.string.network_error_message)) + onFailure(context.getString(R.string.network_error_message)) } else -> { Logger.e("로그인 실패: ${error.message}") - onFailure(context.getString(designR.string.unknown_error_message)) + onFailure(context.getString(R.string.unknown_error_message)) } } } @@ -57,7 +57,7 @@ internal class KakaoLoginClient { UserApiClient.instance.me { user, _ -> user?.let { onSuccess(token.accessToken) - } ?: onFailure(context.getString(designR.string.unknown_error_message)) + } ?: onFailure(context.getString(R.string.unknown_error_message)) } } } diff --git a/feature/login/src/main/res/drawable/ic_google.xml b/feature/login/src/main/res/drawable/ic_google.xml new file mode 100644 index 00000000..4b106792 --- /dev/null +++ b/feature/login/src/main/res/drawable/ic_google.xml @@ -0,0 +1,26 @@ + + + + + + + diff --git a/feature/login/src/main/res/values/strings.xml b/feature/login/src/main/res/values/strings.xml index 51ac38df..32c3f405 100644 --- a/feature/login/src/main/res/values/strings.xml +++ b/feature/login/src/main/res/values/strings.xml @@ -2,6 +2,7 @@ 책 덮기 전 한 문장을 기록해보세요 카카오로 시작하기 + Google로 시작하기 약관 동의 후\n독서 기록을 남겨보세요 약관 전체 동의 시작하기 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b3f5690b..5a739351 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -51,6 +51,10 @@ logger = "2.2.0" ## Kakao Login kakao-core = "2.22.0" +## Google Credential Manager +androidx-credentials = "1.6.0-beta03" +googleid = "1.1.1" + ## Image Load coil-compose = "2.7.0" landscapist = "2.5.1" @@ -138,6 +142,10 @@ detekt-formatting = { group = "io.gitlab.arturbosch.detekt", name = "detekt-form kakao-auth = { group = "com.kakao.sdk", name = "v2-user", version.ref = "kakao-core" } +androidx-credentials = { group = "androidx.credentials", name = "credentials", version.ref = "androidx-credentials" } +androidx-credentials-play-services-auth = { group = "androidx.credentials", name = "credentials-play-services-auth", version.ref = "androidx-credentials" } +googleid = { group = "com.google.android.libraries.identity.googleid", name = "googleid", version.ref = "googleid" } + lottie-compose = { module = "com.airbnb.android:lottie-compose", version.ref = "lottie" } androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" }