diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index a22788079..ac0f99183 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -10,7 +10,10 @@ android { } dependencies { + implementation(projects.core.domain) implementation(libs.androidx.work.runtime.ktx) + implementation(libs.kakao.sdk.auth) + implementation(libs.kakao.sdk.user) // BiometricCryptoGate 가 BiometricPrompt.CryptoObject 를 공개 시그니처로 돌려주므로 // 소비 모듈의 컴파일 클래스패스에도 올라가야 한다 — implementation 이면 반환 타입을 못 본다. diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/account/social/KakaoLinkHelper.kt b/core/common/src/main/kotlin/com/afternote/core/common/auth/KakaoAccessToken.kt similarity index 58% rename from feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/account/social/KakaoLinkHelper.kt rename to core/common/src/main/kotlin/com/afternote/core/common/auth/KakaoAccessToken.kt index 7a4335826..facdc58dc 100644 --- a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/account/social/KakaoLinkHelper.kt +++ b/core/common/src/main/kotlin/com/afternote/core/common/auth/KakaoAccessToken.kt @@ -1,4 +1,4 @@ -package com.afternote.feature.setting.presentation.account.social +package com.afternote.core.common.auth import android.app.Activity import com.afternote.core.domain.error.CoreAuthFailure @@ -9,8 +9,22 @@ import com.kakao.sdk.user.UserApiClient import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume -suspend fun requestKakaoAccessToken(activity: Activity): Result = +/** + * 카카오 SDK로부터 OAuth 액세스 토큰을 획득한다. + * + * 플랫폼 SDK가 [Activity] 컨텍스트를 요구하므로 이 함수는 UI 레이어(화면)에서 호출한다. + * Data 레이어(Repository)에는 획득한 액세스 토큰 문자열만 전달되어, + * Data 레이어가 UI 컴포넌트(Activity)에 의존하지 않도록 한다. + * 카카오 로그인(온보딩)과 카카오 계정 연결(설정)이 같은 흐름을 쓰므로 feature 가 아닌 core 에 한 벌을 둔다 (#2194). + * + * 디바이스에 카카오톡이 설치된 경우 앱 로그인을, 그 외에는 웹 계정 로그인을 수행하며, + * 앱 로그인이 비정상 종료된 경우에는 웹 계정 로그인으로 폴백한다. + * 사용자가 로그인을 취소하면 폴백 없이 [CoreAuthFailure.UserCancelledAuth] 로 실패한다. + * 콜백 기반 SDK API를 [suspendCancellableCoroutine]으로 감싸 코루틴 흐름으로 변환한다. + */ +public suspend fun requestKakaoAccessToken(activity: Activity): Result = suspendCancellableCoroutine { continuation -> + // 공통 콜백: 카카오톡 앱 로그인·웹 계정 로그인 결과를 모두 처리한다. val callback: (OAuthToken?, Throwable?) -> Unit = { token, error -> if (continuation.isActive) { when { @@ -36,20 +50,24 @@ suspend fun requestKakaoAccessToken(activity: Activity): Result = } if (UserApiClient.instance.isKakaoTalkLoginAvailable(activity)) { + // 카카오톡 앱이 설치된 경우: 앱 로그인 시도 UserApiClient.instance.loginWithKakaoTalk(activity) { token, error -> if (error != null) { + // 사용자가 의도적으로 취소한 경우에는 폴백 없이 즉시 실패 처리 if (error is ClientError && error.reason == ClientErrorCause.Cancelled) { if (continuation.isActive) { continuation.resume(Result.failure(CoreAuthFailure.UserCancelledAuth())) } return@loginWithKakaoTalk } + // 그 외 에러(네트워크·앱 오류 등)는 웹 계정 로그인으로 폴백 UserApiClient.instance.loginWithKakaoAccount(activity, callback = callback) } else { callback(token, null) } } } else { + // 카카오톡 앱이 없는 경우: 곧바로 웹 계정 로그인 UserApiClient.instance.loginWithKakaoAccount(activity, callback = callback) } } diff --git a/feature/setting/presentation/build.gradle.kts b/feature/setting/presentation/build.gradle.kts index 4eb2c8004..abc770b98 100644 --- a/feature/setting/presentation/build.gradle.kts +++ b/feature/setting/presentation/build.gradle.kts @@ -33,8 +33,6 @@ dependencies { implementation(projects.core.ui) implementation(libs.androidx.hilt.navigation.compose) implementation(libs.androidx.compose.runtime) - implementation(libs.kakao.sdk.auth) - implementation(libs.kakao.sdk.user) implementation(libs.androidx.biometric) implementation(libs.androidx.credentials) implementation(libs.androidx.credentials.play.services.auth) diff --git a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/account/ConnectedAccountsScreen.kt b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/account/ConnectedAccountsScreen.kt index 6e6d6295f..38eafcf3a 100644 --- a/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/account/ConnectedAccountsScreen.kt +++ b/feature/setting/presentation/src/main/kotlin/com/afternote/feature/setting/presentation/account/ConnectedAccountsScreen.kt @@ -12,13 +12,13 @@ import androidx.compose.ui.res.stringResource import androidx.credentials.CredentialManager import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.afternote.core.common.auth.requestKakaoAccessToken import com.afternote.core.domain.error.CoreAuthFailure import com.afternote.core.ui.findActivity import com.afternote.feature.setting.presentation.BuildConfig import com.afternote.feature.setting.presentation.R import com.afternote.feature.setting.presentation.account.social.KakaoAuthResult import com.afternote.feature.setting.presentation.account.social.requestGoogleIdToken -import com.afternote.feature.setting.presentation.account.social.requestKakaoAccessToken import com.afternote.feature.setting.presentation.account.social.toKakaoAuthResult @Composable