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")
}
}