diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c8bfb09f..0812d8ce 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" @@ -159,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"> = 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/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/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/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/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/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/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 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/SignUpViewModel.kt b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpViewModel.kt index 3ecb7f01..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,45 +156,43 @@ 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 } val request = SignUpRequestDto( - platform = OAuthProvider.GOOGLE.apiValue, + platform = OAuthProvider.GOOGLE.platform, name = state.nickname, fcmToken = fcmToken, ) 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.apiValue, - 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/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/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..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 @@ -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,9 @@ 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")) { + 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 = { @@ -124,8 +123,8 @@ class LoginViewModel @AssistedInject constructor( setState { copy(isLoading = false) } val msg = error.message.orEmpty() - if (msg.contains("500") || msg.contains("유저가 없습니다")) { - oauthDataStore.saveIdToken(idToken) + if (msg.contains("404")) { + oauthDataStore.saveIdToken(platform = "google", token = idToken) oauthDataStore.savePlatform(OAuthProvider.GOOGLE) _sideEffects.send(LoginContract.LoginSideEffect.NavigateToSignUp) } else { 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/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/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/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/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(), ) } } 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++ 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..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 @@ -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,9 +35,9 @@ 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() AmplitudeUtils.trackEvent(eventName = AmplitudeConstraints.SETTING) } @@ -45,10 +46,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/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/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/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/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/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" 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 } 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 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a95a7d14..061b625b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -57,6 +57,8 @@ androidxCredentials = "1.5.0" googleAuth = "21.3.0" datastore = "1.1.7" +airbridge = "4.7.0" + [libraries] # AndroidX Core core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" } @@ -119,6 +121,7 @@ lottie-compose = { group = "com.airbnb.android", name = "lottie-compose", versio coil = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" } kakao-user = { group = "com.kakao.sdk", name = "v2-user", version.ref = "kakao" } admob = { group = "com.google.android.gms", name = "play-services-ads", version.ref = "admob" } +airbridge = { group = "io.airbridge", name = "sdk-android", version.ref = "airbridge"} # Accompanist accompanist-systemuicontroller = { group = "com.google.accompanist", name = "accompanist-systemuicontroller", version.ref = "accompanist" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 9b8bbe25..ee351d1a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,6 +20,7 @@ dependencyResolutionManagement { mavenCentral() maven(url = "https://jitpack.io") maven { url = java.net.URI("https://devrepo.kakao.com/nexus/content/groups/public/") } + maven(url = "https://sdk-download.airbridge.io/maven") } }