diff --git a/build-logic/src/main/kotlin/com/titi/common/BuildInfo.kt b/build-logic/src/main/kotlin/com/titi/common/BuildInfo.kt
index f5825f41..2aeed66f 100644
--- a/build-logic/src/main/kotlin/com/titi/common/BuildInfo.kt
+++ b/build-logic/src/main/kotlin/com/titi/common/BuildInfo.kt
@@ -9,7 +9,7 @@ object BuildType {
object AppConfig {
const val APP_ID = "com.titi.app"
- const val APP_VERSION_NAME = "1.3.1"
- const val APP_VERSION_CODE = 39
+ const val APP_VERSION_NAME = "1.4.0"
+ const val APP_VERSION_CODE = 40
const val APP_NAME = "TiTi"
}
\ No newline at end of file
diff --git a/core/designsystem/src/main/kotlin/com/titi/app/core/designsystem/component/TdsGraphContent.kt b/core/designsystem/src/main/kotlin/com/titi/app/core/designsystem/component/TdsGraphContent.kt
index 2066db15..b397f411 100644
--- a/core/designsystem/src/main/kotlin/com/titi/app/core/designsystem/component/TdsGraphContent.kt
+++ b/core/designsystem/src/main/kotlin/com/titi/app/core/designsystem/component/TdsGraphContent.kt
@@ -49,7 +49,7 @@ fun TdsGraphContent(
modifier = Modifier.wrapContentSize(),
userScrollEnabled = true,
state = pagerState,
- beyondBoundsPageCount = 2,
+ beyondBoundsPageCount = 3,
) { page ->
when (page % 4) {
0 -> TdsStandardDailyGraph(
diff --git a/core/designsystem/src/main/res/values-ko-rKR/strings.xml b/core/designsystem/src/main/res/values-ko-rKR/strings.xml
index 181e97aa..670b3fca 100644
--- a/core/designsystem/src/main/res/values-ko-rKR/strings.xml
+++ b/core/designsystem/src/main/res/values-ko-rKR/strings.xml
@@ -65,4 +65,9 @@
새로운 기록 설정
Daily 수정/생성
중복된 내역이 존재 합니다.
+ 언어
+ 시스템
+ 한국어
+ 영어
+ 중국어
\ No newline at end of file
diff --git a/core/designsystem/src/main/res/values-zh/strings.xml b/core/designsystem/src/main/res/values-zh/strings.xml
index 43dbd7af..3cd6d171 100644
--- a/core/designsystem/src/main/res/values-zh/strings.xml
+++ b/core/designsystem/src/main/res/values-zh/strings.xml
@@ -65,4 +65,9 @@
新建日志设置
"每日修改/创建 "
重复的历史存在。
+ 语言
+ 系统
+ 韩国语
+ 英语
+ 中国话
\ No newline at end of file
diff --git a/core/designsystem/src/main/res/values/strings.xml b/core/designsystem/src/main/res/values/strings.xml
index 09c9ff95..16f7941a 100644
--- a/core/designsystem/src/main/res/values/strings.xml
+++ b/core/designsystem/src/main/res/values/strings.xml
@@ -70,4 +70,9 @@
Setting New Record
Daily Modify/Create
Duplicate history exists.
+ Language
+ System
+ Korean
+ English
+ Chinese
\ No newline at end of file
diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts
index 443ea1bf..9020b4e7 100644
--- a/core/ui/build.gradle.kts
+++ b/core/ui/build.gradle.kts
@@ -1,4 +1,5 @@
plugins {
+ id("titi.android.compose")
id("titi.android.library")
}
diff --git a/core/ui/src/main/kotlin/com/titi/app/core/ui/LanguageManager.kt b/core/ui/src/main/kotlin/com/titi/app/core/ui/LanguageManager.kt
new file mode 100644
index 00000000..0ec2c90d
--- /dev/null
+++ b/core/ui/src/main/kotlin/com/titi/app/core/ui/LanguageManager.kt
@@ -0,0 +1,61 @@
+package com.titi.app.core.ui
+
+import android.app.LocaleManager
+import android.content.Context
+import android.os.Build
+import android.os.LocaleList
+import androidx.core.app.LocaleManagerCompat
+import java.util.Locale
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class LanguageManager(private val context: Context) {
+ private val _currentLanguage = MutableStateFlow(getCurrentLanguage())
+ val currentLanguage: StateFlow = _currentLanguage.asStateFlow()
+
+ fun setLanguage(languageCode: String) {
+ val localeList = when (languageCode) {
+ SYSTEM_DEFAULT -> LocaleList.getEmptyLocaleList()
+ else -> LocaleList.forLanguageTags(languageCode)
+ }
+
+ _currentLanguage.value = languageCode
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ context.getSystemService(LocaleManager::class.java).applicationLocales = localeList
+ } else {
+ val locale = Locale(languageCode)
+ Locale.setDefault(locale)
+
+ val resources = context.resources
+ val configuration = resources.configuration
+ configuration.setLocale(locale)
+ resources.updateConfiguration(configuration, resources.displayMetrics)
+ }
+ }
+
+ private fun getCurrentLanguage(): String {
+ return if (isUsingSystemDefault()) {
+ SYSTEM_DEFAULT
+ } else {
+ val currentLocale = context.resources.configuration.locales[0]
+ when (currentLocale.language) {
+ "en" -> ENGLISH
+ "ko" -> KOREAN
+ "zh" -> CHINA
+ else -> SYSTEM_DEFAULT
+ }
+ }
+ }
+
+ private fun isUsingSystemDefault(): Boolean {
+ return LocaleManagerCompat.getApplicationLocales(context).isEmpty
+ }
+
+ companion object {
+ const val SYSTEM_DEFAULT = "system"
+ const val ENGLISH = "en"
+ const val KOREAN = "ko"
+ const val CHINA = "zh"
+ }
+}
diff --git a/feature/setting/src/main/kotlin/com/titi/app/feature/setting/mapper/FeatureToRepositoryMapper.kt b/feature/setting/src/main/kotlin/com/titi/app/feature/setting/mapper/FeatureToRepositoryMapper.kt
index 393b9d85..182f8591 100644
--- a/feature/setting/src/main/kotlin/com/titi/app/feature/setting/mapper/FeatureToRepositoryMapper.kt
+++ b/feature/setting/src/main/kotlin/com/titi/app/feature/setting/mapper/FeatureToRepositoryMapper.kt
@@ -3,7 +3,7 @@ package com.titi.app.feature.setting.mapper
import com.titi.app.data.notification.api.model.NotificationRepositoryModel
import com.titi.app.feature.setting.model.SettingUiState
-fun SettingUiState.SwitchState.toRepositoryModel() = NotificationRepositoryModel(
+internal fun SettingUiState.SwitchState.toRepositoryModel() = NotificationRepositoryModel(
timerFiveMinutesBeforeTheEnd = timerFiveMinutesBeforeTheEnd,
timerBeforeTheEnd = timerBeforeTheEnd,
stopwatch = stopwatch,
diff --git a/feature/setting/src/main/kotlin/com/titi/app/feature/setting/mapper/RepositoryToFeatureMapper.kt b/feature/setting/src/main/kotlin/com/titi/app/feature/setting/mapper/RepositoryToFeatureMapper.kt
index c261b703..a0d3f088 100644
--- a/feature/setting/src/main/kotlin/com/titi/app/feature/setting/mapper/RepositoryToFeatureMapper.kt
+++ b/feature/setting/src/main/kotlin/com/titi/app/feature/setting/mapper/RepositoryToFeatureMapper.kt
@@ -3,7 +3,7 @@ package com.titi.app.feature.setting.mapper
import com.titi.app.data.notification.api.model.NotificationRepositoryModel
import com.titi.app.feature.setting.model.SettingUiState
-fun NotificationRepositoryModel.toFeatureModel() = SettingUiState.SwitchState(
+internal fun NotificationRepositoryModel.toFeatureModel() = SettingUiState.SwitchState(
timerFiveMinutesBeforeTheEnd = timerFiveMinutesBeforeTheEnd,
timerBeforeTheEnd = timerBeforeTheEnd,
stopwatch = stopwatch,
diff --git a/feature/setting/src/main/kotlin/com/titi/app/feature/setting/model/SettingActions.kt b/feature/setting/src/main/kotlin/com/titi/app/feature/setting/model/SettingActions.kt
index d665f7ba..568c3b25 100644
--- a/feature/setting/src/main/kotlin/com/titi/app/feature/setting/model/SettingActions.kt
+++ b/feature/setting/src/main/kotlin/com/titi/app/feature/setting/model/SettingActions.kt
@@ -18,4 +18,7 @@ sealed interface SettingActions {
@JvmInline
value class Version(val versionState: SettingUiState.VersionState) : Updates
}
+
+ @JvmInline
+ value class Language(val language: String) : SettingActions
}
diff --git a/feature/setting/src/main/kotlin/com/titi/app/feature/setting/ui/SettingScreen.kt b/feature/setting/src/main/kotlin/com/titi/app/feature/setting/ui/SettingScreen.kt
index 017bd96a..7af4bc97 100644
--- a/feature/setting/src/main/kotlin/com/titi/app/feature/setting/ui/SettingScreen.kt
+++ b/feature/setting/src/main/kotlin/com/titi/app/feature/setting/ui/SettingScreen.kt
@@ -23,6 +23,7 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
+import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.TopAppBar
@@ -30,6 +31,7 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -40,6 +42,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.airbnb.mvrx.compose.collectAsState
import com.airbnb.mvrx.compose.mavericksViewModel
import com.google.firebase.database.DataSnapshot
@@ -54,13 +57,14 @@ import com.titi.app.core.designsystem.navigation.TopLevelDestination
import com.titi.app.core.designsystem.theme.TdsColor
import com.titi.app.core.designsystem.theme.TdsTextStyle
import com.titi.app.core.designsystem.theme.TiTiTheme
+import com.titi.app.core.ui.LanguageManager
import com.titi.app.feature.setting.model.SettingActions
import com.titi.app.feature.setting.model.SettingUiState
import com.titi.app.feature.setting.model.Version
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun SettingScreen(
+internal fun SettingScreen(
viewModel: SettingViewModel = mavericksViewModel(),
handleNavigateActions: (SettingActions.Navigates) -> Unit,
onNavigateToDestination: (TopLevelDestination) -> Unit,
@@ -71,6 +75,8 @@ fun SettingScreen(
val databaseReference = firebaseDatabase.getReference("versions")
val context = LocalContext.current
+ val languageManager = remember { LanguageManager(context) }
+ val currentLanguage by languageManager.currentLanguage.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
databaseReference.addValueEventListener(
@@ -142,11 +148,16 @@ fun SettingScreen(
.padding(it)
.safeDrawingPadding(),
uiState = uiState,
+ currentLanguage = currentLanguage,
onSettingActions = { settingActions ->
when (settingActions) {
is SettingActions.Navigates -> handleNavigateActions(settingActions)
is SettingActions.Updates -> viewModel.handleUpdateActions(settingActions)
+
+ is SettingActions.Language -> {
+ languageManager.setLanguage(settingActions.language)
+ }
}
},
)
@@ -157,6 +168,7 @@ fun SettingScreen(
private fun SettingScreen(
modifier: Modifier,
uiState: SettingUiState,
+ currentLanguage: String,
onSettingActions: (SettingActions) -> Unit,
) {
val scrollState = rememberScrollState()
@@ -179,6 +191,13 @@ private fun SettingScreen(
Spacer(modifier = Modifier.height(35.dp))
+ SettingLanguageSection(
+ currentLanguage = currentLanguage,
+ onSettingActions = onSettingActions,
+ )
+
+ Spacer(modifier = Modifier.height(35.dp))
+
SettingVersionSection(
versionState = uiState.versionState,
onSettingActions = onSettingActions,
@@ -297,6 +316,86 @@ private fun SettingNotificationSection(
)
}
+@Composable
+private fun SettingLanguageSection(
+ currentLanguage: String,
+ onSettingActions: (SettingActions) -> Unit,
+) {
+ TdsText(
+ modifier = Modifier.padding(start = 16.dp),
+ text = stringResource(R.string.setting_text_language),
+ textStyle = TdsTextStyle.SEMI_BOLD_TEXT_STYLE,
+ fontSize = 14.sp,
+ color = TdsColor.TEXT,
+ )
+
+ Spacer(modifier = Modifier.height(4.dp))
+
+ ListContent(
+ title = stringResource(R.string.setting_text_system),
+ rightAreaContent = {
+ RadioButton(
+ selected = currentLanguage == LanguageManager.SYSTEM_DEFAULT,
+ onClick = {
+ onSettingActions(
+ SettingActions.Language(LanguageManager.SYSTEM_DEFAULT),
+ )
+ },
+ )
+ },
+ )
+
+ Spacer(modifier = Modifier.height(1.dp))
+
+ ListContent(
+ title = stringResource(R.string.setting_text_korean),
+ rightAreaContent = {
+ RadioButton(
+ selected = currentLanguage == LanguageManager.KOREAN,
+ onClick = {
+ onSettingActions(
+ SettingActions.Language(LanguageManager.KOREAN),
+ )
+ },
+ )
+ },
+ )
+
+ Spacer(modifier = Modifier.height(1.dp))
+
+ ListContent(
+ title = stringResource(R.string.setting_text_english),
+ rightAreaContent = {
+ RadioButton(
+ selected = currentLanguage == LanguageManager.ENGLISH,
+ onClick = {
+ onSettingActions(
+ SettingActions.Language(LanguageManager.ENGLISH),
+ )
+ },
+ )
+ },
+ )
+
+ Spacer(modifier = Modifier.height(1.dp))
+
+ ListContent(
+ title = stringResource(R.string.setting_text_china),
+ rightAreaContent = {
+ RadioButton(
+ selected = currentLanguage == LanguageManager.CHINA,
+ onClick = {
+ onSettingActions(
+ SettingActions.Language(LanguageManager.CHINA),
+ )
+ },
+ )
+ },
+ )
+
+ Spacer(modifier = Modifier.height(1.dp))
+}
+
@Composable
private fun SettingVersionSection(
versionState: SettingUiState.VersionState,
@@ -500,6 +599,7 @@ private fun SettingScreenPreview() {
SettingScreen(
modifier = Modifier,
uiState = SettingUiState(),
+ currentLanguage = LanguageManager.SYSTEM_DEFAULT,
onSettingActions = {},
)
}
diff --git a/feature/setting/src/main/kotlin/com/titi/app/feature/setting/ui/SettingViewModel.kt b/feature/setting/src/main/kotlin/com/titi/app/feature/setting/ui/SettingViewModel.kt
index d0b399f1..a88ac1ad 100644
--- a/feature/setting/src/main/kotlin/com/titi/app/feature/setting/ui/SettingViewModel.kt
+++ b/feature/setting/src/main/kotlin/com/titi/app/feature/setting/ui/SettingViewModel.kt
@@ -1,5 +1,6 @@
package com.titi.app.feature.setting.ui
+import android.util.Log
import com.airbnb.mvrx.MavericksViewModel
import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.hilt.AssistedViewModelFactory
@@ -12,10 +13,10 @@ import com.titi.app.feature.setting.model.SettingUiState
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.launch
-class SettingViewModel @AssistedInject constructor(
+internal class SettingViewModel @AssistedInject constructor(
@Assisted initialState: SettingUiState,
private val notificationRepository: NotificationRepository,
) : MavericksViewModel(initialState) {
@@ -23,28 +24,25 @@ class SettingViewModel @AssistedInject constructor(
init {
viewModelScope.launch {
notificationRepository.getNotificationFlow()
- .collectLatest {
- updateSwitch(it.toFeatureModel())
+ .catch {
+ Log.e("SettingViewModel", it.message.toString())
+ }.setOnEach {
+ copy(switchState = it.toFeatureModel())
}
}
}
fun handleUpdateActions(updateActions: SettingActions.Updates) {
when (updateActions) {
- is SettingActions.Updates.Switch -> {
- viewModelScope.launch {
- notificationRepository
- .setNotification(updateActions.switchState.toRepositoryModel())
- }
- }
+ is SettingActions.Updates.Switch -> updateSwitch(updateActions.switchState)
is SettingActions.Updates.Version -> updateVersion(updateActions.versionState)
}
}
private fun updateSwitch(switchState: SettingUiState.SwitchState) {
- setState {
- copy(switchState = switchState)
+ viewModelScope.launch {
+ notificationRepository.setNotification(switchState.toRepositoryModel())
}
}
diff --git a/release-note.txt b/release-note.txt
index cd9e99c0..eec84856 100644
--- a/release-note.txt
+++ b/release-note.txt
@@ -1,2 +1,3 @@
-TiTi android dev 1.3.1
--
\ No newline at end of file
+TiTi android 1.4.0
+- Setting Screen Ver.3
+- Bug share picture
\ No newline at end of file