diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb434..ea1e3875 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,7 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +-keep class * extends com.google.gson.TypeAdapter +-keep class com.google.googlesignin.** { *; } \ No newline at end of file diff --git a/build-logic/convention/src/main/java/com/teamsolply/solply/convention/BuildConfig.kt b/build-logic/convention/src/main/java/com/teamsolply/solply/convention/BuildConfig.kt index ab0598c0..b6528594 100644 --- a/build-logic/convention/src/main/java/com/teamsolply/solply/convention/BuildConfig.kt +++ b/build-logic/convention/src/main/java/com/teamsolply/solply/convention/BuildConfig.kt @@ -34,6 +34,11 @@ internal fun Project.configureBuildConfig( "NAVER_DEVELOPERS_CLIENT_SECRET", gradleLocalProperties(rootDir, providers).getProperty("naver.developers.client.secret") ) + buildConfigField( + "String", + "GOOGLE_WEB_CLIENT_ID", + gradleLocalProperties(rootDir, providers).getProperty("google.web.client.id") + ) } buildFeatures { diff --git a/core/buildconfig/src/main/java/com/teamsolply/solply/buildconfig/impl/BuildConfigFieldsProviderImpl.kt b/core/buildconfig/src/main/java/com/teamsolply/solply/buildconfig/impl/BuildConfigFieldsProviderImpl.kt index 85469c5b..d4d765bd 100644 --- a/core/buildconfig/src/main/java/com/teamsolply/solply/buildconfig/impl/BuildConfigFieldsProviderImpl.kt +++ b/core/buildconfig/src/main/java/com/teamsolply/solply/buildconfig/impl/BuildConfigFieldsProviderImpl.kt @@ -1,6 +1,7 @@ package com.teamsolply.solply.buildconfig.impl import com.teamsolply.solply.buildconfig.BuildConfig.BASE_URL +import com.teamsolply.solply.buildconfig.BuildConfig.GOOGLE_WEB_CLIENT_ID import com.teamsolply.solply.buildconfig.BuildConfig.KAKAO_NATIVE_KEY import com.teamsolply.solply.buildconfig.BuildConfig.NAVER_CLIENT_ID import com.teamsolply.solply.buildconfig.BuildConfig.NAVER_DEVELOPERS_CLIENT_ID @@ -14,6 +15,7 @@ class BuildConfigFieldsProviderImpl @Inject constructor() : BuildConfigFieldProv BuildConfigFields( baseUrl = BASE_URL, kakaoNativeKey = KAKAO_NATIVE_KEY, + googleWebClientId = GOOGLE_WEB_CLIENT_ID, naverClientId = NAVER_CLIENT_ID, naverDevelopersClientId = NAVER_DEVELOPERS_CLIENT_ID, naverDevelopersClientSecret = NAVER_DEVELOPERS_CLIENT_SECRET, diff --git a/core/common/src/main/java/com/teamsolply/solply/common/buildconfig/BuildConfigFields.kt b/core/common/src/main/java/com/teamsolply/solply/common/buildconfig/BuildConfigFields.kt index 16de764e..6b8875a1 100644 --- a/core/common/src/main/java/com/teamsolply/solply/common/buildconfig/BuildConfigFields.kt +++ b/core/common/src/main/java/com/teamsolply/solply/common/buildconfig/BuildConfigFields.kt @@ -3,6 +3,7 @@ package com.teamsolply.solply.common.buildconfig data class BuildConfigFields( val baseUrl: String, val kakaoNativeKey: String, + val googleWebClientId: String, val naverClientId: String, val naverDevelopersClientId: String, val naverDevelopersClientSecret: String, diff --git a/feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/ProfileEditScreen.kt b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/ProfileEditScreen.kt index 914d1712..01076658 100644 --- a/feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/ProfileEditScreen.kt +++ b/feature/mypage/src/main/java/com/teamsolply/solply/mypage/profile/ProfileEditScreen.kt @@ -127,7 +127,7 @@ fun ProfileEditScreen( onBackButtonClick = onBackButtonClick ) Spacer( - modifier = Modifier.weight(1f) + modifier = Modifier.height(16.dp) ) Image( painter = painterResource(R.drawable.img_basic_profile), @@ -138,7 +138,7 @@ fun ProfileEditScreen( .clip(CircleShape), contentScale = ContentScale.Fit ) - Spacer(modifier = Modifier.weight(2f)) + Spacer(modifier = Modifier.height(32.dp)) Column( modifier = Modifier.padding(horizontal = 16.dp), verticalArrangement = Arrangement.Top, @@ -166,7 +166,7 @@ fun ProfileEditScreen( modifier = Modifier.padding(top = 12.dp) ) } - Spacer(modifier = Modifier.weight(1.5f)) + Spacer(modifier = Modifier.height(24.dp)) Column( modifier = Modifier.padding(horizontal = 16.dp), verticalArrangement = Arrangement.Top, diff --git a/feature/oauth/build.gradle.kts b/feature/oauth/build.gradle.kts index 10e4b72a..5630c3b9 100644 --- a/feature/oauth/build.gradle.kts +++ b/feature/oauth/build.gradle.kts @@ -1,3 +1,5 @@ +import com.teamsolply.solply.convention.extension.implementation + plugins { alias(libs.plugins.solply.feature) } @@ -8,5 +10,9 @@ android { dependencies { implementation(libs.kakao.login) + implementation(libs.google.id) + implementation(libs.credentials.play.auth) + implementation(libs.credentials) implementation(projects.domain.oauth) + implementation(projects.core.buildconfig) } diff --git a/feature/oauth/src/main/java/com/teamsolply/solply/oauth/GoogleLoginHelper.kt b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/GoogleLoginHelper.kt new file mode 100644 index 00000000..83adb44e --- /dev/null +++ b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/GoogleLoginHelper.kt @@ -0,0 +1,88 @@ +package com.teamsolply.solply.oauth + +import android.content.Context +import android.util.Log +import androidx.credentials.CredentialManager +import androidx.credentials.CustomCredential +import androidx.credentials.GetCredentialRequest +import androidx.credentials.GetCredentialResponse +import androidx.credentials.exceptions.GetCredentialException +import com.google.android.libraries.identity.googleid.GetGoogleIdOption +import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential +import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.util.UUID + +class GoogleLoginHelper( + private val context: Context +) { + + companion object { + const val TAG = "GoogleLogin" + const val WEB_CLIENT_ID = com.teamsolply.solply.buildconfig.BuildConfig.GOOGLE_WEB_CLIENT_ID + // const val SERVER_URL = "" + } + + private val credentialManager: CredentialManager = CredentialManager.create(context) + private val nonce = UUID.randomUUID().toString() + private val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder() + .setServerClientId(WEB_CLIENT_ID) // 웹 클라이언트 ID + .setFilterByAuthorizedAccounts(false) +// .setNonce(nonce) + .build() + private val request: GetCredentialRequest = GetCredentialRequest.Builder() + .addCredentialOption(googleIdOption) + .build() + private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main) + + fun requestGoogleLogin( + onSuccess: (String) -> Unit, + onFailure: (String) -> Unit + ) { + coroutineScope.launch { + try { + val result = credentialManager.getCredential( + request = request, + context = context + ) + handleSignInResult(result, onSuccess, onFailure) + } catch (e: GetCredentialException) { + Log.e("Google Sign-in failed", " ${e.localizedMessage}") + } + } + } + + private fun handleSignInResult( + result: GetCredentialResponse, + onSuccess: (String) -> Unit, + onFailure: (String) -> Unit + ) { + when (val credential = result.credential) { + is CustomCredential -> { + if (credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) { + try { + val googleIdTokenCredential = + GoogleIdTokenCredential.createFrom(credential.data) + val idToken = googleIdTokenCredential.idToken +// Log.d(TAG, idToken) //토큰 +// Log.d(TAG, googleIdTokenCredential.id) //이메일 +// googleIdTokenCredential.displayName?.let { Log.d(TAG, it) } //이름 + onSuccess(idToken) // 성공 시 처리 함수, 서버 응답 후 실행, 여기서는 테스트를 위해 이곳에서 실행 + } catch (e: GoogleIdTokenParsingException) { +// Log.e(TAG, "Received an invalid google id token response", e) + } + } else { +// Log.e(TAG, "Unexpected type of credential") + onFailure("구글 로그인에 실패하였습니다. 다시 시도해주세요.") + } + } + + else -> { +// Log.e(TAG, "Unexpected type of credential") + onFailure("구글 로그인에 실패하였습니다. 다시 시도해주세요.") + } + } + } +} diff --git a/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthContract.kt b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthContract.kt index 5d6bbc03..60da17d4 100644 --- a/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthContract.kt +++ b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthContract.kt @@ -12,6 +12,9 @@ sealed interface OauthIntent : UiIntent { data object KakaoLoginClick : OauthIntent data class KakaoLoginSuccess(val provider: String, val accessToken: String) : OauthIntent data class KakaoLoginFailure(val error: Throwable) : OauthIntent + data object GoogleLoginClick : OauthIntent + data class GoogleLoginSuccess(val provider: String, val accessToken: String) : OauthIntent + data class GoogleLoginFailure(val error: Throwable) : OauthIntent data class SaveJwtToken( val accessToken: String, val refreshToken: String, @@ -21,6 +24,7 @@ sealed interface OauthIntent : UiIntent { sealed interface OauthSideEffect : SideEffect { data object StartKakaoLogin : OauthSideEffect + data object StartGoogleLogin : OauthSideEffect data object NavigateToOnBoarding : OauthSideEffect data object NavigateToPlace : OauthSideEffect } diff --git a/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthScreen.kt b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthScreen.kt index d155ea1e..26e5f0f9 100644 --- a/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthScreen.kt +++ b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -46,6 +47,7 @@ fun OauthRoute( viewModel: OauthViewModel = hiltViewModel() ) { val context = LocalContext.current + val googleLoginHelper = remember { GoogleLoginHelper(context = context) } val uiState by viewModel.uiState.collectAsStateWithLifecycle() LaunchedEffectWithLifecycle { @@ -63,6 +65,18 @@ fun OauthRoute( } ) + OauthSideEffect.StartGoogleLogin -> googleLoginHelper.requestGoogleLogin( + onSuccess = { accessToken -> + viewModel.sendIntent( + OauthIntent.GoogleLoginSuccess( + provider = "GOOGLE", + accessToken = accessToken + ) + ) + }, + onFailure = {} + ) + OauthSideEffect.NavigateToOnBoarding -> navigateToOnBoarding() OauthSideEffect.NavigateToPlace -> navigateToPlace() } @@ -70,13 +84,27 @@ fun OauthRoute( } OauthScreen( - kakaoLoginClick = { viewModel.sendIntent(OauthIntent.KakaoLoginClick) } + kakaoLoginClick = { viewModel.sendIntent(OauthIntent.KakaoLoginClick) }, + googleLoginClick = { + googleLoginHelper.requestGoogleLogin( + onSuccess = { accessToken -> + viewModel.sendIntent( + OauthIntent.GoogleLoginSuccess( + provider = "GOOGLE", + accessToken = accessToken + ) + ) + }, + onFailure = {} + ) + } ) } @Composable fun OauthScreen( kakaoLoginClick: () -> Unit, + googleLoginClick: () -> Unit, modifier: Modifier = Modifier ) { Column( @@ -157,7 +185,7 @@ fun OauthScreen( .customClickable( rippleEnabled = false ) { - kakaoLoginClick() + googleLoginClick() }, verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start diff --git a/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthViewModel.kt b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthViewModel.kt index b9c97cd3..1637b5b8 100644 --- a/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthViewModel.kt +++ b/feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthViewModel.kt @@ -24,6 +24,17 @@ class OauthViewModel @Inject constructor( TODO() } + OauthIntent.GoogleLoginClick -> postSideEffect(OauthSideEffect.StartGoogleLogin) + + is OauthIntent.GoogleLoginSuccess -> postSocialLogin( + provider = intent.provider, + oauthAccessToken = intent.accessToken + ) + + is OauthIntent.GoogleLoginFailure -> { + TODO() + } + is OauthIntent.SaveJwtToken -> { viewModelScope.launch { oauthRepository.saveJwtToken( diff --git a/feature/oauth/src/main/res/values/strings.xml b/feature/oauth/src/main/res/values/strings.xml index 1bc495ae..c686eb09 100644 --- a/feature/oauth/src/main/res/values/strings.xml +++ b/feature/oauth/src/main/res/values/strings.xml @@ -2,4 +2,5 @@ 카카오로 계속하기 Google로 계속하기 + Apple로 계속하기 \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b9b8ad48..cc1293c0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -90,6 +90,8 @@ coil = "2.7.0" lottie = "6.4.1" jsoup = "1.17.2" kakao-login = "2.19.0" +credentials = "1.6.0-beta03" +googleid = "1.1.1" process-pheonix = "3.0.0" preference = "1.2.1" collapsing-toolbar = "2.3.5" @@ -253,6 +255,11 @@ lottie = { group = "com.airbnb.android", name = "lottie", version.ref = "lottie" lottie-compose = { group = "com.airbnb.android", name = "lottie-compose", version.ref = "lottie" } jsoup = { group = "org.jsoup", name = "jsoup", version.ref = "jsoup" } kakao-login = { group = "com.kakao.sdk", name = "v2-user", version.ref = "kakao-login" } + +credentials = { module = "androidx.credentials:credentials", version.ref = "credentials" } +credentials-play-auth = { module = "androidx.credentials:credentials-play-services-auth", version.ref = "credentials" } +google-id = { module = "com.google.android.libraries.identity.googleid:googleid", version.ref = "googleid" } + process-phoenix = { module = "com.jakewharton:process-phoenix", version.ref = "process-pheonix" } collapsing-toolbar = { group = "me.onebone", name = "toolbar-compose", version.ref = "collapsing-toolbar" } androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroid" }