From 50db4e445d496799a26612b7b670f8f9079badde Mon Sep 17 00:00:00 2001 From: SYAAINN Date: Fri, 1 Aug 2025 16:11:12 +0900 Subject: [PATCH 1/8] =?UTF-8?q?[REFACTOR/#314]=20=EA=B5=AC=EA=B8=80=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=B0=8F=20=EA=B3=84=EC=A0=95=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20=EB=B6=80=EB=B6=84?= =?UTF-8?q?=EC=9D=84=20=EC=88=98=EC=A0=95=ED=95=A9=EB=8B=88=EB=8B=A4.=20-?= =?UTF-8?q?=20OAuthProvider=20=ED=81=B4=EB=9E=98=EC=8A=A4=EC=9D=98=20apiVa?= =?UTF-8?q?lue=20=EC=86=8D=EC=84=B1=EB=AA=85=EC=9D=84platform=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD=ED=95=98=EC=97=AC=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=EC=99=80=20=ED=86=B5=EC=9D=BC=ED=95=98=EA=B3=A0,=20?= =?UTF-8?q?=EC=A7=81=EA=B4=80=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=ED=96=88=EC=8A=B5=EB=8B=88=EB=8B=A4.=20-=20UserInfoRe?= =?UTF-8?q?sponseDto=EB=8A=94=20=EC=84=9C=EB=B2=84=EB=A1=9C=20=EC=98=A4?= =?UTF-8?q?=EB=8A=94=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=ED=83=80=EC=9E=85?= =?UTF-8?q?=EA=B3=BC=20=EC=B5=9C=EB=8C=80=ED=95=9C=20=EC=9C=A0=EC=82=AC?= =?UTF-8?q?=ED=95=98=EA=B2=8C=20=EB=B0=9B=EA=B3=A0=20OAuthProvider=20?= =?UTF-8?q?=EB=93=B1=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EB=A1=9C=20=EB=A7=A4=ED=95=91=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=EC=9D=80=20=EC=B6=94=ED=9B=84=20Domain=20Lay?= =?UTF-8?q?er=EB=A5=BC=20=EC=9D=B4=EC=9A=A9=ED=95=98=EB=8A=94=EA=B2=8C=20?= =?UTF-8?q?=EC=A2=8B=EC=9D=84=20=EA=B2=83=20=EA=B0=99=EB=8B=A4=20=EC=83=9D?= =?UTF-8?q?=EA=B0=81=EB=90=98=EC=96=B4=20=EC=88=98=EC=A0=95=ED=96=88?= =?UTF-8?q?=EC=8A=B5=EB=8B=88=EB=8B=A4.=20-=20=EC=84=9C=EB=B2=84=EC=99=80?= =?UTF-8?q?=20=ED=98=91=EC=9D=98=20=ED=9B=84=EC=97=90=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20API=EC=9D=98=20404=20=EA=B2=B0=EA=B3=BC=EB=A5=BC=20?= =?UTF-8?q?=ED=95=B8=EB=93=A4=EB=A7=81=ED=95=98=EC=97=AC=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EA=B0=80=EC=9E=85=20=EB=B6=84=EA=B8=B0=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=EB=A5=BC=20=ED=95=98=EB=8F=84=EB=A1=9D=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC=ED=96=88=EC=8A=B5=EB=8B=88=EB=8B=A4.=20(500=EC=82=AD?= =?UTF-8?q?=EC=A0=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../clody/data/datastore/OAuthProvider.kt | 2 +- .../dto/response/UserInfoResponseDto.kt | 3 +- .../ui/auth/signup/SignUpViewModel.kt | 4 +-- .../ui/login/GoogleSignInHelper.kt | 29 ++++++++++--------- .../presentation/ui/login/LoginScreen.kt | 4 +-- .../presentation/ui/login/LoginViewModel.kt | 6 ++-- .../AccountManagementLogoutOption.kt | 7 ++--- .../screen/AccountManagementViewModel.kt | 3 +- 8 files changed, 27 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/sopt/clody/data/datastore/OAuthProvider.kt b/app/src/main/java/com/sopt/clody/data/datastore/OAuthProvider.kt index c8c57020..e4151077 100644 --- a/app/src/main/java/com/sopt/clody/data/datastore/OAuthProvider.kt +++ b/app/src/main/java/com/sopt/clody/data/datastore/OAuthProvider.kt @@ -1,6 +1,6 @@ package com.sopt.clody.data.datastore -enum class OAuthProvider(val apiValue: String) { +enum class OAuthProvider(val platform: String) { GOOGLE("google"), KAKAO("kakao"), } diff --git a/app/src/main/java/com/sopt/clody/data/remote/dto/response/UserInfoResponseDto.kt b/app/src/main/java/com/sopt/clody/data/remote/dto/response/UserInfoResponseDto.kt index ba4297e9..558e2797 100644 --- a/app/src/main/java/com/sopt/clody/data/remote/dto/response/UserInfoResponseDto.kt +++ b/app/src/main/java/com/sopt/clody/data/remote/dto/response/UserInfoResponseDto.kt @@ -1,6 +1,5 @@ package com.sopt.clody.data.remote.dto.response -import com.sopt.clody.data.datastore.OAuthProvider import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -8,5 +7,5 @@ import kotlinx.serialization.Serializable data class UserInfoResponseDto( @SerialName("email") val email: String, @SerialName("name") val name: String, - @SerialName("platform") val platform: OAuthProvider?, + @SerialName("platform") val platform: String, ) diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpViewModel.kt b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpViewModel.kt index 3ecb7f01..b9ee5e6d 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpViewModel.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpViewModel.kt @@ -162,7 +162,7 @@ class SignUpViewModel @AssistedInject constructor( return } val request = SignUpRequestDto( - platform = OAuthProvider.GOOGLE.apiValue, + platform = OAuthProvider.GOOGLE.platform, name = state.nickname, fcmToken = fcmToken, ) @@ -173,7 +173,7 @@ class SignUpViewModel @AssistedInject constructor( loginSdk.login(context).fold( onSuccess = { token -> val request = SignUpRequestDto( - platform = OAuthProvider.KAKAO.apiValue, + platform = OAuthProvider.KAKAO.platform, name = state.nickname, fcmToken = fcmToken, ) diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/login/GoogleSignInHelper.kt b/app/src/main/java/com/sopt/clody/presentation/ui/login/GoogleSignInHelper.kt index 67dc80f1..a9644701 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/login/GoogleSignInHelper.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/login/GoogleSignInHelper.kt @@ -1,6 +1,7 @@ package com.sopt.clody.presentation.ui.login import android.content.Context +import android.content.Intent import androidx.activity.result.IntentSenderRequest import com.google.android.gms.auth.api.identity.BeginSignInRequest import com.google.android.gms.auth.api.identity.Identity @@ -11,19 +12,6 @@ class GoogleSignInHelper(context: Context) { private val signInClient: SignInClient = Identity.getSignInClient(context.applicationContext) - fun buildSignInRequest(): BeginSignInRequest { - return BeginSignInRequest.Builder() - .setGoogleIdTokenRequestOptions( - BeginSignInRequest.GoogleIdTokenRequestOptions.builder() - .setSupported(true) - .setServerClientId(BuildConfig.GOOGLE_AUTH_WEB_CLIENT_ID) - .setFilterByAuthorizedAccounts(false) - .build(), - ) - .setAutoSelectEnabled(false) - .build() - } - fun requestSignIn( onSuccess: (IntentSenderRequest) -> Unit, onFailure: (Exception) -> Unit, @@ -40,7 +28,20 @@ class GoogleSignInHelper(context: Context) { } } - fun extractIdToken(data: android.content.Intent?): String? { + private fun buildSignInRequest(): BeginSignInRequest { + return BeginSignInRequest.Builder() + .setGoogleIdTokenRequestOptions( + BeginSignInRequest.GoogleIdTokenRequestOptions.builder() + .setSupported(true) + .setServerClientId(BuildConfig.GOOGLE_AUTH_WEB_CLIENT_ID) + .setFilterByAuthorizedAccounts(false) + .build(), + ) + .setAutoSelectEnabled(false) + .build() + } + + fun extractIdToken(data: Intent?): String? { return runCatching { val credential = signInClient.getSignInCredentialFromIntent(data) credential.googleIdToken diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginScreen.kt b/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginScreen.kt index 476e81be..783962b2 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginScreen.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginScreen.kt @@ -83,9 +83,7 @@ fun LoginRoute( onGoogleLoginClick = { googleSignInHelper.requestSignIn( onSuccess = { intentSenderRequest -> googleSignInLauncher.launch(intentSenderRequest) }, - onFailure = { - viewModel.postIntent(LoginIntent.ClearError) - }, + onFailure = { viewModel.postIntent(LoginIntent.ClearError) }, ) }, ) diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginViewModel.kt b/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginViewModel.kt index e5d53217..498fa34b 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginViewModel.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginViewModel.kt @@ -87,7 +87,7 @@ class LoginViewModel @AssistedInject constructor( private suspend fun validateKakaoUser(kakaoToken: String) { val fcmToken = fcmTokenProvider.getToken().orEmpty() - val request = LoginRequestDto(platform = OAuthProvider.KAKAO.apiValue, fcmToken = fcmToken) + val request = LoginRequestDto(platform = OAuthProvider.KAKAO.platform, fcmToken = fcmToken) authRepository.signIn("Bearer $kakaoToken", request).fold( onSuccess = { @@ -98,7 +98,7 @@ class LoginViewModel @AssistedInject constructor( onFailure = { error -> setState { copy(isLoading = false) } val msg = error.message.orEmpty() - if (msg.contains("404") || msg.contains("유저가 없습니다")) { + if (msg.contains("404")) { _sideEffects.send(LoginContract.LoginSideEffect.NavigateToSignUp) } else { _sideEffects.send(LoginContract.LoginSideEffect.ShowError(msg)) @@ -124,7 +124,7 @@ class LoginViewModel @AssistedInject constructor( setState { copy(isLoading = false) } val msg = error.message.orEmpty() - if (msg.contains("500") || msg.contains("유저가 없습니다")) { + if (msg.contains("404")) { oauthDataStore.saveIdToken(idToken) oauthDataStore.savePlatform(OAuthProvider.GOOGLE) _sideEffects.send(LoginContract.LoginSideEffect.NavigateToSignUp) diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/setting/component/AccountManagementLogoutOption.kt b/app/src/main/java/com/sopt/clody/presentation/ui/setting/component/AccountManagementLogoutOption.kt index 435d7fcf..f171c547 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/setting/component/AccountManagementLogoutOption.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/setting/component/AccountManagementLogoutOption.kt @@ -16,18 +16,17 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.sopt.clody.R -import com.sopt.clody.data.datastore.OAuthProvider import com.sopt.clody.ui.theme.ClodyTheme @Composable fun AccountManagementLogoutOption( userEmail: String, - platform: OAuthProvider?, + platform: String, updateLogoutDialog: (Boolean) -> Unit, ) { val platformIconRes = when (platform) { - OAuthProvider.KAKAO -> R.drawable.img_account_management_kakao - OAuthProvider.GOOGLE -> R.drawable.img_google_button_logo + "kakao" -> R.drawable.img_account_management_kakao + "google" -> R.drawable.img_google_button_logo else -> R.drawable.img_google_button_logo // 서버에서 google을 어떻게 내려줄까요? } diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/AccountManagementViewModel.kt b/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/AccountManagementViewModel.kt index 976d9893..5c374523 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/AccountManagementViewModel.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/AccountManagementViewModel.kt @@ -68,9 +68,8 @@ class AccountManagementViewModel @Inject constructor( val result = accountManagementRepository.getUserInfo() _userInfoState.value = result.fold( onSuccess = { - val platformEnum = it.platform retryCount = 0 - UserInfoState.Success(it.copy(platform = platformEnum)) + UserInfoState.Success(it) }, onFailure = { retryCount++ From a066b7eea9adfa3a0fa670e09c5b24b538c04665 Mon Sep 17 00:00:00 2001 From: SYAAINN Date: Sat, 2 Aug 2025 01:22:00 +0900 Subject: [PATCH 2/8] =?UTF-8?q?[REFACTOR/#314]=20=EC=9B=B9=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=A1=9C=EB=93=9C=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=EC=9D=84=20=EC=9B=B9=EB=B7=B0=EC=97=90=EC=84=9C=20=EC=99=B8?= =?UTF-8?q?=EB=B6=80=20=EB=B8=8C=EB=9D=BC=EC=9A=B0=EC=A0=80=20=EC=95=B1=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9C=BC=EB=A1=9C=20=EA=B0=9C=EC=84=A0?= =?UTF-8?q?=ED=96=88=EC=8A=B5=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 2 - .../sopt/clody/core/login/KakaoLoginSdk.kt | 2 +- .../DefaultLoginSecurityChecker.kt | 2 +- .../{login => }/LoginSecurityChecker.kt | 2 +- .../security/{login => }/SecurityModule.kt | 2 +- .../security/weview/SecureWebViewClient.kt | 33 -------- .../ui/auth/signup/SignUpScreen.kt | 6 +- .../signup/navigation/SignUpNavigation.kt | 2 - .../presentation/ui/main/ClodyNavHost.kt | 7 -- .../setting/navigation/SettingNavigation.kt | 9 -- .../ui/setting/screen/SettingScreen.kt | 12 +-- .../ui/webview/WebViewNavGraph.kt | 19 ----- .../presentation/ui/webview/WebViewScreen.kt | 83 ------------------- .../presentation/utils/OpenExternalBrowser.kt | 17 ++++ .../presentation/utils/navigation/Route.kt | 3 - 15 files changed, 30 insertions(+), 171 deletions(-) rename app/src/main/java/com/sopt/clody/core/security/{login => }/DefaultLoginSecurityChecker.kt (97%) rename app/src/main/java/com/sopt/clody/core/security/{login => }/LoginSecurityChecker.kt (78%) rename app/src/main/java/com/sopt/clody/core/security/{login => }/SecurityModule.kt (89%) delete mode 100644 app/src/main/java/com/sopt/clody/core/security/weview/SecureWebViewClient.kt delete mode 100644 app/src/main/java/com/sopt/clody/presentation/ui/webview/WebViewNavGraph.kt delete mode 100644 app/src/main/java/com/sopt/clody/presentation/ui/webview/WebViewScreen.kt create mode 100644 app/src/main/java/com/sopt/clody/presentation/utils/OpenExternalBrowser.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c8bfb09f..fd21eb50 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -32,14 +32,12 @@ android { val googleAdmobAppId: String = properties.getProperty("GOOGLE_ADMOB_APP_ID", "") val googleAdmobUnitId: String = properties.getProperty("GOOGLE_ADMOB_UNIT_ID", "") val googleAuthWebClientId: String = properties.getProperty("GOOGLE_AUTH_WEB_CLIENT_ID", "") - val allowedDomains: String = properties.getProperty("allowed.webview.domains", "notion.so,google.com") buildConfigField("String", "GOOGLE_ADMOB_APP_ID", "\"$googleAdmobAppId\"") buildConfigField("String", "GOOGLE_ADMOB_UNIT_ID", "\"$googleAdmobUnitId\"") buildConfigField("String", "KAKAO_API_KEY", "\"$kakaoApiKey\"") buildConfigField("String", "AMPLITUDE_API_KEY", "\"$amplitudeApiKey\"") buildConfigField("String", "GOOGLE_AUTH_WEB_CLIENT_ID", "\"$googleAuthWebClientId\"") - buildConfigField("String", "ALLOWED_WEBVIEW_DOMAINS", "\"$allowedDomains\"") manifestPlaceholders["kakaoRedirectUri"] = "kakao$kakaoApiKey" manifestPlaceholders["GOOGLE_ADMOB_APP_ID"] = googleAdmobAppId testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/com/sopt/clody/core/login/KakaoLoginSdk.kt b/app/src/main/java/com/sopt/clody/core/login/KakaoLoginSdk.kt index d24a3e95..8abc67bd 100644 --- a/app/src/main/java/com/sopt/clody/core/login/KakaoLoginSdk.kt +++ b/app/src/main/java/com/sopt/clody/core/login/KakaoLoginSdk.kt @@ -7,7 +7,7 @@ import com.kakao.sdk.common.model.ClientError import com.kakao.sdk.common.model.ClientErrorCause import com.kakao.sdk.user.UserApiClient import com.sopt.clody.R -import com.sopt.clody.core.security.login.LoginSecurityChecker +import com.sopt.clody.core.security.LoginSecurityChecker import kotlinx.coroutines.suspendCancellableCoroutine import javax.inject.Inject import javax.inject.Singleton diff --git a/app/src/main/java/com/sopt/clody/core/security/login/DefaultLoginSecurityChecker.kt b/app/src/main/java/com/sopt/clody/core/security/DefaultLoginSecurityChecker.kt similarity index 97% rename from app/src/main/java/com/sopt/clody/core/security/login/DefaultLoginSecurityChecker.kt rename to app/src/main/java/com/sopt/clody/core/security/DefaultLoginSecurityChecker.kt index 400c45d0..21f4e87f 100644 --- a/app/src/main/java/com/sopt/clody/core/security/login/DefaultLoginSecurityChecker.kt +++ b/app/src/main/java/com/sopt/clody/core/security/DefaultLoginSecurityChecker.kt @@ -1,4 +1,4 @@ -package com.sopt.clody.core.security.login +package com.sopt.clody.core.security import android.content.Context import android.content.pm.PackageManager diff --git a/app/src/main/java/com/sopt/clody/core/security/login/LoginSecurityChecker.kt b/app/src/main/java/com/sopt/clody/core/security/LoginSecurityChecker.kt similarity index 78% rename from app/src/main/java/com/sopt/clody/core/security/login/LoginSecurityChecker.kt rename to app/src/main/java/com/sopt/clody/core/security/LoginSecurityChecker.kt index 4e0b06e3..023bff55 100644 --- a/app/src/main/java/com/sopt/clody/core/security/login/LoginSecurityChecker.kt +++ b/app/src/main/java/com/sopt/clody/core/security/LoginSecurityChecker.kt @@ -1,4 +1,4 @@ -package com.sopt.clody.core.security.login +package com.sopt.clody.core.security import android.content.Context diff --git a/app/src/main/java/com/sopt/clody/core/security/login/SecurityModule.kt b/app/src/main/java/com/sopt/clody/core/security/SecurityModule.kt similarity index 89% rename from app/src/main/java/com/sopt/clody/core/security/login/SecurityModule.kt rename to app/src/main/java/com/sopt/clody/core/security/SecurityModule.kt index e20c1e10..0f514829 100644 --- a/app/src/main/java/com/sopt/clody/core/security/login/SecurityModule.kt +++ b/app/src/main/java/com/sopt/clody/core/security/SecurityModule.kt @@ -1,4 +1,4 @@ -package com.sopt.clody.core.security.login +package com.sopt.clody.core.security import dagger.Binds import dagger.Module diff --git a/app/src/main/java/com/sopt/clody/core/security/weview/SecureWebViewClient.kt b/app/src/main/java/com/sopt/clody/core/security/weview/SecureWebViewClient.kt deleted file mode 100644 index 44bd523b..00000000 --- a/app/src/main/java/com/sopt/clody/core/security/weview/SecureWebViewClient.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.sopt.clody.core.security.weview - -import android.content.Context -import android.content.Intent -import android.net.http.SslError -import android.webkit.SslErrorHandler -import android.webkit.WebResourceRequest -import android.webkit.WebView -import android.webkit.WebViewClient - -class SecureWebViewClient( - private val context: Context, - private val allowedDomains: List = listOf("notion.so", "forms.gle"), -) : WebViewClient() { - - override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { - val host = request.url.host ?: return true - val isSafeDomain = allowedDomains.any { host.contains(it) } - - return if (isSafeDomain) { - false - } else { - Intent(Intent.ACTION_VIEW, request.url).let { - context.startActivity(it) - } - true - } - } - - override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) { - handler.cancel() - } -} diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpScreen.kt b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpScreen.kt index 160e48f2..3937e5d7 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpScreen.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpScreen.kt @@ -12,13 +12,13 @@ import com.sopt.clody.presentation.ui.auth.signup.page.NickNamePage import com.sopt.clody.presentation.ui.auth.signup.page.TermsOfServicePage import com.sopt.clody.presentation.ui.component.dialog.FailureDialog import com.sopt.clody.presentation.utils.extension.repeatOnStarted +import com.sopt.clody.presentation.utils.openExternalBrowser @Composable fun SignUpRoute( viewModel: SignUpViewModel = mavericksViewModel(), navigateToHome: () -> Unit, navigateToPrevious: () -> Unit, - navigateToWebView: (String) -> Unit, ) { val state by viewModel.collectAsState() val context = LocalContext.current @@ -29,9 +29,7 @@ fun SignUpRoute( viewModel.sideEffects.collect { effect -> when (effect) { is SignUpContract.SignUpSideEffect.NavigateToTimeReminder -> navigateToHome() - is SignUpContract.SignUpSideEffect.NavigateToWebView -> { - navigateToWebView(effect.url) // ✅ WebView 이동 처리 - } + is SignUpContract.SignUpSideEffect.NavigateToWebView -> { openExternalBrowser(context, effect.url) } is SignUpContract.SignUpSideEffect.ShowMessage -> {} } } diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/navigation/SignUpNavigation.kt b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/navigation/SignUpNavigation.kt index 42ea17b8..9c7a7de6 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/navigation/SignUpNavigation.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/navigation/SignUpNavigation.kt @@ -10,13 +10,11 @@ import com.sopt.clody.presentation.utils.navigation.Route fun NavGraphBuilder.signUpScreen( navigateToHome: () -> Unit, navigateToPrevious: () -> Unit, - navigateToWebView: (String) -> Unit, ) { composable { SignUpRoute( navigateToHome = navigateToHome, navigateToPrevious = navigateToPrevious, - navigateToWebView = navigateToWebView, ) } } diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/main/ClodyNavHost.kt b/app/src/main/java/com/sopt/clody/presentation/ui/main/ClodyNavHost.kt index b8fcbb80..8408e01d 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/main/ClodyNavHost.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/main/ClodyNavHost.kt @@ -27,11 +27,9 @@ import com.sopt.clody.presentation.ui.setting.navigation.accountManagementScreen import com.sopt.clody.presentation.ui.setting.navigation.navigateToAccountManagement import com.sopt.clody.presentation.ui.setting.navigation.navigateToNotificationSetting import com.sopt.clody.presentation.ui.setting.navigation.navigateToSetting -import com.sopt.clody.presentation.ui.setting.navigation.navigateToWebView import com.sopt.clody.presentation.ui.setting.navigation.notificationSettingScreen import com.sopt.clody.presentation.ui.setting.navigation.settingScreen import com.sopt.clody.presentation.ui.splash.navigation.splashScreen -import com.sopt.clody.presentation.ui.webview.webViewScreen import com.sopt.clody.presentation.ui.writediary.navigation.navigateToWriteDiary import com.sopt.clody.presentation.ui.writediary.navigation.writeDiaryScreen import com.sopt.clody.presentation.utils.navigation.safePopBackStack @@ -73,7 +71,6 @@ fun ClodyNavHost( signUpScreen( navigateToHome = navController::navigateToTimeReminder, navigateToPrevious = navController::safePopBackStack, - navigateToWebView = navController::navigateToWebView, ) timeReminderScreen( navigateToGuide = navController::navigateToGuide, @@ -108,7 +105,6 @@ fun ClodyNavHost( navigateToAccountManagement = navController::navigateToAccountManagement, navigateToNotification = navController::navigateToNotificationSetting, navigateToPrevious = navController::safePopBackStack, - navigateToWebView = navController::navigateToWebView, ) accountManagementScreen( navigateToPrevious = navController::safePopBackStack, @@ -117,9 +113,6 @@ fun ClodyNavHost( notificationSettingScreen( navigateToPrevious = navController::safePopBackStack, ) - webViewScreen( - navigateToPrevious = navController::safePopBackStack, - ) } } } diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/setting/navigation/SettingNavigation.kt b/app/src/main/java/com/sopt/clody/presentation/ui/setting/navigation/SettingNavigation.kt index 572e088c..b2fc48f4 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/setting/navigation/SettingNavigation.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/setting/navigation/SettingNavigation.kt @@ -13,14 +13,12 @@ fun NavGraphBuilder.settingScreen( navigateToAccountManagement: () -> Unit, navigateToNotification: () -> Unit, navigateToPrevious: () -> Unit, - navigateToWebView: (String) -> Unit, ) { composable { SettingRoute( navigateToAccountManagement = navigateToAccountManagement, navigateToNotification = navigateToNotification, navigateToPrevious = navigateToPrevious, - navigateToWebView = navigateToWebView, ) } } @@ -62,10 +60,3 @@ fun NavController.navigateToNotificationSetting( ) { navigate(Route.NotificationSetting, navOptions) } - -fun NavController.navigateToWebView( - encodedUrl: String, - navOptions: NavOptionsBuilder.() -> Unit = {}, -) { - navigate(Route.WebView(encodedUrl), navOptions) -} diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingScreen.kt b/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingScreen.kt index 291bed09..07b12378 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingScreen.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import com.sopt.clody.R @@ -19,6 +20,7 @@ import com.sopt.clody.presentation.ui.setting.component.SettingTopAppBar import com.sopt.clody.presentation.ui.setting.component.SettingVersionInfo import com.sopt.clody.presentation.utils.amplitude.AmplitudeConstraints import com.sopt.clody.presentation.utils.amplitude.AmplitudeUtils +import com.sopt.clody.presentation.utils.openExternalBrowser import com.sopt.clody.ui.theme.ClodyTheme @Composable @@ -26,7 +28,6 @@ fun SettingRoute( navigateToAccountManagement: () -> Unit, navigateToNotification: () -> Unit, navigateToPrevious: () -> Unit, - navigateToWebView: (String) -> Unit, settingViewModel: SettingViewModel = hiltViewModel(), ) { val notice by settingViewModel::noticeUrl @@ -34,6 +35,7 @@ fun SettingRoute( val termsOfService by settingViewModel::termsOfServiceUrl val privacyPolicy by settingViewModel::privacyPolicyUrl val versionInfo by settingViewModel::versionInfo + val context = LocalContext.current LaunchedEffect(Unit) { settingViewModel.getVersionInfo() @@ -45,10 +47,10 @@ fun SettingRoute( onClickBack = navigateToPrevious, onClickAccountManagement = navigateToAccountManagement, onClickNotificationSetting = navigateToNotification, - onClickNotice = { navigateToWebView(notice) }, - onClickSupportFeedback = { navigateToWebView(supportFeedback) }, - onClickTerms = { navigateToWebView(termsOfService) }, - onClickPrivacy = { navigateToWebView(privacyPolicy) }, + onClickNotice = { openExternalBrowser(context, notice) }, + onClickSupportFeedback = { openExternalBrowser(context, supportFeedback) }, + onClickTerms = { openExternalBrowser(context, termsOfService) }, + onClickPrivacy = { openExternalBrowser(context, privacyPolicy) }, ) } diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/webview/WebViewNavGraph.kt b/app/src/main/java/com/sopt/clody/presentation/ui/webview/WebViewNavGraph.kt deleted file mode 100644 index 8c20e749..00000000 --- a/app/src/main/java/com/sopt/clody/presentation/ui/webview/WebViewNavGraph.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.sopt.clody.presentation.ui.webview - -import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import androidx.navigation.toRoute -import com.sopt.clody.presentation.utils.navigation.Route - -fun NavGraphBuilder.webViewScreen( - navigateToPrevious: () -> Unit, -) { - composable { backStackEntry -> - backStackEntry.toRoute().apply { - WebViewRoute( - encodedUrl = encodedUrl, - navigateToPrevious = navigateToPrevious, - ) - } - } -} diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/webview/WebViewScreen.kt b/app/src/main/java/com/sopt/clody/presentation/ui/webview/WebViewScreen.kt deleted file mode 100644 index 0515fc59..00000000 --- a/app/src/main/java/com/sopt/clody/presentation/ui/webview/WebViewScreen.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.sopt.clody.presentation.ui.webview - -import android.annotation.SuppressLint -import android.net.Uri -import android.webkit.WebSettings -import android.webkit.WebView -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.viewinterop.AndroidView -import com.sopt.clody.BuildConfig -import com.sopt.clody.core.security.weview.SecureWebViewClient - -@Composable -fun WebViewRoute( - navigateToPrevious: () -> Unit, - encodedUrl: String, -) { - WebViewScreen( - encodedUrl = encodedUrl, - onClickBack = navigateToPrevious, - ) -} - -@SuppressLint("SetJavaScriptEnabled") -@Composable -fun WebViewScreen( - encodedUrl: String, - onClickBack: () -> Unit, -) { - val decodedUrl = remember(encodedUrl) { - Uri.decode(encodedUrl) - } - - var webView: WebView? by remember { mutableStateOf(null) } - val canGoBack by remember { derivedStateOf { webView?.canGoBack() ?: false } } - - val allowedDomains = BuildConfig.ALLOWED_WEBVIEW_DOMAINS.split(",").map { it.trim() } - - Scaffold( - modifier = Modifier.fillMaxSize(), - content = { innerPadding -> - AndroidView( - factory = { context -> - WebView(context).apply { - webViewClient = SecureWebViewClient(context, allowedDomains) - settings.apply { - javaScriptEnabled = true - domStorageEnabled = true - useWideViewPort = true - loadWithOverviewMode = true - allowFileAccess = false - allowContentAccess = false - javaScriptCanOpenWindowsAutomatically = false - mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW - } - loadUrl(decodedUrl) - webView = this - } - }, - modifier = Modifier - .fillMaxSize() - .padding(innerPadding), - ) - }, - ) - - BackHandler(enabled = canGoBack) { - if (canGoBack) { - webView?.goBack() - } else { - onClickBack() - } - } -} diff --git a/app/src/main/java/com/sopt/clody/presentation/utils/OpenExternalBrowser.kt b/app/src/main/java/com/sopt/clody/presentation/utils/OpenExternalBrowser.kt new file mode 100644 index 00000000..aaab7895 --- /dev/null +++ b/app/src/main/java/com/sopt/clody/presentation/utils/OpenExternalBrowser.kt @@ -0,0 +1,17 @@ +package com.sopt.clody.presentation.utils + +import android.content.Context +import android.content.Intent +import android.net.Uri + +fun openExternalBrowser(context: Context, url: String) { + val uri = Uri.parse(url) + val intent = Intent(Intent.ACTION_VIEW, uri).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + } + + // 웹 브라우저 앱이 설치되어 있지 않은 경우 + context.packageManager.resolveActivity(intent, 0)?.let { + context.startActivity(intent) + } ?: return +} diff --git a/app/src/main/java/com/sopt/clody/presentation/utils/navigation/Route.kt b/app/src/main/java/com/sopt/clody/presentation/utils/navigation/Route.kt index 334475b6..d00a7cff 100644 --- a/app/src/main/java/com/sopt/clody/presentation/utils/navigation/Route.kt +++ b/app/src/main/java/com/sopt/clody/presentation/utils/navigation/Route.kt @@ -78,7 +78,4 @@ sealed interface Route { @Serializable data object NotificationSetting : Route - - @Serializable - data class WebView(val encodedUrl: String) : Route } From d8b5dd3a0456c577e4a73985dabc68781be05073 Mon Sep 17 00:00:00 2001 From: SYAAINN Date: Sat, 2 Aug 2025 02:12:17 +0900 Subject: [PATCH 3/8] =?UTF-8?q?[FEAT/#314]=20=EC=95=B1=EC=9D=B4=20?= =?UTF-8?q?=EC=B5=9C=EC=8B=A0=20=EB=B2=84=EC=A0=84=EC=9D=BC=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20=EC=B5=9C=EC=8B=A0=20=EB=B2=84=EC=A0=84=EC=9D=B4?= =?UTF-8?q?=EB=9D=BC=EA=B3=A0=20=ED=91=9C=EC=8B=9C=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EA=B5=AC=ED=98=84=ED=95=A9=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/setting/component/SettingVersionInfo.kt | 5 +---- .../ui/setting/screen/SettingScreen.kt | 1 - .../ui/setting/screen/SettingViewModel.kt | 16 +++++++++++++--- app/src/main/res/values-ko/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/setting/component/SettingVersionInfo.kt b/app/src/main/java/com/sopt/clody/presentation/ui/setting/component/SettingVersionInfo.kt index fcb068ea..84649dd9 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/setting/component/SettingVersionInfo.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/setting/component/SettingVersionInfo.kt @@ -10,7 +10,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import com.sopt.clody.R import com.sopt.clody.ui.theme.ClodyTheme @@ -34,9 +33,7 @@ fun SettingVersionInfo( Text( text = versionInfo, color = ClodyTheme.colors.gray05, - style = ClodyTheme.typography.body4Medium.copy( - letterSpacing = 2.sp, - ), + style = ClodyTheme.typography.body4Medium, ) } } diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingScreen.kt b/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingScreen.kt index 07b12378..8af5e2c6 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingScreen.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingScreen.kt @@ -38,7 +38,6 @@ fun SettingRoute( val context = LocalContext.current LaunchedEffect(Unit) { - settingViewModel.getVersionInfo() AmplitudeUtils.trackEvent(eventName = AmplitudeConstraints.SETTING) } diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingViewModel.kt b/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingViewModel.kt index e24e922e..d379ed02 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingViewModel.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/setting/screen/SettingViewModel.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.sopt.clody.R +import com.sopt.clody.data.remote.datasource.RemoteConfigDataSource import com.sopt.clody.presentation.utils.language.LanguageProvider import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext @@ -17,23 +18,32 @@ import javax.inject.Inject @HiltViewModel class SettingViewModel @Inject constructor( @ApplicationContext private val context: Context, + private val remoteConfigDataSource: RemoteConfigDataSource, private val languageProvider: LanguageProvider, ) : ViewModel() { val noticeUrl = languageProvider.getWebViewUrlFor(SettingOptionUrls.NOTICES_URL) val supportFeedbackUrl = languageProvider.getWebViewUrlFor(SettingOptionUrls.SUPPORT_FEEDBACK_URL) val termsOfServiceUrl = languageProvider.getWebViewUrlFor(SettingOptionUrls.TERMS_OF_SERVICE_URL) val privacyPolicyUrl = languageProvider.getWebViewUrlFor(SettingOptionUrls.PRIVACY_POLICY_URL) - var versionInfo by mutableStateOf(null) private set - fun getVersionInfo() { + init { + getVersionInfo() + } + + private fun getVersionInfo() { viewModelScope.launch { runCatching { val info: PackageInfo = context.packageManager.getPackageInfo(context.packageName, 0) info.versionName }.onSuccess { versionName -> - versionInfo = versionName + val latestVersion = remoteConfigDataSource.getLatestVersion() + versionInfo = if (versionName == latestVersion) { + context.getString(R.string.setting_option_app_version_info_latest) + } else { + versionName + } }.onFailure { versionInfo = context.getString(R.string.setting_option_app_version_info_failure) } diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index bf0e2d55..dc4a25c8 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -91,6 +91,7 @@ 서비스 이용 약관 개인정보 처리방침 앱 버전 + 최신 버전 버전 정보를 불러오는데 실패했습니다. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e026628a..9aea0081 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -92,6 +92,7 @@ Terms of Service Privacy Policy Version + Latest version Fail to Fetch From f3764ffa253817e326dfe8653117fb541279de70 Mon Sep 17 00:00:00 2001 From: SYAAINN Date: Sat, 2 Aug 2025 12:05:07 +0900 Subject: [PATCH 4/8] =?UTF-8?q?[CHORE/#314]=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EC=84=A4=EC=A0=95=20=EC=99=84=EB=A3=8C=20?= =?UTF-8?q?=ED=86=A0=EC=8A=A4=ED=8A=B8=20=EB=A9=94=EC=8B=9C=EC=A7=80?= =?UTF-8?q?=EC=9D=98=20=EC=9C=84=EC=B9=98=EB=A5=BC=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=ED=95=A9=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notificationsetting/screen/NotificationSettingScreen.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/screen/NotificationSettingScreen.kt b/app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/screen/NotificationSettingScreen.kt index 064424fd..ce2542af 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/screen/NotificationSettingScreen.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/setting/notificationsetting/screen/NotificationSettingScreen.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable @@ -92,6 +93,7 @@ fun NotificationSettingRoute( contentColor = ClodyTheme.colors.white, durationMillis = 3000, onDismiss = { notificationSettingViewModel.resetNotificationTimeChangeState() }, + modifier = Modifier.navigationBarsPadding(), ) } } From 8261e67dbce1661ebb517603866ffde2e3e46296 Mon Sep 17 00:00:00 2001 From: SYAAINN Date: Sat, 2 Aug 2025 17:38:00 +0900 Subject: [PATCH 5/8] =?UTF-8?q?[CHORE/#314]=20=EA=B0=80=EC=9D=B4=EB=93=9C?= =?UTF-8?q?=20=EC=8A=A4=ED=81=AC=EB=A6=B0=EC=9D=98=20UI=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=EB=A5=BC=20=EC=88=98=EC=A0=95=ED=95=A9=EB=8B=88?= =?UTF-8?q?=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/ui/auth/guide/GuideScreen.kt | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/auth/guide/GuideScreen.kt b/app/src/main/java/com/sopt/clody/presentation/ui/auth/guide/GuideScreen.kt index 02422827..ddaa52c5 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/auth/guide/GuideScreen.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/auth/guide/GuideScreen.kt @@ -5,13 +5,13 @@ import androidx.compose.animation.core.tween import androidx.compose.animation.fadeOut import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -38,7 +38,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.sopt.clody.R import com.sopt.clody.presentation.ui.component.button.ClodyButton -import com.sopt.clody.presentation.utils.extension.heightForScreenPercentage import com.sopt.clody.ui.theme.ClodyTheme import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -111,18 +110,16 @@ fun GuideScreen( AnimatedVisibility( visible = !isExiting, exit = fadeOut(animationSpec = tween(1000)), // 1초 페이드 아웃 - modifier = Modifier - .fillMaxSize() - .padding(innerPadding), + modifier = Modifier.padding(innerPadding), ) { Column( - modifier = Modifier - .fillMaxSize(), + modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, ) { - Spacer(modifier = Modifier.heightForScreenPercentage(0.21f)) + Spacer(modifier = Modifier.weight(1f)) HorizontalPager( state = pagerState, + modifier = Modifier.weight(4f), ) { page -> Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -134,14 +131,14 @@ fun GuideScreen( color = ClodyTheme.colors.gray01, textAlign = TextAlign.Center, ) - Spacer(modifier = Modifier.heightForScreenPercentage(0.02f)) + Spacer(modifier = Modifier.height(20.dp)) Text( text = pages[page].description, style = ClodyTheme.typography.body1Medium, color = ClodyTheme.colors.gray05, textAlign = TextAlign.Center, ) - Spacer(modifier = Modifier.heightForScreenPercentage(0.04f)) + Spacer(modifier = Modifier.weight(1f)) Image( painter = painterResource(id = pages[page].imageRes), contentDescription = null, @@ -149,12 +146,11 @@ fun GuideScreen( .fillMaxWidth(), contentScale = ContentScale.Fit, ) + Spacer(modifier = Modifier.weight(1f)) } } - - Spacer(modifier = Modifier.heightForScreenPercentage(0.2f)) Row( - horizontalArrangement = Arrangement.Center, + modifier = Modifier.padding(vertical = 50.dp), ) { repeat(pagerState.pageCount) { iteration -> val color = if (pagerState.currentPage == iteration) ClodyTheme.colors.gray03 else ClodyTheme.colors.gray07 From 24ed3dfd4ca65dc7dcbd0911edfe7394f0310987 Mon Sep 17 00:00:00 2001 From: SYAAINN Date: Sun, 3 Aug 2025 17:55:12 +0900 Subject: [PATCH 6/8] =?UTF-8?q?[CHORE/#314]=20=EC=9D=BC=EA=B8=B0=EC=93=B0?= =?UTF-8?q?=EA=B8=B0=20API=20=EB=B3=80=EA=B2=BD=EC=82=AC=ED=95=AD=EC=9D=84?= =?UTF-8?q?=20=EB=B0=98=EC=98=81=ED=96=88=EC=8A=B5=EB=8B=88=EB=8B=A4.(data?= =?UTF-8?q?=20=ED=8F=AC=EB=A7=B7=20=EB=B3=80=EA=B2=BD,=20lang=20=ED=97=A4?= =?UTF-8?q?=EB=8D=94=20=EC=B6=94=EA=B0=80)=20-=20LanguageProviderImpl?= =?UTF-8?q?=EC=97=90=EC=84=9C=20locale=20=EB=B3=80=EC=88=98=20=EA=B0=80?= =?UTF-8?q?=EC=A0=B8=EC=98=A4=EB=8A=94=20=EB=B0=A9=EC=8B=9D=EC=9D=84=20?= =?UTF-8?q?=EC=A1=B0=EA=B8=88=20=EB=8D=94=20=EA=B0=80=EB=B3=8D=EA=B2=8C=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0=ED=96=88=EC=8A=B5=EB=8B=88=EB=8B=A4.=20?= =?UTF-8?q?=EA=B8=B0=EC=A1=B4=20=EB=B0=A9=EC=8B=9D=EC=9D=80=20=EC=95=B1=20?= =?UTF-8?q?=EB=82=B4=20=EB=8B=A4=EA=B5=AD=EC=96=B4=20=EC=A7=80=EC=9B=90=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=EC=9D=B4=20=EC=9E=88=EC=9D=84=EB=95=8C=20?= =?UTF-8?q?=ED=9A=A8=EA=B3=BC=EC=A0=81=EC=9D=B8=20=EB=B0=A9=EB=B2=95?= =?UTF-8?q?=EC=9D=B4=EB=9D=BC=EA=B3=A0=20=ED=95=A9=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../clody/data/remote/api/DiaryService.kt | 2 ++ .../datasource/DiaryRemoteDataSource.kt | 2 +- .../DiaryRemoteDataSourceImpl.kt | 4 +-- .../dto/request/WriteDiaryRequestDto.kt | 2 +- .../repositoryimpl/DiaryRepositoryImpl.kt | 4 +-- .../domain/repository/DiaryRepository.kt | 2 +- .../writediary/screen/WriteDiaryViewModel.kt | 6 ++-- .../utils/extension/TimeZoneExt.kt | 26 ++++++++++++-- .../utils/language/LanguageProvider.kt | 1 + .../utils/language/LanguageProviderImpl.kt | 35 +++++++------------ 10 files changed, 51 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/com/sopt/clody/data/remote/api/DiaryService.kt b/app/src/main/java/com/sopt/clody/data/remote/api/DiaryService.kt index bd8981d7..62540db6 100644 --- a/app/src/main/java/com/sopt/clody/data/remote/api/DiaryService.kt +++ b/app/src/main/java/com/sopt/clody/data/remote/api/DiaryService.kt @@ -13,12 +13,14 @@ import com.sopt.clody.data.remote.dto.response.WriteDiaryResponseDto import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET +import retrofit2.http.Header import retrofit2.http.POST import retrofit2.http.Query interface DiaryService { @POST("api/v1/diary") suspend fun writeDiary( + @Header("Accept-Language") lang: String, @Body writeDiaryRequestDto: WriteDiaryRequestDto, ): ApiResponse diff --git a/app/src/main/java/com/sopt/clody/data/remote/datasource/DiaryRemoteDataSource.kt b/app/src/main/java/com/sopt/clody/data/remote/datasource/DiaryRemoteDataSource.kt index 26c0d844..be351600 100644 --- a/app/src/main/java/com/sopt/clody/data/remote/datasource/DiaryRemoteDataSource.kt +++ b/app/src/main/java/com/sopt/clody/data/remote/datasource/DiaryRemoteDataSource.kt @@ -11,7 +11,7 @@ import com.sopt.clody.data.remote.dto.response.ReplyDiaryResponseDto import com.sopt.clody.data.remote.dto.response.WriteDiaryResponseDto interface DiaryRemoteDataSource { - suspend fun writeDiary(date: String, content: List): ApiResponse + suspend fun writeDiary(lang: String, date: String, content: List): ApiResponse suspend fun deleteDailyDiary(year: Int, month: Int, date: Int): ApiResponse suspend fun getDailyDiariesData(year: Int, month: Int, date: Int): ApiResponse suspend fun getDiaryTime(year: Int, month: Int, date: Int): ApiResponse diff --git a/app/src/main/java/com/sopt/clody/data/remote/datasourceimpl/DiaryRemoteDataSourceImpl.kt b/app/src/main/java/com/sopt/clody/data/remote/datasourceimpl/DiaryRemoteDataSourceImpl.kt index f5e35d2a..4522e637 100644 --- a/app/src/main/java/com/sopt/clody/data/remote/datasourceimpl/DiaryRemoteDataSourceImpl.kt +++ b/app/src/main/java/com/sopt/clody/data/remote/datasourceimpl/DiaryRemoteDataSourceImpl.kt @@ -17,8 +17,8 @@ import javax.inject.Inject class DiaryRemoteDataSourceImpl @Inject constructor( private val diaryService: DiaryService, ) : DiaryRemoteDataSource { - override suspend fun writeDiary(date: String, content: List): ApiResponse = - diaryService.writeDiary(WriteDiaryRequestDto(date, content)) + override suspend fun writeDiary(lang: String, date: String, content: List): ApiResponse = + diaryService.writeDiary(lang, WriteDiaryRequestDto(date, content)) override suspend fun deleteDailyDiary(year: Int, month: Int, date: Int): ApiResponse = diaryService.deleteDailyDiary(year = year, month = month, date = date) diff --git a/app/src/main/java/com/sopt/clody/data/remote/dto/request/WriteDiaryRequestDto.kt b/app/src/main/java/com/sopt/clody/data/remote/dto/request/WriteDiaryRequestDto.kt index 7026db09..e813934e 100644 --- a/app/src/main/java/com/sopt/clody/data/remote/dto/request/WriteDiaryRequestDto.kt +++ b/app/src/main/java/com/sopt/clody/data/remote/dto/request/WriteDiaryRequestDto.kt @@ -6,5 +6,5 @@ import kotlinx.serialization.Serializable @Serializable data class WriteDiaryRequestDto( @SerialName("date") val date: String, - @SerialName("content")val content: List, + @SerialName("content") val content: List, ) diff --git a/app/src/main/java/com/sopt/clody/data/repositoryimpl/DiaryRepositoryImpl.kt b/app/src/main/java/com/sopt/clody/data/repositoryimpl/DiaryRepositoryImpl.kt index febe3024..f09d9787 100644 --- a/app/src/main/java/com/sopt/clody/data/repositoryimpl/DiaryRepositoryImpl.kt +++ b/app/src/main/java/com/sopt/clody/data/repositoryimpl/DiaryRepositoryImpl.kt @@ -19,9 +19,9 @@ import javax.inject.Inject class DiaryRepositoryImpl @Inject constructor( private val diaryRemoteDataSource: DiaryRemoteDataSource, ) : DiaryRepository { - override suspend fun writeDiary(date: String, content: List): Result = + override suspend fun writeDiary(lang: String, date: String, content: List): Result = runCatching { - diaryRemoteDataSource.writeDiary(date, content).handleApiResponse().getOrThrow() + diaryRemoteDataSource.writeDiary(lang, date, content).handleApiResponse().getOrThrow() } override suspend fun deleteDailyDiary(year: Int, month: Int, day: Int): Result = diff --git a/app/src/main/java/com/sopt/clody/domain/repository/DiaryRepository.kt b/app/src/main/java/com/sopt/clody/domain/repository/DiaryRepository.kt index 3666ed18..0ab3ff7f 100644 --- a/app/src/main/java/com/sopt/clody/domain/repository/DiaryRepository.kt +++ b/app/src/main/java/com/sopt/clody/domain/repository/DiaryRepository.kt @@ -9,7 +9,7 @@ import com.sopt.clody.data.remote.dto.response.WriteDiaryResponseDto import com.sopt.clody.domain.model.DraftDiaryContents interface DiaryRepository { - suspend fun writeDiary(date: String, content: List): Result + suspend fun writeDiary(lang: String, date: String, content: List): Result suspend fun deleteDailyDiary(year: Int, month: Int, day: Int): Result suspend fun getDailyDiariesData(year: Int, month: Int, date: Int): Result suspend fun getDiaryTime(year: Int, month: Int, date: Int): Result diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryViewModel.kt b/app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryViewModel.kt index 61c133cc..b5a567ec 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryViewModel.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryViewModel.kt @@ -12,6 +12,7 @@ import com.sopt.clody.domain.repository.DiaryRepository import com.sopt.clody.domain.repository.DraftRepository import com.sopt.clody.domain.usecase.FetchDraftDiaryUseCase import com.sopt.clody.domain.usecase.SaveDraftDiaryUseCase +import com.sopt.clody.presentation.utils.extension.convertDateToKstDateTime import com.sopt.clody.presentation.utils.language.LanguageProvider import com.sopt.clody.presentation.utils.network.ErrorMessages import com.sopt.clody.presentation.utils.network.ErrorMessages.FAILURE_NETWORK_MESSAGE @@ -81,8 +82,9 @@ class WriteDiaryViewModel @Inject constructor( } _writeDiaryState.value = WriteDiaryState.Loading - val date = String.format("%04d-%02d-%02d", year, month, day) - val result = diaryRepository.writeDiary(date, contents) + val lang = languageProvider.getCurrentLanguageTag() + val date = convertDateToKstDateTime(year, month, day) + val result = diaryRepository.writeDiary(lang, date, contents) _writeDiaryState.value = result.fold( onSuccess = { response -> if (isDiaryExpired(year, month, day)) { diff --git a/app/src/main/java/com/sopt/clody/presentation/utils/extension/TimeZoneExt.kt b/app/src/main/java/com/sopt/clody/presentation/utils/extension/TimeZoneExt.kt index b3b6832d..4314fdb4 100644 --- a/app/src/main/java/com/sopt/clody/presentation/utils/extension/TimeZoneExt.kt +++ b/app/src/main/java/com/sopt/clody/presentation/utils/extension/TimeZoneExt.kt @@ -7,8 +7,8 @@ import java.time.ZonedDateTime import java.time.format.DateTimeFormatter /** -* @param time 서버로부터 수신받은 시간으로 "21:30" 와 같은 형태로 전달받는다. -* */ + * @param time 서버로부터 수신받은 시간으로 "21:30" 와 같은 형태로 전달받는다. + * */ fun convertKSTtoUTZ(time: String, referenceDate: LocalDate = LocalDate.now()): Triple { val kstZoneId = ZoneId.of("Asia/Seoul") val userZoneId = ZoneId.systemDefault() @@ -58,3 +58,25 @@ fun convertUTZtoKST(timePeriod: TimePeriod, hour: String, minute: String, refere return String.format(java.util.Locale.ROOT, "%02d:%02d", kstHour, kstMinute) } + +/** + * 일기작성 API 호출 시 유저의 현재 시점(연/월/일/시각)을 KST 시간대로 변환 후 "yyyy-MM-dd'T'HH:mm:ss" 형식으로 전달한다. + * @param year 작성된 일기의 연도 + * @param month 작성된 일기의 월 + * @param day 작성된 일기의 일 + * */ +fun convertDateToKstDateTime(year: Int, month: Int, day: Int): String { + val localNowDate = LocalDate.now() + val targetDate = LocalDate.of(year, month, day) + val kstZone = ZoneId.of("Asia/Seoul") + val nowKst = ZonedDateTime.now(kstZone) + + val targetZonedDateTime = if (targetDate == localNowDate) { + nowKst + } else { + nowKst.minusDays(1) + } + + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss") + return targetZonedDateTime.format(formatter) +} diff --git a/app/src/main/java/com/sopt/clody/presentation/utils/language/LanguageProvider.kt b/app/src/main/java/com/sopt/clody/presentation/utils/language/LanguageProvider.kt index 4c697c74..57a7889a 100644 --- a/app/src/main/java/com/sopt/clody/presentation/utils/language/LanguageProvider.kt +++ b/app/src/main/java/com/sopt/clody/presentation/utils/language/LanguageProvider.kt @@ -4,6 +4,7 @@ import com.sopt.clody.data.datastore.OAuthProvider import com.sopt.clody.presentation.ui.setting.screen.SettingOptionUrls interface LanguageProvider { + fun getCurrentLanguageTag(): String fun getLoginType(): OAuthProvider fun getNicknameMaxLength(): Int fun getDiaryMaxLength(): Int diff --git a/app/src/main/java/com/sopt/clody/presentation/utils/language/LanguageProviderImpl.kt b/app/src/main/java/com/sopt/clody/presentation/utils/language/LanguageProviderImpl.kt index 6bf0749f..933ecc97 100644 --- a/app/src/main/java/com/sopt/clody/presentation/utils/language/LanguageProviderImpl.kt +++ b/app/src/main/java/com/sopt/clody/presentation/utils/language/LanguageProviderImpl.kt @@ -1,38 +1,29 @@ package com.sopt.clody.presentation.utils.language -import android.content.Context import com.sopt.clody.data.datastore.OAuthProvider import com.sopt.clody.presentation.ui.setting.screen.SettingOptionUrls -import dagger.hilt.android.qualifiers.ApplicationContext import java.util.Locale import javax.inject.Inject -class LanguageProviderImpl @Inject constructor( - @ApplicationContext private val context: Context, -) : LanguageProvider { - private val locale: Locale - get() = context.resources.configuration.locales[0] +class LanguageProviderImpl @Inject constructor() : LanguageProvider { + private val locale = Locale.getDefault() private fun isKorean(): Boolean = locale.language == LANGUAGE_KO - override fun getLoginType(): OAuthProvider { - return when (locale.language) { - LANGUAGE_KO -> OAuthProvider.KAKAO - else -> OAuthProvider.GOOGLE - } - } + override fun getCurrentLanguageTag(): String = + locale.toLanguageTag() // e.g., "ko-KR" or "en-US" - override fun getNicknameMaxLength(): Int { - return if (isKorean()) NICKNAME_MAX_LENGTH_KO else NICKNAME_MAX_LENGTH_EN - } + override fun getLoginType(): OAuthProvider = + if (isKorean()) OAuthProvider.KAKAO else OAuthProvider.GOOGLE - override fun getDiaryMaxLength(): Int { - return if (isKorean()) DIARY_MAX_LENGTH_KO else DIARY_MAX_LENGTH_EN - } + override fun getNicknameMaxLength(): Int = + if (isKorean()) NICKNAME_MAX_LENGTH_KO else NICKNAME_MAX_LENGTH_EN - override fun getWebViewUrlFor(option: SettingOptionUrls): String { - return if (isKorean()) option.koUrl else option.enUrl - } + override fun getDiaryMaxLength(): Int = + if (isKorean()) DIARY_MAX_LENGTH_KO else DIARY_MAX_LENGTH_EN + + override fun getWebViewUrlFor(option: SettingOptionUrls): String = + if (isKorean()) option.koUrl else option.enUrl companion object { const val LANGUAGE_KO = "ko" From 54564bb4c769f10d47a6324be5496508bda2526a Mon Sep 17 00:00:00 2001 From: SYAAINN Date: Sun, 3 Aug 2025 18:35:06 +0900 Subject: [PATCH 7/8] =?UTF-8?q?[REFACTOR/#314]=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20sdk=20=ED=98=B8=EC=B6=9C=EC=9D=B4=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8,=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20?= =?UTF-8?q?=EC=8B=9C=202=EB=B2=88=20=ED=98=B8=EC=B6=9C=EB=90=98=EB=8A=94?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=EC=88=98=EC=A0=95=ED=95=A9?= =?UTF-8?q?=EB=8B=88=EB=8B=A4.=20-=20=EA=B8=B0=EC=A1=B4=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=9D=80=20=EB=8B=89=EB=84=A4=EC=9E=84=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=99=84=EB=A3=8C=20=ED=9B=84=20=EB=8B=A4=EC=8B=9C?= =?UTF-8?q?=20=ED=95=9C=EB=B2=88=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20sdk?= =?UTF-8?q?=EB=A5=BC=20=ED=98=B8=EC=B6=9C=ED=95=98=EC=97=AC=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=EC=9D=84=20=EB=B0=9B=EC=95=84=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20api=EC=97=90=20=EC=82=AC=EC=9A=A9=ED=96=88?= =?UTF-8?q?=EC=8A=B5=EB=8B=88=EB=8B=A4.=20-=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=ED=99=94=EB=A9=B4=EC=97=90=EC=84=9C=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20sdk=20=ED=98=B8=EC=B6=9C=20=ED=9B=84=20OAuthDataSto?= =?UTF-8?q?re=EC=97=90=20=ED=86=A0=ED=81=B0=EC=9D=84=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EB=91=90=20=EB=B2=88=20=ED=98=B8=EC=B6=9C=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=ED=95=98=EB=A9=B0,=20=EA=B5=AC=EA=B8=80=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=EA=B3=BC=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=EB=B9=84?= =?UTF-8?q?=EC=8A=B7=ED=95=98=EA=B2=8C=20=EC=88=98=EC=A0=95=ED=95=98?= =?UTF-8?q?=EC=98=80=EC=8A=B5=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../clody/data/datastore/OAuthDataStore.kt | 17 +++++++--- .../data/datastore/OAuthDataStoreKeys.kt | 3 +- .../ui/auth/signup/SignUpViewModel.kt | 32 +++++++++---------- .../presentation/ui/login/LoginViewModel.kt | 9 +++--- 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/com/sopt/clody/data/datastore/OAuthDataStore.kt b/app/src/main/java/com/sopt/clody/data/datastore/OAuthDataStore.kt index 9fbaf319..9e6c97e4 100644 --- a/app/src/main/java/com/sopt/clody/data/datastore/OAuthDataStore.kt +++ b/app/src/main/java/com/sopt/clody/data/datastore/OAuthDataStore.kt @@ -11,12 +11,20 @@ class OAuthDataStore @Inject constructor(@ApplicationContext context: Context) { private val Context.dataStore by preferencesDataStore(name = "oauth_pref") private val dataStore = context.dataStore - suspend fun saveIdToken(token: String) { - dataStore.edit { it[OAuthDataStoreKeys.GOOGLE_ID_TOKEN] = token } + suspend fun saveIdToken(platform: String, token: String) { + if (platform == OAuthProvider.GOOGLE.platform) { + dataStore.edit { it[OAuthDataStoreKeys.GOOGLE_ID_TOKEN] = token } + } else { + dataStore.edit { it[OAuthDataStoreKeys.KAKAO_ID_TOKEN] = token } + } } - suspend fun getIdToken(): String? { - return dataStore.data.first()[OAuthDataStoreKeys.GOOGLE_ID_TOKEN] + suspend fun getIdToken(platform: String): String? { + return if (platform == OAuthProvider.GOOGLE.platform) { + dataStore.data.first()[OAuthDataStoreKeys.GOOGLE_ID_TOKEN] + } else { + dataStore.data.first()[OAuthDataStoreKeys.KAKAO_ID_TOKEN] + } } suspend fun savePlatform(provider: OAuthProvider) { @@ -32,6 +40,7 @@ class OAuthDataStore @Inject constructor(@ApplicationContext context: Context) { suspend fun clear() { dataStore.edit { it.remove(OAuthDataStoreKeys.GOOGLE_ID_TOKEN) + it.remove(OAuthDataStoreKeys.KAKAO_ID_TOKEN) it.remove(OAuthDataStoreKeys.OAUTH_PLATFORM) } } diff --git a/app/src/main/java/com/sopt/clody/data/datastore/OAuthDataStoreKeys.kt b/app/src/main/java/com/sopt/clody/data/datastore/OAuthDataStoreKeys.kt index 9bdac71b..97af4495 100644 --- a/app/src/main/java/com/sopt/clody/data/datastore/OAuthDataStoreKeys.kt +++ b/app/src/main/java/com/sopt/clody/data/datastore/OAuthDataStoreKeys.kt @@ -3,6 +3,7 @@ package com.sopt.clody.data.datastore import androidx.datastore.preferences.core.stringPreferencesKey object OAuthDataStoreKeys { - val GOOGLE_ID_TOKEN = stringPreferencesKey("google_id_token") val OAUTH_PLATFORM = stringPreferencesKey("oauth_platform") + val GOOGLE_ID_TOKEN = stringPreferencesKey("google_id_token") + val KAKAO_ID_TOKEN = stringPreferencesKey("kakao_id_token") } diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpViewModel.kt b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpViewModel.kt index b9ee5e6d..ffe4a5ae 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpViewModel.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpViewModel.kt @@ -156,7 +156,7 @@ class SignUpViewModel @AssistedInject constructor( val fcmToken = fcmTokenProvider.getToken().orEmpty() if (platform == OAuthProvider.GOOGLE) { - val idToken = oAuthDataStore.getIdToken() + val idToken = oAuthDataStore.getIdToken(platform = "google") if (idToken.isNullOrBlank()) { setState { copy(errorMessage = "Google ID Token이 없습니다.", isLoading = false) } return @@ -168,33 +168,31 @@ class SignUpViewModel @AssistedInject constructor( ) val result = authRepository.signUp("Bearer $idToken", request) - handleSignUpResult(result, isGoogle = true) + handleSignUpResult(result) } else { - loginSdk.login(context).fold( - onSuccess = { token -> - val request = SignUpRequestDto( - platform = OAuthProvider.KAKAO.platform, - name = state.nickname, - fcmToken = fcmToken, - ) - val result = authRepository.signUp("Bearer ${token.value}", request) - handleSignUpResult(result, isGoogle = false) - }, - onFailure = { - setState { copy(errorMessage = "로그인에 실패했어요~", isLoading = false) } - }, + val idToken = oAuthDataStore.getIdToken(platform = "kakao") + if (idToken.isNullOrBlank()) { + setState { copy(errorMessage = "Kakao ID Token이 없습니다.", isLoading = false) } + return + } + val request = SignUpRequestDto( + platform = OAuthProvider.KAKAO.platform, + name = state.nickname, + fcmToken = fcmToken, ) + + val result = authRepository.signUp("Bearer $idToken", request) + handleSignUpResult(result) } } private suspend fun handleSignUpResult( result: Result, - isGoogle: Boolean, ) { result.fold( onSuccess = { tokenRepository.setTokens(it.accessToken, it.refreshToken) - if (isGoogle) oAuthDataStore.clear() + oAuthDataStore.clear() _sideEffects.send(SignUpContract.SignUpSideEffect.NavigateToTimeReminder) }, onFailure = { diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginViewModel.kt b/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginViewModel.kt index 498fa34b..c7540425 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginViewModel.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/login/LoginViewModel.kt @@ -99,6 +99,8 @@ class LoginViewModel @AssistedInject constructor( setState { copy(isLoading = false) } val msg = error.message.orEmpty() if (msg.contains("404")) { + oauthDataStore.saveIdToken(platform = "kakao", token = kakaoToken) + oauthDataStore.savePlatform(OAuthProvider.KAKAO) _sideEffects.send(LoginContract.LoginSideEffect.NavigateToSignUp) } else { _sideEffects.send(LoginContract.LoginSideEffect.ShowError(msg)) @@ -109,10 +111,7 @@ class LoginViewModel @AssistedInject constructor( private suspend fun validateGoogleUser(idToken: String) { val fcmToken = fcmTokenProvider.getToken().orEmpty() - val request = GoogleSignUpRequestDto( - idToken = idToken, - fcmToken = fcmToken, - ) + val request = GoogleSignUpRequestDto(idToken = idToken, fcmToken = fcmToken) authRepository.signUpWithGoogle(request).fold( onSuccess = { @@ -125,7 +124,7 @@ class LoginViewModel @AssistedInject constructor( val msg = error.message.orEmpty() if (msg.contains("404")) { - oauthDataStore.saveIdToken(idToken) + oauthDataStore.saveIdToken(platform = "google", token = idToken) oauthDataStore.savePlatform(OAuthProvider.GOOGLE) _sideEffects.send(LoginContract.LoginSideEffect.NavigateToSignUp) } else { From d87d6cdbbfd526a3cc2ae19676bbed07eb241c45 Mon Sep 17 00:00:00 2001 From: SYAAINN Date: Mon, 4 Aug 2025 00:11:19 +0900 Subject: [PATCH 8/8] =?UTF-8?q?[FEAT/#314]=20=EB=94=A5=EB=A7=81=ED=81=AC?= =?UTF-8?q?=20=EC=B6=94=EC=A0=81=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=97=90?= =?UTF-8?q?=EC=96=B4=EB=B8=8C=EB=A6=BF=EC=A7=80=EB=A5=BC=20=EB=8F=84?= =?UTF-8?q?=EC=9E=85=ED=95=A9=EB=8B=88=EB=8B=A4.=20-=20airbridge=20?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EB=95=8C?= =?UTF-8?q?=EB=AC=B8=EC=97=90=20dataExtractionRules,=20fullBackupContent?= =?UTF-8?q?=20=EB=B6=80=EB=B6=84=EC=97=90=EC=84=9C=20=EC=A4=91=EB=B3=B5?= =?UTF-8?q?=EC=84=A0=EC=96=B8=EC=9D=B4=20=EC=9D=B4=EB=A3=A8=EC=96=B4?= =?UTF-8?q?=EC=A0=B8=20replace=EB=A5=BC=20=ED=99=95=EC=9E=A5=ED=96=88?= =?UTF-8?q?=EC=8A=B5=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 3 ++- app/src/main/java/com/sopt/clody/ClodyApplication.kt | 8 ++++++++ gradle/libs.versions.toml | 3 +++ settings.gradle.kts | 1 + 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fd21eb50..0812d8ce 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -157,4 +157,5 @@ dependencies { implementation(libs.androidx.credentials.play.services.auth) implementation(libs.google.auth) implementation(libs.androidx.datastore.preferences) + implementation(libs.airbridge) } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 341bddc1..95012ebc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools"> + @@ -19,7 +20,7 @@ android:supportsRtl="true" android:theme="@style/Theme.CLODY" android:usesCleartextTraffic="true" - tools:replace="icon, label" + tools:replace="android:icon,android:label,android:dataExtractionRules,android:fullBackupContent" tools:targetApi="31">