Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Settings page #199

Merged
merged 5 commits into from
Feb 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ val androidNodeModule = module {
get(),
get(),
get(),
get(),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -15,14 +16,16 @@ class NodeSplashPresenter(
userProfileService: UserProfileServiceFacade,
userRepository: UserRepository,
settingsRepository: SettingsRepository,
settingsServiceFacade: SettingsServiceFacade
settingsServiceFacade: SettingsServiceFacade,
languageServiceFacade: LanguageServiceFacade,
) : SplashPresenter(
mainPresenter,
applicationBootstrapFacade,
userProfileService,
userRepository,
settingsRepository,
settingsServiceFacade,
languageServiceFacade,
null
) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>()
for (code in LanguageRepository.I18N_CODES) {
Expand All @@ -49,6 +52,7 @@ class NodeLanguageServiceFacade(private val applicationService: AndroidApplicati
}
_allPairs.value = LanguageRepository.CODES.zip(displayTextList)

LanguageRepository.setDefaultLanguage(langCode)
}

override suspend fun sync() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Map<String, String>> =
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<Map<String, String>> =
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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ class I18nSupport {
// bundles = BUNDLE_NAMES.map { ResourceBundle.getBundle(it, languageCode) }
val bundleMapsByName: Map<String, Map<String, String>> = 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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import org.koin.dsl.module
val presentationModule = module {
single<MainPresenter> { ClientMainPresenter(get(), get(), get(), get(), get(), get(), get(), get(), get()) } bind AppPresenter::class

single<TopBarPresenter> { TopBarPresenter(get(), get()) } bind ITopBarPresenter::class
single<TopBarPresenter> { TopBarPresenter(get(), get(), get()) } bind ITopBarPresenter::class

single<SplashPresenter> {
SplashPresenter(
Expand All @@ -61,6 +61,7 @@ val presentationModule = module {
get(),
get(),
get(),
get(),
get()
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ fun App() {
})

val lyricist = rememberStrings()
// TODO pass user language code
val languageCode = presenter.languageCode.collectAsState().value
I18nSupport.initialize(languageCode)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "",
Expand All @@ -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) }
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import org.koin.compose.koinInject
interface ITopBarPresenter : ViewPresenter {
val uniqueAvatar: StateFlow<PlatformImage?>
fun onAvatarClicked()

val showAnimation: StateFlow<Boolean>
}

@OptIn(ExperimentalMaterial3Api::class)
Expand All @@ -54,6 +56,7 @@ fun TopBar(
val tabNavController: NavHostController = presenter.getRootTabNavController()

val interactionEnabled = presenter.isInteractive.collectAsState().value
val showAnimation = presenter.showAnimation.collectAsState().value
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:)

var showBackConfirmationDialog by remember { mutableStateOf(false) }

val currentTab = tabNavController.currentBackStackEntryAsState().value?.destination?.route
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -21,6 +23,8 @@ open class TopBarPresenter(
_uniqueAvatar.value = value
}

override val showAnimation: StateFlow<Boolean> get() = settingsServiceFacade.useAnimations

init {
refresh()
}
Expand Down
Loading
Loading