diff --git a/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt b/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt index 8ef0a100..4ea3cfbd 100644 --- a/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt +++ b/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt @@ -81,6 +81,7 @@ val androidNodeModule = module { get(), get(), get(), + get(), ) } diff --git a/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/NodeSplashPresenter.kt b/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/NodeSplashPresenter.kt index 4504c54a..fc114c87 100644 --- a/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/NodeSplashPresenter.kt +++ b/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/NodeSplashPresenter.kt @@ -4,6 +4,7 @@ import network.bisq.mobile.domain.data.model.Settings import network.bisq.mobile.domain.data.repository.SettingsRepository import network.bisq.mobile.domain.data.repository.UserRepository import network.bisq.mobile.domain.service.bootstrap.ApplicationBootstrapFacade +import network.bisq.mobile.domain.service.common.LanguageServiceFacade import network.bisq.mobile.domain.service.settings.SettingsServiceFacade import network.bisq.mobile.domain.service.user_profile.UserProfileServiceFacade import network.bisq.mobile.presentation.MainPresenter @@ -15,7 +16,8 @@ class NodeSplashPresenter( userProfileService: UserProfileServiceFacade, userRepository: UserRepository, settingsRepository: SettingsRepository, - settingsServiceFacade: SettingsServiceFacade + settingsServiceFacade: SettingsServiceFacade, + languageServiceFacade: LanguageServiceFacade, ) : SplashPresenter( mainPresenter, applicationBootstrapFacade, @@ -23,6 +25,7 @@ class NodeSplashPresenter( userRepository, settingsRepository, settingsServiceFacade, + languageServiceFacade, null ) { diff --git a/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/service/common/NodeLanguageServiceFacade.kt b/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/service/common/NodeLanguageServiceFacade.kt index a477c270..dd0cf145 100644 --- a/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/service/common/NodeLanguageServiceFacade.kt +++ b/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/service/common/NodeLanguageServiceFacade.kt @@ -36,6 +36,9 @@ class NodeLanguageServiceFacade(private val applicationService: AndroidApplicati // Life cycle override fun activate() { + // To keep "en" the only language for language lists + val langCode = LanguageRepository.getDefaultLanguage() ?: "en" + LanguageRepository.setDefaultLanguage("en") val displayTextList = mutableListOf() for (code in LanguageRepository.I18N_CODES) { @@ -49,6 +52,7 @@ class NodeLanguageServiceFacade(private val applicationService: AndroidApplicati } _allPairs.value = LanguageRepository.CODES.zip(displayTextList) + LanguageRepository.setDefaultLanguage(langCode) } override suspend fun sync() { diff --git a/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/common/ClientLanguageServiceFacade.kt b/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/common/ClientLanguageServiceFacade.kt index 7ac434ed..3998bc92 100644 --- a/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/common/ClientLanguageServiceFacade.kt +++ b/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/common/ClientLanguageServiceFacade.kt @@ -45,52 +45,105 @@ class ClientLanguageServiceFacade( // Life cycle override fun activate() { job = coroutineScope.launch { - launch { - if (_i18nObserver.value == null) { - _i18nObserver.value = apiGateway.subscribeI18NCodes(_defaultLanguage.value) - } - _i18nObserver.value!!.webSocketEvent.collect{ webSocketEvent -> - try { - if (webSocketEvent?.deferredPayload == null) { - return@collect - } - val webSocketEventPayload: WebSocketEventPayload> = - WebSocketEventPayload.from(json, webSocketEvent) - val response = webSocketEventPayload.payload - println(response) - _i18nPairs.value = response.toList() - } catch (e: Exception) { - log.e(e.toString(), e) - } - } - } - launch { - if (_allPairsObserver.value == null) { - _allPairsObserver.value = apiGateway.subscribeAllLanguageCodes(_defaultLanguage.value) - } - _allPairsObserver.value!!.webSocketEvent.collect{ webSocketEvent -> - try { - if (webSocketEvent?.deferredPayload == null) { - return@collect - } - val webSocketEventPayload: WebSocketEventPayload> = - WebSocketEventPayload.from(json, webSocketEvent) - val response = webSocketEventPayload.payload - _allPairs.value = response.toList() - } catch (e: Exception) { - log.e(e.toString(), e) - } - } - } + _i18nPairs.value = listOf( + "en" to "English (English)", + "de" to "German (Deutsch)", + "es" to "Spanish (español)", + "it" to "Italian (italiano)", + "pt-BR" to "Portuguese (português (Brasil))", + "cs" to "Czech (čeština)", + "pcm" to "Nigerian Pidgin (Naijíriá Píjin)", + "ru" to "Russian (русский)", + "af-ZA" to "Afrikaans (Afrikaans (Suid-Afrika))", + ) + + _allPairs.value = listOf( + "af" to "Afrikaans (Afrikaans)", + "sq" to "Albanian (shqip)", + "am" to "Amharic (አማርኛ)", + "ar" to "Arabic (العربية)", + "hy" to "Armenian (հայերեն)", + "az" to "Azerbaijani (azərbaycan)", + "bn" to "Bangla (বাংলা)", + "be" to "Belarusian (беларуская)", + "bi" to "Bislama (Bislama)", + "bs" to "Bosnian (bosanski)", + "bg" to "Bulgarian (български)", + "my" to "Burmese (မြန်မာ)", + "ca" to "Catalan (català)", + "zh" to "Chinese (中文)", + "hr" to "Croatian (hrvatski)", + "cs" to "Czech (čeština)", + "da" to "Danish (dansk)", + "dv" to "Divehi (Divehi)", + "nl" to "Dutch (Nederlands)", + "dz" to "Dzongkha (རྫོང་ཁ)", + "en" to "English (English)", + "et" to "Estonian (eesti)", + "fo" to "Faroese (føroyskt)", + "fi" to "Finnish (suomi)", + "fr" to "French (français)", + "ka" to "Georgian (ქართული)", + "de" to "German (Deutsch)", + "el" to "Greek (Ελληνικά)", + "he" to "Hebrew (עברית)", + "hi" to "Hindi (हिन्दी)", + "hu" to "Hungarian (magyar)", + "is" to "Icelandic (íslenska)", + "id" to "Indonesian (Indonesia)", + "ga" to "Irish (Gaeilge)", + "it" to "Italian (italiano)", + "ja" to "Japanese (日本語)", + "kl" to "Kalaallisut (kalaallisut)", + "kk" to "Kazakh (қазақ тілі)", + "km" to "Khmer (ខ្មែរ)", + "rw" to "Kinyarwanda (Kinyarwanda)", + "ko" to "Korean (한국어)", + "ky" to "Kyrgyz (кыргызча)", + "lo" to "Lao (ລາວ)", + "la" to "Latin (Latin)", + "lv" to "Latvian (latviešu)", + "lt" to "Lithuanian (lietuvių)", + "mk" to "Macedonian (македонски)", + "ms" to "Malay (Melayu)", + "mt" to "Maltese (Malti)", + "mn" to "Mongolian (монгол)", + "ne" to "Nepali (नेपाली)", + "no" to "Norwegian (norsk)", + "ps" to "Pashto (پښتو)", + "fa" to "Persian (فارسی)", + "pl" to "Polish (polski)", + "pt" to "Portuguese (português)", + "ro" to "Romanian (română)", + "ru" to "Russian (русский)", + "sm" to "Samoan (Samoan)", + "sr" to "Serbian (српски)", + "si" to "Sinhala (සිංහල)", + "sk" to "Slovak (slovenčina)", + "sl" to "Slovenian (slovenščina)", + "so" to "Somali (Soomaali)", + "es" to "Spanish (español)", + "sw" to "Swahili (Kiswahili)", + "sv" to "Swedish (svenska)", + "tg" to "Tajik (тоҷикӣ)", + "th" to "Thai (ไทย)", + "ti" to "Tigrinya (ትግርኛ)", + "tr" to "Turkish (Türkçe)", + "tk" to "Turkmen (türkmen dili)", + "uk" to "Ukrainian (українська)", + "uz" to "Uzbek (o‘zbek)", + "vi" to "Vietnamese (Tiếng Việt)" + ) } } override suspend fun sync() { - val subscriberId = _i18nObserver.value?.webSocketEvent?.value?.subscriberId ?: "" - apiGateway.syncI18NCodes(subscriberId, _defaultLanguage.value) - apiGateway.syncAllLanguageCodes(subscriberId, _defaultLanguage.value) + activate() +// val subscriberId = _i18nObserver.value?.webSocketEvent?.value?.subscriberId ?: "" +// apiGateway.syncI18NCodes(subscriberId, _defaultLanguage.value) +// apiGateway.syncAllLanguageCodes(subscriberId, _defaultLanguage.value) } override fun deactivate() { diff --git a/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/settings/ClientSettingsServiceFacade.kt b/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/settings/ClientSettingsServiceFacade.kt index dfe7b3fc..77b24d7d 100644 --- a/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/settings/ClientSettingsServiceFacade.kt +++ b/shared/domain/src/commonMain/kotlin/network/bisq/mobile/client/service/settings/ClientSettingsServiceFacade.kt @@ -107,9 +107,9 @@ class ClientSettingsServiceFacade(val apiGateway: SettingsApiGateway) : Settings _isTacAccepted.value = it.isTacAccepted _tradeRulesConfirmed.value = it.tradeRulesConfirmed _languageCode.value = it.languageCode + _maxTradePriceDeviation.value = it.maxTradePriceDeviation _supportedLanguageCodes.value = it.supportedLanguageCodes _closeMyOfferWhenTaken.value = it.closeMyOfferWhenTaken - _maxTradePriceDeviation.value = it.maxTradePriceDeviation _useAnimations.value = it.useAnimations _numDaysAfterRedactingTradeData.value = it.numDaysAfterRedactingTradeData } diff --git a/shared/domain/src/commonMain/kotlin/network/bisq/mobile/i18n/I18nSupport.kt b/shared/domain/src/commonMain/kotlin/network/bisq/mobile/i18n/I18nSupport.kt index c4d83eb2..3ea1395e 100644 --- a/shared/domain/src/commonMain/kotlin/network/bisq/mobile/i18n/I18nSupport.kt +++ b/shared/domain/src/commonMain/kotlin/network/bisq/mobile/i18n/I18nSupport.kt @@ -6,13 +6,13 @@ class I18nSupport { // bundles = BUNDLE_NAMES.map { ResourceBundle.getBundle(it, languageCode) } val bundleMapsByName: Map> = when (languageCode) { "en" -> GeneratedResourceBundles_en.bundles - "af_ZA" -> GeneratedResourceBundles_af_ZA.bundles + "af-ZA" -> GeneratedResourceBundles_af_ZA.bundles "cs" -> GeneratedResourceBundles_cs.bundles "de" -> GeneratedResourceBundles_de.bundles "es" -> GeneratedResourceBundles_es.bundles "it" -> GeneratedResourceBundles_it.bundles "pcm" -> GeneratedResourceBundles_pcm.bundles - "pt_BR" -> GeneratedResourceBundles_pt_BR.bundles + "pt-BR" -> GeneratedResourceBundles_pt_BR.bundles "ru" -> GeneratedResourceBundles_ru.bundles else -> GeneratedResourceBundles_en.bundles } diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/di/PresentationModule.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/di/PresentationModule.kt index 53d5c529..1a315143 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/di/PresentationModule.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/di/PresentationModule.kt @@ -51,7 +51,7 @@ import org.koin.dsl.module val presentationModule = module { single { ClientMainPresenter(get(), get(), get(), get(), get(), get(), get(), get(), get()) } bind AppPresenter::class - single { TopBarPresenter(get(), get()) } bind ITopBarPresenter::class + single { TopBarPresenter(get(), get(), get()) } bind ITopBarPresenter::class single { SplashPresenter( @@ -61,6 +61,7 @@ val presentationModule = module { get(), get(), get(), + get(), get() ) } diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/App.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/App.kt index 56d8818b..df2bee23 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/App.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/App.kt @@ -55,7 +55,6 @@ fun App() { }) val lyricist = rememberStrings() - // TODO pass user language code val languageCode = presenter.languageCode.collectAsState().value I18nSupport.initialize(languageCode) diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/TextField.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/TextField.kt index 70ca4579..1c1e43ca 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/TextField.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/atoms/TextField.kt @@ -32,6 +32,11 @@ import androidx.compose.ui.unit.sp import network.bisq.mobile.presentation.ui.components.atoms.button.CopyIconButton import network.bisq.mobile.presentation.ui.theme.BisqTheme +/** + * TODO: + * Should have a BisqNumberField with customizations like numberWithTwoDecimals + * and whose value is Double and onValueChange emits Double + */ @Composable fun BisqTextField( label: String = "", @@ -54,6 +59,7 @@ fun BisqTextField( valuePrefix: String? = null, valueSuffix: String? = null, validation: ((String) -> String?)? = null, + numberWithTwoDecimals: Boolean = false, modifier: Modifier = Modifier, ) { var isFocused by remember { mutableStateOf(false) } @@ -157,8 +163,16 @@ fun BisqTextField( if (valueSuffix != null && cleanValue.endsWith(valueSuffix)) { cleanValue = cleanValue.removeSuffix(valueSuffix) } - validationError = validation?.invoke(cleanValue) - onValueChange(cleanValue, validationError == null || validationError?.isEmpty() == true) + if (numberWithTwoDecimals) { + val decimalPattern = Regex("^\\d*\\.?\\d{0,2}$") + if (decimalPattern.matches(cleanValue)) { + validationError = validation?.invoke(cleanValue) + onValueChange(cleanValue, validationError == null || validationError?.isEmpty() == true) + } + } else { + validationError = validation?.invoke(cleanValue) + onValueChange(cleanValue, validationError == null || validationError?.isEmpty() == true) + } }, modifier = Modifier .padding(paddingValues) diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/TopBar.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/TopBar.kt index 1810fde3..0bdb5ca9 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/TopBar.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/TopBar.kt @@ -36,6 +36,8 @@ import org.koin.compose.koinInject interface ITopBarPresenter : ViewPresenter { val uniqueAvatar: StateFlow fun onAvatarClicked() + + val showAnimation: StateFlow } @OptIn(ExperimentalMaterial3Api::class) @@ -54,6 +56,7 @@ fun TopBar( val tabNavController: NavHostController = presenter.getRootTabNavController() val interactionEnabled = presenter.isInteractive.collectAsState().value + val showAnimation = presenter.showAnimation.collectAsState().value var showBackConfirmationDialog by remember { mutableStateOf(false) } val currentTab = tabNavController.currentBackStackEntryAsState().value?.destination?.route @@ -62,7 +65,7 @@ fun TopBar( val topBarState = rememberTopAppBarState() val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(topBarState, canScroll = { false }) - + val defaultBackButton: @Composable () -> Unit = { IconButton(onClick = { if (navController.previousBackStackEntry != null) { @@ -124,20 +127,24 @@ fun TopBar( modifier = Modifier.padding(top = if (isFlowScreen) 15.dp else 0.dp, end = 16.dp), verticalAlignment = Alignment.CenterVertically ) { + + val userIconModifier = Modifier + .size(30.dp) + .alpha(if (currentTab == Routes.TabSettings.name) 0.5f else 1.0f) + .clickable { + if (currentTab != Routes.TabSettings.name) { + // TODO this should be presenter code, with the proper main thread coroutine used (causes random crashes as is) + navController.navigate(Routes.UserProfileSettings.name) + } + } + // TODO implement full feature after MVP // BellIcon() Spacer(modifier = Modifier.width(12.dp)) - ShineOverlay { - UserIcon( - presenter.uniqueAvatar.value, - modifier = Modifier.size(30.dp) - .alpha(if (currentTab == Routes.TabSettings.name) 0.5f else 1.0f) - .clickable { - if (currentTab != Routes.TabSettings.name) { - // TODO this should be presenter code, with the proper main thread coroutine used (causes random crashes as is) - navController.navigate(Routes.UserProfileSettings.name) - } - }) + if (showAnimation) { + ShineOverlay { UserIcon(presenter.uniqueAvatar.value, modifier = userIconModifier) } + } else { + UserIcon(presenter.uniqueAvatar.value, modifier = userIconModifier) } } }, diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/TopBarPresenter.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/TopBarPresenter.kt index 95d4438b..c6cb8a0e 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/TopBarPresenter.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/components/molecules/TopBarPresenter.kt @@ -5,12 +5,14 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import network.bisq.mobile.domain.PlatformImage import network.bisq.mobile.domain.data.repository.UserRepository +import network.bisq.mobile.domain.service.settings.SettingsServiceFacade import network.bisq.mobile.presentation.BasePresenter import network.bisq.mobile.presentation.MainPresenter import network.bisq.mobile.presentation.ui.navigation.Routes open class TopBarPresenter( private val userRepository: UserRepository, + private val settingsServiceFacade: SettingsServiceFacade, mainPresenter: MainPresenter ): BasePresenter(mainPresenter), ITopBarPresenter { @@ -21,6 +23,8 @@ open class TopBarPresenter( _uniqueAvatar.value = value } + override val showAnimation: StateFlow get() = settingsServiceFacade.useAnimations + init { refresh() } diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/GeneralSettingsPresenter.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/GeneralSettingsPresenter.kt index a33e7376..8bb435ba 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/GeneralSettingsPresenter.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/GeneralSettingsPresenter.kt @@ -22,6 +22,7 @@ open class GeneralSettingsPresenter( override val i18nPairs: StateFlow>> = languageServiceFacade.i18nPairs override val allLanguagePairs: StateFlow>> = languageServiceFacade.allPairs + private val _languageCode: MutableStateFlow = MutableStateFlow("en") override val languageCode: MutableStateFlow = _languageCode @@ -34,11 +35,10 @@ open class GeneralSettingsPresenter( // Doing this to reload all bundles of the newly selected language, // all String.i18n() across the app gets the text of selected language I18nSupport.initialize(langCode) - // TODO: This will update the default language of remote instance. - // Is this okay? Considering we will have multiple clients to connect to same remote node in the future + // As per chat with @Henrik Feb 4, it's okay not to translate these lists into selected languages, for now. // To update display values in i18Pairs, allLanguagePairs with the new language - languageServiceFacade.setDefaultLanguage(langCode) - languageServiceFacade.sync() + // languageServiceFacade.setDefaultLanguage(langCode) + // languageServiceFacade.sync() _languageCode.value = langCode } } @@ -72,13 +72,16 @@ open class GeneralSettingsPresenter( } } - private val _tradePriceTolerance: MutableStateFlow = MutableStateFlow(5.0) - override val tradePriceTolerance: StateFlow = _tradePriceTolerance - override fun setTradePriceTolerance(value: Double) { + // This is internally represented as ratio. So 100% is saved as 1.0, 5% as 0.05. + // Hence the 100 multiplier and divider + private val _tradePriceTolerance: MutableStateFlow = MutableStateFlow("5") + override val tradePriceTolerance: StateFlow = _tradePriceTolerance + override fun setTradePriceTolerance(value: String, isValid: Boolean) { backgroundScope.launch { _tradePriceTolerance.value = value - if (value in 1.0..10.0) { - settingsServiceFacade.setMaxTradePriceDeviation(value) + if (isValid) { + val _value = value.toDoubleOrNull() + settingsServiceFacade.setMaxTradePriceDeviation((_value ?: 0.0)/100) } } } @@ -92,13 +95,14 @@ open class GeneralSettingsPresenter( } } - private val _powFactor: MutableStateFlow = MutableStateFlow(1.0) - override val powFactor: StateFlow = _powFactor - override fun setPowFactor(value: Double) { + private val _powFactor: MutableStateFlow = MutableStateFlow("1") + override val powFactor: StateFlow = _powFactor + override fun setPowFactor(value: String, isValid: Boolean) { backgroundScope.launch { _powFactor.value = value - if (value in 0.0..160000.0) { - settingsServiceFacade.setDifficultyAdjustmentFactor(value) + if (isValid) { + val _value = value.toDoubleOrNull() + settingsServiceFacade.setDifficultyAdjustmentFactor(_value ?: 0.0) } } } @@ -118,26 +122,20 @@ open class GeneralSettingsPresenter( override fun onViewAttached() { jobs.add(backgroundScope.launch { - try { - val settings: SettingsVO = settingsServiceFacade.getSettings().getOrThrow() - _languageCode.value = settings.languageCode - _supportedLanguageCodes.value = if (settings.supportedLanguageCodes.isNotEmpty()) - settings.supportedLanguageCodes - else - setOf("en") // setOf(i18nPairs.collectAsState().value.first().first) - - // _tradeNotification.value = - // _chatNotification.value = - _closeOfferWhenTradeTaken.value = settings.closeMyOfferWhenTaken - _tradePriceTolerance.value = settings.maxTradePriceDeviation - _useAnimations.value = settings.useAnimations - - _numDaysAfterRedactingTradeData.value = settings.numDaysAfterRedactingTradeData - _powFactor.value = settingsServiceFacade.difficultyAdjustmentFactor.value - _ignorePow.value = settingsServiceFacade.ignoreDiffAdjustmentFromSecManager.value - } catch (e: Exception) { - print(e) - } + val settings: SettingsVO = settingsServiceFacade.getSettings().getOrThrow() + _languageCode.value = settings.languageCode + _supportedLanguageCodes.value = if (settings.supportedLanguageCodes.isNotEmpty()) + settings.supportedLanguageCodes + else + setOf("en") // setOf(i18nPairs.collectAsState().value.first().first) + + // _chatNotification.value = + _closeOfferWhenTradeTaken.value = settings.closeMyOfferWhenTaken + _tradePriceTolerance.value = (settings.maxTradePriceDeviation * 100).toString() + _useAnimations.value = settings.useAnimations + _numDaysAfterRedactingTradeData.value = settings.numDaysAfterRedactingTradeData + _powFactor.value = settingsServiceFacade.difficultyAdjustmentFactor.value.toString() + _ignorePow.value = settingsServiceFacade.ignoreDiffAdjustmentFromSecManager.value }) } diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/GeneralSettingsScreen.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/GeneralSettingsScreen.kt index 32a4bb9b..bacba6bd 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/GeneralSettingsScreen.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/GeneralSettingsScreen.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.StateFlow import network.bisq.mobile.i18n.i18n import network.bisq.mobile.presentation.ViewPresenter import network.bisq.mobile.presentation.ui.components.atoms.* +import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqGap import network.bisq.mobile.presentation.ui.components.atoms.layout.BisqHDivider import network.bisq.mobile.presentation.ui.components.layout.BisqScrollLayout import network.bisq.mobile.presentation.ui.helpers.RememberPresenterLifecycle @@ -32,16 +33,16 @@ interface IGeneralSettingsPresenter : ViewPresenter { val closeOfferWhenTradeTaken: StateFlow fun setCloseOfferWhenTradeTaken(value: Boolean) - val tradePriceTolerance: StateFlow - fun setTradePriceTolerance(value: Double) + val tradePriceTolerance: StateFlow + fun setTradePriceTolerance(value: String, isValid: Boolean) val useAnimations: StateFlow fun setUseAnimations(value: Boolean) val numDaysAfterRedactingTradeData: StateFlow - val powFactor: StateFlow - fun setPowFactor(value: Double) + val powFactor: StateFlow + fun setPowFactor(value: String, isValid: Boolean) val ignorePow: StateFlow fun setIgnorePow(value: Boolean) @@ -125,17 +126,13 @@ fun GeneralSettingsScreen(showBackNavigation: Boolean = false) { ) BisqTextField( - label = "settings.trade.maxTradePriceDeviation".i18n() + " (TODO in androidNode)", - value = tradePriceTolerance.toString(), - valueSuffix = "%", + label = "settings.trade.maxTradePriceDeviation".i18n(), + value = tradePriceTolerance, keyboardType = KeyboardType.Decimal, - onValueChange = { it, isValid -> - val parsedValue = it.toDoubleOrNull() - if (parsedValue != null) { - presenter.setTradePriceTolerance(parsedValue) - } - }, + onValueChange = { it, isValid -> presenter.setTradePriceTolerance(it, isValid) }, helperText = "settings.trade.maxTradePriceDeviation.help".i18n(), + numberWithTwoDecimals = true, + valueSuffix = "%", validation = { val parsedValue = it.toDoubleOrNull() if (parsedValue == null) { @@ -168,12 +165,8 @@ fun GeneralSettingsScreen(showBackNavigation: Boolean = false) { value = powFactor.toString(), keyboardType = KeyboardType.Decimal, disabled = !ignorePow, - onValueChange = { it, isValid -> - val parsedValue = it.toDoubleOrNull() - if (parsedValue != null) { - presenter.setPowFactor(parsedValue) - } - }, + numberWithTwoDecimals = true, + onValueChange = { it, isValid -> presenter.setPowFactor(it, isValid) }, validation = { val parsedValue = it.toDoubleOrNull() if (parsedValue == null) { diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/SettingsPresenter.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/SettingsPresenter.kt index 81ec6795..6e610f3d 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/SettingsPresenter.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/settings/SettingsPresenter.kt @@ -24,7 +24,7 @@ open class SettingsPresenter( ) ), // TODO General settings has several issues that needs to be fixed, uncomment when the functionality gets fully implemented including the usage of those settings -// MenuItem.Leaf(label = "General", content = { GeneralSettingsScreen() }) + MenuItem.Leaf(label = "General", content = { GeneralSettingsScreen() }) ) return MenuItem.Parent( label = "Bisq", diff --git a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashPresenter.kt b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashPresenter.kt index ed3eceff..d2c747b2 100644 --- a/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashPresenter.kt +++ b/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/uicases/startup/SplashPresenter.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.datetime.Clock import network.bisq.mobile.client.websocket.WebSocketClientProvider @@ -12,7 +13,9 @@ import network.bisq.mobile.domain.data.model.User import network.bisq.mobile.domain.data.replicated.settings.SettingsVO import network.bisq.mobile.domain.data.repository.SettingsRepository import network.bisq.mobile.domain.data.repository.UserRepository +import network.bisq.mobile.domain.getDeviceLanguageCode import network.bisq.mobile.domain.service.bootstrap.ApplicationBootstrapFacade +import network.bisq.mobile.domain.service.common.LanguageServiceFacade import network.bisq.mobile.domain.service.settings.SettingsServiceFacade import network.bisq.mobile.domain.service.user_profile.UserProfileServiceFacade import network.bisq.mobile.presentation.BasePresenter @@ -26,6 +29,7 @@ open class SplashPresenter( private val userRepository: UserRepository, private val settingsRepository: SettingsRepository, private val settingsServiceFacade: SettingsServiceFacade, + private val languageServiceFacade: LanguageServiceFacade, private val webSocketClientProvider: WebSocketClientProvider?, ) : BasePresenter(mainPresenter) { @@ -36,6 +40,17 @@ open class SplashPresenter( override fun onViewAttached() { jobs.add(backgroundScope.launch { + val settingsMobile: Settings = settingsRepository.fetch() ?: Settings() + if (settingsMobile.firstLaunch) { + val deviceLanguageCode = getDeviceLanguageCode() + val i18nSupportedCodes = languageServiceFacade.i18nPairs.value.map { it.first } + if (i18nSupportedCodes.contains(deviceLanguageCode)) { + settingsServiceFacade.setLanguageCode(deviceLanguageCode) + } else { + settingsServiceFacade.setLanguageCode("en") + } + } + userRepository.fetch()?.let { it.lastActivity = Clock.System.now().toEpochMilliseconds() userRepository.update(it)