From 1eea7007f38fb308a2c83d779ec549e0e780e22c Mon Sep 17 00:00:00 2001 From: mandviverma Date: Mon, 9 Feb 2026 19:25:09 +0530 Subject: [PATCH 01/30] intial functionality checked --- .../world/respect/app/app/AppNavHost.kt | 15 ++- .../app/view/settings/SettingsScreen.kt | 95 ++++++++++++++++++- .../accountlist/AccountListViewModel.kt | 11 ++- .../viewmodel/settings/SettingsViewModel.kt | 57 ++++++++++- 4 files changed, 160 insertions(+), 18 deletions(-) diff --git a/respect-app-compose/src/commonMain/kotlin/world/respect/app/app/AppNavHost.kt b/respect-app-compose/src/commonMain/kotlin/world/respect/app/app/AppNavHost.kt index 59b3b4972..07431829a 100644 --- a/respect-app-compose/src/commonMain/kotlin/world/respect/app/app/AppNavHost.kt +++ b/respect-app-compose/src/commonMain/kotlin/world/respect/app/app/AppNavHost.kt @@ -59,7 +59,7 @@ import world.respect.app.view.report.list.ReportTemplateListScreen import world.respect.app.view.scanqrcode.ScanQRCodeScreen import world.respect.app.view.schooldirectory.edit.SchoolDirectoryEditScreen import world.respect.app.view.schooldirectory.list.SchoolDirectoryListScreen -import world.respect.app.view.settings.SettingsScreenForViewModel +import world.respect.app.view.settings.SettingsScreen import world.respect.app.viewmodel.respectViewModel import world.respect.shared.navigation.AccountList import world.respect.shared.navigation.Acknowledgement @@ -154,8 +154,6 @@ import world.respect.shared.viewmodel.report.list.ReportListViewModel import world.respect.shared.viewmodel.report.list.ReportTemplateListViewModel import world.respect.shared.viewmodel.schooldirectory.edit.SchoolDirectoryEditViewModel import world.respect.shared.viewmodel.schooldirectory.list.SchoolDirectoryListViewModel -import world.respect.shared.viewmodel.settings.SettingsViewModel - @Composable fun AppNavHost( @@ -539,12 +537,11 @@ fun AppNavHost( ) } composable { - val viewModel: SettingsViewModel = respectViewModel( - onSetAppUiState = onSetAppUiState, - navController = respectNavController - ) - SettingsScreenForViewModel( - viewModel = viewModel + SettingsScreen( + viewModel = respectViewModel( + onSetAppUiState = onSetAppUiState, + navController = respectNavController + ) ) } composable { diff --git a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt index e252880ed..ba653125a 100644 --- a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt +++ b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt @@ -5,27 +5,75 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Language import androidx.compose.material.icons.filled.Map +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Card import androidx.compose.material3.Icon import androidx.compose.material3.ListItem import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import okhttp3.internal.http2.Settings import org.jetbrains.compose.resources.stringResource +import world.respect.app.view.manageuser.accountlist.AccountListScreen import world.respect.shared.generated.resources.Res +import world.respect.shared.generated.resources.language import world.respect.shared.generated.resources.loading import world.respect.shared.generated.resources.mappings +import world.respect.shared.viewmodel.settings.SettingsUiState import world.respect.shared.viewmodel.settings.SettingsViewModel @Composable fun SettingsScreen( + viewModel: SettingsViewModel +) { + val uiState by viewModel.uiState.collectAsState() + SettingsScreen( + uiState = uiState, + onNavigateToMapping = viewModel::onNavigateToMapping, + onClickLang = viewModel::onClickLang, + onClickLanguage = viewModel::onClickLanguage, + onDismissLangDialog = viewModel::onDismissLangDialog + ) +} + +@Composable +fun SettingsScreen( + uiState: SettingsUiState, onNavigateToMapping: () -> Unit = {}, + onClickLanguage: () -> Unit = {}, + onDismissLangDialog: () -> Unit = {}, + onClickLang: (world.respect.shared.viewmodel.settings.UiLanguage) -> Unit = {} ) { + + if(uiState.langDialogVisible) { + //As per https://developer.android.com/jetpack/compose/components/dialog + SettingsDialog( + onDismissRequest = onDismissLangDialog, + ) { + + uiState.availableLanguages.forEach { lang -> + ListItem( + modifier = Modifier.clickable { onClickLang(lang) }, + headlineContent = { Text(lang.langDisplay) } + ) + } + } + } + LazyColumn( modifier = Modifier .fillMaxSize() @@ -39,6 +87,31 @@ fun SettingsScreen( testTag = "mapping_setting_item" ) } + + item { + ListItem( + headlineContent = { + Text(text = stringResource(Res.string.language)) + }, + leadingContent = { + Icon( + imageVector = Icons.Filled.Language, + contentDescription = stringResource(Res.string.language) + ) + }, + supportingContent = { + Text(text = uiState.currentLanguage) + }, + modifier = Modifier + .fillMaxWidth() + .clickable( + onClick = { + onClickLanguage() + + } + ) + ) + } } } @@ -74,11 +147,23 @@ private fun SettingsListItem( ) } + @Composable -fun SettingsScreenForViewModel( - viewModel: SettingsViewModel +fun SettingsDialog( + onDismissRequest: () -> Unit, + content: @Composable () -> Unit, ) { - SettingsScreen( - onNavigateToMapping = viewModel::onNavigateToMapping - ) + Dialog( + onDismissRequest = onDismissRequest, + ) { + Card( + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()) + .padding(16.dp), + shape = RoundedCornerShape(16.dp), + ) { + content() + } + } } diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/manageuser/accountlist/AccountListViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/manageuser/accountlist/AccountListViewModel.kt index 57a0212d9..3161ec10c 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/manageuser/accountlist/AccountListViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/manageuser/accountlist/AccountListViewModel.kt @@ -27,6 +27,7 @@ import world.respect.shared.navigation.GetStartedScreen import world.respect.shared.navigation.NavCommand import world.respect.shared.navigation.PersonDetail import world.respect.shared.navigation.RespectAppLauncher +import world.respect.shared.navigation.Settings import world.respect.shared.navigation.WaitingForApproval import world.respect.shared.util.ext.asUiText import world.respect.shared.util.ext.isSameAccount @@ -63,9 +64,11 @@ class AccountListViewModel( init { _appUiState.update { it.copy( + onClickSettings=::onClickSettings, title = Res.string.accounts.asUiText(), hideBottomNavigation = true, userAccountIconVisible = false, + settingsIconVisible = true ) } @@ -154,6 +157,12 @@ class AccountListViewModel( } } + fun onClickSettings() { + _navCommandFlow.tryEmit( + NavCommand.Navigate(Settings) + ) + } + fun onClickAccount(account: RespectAccount) { respectAccountManager.switchAccount(account) @@ -213,7 +222,6 @@ class AccountListViewModel( } } - fun onClickLogout() { uiState.value.selectedAccount?.also { viewModelScope.launch { @@ -221,5 +229,4 @@ class AccountListViewModel( } } } - } \ No newline at end of file diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt index 23b8c3b98..658abb119 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt @@ -1,20 +1,29 @@ package world.respect.shared.viewmodel.settings import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import kotlinx.serialization.json.Json import world.respect.shared.generated.resources.Res import world.respect.shared.generated.resources.settings import world.respect.shared.navigation.CurriculumMappingList import world.respect.shared.navigation.NavCommand +import world.respect.shared.navigation.Settings import world.respect.shared.util.ext.asUiText import world.respect.shared.viewmodel.RespectViewModel +data class UiLanguage(val langCode: String, val langDisplay: String) + data class SettingsUiState( val loading: Boolean = false, + val langDialogVisible: Boolean = false, + val currentLanguage: String = "", + val availableLanguages: List = emptyList() + ) class SettingsViewModel( @@ -23,7 +32,7 @@ class SettingsViewModel( ) : RespectViewModel(savedStateHandle) { private val _uiState = MutableStateFlow(SettingsUiState()) - val uiState: Flow = _uiState.asStateFlow() + val uiState = _uiState.asStateFlow() init { _appUiState.update { prev -> @@ -35,15 +44,59 @@ class SettingsViewModel( hideBottomNavigation = true, ) } + _uiState.update { + it.copy( + availableLanguages = listOf( + UiLanguage("en", "English"), + UiLanguage("hi", "Hindi"), + UiLanguage("fr", "French") + ), + currentLanguage = "English" + ) + } } + fun onClickSettings() { + _navCommandFlow.tryEmit( + NavCommand.Navigate(Settings) + ) + } fun onNavigateToLanguage() { // TODO } + fun onClickLanguage() { + _uiState.update { prev -> + prev.copy( + langDialogVisible = true + ) + } + } + + fun onDismissLangDialog() { + _uiState.update { prev -> + prev.copy(langDialogVisible = false) + } + } + + fun onClickLang(lang: UiLanguage) { + + _uiState.update { prev -> + prev.copy(langDialogVisible = false) + } + + _uiState.update { prev -> + prev.copy( + currentLanguage = lang.langDisplay + ) + } + + + } + fun onNavigateToMapping() { _navCommandFlow.tryEmit( NavCommand.Navigate(CurriculumMappingList) ) } -} \ No newline at end of file +} From a2f29eb02397b994fe3f5a3c7feac0f06aad485a Mon Sep 17 00:00:00 2001 From: mandviverma Date: Mon, 9 Feb 2026 21:14:36 +0530 Subject: [PATCH 02/30] Enable automatic per-app language support --- respect-app-compose/build.gradle.kts | 6 ++++++ .../kotlin/world/respect/MainActivity.kt | 14 +++++++++++++- .../src/androidMain/res/values-hi/strings.xml | 3 +++ .../composeResources/values-hi/strings.xml | 8 ++++++++ .../shared/viewmodel/settings/SettingsViewModel.kt | 6 +----- 5 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 respect-app-compose/src/androidMain/res/values-hi/strings.xml create mode 100644 respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml diff --git a/respect-app-compose/build.gradle.kts b/respect-app-compose/build.gradle.kts index f41b40f62..63234816c 100644 --- a/respect-app-compose/build.gradle.kts +++ b/respect-app-compose/build.gradle.kts @@ -177,6 +177,10 @@ android { namespace = "world.respect.app" compileSdk = libs.versions.android.compileSdk.get().toInt() + androidResources{ + generateLocaleConfig = true + } + defaultConfig { applicationId = "world.respect.app" minSdk = libs.versions.android.minSdk.get().toInt() @@ -184,6 +188,8 @@ android { versionCode = 121 versionName = "1.0.21" + resConfigs("en", "hi") + for(propName in ACRA_PROP_NAMES) { buildConfigField( type = "String", diff --git a/respect-app-compose/src/androidMain/kotlin/world/respect/MainActivity.kt b/respect-app-compose/src/androidMain/kotlin/world/respect/MainActivity.kt index 00cbec225..0edebb4fe 100644 --- a/respect-app-compose/src/androidMain/kotlin/world/respect/MainActivity.kt +++ b/respect-app-compose/src/androidMain/kotlin/world/respect/MainActivity.kt @@ -1,8 +1,12 @@ package world.respect +import android.app.LocaleManager +import android.os.Build import android.os.Bundle +import androidx.appcompat.app.AppCompatDelegate import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview +import androidx.core.os.LocaleListCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle @@ -12,7 +16,6 @@ import org.koin.android.ext.android.getKoin import org.koin.android.scope.AndroidScopeComponent import org.koin.androidx.scope.activityScope import org.koin.core.scope.Scope -import world.respect.app.app.App import world.respect.credentials.passkey.CreatePasskeyUseCaseAndroidChannelHost import world.respect.credentials.passkey.CreatePasskeyUseCaseProcessor import world.respect.credentials.passkey.GetCredentialUseCase @@ -26,6 +29,7 @@ import world.respect.datalayer.respect.model.RespectSchoolDirectory import world.respect.shared.domain.biometric.BiometricAuthProcessor import world.respect.shared.domain.biometric.BiometricAuthUseCaseAndroidImpl import world.respect.view.app.AbstractAppActivity +import kotlin.jvm.java class MainActivity : AbstractAppActivity(), AndroidScopeComponent { @@ -39,6 +43,14 @@ class MainActivity : AbstractAppActivity(), AndroidScopeComponent { val koin = getKoin() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + val localeManager = getSystemService(LocaleManager::class.java) + localeManager?.applicationLocales ?: LocaleListCompat.getEmptyLocaleList() + } else { + AppCompatDelegate.getApplicationLocales() + } + + val createPasskeyChannelHost = koin.get() val getCredentialUseCase = koin.get() as GetCredentialUseCaseAndroidImpl diff --git a/respect-app-compose/src/androidMain/res/values-hi/strings.xml b/respect-app-compose/src/androidMain/res/values-hi/strings.xml new file mode 100644 index 000000000..55344e519 --- /dev/null +++ b/respect-app-compose/src/androidMain/res/values-hi/strings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml b/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml new file mode 100644 index 000000000..b8b444089 --- /dev/null +++ b/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml @@ -0,0 +1,8 @@ + + + + अनुप्रयोग + आपने स्कूल को सफलतापूर्वक रजिस्टर कर लिया है + आपने अभी तक अपने स्कूल के लिए कोई ऐप नहीं जोड़ा है। + + \ No newline at end of file diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt index 658abb119..913b2a7cb 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt @@ -56,11 +56,7 @@ class SettingsViewModel( } } - fun onClickSettings() { - _navCommandFlow.tryEmit( - NavCommand.Navigate(Settings) - ) - } + fun onNavigateToLanguage() { // TODO } From 829dcd8fe44afbe8ce4f3f3e8d2012e4d2892735 Mon Sep 17 00:00:00 2001 From: mandviverma Date: Tue, 10 Feb 2026 15:42:36 +0530 Subject: [PATCH 03/30] This commit introduces an in-app language selection feature, allowing users to change the application's language independently of the system settings. --- respect-app-compose/build.gradle.kts | 1 + .../src/androidMain/AndroidManifest.xml | 9 ++ .../kotlin/world/respect/AppKoinModule.kt | 34 ++++++++ .../kotlin/world/respect/MainActivity.kt | 8 -- .../app/view/settings/SettingsScreen.kt | 7 +- .../LocaleSettingDelegateAndroid.kt | 19 +++++ .../domain/applanguage/ProvideAppLocale.kt | 37 ++++++++ .../applanguage/RespectMobileSystemImpl.kt | 51 +++++++++++ .../composeResources/values-hi/strings.xml | 1 + .../composeResources/values/strings.xml | 9 ++ .../applanguage/RespectMobileSystemCommon.kt | 29 +++++++ .../viewmodel/settings/SettingsViewModel.kt | 84 ++++++++++++------- 12 files changed, 246 insertions(+), 43 deletions(-) create mode 100644 respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/LocaleSettingDelegateAndroid.kt create mode 100644 respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/ProvideAppLocale.kt create mode 100644 respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.kt create mode 100755 respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt diff --git a/respect-app-compose/build.gradle.kts b/respect-app-compose/build.gradle.kts index 63234816c..c583adad9 100644 --- a/respect-app-compose/build.gradle.kts +++ b/respect-app-compose/build.gradle.kts @@ -146,6 +146,7 @@ kotlin { implementation(libs.reorderable) implementation(libs.kscan) implementation(libs.qrose) + implementation(compose.components.resources) } desktopMain.dependencies { diff --git a/respect-app-compose/src/androidMain/AndroidManifest.xml b/respect-app-compose/src/androidMain/AndroidManifest.xml index 49ec9ec13..146297299 100644 --- a/respect-app-compose/src/androidMain/AndroidManifest.xml +++ b/respect-app-compose/src/androidMain/AndroidManifest.xml @@ -82,6 +82,15 @@ + + + + > { + androidx.core.os.ConfigurationCompat.getLocales( + androidContext().resources.configuration + ).toLanguageTags().split(",") + } + + single { + SupportedLanguagesConfig( + systemLocales = get(), + settings = get() + ) + } + single { + LocaleSettingDelegateAndroid() + } + single { + RespectMobileSystemImpl( + context = androidContext(), + settings = get(), + langConfig = get() + ) + } + single { + SetLanguageUseCaseAndroid( + languagesConfig = get() + ) + } + single(createdAtStart = true) { GetDeferredDeepLinkUseCaseAndroid( context = androidContext(), diff --git a/respect-app-compose/src/androidMain/kotlin/world/respect/MainActivity.kt b/respect-app-compose/src/androidMain/kotlin/world/respect/MainActivity.kt index 0edebb4fe..746dfbf79 100644 --- a/respect-app-compose/src/androidMain/kotlin/world/respect/MainActivity.kt +++ b/respect-app-compose/src/androidMain/kotlin/world/respect/MainActivity.kt @@ -43,14 +43,6 @@ class MainActivity : AbstractAppActivity(), AndroidScopeComponent { val koin = getKoin() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - val localeManager = getSystemService(LocaleManager::class.java) - localeManager?.applicationLocales ?: LocaleListCompat.getEmptyLocaleList() - } else { - AppCompatDelegate.getApplicationLocales() - } - - val createPasskeyChannelHost = koin.get() val getCredentialUseCase = koin.get() as GetCredentialUseCaseAndroidImpl diff --git a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt index ba653125a..f85adef49 100644 --- a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt +++ b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt @@ -5,14 +5,12 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Language import androidx.compose.material.icons.filled.Map -import androidx.compose.material3.AlertDialog import androidx.compose.material3.Card import androidx.compose.material3.Icon import androidx.compose.material3.ListItem @@ -26,9 +24,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog -import okhttp3.internal.http2.Settings import org.jetbrains.compose.resources.stringResource -import world.respect.app.view.manageuser.accountlist.AccountListScreen +import world.respect.shared.domain.applanguage.RespectMobileSystemCommon import world.respect.shared.generated.resources.Res import world.respect.shared.generated.resources.language import world.respect.shared.generated.resources.loading @@ -56,7 +53,7 @@ fun SettingsScreen( onNavigateToMapping: () -> Unit = {}, onClickLanguage: () -> Unit = {}, onDismissLangDialog: () -> Unit = {}, - onClickLang: (world.respect.shared.viewmodel.settings.UiLanguage) -> Unit = {} + onClickLang: (RespectMobileSystemCommon.UiLanguage) -> Unit = {} ) { if(uiState.langDialogVisible) { diff --git a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/LocaleSettingDelegateAndroid.kt b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/LocaleSettingDelegateAndroid.kt new file mode 100644 index 000000000..33e68faeb --- /dev/null +++ b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/LocaleSettingDelegateAndroid.kt @@ -0,0 +1,19 @@ +package world.respect.shared.domain.applanguage + +import androidx.appcompat.app.AppCompatDelegate +import androidx.core.os.LocaleListCompat + +class LocaleSettingDelegateAndroid: SupportedLanguagesConfig.LocaleSettingDelegate { + + override var localeSetting: String? + get() = AppCompatDelegate.getApplicationLocales().get(0)?.language ?: "en" + set(value) { + val localeList = if(value == RespectMobileSystemCommon.LOCALE_USE_SYSTEM) { + LocaleListCompat.getAdjustedDefault() + }else { + LocaleListCompat.forLanguageTags(value) + } + + AppCompatDelegate.setApplicationLocales(localeList) + } +} \ No newline at end of file diff --git a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/ProvideAppLocale.kt b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/ProvideAppLocale.kt new file mode 100644 index 000000000..488f45552 --- /dev/null +++ b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/ProvideAppLocale.kt @@ -0,0 +1,37 @@ +package world.respect.shared.domain.applanguage + +import android.content.res.Configuration +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import java.util.Locale + +@Composable +fun ProvideAppLocale( + languageCode: String, + content: @Composable () -> Unit +) { + val baseContext = LocalContext.current + + val localizedContext = remember(languageCode) { + val locale = if (languageCode.isEmpty()) { + Locale.getDefault() + } else { + Locale.forLanguageTag(languageCode) + } + + Locale.setDefault(locale) + + val config = Configuration(baseContext.resources.configuration) + config.setLocale(locale) + + baseContext.createConfigurationContext(config) + } + + CompositionLocalProvider( + LocalContext provides localizedContext + ) { + content() + } +} diff --git a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.kt b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.kt new file mode 100644 index 000000000..3ee668354 --- /dev/null +++ b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.kt @@ -0,0 +1,51 @@ +package world.respect.shared.domain.applanguage + +import android.content.Context +import com.russhwolf.settings.Settings +import org.jetbrains.compose.resources.StringResource +import java.util.Locale +import java.util.Properties + + +actual class RespectMobileSystemImpl( + private val context: Context, + settings: Settings, + langConfig: SupportedLanguagesConfig +) : RespectMobileSystemCommon(settings, langConfig) { + + actual override fun setSystemLocale(langCode: String) { + // NO-OP on Android + // Locale is handled via AppCompatDelegate + Compose + } + + actual override fun getString(stringResource: StringResource): String { + val resName = stringResource.key + .substringAfterLast(':') + .substringAfterLast('/') + + val id = context.resources.getIdentifier( + resName, + "string", + context.packageName + ) + + return if (id != 0) context.getString(id) else resName + } + + actual override fun formatString( + stringResource: StringResource, + vararg args: Any + ): String { + val resName = stringResource.key + .substringAfterLast(':') + .substringAfterLast('/') + + val id = context.resources.getIdentifier( + resName, + "string", + context.packageName + ) + + return if (id != 0) context.getString(id, *args) else resName + } +} diff --git a/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml b/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml index b8b444089..243d49abf 100644 --- a/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml +++ b/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml @@ -2,6 +2,7 @@ अनुप्रयोग + भाषा आपने स्कूल को सफलतापूर्वक रजिस्टर कर लिया है आपने अभी तक अपने स्कूल के लिए कोई ऐप नहीं जोड़ा है। diff --git a/respect-lib-shared/src/commonMain/composeResources/values/strings.xml b/respect-lib-shared/src/commonMain/composeResources/values/strings.xml index 52b9cb015..b72fe56dc 100644 --- a/respect-lib-shared/src/commonMain/composeResources/values/strings.xml +++ b/respect-lib-shared/src/commonMain/composeResources/values/strings.xml @@ -426,6 +426,15 @@ Select person Edit person + Monday + Tuesday + Wednesday + Thursday + Friday + Saturday + Sunday + Use system default (English) + First names Mappings Mapping diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt new file mode 100755 index 000000000..2e73c5ad4 --- /dev/null +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt @@ -0,0 +1,29 @@ +package world.respect.shared.domain.applanguage + +import org.jetbrains.compose.resources.StringResource +import com.russhwolf.settings.Settings + +abstract class RespectMobileSystemCommon( + protected val settings: Settings, + protected val langConfig: SupportedLanguagesConfig, +) { + + data class UiLanguage( + val langCode: String, + val langDisplay: String + ) + + // Domain-level hook only (no UI side effects) + abstract fun setSystemLocale(langCode: String) + + abstract fun getString(stringResource: StringResource): String + + abstract fun formatString( + stringResource: StringResource, + vararg args: Any + ): String + + companion object { + const val LOCALE_USE_SYSTEM = "" + } +} diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt index 913b2a7cb..93bcf3ec9 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt @@ -1,38 +1,47 @@ package world.respect.shared.viewmodel.settings import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import kotlinx.serialization.json.Json +import org.koin.core.component.KoinScopeComponent +import org.koin.core.scope.Scope +import world.respect.shared.domain.applanguage.SupportedLanguagesConfig +import world.respect.shared.domain.applanguage.RespectMobileSystemCommon +import world.respect.shared.domain.account.RespectAccountManager +import world.respect.shared.domain.applanguage.SetLanguageUseCase import world.respect.shared.generated.resources.Res import world.respect.shared.generated.resources.settings import world.respect.shared.navigation.CurriculumMappingList import world.respect.shared.navigation.NavCommand -import world.respect.shared.navigation.Settings import world.respect.shared.util.ext.asUiText import world.respect.shared.viewmodel.RespectViewModel -data class UiLanguage(val langCode: String, val langDisplay: String) data class SettingsUiState( val loading: Boolean = false, val langDialogVisible: Boolean = false, val currentLanguage: String = "", - val availableLanguages: List = emptyList() + val availableLanguages: List = emptyList(), + val waitForRestartDialogVisible: Boolean = false, -) + ) class SettingsViewModel( savedStateHandle: SavedStateHandle, - private val json: Json, -) : RespectViewModel(savedStateHandle) { + accountManager: RespectAccountManager, + supportedLangConfig: SupportedLanguagesConfig, + systemImpl: RespectMobileSystemCommon, + private val setLanguageUseCase: SetLanguageUseCase, + ) : RespectViewModel(savedStateHandle), KoinScopeComponent { + + override val scope: Scope = accountManager.requireActiveAccountScope() private val _uiState = MutableStateFlow(SettingsUiState()) val uiState = _uiState.asStateFlow() + private val availableLangs = supportedLangConfig.supportedUiLanguagesAndSysDefault(systemImpl) + + init { _appUiState.update { prev -> @@ -44,23 +53,25 @@ class SettingsViewModel( hideBottomNavigation = true, ) } - _uiState.update { - it.copy( - availableLanguages = listOf( - UiLanguage("en", "English"), - UiLanguage("hi", "Hindi"), - UiLanguage("fr", "French") - ), - currentLanguage = "English" - ) - } - } - fun onNavigateToLanguage() { - // TODO + val savedLangCode = supportedLangConfig.localeSetting ?: RespectMobileSystemCommon.LOCALE_USE_SYSTEM + + val currentLang = if (savedLangCode == RespectMobileSystemCommon.LOCALE_USE_SYSTEM) { + val resolvedLocale = supportedLangConfig.selectFirstSupportedLocale().langCode + availableLangs.firstOrNull { it.langCode == resolvedLocale } + } else { + availableLangs.firstOrNull { it.langCode == savedLangCode } + } ?: availableLangs.first() + + _uiState.update { it.copy( + availableLanguages = availableLangs, + currentLanguage = currentLang.langDisplay + )} + } + fun onClickLanguage() { _uiState.update { prev -> prev.copy( @@ -75,18 +86,30 @@ class SettingsViewModel( } } - fun onClickLang(lang: UiLanguage) { - + fun onClickLang(lang: RespectMobileSystemCommon.UiLanguage) { _uiState.update { prev -> prev.copy(langDialogVisible = false) } - _uiState.update { prev -> - prev.copy( - currentLanguage = lang.langDisplay - ) - } + val result = setLanguageUseCase( + uiLang = lang, + ) + println("Result language $result $lang") + if(result.waitForRestart) { + _uiState.update { prev -> + prev.copy( + waitForRestartDialogVisible = true, + currentLanguage = lang.langDisplay + ) + } + }else { + _uiState.update { prev -> + prev.copy( + currentLanguage = lang.langDisplay + ) + } + } } @@ -95,4 +118,5 @@ class SettingsViewModel( NavCommand.Navigate(CurriculumMappingList) ) } + } From 1d7b237270a262b6848b502d6ecc8e1c8ad9aa47 Mon Sep 17 00:00:00 2001 From: mandviverma Date: Tue, 10 Feb 2026 22:55:58 +0530 Subject: [PATCH 04/30] This commit refactors the application's language selection functionality to use Android's built-in per-app language support. --- .../kotlin/world/respect/AppKoinModule.kt | 5 +- .../src/androidMain/res/resources.properties | 1 + .../app/view/settings/SettingsScreen.kt | 1 - .../LocaleSettingDelegateAndroid.kt | 1 - .../domain/applanguage/ProvideAppLocale.kt | 37 ----- .../applanguage/RespectMobileSystemImpl.kt | 2 - .../applanguage/SetLanguageUseCaseAndroid.kt | 17 ++ .../shared/domain/applanguage/DayOfWeekExt.kt | 25 +++ .../applanguage/RespectMobileConstants.kt | 100 ++++++++++++ .../applanguage/RespectMobileSystemCommon.kt | 18 ++- .../applanguage/RespectMobileSystemImpl.kt | 19 +++ .../domain/applanguage/SetLanguageUseCase.kt | 13 ++ .../applanguage/SupportedLanguagesConfig.kt | 148 ++++++++++++++++++ .../viewmodel/settings/SettingsViewModel.kt | 16 +- .../RespectMobileSystemImpl.jvm.kt | 23 +++ 15 files changed, 370 insertions(+), 56 deletions(-) create mode 100644 respect-app-compose/src/androidMain/res/resources.properties delete mode 100644 respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/ProvideAppLocale.kt create mode 100644 respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCaseAndroid.kt create mode 100644 respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/DayOfWeekExt.kt create mode 100755 respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileConstants.kt create mode 100644 respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.kt create mode 100644 respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCase.kt create mode 100644 respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt create mode 100644 respect-lib-shared/src/jvmMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.jvm.kt diff --git a/respect-app-compose/src/androidMain/kotlin/world/respect/AppKoinModule.kt b/respect-app-compose/src/androidMain/kotlin/world/respect/AppKoinModule.kt index fcd9a0943..7ec0eff97 100644 --- a/respect-app-compose/src/androidMain/kotlin/world/respect/AppKoinModule.kt +++ b/respect-app-compose/src/androidMain/kotlin/world/respect/AppKoinModule.kt @@ -292,12 +292,9 @@ val appKoinModule = module { single { SupportedLanguagesConfig( systemLocales = get(), - settings = get() + localeSettingDelegate = LocaleSettingDelegateAndroid() ) } - single { - LocaleSettingDelegateAndroid() - } single { RespectMobileSystemImpl( context = androidContext(), diff --git a/respect-app-compose/src/androidMain/res/resources.properties b/respect-app-compose/src/androidMain/res/resources.properties new file mode 100644 index 000000000..d5a3ddc92 --- /dev/null +++ b/respect-app-compose/src/androidMain/res/resources.properties @@ -0,0 +1 @@ +unqualifiedResLocale=en-US \ No newline at end of file diff --git a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt index f85adef49..8133e01b3 100644 --- a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt +++ b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt @@ -57,7 +57,6 @@ fun SettingsScreen( ) { if(uiState.langDialogVisible) { - //As per https://developer.android.com/jetpack/compose/components/dialog SettingsDialog( onDismissRequest = onDismissLangDialog, ) { diff --git a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/LocaleSettingDelegateAndroid.kt b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/LocaleSettingDelegateAndroid.kt index 33e68faeb..a849d4079 100644 --- a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/LocaleSettingDelegateAndroid.kt +++ b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/LocaleSettingDelegateAndroid.kt @@ -13,7 +13,6 @@ class LocaleSettingDelegateAndroid: SupportedLanguagesConfig.LocaleSettingDelega }else { LocaleListCompat.forLanguageTags(value) } - AppCompatDelegate.setApplicationLocales(localeList) } } \ No newline at end of file diff --git a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/ProvideAppLocale.kt b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/ProvideAppLocale.kt deleted file mode 100644 index 488f45552..000000000 --- a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/ProvideAppLocale.kt +++ /dev/null @@ -1,37 +0,0 @@ -package world.respect.shared.domain.applanguage - -import android.content.res.Configuration -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalContext -import java.util.Locale - -@Composable -fun ProvideAppLocale( - languageCode: String, - content: @Composable () -> Unit -) { - val baseContext = LocalContext.current - - val localizedContext = remember(languageCode) { - val locale = if (languageCode.isEmpty()) { - Locale.getDefault() - } else { - Locale.forLanguageTag(languageCode) - } - - Locale.setDefault(locale) - - val config = Configuration(baseContext.resources.configuration) - config.setLocale(locale) - - baseContext.createConfigurationContext(config) - } - - CompositionLocalProvider( - LocalContext provides localizedContext - ) { - content() - } -} diff --git a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.kt b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.kt index 3ee668354..84981a95f 100644 --- a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.kt +++ b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.kt @@ -3,8 +3,6 @@ package world.respect.shared.domain.applanguage import android.content.Context import com.russhwolf.settings.Settings import org.jetbrains.compose.resources.StringResource -import java.util.Locale -import java.util.Properties actual class RespectMobileSystemImpl( diff --git a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCaseAndroid.kt b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCaseAndroid.kt new file mode 100644 index 000000000..34406a5ef --- /dev/null +++ b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCaseAndroid.kt @@ -0,0 +1,17 @@ +package world.respect.shared.domain.applanguage + +class SetLanguageUseCaseAndroid( + private val languagesConfig: SupportedLanguagesConfig, +): SetLanguageUseCase { + + override fun invoke( + uiLang: RespectMobileSystemCommon.UiLanguage, + ): SetLanguageUseCase.SetLangResult { + //languagesConfig uses a delegate on Android that will use Android's per-app locale setting + languagesConfig.localeSetting = uiLang.langCode + + return SetLanguageUseCase.SetLangResult( + waitForRestart = true + ) + } +} \ No newline at end of file diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/DayOfWeekExt.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/DayOfWeekExt.kt new file mode 100644 index 000000000..2bacae35a --- /dev/null +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/DayOfWeekExt.kt @@ -0,0 +1,25 @@ +package world.respect.shared.domain.applanguage + +import org.jetbrains.compose.resources.StringResource +import kotlinx.datetime.DayOfWeek +import world.respect.shared.generated.resources.Res +import world.respect.shared.generated.resources.friday +import world.respect.shared.generated.resources.monday +import world.respect.shared.generated.resources.saturday +import world.respect.shared.generated.resources.sunday +import world.respect.shared.generated.resources.thursday +import world.respect.shared.generated.resources.tuesday +import world.respect.shared.generated.resources.wednesday + + +val DayOfWeek.dayStringResource: StringResource + get() = when(this) { + DayOfWeek.MONDAY -> Res.string.monday + DayOfWeek.TUESDAY -> Res.string.tuesday + DayOfWeek.WEDNESDAY -> Res.string.wednesday + DayOfWeek.THURSDAY -> Res.string.thursday + DayOfWeek.FRIDAY -> Res.string.friday + DayOfWeek.SATURDAY -> Res.string.saturday + DayOfWeek.SUNDAY -> Res.string.sunday + else -> Res.string.monday + } diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileConstants.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileConstants.kt new file mode 100755 index 000000000..8e8eb9cb0 --- /dev/null +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileConstants.kt @@ -0,0 +1,100 @@ +/* + This file is part of Ustad Mobile. + + Ustad Mobile Copyright (C) 2011-2014 UstadMobile Inc. + + Ustad Mobile is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version with the following additional terms: + + All names, links, and logos of Ustad Mobile and Toughra Technologies FZ + LLC must be kept as they are in the original distribution. If any new + screens are added you must include the Ustad Mobile logo as it has been + used in the original distribution. You may not create any new + functionality whose purpose is to diminish or remove the Ustad Mobile + Logo. You must leave the Ustad Mobile logo as the logo for the + application to be used with any launcher (e.g. the mobile app launcher). + + If you want a commercial license to remove the above restriction you must + contact us. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Ustad Mobile is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + */ +package com.ustadmobile.core.impl + + +/** + * + * @author mike + */ +object RespectMobileConstants { + + /** + * Hardcode UTF8 Encoding String - value "UTF-8" + */ + const val UTF8 = "UTF-8" + + /** + * Index of the locale code in the string array - e.g. + * SUPPORTED_LOCALES[i][LOCALE_CODE] = "en" + */ + const val LOCALE_CODE = 0 + + /** + * String constant for Coordinated Universal Time + */ + const val UTC = "UTC" + + /** + * Map of all supported UI language to the language name (in its respective language) + */ + val LANGUAGE_NAMES = mapOf( + "en" to "English", + "tg" to "Тоҷикӣ", + "ps" to "\u067e\u069a\u062a\u0648", + "fa" to "\u062f\u0631\u06cc", + "ar" to "العربية", + "bn" to "বাংলা", + "ne" to "नेपाली", + "my" to "မြန်မာစာ", + "ru" to "Русский", + "rw" to "Kinyarwanda", + "hi" to "हिन्दी", + ) + + /** + * A list known RTL languages + */ + val RTL_LANGUAGES = + arrayOf("ar", "arc", "dv", "fa", "ha", "he", "khw", "ks", "ku", "ps", "ur", "yi") + + /** + * Anyone under this age is considered a minor. They can only register with the consent of a + * parent/guardian. + */ + val MINOR_AGE_THRESHOLD = 13 + + /** + * Anyone over this age is considered an adult. They can approve accounts for minors. + */ + val ADULT_AGE_THRESHOLD = 18 + + /** + * Default number of iterations for pbkdf2 hashing + */ + const val PBKDF2_ITERATIONS = 10000 + + /** + * Default key length for PBKDF2 hashing + */ + const val PBKDF2_KEYLENGTH = 512 + +} diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt index 2e73c5ad4..d5ac68e15 100755 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt @@ -3,17 +3,15 @@ package world.respect.shared.domain.applanguage import org.jetbrains.compose.resources.StringResource import com.russhwolf.settings.Settings + abstract class RespectMobileSystemCommon( - protected val settings: Settings, + private val settings: Settings, protected val langConfig: SupportedLanguagesConfig, ) { - data class UiLanguage( - val langCode: String, - val langDisplay: String - ) + data class UiLanguage(val langCode: String, val langDisplay: String) + - // Domain-level hook only (no UI side effects) abstract fun setSystemLocale(langCode: String) abstract fun getString(stringResource: StringResource): String @@ -23,7 +21,13 @@ abstract class RespectMobileSystemCommon( vararg args: Any ): String + companion object { + + /** + * The return value from getLocale when the user has said to use the system's locale + */ const val LOCALE_USE_SYSTEM = "" + } -} +} \ No newline at end of file diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.kt new file mode 100644 index 000000000..415fe434c --- /dev/null +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.kt @@ -0,0 +1,19 @@ +package world.respect.shared.domain.applanguage + +import org.jetbrains.compose.resources.StringResource + +expect class RespectMobileSystemImpl : RespectMobileSystemCommon { + + override fun getString( + stringResource: StringResource + ): String + + override fun formatString( + stringResource: StringResource, + vararg args: Any + ): String + + override fun setSystemLocale( + langCode: String + ) +} diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCase.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCase.kt new file mode 100644 index 000000000..cc37c96b0 --- /dev/null +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCase.kt @@ -0,0 +1,13 @@ +package world.respect.shared.domain.applanguage + +interface SetLanguageUseCase { + + data class SetLangResult( + val waitForRestart: Boolean + ) + + operator fun invoke( + uiLang: RespectMobileSystemCommon.UiLanguage + ): SetLangResult + +} diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt new file mode 100644 index 000000000..3fba6fe11 --- /dev/null +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt @@ -0,0 +1,148 @@ +package world.respect.shared.domain.applanguage + +import world.respect.shared.generated.resources.Res +import world.respect.shared.generated.resources.default_language +import com.russhwolf.settings.Settings +import com.ustadmobile.core.impl.RespectMobileConstants +import world.respect.shared.generated.resources.language + +import kotlin.concurrent.Volatile + +/** + * Manages language configuration + * + * @param availableLanguagesConfig comma separated list of languages supported by the app. + * Specify as follows: + * Android: Set via meta-data supported-languages + * Web: ustad-config.json (in resources) in the property com.ustadmobile.uilanguages + * + * @param localeSettingDelegate Used to store the user selected language. On JVM and Web, this is a + * simple delegate to the settings. On Android this needs to delegate to the platform specific + * code, because the user can also change the setting in app settings. + * + * @param systemLocales the system locales in order of user preference. On Android this comes from + * getConfiguration().getLocales(). On JVM this is a single item list, on the web this comes + * from navigator.languages . + * + * @param fallbackLocaleCode the fallback locale code that will be used if the user does not + * explicitly set a language and none of their preferred longuages are supported + */ +class SupportedLanguagesConfig ( + val systemLocales: List, + private val localeSettingDelegate: LocaleSettingDelegate, + private val availableLanguagesConfig: String = DEFAULT_SUPPORTED_LANGUAGES, + private val fallbackLocaleCode: String = "en", +) { + + constructor( + systemLocales: List, + settings: Settings, + availableLanguagesConfig: String = DEFAULT_SUPPORTED_LANGUAGES, + fallbackLocaleCode: String = "en", + ): this( + systemLocales = systemLocales, + localeSettingDelegate = SettingsLocaleSettingDelegate(settings), + availableLanguagesConfig = availableLanguagesConfig, + fallbackLocaleCode = fallbackLocaleCode, + ) + interface LocaleSettingDelegate { + + var localeSetting: String? + + } + + class SettingsLocaleSettingDelegate( + private val settings: Settings + ) : LocaleSettingDelegate { + override var localeSetting: String? + get() = settings.getStringOrNull(PREFKEY_LOCALE) + set(value) { + println("Setting locale $value") + if(value != null) { + settings.putString(PREFKEY_LOCALE, value) + }else { + settings.remove(PREFKEY_LOCALE) + } + } + } + + + val supportedUiLanguages: List = availableLanguagesConfig + .split(",") + .sorted() + .map { + RespectMobileSystemCommon.UiLanguage(it, (RespectMobileConstants.LANGUAGE_NAMES[it] ?: it)) + } + + private val supportedLangMap: Map = supportedUiLanguages + .associateBy { it.langCode } + + /** + * The user selected locale within the app (if any). This should be the language code as per + * the UiLanguage object, or null to indicate that the user has not explicitly selected any + * language + */ + var localeSetting: String? + get() = localeSettingDelegate.localeSetting + set(value) { + localeSettingDelegate.localeSetting = value + displayedLocale = displayLocaleForLangSetting(value) + } + + + /** + * This is stored because it will be looked up every time a string lookup is done via systemImpl + */ + @Volatile + var displayedLocale: String = displayLocaleForLangSetting(localeSetting) + private set + + init { + if(!supportedLangMap.containsKey(fallbackLocaleCode)) + throw IllegalStateException("available languages $availableLanguagesConfig does not " + + "include fallback: '$fallbackLocaleCode'") + } + + private fun displayLocaleForLangSetting(setting: String?): String { + return if(setting.isNullOrEmpty()) + selectFirstSupportedLocale().langCode + else + setting + } + + fun supportedUiLanguagesAndSysDefault( + useDeviceLangDisplay: String, + ) : List{ + return listOf( + RespectMobileSystemCommon.UiLanguage(RespectMobileSystemCommon.LOCALE_USE_SYSTEM, + useDeviceLangDisplay) + ) + supportedUiLanguages + } + + /** + * Select the locale to display based on a list of the locales preferred by the user. On Android + * and other systems, the user can specify a list of their preferred languages in order. We will + * select the first language in that list that is in the supported. If none is available, then we + * will use the fallback. + */ + fun selectFirstSupportedLocale( + preferredLocales: List = systemLocales, + ): RespectMobileSystemCommon.UiLanguage { + val supportedLocaleCodes = supportedUiLanguages.map { + it.langCode + } + + return preferredLocales.firstOrNull { + it.substring(0, 2) in supportedLocaleCodes + }?.let { supportedLangMap[it.substring(0, 2)]!! } ?: supportedLangMap[fallbackLocaleCode]!! + } + + companion object { + + const val PREFKEY_LOCALE = "locale" + + const val DEFAULT_SUPPORTED_LANGUAGES = "en,hi,fa,ps,ar,tg,bn,ne,my,rw,ru" + + } + +} diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt index 93bcf3ec9..aa4b24dcf 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt @@ -1,9 +1,13 @@ package world.respect.shared.viewmodel.settings import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import org.jetbrains.compose.resources.getString +import org.jetbrains.compose.resources.stringResource import org.koin.core.component.KoinScopeComponent import org.koin.core.scope.Scope import world.respect.shared.domain.applanguage.SupportedLanguagesConfig @@ -11,6 +15,7 @@ import world.respect.shared.domain.applanguage.RespectMobileSystemCommon import world.respect.shared.domain.account.RespectAccountManager import world.respect.shared.domain.applanguage.SetLanguageUseCase import world.respect.shared.generated.resources.Res +import world.respect.shared.generated.resources.default_language import world.respect.shared.generated.resources.settings import world.respect.shared.navigation.CurriculumMappingList import world.respect.shared.navigation.NavCommand @@ -35,13 +40,12 @@ class SettingsViewModel( private val setLanguageUseCase: SetLanguageUseCase, ) : RespectViewModel(savedStateHandle), KoinScopeComponent { + private var availableLangs: List = emptyList() + override val scope: Scope = accountManager.requireActiveAccountScope() private val _uiState = MutableStateFlow(SettingsUiState()) val uiState = _uiState.asStateFlow() - private val availableLangs = supportedLangConfig.supportedUiLanguagesAndSysDefault(systemImpl) - - init { _appUiState.update { prev -> @@ -54,7 +58,8 @@ class SettingsViewModel( ) } - + viewModelScope.launch { + availableLangs = supportedLangConfig.supportedUiLanguagesAndSysDefault(getString(Res.string.default_language)) val savedLangCode = supportedLangConfig.localeSetting ?: RespectMobileSystemCommon.LOCALE_USE_SYSTEM val currentLang = if (savedLangCode == RespectMobileSystemCommon.LOCALE_USE_SYSTEM) { @@ -68,6 +73,9 @@ class SettingsViewModel( availableLanguages = availableLangs, currentLanguage = currentLang.langDisplay )} + } + + } diff --git a/respect-lib-shared/src/jvmMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.jvm.kt b/respect-lib-shared/src/jvmMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.jvm.kt new file mode 100644 index 000000000..cb15213fe --- /dev/null +++ b/respect-lib-shared/src/jvmMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.jvm.kt @@ -0,0 +1,23 @@ +package world.respect.shared.domain.applanguage + +import com.russhwolf.settings.Settings +import org.jetbrains.compose.resources.StringResource + +actual class RespectMobileSystemImpl( + settings: Settings, + langConfig: SupportedLanguagesConfig +) : RespectMobileSystemCommon(settings, langConfig) { + actual override fun getString(stringResource: StringResource): String { + TODO("Not yet implemented") + } + + actual override fun formatString( + stringResource: StringResource, + vararg args: Any + ): String { + TODO("Not yet implemented") + } + + actual override fun setSystemLocale(langCode: String) { + } +} \ No newline at end of file From 39eee9fbcc17474bac70755b28e0188e8540791c Mon Sep 17 00:00:00 2001 From: mandviverma Date: Tue, 10 Feb 2026 23:01:39 +0530 Subject: [PATCH 05/30] This commit removes an unused `systemImpl` parameter and addresses various code formatting inconsistencies within the `SettingsViewModel`, including spacing and line breaks. --- .../viewmodel/settings/SettingsViewModel.kt | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt index aa4b24dcf..78358c8dc 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt @@ -36,11 +36,10 @@ class SettingsViewModel( savedStateHandle: SavedStateHandle, accountManager: RespectAccountManager, supportedLangConfig: SupportedLanguagesConfig, - systemImpl: RespectMobileSystemCommon, private val setLanguageUseCase: SetLanguageUseCase, - ) : RespectViewModel(savedStateHandle), KoinScopeComponent { +) : RespectViewModel(savedStateHandle), KoinScopeComponent { - private var availableLangs: List = emptyList() + private var availableLangs: List = emptyList() override val scope: Scope = accountManager.requireActiveAccountScope() @@ -59,8 +58,10 @@ class SettingsViewModel( } viewModelScope.launch { - availableLangs = supportedLangConfig.supportedUiLanguagesAndSysDefault(getString(Res.string.default_language)) - val savedLangCode = supportedLangConfig.localeSetting ?: RespectMobileSystemCommon.LOCALE_USE_SYSTEM + availableLangs = + supportedLangConfig.supportedUiLanguagesAndSysDefault(getString(Res.string.default_language)) + val savedLangCode = + supportedLangConfig.localeSetting ?: RespectMobileSystemCommon.LOCALE_USE_SYSTEM val currentLang = if (savedLangCode == RespectMobileSystemCommon.LOCALE_USE_SYSTEM) { val resolvedLocale = supportedLangConfig.selectFirstSupportedLocale().langCode @@ -69,17 +70,15 @@ class SettingsViewModel( availableLangs.firstOrNull { it.langCode == savedLangCode } } ?: availableLangs.first() - _uiState.update { it.copy( - availableLanguages = availableLangs, - currentLanguage = currentLang.langDisplay - )} + _uiState.update { + it.copy( + availableLanguages = availableLangs, + currentLanguage = currentLang.langDisplay + ) + } } - - - } - fun onClickLanguage() { _uiState.update { prev -> prev.copy( @@ -104,14 +103,14 @@ class SettingsViewModel( ) println("Result language $result $lang") - if(result.waitForRestart) { + if (result.waitForRestart) { _uiState.update { prev -> prev.copy( waitForRestartDialogVisible = true, currentLanguage = lang.langDisplay ) } - }else { + } else { _uiState.update { prev -> prev.copy( currentLanguage = lang.langDisplay From 680b332c358cd7cc23147cec0a339233d8940548 Mon Sep 17 00:00:00 2001 From: mandviverma Date: Wed, 11 Feb 2026 11:28:54 +0530 Subject: [PATCH 06/30] Simplify language and system resource management --- .../kotlin/world/respect/AppKoinModule.kt | 9 +- .../applanguage/RespectMobileSystemImpl.kt | 49 ----------- .../applanguage/RespectMobileConstants.kt | 83 +------------------ .../applanguage/RespectMobileSystemCommon.kt | 13 --- .../applanguage/RespectMobileSystemImpl.kt | 19 ----- .../applanguage/SupportedLanguagesConfig.kt | 23 ++--- .../viewmodel/settings/SettingsViewModel.kt | 5 +- .../RespectMobileSystemImpl.jvm.kt | 28 +++---- 8 files changed, 27 insertions(+), 202 deletions(-) delete mode 100644 respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.kt delete mode 100644 respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.kt diff --git a/respect-app-compose/src/androidMain/kotlin/world/respect/AppKoinModule.kt b/respect-app-compose/src/androidMain/kotlin/world/respect/AppKoinModule.kt index 7ec0eff97..81e8bae33 100644 --- a/respect-app-compose/src/androidMain/kotlin/world/respect/AppKoinModule.kt +++ b/respect-app-compose/src/androidMain/kotlin/world/respect/AppKoinModule.kt @@ -134,7 +134,6 @@ import world.respect.shared.domain.account.validateqrbadge.ValidateQrCodeUseCase import world.respect.shared.domain.applanguage.LocaleSettingDelegateAndroid import world.respect.shared.domain.applanguage.SetLanguageUseCase import world.respect.shared.domain.applanguage.SetLanguageUseCaseAndroid -import world.respect.shared.domain.applanguage.RespectMobileSystemImpl import world.respect.shared.domain.appversioninfo.GetAppVersionInfoUseCase import world.respect.shared.domain.appversioninfo.GetAppVersionInfoUseCaseAndroid import world.respect.shared.domain.clipboard.SetClipboardStringUseCase @@ -295,13 +294,7 @@ val appKoinModule = module { localeSettingDelegate = LocaleSettingDelegateAndroid() ) } - single { - RespectMobileSystemImpl( - context = androidContext(), - settings = get(), - langConfig = get() - ) - } + single { SetLanguageUseCaseAndroid( languagesConfig = get() diff --git a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.kt b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.kt deleted file mode 100644 index 84981a95f..000000000 --- a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.kt +++ /dev/null @@ -1,49 +0,0 @@ -package world.respect.shared.domain.applanguage - -import android.content.Context -import com.russhwolf.settings.Settings -import org.jetbrains.compose.resources.StringResource - - -actual class RespectMobileSystemImpl( - private val context: Context, - settings: Settings, - langConfig: SupportedLanguagesConfig -) : RespectMobileSystemCommon(settings, langConfig) { - - actual override fun setSystemLocale(langCode: String) { - // NO-OP on Android - // Locale is handled via AppCompatDelegate + Compose - } - - actual override fun getString(stringResource: StringResource): String { - val resName = stringResource.key - .substringAfterLast(':') - .substringAfterLast('/') - - val id = context.resources.getIdentifier( - resName, - "string", - context.packageName - ) - - return if (id != 0) context.getString(id) else resName - } - - actual override fun formatString( - stringResource: StringResource, - vararg args: Any - ): String { - val resName = stringResource.key - .substringAfterLast(':') - .substringAfterLast('/') - - val id = context.resources.getIdentifier( - resName, - "string", - context.packageName - ) - - return if (id != 0) context.getString(id, *args) else resName - } -} diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileConstants.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileConstants.kt index 8e8eb9cb0..442721fe0 100755 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileConstants.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileConstants.kt @@ -1,61 +1,8 @@ -/* - This file is part of Ustad Mobile. - Ustad Mobile Copyright (C) 2011-2014 UstadMobile Inc. +package world.respect.shared.domain.applanguage - Ustad Mobile is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version with the following additional terms: - - All names, links, and logos of Ustad Mobile and Toughra Technologies FZ - LLC must be kept as they are in the original distribution. If any new - screens are added you must include the Ustad Mobile logo as it has been - used in the original distribution. You may not create any new - functionality whose purpose is to diminish or remove the Ustad Mobile - Logo. You must leave the Ustad Mobile logo as the logo for the - application to be used with any launcher (e.g. the mobile app launcher). - - If you want a commercial license to remove the above restriction you must - contact us. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - - Ustad Mobile is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - */ -package com.ustadmobile.core.impl - - -/** - * - * @author mike - */ object RespectMobileConstants { - /** - * Hardcode UTF8 Encoding String - value "UTF-8" - */ - const val UTF8 = "UTF-8" - - /** - * Index of the locale code in the string array - e.g. - * SUPPORTED_LOCALES[i][LOCALE_CODE] = "en" - */ - const val LOCALE_CODE = 0 - - /** - * String constant for Coordinated Universal Time - */ - const val UTC = "UTC" - - /** - * Map of all supported UI language to the language name (in its respective language) - */ val LANGUAGE_NAMES = mapOf( "en" to "English", "tg" to "Тоҷикӣ", @@ -69,32 +16,4 @@ object RespectMobileConstants { "rw" to "Kinyarwanda", "hi" to "हिन्दी", ) - - /** - * A list known RTL languages - */ - val RTL_LANGUAGES = - arrayOf("ar", "arc", "dv", "fa", "ha", "he", "khw", "ks", "ku", "ps", "ur", "yi") - - /** - * Anyone under this age is considered a minor. They can only register with the consent of a - * parent/guardian. - */ - val MINOR_AGE_THRESHOLD = 13 - - /** - * Anyone over this age is considered an adult. They can approve accounts for minors. - */ - val ADULT_AGE_THRESHOLD = 18 - - /** - * Default number of iterations for pbkdf2 hashing - */ - const val PBKDF2_ITERATIONS = 10000 - - /** - * Default key length for PBKDF2 hashing - */ - const val PBKDF2_KEYLENGTH = 512 - } diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt index d5ac68e15..40b50bada 100755 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt @@ -12,21 +12,8 @@ abstract class RespectMobileSystemCommon( data class UiLanguage(val langCode: String, val langDisplay: String) - abstract fun setSystemLocale(langCode: String) - - abstract fun getString(stringResource: StringResource): String - - abstract fun formatString( - stringResource: StringResource, - vararg args: Any - ): String - companion object { - - /** - * The return value from getLocale when the user has said to use the system's locale - */ const val LOCALE_USE_SYSTEM = "" } diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.kt deleted file mode 100644 index 415fe434c..000000000 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.kt +++ /dev/null @@ -1,19 +0,0 @@ -package world.respect.shared.domain.applanguage - -import org.jetbrains.compose.resources.StringResource - -expect class RespectMobileSystemImpl : RespectMobileSystemCommon { - - override fun getString( - stringResource: StringResource - ): String - - override fun formatString( - stringResource: StringResource, - vararg args: Any - ): String - - override fun setSystemLocale( - langCode: String - ) -} diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt index 3fba6fe11..05f20731e 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt @@ -1,10 +1,6 @@ package world.respect.shared.domain.applanguage -import world.respect.shared.generated.resources.Res -import world.respect.shared.generated.resources.default_language import com.russhwolf.settings.Settings -import com.ustadmobile.core.impl.RespectMobileConstants -import world.respect.shared.generated.resources.language import kotlin.concurrent.Volatile @@ -12,10 +8,6 @@ import kotlin.concurrent.Volatile * Manages language configuration * * @param availableLanguagesConfig comma separated list of languages supported by the app. - * Specify as follows: - * Android: Set via meta-data supported-languages - * Web: ustad-config.json (in resources) in the property com.ustadmobile.uilanguages - * * @param localeSettingDelegate Used to store the user selected language. On JVM and Web, this is a * simple delegate to the settings. On Android this needs to delegate to the platform specific * code, because the user can also change the setting in app settings. @@ -111,12 +103,15 @@ class SupportedLanguagesConfig ( } fun supportedUiLanguagesAndSysDefault( - useDeviceLangDisplay: String, - ) : List{ - return listOf( - RespectMobileSystemCommon.UiLanguage(RespectMobileSystemCommon.LOCALE_USE_SYSTEM, - useDeviceLangDisplay) - ) + supportedUiLanguages + defaultLangDisplay: String, + ): List { + + val defaultEnglish = RespectMobileSystemCommon.UiLanguage( + langCode = "en", // IMPORTANT: set to English + langDisplay = defaultLangDisplay + ) + + return listOf(defaultEnglish) + supportedUiLanguages } /** diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt index 78358c8dc..3fcf4f1f8 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt @@ -28,9 +28,8 @@ data class SettingsUiState( val langDialogVisible: Boolean = false, val currentLanguage: String = "", val availableLanguages: List = emptyList(), - val waitForRestartDialogVisible: Boolean = false, - - ) + val waitForRestartDialogVisible: Boolean = false +) class SettingsViewModel( savedStateHandle: SavedStateHandle, diff --git a/respect-lib-shared/src/jvmMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.jvm.kt b/respect-lib-shared/src/jvmMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.jvm.kt index cb15213fe..ffb57d706 100644 --- a/respect-lib-shared/src/jvmMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.jvm.kt +++ b/respect-lib-shared/src/jvmMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.jvm.kt @@ -3,21 +3,21 @@ package world.respect.shared.domain.applanguage import com.russhwolf.settings.Settings import org.jetbrains.compose.resources.StringResource -actual class RespectMobileSystemImpl( +actual class ( settings: Settings, langConfig: SupportedLanguagesConfig ) : RespectMobileSystemCommon(settings, langConfig) { - actual override fun getString(stringResource: StringResource): String { - TODO("Not yet implemented") - } - - actual override fun formatString( - stringResource: StringResource, - vararg args: Any - ): String { - TODO("Not yet implemented") - } - - actual override fun setSystemLocale(langCode: String) { - } +// actual override fun getString(stringResource: StringResource): String { +// TODO("Not yet implemented") +// } +// +// actual override fun formatString( +// stringResource: StringResource, +// vararg args: Any +// ): String { +// TODO("Not yet implemented") +// } +// +// actual override fun setSystemLocale(langCode: String) { +// } } \ No newline at end of file From 158ef35f05349b111fbee1c19a145ba586f0dacc Mon Sep 17 00:00:00 2001 From: mandviverma Date: Wed, 11 Feb 2026 11:38:55 +0530 Subject: [PATCH 07/30] Simplify language and system resource management --- .../shared/domain/applanguage/DayOfWeekExt.kt | 25 ---------------- .../applanguage/SupportedLanguagesConfig.kt | 30 ------------------- .../RespectMobileSystemImpl.jvm.kt | 23 -------------- 3 files changed, 78 deletions(-) delete mode 100644 respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/DayOfWeekExt.kt delete mode 100644 respect-lib-shared/src/jvmMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.jvm.kt diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/DayOfWeekExt.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/DayOfWeekExt.kt deleted file mode 100644 index 2bacae35a..000000000 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/DayOfWeekExt.kt +++ /dev/null @@ -1,25 +0,0 @@ -package world.respect.shared.domain.applanguage - -import org.jetbrains.compose.resources.StringResource -import kotlinx.datetime.DayOfWeek -import world.respect.shared.generated.resources.Res -import world.respect.shared.generated.resources.friday -import world.respect.shared.generated.resources.monday -import world.respect.shared.generated.resources.saturday -import world.respect.shared.generated.resources.sunday -import world.respect.shared.generated.resources.thursday -import world.respect.shared.generated.resources.tuesday -import world.respect.shared.generated.resources.wednesday - - -val DayOfWeek.dayStringResource: StringResource - get() = when(this) { - DayOfWeek.MONDAY -> Res.string.monday - DayOfWeek.TUESDAY -> Res.string.tuesday - DayOfWeek.WEDNESDAY -> Res.string.wednesday - DayOfWeek.THURSDAY -> Res.string.thursday - DayOfWeek.FRIDAY -> Res.string.friday - DayOfWeek.SATURDAY -> Res.string.saturday - DayOfWeek.SUNDAY -> Res.string.sunday - else -> Res.string.monday - } diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt index 05f20731e..54bea567f 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt @@ -1,7 +1,5 @@ package world.respect.shared.domain.applanguage -import com.russhwolf.settings.Settings - import kotlin.concurrent.Volatile /** @@ -26,38 +24,12 @@ class SupportedLanguagesConfig ( private val fallbackLocaleCode: String = "en", ) { - constructor( - systemLocales: List, - settings: Settings, - availableLanguagesConfig: String = DEFAULT_SUPPORTED_LANGUAGES, - fallbackLocaleCode: String = "en", - ): this( - systemLocales = systemLocales, - localeSettingDelegate = SettingsLocaleSettingDelegate(settings), - availableLanguagesConfig = availableLanguagesConfig, - fallbackLocaleCode = fallbackLocaleCode, - ) interface LocaleSettingDelegate { var localeSetting: String? } - class SettingsLocaleSettingDelegate( - private val settings: Settings - ) : LocaleSettingDelegate { - override var localeSetting: String? - get() = settings.getStringOrNull(PREFKEY_LOCALE) - set(value) { - println("Setting locale $value") - if(value != null) { - settings.putString(PREFKEY_LOCALE, value) - }else { - settings.remove(PREFKEY_LOCALE) - } - } - } - val supportedUiLanguages: List = availableLanguagesConfig .split(",") @@ -134,8 +106,6 @@ class SupportedLanguagesConfig ( companion object { - const val PREFKEY_LOCALE = "locale" - const val DEFAULT_SUPPORTED_LANGUAGES = "en,hi,fa,ps,ar,tg,bn,ne,my,rw,ru" } diff --git a/respect-lib-shared/src/jvmMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.jvm.kt b/respect-lib-shared/src/jvmMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.jvm.kt deleted file mode 100644 index ffb57d706..000000000 --- a/respect-lib-shared/src/jvmMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemImpl.jvm.kt +++ /dev/null @@ -1,23 +0,0 @@ -package world.respect.shared.domain.applanguage - -import com.russhwolf.settings.Settings -import org.jetbrains.compose.resources.StringResource - -actual class ( - settings: Settings, - langConfig: SupportedLanguagesConfig -) : RespectMobileSystemCommon(settings, langConfig) { -// actual override fun getString(stringResource: StringResource): String { -// TODO("Not yet implemented") -// } -// -// actual override fun formatString( -// stringResource: StringResource, -// vararg args: Any -// ): String { -// TODO("Not yet implemented") -// } -// -// actual override fun setSystemLocale(langCode: String) { -// } -} \ No newline at end of file From 889c9c9751c14da6b330cf6512873f98fc3e4904 Mon Sep 17 00:00:00 2001 From: mandviverma Date: Wed, 11 Feb 2026 12:51:28 +0530 Subject: [PATCH 08/30] Add language selection to onboarding screen --- .../app/view/onboarding/OnboardingScreen.kt | 69 +++++++++++++++++++ .../composeResources/values-hi/strings.xml | 1 + .../onboarding/OnboardingViewModel.kt | 54 +++++++++++++++ 3 files changed, 124 insertions(+) diff --git a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/onboarding/OnboardingScreen.kt b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/onboarding/OnboardingScreen.kt index 02ae3ad8c..3742cc8b4 100644 --- a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/onboarding/OnboardingScreen.kt +++ b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/onboarding/OnboardingScreen.kt @@ -18,11 +18,19 @@ import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material3.Button import androidx.compose.material3.Checkbox +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -34,8 +42,10 @@ import world.respect.shared.viewmodel.onboarding.OnboardingUiState import world.respect.shared.viewmodel.onboarding.OnboardingViewModel import org.jetbrains.compose.resources.stringResource import world.respect.app.components.defaultItemPadding +import world.respect.shared.domain.applanguage.RespectMobileSystemCommon import world.respect.shared.generated.resources.Res import world.respect.shared.generated.resources.get_started +import world.respect.shared.generated.resources.language import world.respect.shared.generated.resources.onboardingDescription1 import world.respect.shared.generated.resources.onboardingDescription2 import world.respect.shared.generated.resources.onboardingDescription3 @@ -45,6 +55,8 @@ import world.respect.shared.generated.resources.onboardingTitle2 import world.respect.shared.generated.resources.onboardingTitle3 import world.respect.shared.generated.resources.onboardingTitle4 import world.respect.shared.generated.resources.send_usage_stats_and_crash_reports +import world.respect.shared.util.ext.isLoading +import kotlin.collections.forEach data class OnboardingItem( @@ -62,6 +74,8 @@ fun OnboardingScreen( uiState = uiState, onClickGetStartedButton = viewModel::onClickGetStartedButton, onToggleUsageStatsOptIn = viewModel::onToggleUsageStatsOptIn, + onLanguageSelected = viewModel::onLanguageSelected + ) } @@ -70,6 +84,8 @@ fun OnboardingScreen( uiState: OnboardingUiState, onClickGetStartedButton: () -> Unit, onToggleUsageStatsOptIn: () -> Unit, + onLanguageSelected: (RespectMobileSystemCommon.UiLanguage) -> Unit + ) { val onboardingItem = listOf( @@ -104,6 +120,14 @@ fun OnboardingScreen( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.SpaceBetween ) { + + LanguageDropdown( + selected = uiState.selectedLanguage, + languages = uiState.availableLanguages, + onSelected = onLanguageSelected, + enabled = !uiState.isLoading + ) + HorizontalPager( state = pagerState, modifier = Modifier @@ -205,4 +229,49 @@ fun OnboardingScreen( +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun LanguageDropdown( + selected: RespectMobileSystemCommon.UiLanguage?, + languages: List, + onSelected: (RespectMobileSystemCommon.UiLanguage) -> Unit, + enabled: Boolean +) { + var expanded by remember { mutableStateOf(false) } + + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { if (enabled) expanded = !expanded } + ) { + OutlinedTextField( + value = selected?.langDisplay ?: "", + onValueChange = {}, + readOnly = true, + label = { Text(stringResource(Res.string.language)) }, + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon(expanded) + }, + modifier = Modifier + .menuAnchor() + .fillMaxWidth() + .defaultItemPadding() + ) + + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + languages.forEach { lang -> + DropdownMenuItem( + text = { Text(lang.langDisplay) }, + onClick = { + onSelected(lang) + expanded = false + } + ) + } + } + } +} + diff --git a/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml b/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml index 243d49abf..69b974b69 100644 --- a/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml +++ b/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml @@ -5,5 +5,6 @@ भाषा आपने स्कूल को सफलतापूर्वक रजिस्टर कर लिया है आपने अभी तक अपने स्कूल के लिए कोई ऐप नहीं जोड़ा है। + लॉग इन करें \ No newline at end of file diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt index 161441dd6..7067d7601 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt @@ -8,14 +8,22 @@ import world.respect.shared.viewmodel.RespectViewModel import com.russhwolf.settings.Settings import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import org.jetbrains.compose.resources.getString +import world.respect.shared.domain.applanguage.RespectMobileSystemCommon +import world.respect.shared.domain.applanguage.SetLanguageUseCase +import world.respect.shared.domain.applanguage.SupportedLanguagesConfig import world.respect.shared.domain.navigation.onappstart.NavigateOnAppStartUseCase import world.respect.shared.domain.onboarding.ShouldShowOnboardingUseCase import world.respect.shared.domain.usagereporting.GetUsageReportingEnabledUseCase import world.respect.shared.domain.usagereporting.SetUsageReportingEnabledUseCase +import world.respect.shared.generated.resources.Res +import world.respect.shared.generated.resources.default_language data class OnboardingUiState( val isLoading: Boolean = false, val usageStatsOptInChecked: Boolean = true, + val availableLanguages: List = emptyList(), + val selectedLanguage: RespectMobileSystemCommon.UiLanguage? = null, ) class OnboardingViewModel( @@ -24,6 +32,8 @@ class OnboardingViewModel( private val setUsageReportingEnabledUseCase: SetUsageReportingEnabledUseCase, private val getUsageReportingEnabledUseCase: GetUsageReportingEnabledUseCase, private val navigateOnAppStartUseCase: NavigateOnAppStartUseCase, + private val supportedLangConfig: SupportedLanguagesConfig, + private val setLanguageUseCase: SetLanguageUseCase ) : RespectViewModel(savedStateHandle) { private val _uiState = MutableStateFlow(OnboardingUiState()) @@ -38,6 +48,34 @@ class OnboardingViewModel( ) } _uiState.update { it.copy(usageStatsOptInChecked = getUsageReportingEnabledUseCase()) } + viewModelScope.launch { + + val availableLangs = + supportedLangConfig.supportedUiLanguagesAndSysDefault( + getString(Res.string.default_language) + ) + + val savedLangCode = + supportedLangConfig.localeSetting + ?: RespectMobileSystemCommon.LOCALE_USE_SYSTEM + + val currentLang = + if (savedLangCode == RespectMobileSystemCommon.LOCALE_USE_SYSTEM) { + val resolvedLocale = + supportedLangConfig.selectFirstSupportedLocale().langCode + + availableLangs.firstOrNull { it.langCode == resolvedLocale } + } else { + availableLangs.firstOrNull { it.langCode == savedLangCode } + } ?: availableLangs.first() + + _uiState.update { + it.copy( + availableLanguages = availableLangs, + selectedLanguage = currentLang + ) + } + } } fun onToggleUsageStatsOptIn() { @@ -59,4 +97,20 @@ class OnboardingViewModel( } + fun onLanguageSelected(lang: RespectMobileSystemCommon.UiLanguage) { + + val result = setLanguageUseCase(uiLang = lang) + + if (result.waitForRestart) { + _uiState.update { + it.copy(selectedLanguage = lang) + } + } else { + _uiState.update { + it.copy(selectedLanguage = lang) + } + } + } + + } \ No newline at end of file From c53314d2a88412693adf4a928f7c8b13712e035e Mon Sep 17 00:00:00 2001 From: mandviverma Date: Wed, 11 Feb 2026 13:00:21 +0530 Subject: [PATCH 09/30] feat: Add Hindi translation for "Get Started" button --- .../src/androidMain/kotlin/world/respect/MainActivity.kt | 6 +----- .../src/commonMain/composeResources/values-hi/strings.xml | 1 + 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/respect-app-compose/src/androidMain/kotlin/world/respect/MainActivity.kt b/respect-app-compose/src/androidMain/kotlin/world/respect/MainActivity.kt index 746dfbf79..00cbec225 100644 --- a/respect-app-compose/src/androidMain/kotlin/world/respect/MainActivity.kt +++ b/respect-app-compose/src/androidMain/kotlin/world/respect/MainActivity.kt @@ -1,12 +1,8 @@ package world.respect -import android.app.LocaleManager -import android.os.Build import android.os.Bundle -import androidx.appcompat.app.AppCompatDelegate import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview -import androidx.core.os.LocaleListCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle @@ -16,6 +12,7 @@ import org.koin.android.ext.android.getKoin import org.koin.android.scope.AndroidScopeComponent import org.koin.androidx.scope.activityScope import org.koin.core.scope.Scope +import world.respect.app.app.App import world.respect.credentials.passkey.CreatePasskeyUseCaseAndroidChannelHost import world.respect.credentials.passkey.CreatePasskeyUseCaseProcessor import world.respect.credentials.passkey.GetCredentialUseCase @@ -29,7 +26,6 @@ import world.respect.datalayer.respect.model.RespectSchoolDirectory import world.respect.shared.domain.biometric.BiometricAuthProcessor import world.respect.shared.domain.biometric.BiometricAuthUseCaseAndroidImpl import world.respect.view.app.AbstractAppActivity -import kotlin.jvm.java class MainActivity : AbstractAppActivity(), AndroidScopeComponent { diff --git a/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml b/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml index 69b974b69..ce8f6b287 100644 --- a/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml +++ b/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml @@ -6,5 +6,6 @@ आपने स्कूल को सफलतापूर्वक रजिस्टर कर लिया है आपने अभी तक अपने स्कूल के लिए कोई ऐप नहीं जोड़ा है। लॉग इन करें + शुरू हो जाओ \ No newline at end of file From 808fd5095842eb55a95309041b82b48a6587ed7b Mon Sep 17 00:00:00 2001 From: mandviverma Date: Wed, 11 Feb 2026 13:10:11 +0530 Subject: [PATCH 10/30] Clean up language selection and related code --- .../app/view/onboarding/OnboardingScreen.kt | 2 -- .../app/view/settings/SettingsScreen.kt | 4 +-- .../applanguage/SetLanguageUseCaseAndroid.kt | 2 +- .../composeResources/values/strings.xml | 7 ----- .../applanguage/RespectMobileSystemCommon.kt | 3 --- .../domain/applanguage/SetLanguageUseCase.kt | 4 +-- .../applanguage/SupportedLanguagesConfig.kt | 26 ++++++++++--------- .../accountlist/AccountListViewModel.kt | 11 ++------ .../onboarding/OnboardingViewModel.kt | 3 --- .../viewmodel/settings/SettingsViewModel.kt | 2 -- 10 files changed, 20 insertions(+), 44 deletions(-) diff --git a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/onboarding/OnboardingScreen.kt b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/onboarding/OnboardingScreen.kt index 3742cc8b4..a98f569ef 100644 --- a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/onboarding/OnboardingScreen.kt +++ b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/onboarding/OnboardingScreen.kt @@ -75,7 +75,6 @@ fun OnboardingScreen( onClickGetStartedButton = viewModel::onClickGetStartedButton, onToggleUsageStatsOptIn = viewModel::onToggleUsageStatsOptIn, onLanguageSelected = viewModel::onLanguageSelected - ) } @@ -85,7 +84,6 @@ fun OnboardingScreen( onClickGetStartedButton: () -> Unit, onToggleUsageStatsOptIn: () -> Unit, onLanguageSelected: (RespectMobileSystemCommon.UiLanguage) -> Unit - ) { val onboardingItem = listOf( diff --git a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt index 8133e01b3..3a9f872b1 100644 --- a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt +++ b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt @@ -56,7 +56,7 @@ fun SettingsScreen( onClickLang: (RespectMobileSystemCommon.UiLanguage) -> Unit = {} ) { - if(uiState.langDialogVisible) { + if (uiState.langDialogVisible) { SettingsDialog( onDismissRequest = onDismissLangDialog, ) { @@ -86,6 +86,7 @@ fun SettingsScreen( item { ListItem( + headlineContent = { Text(text = stringResource(Res.string.language)) }, @@ -103,7 +104,6 @@ fun SettingsScreen( .clickable( onClick = { onClickLanguage() - } ) ) diff --git a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCaseAndroid.kt b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCaseAndroid.kt index 34406a5ef..18c2e9b53 100644 --- a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCaseAndroid.kt +++ b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCaseAndroid.kt @@ -7,7 +7,7 @@ class SetLanguageUseCaseAndroid( override fun invoke( uiLang: RespectMobileSystemCommon.UiLanguage, ): SetLanguageUseCase.SetLangResult { - //languagesConfig uses a delegate on Android that will use Android's per-app locale setting + languagesConfig.localeSetting = uiLang.langCode return SetLanguageUseCase.SetLangResult( diff --git a/respect-lib-shared/src/commonMain/composeResources/values/strings.xml b/respect-lib-shared/src/commonMain/composeResources/values/strings.xml index b72fe56dc..2bef39ed3 100644 --- a/respect-lib-shared/src/commonMain/composeResources/values/strings.xml +++ b/respect-lib-shared/src/commonMain/composeResources/values/strings.xml @@ -426,13 +426,6 @@ Select person Edit person - Monday - Tuesday - Wednesday - Thursday - Friday - Saturday - Sunday Use system default (English) First names diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt index 40b50bada..dd79f4b63 100755 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt @@ -8,11 +8,8 @@ abstract class RespectMobileSystemCommon( private val settings: Settings, protected val langConfig: SupportedLanguagesConfig, ) { - data class UiLanguage(val langCode: String, val langDisplay: String) - - companion object { const val LOCALE_USE_SYSTEM = "" diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCase.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCase.kt index cc37c96b0..9c85ad2ae 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCase.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCase.kt @@ -1,13 +1,11 @@ package world.respect.shared.domain.applanguage interface SetLanguageUseCase { - data class SetLangResult( val waitForRestart: Boolean ) - + operator fun invoke( uiLang: RespectMobileSystemCommon.UiLanguage ): SetLangResult - } diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt index 54bea567f..c7f177e3f 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt @@ -17,7 +17,7 @@ import kotlin.concurrent.Volatile * @param fallbackLocaleCode the fallback locale code that will be used if the user does not * explicitly set a language and none of their preferred longuages are supported */ -class SupportedLanguagesConfig ( +class SupportedLanguagesConfig( val systemLocales: List, private val localeSettingDelegate: LocaleSettingDelegate, private val availableLanguagesConfig: String = DEFAULT_SUPPORTED_LANGUAGES, @@ -25,21 +25,21 @@ class SupportedLanguagesConfig ( ) { interface LocaleSettingDelegate { - var localeSetting: String? - } - val supportedUiLanguages: List = availableLanguagesConfig .split(",") .sorted() .map { - RespectMobileSystemCommon.UiLanguage(it, (RespectMobileConstants.LANGUAGE_NAMES[it] ?: it)) + RespectMobileSystemCommon.UiLanguage( + it, + (RespectMobileConstants.LANGUAGE_NAMES[it] ?: it) + ) } - - private val supportedLangMap: Map = supportedUiLanguages - .associateBy { it.langCode } + private val supportedLangMap: Map = + supportedUiLanguages + .associateBy { it.langCode } /** * The user selected locale within the app (if any). This should be the language code as per @@ -62,13 +62,15 @@ class SupportedLanguagesConfig ( private set init { - if(!supportedLangMap.containsKey(fallbackLocaleCode)) - throw IllegalStateException("available languages $availableLanguagesConfig does not " + - "include fallback: '$fallbackLocaleCode'") + if (!supportedLangMap.containsKey(fallbackLocaleCode)) + throw IllegalStateException( + "available languages $availableLanguagesConfig does not " + + "include fallback: '$fallbackLocaleCode'" + ) } private fun displayLocaleForLangSetting(setting: String?): String { - return if(setting.isNullOrEmpty()) + return if (setting.isNullOrEmpty()) selectFirstSupportedLocale().langCode else setting diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/manageuser/accountlist/AccountListViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/manageuser/accountlist/AccountListViewModel.kt index 3161ec10c..57a0212d9 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/manageuser/accountlist/AccountListViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/manageuser/accountlist/AccountListViewModel.kt @@ -27,7 +27,6 @@ import world.respect.shared.navigation.GetStartedScreen import world.respect.shared.navigation.NavCommand import world.respect.shared.navigation.PersonDetail import world.respect.shared.navigation.RespectAppLauncher -import world.respect.shared.navigation.Settings import world.respect.shared.navigation.WaitingForApproval import world.respect.shared.util.ext.asUiText import world.respect.shared.util.ext.isSameAccount @@ -64,11 +63,9 @@ class AccountListViewModel( init { _appUiState.update { it.copy( - onClickSettings=::onClickSettings, title = Res.string.accounts.asUiText(), hideBottomNavigation = true, userAccountIconVisible = false, - settingsIconVisible = true ) } @@ -157,12 +154,6 @@ class AccountListViewModel( } } - fun onClickSettings() { - _navCommandFlow.tryEmit( - NavCommand.Navigate(Settings) - ) - } - fun onClickAccount(account: RespectAccount) { respectAccountManager.switchAccount(account) @@ -222,6 +213,7 @@ class AccountListViewModel( } } + fun onClickLogout() { uiState.value.selectedAccount?.also { viewModelScope.launch { @@ -229,4 +221,5 @@ class AccountListViewModel( } } } + } \ No newline at end of file diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt index 7067d7601..3c059963b 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt @@ -49,7 +49,6 @@ class OnboardingViewModel( } _uiState.update { it.copy(usageStatsOptInChecked = getUsageReportingEnabledUseCase()) } viewModelScope.launch { - val availableLangs = supportedLangConfig.supportedUiLanguagesAndSysDefault( getString(Res.string.default_language) @@ -111,6 +110,4 @@ class OnboardingViewModel( } } } - - } \ No newline at end of file diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt index 3fcf4f1f8..f229d2612 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt @@ -116,7 +116,6 @@ class SettingsViewModel( ) } } - } fun onNavigateToMapping() { @@ -124,5 +123,4 @@ class SettingsViewModel( NavCommand.Navigate(CurriculumMappingList) ) } - } From 097b55a011bee129a01dffcd0bd20eefc4fcf859 Mon Sep 17 00:00:00 2001 From: mandviverma Date: Wed, 11 Feb 2026 22:20:42 +0530 Subject: [PATCH 11/30] Extract LanguageDropdown into a separate component --- .../app/components/LanguageDropdown.kt | 68 +++++++++++++++++++ .../app/view/onboarding/OnboardingScreen.kt | 60 +--------------- .../composeResources/values-hi/strings.xml | 3 + 3 files changed, 73 insertions(+), 58 deletions(-) create mode 100644 respect-app-compose/src/commonMain/kotlin/world/respect/app/components/LanguageDropdown.kt diff --git a/respect-app-compose/src/commonMain/kotlin/world/respect/app/components/LanguageDropdown.kt b/respect-app-compose/src/commonMain/kotlin/world/respect/app/components/LanguageDropdown.kt new file mode 100644 index 000000000..f5a15d5c2 --- /dev/null +++ b/respect-app-compose/src/commonMain/kotlin/world/respect/app/components/LanguageDropdown.kt @@ -0,0 +1,68 @@ +package world.respect.app.components + + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults +import world.respect.shared.generated.resources.language +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import org.jetbrains.compose.resources.stringResource +import world.respect.shared.domain.applanguage.RespectMobileSystemCommon +import world.respect.shared.generated.resources.Res + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun LanguageDropdown( + selected: RespectMobileSystemCommon.UiLanguage?, + languages: List, + onSelected: (RespectMobileSystemCommon.UiLanguage) -> Unit, + enabled: Boolean +) { + var expanded by remember { mutableStateOf(false) } + + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { if (enabled) expanded = !expanded } + ) { + OutlinedTextField( + value = selected?.langDisplay ?: "", + onValueChange = {}, + readOnly = true, + label = { Text(stringResource(Res.string.language)) }, + trailingIcon = { + ExposedDropdownMenuDefaults.TrailingIcon(expanded) + }, + modifier = Modifier + .menuAnchor() + .fillMaxWidth() + .defaultItemPadding() + ) + + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + languages.forEach { lang -> + DropdownMenuItem( + text = { Text(lang.langDisplay) }, + onClick = { + onSelected(lang) + expanded = false + } + ) + } + } + } +} + + diff --git a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/onboarding/OnboardingScreen.kt b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/onboarding/OnboardingScreen.kt index a98f569ef..50a71c48e 100644 --- a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/onboarding/OnboardingScreen.kt +++ b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/onboarding/OnboardingScreen.kt @@ -18,19 +18,11 @@ import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material3.Button import androidx.compose.material3.Checkbox -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -41,11 +33,11 @@ import world.respect.images.respectImagePainter import world.respect.shared.viewmodel.onboarding.OnboardingUiState import world.respect.shared.viewmodel.onboarding.OnboardingViewModel import org.jetbrains.compose.resources.stringResource +import world.respect.app.components.LanguageDropdown import world.respect.app.components.defaultItemPadding import world.respect.shared.domain.applanguage.RespectMobileSystemCommon import world.respect.shared.generated.resources.Res import world.respect.shared.generated.resources.get_started -import world.respect.shared.generated.resources.language import world.respect.shared.generated.resources.onboardingDescription1 import world.respect.shared.generated.resources.onboardingDescription2 import world.respect.shared.generated.resources.onboardingDescription3 @@ -55,8 +47,7 @@ import world.respect.shared.generated.resources.onboardingTitle2 import world.respect.shared.generated.resources.onboardingTitle3 import world.respect.shared.generated.resources.onboardingTitle4 import world.respect.shared.generated.resources.send_usage_stats_and_crash_reports -import world.respect.shared.util.ext.isLoading -import kotlin.collections.forEach + data class OnboardingItem( @@ -226,50 +217,3 @@ fun OnboardingScreen( } - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun LanguageDropdown( - selected: RespectMobileSystemCommon.UiLanguage?, - languages: List, - onSelected: (RespectMobileSystemCommon.UiLanguage) -> Unit, - enabled: Boolean -) { - var expanded by remember { mutableStateOf(false) } - - ExposedDropdownMenuBox( - expanded = expanded, - onExpandedChange = { if (enabled) expanded = !expanded } - ) { - OutlinedTextField( - value = selected?.langDisplay ?: "", - onValueChange = {}, - readOnly = true, - label = { Text(stringResource(Res.string.language)) }, - trailingIcon = { - ExposedDropdownMenuDefaults.TrailingIcon(expanded) - }, - modifier = Modifier - .menuAnchor() - .fillMaxWidth() - .defaultItemPadding() - ) - - ExposedDropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false } - ) { - languages.forEach { lang -> - DropdownMenuItem( - text = { Text(lang.langDisplay) }, - onClick = { - onSelected(lang) - expanded = false - } - ) - } - } - } -} - - diff --git a/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml b/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml index ce8f6b287..70b38a581 100644 --- a/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml +++ b/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml @@ -7,5 +7,8 @@ आपने अभी तक अपने स्कूल के लिए कोई ऐप नहीं जोड़ा है। लॉग इन करें शुरू हो जाओ + सेटिंग्स + मानचित्रण + ऐप्स \ No newline at end of file From 1980c9831a78236b225c6bbe5c21f71fde4ec3a4 Mon Sep 17 00:00:00 2001 From: mandviverma Date: Wed, 11 Feb 2026 22:48:30 +0530 Subject: [PATCH 12/30] Centralize UI language selection logic unique internal code, `system_default_internal`, is now used to represent the "System Default" language option, preventing potential conflicts with actual language codes like "en". --- .../applanguage/SupportedLanguagesConfig.kt | 13 +++++++++++- .../onboarding/OnboardingViewModel.kt | 21 ++++--------------- .../viewmodel/settings/SettingsViewModel.kt | 18 +++++++--------- 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt index c7f177e3f..90b126138 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt @@ -75,13 +75,23 @@ class SupportedLanguagesConfig( else setting } + fun getCurrentUiLanguage( + availableLangs: List + ): RespectMobileSystemCommon.UiLanguage { + val savedLangCode = localeSetting + return if (savedLangCode == null || savedLangCode == RespectMobileSystemCommon.LOCALE_USE_SYSTEM) { + availableLangs.firstOrNull { it.langCode == SYSTEM_DEFAULT_CODE } + } else { + availableLangs.find { it.langCode == savedLangCode && it.langCode != SYSTEM_DEFAULT_CODE } + } ?: availableLangs.first() + } fun supportedUiLanguagesAndSysDefault( defaultLangDisplay: String, ): List { val defaultEnglish = RespectMobileSystemCommon.UiLanguage( - langCode = "en", // IMPORTANT: set to English + langCode = SYSTEM_DEFAULT_CODE, langDisplay = defaultLangDisplay ) @@ -109,6 +119,7 @@ class SupportedLanguagesConfig( companion object { const val DEFAULT_SUPPORTED_LANGUAGES = "en,hi,fa,ps,ar,tg,bn,ne,my,rw,ru" + const val SYSTEM_DEFAULT_CODE = "system_default_internal" } diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt index 3c059963b..090f11f87 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt @@ -49,24 +49,11 @@ class OnboardingViewModel( } _uiState.update { it.copy(usageStatsOptInChecked = getUsageReportingEnabledUseCase()) } viewModelScope.launch { - val availableLangs = - supportedLangConfig.supportedUiLanguagesAndSysDefault( - getString(Res.string.default_language) - ) - - val savedLangCode = - supportedLangConfig.localeSetting - ?: RespectMobileSystemCommon.LOCALE_USE_SYSTEM - - val currentLang = - if (savedLangCode == RespectMobileSystemCommon.LOCALE_USE_SYSTEM) { - val resolvedLocale = - supportedLangConfig.selectFirstSupportedLocale().langCode + var availableLangs = supportedLangConfig.supportedUiLanguagesAndSysDefault( + getString(Res.string.default_language) + ) - availableLangs.firstOrNull { it.langCode == resolvedLocale } - } else { - availableLangs.firstOrNull { it.langCode == savedLangCode } - } ?: availableLangs.first() + val currentLang = supportedLangConfig.getCurrentUiLanguage(availableLangs) _uiState.update { it.copy( diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt index f229d2612..1ae41bc4f 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt @@ -57,17 +57,13 @@ class SettingsViewModel( } viewModelScope.launch { - availableLangs = - supportedLangConfig.supportedUiLanguagesAndSysDefault(getString(Res.string.default_language)) - val savedLangCode = - supportedLangConfig.localeSetting ?: RespectMobileSystemCommon.LOCALE_USE_SYSTEM - - val currentLang = if (savedLangCode == RespectMobileSystemCommon.LOCALE_USE_SYSTEM) { - val resolvedLocale = supportedLangConfig.selectFirstSupportedLocale().langCode - availableLangs.firstOrNull { it.langCode == resolvedLocale } - } else { - availableLangs.firstOrNull { it.langCode == savedLangCode } - } ?: availableLangs.first() + // 1. Get the list of languages (first item has the unique "system_default_internal" code) + availableLangs = supportedLangConfig.supportedUiLanguagesAndSysDefault( + getString(Res.string.default_language) + ) + + // 2. Use the new domain function to find which one to display + val currentLang = supportedLangConfig.getCurrentUiLanguage(availableLangs) _uiState.update { it.copy( From c19a4a80324de4f05b98b5cbae1db1bdbfcde63e Mon Sep 17 00:00:00 2001 From: mandviverma Date: Thu, 12 Feb 2026 11:10:59 +0530 Subject: [PATCH 13/30] Refactor: Centralize app language logic into `SupportedLanguagesConfig` This commit refactors the application's language handling by removing the `RespectMobileSystemCommon` class and consolidating its logic and data structures into `SupportedLanguagesConfig`. --- respect-app-compose/build.gradle.kts | 2 +- .../kotlin/world/respect/AppKoinModule.kt | 1 - .../src/androidMain/res/values-ne/strings.xml | 2 + .../app/components/LanguageDropdown.kt | 8 +- .../app/view/onboarding/OnboardingScreen.kt | 4 +- .../app/view/settings/SettingsScreen.kt | 4 +- .../LocaleSettingDelegateAndroid.kt | 3 +- .../applanguage/SetLanguageUseCaseAndroid.kt | 2 +- .../composeResources/values/strings.xml | 3 +- .../applanguage/RespectMobileSystemCommon.kt | 17 ---- .../domain/applanguage/SetLanguageUseCase.kt | 4 +- .../applanguage/SupportedLanguagesConfig.kt | 92 +++++++++++-------- .../onboarding/OnboardingViewModel.kt | 13 ++- .../viewmodel/settings/SettingsViewModel.kt | 20 ++-- 14 files changed, 89 insertions(+), 86 deletions(-) create mode 100644 respect-app-compose/src/androidMain/res/values-ne/strings.xml delete mode 100755 respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt diff --git a/respect-app-compose/build.gradle.kts b/respect-app-compose/build.gradle.kts index c583adad9..4a18d864d 100644 --- a/respect-app-compose/build.gradle.kts +++ b/respect-app-compose/build.gradle.kts @@ -180,6 +180,7 @@ android { androidResources{ generateLocaleConfig = true + localeFilters.addAll(listOf("en", "hi", "ne")) } defaultConfig { @@ -189,7 +190,6 @@ android { versionCode = 121 versionName = "1.0.21" - resConfigs("en", "hi") for(propName in ACRA_PROP_NAMES) { buildConfigField( diff --git a/respect-app-compose/src/androidMain/kotlin/world/respect/AppKoinModule.kt b/respect-app-compose/src/androidMain/kotlin/world/respect/AppKoinModule.kt index 81e8bae33..fd99bdfd2 100644 --- a/respect-app-compose/src/androidMain/kotlin/world/respect/AppKoinModule.kt +++ b/respect-app-compose/src/androidMain/kotlin/world/respect/AppKoinModule.kt @@ -98,7 +98,6 @@ import world.respect.libxxhash.XXStringHasher import world.respect.libxxhash.jvmimpl.XXHasher64FactoryCommonJvm import world.respect.libxxhash.jvmimpl.XXStringHasherCommonJvm import world.respect.shared.domain.applanguage.SupportedLanguagesConfig -import world.respect.shared.domain.applanguage.RespectMobileSystemCommon import world.respect.shared.domain.account.RespectAccount import world.respect.shared.domain.account.RespectAccountManager import world.respect.shared.domain.account.RespectAccountSchoolScopeLink diff --git a/respect-app-compose/src/androidMain/res/values-ne/strings.xml b/respect-app-compose/src/androidMain/res/values-ne/strings.xml new file mode 100644 index 000000000..a6b3daec9 --- /dev/null +++ b/respect-app-compose/src/androidMain/res/values-ne/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/respect-app-compose/src/commonMain/kotlin/world/respect/app/components/LanguageDropdown.kt b/respect-app-compose/src/commonMain/kotlin/world/respect/app/components/LanguageDropdown.kt index f5a15d5c2..b22e36d78 100644 --- a/respect-app-compose/src/commonMain/kotlin/world/respect/app/components/LanguageDropdown.kt +++ b/respect-app-compose/src/commonMain/kotlin/world/respect/app/components/LanguageDropdown.kt @@ -16,16 +16,16 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import org.jetbrains.compose.resources.stringResource -import world.respect.shared.domain.applanguage.RespectMobileSystemCommon +import world.respect.shared.domain.applanguage.SupportedLanguagesConfig import world.respect.shared.generated.resources.Res @OptIn(ExperimentalMaterial3Api::class) @Composable fun LanguageDropdown( - selected: RespectMobileSystemCommon.UiLanguage?, - languages: List, - onSelected: (RespectMobileSystemCommon.UiLanguage) -> Unit, + selected: SupportedLanguagesConfig.UiLanguage?, + languages: List, + onSelected: (SupportedLanguagesConfig.UiLanguage) -> Unit, enabled: Boolean ) { var expanded by remember { mutableStateOf(false) } diff --git a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/onboarding/OnboardingScreen.kt b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/onboarding/OnboardingScreen.kt index 50a71c48e..98ca8979f 100644 --- a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/onboarding/OnboardingScreen.kt +++ b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/onboarding/OnboardingScreen.kt @@ -35,7 +35,7 @@ import world.respect.shared.viewmodel.onboarding.OnboardingViewModel import org.jetbrains.compose.resources.stringResource import world.respect.app.components.LanguageDropdown import world.respect.app.components.defaultItemPadding -import world.respect.shared.domain.applanguage.RespectMobileSystemCommon +import world.respect.shared.domain.applanguage.SupportedLanguagesConfig import world.respect.shared.generated.resources.Res import world.respect.shared.generated.resources.get_started import world.respect.shared.generated.resources.onboardingDescription1 @@ -74,7 +74,7 @@ fun OnboardingScreen( uiState: OnboardingUiState, onClickGetStartedButton: () -> Unit, onToggleUsageStatsOptIn: () -> Unit, - onLanguageSelected: (RespectMobileSystemCommon.UiLanguage) -> Unit + onLanguageSelected: (SupportedLanguagesConfig.UiLanguage) -> Unit ) { val onboardingItem = listOf( diff --git a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt index 3a9f872b1..90a1d062f 100644 --- a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt +++ b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt @@ -25,7 +25,7 @@ import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import org.jetbrains.compose.resources.stringResource -import world.respect.shared.domain.applanguage.RespectMobileSystemCommon +import world.respect.shared.domain.applanguage.SupportedLanguagesConfig import world.respect.shared.generated.resources.Res import world.respect.shared.generated.resources.language import world.respect.shared.generated.resources.loading @@ -53,7 +53,7 @@ fun SettingsScreen( onNavigateToMapping: () -> Unit = {}, onClickLanguage: () -> Unit = {}, onDismissLangDialog: () -> Unit = {}, - onClickLang: (RespectMobileSystemCommon.UiLanguage) -> Unit = {} + onClickLang: (SupportedLanguagesConfig.UiLanguage) -> Unit = {} ) { if (uiState.langDialogVisible) { diff --git a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/LocaleSettingDelegateAndroid.kt b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/LocaleSettingDelegateAndroid.kt index a849d4079..04ce3ae99 100644 --- a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/LocaleSettingDelegateAndroid.kt +++ b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/LocaleSettingDelegateAndroid.kt @@ -2,13 +2,14 @@ package world.respect.shared.domain.applanguage import androidx.appcompat.app.AppCompatDelegate import androidx.core.os.LocaleListCompat +import world.respect.shared.domain.applanguage.SupportedLanguagesConfig.Companion.LOCALE_USE_SYSTEM class LocaleSettingDelegateAndroid: SupportedLanguagesConfig.LocaleSettingDelegate { override var localeSetting: String? get() = AppCompatDelegate.getApplicationLocales().get(0)?.language ?: "en" set(value) { - val localeList = if(value == RespectMobileSystemCommon.LOCALE_USE_SYSTEM) { + val localeList = if(value == LOCALE_USE_SYSTEM) { LocaleListCompat.getAdjustedDefault() }else { LocaleListCompat.forLanguageTags(value) diff --git a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCaseAndroid.kt b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCaseAndroid.kt index 18c2e9b53..5a59f9d0e 100644 --- a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCaseAndroid.kt +++ b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCaseAndroid.kt @@ -5,7 +5,7 @@ class SetLanguageUseCaseAndroid( ): SetLanguageUseCase { override fun invoke( - uiLang: RespectMobileSystemCommon.UiLanguage, + uiLang: SupportedLanguagesConfig.UiLanguage, ): SetLanguageUseCase.SetLangResult { languagesConfig.localeSetting = uiLang.langCode diff --git a/respect-lib-shared/src/commonMain/composeResources/values/strings.xml b/respect-lib-shared/src/commonMain/composeResources/values/strings.xml index 2bef39ed3..5e0b38596 100644 --- a/respect-lib-shared/src/commonMain/composeResources/values/strings.xml +++ b/respect-lib-shared/src/commonMain/composeResources/values/strings.xml @@ -426,8 +426,7 @@ Select person Edit person - Use system default (English) - + Use system default First names Mappings Mapping diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt deleted file mode 100755 index dd79f4b63..000000000 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt +++ /dev/null @@ -1,17 +0,0 @@ -package world.respect.shared.domain.applanguage - -import org.jetbrains.compose.resources.StringResource -import com.russhwolf.settings.Settings - - -abstract class RespectMobileSystemCommon( - private val settings: Settings, - protected val langConfig: SupportedLanguagesConfig, -) { - data class UiLanguage(val langCode: String, val langDisplay: String) - - companion object { - const val LOCALE_USE_SYSTEM = "" - - } -} \ No newline at end of file diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCase.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCase.kt index 9c85ad2ae..0015b4b05 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCase.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCase.kt @@ -4,8 +4,8 @@ interface SetLanguageUseCase { data class SetLangResult( val waitForRestart: Boolean ) - + operator fun invoke( - uiLang: RespectMobileSystemCommon.UiLanguage + uiLang: SupportedLanguagesConfig.UiLanguage ): SetLangResult } diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt index 90b126138..826e116e8 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt @@ -1,5 +1,6 @@ package world.respect.shared.domain.applanguage +import com.russhwolf.settings.Settings import kotlin.concurrent.Volatile /** @@ -17,29 +18,57 @@ import kotlin.concurrent.Volatile * @param fallbackLocaleCode the fallback locale code that will be used if the user does not * explicitly set a language and none of their preferred longuages are supported */ -class SupportedLanguagesConfig( +class SupportedLanguagesConfig ( val systemLocales: List, private val localeSettingDelegate: LocaleSettingDelegate, private val availableLanguagesConfig: String = DEFAULT_SUPPORTED_LANGUAGES, private val fallbackLocaleCode: String = "en", ) { + constructor( + systemLocales: List, + settings: Settings, + availableLanguagesConfig: String = DEFAULT_SUPPORTED_LANGUAGES, + fallbackLocaleCode: String = "en", + ): this( + systemLocales = systemLocales, + localeSettingDelegate = SettingsLocaleSettingDelegate(settings), + availableLanguagesConfig = availableLanguagesConfig, + fallbackLocaleCode = fallbackLocaleCode, + ) + + data class UiLanguage(val langCode: String, val langDisplay: String) + interface LocaleSettingDelegate { + var localeSetting: String? + } - val supportedUiLanguages: List = availableLanguagesConfig + class SettingsLocaleSettingDelegate( + private val settings: Settings + ) : LocaleSettingDelegate { + override var localeSetting: String? + get() = settings.getStringOrNull(PREFKEY_LOCALE) + set(value) { + if(value != null) { + settings.putString(PREFKEY_LOCALE, value) + }else { + settings.remove(PREFKEY_LOCALE) + } + } + } + + + val supportedUiLanguages: List = availableLanguagesConfig .split(",") .sorted() .map { - RespectMobileSystemCommon.UiLanguage( - it, - (RespectMobileConstants.LANGUAGE_NAMES[it] ?: it) - ) + UiLanguage(it, (RespectMobileConstants.LANGUAGE_NAMES[it] ?: it)) } - private val supportedLangMap: Map = - supportedUiLanguages - .associateBy { it.langCode } + + private val supportedLangMap: Map = supportedUiLanguages + .associateBy { it.langCode } /** * The user selected locale within the app (if any). This should be the language code as per @@ -53,7 +82,6 @@ class SupportedLanguagesConfig( displayedLocale = displayLocaleForLangSetting(value) } - /** * This is stored because it will be looked up every time a string lookup is done via systemImpl */ @@ -62,40 +90,25 @@ class SupportedLanguagesConfig( private set init { - if (!supportedLangMap.containsKey(fallbackLocaleCode)) - throw IllegalStateException( - "available languages $availableLanguagesConfig does not " + - "include fallback: '$fallbackLocaleCode'" - ) + if(!supportedLangMap.containsKey(fallbackLocaleCode)) + throw IllegalStateException("available languages $availableLanguagesConfig does not " + + "include fallback: '$fallbackLocaleCode'") } private fun displayLocaleForLangSetting(setting: String?): String { - return if (setting.isNullOrEmpty()) + return if(setting.isNullOrEmpty()) selectFirstSupportedLocale().langCode else setting } - fun getCurrentUiLanguage( - availableLangs: List - ): RespectMobileSystemCommon.UiLanguage { - val savedLangCode = localeSetting - - return if (savedLangCode == null || savedLangCode == RespectMobileSystemCommon.LOCALE_USE_SYSTEM) { - availableLangs.firstOrNull { it.langCode == SYSTEM_DEFAULT_CODE } - } else { - availableLangs.find { it.langCode == savedLangCode && it.langCode != SYSTEM_DEFAULT_CODE } - } ?: availableLangs.first() - } - fun supportedUiLanguagesAndSysDefault( - defaultLangDisplay: String, - ): List { - - val defaultEnglish = RespectMobileSystemCommon.UiLanguage( - langCode = SYSTEM_DEFAULT_CODE, - langDisplay = defaultLangDisplay - ) - return listOf(defaultEnglish) + supportedUiLanguages + fun supportedUiLanguagesAndSysDefault( + useDeviceLangDisplay: String, + ) : List{ + return listOf( + UiLanguage(LOCALE_USE_SYSTEM, + useDeviceLangDisplay) + ) + supportedUiLanguages } /** @@ -106,7 +119,7 @@ class SupportedLanguagesConfig( */ fun selectFirstSupportedLocale( preferredLocales: List = systemLocales, - ): RespectMobileSystemCommon.UiLanguage { + ): UiLanguage { val supportedLocaleCodes = supportedUiLanguages.map { it.langCode } @@ -118,8 +131,11 @@ class SupportedLanguagesConfig( companion object { + const val PREFKEY_LOCALE = "locale" + + const val DEFAULT_SUPPORTED_LANGUAGES = "en,hi,fa,ps,ar,tg,bn,ne,my,rw,ru" - const val SYSTEM_DEFAULT_CODE = "system_default_internal" + const val LOCALE_USE_SYSTEM = "" } diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt index 090f11f87..41db21784 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt @@ -9,9 +9,9 @@ import com.russhwolf.settings.Settings import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.jetbrains.compose.resources.getString -import world.respect.shared.domain.applanguage.RespectMobileSystemCommon import world.respect.shared.domain.applanguage.SetLanguageUseCase import world.respect.shared.domain.applanguage.SupportedLanguagesConfig +import world.respect.shared.domain.applanguage.SupportedLanguagesConfig.Companion.LOCALE_USE_SYSTEM import world.respect.shared.domain.navigation.onappstart.NavigateOnAppStartUseCase import world.respect.shared.domain.onboarding.ShouldShowOnboardingUseCase import world.respect.shared.domain.usagereporting.GetUsageReportingEnabledUseCase @@ -22,8 +22,8 @@ import world.respect.shared.generated.resources.default_language data class OnboardingUiState( val isLoading: Boolean = false, val usageStatsOptInChecked: Boolean = true, - val availableLanguages: List = emptyList(), - val selectedLanguage: RespectMobileSystemCommon.UiLanguage? = null, + val availableLanguages: List = emptyList(), + val selectedLanguage: SupportedLanguagesConfig.UiLanguage? = null, ) class OnboardingViewModel( @@ -52,8 +52,11 @@ class OnboardingViewModel( var availableLangs = supportedLangConfig.supportedUiLanguagesAndSysDefault( getString(Res.string.default_language) ) + val langSetting = supportedLangConfig.localeSetting ?: LOCALE_USE_SYSTEM - val currentLang = supportedLangConfig.getCurrentUiLanguage(availableLangs) + val currentLang = availableLangs.first { + it.langCode == langSetting + } _uiState.update { it.copy( @@ -83,7 +86,7 @@ class OnboardingViewModel( } - fun onLanguageSelected(lang: RespectMobileSystemCommon.UiLanguage) { + fun onLanguageSelected(lang: SupportedLanguagesConfig.UiLanguage) { val result = setLanguageUseCase(uiLang = lang) diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt index 1ae41bc4f..9c9c4a7a8 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt @@ -7,13 +7,12 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.jetbrains.compose.resources.getString -import org.jetbrains.compose.resources.stringResource import org.koin.core.component.KoinScopeComponent import org.koin.core.scope.Scope import world.respect.shared.domain.applanguage.SupportedLanguagesConfig -import world.respect.shared.domain.applanguage.RespectMobileSystemCommon import world.respect.shared.domain.account.RespectAccountManager import world.respect.shared.domain.applanguage.SetLanguageUseCase +import world.respect.shared.domain.applanguage.SupportedLanguagesConfig.Companion.LOCALE_USE_SYSTEM import world.respect.shared.generated.resources.Res import world.respect.shared.generated.resources.default_language import world.respect.shared.generated.resources.settings @@ -27,7 +26,7 @@ data class SettingsUiState( val loading: Boolean = false, val langDialogVisible: Boolean = false, val currentLanguage: String = "", - val availableLanguages: List = emptyList(), + val availableLanguages: List = emptyList(), val waitForRestartDialogVisible: Boolean = false ) @@ -38,7 +37,7 @@ class SettingsViewModel( private val setLanguageUseCase: SetLanguageUseCase, ) : RespectViewModel(savedStateHandle), KoinScopeComponent { - private var availableLangs: List = emptyList() + private var availableLangs: List = emptyList() override val scope: Scope = accountManager.requireActiveAccountScope() @@ -57,14 +56,15 @@ class SettingsViewModel( } viewModelScope.launch { - // 1. Get the list of languages (first item has the unique "system_default_internal" code) + availableLangs = supportedLangConfig.supportedUiLanguagesAndSysDefault( - getString(Res.string.default_language) - ) + getString(Res.string.default_language)) - // 2. Use the new domain function to find which one to display - val currentLang = supportedLangConfig.getCurrentUiLanguage(availableLangs) + val langSetting = supportedLangConfig.localeSetting ?: LOCALE_USE_SYSTEM + val currentLang = availableLangs.first { + it.langCode == langSetting + } _uiState.update { it.copy( availableLanguages = availableLangs, @@ -88,7 +88,7 @@ class SettingsViewModel( } } - fun onClickLang(lang: RespectMobileSystemCommon.UiLanguage) { + fun onClickLang(lang: SupportedLanguagesConfig.UiLanguage) { _uiState.update { prev -> prev.copy(langDialogVisible = false) } From b3306e77c544d5e21e5177ab4c9e07a93709f355 Mon Sep 17 00:00:00 2001 From: mandviverma Date: Thu, 12 Feb 2026 16:17:22 +0530 Subject: [PATCH 14/30] feat: Add Nepali language support and refactor language settings --- .../src/androidMain/res/values-ne/strings.xml | 2 + .../composeResources/values-ne/strings.xml | 16 ++++ .../applanguage/SupportedLanguagesConfig.kt | 79 ++++++++++--------- 3 files changed, 60 insertions(+), 37 deletions(-) create mode 100644 respect-app-compose/src/androidMain/res/values-ne/strings.xml create mode 100644 respect-lib-shared/src/commonMain/composeResources/values-ne/strings.xml diff --git a/respect-app-compose/src/androidMain/res/values-ne/strings.xml b/respect-app-compose/src/androidMain/res/values-ne/strings.xml new file mode 100644 index 000000000..a6b3daec9 --- /dev/null +++ b/respect-app-compose/src/androidMain/res/values-ne/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/respect-lib-shared/src/commonMain/composeResources/values-ne/strings.xml b/respect-lib-shared/src/commonMain/composeResources/values-ne/strings.xml new file mode 100644 index 000000000..050e26a49 --- /dev/null +++ b/respect-lib-shared/src/commonMain/composeResources/values-ne/strings.xml @@ -0,0 +1,16 @@ + + + + एप + भाषा + तपाईंले विद्यालय सफलतापूर्वक दर्ता गर्नुभयो + + तपाईंले अझै आफ्नो विद्यालयको लागि कुनै एप थप्नुभएको छैन। + लग-इन गर्नुहोस् + सुरु गर्नुहोस् + सेटिङहरू + म्यापिङ + एपहरू + + प्रणालीको पूर्वनिर्धारित प्रयोग गर्नुहोस् (%1$s) + \ No newline at end of file diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt index 90b126138..62d615ce2 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt @@ -1,5 +1,6 @@ package world.respect.shared.domain.applanguage +import com.russhwolf.settings.Settings import kotlin.concurrent.Volatile /** @@ -24,22 +25,39 @@ class SupportedLanguagesConfig( private val fallbackLocaleCode: String = "en", ) { + + data class UiLanguage(val langCode: String, val langDisplay: String) + interface LocaleSettingDelegate { + var localeSetting: String? + } - val supportedUiLanguages: List = availableLanguagesConfig + class SettingsLocaleSettingDelegate( + private val settings: Settings + ) : LocaleSettingDelegate { + override var localeSetting: String? + get() = settings.getStringOrNull(PREFKEY_LOCALE) + set(value) { + if(value != null) { + settings.putString(PREFKEY_LOCALE, value) + }else { + settings.remove(PREFKEY_LOCALE) + } + } + } + + + val supportedUiLanguages: List = availableLanguagesConfig .split(",") .sorted() .map { - RespectMobileSystemCommon.UiLanguage( - it, - (RespectMobileConstants.LANGUAGE_NAMES[it] ?: it) - ) + UiLanguage(it, (RespectMobileConstants.LANGUAGE_NAMES[it] ?: it)) } - private val supportedLangMap: Map = - supportedUiLanguages - .associateBy { it.langCode } + + private val supportedLangMap: Map = supportedUiLanguages + .associateBy { it.langCode } /** * The user selected locale within the app (if any). This should be the language code as per @@ -53,7 +71,6 @@ class SupportedLanguagesConfig( displayedLocale = displayLocaleForLangSetting(value) } - /** * This is stored because it will be looked up every time a string lookup is done via systemImpl */ @@ -62,40 +79,25 @@ class SupportedLanguagesConfig( private set init { - if (!supportedLangMap.containsKey(fallbackLocaleCode)) - throw IllegalStateException( - "available languages $availableLanguagesConfig does not " + - "include fallback: '$fallbackLocaleCode'" - ) + if(!supportedLangMap.containsKey(fallbackLocaleCode)) + throw IllegalStateException("available languages $availableLanguagesConfig does not " + + "include fallback: '$fallbackLocaleCode'") } private fun displayLocaleForLangSetting(setting: String?): String { - return if (setting.isNullOrEmpty()) + return if(setting.isNullOrEmpty()) selectFirstSupportedLocale().langCode else setting } - fun getCurrentUiLanguage( - availableLangs: List - ): RespectMobileSystemCommon.UiLanguage { - val savedLangCode = localeSetting - - return if (savedLangCode == null || savedLangCode == RespectMobileSystemCommon.LOCALE_USE_SYSTEM) { - availableLangs.firstOrNull { it.langCode == SYSTEM_DEFAULT_CODE } - } else { - availableLangs.find { it.langCode == savedLangCode && it.langCode != SYSTEM_DEFAULT_CODE } - } ?: availableLangs.first() - } - fun supportedUiLanguagesAndSysDefault( - defaultLangDisplay: String, - ): List { - - val defaultEnglish = RespectMobileSystemCommon.UiLanguage( - langCode = SYSTEM_DEFAULT_CODE, - langDisplay = defaultLangDisplay - ) - return listOf(defaultEnglish) + supportedUiLanguages + fun supportedUiLanguagesAndSysDefault( + useDeviceLangDisplay: String, + ) : List{ + return listOf( + UiLanguage(LOCALE_USE_SYSTEM, + useDeviceLangDisplay) + ) + supportedUiLanguages } /** @@ -106,7 +108,7 @@ class SupportedLanguagesConfig( */ fun selectFirstSupportedLocale( preferredLocales: List = systemLocales, - ): RespectMobileSystemCommon.UiLanguage { + ): UiLanguage { val supportedLocaleCodes = supportedUiLanguages.map { it.langCode } @@ -118,8 +120,11 @@ class SupportedLanguagesConfig( companion object { + const val PREFKEY_LOCALE = "locale" + + const val DEFAULT_SUPPORTED_LANGUAGES = "en,hi,fa,ps,ar,tg,bn,ne,my,rw,ru" - const val SYSTEM_DEFAULT_CODE = "system_default_internal" + const val LOCALE_USE_SYSTEM = "" } From 97934f2bfadf197e7a701be929dd6e9f24a529ab Mon Sep 17 00:00:00 2001 From: mandviverma Date: Thu, 12 Feb 2026 16:23:59 +0530 Subject: [PATCH 15/30] Refactor: Centralize app language logic into `SupportedLanguagesConfig` --- .../kotlin/world/respect/AppKoinModule.kt | 1 - .../app/components/LanguageDropdown.kt | 8 +++--- .../app/view/onboarding/OnboardingScreen.kt | 4 +-- .../app/view/settings/SettingsScreen.kt | 4 +-- .../LocaleSettingDelegateAndroid.kt | 3 ++- .../applanguage/SetLanguageUseCaseAndroid.kt | 2 +- .../applanguage/RespectMobileSystemCommon.kt | 17 ------------- .../domain/applanguage/SetLanguageUseCase.kt | 2 +- .../onboarding/OnboardingViewModel.kt | 23 +++++++++++------ .../viewmodel/settings/SettingsViewModel.kt | 25 ++++++++++++------- 10 files changed, 44 insertions(+), 45 deletions(-) delete mode 100755 respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt diff --git a/respect-app-compose/src/androidMain/kotlin/world/respect/AppKoinModule.kt b/respect-app-compose/src/androidMain/kotlin/world/respect/AppKoinModule.kt index 81e8bae33..fd99bdfd2 100644 --- a/respect-app-compose/src/androidMain/kotlin/world/respect/AppKoinModule.kt +++ b/respect-app-compose/src/androidMain/kotlin/world/respect/AppKoinModule.kt @@ -98,7 +98,6 @@ import world.respect.libxxhash.XXStringHasher import world.respect.libxxhash.jvmimpl.XXHasher64FactoryCommonJvm import world.respect.libxxhash.jvmimpl.XXStringHasherCommonJvm import world.respect.shared.domain.applanguage.SupportedLanguagesConfig -import world.respect.shared.domain.applanguage.RespectMobileSystemCommon import world.respect.shared.domain.account.RespectAccount import world.respect.shared.domain.account.RespectAccountManager import world.respect.shared.domain.account.RespectAccountSchoolScopeLink diff --git a/respect-app-compose/src/commonMain/kotlin/world/respect/app/components/LanguageDropdown.kt b/respect-app-compose/src/commonMain/kotlin/world/respect/app/components/LanguageDropdown.kt index f5a15d5c2..b22e36d78 100644 --- a/respect-app-compose/src/commonMain/kotlin/world/respect/app/components/LanguageDropdown.kt +++ b/respect-app-compose/src/commonMain/kotlin/world/respect/app/components/LanguageDropdown.kt @@ -16,16 +16,16 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import org.jetbrains.compose.resources.stringResource -import world.respect.shared.domain.applanguage.RespectMobileSystemCommon +import world.respect.shared.domain.applanguage.SupportedLanguagesConfig import world.respect.shared.generated.resources.Res @OptIn(ExperimentalMaterial3Api::class) @Composable fun LanguageDropdown( - selected: RespectMobileSystemCommon.UiLanguage?, - languages: List, - onSelected: (RespectMobileSystemCommon.UiLanguage) -> Unit, + selected: SupportedLanguagesConfig.UiLanguage?, + languages: List, + onSelected: (SupportedLanguagesConfig.UiLanguage) -> Unit, enabled: Boolean ) { var expanded by remember { mutableStateOf(false) } diff --git a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/onboarding/OnboardingScreen.kt b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/onboarding/OnboardingScreen.kt index 50a71c48e..98ca8979f 100644 --- a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/onboarding/OnboardingScreen.kt +++ b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/onboarding/OnboardingScreen.kt @@ -35,7 +35,7 @@ import world.respect.shared.viewmodel.onboarding.OnboardingViewModel import org.jetbrains.compose.resources.stringResource import world.respect.app.components.LanguageDropdown import world.respect.app.components.defaultItemPadding -import world.respect.shared.domain.applanguage.RespectMobileSystemCommon +import world.respect.shared.domain.applanguage.SupportedLanguagesConfig import world.respect.shared.generated.resources.Res import world.respect.shared.generated.resources.get_started import world.respect.shared.generated.resources.onboardingDescription1 @@ -74,7 +74,7 @@ fun OnboardingScreen( uiState: OnboardingUiState, onClickGetStartedButton: () -> Unit, onToggleUsageStatsOptIn: () -> Unit, - onLanguageSelected: (RespectMobileSystemCommon.UiLanguage) -> Unit + onLanguageSelected: (SupportedLanguagesConfig.UiLanguage) -> Unit ) { val onboardingItem = listOf( diff --git a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt index 3a9f872b1..90a1d062f 100644 --- a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt +++ b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt @@ -25,7 +25,7 @@ import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import org.jetbrains.compose.resources.stringResource -import world.respect.shared.domain.applanguage.RespectMobileSystemCommon +import world.respect.shared.domain.applanguage.SupportedLanguagesConfig import world.respect.shared.generated.resources.Res import world.respect.shared.generated.resources.language import world.respect.shared.generated.resources.loading @@ -53,7 +53,7 @@ fun SettingsScreen( onNavigateToMapping: () -> Unit = {}, onClickLanguage: () -> Unit = {}, onDismissLangDialog: () -> Unit = {}, - onClickLang: (RespectMobileSystemCommon.UiLanguage) -> Unit = {} + onClickLang: (SupportedLanguagesConfig.UiLanguage) -> Unit = {} ) { if (uiState.langDialogVisible) { diff --git a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/LocaleSettingDelegateAndroid.kt b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/LocaleSettingDelegateAndroid.kt index a849d4079..04ce3ae99 100644 --- a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/LocaleSettingDelegateAndroid.kt +++ b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/LocaleSettingDelegateAndroid.kt @@ -2,13 +2,14 @@ package world.respect.shared.domain.applanguage import androidx.appcompat.app.AppCompatDelegate import androidx.core.os.LocaleListCompat +import world.respect.shared.domain.applanguage.SupportedLanguagesConfig.Companion.LOCALE_USE_SYSTEM class LocaleSettingDelegateAndroid: SupportedLanguagesConfig.LocaleSettingDelegate { override var localeSetting: String? get() = AppCompatDelegate.getApplicationLocales().get(0)?.language ?: "en" set(value) { - val localeList = if(value == RespectMobileSystemCommon.LOCALE_USE_SYSTEM) { + val localeList = if(value == LOCALE_USE_SYSTEM) { LocaleListCompat.getAdjustedDefault() }else { LocaleListCompat.forLanguageTags(value) diff --git a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCaseAndroid.kt b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCaseAndroid.kt index 18c2e9b53..5a59f9d0e 100644 --- a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCaseAndroid.kt +++ b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCaseAndroid.kt @@ -5,7 +5,7 @@ class SetLanguageUseCaseAndroid( ): SetLanguageUseCase { override fun invoke( - uiLang: RespectMobileSystemCommon.UiLanguage, + uiLang: SupportedLanguagesConfig.UiLanguage, ): SetLanguageUseCase.SetLangResult { languagesConfig.localeSetting = uiLang.langCode diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt deleted file mode 100755 index dd79f4b63..000000000 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileSystemCommon.kt +++ /dev/null @@ -1,17 +0,0 @@ -package world.respect.shared.domain.applanguage - -import org.jetbrains.compose.resources.StringResource -import com.russhwolf.settings.Settings - - -abstract class RespectMobileSystemCommon( - private val settings: Settings, - protected val langConfig: SupportedLanguagesConfig, -) { - data class UiLanguage(val langCode: String, val langDisplay: String) - - companion object { - const val LOCALE_USE_SYSTEM = "" - - } -} \ No newline at end of file diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCase.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCase.kt index 9c85ad2ae..a9ecada49 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCase.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SetLanguageUseCase.kt @@ -6,6 +6,6 @@ interface SetLanguageUseCase { ) operator fun invoke( - uiLang: RespectMobileSystemCommon.UiLanguage + uiLang: SupportedLanguagesConfig.UiLanguage ): SetLangResult } diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt index 090f11f87..1730a1e92 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt @@ -9,9 +9,9 @@ import com.russhwolf.settings.Settings import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.jetbrains.compose.resources.getString -import world.respect.shared.domain.applanguage.RespectMobileSystemCommon import world.respect.shared.domain.applanguage.SetLanguageUseCase import world.respect.shared.domain.applanguage.SupportedLanguagesConfig +import world.respect.shared.domain.applanguage.SupportedLanguagesConfig.Companion.LOCALE_USE_SYSTEM import world.respect.shared.domain.navigation.onappstart.NavigateOnAppStartUseCase import world.respect.shared.domain.onboarding.ShouldShowOnboardingUseCase import world.respect.shared.domain.usagereporting.GetUsageReportingEnabledUseCase @@ -22,8 +22,8 @@ import world.respect.shared.generated.resources.default_language data class OnboardingUiState( val isLoading: Boolean = false, val usageStatsOptInChecked: Boolean = true, - val availableLanguages: List = emptyList(), - val selectedLanguage: RespectMobileSystemCommon.UiLanguage? = null, + val availableLanguages: List = emptyList(), + val selectedLanguage: SupportedLanguagesConfig.UiLanguage? = null, ) class OnboardingViewModel( @@ -49,11 +49,20 @@ class OnboardingViewModel( } _uiState.update { it.copy(usageStatsOptInChecked = getUsageReportingEnabledUseCase()) } viewModelScope.launch { - var availableLangs = supportedLangConfig.supportedUiLanguagesAndSysDefault( - getString(Res.string.default_language) + val resolvedSystemLang = supportedLangConfig.selectFirstSupportedLocale() + + val systemDefaultLabel = getString( + Res.string.default_language, + resolvedSystemLang.langDisplay ) - val currentLang = supportedLangConfig.getCurrentUiLanguage(availableLangs) + val availableLangs = supportedLangConfig + .supportedUiLanguagesAndSysDefault(systemDefaultLabel) + val langSetting = supportedLangConfig.localeSetting ?: LOCALE_USE_SYSTEM + + val currentLang = availableLangs.first { + it.langCode == langSetting + } _uiState.update { it.copy( @@ -83,7 +92,7 @@ class OnboardingViewModel( } - fun onLanguageSelected(lang: RespectMobileSystemCommon.UiLanguage) { + fun onLanguageSelected(lang: SupportedLanguagesConfig.UiLanguage) { val result = setLanguageUseCase(uiLang = lang) diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt index 1ae41bc4f..2699fad36 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt @@ -11,9 +11,9 @@ import org.jetbrains.compose.resources.stringResource import org.koin.core.component.KoinScopeComponent import org.koin.core.scope.Scope import world.respect.shared.domain.applanguage.SupportedLanguagesConfig -import world.respect.shared.domain.applanguage.RespectMobileSystemCommon import world.respect.shared.domain.account.RespectAccountManager import world.respect.shared.domain.applanguage.SetLanguageUseCase +import world.respect.shared.domain.applanguage.SupportedLanguagesConfig.Companion.LOCALE_USE_SYSTEM import world.respect.shared.generated.resources.Res import world.respect.shared.generated.resources.default_language import world.respect.shared.generated.resources.settings @@ -27,7 +27,7 @@ data class SettingsUiState( val loading: Boolean = false, val langDialogVisible: Boolean = false, val currentLanguage: String = "", - val availableLanguages: List = emptyList(), + val availableLanguages: List = emptyList(), val waitForRestartDialogVisible: Boolean = false ) @@ -38,7 +38,7 @@ class SettingsViewModel( private val setLanguageUseCase: SetLanguageUseCase, ) : RespectViewModel(savedStateHandle), KoinScopeComponent { - private var availableLangs: List = emptyList() + private var availableLangs: List = emptyList() override val scope: Scope = accountManager.requireActiveAccountScope() @@ -57,13 +57,20 @@ class SettingsViewModel( } viewModelScope.launch { - // 1. Get the list of languages (first item has the unique "system_default_internal" code) - availableLangs = supportedLangConfig.supportedUiLanguagesAndSysDefault( - getString(Res.string.default_language) + val resolvedSystemLang = supportedLangConfig.selectFirstSupportedLocale() + + val systemDefaultLabel = getString( + Res.string.default_language, + resolvedSystemLang.langDisplay ) - // 2. Use the new domain function to find which one to display - val currentLang = supportedLangConfig.getCurrentUiLanguage(availableLangs) + val availableLangs = supportedLangConfig + .supportedUiLanguagesAndSysDefault(systemDefaultLabel) + val langSetting = supportedLangConfig.localeSetting ?: LOCALE_USE_SYSTEM + + val currentLang = availableLangs.first { + it.langCode == langSetting + } _uiState.update { it.copy( @@ -88,7 +95,7 @@ class SettingsViewModel( } } - fun onClickLang(lang: RespectMobileSystemCommon.UiLanguage) { + fun onClickLang(lang: SupportedLanguagesConfig.UiLanguage) { _uiState.update { prev -> prev.copy(langDialogVisible = false) } From 17ba4257ef72cabd9af71faf363b9d7140ebb3fd Mon Sep 17 00:00:00 2001 From: mandviverma Date: Thu, 12 Feb 2026 20:52:43 +0530 Subject: [PATCH 16/30] Refactor: Improve system language handling and UI --- .../kotlin/world/respect/AppKoinModule.kt | 11 ++-- .../app/components/LanguageDropdown.kt | 13 ++++- .../LocaleSettingDelegateAndroid.kt | 12 ++-- .../composeResources/values-hi/strings.xml | 4 ++ .../composeResources/values-ne/strings.xml | 6 +- .../composeResources/values/strings.xml | 2 +- .../onboarding/OnboardingViewModel.kt | 58 +++++++++---------- 7 files changed, 59 insertions(+), 47 deletions(-) diff --git a/respect-app-compose/src/androidMain/kotlin/world/respect/AppKoinModule.kt b/respect-app-compose/src/androidMain/kotlin/world/respect/AppKoinModule.kt index fd99bdfd2..9164ace89 100644 --- a/respect-app-compose/src/androidMain/kotlin/world/respect/AppKoinModule.kt +++ b/respect-app-compose/src/androidMain/kotlin/world/respect/AppKoinModule.kt @@ -2,6 +2,7 @@ package world.respect import android.content.Context +import androidx.core.os.LocaleListCompat import androidx.room.Room import androidx.sqlite.driver.bundled.BundledSQLiteDriver import com.russhwolf.settings.Settings @@ -281,15 +282,11 @@ val appKoinModule = module { LaunchSendEmailAndroid(androidContext()) } - single> { - androidx.core.os.ConfigurationCompat.getLocales( - androidContext().resources.configuration - ).toLanguageTags().split(",") - } - single { SupportedLanguagesConfig( - systemLocales = get(), + systemLocales = LocaleListCompat.getAdjustedDefault().let { localeList -> + (0 .. localeList.size()).mapNotNull { localeList[it]?.language } + }, localeSettingDelegate = LocaleSettingDelegateAndroid() ) } diff --git a/respect-app-compose/src/commonMain/kotlin/world/respect/app/components/LanguageDropdown.kt b/respect-app-compose/src/commonMain/kotlin/world/respect/app/components/LanguageDropdown.kt index b22e36d78..60997cc03 100644 --- a/respect-app-compose/src/commonMain/kotlin/world/respect/app/components/LanguageDropdown.kt +++ b/respect-app-compose/src/commonMain/kotlin/world/respect/app/components/LanguageDropdown.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.Modifier import org.jetbrains.compose.resources.stringResource import world.respect.shared.domain.applanguage.SupportedLanguagesConfig import world.respect.shared.generated.resources.Res +import world.respect.shared.generated.resources.default_language @OptIn(ExperimentalMaterial3Api::class) @@ -35,7 +36,7 @@ fun LanguageDropdown( onExpandedChange = { if (enabled) expanded = !expanded } ) { OutlinedTextField( - value = selected?.langDisplay ?: "", + value = getLanguageDisplayText(selected), onValueChange = {}, readOnly = true, label = { Text(stringResource(Res.string.language)) }, @@ -54,7 +55,7 @@ fun LanguageDropdown( ) { languages.forEach { lang -> DropdownMenuItem( - text = { Text(lang.langDisplay) }, + text = { Text(getLanguageDisplayText(lang) ) }, onClick = { onSelected(lang) expanded = false @@ -65,4 +66,12 @@ fun LanguageDropdown( } } +@Composable +fun getLanguageDisplayText(lang: SupportedLanguagesConfig.UiLanguage?): String { + return if(lang?.langCode?.isEmpty() == true) + stringResource(Res.string.default_language, lang.langDisplay) + else + lang?.langDisplay ?: "" +} + diff --git a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/LocaleSettingDelegateAndroid.kt b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/LocaleSettingDelegateAndroid.kt index 04ce3ae99..43629c862 100644 --- a/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/LocaleSettingDelegateAndroid.kt +++ b/respect-lib-shared/src/androidMain/kotlin/world/respect/shared/domain/applanguage/LocaleSettingDelegateAndroid.kt @@ -7,11 +7,15 @@ import world.respect.shared.domain.applanguage.SupportedLanguagesConfig.Companio class LocaleSettingDelegateAndroid: SupportedLanguagesConfig.LocaleSettingDelegate { override var localeSetting: String? - get() = AppCompatDelegate.getApplicationLocales().get(0)?.language ?: "en" + get() = if (AppCompatDelegate.getApplicationLocales().isEmpty) { + null + } else { + AppCompatDelegate.getApplicationLocales().get(0)?.language + } set(value) { - val localeList = if(value == LOCALE_USE_SYSTEM) { - LocaleListCompat.getAdjustedDefault() - }else { + val localeList = if (value == LOCALE_USE_SYSTEM || value == null) { + LocaleListCompat.getEmptyLocaleList() + } else { LocaleListCompat.forLanguageTags(value) } AppCompatDelegate.setApplicationLocales(localeList) diff --git a/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml b/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml index 70b38a581..0eda0a233 100644 --- a/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml +++ b/respect-lib-shared/src/commonMain/composeResources/values-hi/strings.xml @@ -10,5 +10,9 @@ सेटिंग्स मानचित्रण ऐप्स + सिस्टम डिफॉल्ट का उपयोग करें (%1$s) + आएँ शुरू करें + अन्य विकल्प + अगला \ No newline at end of file diff --git a/respect-lib-shared/src/commonMain/composeResources/values-ne/strings.xml b/respect-lib-shared/src/commonMain/composeResources/values-ne/strings.xml index 050e26a49..7bae63f7b 100644 --- a/respect-lib-shared/src/commonMain/composeResources/values-ne/strings.xml +++ b/respect-lib-shared/src/commonMain/composeResources/values-ne/strings.xml @@ -4,13 +4,15 @@ एप भाषा तपाईंले विद्यालय सफलतापूर्वक दर्ता गर्नुभयो - तपाईंले अझै आफ्नो विद्यालयको लागि कुनै एप थप्नुभएको छैन। लग-इन गर्नुहोस् सुरु गर्नुहोस् सेटिङहरू म्यापिङ एपहरू - प्रणालीको पूर्वनिर्धारित प्रयोग गर्नुहोस् (%1$s) + सुरु गरौं + अन्य विकल्प + अर्को + \ No newline at end of file diff --git a/respect-lib-shared/src/commonMain/composeResources/values/strings.xml b/respect-lib-shared/src/commonMain/composeResources/values/strings.xml index 5e0b38596..c9d2fa360 100644 --- a/respect-lib-shared/src/commonMain/composeResources/values/strings.xml +++ b/respect-lib-shared/src/commonMain/composeResources/values/strings.xml @@ -426,7 +426,7 @@ Select person Edit person - Use system default + Use system default (%1$s) First names Mappings Mapping diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt index 1730a1e92..65d710ae6 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt @@ -16,15 +16,12 @@ import world.respect.shared.domain.navigation.onappstart.NavigateOnAppStartUseCa import world.respect.shared.domain.onboarding.ShouldShowOnboardingUseCase import world.respect.shared.domain.usagereporting.GetUsageReportingEnabledUseCase import world.respect.shared.domain.usagereporting.SetUsageReportingEnabledUseCase -import world.respect.shared.generated.resources.Res -import world.respect.shared.generated.resources.default_language data class OnboardingUiState( val isLoading: Boolean = false, val usageStatsOptInChecked: Boolean = true, val availableLanguages: List = emptyList(), - val selectedLanguage: SupportedLanguagesConfig.UiLanguage? = null, -) + val selectedLanguage: SupportedLanguagesConfig.UiLanguage? = null) class OnboardingViewModel( savedStateHandle: SavedStateHandle, @@ -48,29 +45,7 @@ class OnboardingViewModel( ) } _uiState.update { it.copy(usageStatsOptInChecked = getUsageReportingEnabledUseCase()) } - viewModelScope.launch { - val resolvedSystemLang = supportedLangConfig.selectFirstSupportedLocale() - - val systemDefaultLabel = getString( - Res.string.default_language, - resolvedSystemLang.langDisplay - ) - - val availableLangs = supportedLangConfig - .supportedUiLanguagesAndSysDefault(systemDefaultLabel) - val langSetting = supportedLangConfig.localeSetting ?: LOCALE_USE_SYSTEM - - val currentLang = availableLangs.first { - it.langCode == langSetting - } - - _uiState.update { - it.copy( - availableLanguages = availableLangs, - selectedLanguage = currentLang - ) - } - } + refreshSystemDefaultLabel() } fun onToggleUsageStatsOptIn() { @@ -93,16 +68,37 @@ class OnboardingViewModel( } fun onLanguageSelected(lang: SupportedLanguagesConfig.UiLanguage) { + viewModelScope.launch { + setLanguageUseCase(uiLang = lang) - val result = setLanguageUseCase(uiLang = lang) + refreshSystemDefaultLabel() - if (result.waitForRestart) { _uiState.update { it.copy(selectedLanguage = lang) } - } else { + } + } + + fun refreshSystemDefaultLabel() { + viewModelScope.launch { + + val resolvedSystemLang = supportedLangConfig.selectFirstSupportedLocale() + + val systemDefaultLabel = resolvedSystemLang.langDisplay + + val availableLangs = supportedLangConfig + .supportedUiLanguagesAndSysDefault(systemDefaultLabel) + + val langSetting = supportedLangConfig.localeSetting ?: LOCALE_USE_SYSTEM + + val currentLang = availableLangs.firstOrNull { it.langCode == langSetting } + ?: availableLangs.first() + _uiState.update { - it.copy(selectedLanguage = lang) + it.copy( + availableLanguages = availableLangs, + selectedLanguage = currentLang + ) } } } From 343351220e5a558d2e660cdbb9053513e9393b52 Mon Sep 17 00:00:00 2001 From: mandviverma Date: Thu, 12 Feb 2026 20:59:03 +0530 Subject: [PATCH 17/30] Refactor: Improve system language handling and UI --- .../{RespectMobileConstants.kt => SupportedLanguageNames.kt} | 2 +- .../shared/domain/applanguage/SupportedLanguagesConfig.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/{RespectMobileConstants.kt => SupportedLanguageNames.kt} (93%) diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileConstants.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguageNames.kt similarity index 93% rename from respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileConstants.kt rename to respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguageNames.kt index 442721fe0..a56301397 100755 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/RespectMobileConstants.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguageNames.kt @@ -1,7 +1,7 @@ package world.respect.shared.domain.applanguage -object RespectMobileConstants { +object SupportedLanguageNames { val LANGUAGE_NAMES = mapOf( "en" to "English", diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt index 62d615ce2..ed1afa29c 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt @@ -53,7 +53,7 @@ class SupportedLanguagesConfig( .split(",") .sorted() .map { - UiLanguage(it, (RespectMobileConstants.LANGUAGE_NAMES[it] ?: it)) + UiLanguage(it, (SupportedLanguageNames.LANGUAGE_NAMES[it] ?: it)) } private val supportedLangMap: Map = supportedUiLanguages From 6b21dc22ee5b105c140bb74647f9d4789ff564ae Mon Sep 17 00:00:00 2001 From: mandviverma Date: Thu, 12 Feb 2026 21:14:46 +0530 Subject: [PATCH 18/30] Refactor: Centralize language loading logic in `SupportedLanguagesConfig` --- .../app/view/settings/SettingsScreen.kt | 2 -- .../applanguage/SupportedLanguagesConfig.kt | 9 ++++++-- .../onboarding/OnboardingViewModel.kt | 23 ++++++++----------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt index 90a1d062f..de04e6d16 100644 --- a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt +++ b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt @@ -60,7 +60,6 @@ fun SettingsScreen( SettingsDialog( onDismissRequest = onDismissLangDialog, ) { - uiState.availableLanguages.forEach { lang -> ListItem( modifier = Modifier.clickable { onClickLang(lang) }, @@ -86,7 +85,6 @@ fun SettingsScreen( item { ListItem( - headlineContent = { Text(text = stringResource(Res.string.language)) }, diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt index ed1afa29c..91c765ed5 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/domain/applanguage/SupportedLanguagesConfig.kt @@ -118,11 +118,16 @@ class SupportedLanguagesConfig( }?.let { supportedLangMap[it.substring(0, 2)]!! } ?: supportedLangMap[fallbackLocaleCode]!! } - companion object { + fun getAvailableLanguages(): List { + val resolvedSystemLang = selectFirstSupportedLocale() + val systemDefaultLabel = resolvedSystemLang.langDisplay - const val PREFKEY_LOCALE = "locale" + return supportedUiLanguagesAndSysDefault(systemDefaultLabel) + } + companion object { + const val PREFKEY_LOCALE = "locale" const val DEFAULT_SUPPORTED_LANGUAGES = "en,hi,fa,ps,ar,tg,bn,ne,my,rw,ru" const val LOCALE_USE_SYSTEM = "" diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt index 65d710ae6..1339627b4 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/onboarding/OnboardingViewModel.kt @@ -8,7 +8,6 @@ import world.respect.shared.viewmodel.RespectViewModel import com.russhwolf.settings.Settings import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import org.jetbrains.compose.resources.getString import world.respect.shared.domain.applanguage.SetLanguageUseCase import world.respect.shared.domain.applanguage.SupportedLanguagesConfig import world.respect.shared.domain.applanguage.SupportedLanguagesConfig.Companion.LOCALE_USE_SYSTEM @@ -21,7 +20,8 @@ data class OnboardingUiState( val isLoading: Boolean = false, val usageStatsOptInChecked: Boolean = true, val availableLanguages: List = emptyList(), - val selectedLanguage: SupportedLanguagesConfig.UiLanguage? = null) + val selectedLanguage: SupportedLanguagesConfig.UiLanguage? = null +) class OnboardingViewModel( savedStateHandle: SavedStateHandle, @@ -45,7 +45,7 @@ class OnboardingViewModel( ) } _uiState.update { it.copy(usageStatsOptInChecked = getUsageReportingEnabledUseCase()) } - refreshSystemDefaultLabel() + loadLanguages() } fun onToggleUsageStatsOptIn() { @@ -71,7 +71,7 @@ class OnboardingViewModel( viewModelScope.launch { setLanguageUseCase(uiLang = lang) - refreshSystemDefaultLabel() + loadLanguages() _uiState.update { it.copy(selectedLanguage = lang) @@ -79,20 +79,16 @@ class OnboardingViewModel( } } - fun refreshSystemDefaultLabel() { + fun loadLanguages() { viewModelScope.launch { - val resolvedSystemLang = supportedLangConfig.selectFirstSupportedLocale() - - val systemDefaultLabel = resolvedSystemLang.langDisplay - - val availableLangs = supportedLangConfig - .supportedUiLanguagesAndSysDefault(systemDefaultLabel) + val availableLangs = supportedLangConfig.getAvailableLanguages() val langSetting = supportedLangConfig.localeSetting ?: LOCALE_USE_SYSTEM - val currentLang = availableLangs.firstOrNull { it.langCode == langSetting } - ?: availableLangs.first() + val currentLang = availableLangs.firstOrNull { + it.langCode == langSetting + } ?: availableLangs.first() _uiState.update { it.copy( @@ -100,6 +96,7 @@ class OnboardingViewModel( selectedLanguage = currentLang ) } + } } } \ No newline at end of file From 523798dab0659373fe80a69d953f467a1b549eb0 Mon Sep 17 00:00:00 2001 From: mandviverma Date: Thu, 12 Feb 2026 21:58:17 +0530 Subject: [PATCH 19/30] Simplify language loading logic in `SettingsViewModel` --- .../app/view/settings/SettingsScreen.kt | 18 ++++- .../accountlist/AccountListViewModel.kt | 9 +++ .../viewmodel/settings/SettingsViewModel.kt | 74 ++++++++----------- 3 files changed, 55 insertions(+), 46 deletions(-) diff --git a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt index de04e6d16..2b02b169a 100644 --- a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt +++ b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.window.Dialog import org.jetbrains.compose.resources.stringResource import world.respect.shared.domain.applanguage.SupportedLanguagesConfig import world.respect.shared.generated.resources.Res +import world.respect.shared.generated.resources.default_language import world.respect.shared.generated.resources.language import world.respect.shared.generated.resources.loading import world.respect.shared.generated.resources.mappings @@ -63,7 +64,13 @@ fun SettingsScreen( uiState.availableLanguages.forEach { lang -> ListItem( modifier = Modifier.clickable { onClickLang(lang) }, - headlineContent = { Text(lang.langDisplay) } + headlineContent = { + if (lang.langCode.isEmpty()) { + Text(stringResource(Res.string.default_language, lang.langDisplay)) + } else { + Text(lang.langDisplay) + } + } ) } } @@ -95,7 +102,14 @@ fun SettingsScreen( ) }, supportingContent = { - Text(text = uiState.currentLanguage) + uiState.currentLanguage?.let { lang -> + if (lang.langCode.isEmpty()) { + Text(stringResource(Res.string.default_language, lang.langDisplay)) + } else { + Text(lang.langDisplay) + } + } + }, modifier = Modifier .fillMaxWidth() diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/manageuser/accountlist/AccountListViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/manageuser/accountlist/AccountListViewModel.kt index 57a0212d9..822f09c20 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/manageuser/accountlist/AccountListViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/manageuser/accountlist/AccountListViewModel.kt @@ -27,6 +27,7 @@ import world.respect.shared.navigation.GetStartedScreen import world.respect.shared.navigation.NavCommand import world.respect.shared.navigation.PersonDetail import world.respect.shared.navigation.RespectAppLauncher +import world.respect.shared.navigation.Settings import world.respect.shared.navigation.WaitingForApproval import world.respect.shared.util.ext.asUiText import world.respect.shared.util.ext.isSameAccount @@ -63,6 +64,9 @@ class AccountListViewModel( init { _appUiState.update { it.copy( + settingsIconVisible = true, + onClickSettings = ::onClickSettings, + title = Res.string.accounts.asUiText(), hideBottomNavigation = true, userAccountIconVisible = false, @@ -221,5 +225,10 @@ class AccountListViewModel( } } } + fun onClickSettings() { + _navCommandFlow.tryEmit( + NavCommand.Navigate(Settings) + ) + } } \ No newline at end of file diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt index 2699fad36..c8f004ee8 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt @@ -6,8 +6,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import org.jetbrains.compose.resources.getString -import org.jetbrains.compose.resources.stringResource import org.koin.core.component.KoinScopeComponent import org.koin.core.scope.Scope import world.respect.shared.domain.applanguage.SupportedLanguagesConfig @@ -15,7 +13,6 @@ import world.respect.shared.domain.account.RespectAccountManager import world.respect.shared.domain.applanguage.SetLanguageUseCase import world.respect.shared.domain.applanguage.SupportedLanguagesConfig.Companion.LOCALE_USE_SYSTEM import world.respect.shared.generated.resources.Res -import world.respect.shared.generated.resources.default_language import world.respect.shared.generated.resources.settings import world.respect.shared.navigation.CurriculumMappingList import world.respect.shared.navigation.NavCommand @@ -26,7 +23,8 @@ import world.respect.shared.viewmodel.RespectViewModel data class SettingsUiState( val loading: Boolean = false, val langDialogVisible: Boolean = false, - val currentLanguage: String = "", + val currentLanguage: SupportedLanguagesConfig.UiLanguage? = null, + val availableLanguages: List = emptyList(), val waitForRestartDialogVisible: Boolean = false ) @@ -34,7 +32,7 @@ data class SettingsUiState( class SettingsViewModel( savedStateHandle: SavedStateHandle, accountManager: RespectAccountManager, - supportedLangConfig: SupportedLanguagesConfig, + private val supportedLangConfig: SupportedLanguagesConfig, private val setLanguageUseCase: SetLanguageUseCase, ) : RespectViewModel(savedStateHandle), KoinScopeComponent { @@ -56,29 +54,7 @@ class SettingsViewModel( ) } - viewModelScope.launch { - val resolvedSystemLang = supportedLangConfig.selectFirstSupportedLocale() - - val systemDefaultLabel = getString( - Res.string.default_language, - resolvedSystemLang.langDisplay - ) - - val availableLangs = supportedLangConfig - .supportedUiLanguagesAndSysDefault(systemDefaultLabel) - val langSetting = supportedLangConfig.localeSetting ?: LOCALE_USE_SYSTEM - - val currentLang = availableLangs.first { - it.langCode == langSetting - } - - _uiState.update { - it.copy( - availableLanguages = availableLangs, - currentLanguage = currentLang.langDisplay - ) - } - } + loadLanguages() } fun onClickLanguage() { @@ -100,25 +76,12 @@ class SettingsViewModel( prev.copy(langDialogVisible = false) } - val result = setLanguageUseCase( + setLanguageUseCase( uiLang = lang, ) - println("Result language $result $lang") - if (result.waitForRestart) { - _uiState.update { prev -> - prev.copy( - waitForRestartDialogVisible = true, - currentLanguage = lang.langDisplay - ) - } - } else { - _uiState.update { prev -> - prev.copy( - currentLanguage = lang.langDisplay - ) - } - } + loadLanguages() + } fun onNavigateToMapping() { @@ -126,4 +89,27 @@ class SettingsViewModel( NavCommand.Navigate(CurriculumMappingList) ) } + + fun loadLanguages() { + viewModelScope.launch { + + val availableLangs = supportedLangConfig.getAvailableLanguages() + + val langSetting = supportedLangConfig.localeSetting ?: LOCALE_USE_SYSTEM + + val currentLang = availableLangs.firstOrNull { + it.langCode == langSetting + } ?: availableLangs.first() + + _uiState.update { + it.copy( + availableLanguages = availableLangs, + currentLanguage = currentLang + + ) + } + + } + } + } From a0a0f2e599de9885d19c5a3c34122e645088d87f Mon Sep 17 00:00:00 2001 From: mandviverma Date: Thu, 12 Feb 2026 23:17:39 +0530 Subject: [PATCH 20/30] Simplify language loading logic in `SettingsViewModel` --- .../respect/shared/viewmodel/settings/SettingsViewModel.kt | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt index c8f004ee8..9d3380dd6 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt @@ -76,12 +76,9 @@ class SettingsViewModel( prev.copy(langDialogVisible = false) } - setLanguageUseCase( - uiLang = lang, - ) + setLanguageUseCase(uiLang = lang) loadLanguages() - } fun onNavigateToMapping() { @@ -108,8 +105,6 @@ class SettingsViewModel( ) } - } } - } From c5ae787bb399d8551277d9412f978cb3cbe658a6 Mon Sep 17 00:00:00 2001 From: mandviverma Date: Fri, 13 Feb 2026 09:36:14 +0530 Subject: [PATCH 21/30] Simplify language loading logic in `SettingsViewModel` --- .../manageuser/accountlist/AccountListViewModel.kt | 9 --------- 1 file changed, 9 deletions(-) diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/manageuser/accountlist/AccountListViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/manageuser/accountlist/AccountListViewModel.kt index 822f09c20..57a0212d9 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/manageuser/accountlist/AccountListViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/manageuser/accountlist/AccountListViewModel.kt @@ -27,7 +27,6 @@ import world.respect.shared.navigation.GetStartedScreen import world.respect.shared.navigation.NavCommand import world.respect.shared.navigation.PersonDetail import world.respect.shared.navigation.RespectAppLauncher -import world.respect.shared.navigation.Settings import world.respect.shared.navigation.WaitingForApproval import world.respect.shared.util.ext.asUiText import world.respect.shared.util.ext.isSameAccount @@ -64,9 +63,6 @@ class AccountListViewModel( init { _appUiState.update { it.copy( - settingsIconVisible = true, - onClickSettings = ::onClickSettings, - title = Res.string.accounts.asUiText(), hideBottomNavigation = true, userAccountIconVisible = false, @@ -225,10 +221,5 @@ class AccountListViewModel( } } } - fun onClickSettings() { - _navCommandFlow.tryEmit( - NavCommand.Navigate(Settings) - ) - } } \ No newline at end of file From 0e4f3b6b2449dc4e6528dd6b997bf6ac6fe93bb5 Mon Sep 17 00:00:00 2001 From: pooja Date: Mon, 16 Feb 2026 17:02:43 +0400 Subject: [PATCH 22/30] added language test --- .../004_user_set_language_on_app_test.yaml | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .maestro/flows/004_user_set_language_on_app_test.yaml diff --git a/.maestro/flows/004_user_set_language_on_app_test.yaml b/.maestro/flows/004_user_set_language_on_app_test.yaml new file mode 100644 index 000000000..71da86afe --- /dev/null +++ b/.maestro/flows/004_user_set_language_on_app_test.yaml @@ -0,0 +1,60 @@ +appId: world.respect.app +onFlowStart: + - clearState: world.respect.app + - runScript: + file: "scripts/school_init.js" + env: + TESTCONTROLLER_URL: ${TESTCONTROLLER_URL} + SCHOOL_ADMIN_PASSWORD: ${SCHOOL_ADMIN_PASSWORD} + DIR_ADMIN_AUTH_HEADER: ${DIR_ADMIN_AUTH_HEADER} + SCHOOL_URL: ${SCHOOL_URL} + SCHOOL_NAME: ${SCHOOL_NAME} + URL_SUBSTITUTION: ${URL_SUBSTITUTION} + NAME: "004_user_set_language_on_app_test" + +onFlowComplete: + - runScript: + file: "scripts/teardown.js" +--- +- clearState: world.respect.app +- launchApp: + arguments: + respect_directory: ${output.SCHOOL_URL} +- tapOn: "Choose your language" +- assertVisible: "Use system default (English)" +- assertVisible: "हिन्दी" +- tapOn: "English" +- tapOn: "Get Started" +- runFlow: + file: "subflows/get_started_select_school_by_name.yaml" + env: + SCHOOL_NAME: ${SCHOOL_NAME} +- tapOn: + id: "username" +- inputText: "admin" +- tapOn: + id : "password" +- inputText: "test123" +- tapOn: "Login" +- assertVisible: + id: "app_title" + text: "Apps" +- tapOn: + id: "settings_icon" +- assertVisible: + id: "app_title" + text: "Settings" +- assertVisible: "English" +- tapOn: "Language" +- assertVisible: "Use system default (English)" +- assertVisible: "English" +- tapOn: "हिन्दी" +- assertVisible: + id: "app_title" + text: "सेटिंग्स" +- assertVisible: "भाषा" +- tapOn: "English" +- assertVisible: + id: "app_title" + text: "Settings" + From 65542d731ef772a4c15aebdc775160e5a2805099 Mon Sep 17 00:00:00 2001 From: pooja Date: Tue, 17 Feb 2026 09:34:53 +0400 Subject: [PATCH 23/30] added id --- .maestro/flows/004_user_set_language_on_app_test.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.maestro/flows/004_user_set_language_on_app_test.yaml b/.maestro/flows/004_user_set_language_on_app_test.yaml index 71da86afe..bd810083d 100644 --- a/.maestro/flows/004_user_set_language_on_app_test.yaml +++ b/.maestro/flows/004_user_set_language_on_app_test.yaml @@ -20,9 +20,11 @@ onFlowComplete: - launchApp: arguments: respect_directory: ${output.SCHOOL_URL} -- tapOn: "Choose your language" +- tapOn: + id: "choose_language" - assertVisible: "Use system default (English)" - assertVisible: "हिन्दी" +- assertVisible: "Choose your language" - tapOn: "English" - tapOn: "Get Started" - runFlow: From 66c5153e1d9fadb485f2eb3c0a2ee326ebf40a5f Mon Sep 17 00:00:00 2001 From: mandviverma Date: Tue, 17 Feb 2026 12:28:36 +0530 Subject: [PATCH 24/30] Refactor: Improve language dropdown UI and testability This commit enhances the `LanguageDropdown` component with the following changes: * **UI Text:** Replaces the generic "Language" label with the more descriptive "Choose your language" to improve user experience. * **Testability:** Adds a `testTag("choose_language")` to the `ExposedDropdownMenuBox` to make the component easier to find and interact with in UI tests. * **Cleanup:** Removes an unused string resource import (`Res.string.language`). --- .../world/respect/app/components/LanguageDropdown.kt | 8 +++++--- .../src/commonMain/composeResources/values/strings.xml | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/respect-app-compose/src/commonMain/kotlin/world/respect/app/components/LanguageDropdown.kt b/respect-app-compose/src/commonMain/kotlin/world/respect/app/components/LanguageDropdown.kt index 60997cc03..7921b7911 100644 --- a/respect-app-compose/src/commonMain/kotlin/world/respect/app/components/LanguageDropdown.kt +++ b/respect-app-compose/src/commonMain/kotlin/world/respect/app/components/LanguageDropdown.kt @@ -6,7 +6,6 @@ import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuDefaults -import world.respect.shared.generated.resources.language import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -15,9 +14,11 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import org.jetbrains.compose.resources.stringResource import world.respect.shared.domain.applanguage.SupportedLanguagesConfig import world.respect.shared.generated.resources.Res +import world.respect.shared.generated.resources.choose_language import world.respect.shared.generated.resources.default_language @@ -33,13 +34,14 @@ fun LanguageDropdown( ExposedDropdownMenuBox( expanded = expanded, - onExpandedChange = { if (enabled) expanded = !expanded } + onExpandedChange = { if (enabled) expanded = !expanded }, + modifier = Modifier.testTag("choose_language") ) { OutlinedTextField( value = getLanguageDisplayText(selected), onValueChange = {}, readOnly = true, - label = { Text(stringResource(Res.string.language)) }, + label = { Text(stringResource(Res.string.choose_language)) }, trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded) }, diff --git a/respect-lib-shared/src/commonMain/composeResources/values/strings.xml b/respect-lib-shared/src/commonMain/composeResources/values/strings.xml index c9d2fa360..4aa08ecc4 100644 --- a/respect-lib-shared/src/commonMain/composeResources/values/strings.xml +++ b/respect-lib-shared/src/commonMain/composeResources/values/strings.xml @@ -427,6 +427,8 @@ Edit person Use system default (%1$s) + Choose your language + First names Mappings Mapping From d8dd5756746afc32b7a2516898ad338647cd9ba2 Mon Sep 17 00:00:00 2001 From: pooja Date: Tue, 17 Feb 2026 10:58:56 +0400 Subject: [PATCH 25/30] updated id --- .maestro/flows/004_user_set_language_on_app_test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.maestro/flows/004_user_set_language_on_app_test.yaml b/.maestro/flows/004_user_set_language_on_app_test.yaml index bd810083d..f28b594c1 100644 --- a/.maestro/flows/004_user_set_language_on_app_test.yaml +++ b/.maestro/flows/004_user_set_language_on_app_test.yaml @@ -42,7 +42,7 @@ onFlowComplete: id: "app_title" text: "Apps" - tapOn: - id: "settings_icon" + id: "Settings" - assertVisible: id: "app_title" text: "Settings" From 63503350c0e344274d0e79142c2dd721aec9b2b4 Mon Sep 17 00:00:00 2001 From: pooja Date: Tue, 17 Feb 2026 12:53:19 +0400 Subject: [PATCH 26/30] updated test - --- .maestro/flows/004_user_set_language_on_app_test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.maestro/flows/004_user_set_language_on_app_test.yaml b/.maestro/flows/004_user_set_language_on_app_test.yaml index f28b594c1..6cdbc3f35 100644 --- a/.maestro/flows/004_user_set_language_on_app_test.yaml +++ b/.maestro/flows/004_user_set_language_on_app_test.yaml @@ -54,7 +54,7 @@ onFlowComplete: - assertVisible: id: "app_title" text: "सेटिंग्स" -- assertVisible: "भाषा" +- tapOn: "भाषा" - tapOn: "English" - assertVisible: id: "app_title" From 31d20ebc2c14a21dcabd2c7a11ddde3e71992fb6 Mon Sep 17 00:00:00 2001 From: pooja Date: Wed, 18 Feb 2026 10:36:44 +0400 Subject: [PATCH 27/30] updated test - password --- .maestro/flows/004_user_set_language_on_app_test.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.maestro/flows/004_user_set_language_on_app_test.yaml b/.maestro/flows/004_user_set_language_on_app_test.yaml index 6cdbc3f35..1bc071379 100644 --- a/.maestro/flows/004_user_set_language_on_app_test.yaml +++ b/.maestro/flows/004_user_set_language_on_app_test.yaml @@ -22,10 +22,11 @@ onFlowComplete: respect_directory: ${output.SCHOOL_URL} - tapOn: id: "choose_language" +- assertVisible: "Choose your language" - assertVisible: "Use system default (English)" - assertVisible: "हिन्दी" -- assertVisible: "Choose your language" - tapOn: "English" +- assertVisible: "English" - tapOn: "Get Started" - runFlow: file: "subflows/get_started_select_school_by_name.yaml" @@ -36,7 +37,7 @@ onFlowComplete: - inputText: "admin" - tapOn: id : "password" -- inputText: "test123" +- inputText: ${SCHOOL_ADMIN_PASSWORD} - tapOn: "Login" - assertVisible: id: "app_title" From 879a2f352a0c904ad352b1e4d0d19642fd84efb3 Mon Sep 17 00:00:00 2001 From: mandviverma Date: Wed, 18 Feb 2026 12:18:41 +0530 Subject: [PATCH 28/30] The settings icon in the app launcher is now always visible for all users, regardless of their admin status or developer mode settings. RemoveD Mappings button from Settings --- .../world/respect/app/view/settings/SettingsScreen.kt | 11 ----------- .../viewmodel/apps/launcher/AppLauncherViewModel.kt | 3 +-- .../shared/viewmodel/settings/SettingsViewModel.kt | 9 +-------- 3 files changed, 2 insertions(+), 21 deletions(-) diff --git a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt index 2b02b169a..8d925a154 100644 --- a/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt +++ b/respect-app-compose/src/commonMain/kotlin/world/respect/app/view/settings/SettingsScreen.kt @@ -30,7 +30,6 @@ import world.respect.shared.generated.resources.Res import world.respect.shared.generated.resources.default_language import world.respect.shared.generated.resources.language import world.respect.shared.generated.resources.loading -import world.respect.shared.generated.resources.mappings import world.respect.shared.viewmodel.settings.SettingsUiState import world.respect.shared.viewmodel.settings.SettingsViewModel @@ -41,7 +40,6 @@ fun SettingsScreen( val uiState by viewModel.uiState.collectAsState() SettingsScreen( uiState = uiState, - onNavigateToMapping = viewModel::onNavigateToMapping, onClickLang = viewModel::onClickLang, onClickLanguage = viewModel::onClickLanguage, onDismissLangDialog = viewModel::onDismissLangDialog @@ -51,7 +49,6 @@ fun SettingsScreen( @Composable fun SettingsScreen( uiState: SettingsUiState, - onNavigateToMapping: () -> Unit = {}, onClickLanguage: () -> Unit = {}, onDismissLangDialog: () -> Unit = {}, onClickLang: (SupportedLanguagesConfig.UiLanguage) -> Unit = {} @@ -81,14 +78,6 @@ fun SettingsScreen( .fillMaxSize() .padding(vertical = 8.dp), ) { - item { - SettingsListItem( - icon = Icons.Filled.Map, - title = stringResource(Res.string.mappings), - onClick = onNavigateToMapping, - testTag = "mapping_setting_item" - ) - } item { ListItem( diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/apps/launcher/AppLauncherViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/apps/launcher/AppLauncherViewModel.kt index 8a27782c0..dce90c30b 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/apps/launcher/AppLauncherViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/apps/launcher/AppLauncherViewModel.kt @@ -81,6 +81,7 @@ class AppLauncherViewModel( _appUiState.update { it.copy( title = Res.string.apps.asUiText(), + settingsIconVisible =true, onClickSettings = ::onClickSettings, fabState = FabUiState( icon = FabUiState.FabIcon.ADD, @@ -109,13 +110,11 @@ class AppLauncherViewModel( viewModelScope.launch { accountManager.selectedAccountAndPersonFlow.collect { selected -> val isAdmin = selected?.person?.isAdmin() == true - val devModeEnabled = getDevModeEnabledUseCase() _appUiState.update { it.copy( fabState = it.fabState.copy( visible = isAdmin ), - settingsIconVisible = isAdmin && devModeEnabled, ) } _uiState.update { diff --git a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt index 9d3380dd6..65cd92009 100644 --- a/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt +++ b/respect-lib-shared/src/commonMain/kotlin/world/respect/shared/viewmodel/settings/SettingsViewModel.kt @@ -36,11 +36,10 @@ class SettingsViewModel( private val setLanguageUseCase: SetLanguageUseCase, ) : RespectViewModel(savedStateHandle), KoinScopeComponent { - private var availableLangs: List = emptyList() - override val scope: Scope = accountManager.requireActiveAccountScope() private val _uiState = MutableStateFlow(SettingsUiState()) + val uiState = _uiState.asStateFlow() init { @@ -81,12 +80,6 @@ class SettingsViewModel( loadLanguages() } - fun onNavigateToMapping() { - _navCommandFlow.tryEmit( - NavCommand.Navigate(CurriculumMappingList) - ) - } - fun loadLanguages() { viewModelScope.launch { From 79539a1d4713781fc63d009ef57c15f124711259 Mon Sep 17 00:00:00 2001 From: pooja Date: Thu, 19 Feb 2026 15:36:41 +0400 Subject: [PATCH 29/30] updated description --- ...> 007_user_signup_using_passkey_test.yaml} | 2 +- ...06_001_user_set_language_on_app_test.yaml} | 2 +- respect-test-end-to-end/README.md | 11 +++++- .../006_001_user_set_language_on_app_test.md | 37 +++++++++++++++++++ ..._signup_using_passkey_test_description.md} | 0 5 files changed, 48 insertions(+), 4 deletions(-) rename .maestro/flow-passkey/{006_user_signup_using_passkey_test.yaml => 007_user_signup_using_passkey_test.yaml} (98%) rename .maestro/flows/{004_user_set_language_on_app_test.yaml => 006_001_user_set_language_on_app_test.yaml} (96%) create mode 100644 respect-test-end-to-end/test-description/006_001_user_set_language_on_app_test.md rename respect-test-end-to-end/test-description/{006_user_signup_using_passkey_test_description.md => 007_001_user_signup_using_passkey_test_description.md} (100%) diff --git a/.maestro/flow-passkey/006_user_signup_using_passkey_test.yaml b/.maestro/flow-passkey/007_user_signup_using_passkey_test.yaml similarity index 98% rename from .maestro/flow-passkey/006_user_signup_using_passkey_test.yaml rename to .maestro/flow-passkey/007_user_signup_using_passkey_test.yaml index 9872d965a..11b442f3e 100644 --- a/.maestro/flow-passkey/006_user_signup_using_passkey_test.yaml +++ b/.maestro/flow-passkey/007_user_signup_using_passkey_test.yaml @@ -11,7 +11,7 @@ onFlowStart: SCHOOL_NAME: ${SCHOOL_NAME} USERNAME: ${USERNAME} URL_SUBSTITUTION: ${URL_SUBSTITUTION} - NAME: "006_user_signup_using_passkey_test.yaml" + NAME: "007_user_signup_using_passkey_test.yaml" onFlowComplete: - runScript: diff --git a/.maestro/flows/004_user_set_language_on_app_test.yaml b/.maestro/flows/006_001_user_set_language_on_app_test.yaml similarity index 96% rename from .maestro/flows/004_user_set_language_on_app_test.yaml rename to .maestro/flows/006_001_user_set_language_on_app_test.yaml index 1bc071379..70265faa9 100644 --- a/.maestro/flows/004_user_set_language_on_app_test.yaml +++ b/.maestro/flows/006_001_user_set_language_on_app_test.yaml @@ -10,7 +10,7 @@ onFlowStart: SCHOOL_URL: ${SCHOOL_URL} SCHOOL_NAME: ${SCHOOL_NAME} URL_SUBSTITUTION: ${URL_SUBSTITUTION} - NAME: "004_user_set_language_on_app_test" + NAME: "006_001_user_set_language_on_app_test" onFlowComplete: - runScript: diff --git a/respect-test-end-to-end/README.md b/respect-test-end-to-end/README.md index 01bebc32d..39b65236b 100644 --- a/respect-test-end-to-end/README.md +++ b/respect-test-end-to-end/README.md @@ -30,6 +30,13 @@ End-to-end tests that start a blank new server/app and test functionality end-to ... -## Semi automated scenarios +### 6 : Common -6.1 [User can signup using passkey with an invite code](test-description/006_user_signup_using_passkey_test_description.md) +6.1 + +6.2 +... + +## 7 : Semi automated scenarios + +7.1 [User can signup using passkey with an invite code](test-description/007_001_user_signup_using_passkey_test_description.md) diff --git a/respect-test-end-to-end/test-description/006_001_user_set_language_on_app_test.md b/respect-test-end-to-end/test-description/006_001_user_set_language_on_app_test.md new file mode 100644 index 000000000..3356d841b --- /dev/null +++ b/respect-test-end-to-end/test-description/006_001_user_set_language_on_app_test.md @@ -0,0 +1,37 @@ +# User able to set language from Onboarding screen and from Settings + +## Description: + +To verify that the admin user can select language during onboarding and change the language later from Settings. + +## Step-by-step procedure: + +1. Launch the application. +2. Verify the language selection screen is displayed. +3. Verify the following options are visible: +4. Choose your language +5. Use system default (English) +6. हिन्दी +7. Select English. +8. Verify English is selected. +9. Tap on Get Started. +10. On school selection screen, enter the School name. +11. Select the school from the list. +12. Verify the Login screen is displayed. +13. Enter Username as admin. +14. Enter the Password. +15. Tap on Login. +16. Verify the user lands on the Apps screen. +17. Tap on Settings. +18. Verify the Settings screen is displayed. +19. Verify the current language shows as English. +20. Tap on Language. +21. Verify the following options are visible: +22. Use system default (English) +23. English +24. हिन्दी +25. Select हिन्दी. +26. Verify the screen title changes to सेटिंग्स (Settings in Hindi). +27. Tap on भाषा (Language). +28. Select English. +29. Verify the screen title changes back to Settings. \ No newline at end of file diff --git a/respect-test-end-to-end/test-description/006_user_signup_using_passkey_test_description.md b/respect-test-end-to-end/test-description/007_001_user_signup_using_passkey_test_description.md similarity index 100% rename from respect-test-end-to-end/test-description/006_user_signup_using_passkey_test_description.md rename to respect-test-end-to-end/test-description/007_001_user_signup_using_passkey_test_description.md From fd63350c0c72fedb98494de8600fed079939e9cb Mon Sep 17 00:00:00 2001 From: pooja Date: Thu, 19 Feb 2026 15:38:21 +0400 Subject: [PATCH 30/30] updated Readme --- respect-test-end-to-end/README.md | 2 +- .../test-description/006_001_user_set_language_on_app_test.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/respect-test-end-to-end/README.md b/respect-test-end-to-end/README.md index 39b65236b..e345d6c89 100644 --- a/respect-test-end-to-end/README.md +++ b/respect-test-end-to-end/README.md @@ -32,7 +32,7 @@ End-to-end tests that start a blank new server/app and test functionality end-to ### 6 : Common -6.1 +6.1 [Admin – Set and Change App Language](test-description/006_001_user_set_language_on_app_test.md) 6.2 ... diff --git a/respect-test-end-to-end/test-description/006_001_user_set_language_on_app_test.md b/respect-test-end-to-end/test-description/006_001_user_set_language_on_app_test.md index 3356d841b..c41f70e77 100644 --- a/respect-test-end-to-end/test-description/006_001_user_set_language_on_app_test.md +++ b/respect-test-end-to-end/test-description/006_001_user_set_language_on_app_test.md @@ -1,4 +1,4 @@ -# User able to set language from Onboarding screen and from Settings +# Admin – Set and Change App Language ## Description: