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