From 4c4699a9aaf9a5e61a2decd68d43ff5ad79eacf9 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 20 Mar 2026 07:24:41 -0300 Subject: [PATCH 01/85] feat: implement settings tab bar WIP --- app/src/main/java/to/bitkit/ui/ContentView.kt | 70 +- .../to/bitkit/ui/components/DrawerMenu.kt | 10 + .../components/settings/SettingsSwitchRow.kt | 25 + .../java/to/bitkit/ui/settings/AboutScreen.kt | 109 --- .../ui/settings/AdvancedSettingsScreen.kt | 205 ------ .../ui/settings/BackupSettingsScreen.kt | 37 +- .../ui/settings/SecuritySettingsScreen.kt | 225 ------ .../to/bitkit/ui/settings/SettingsScreen.kt | 692 ++++++++++++++---- .../settings/general/GeneralSettingsScreen.kt | 182 ----- .../settings/general/WidgetsSettingsScreen.kt | 52 +- .../ui/settings/pin/ChangePinResultScreen.kt | 2 +- .../ui/settings/pin/DisablePinScreen.kt | 96 --- .../ui/settings/pin/PinManagementScreen.kt | 157 ++++ .../ui/settings/support/SupportScreen.kt | 127 +++- .../to/bitkit/viewmodels/SettingsViewModel.kt | 6 + app/src/main/res/values/strings.xml | 28 +- 16 files changed, 960 insertions(+), 1063 deletions(-) delete mode 100644 app/src/main/java/to/bitkit/ui/settings/AboutScreen.kt delete mode 100644 app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsScreen.kt delete mode 100644 app/src/main/java/to/bitkit/ui/settings/SecuritySettingsScreen.kt delete mode 100644 app/src/main/java/to/bitkit/ui/settings/general/GeneralSettingsScreen.kt delete mode 100644 app/src/main/java/to/bitkit/ui/settings/pin/DisablePinScreen.kt create mode 100644 app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt index b504d0fa6..009f9ffbc 100644 --- a/app/src/main/java/to/bitkit/ui/ContentView.kt +++ b/app/src/main/java/to/bitkit/ui/ContentView.kt @@ -122,8 +122,6 @@ import to.bitkit.ui.screens.widgets.suggestions.SuggestionsViewModel import to.bitkit.ui.screens.widgets.weather.WeatherEditScreen import to.bitkit.ui.screens.widgets.weather.WeatherPreviewScreen import to.bitkit.ui.screens.widgets.weather.WeatherViewModel -import to.bitkit.ui.settings.AboutScreen -import to.bitkit.ui.settings.AdvancedSettingsScreen import to.bitkit.ui.settings.BackupSettingsScreen import to.bitkit.ui.settings.BlocktankRegtestScreen import to.bitkit.ui.settings.CJitDetailScreen @@ -132,7 +130,6 @@ import to.bitkit.ui.settings.LanguageSettingsScreen import to.bitkit.ui.settings.LogDetailScreen import to.bitkit.ui.settings.LogsScreen import to.bitkit.ui.settings.OrderDetailScreen -import to.bitkit.ui.settings.SecuritySettingsScreen import to.bitkit.ui.settings.SettingsScreen import to.bitkit.ui.settings.advanced.AddressTypePreferenceScreen import to.bitkit.ui.settings.advanced.AddressViewerScreen @@ -144,7 +141,6 @@ import to.bitkit.ui.settings.backgroundPayments.BackgroundPaymentsIntroScreen import to.bitkit.ui.settings.backgroundPayments.BackgroundPaymentsSettings import to.bitkit.ui.settings.backups.ResetAndRestoreScreen import to.bitkit.ui.settings.general.DefaultUnitSettingsScreen -import to.bitkit.ui.settings.general.GeneralSettingsScreen import to.bitkit.ui.settings.general.LocalCurrencySettingsScreen import to.bitkit.ui.settings.general.TagsSettingsScreen import to.bitkit.ui.settings.general.WidgetsSettingsScreen @@ -156,7 +152,7 @@ import to.bitkit.ui.settings.pin.ChangePinConfirmScreen import to.bitkit.ui.settings.pin.ChangePinNewScreen import to.bitkit.ui.settings.pin.ChangePinResultScreen import to.bitkit.ui.settings.pin.ChangePinScreen -import to.bitkit.ui.settings.pin.DisablePinScreen +import to.bitkit.ui.settings.pin.PinManagementScreen import to.bitkit.ui.settings.quickPay.QuickPayIntroScreen import to.bitkit.ui.settings.quickPay.QuickPaySettingsScreen import to.bitkit.ui.settings.support.ReportIssueResultScreen @@ -527,12 +523,10 @@ private fun RootNavHost( comingSoon(navController) profile(navController, settingsViewModel) shop(navController, settingsViewModel, appViewModel) - generalSettings(navController) - advancedSettings(navController) - aboutSettings(navController) + generalSettingsSubScreens(navController) + advancedSettingsSubScreens(navController) transactionSpeedSettings(navController) - securitySettings(navController) - disablePin(navController) + pinManagement(navController) changePin(navController) changePinNew(navController) changePinConfirm(navController) @@ -972,11 +966,7 @@ private fun NavGraphBuilder.shop( } } -private fun NavGraphBuilder.generalSettings(navController: NavHostController) { - composableWithDefaultTransitions { - GeneralSettingsScreen(navController) - } - +private fun NavGraphBuilder.generalSettingsSubScreens(navController: NavHostController) { composableWithDefaultTransitions { WidgetsSettingsScreen(navController) } @@ -1000,10 +990,7 @@ private fun NavGraphBuilder.generalSettings(navController: NavHostController) { } } -private fun NavGraphBuilder.advancedSettings(navController: NavHostController) { - composableWithDefaultTransitions { - AdvancedSettingsScreen(navController) - } +private fun NavGraphBuilder.advancedSettingsSubScreens(navController: NavHostController) { composableWithDefaultTransitions { CoinSelectPreferenceScreen(navController) } @@ -1024,16 +1011,6 @@ private fun NavGraphBuilder.advancedSettings(navController: NavHostController) { } } -private fun NavGraphBuilder.aboutSettings(navController: NavHostController) { - composableWithDefaultTransitions { - AboutScreen( - onBack = { - navController.popBackStack() - } - ) - } -} - private fun NavGraphBuilder.transactionSpeedSettings(navController: NavHostController) { composableWithDefaultTransitions { TransactionSpeedSettingsScreen(navController) @@ -1043,15 +1020,9 @@ private fun NavGraphBuilder.transactionSpeedSettings(navController: NavHostContr } } -private fun NavGraphBuilder.securitySettings(navController: NavHostController) { - composableWithDefaultTransitions { - SecuritySettingsScreen(navController = navController) - } -} - -private fun NavGraphBuilder.disablePin(navController: NavHostController) { - composableWithDefaultTransitions { - DisablePinScreen(navController) +private fun NavGraphBuilder.pinManagement(navController: NavHostController) { + composableWithDefaultTransitions { + PinManagementScreen(navController) } } @@ -1517,11 +1488,7 @@ inline fun NavController.navigateTo( } } -fun NavController.navigateToGeneralSettings() = navigateTo(Routes.GeneralSettings) - -fun NavController.navigateToSecuritySettings() = navigateTo(Routes.SecuritySettings) - -fun NavController.navigateToDisablePin() = navigateTo(Routes.DisablePin) +fun NavController.navigateToPinManagement() = navigateTo(Routes.PinManagement) fun NavController.navigateToChangePin() = navigateTo(Routes.ChangePin) @@ -1592,9 +1559,6 @@ fun NavController.navigateToTagsSettings() = navigateTo(Routes.TagsSettings) fun NavController.navigateToLanguageSettings() = navigateTo(Routes.LanguageSettings) -fun NavController.navigateToAdvancedSettings() = navigateTo(Routes.AdvancedSettings) - -fun NavController.navigateToAboutSettings() = navigateTo(Routes.AboutSettings) // endregion @Stable @@ -1614,9 +1578,6 @@ sealed interface Routes { @Serializable data object NodeInfo : Routes - @Serializable - data object GeneralSettings : Routes - @Serializable data object TransactionSpeedSettings : Routes @@ -1626,9 +1587,6 @@ sealed interface Routes { @Serializable data object TagsSettings : Routes - @Serializable - data object AdvancedSettings : Routes - @Serializable data object CoinSelectPreference : Routes @@ -1644,17 +1602,11 @@ sealed interface Routes { @Serializable data object AddressViewer : Routes - @Serializable - data object AboutSettings : Routes - @Serializable data object CustomFeeSettings : Routes @Serializable - data object SecuritySettings : Routes - - @Serializable - data object DisablePin : Routes + data object PinManagement : Routes @Serializable data object ChangePin : Routes diff --git a/app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt b/app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt index 4e5c4b176..759e8731b 100644 --- a/app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt +++ b/app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt @@ -199,6 +199,16 @@ private fun Menu( modifier = Modifier.testTag("DrawerShop") ) + DrawerItem( + label = stringResource(R.string.wallet__drawer__support), + iconRes = R.drawable.ic_settings_support, + onClick = { + rootNavController.navigateIfNotCurrent(Routes.Support) + scope.launch { drawerState.close() } + }, + modifier = Modifier.testTag("DrawerSupport") + ) + DrawerItem( label = stringResource(R.string.wallet__drawer__settings), iconRes = R.drawable.ic_settings, diff --git a/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt b/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt index 2b512440f..797ab09c3 100644 --- a/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt +++ b/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt @@ -3,18 +3,25 @@ package to.bitkit.ui.components.settings import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon import androidx.compose.material3.Switch import androidx.compose.material3.SwitchColors import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import to.bitkit.R import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.BodyS import to.bitkit.ui.shared.modifiers.clickableAlpha @@ -29,6 +36,8 @@ fun SettingsSwitchRow( onClick: () -> Unit, modifier: Modifier = Modifier, subtitle: String? = null, + iconRes: Int? = null, + iconTint: Color = Color.Unspecified, colors: SwitchColors = AppSwitchDefaults.colors, ) { Column( @@ -42,6 +51,16 @@ fun SettingsSwitchRow( .clickableAlpha { onClick() } .padding(vertical = 16.dp) ) { + if (iconRes != null) { + Icon( + painter = painterResource(iconRes), + contentDescription = null, + tint = iconTint, + modifier = Modifier.size(32.dp), + ) + Spacer(modifier = Modifier.width(10.dp)) + } + Column( modifier = Modifier .weight(1f) @@ -79,6 +98,12 @@ private fun Preview() { isChecked = false, onClick = {}, ) + SettingsSwitchRow( + title = "With Icon", + isChecked = true, + iconRes = R.drawable.ic_eye, + onClick = {}, + ) } } } diff --git a/app/src/main/java/to/bitkit/ui/settings/AboutScreen.kt b/app/src/main/java/to/bitkit/ui/settings/AboutScreen.kt deleted file mode 100644 index c2ee445a2..000000000 --- a/app/src/main/java/to/bitkit/ui/settings/AboutScreen.kt +++ /dev/null @@ -1,109 +0,0 @@ -package to.bitkit.ui.settings - -import android.content.Intent -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.HorizontalDivider -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.core.net.toUri -import to.bitkit.BuildConfig -import to.bitkit.R -import to.bitkit.env.Env -import to.bitkit.ui.components.BodyM -import to.bitkit.ui.components.VerticalSpacer -import to.bitkit.ui.components.settings.Links -import to.bitkit.ui.components.settings.SettingsButtonRow -import to.bitkit.ui.scaffold.AppTopBar -import to.bitkit.ui.scaffold.DrawerNavIcon -import to.bitkit.ui.scaffold.ScreenColumn -import to.bitkit.ui.shared.util.shareText -import to.bitkit.ui.theme.AppThemeSurface -import to.bitkit.ui.theme.Colors - -@Composable -fun AboutScreen( - onBack: () -> Unit, -) { - val context = LocalContext.current - - ScreenColumn { - AppTopBar( - titleText = stringResource(R.string.settings__about__title), - onBackClick = onBack, - actions = { DrawerNavIcon() }, - ) - - Column( - modifier = Modifier.padding(horizontal = 16.dp) - ) { - VerticalSpacer(32.dp) - - BodyM(text = stringResource(R.string.settings__about__text), color = Colors.White64) - - VerticalSpacer(32.dp) - - SettingsButtonRow(title = stringResource(R.string.settings__about__legal), onClick = { - val intent = Intent(Intent.ACTION_VIEW, Env.TERMS_OF_USE_URL.toUri()) - context.startActivity(intent) - }) - - SettingsButtonRow(title = stringResource(R.string.settings__about__share), onClick = { - shareText( - context, - context.getString(R.string.settings__about__shareText) - .replace("{appStoreUrl}", Env.APP_STORE_URL) - .replace("{playStoreUrl}", Env.PLAY_STORE_URL) - ) - }) - - VerticalSpacer(14.dp) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - BodyM(text = stringResource(R.string.settings__about__version)) - BodyM(text = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})", color = Colors.White50) - } - - VerticalSpacer(14.dp) - - HorizontalDivider() - - Image( - painter = painterResource(R.drawable.bitkit_logo), - contentDescription = null, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 32.dp) - .weight(1f) - .testTag("AboutLogo") - ) - - Links(modifier = Modifier.fillMaxWidth()) - - VerticalSpacer(16.dp) - } - } -} - -@Preview -@Composable -private fun Preview() { - AppThemeSurface { - AboutScreen( - onBack = {}, - ) - } -} diff --git a/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsScreen.kt deleted file mode 100644 index db5235cca..000000000 --- a/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsScreen.kt +++ /dev/null @@ -1,205 +0,0 @@ -package to.bitkit.ui.settings - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.navigation.NavController -import to.bitkit.R -import to.bitkit.ui.Routes -import to.bitkit.ui.components.VerticalSpacer -import to.bitkit.ui.components.settings.SectionHeader -import to.bitkit.ui.components.settings.SettingsButtonRow -import to.bitkit.ui.components.settings.SettingsButtonValue -import to.bitkit.ui.navigateTo -import to.bitkit.ui.navigateToHome -import to.bitkit.ui.scaffold.AppAlertDialog -import to.bitkit.ui.scaffold.AppTopBar -import to.bitkit.ui.scaffold.DrawerNavIcon -import to.bitkit.ui.scaffold.ScreenColumn -import to.bitkit.ui.theme.AppThemeSurface - -@Composable -fun AdvancedSettingsScreen( - navController: NavController, - viewModel: AdvancedSettingsViewModel = hiltViewModel(), -) { - var showResetSuggestionsDialog by remember { mutableStateOf(false) } - val selectedAddressTypeName by viewModel.selectedAddressTypeName.collectAsStateWithLifecycle() - - Content( - showResetSuggestionsDialog = showResetSuggestionsDialog, - selectedAddressTypeName = selectedAddressTypeName, - onBack = { navController.popBackStack() }, - onCoinSelectionClick = { - navController.navigateTo(Routes.CoinSelectPreference) - }, - onAddressTypePreferenceClick = { - navController.navigateTo(Routes.AddressTypePreference) - }, - onLightningConnectionsClick = { - navController.navigateTo(Routes.LightningConnections) - }, - onLightningNodeClick = { - navController.navigateTo(Routes.NodeInfo) - }, - onElectrumServerClick = { - navController.navigateTo(Routes.ElectrumConfig) - }, - onRgsServerClick = { - navController.navigateTo(Routes.RgsServer) - }, - onAddressViewerClick = { - navController.navigateTo(Routes.AddressViewer) - }, - onSuggestionsResetClick = { showResetSuggestionsDialog = true }, - onResetSuggestionsDialogConfirm = { - viewModel.resetSuggestions() - showResetSuggestionsDialog = false - navController.navigateToHome() - }, - onResetSuggestionsDialogCancel = { showResetSuggestionsDialog = false }, - ) -} - -@Composable -private fun Content( - showResetSuggestionsDialog: Boolean, - selectedAddressTypeName: String = "", - onBack: () -> Unit = {}, - onCoinSelectionClick: () -> Unit = {}, - onAddressTypePreferenceClick: () -> Unit = {}, - onLightningConnectionsClick: () -> Unit = {}, - onLightningNodeClick: () -> Unit = {}, - onElectrumServerClick: () -> Unit = {}, - onRgsServerClick: () -> Unit = {}, - onAddressViewerClick: () -> Unit = {}, - onSuggestionsResetClick: () -> Unit = {}, - onResetSuggestionsDialogConfirm: () -> Unit = {}, - onResetSuggestionsDialogCancel: () -> Unit = {}, -) { - ScreenColumn { - AppTopBar( - titleText = stringResource(R.string.settings__advanced_title), - onBackClick = onBack, - actions = { DrawerNavIcon() }, - ) - Column( - modifier = Modifier - .padding(horizontal = 16.dp) - .fillMaxSize() - .verticalScroll(rememberScrollState()) - .testTag("advanced_settings_screen") - ) { - // Payments Section - SectionHeader(title = stringResource(R.string.settings__adv__section_payments)) - - SettingsButtonRow( - title = stringResource(R.string.settings__addr_type__title), - value = if (selectedAddressTypeName.isNotEmpty()) { - SettingsButtonValue.StringValue(selectedAddressTypeName) - } else { - SettingsButtonValue.None - }, - onClick = onAddressTypePreferenceClick, - modifier = Modifier.testTag("AddressTypePreference"), - ) - - SettingsButtonRow( - title = stringResource(R.string.settings__adv__coin_selection), - onClick = onCoinSelectionClick, - modifier = Modifier.testTag("CoinSelectPreference"), - ) - - // Networks Section - SectionHeader(title = stringResource(R.string.settings__adv__section_networks)) - - SettingsButtonRow( - title = stringResource(R.string.settings__adv__lightning_connections), - onClick = onLightningConnectionsClick, - modifier = Modifier.testTag("Channels"), - ) - - SettingsButtonRow( - title = stringResource(R.string.settings__adv__lightning_node), - onClick = onLightningNodeClick, - modifier = Modifier.testTag("LightningNodeInfo"), - ) - - SettingsButtonRow( - title = stringResource(R.string.settings__adv__electrum_server), - onClick = onElectrumServerClick, - modifier = Modifier.testTag("ElectrumConfig"), - ) - - SettingsButtonRow( - title = stringResource(R.string.settings__adv__rgs_server), - onClick = onRgsServerClick, - modifier = Modifier.testTag("RGSServer"), - ) - - // Other Section - SectionHeader(title = stringResource(R.string.settings__adv__section_other)) - - SettingsButtonRow( - title = stringResource(R.string.settings__adv__address_viewer), - onClick = onAddressViewerClick, - modifier = Modifier.testTag("AddressViewer"), - ) - - SettingsButtonRow( - title = stringResource(R.string.settings__adv__suggestions_reset), - onClick = onSuggestionsResetClick, - modifier = Modifier.testTag("ResetSuggestions"), - ) - - VerticalSpacer(32.dp) - } - - if (showResetSuggestionsDialog) { - AppAlertDialog( - title = stringResource(R.string.settings__adv__reset_title), - text = stringResource(R.string.settings__adv__reset_desc), - confirmText = stringResource(R.string.settings__adv__reset_confirm), - onConfirm = onResetSuggestionsDialogConfirm, - onDismiss = onResetSuggestionsDialogCancel, - modifier = Modifier.testTag("reset_suggestions_dialog"), - ) - } - } -} - -@Preview -@Composable -private fun Preview() { - AppThemeSurface { - Content( - showResetSuggestionsDialog = false, - selectedAddressTypeName = "Taproot", - ) - } -} - -@Preview -@Composable -private fun PreviewDialog() { - AppThemeSurface { - Content( - showResetSuggestionsDialog = true, - selectedAddressTypeName = "Taproot", - ) - } -} diff --git a/app/src/main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt index cbafab83e..b872ec3d8 100644 --- a/app/src/main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt @@ -31,23 +31,15 @@ import to.bitkit.env.Env import to.bitkit.ext.toRelativeTimeString import to.bitkit.models.BackupCategory import to.bitkit.models.BackupItemStatus -import to.bitkit.ui.Routes -import to.bitkit.ui.appViewModel import to.bitkit.ui.backupsViewModel -import to.bitkit.ui.components.AuthCheckAction import to.bitkit.ui.components.BodyMSB import to.bitkit.ui.components.Caption13Up import to.bitkit.ui.components.CaptionB import to.bitkit.ui.components.FillWidth -import to.bitkit.ui.components.Sheet import to.bitkit.ui.components.VerticalSpacer -import to.bitkit.ui.components.settings.SettingsButtonRow -import to.bitkit.ui.navigateTo -import to.bitkit.ui.navigateToAuthCheck import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.DrawerNavIcon import to.bitkit.ui.scaffold.ScreenColumn -import to.bitkit.ui.settingsViewModel import to.bitkit.ui.shared.modifiers.clickableAlpha import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors @@ -60,23 +52,12 @@ import kotlin.time.ExperimentalTime fun BackupSettingsScreen( navController: NavController, ) { - val app = appViewModel ?: return - val settings = settingsViewModel ?: return val viewModel = backupsViewModel ?: return - val isPinEnabled by settings.isPinEnabled.collectAsStateWithLifecycle() val uiState by viewModel.uiState.collectAsStateWithLifecycle() BackupSettingsScreenContent( uiState = uiState, - onBackupClick = { app.showSheet(Sheet.Backup()) }, - onResetAndRestoreClick = { - if (isPinEnabled) { - navController.navigateToAuthCheck(onSuccessActionId = AuthCheckAction.NAV_TO_RESET) - } else { - navController.navigateTo(Routes.ResetAndRestoreSettings) - } - }, onRetryBackup = { category -> viewModel.retryBackup(category) }, onBack = { navController.popBackStack() }, ) @@ -85,15 +66,13 @@ fun BackupSettingsScreen( @Composable private fun BackupSettingsScreenContent( uiState: BackupStatusUiState, - onBackupClick: () -> Unit, - onResetAndRestoreClick: () -> Unit, onRetryBackup: (BackupCategory) -> Unit, onBack: () -> Unit, ) { val allSynced = uiState.categories.all { !it.status.isRequired } ScreenColumn { AppTopBar( - titleText = stringResource(R.string.settings__backup__title), + titleText = stringResource(R.string.settings__backup__data), onBackClick = onBack, actions = { DrawerNavIcon() }, ) @@ -103,17 +82,7 @@ private fun BackupSettingsScreenContent( .verticalScroll(rememberScrollState()) .testTag("BackupScrollView") ) { - SettingsButtonRow( - title = stringResource(R.string.settings__backup__wallet), - onClick = onBackupClick, - modifier = Modifier.testTag("BackupWallet"), - ) - SettingsButtonRow( - title = stringResource(R.string.settings__backup__reset), - onClick = onResetAndRestoreClick, - modifier = Modifier.testTag("ResetAndRestore"), - ) - VerticalSpacer(28.dp) + VerticalSpacer(16.dp) Row(verticalAlignment = Alignment.CenterVertically) { Caption13Up( @@ -263,8 +232,6 @@ private fun Preview() { AppThemeSurface { BackupSettingsScreenContent( uiState = BackupStatusUiState(categories = categories), - onBackupClick = {}, - onResetAndRestoreClick = {}, onRetryBackup = {}, onBack = {}, ) diff --git a/app/src/main/java/to/bitkit/ui/settings/SecuritySettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SecuritySettingsScreen.kt deleted file mode 100644 index 510baa93b..000000000 --- a/app/src/main/java/to/bitkit/ui/settings/SecuritySettingsScreen.kt +++ /dev/null @@ -1,225 +0,0 @@ -package to.bitkit.ui.settings - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.navigation.NavController -import to.bitkit.R -import to.bitkit.ui.appViewModel -import to.bitkit.ui.components.AuthCheckAction -import to.bitkit.ui.components.BodyS -import to.bitkit.ui.components.Sheet -import to.bitkit.ui.components.settings.SettingsButtonRow -import to.bitkit.ui.components.settings.SettingsButtonValue -import to.bitkit.ui.components.settings.SettingsSwitchRow -import to.bitkit.ui.navigateToAuthCheck -import to.bitkit.ui.navigateToChangePin -import to.bitkit.ui.navigateToDisablePin -import to.bitkit.ui.scaffold.AppTopBar -import to.bitkit.ui.scaffold.DrawerNavIcon -import to.bitkit.ui.scaffold.ScreenColumn -import to.bitkit.ui.settingsViewModel -import to.bitkit.ui.theme.AppThemeSurface -import to.bitkit.ui.theme.Colors -import to.bitkit.ui.utils.rememberBiometricAuthSupported - -@Composable -fun SecuritySettingsScreen( - navController: NavController, -) { - val settings = settingsViewModel ?: return - val app = appViewModel ?: return - - val isPinEnabled by settings.isPinEnabled.collectAsStateWithLifecycle() - val isBiometricEnabled by settings.isBiometricEnabled.collectAsStateWithLifecycle() - val isPinForPaymentsEnabled by settings.isPinForPaymentsEnabled.collectAsStateWithLifecycle() - val enableSwipeToHideBalance by settings.enableSwipeToHideBalance.collectAsStateWithLifecycle() - val hideBalanceOnOpen by settings.hideBalanceOnOpen.collectAsStateWithLifecycle() - val enableAutoReadClipboard by settings.enableAutoReadClipboard.collectAsStateWithLifecycle() - val enableSendAmountWarning by settings.enableSendAmountWarning.collectAsStateWithLifecycle() - - Content( - isPinEnabled = isPinEnabled, - isBiometricEnabled = isBiometricEnabled, - isPinForPaymentsEnabled = isPinForPaymentsEnabled, - enableSwipeToHideBalance = enableSwipeToHideBalance, - hideBalanceOnOpen = hideBalanceOnOpen, - enableAutoReadClipboard = enableAutoReadClipboard, - enableSendAmountWarning = enableSendAmountWarning, - isBiometrySupported = rememberBiometricAuthSupported(), - onPinClick = { - if (!isPinEnabled) { - app.showSheet(Sheet.Pin()) - } else { - navController.navigateToDisablePin() - } - }, - onChangePinClick = { - navController.navigateToChangePin() - }, - onPinForPaymentsClick = { - navController.navigateToAuthCheck( - onSuccessActionId = AuthCheckAction.TOGGLE_PIN_FOR_PAYMENTS, - ) - }, - onUseBiometricsClick = { - navController.navigateToAuthCheck( - requireBiometrics = true, - onSuccessActionId = AuthCheckAction.TOGGLE_BIOMETRICS, - ) - }, - onSwipeToHideBalanceClick = { - settings.setEnableSwipeToHideBalance(!enableSwipeToHideBalance) - }, - onHideBalanceOnOpenClick = { - settings.setHideBalanceOnOpen(!hideBalanceOnOpen) - }, - onAutoReadClipboardClick = { - settings.setEnableAutoReadClipboard(!enableAutoReadClipboard) - }, - onSendAmountWarningClick = { - settings.setEnableSendAmountWarning(!enableSendAmountWarning) - }, - onBackClick = { navController.popBackStack() }, - ) -} - -@Composable -private fun Content( - isPinEnabled: Boolean, - isBiometricEnabled: Boolean, - isPinForPaymentsEnabled: Boolean, - enableSwipeToHideBalance: Boolean, - hideBalanceOnOpen: Boolean, - enableAutoReadClipboard: Boolean, - enableSendAmountWarning: Boolean, - isBiometrySupported: Boolean, - onPinClick: () -> Unit = {}, - onChangePinClick: () -> Unit = {}, - onPinForPaymentsClick: () -> Unit = {}, - onUseBiometricsClick: () -> Unit = {}, - onSwipeToHideBalanceClick: () -> Unit = {}, - onHideBalanceOnOpenClick: () -> Unit = {}, - onAutoReadClipboardClick: () -> Unit = {}, - onSendAmountWarningClick: () -> Unit = {}, - onBackClick: () -> Unit = {}, -) { - ScreenColumn( - modifier = Modifier - .verticalScroll(rememberScrollState()) - ) { - AppTopBar( - titleText = stringResource(R.string.settings__security_title), - onBackClick = onBackClick, - actions = { DrawerNavIcon() }, - ) - Column( - modifier = Modifier.padding(horizontal = 16.dp) - ) { - SettingsSwitchRow( - title = stringResource(R.string.settings__security__swipe_balance_to_hide), - isChecked = enableSwipeToHideBalance, - onClick = onSwipeToHideBalanceClick, - modifier = Modifier.testTag("SwipeBalanceToHide"), - ) - - if (enableSwipeToHideBalance) { - SettingsSwitchRow( - title = stringResource(R.string.settings__security__hide_balance_on_open), - isChecked = hideBalanceOnOpen, - onClick = onHideBalanceOnOpenClick, - modifier = Modifier.testTag("HideBalanceOnOpen"), - ) - } - - SettingsSwitchRow( - title = stringResource(R.string.settings__security__clipboard), - isChecked = enableAutoReadClipboard, - onClick = onAutoReadClipboardClick, - modifier = Modifier.testTag("AutoReadClipboard"), - ) - - SettingsSwitchRow( - title = stringResource(R.string.settings__security__warn_100), - isChecked = enableSendAmountWarning, - onClick = onSendAmountWarningClick, - modifier = Modifier.testTag("SendAmountWarning"), - ) - - SettingsButtonRow( - title = stringResource(R.string.settings__security__pin), - value = SettingsButtonValue.StringValue( - stringResource( - if (isPinEnabled) { - R.string.settings__security__pin_enabled - } else { - R.string.settings__security__pin_disabled - } - ) - ), - onClick = onPinClick, - modifier = Modifier.testTag("PINCode"), - ) - if (isPinEnabled) { - SettingsButtonRow( - title = stringResource(R.string.settings__security__pin_change), - onClick = onChangePinClick, - modifier = Modifier.testTag("PINChange"), - ) - SettingsSwitchRow( - title = stringResource(R.string.settings__security__pin_payments), - isChecked = isPinForPaymentsEnabled, - onClick = onPinForPaymentsClick, - modifier = Modifier.testTag("EnablePinForPayments"), - ) - } - if (isPinEnabled && isBiometrySupported) { - SettingsSwitchRow( - title = run { - val bioTypeName = stringResource(R.string.security__bio) - stringResource(R.string.settings__security__use_bio).replace("{biometryTypeName}", bioTypeName) - }, - isChecked = isBiometricEnabled, - onClick = onUseBiometricsClick, - modifier = Modifier.testTag("UseBiometryInstead"), - ) - } - if (isPinEnabled && isBiometrySupported) { - BodyS( - text = run { - val bioTypeName = stringResource(R.string.security__bio) - stringResource(R.string.settings__security__footer).replace("{biometryTypeName}", bioTypeName) - }, - color = Colors.White64, - modifier = Modifier.padding(vertical = 16.dp) - ) - } - } - } -} - -@Preview -@Composable -private fun Preview() { - AppThemeSurface { - Content( - isPinEnabled = true, - isBiometricEnabled = false, - isPinForPaymentsEnabled = false, - enableSwipeToHideBalance = true, - hideBalanceOnOpen = false, - enableAutoReadClipboard = true, - enableSendAmountWarning = true, - isBiometrySupported = true, - ) - } -} diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index 587df3ef9..f4001e640 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -1,203 +1,629 @@ package to.bitkit.ui.settings -import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.hapticfeedback.HapticFeedbackType -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController import to.bitkit.R -import to.bitkit.models.Toast +import to.bitkit.models.PrimaryDisplay +import to.bitkit.models.TransactionSpeed +import to.bitkit.models.transactionSpeedUiText +import to.bitkit.ui.LocalCurrencies import to.bitkit.ui.Routes import to.bitkit.ui.appViewModel +import to.bitkit.ui.components.AuthCheckAction +import to.bitkit.ui.components.Sheet +import to.bitkit.ui.components.VerticalSpacer +import to.bitkit.ui.components.settings.SectionHeader import to.bitkit.ui.components.settings.SettingsButtonRow +import to.bitkit.ui.components.settings.SettingsButtonValue +import to.bitkit.ui.components.settings.SettingsSwitchRow import to.bitkit.ui.navigateTo -import to.bitkit.ui.navigateToAboutSettings -import to.bitkit.ui.navigateToAdvancedSettings -import to.bitkit.ui.navigateToBackupSettings +import to.bitkit.ui.navigateToAuthCheck +import to.bitkit.ui.navigateToDefaultUnitSettings import to.bitkit.ui.navigateToDevSettings -import to.bitkit.ui.navigateToGeneralSettings -import to.bitkit.ui.navigateToSecuritySettings +import to.bitkit.ui.navigateToLanguageSettings +import to.bitkit.ui.navigateToLocalCurrencySettings +import to.bitkit.ui.navigateToPinManagement +import to.bitkit.ui.navigateToQuickPaySettings +import to.bitkit.ui.navigateToTagsSettings +import to.bitkit.ui.navigateToTransactionSpeedSettings +import to.bitkit.ui.navigateToWidgetsSettings import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.DrawerNavIcon import to.bitkit.ui.scaffold.ScreenColumn +import to.bitkit.ui.screens.wallets.activity.components.CustomTabRowWithSpacing +import to.bitkit.ui.screens.wallets.activity.components.TabItem import to.bitkit.ui.settingsViewModel -import to.bitkit.ui.shared.modifiers.clickableAlpha import to.bitkit.ui.theme.AppThemeSurface +import to.bitkit.ui.utils.rememberBiometricAuthSupported +import to.bitkit.viewmodels.LanguageViewModel -private const val DEV_MODE_TAP_THRESHOLD = 5 +private enum class SettingsTab(private val titleRes: Int) : TabItem { + General(R.string.settings__general_title), + Security(R.string.settings__security_title), + Advanced(R.string.settings__advanced_title); + + override val uiText @Composable get() = stringResource(titleRes) +} @Composable fun SettingsScreen( navController: NavController, + advancedViewModel: AdvancedSettingsViewModel = hiltViewModel(), + languageViewModel: LanguageViewModel = hiltViewModel(), ) { val app = appViewModel ?: return val settings = settingsViewModel ?: return + val currencies = LocalCurrencies.current + + // General tab state + val defaultTransactionSpeed by settings.defaultTransactionSpeed.collectAsStateWithLifecycle() + val lastUsedTags by settings.lastUsedTags.collectAsStateWithLifecycle() + val quickPayIntroSeen by settings.quickPayIntroSeen.collectAsStateWithLifecycle() + val bgPaymentsIntroSeen by settings.bgPaymentsIntroSeen.collectAsStateWithLifecycle() + val notificationsGranted by settings.notificationsGranted.collectAsStateWithLifecycle() + val languageUiState by languageViewModel.uiState.collectAsStateWithLifecycle() + + // Security tab state + val isPinEnabled by settings.isPinEnabled.collectAsStateWithLifecycle() + val isBiometricEnabled by settings.isBiometricEnabled.collectAsStateWithLifecycle() + val isPinForPaymentsEnabled by settings.isPinForPaymentsEnabled.collectAsStateWithLifecycle() + val enableSwipeToHideBalance by settings.enableSwipeToHideBalance.collectAsStateWithLifecycle() + val hideBalanceOnOpen by settings.hideBalanceOnOpen.collectAsStateWithLifecycle() + val enableAutoReadClipboard by settings.enableAutoReadClipboard.collectAsStateWithLifecycle() + val enableSendAmountWarning by settings.enableSendAmountWarning.collectAsStateWithLifecycle() + + // Advanced tab state val isDevModeEnabled by settings.isDevModeEnabled.collectAsStateWithLifecycle() - var enableDevModeTapCount by remember { mutableIntStateOf(0) } - val haptic = LocalHapticFeedback.current - val context = LocalContext.current + val selectedAddressTypeName by advancedViewModel.selectedAddressTypeName.collectAsStateWithLifecycle() - SettingsScreenContent( - isDevModeEnabled = isDevModeEnabled, - onGeneralClick = { navController.navigateToGeneralSettings() }, - onSecurityClick = { navController.navigateToSecuritySettings() }, - onBackupClick = { navController.navigateToBackupSettings() }, - onAdvancedClick = { navController.navigateToAdvancedSettings() }, - onSupportClick = { navController.navigateTo(Routes.Support) }, - onAboutClick = { navController.navigateToAboutSettings() }, - onDevClick = { navController.navigateToDevSettings() }, - onBackClick = { navController.popBackStack() }, - onCogTap = { - haptic.performHapticFeedback(HapticFeedbackType.Confirm) - enableDevModeTapCount = enableDevModeTapCount + 1 - - if (enableDevModeTapCount >= DEV_MODE_TAP_THRESHOLD) { - val newValue = !isDevModeEnabled - settings.setIsDevModeEnabled(newValue) - haptic.performHapticFeedback(HapticFeedbackType.LongPress) - - app.toast( - type = Toast.ToastType.SUCCESS, - title = context.getString( - if (newValue) { - R.string.settings__dev_enabled_title - } else { - R.string.settings__dev_disabled_title - } - ), - description = context.getString( - if (newValue) { - R.string.settings__dev_enabled_message - } else { - R.string.settings__dev_disabled_message - } - ), - ) - enableDevModeTapCount = 0 + LaunchedEffect(Unit) { languageViewModel.fetchLanguageInfo() } + + SettingsContent( + // General + selectedCurrency = currencies.selectedCurrency, + primaryDisplay = currencies.primaryDisplay, + defaultTransactionSpeed = defaultTransactionSpeed, + selectedLanguage = languageUiState.selectedLanguage.displayName, + showTagsButton = lastUsedTags.isNotEmpty(), + notificationsGranted = notificationsGranted, + onLanguageClick = { navController.navigateToLanguageSettings() }, + onLocalCurrencyClick = { navController.navigateToLocalCurrencySettings() }, + onDefaultUnitClick = { navController.navigateToDefaultUnitSettings() }, + onWidgetsClick = { navController.navigateToWidgetsSettings() }, + onTagsClick = { navController.navigateToTagsSettings() }, + onTransactionSpeedClick = { navController.navigateToTransactionSpeedSettings() }, + onQuickPayClick = { navController.navigateToQuickPaySettings(quickPayIntroSeen) }, + onBgPaymentsClick = { + if (bgPaymentsIntroSeen || notificationsGranted) { + navController.navigateTo(Routes.BackgroundPaymentsSettings) + } else { + navController.navigateTo(Routes.BackgroundPaymentsIntro) + } + }, + // Security + isPinEnabled = isPinEnabled, + isBiometricEnabled = isBiometricEnabled, + isPinForPaymentsEnabled = isPinForPaymentsEnabled, + enableSwipeToHideBalance = enableSwipeToHideBalance, + hideBalanceOnOpen = hideBalanceOnOpen, + enableAutoReadClipboard = enableAutoReadClipboard, + enableSendAmountWarning = enableSendAmountWarning, + isBiometrySupported = rememberBiometricAuthSupported(), + onBackupWalletClick = { app.showSheet(Sheet.Backup()) }, + onDataBackupsClick = { navController.navigateTo(Routes.BackupSettings) }, + onResetWalletClick = { + if (isPinEnabled) { + navController.navigateToAuthCheck(onSuccessActionId = AuthCheckAction.NAV_TO_RESET) + } else { + navController.navigateTo(Routes.ResetAndRestoreSettings) } }, + onPinClick = { navController.navigateToPinManagement() }, + onPinForPaymentsClick = { + navController.navigateToAuthCheck( + onSuccessActionId = AuthCheckAction.TOGGLE_PIN_FOR_PAYMENTS, + ) + }, + onUseBiometricsClick = { + navController.navigateToAuthCheck( + requireBiometrics = true, + onSuccessActionId = AuthCheckAction.TOGGLE_BIOMETRICS, + ) + }, + onSwipeToHideBalanceClick = { settings.setEnableSwipeToHideBalance(!enableSwipeToHideBalance) }, + onHideBalanceOnOpenClick = { settings.setHideBalanceOnOpen(!hideBalanceOnOpen) }, + onAutoReadClipboardClick = { settings.setEnableAutoReadClipboard(!enableAutoReadClipboard) }, + onSendAmountWarningClick = { settings.setEnableSendAmountWarning(!enableSendAmountWarning) }, + // Advanced + isDevModeEnabled = isDevModeEnabled, + selectedAddressTypeName = selectedAddressTypeName, + onDevSettingsClick = { navController.navigateToDevSettings() }, + onAddressTypeClick = { navController.navigateTo(Routes.AddressTypePreference) }, + onCoinSelectionClick = { navController.navigateTo(Routes.CoinSelectPreference) }, + onAddressViewerClick = { navController.navigateTo(Routes.AddressViewer) }, + onLightningConnectionsClick = { navController.navigateTo(Routes.LightningConnections) }, + onLightningNodeClick = { navController.navigateTo(Routes.NodeInfo) }, + onElectrumServerClick = { navController.navigateTo(Routes.ElectrumConfig) }, + onRgsServerClick = { navController.navigateTo(Routes.RgsServer) }, + // Navigation + onBackClick = { navController.popBackStack() }, ) } +@Suppress("LongParameterList", "LongMethod") @Composable -fun SettingsScreenContent( - isDevModeEnabled: Boolean, - onGeneralClick: () -> Unit, - onSecurityClick: () -> Unit, - onBackupClick: () -> Unit, - onAdvancedClick: () -> Unit, - onSupportClick: () -> Unit, - onAboutClick: () -> Unit, - onDevClick: () -> Unit, - onCogTap: () -> Unit, - onBackClick: () -> Unit, +private fun SettingsContent( + // General + selectedCurrency: String = "USD", + primaryDisplay: PrimaryDisplay = PrimaryDisplay.BITCOIN, + defaultTransactionSpeed: TransactionSpeed = TransactionSpeed.Medium, + selectedLanguage: String = "", + showTagsButton: Boolean = false, + notificationsGranted: Boolean = false, + onLanguageClick: () -> Unit = {}, + onLocalCurrencyClick: () -> Unit = {}, + onDefaultUnitClick: () -> Unit = {}, + onWidgetsClick: () -> Unit = {}, + onTagsClick: () -> Unit = {}, + onTransactionSpeedClick: () -> Unit = {}, + onQuickPayClick: () -> Unit = {}, + onBgPaymentsClick: () -> Unit = {}, + // Security + isPinEnabled: Boolean = false, + isBiometricEnabled: Boolean = false, + isPinForPaymentsEnabled: Boolean = false, + enableSwipeToHideBalance: Boolean = false, + hideBalanceOnOpen: Boolean = false, + enableAutoReadClipboard: Boolean = true, + enableSendAmountWarning: Boolean = true, + isBiometrySupported: Boolean = false, + onBackupWalletClick: () -> Unit = {}, + onDataBackupsClick: () -> Unit = {}, + onResetWalletClick: () -> Unit = {}, + onPinClick: () -> Unit = {}, + onPinForPaymentsClick: () -> Unit = {}, + onUseBiometricsClick: () -> Unit = {}, + onSwipeToHideBalanceClick: () -> Unit = {}, + onHideBalanceOnOpenClick: () -> Unit = {}, + onAutoReadClipboardClick: () -> Unit = {}, + onSendAmountWarningClick: () -> Unit = {}, + // Advanced + isDevModeEnabled: Boolean = false, + selectedAddressTypeName: String = "", + onDevSettingsClick: () -> Unit = {}, + onAddressTypeClick: () -> Unit = {}, + onCoinSelectionClick: () -> Unit = {}, + onAddressViewerClick: () -> Unit = {}, + onLightningConnectionsClick: () -> Unit = {}, + onLightningNodeClick: () -> Unit = {}, + onElectrumServerClick: () -> Unit = {}, + onRgsServerClick: () -> Unit = {}, + // Navigation + onBackClick: () -> Unit = {}, ) { + var selectedTab by remember { mutableStateOf(SettingsTab.General) } + val tabs = remember { SettingsTab.entries } + ScreenColumn { AppTopBar( titleText = stringResource(R.string.settings__settings), onBackClick = onBackClick, actions = { DrawerNavIcon() }, ) - Column( - modifier = Modifier - .padding(horizontal = 16.dp) - .fillMaxSize() - .verticalScroll(rememberScrollState()) - ) { - SettingsButtonRow( - title = stringResource(R.string.settings__general_title), - iconRes = R.drawable.ic_settings_general, - onClick = onGeneralClick, - modifier = Modifier.testTag("GeneralSettings") - ) - SettingsButtonRow( - title = stringResource(R.string.settings__security_title), - iconRes = R.drawable.ic_settings_security, - onClick = onSecurityClick, - modifier = Modifier.testTag("SecuritySettings") + + CustomTabRowWithSpacing( + tabs = tabs, + currentTabIndex = tabs.indexOf(selectedTab), + onTabChange = { selectedTab = it }, + modifier = Modifier.padding(horizontal = 16.dp), + ) + + when (selectedTab) { + SettingsTab.General -> GeneralTabContent( + selectedCurrency = selectedCurrency, + primaryDisplay = primaryDisplay, + defaultTransactionSpeed = defaultTransactionSpeed, + selectedLanguage = selectedLanguage, + showTagsButton = showTagsButton, + notificationsGranted = notificationsGranted, + onLanguageClick = onLanguageClick, + onLocalCurrencyClick = onLocalCurrencyClick, + onDefaultUnitClick = onDefaultUnitClick, + onWidgetsClick = onWidgetsClick, + onTagsClick = onTagsClick, + onTransactionSpeedClick = onTransactionSpeedClick, + onQuickPayClick = onQuickPayClick, + onBgPaymentsClick = onBgPaymentsClick, ) - SettingsButtonRow( - title = stringResource(R.string.settings__backup_title), - iconRes = R.drawable.ic_settings_backup, - onClick = onBackupClick, - modifier = Modifier.testTag("BackupSettings") + + SettingsTab.Security -> SecurityTabContent( + isPinEnabled = isPinEnabled, + isBiometricEnabled = isBiometricEnabled, + isPinForPaymentsEnabled = isPinForPaymentsEnabled, + enableSwipeToHideBalance = enableSwipeToHideBalance, + hideBalanceOnOpen = hideBalanceOnOpen, + enableAutoReadClipboard = enableAutoReadClipboard, + enableSendAmountWarning = enableSendAmountWarning, + isBiometrySupported = isBiometrySupported, + onBackupWalletClick = onBackupWalletClick, + onDataBackupsClick = onDataBackupsClick, + onResetWalletClick = onResetWalletClick, + onPinClick = onPinClick, + onPinForPaymentsClick = onPinForPaymentsClick, + onUseBiometricsClick = onUseBiometricsClick, + onSwipeToHideBalanceClick = onSwipeToHideBalanceClick, + onHideBalanceOnOpenClick = onHideBalanceOnOpenClick, + onAutoReadClipboardClick = onAutoReadClipboardClick, + onSendAmountWarningClick = onSendAmountWarningClick, ) - SettingsButtonRow( - title = stringResource(R.string.settings__advanced_title), - iconRes = R.drawable.ic_settings_advanced, - onClick = onAdvancedClick, - modifier = Modifier.testTag("AdvancedSettings") + + SettingsTab.Advanced -> AdvancedTabContent( + isDevModeEnabled = isDevModeEnabled, + selectedAddressTypeName = selectedAddressTypeName, + onDevSettingsClick = onDevSettingsClick, + onAddressTypeClick = onAddressTypeClick, + onCoinSelectionClick = onCoinSelectionClick, + onAddressViewerClick = onAddressViewerClick, + onLightningConnectionsClick = onLightningConnectionsClick, + onLightningNodeClick = onLightningNodeClick, + onElectrumServerClick = onElectrumServerClick, + onRgsServerClick = onRgsServerClick, ) + } + } +} + +@Composable +private fun GeneralTabContent( + selectedCurrency: String, + primaryDisplay: PrimaryDisplay, + defaultTransactionSpeed: TransactionSpeed, + selectedLanguage: String, + showTagsButton: Boolean, + notificationsGranted: Boolean, + onLanguageClick: () -> Unit, + onLocalCurrencyClick: () -> Unit, + onDefaultUnitClick: () -> Unit, + onWidgetsClick: () -> Unit, + onTagsClick: () -> Unit, + onTransactionSpeedClick: () -> Unit, + onQuickPayClick: () -> Unit, + onBgPaymentsClick: () -> Unit, +) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()) + ) { + // Interface section + SectionHeader(title = stringResource(R.string.settings__general__section_interface)) + + SettingsButtonRow( + title = stringResource(R.string.settings__language_title), + iconRes = R.drawable.ic_warning, + value = SettingsButtonValue.StringValue(selectedLanguage), + onClick = onLanguageClick, + modifier = Modifier.testTag("LanguageSettings"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__general__currency_local), + iconRes = R.drawable.ic_coins, + value = SettingsButtonValue.StringValue(selectedCurrency), + onClick = onLocalCurrencyClick, + modifier = Modifier.testTag("CurrenciesSettings"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__general__unit), + iconRes = R.drawable.ic_coins, + value = SettingsButtonValue.StringValue( + when (primaryDisplay) { + PrimaryDisplay.BITCOIN -> stringResource(R.string.settings__general__unit_bitcoin) + PrimaryDisplay.FIAT -> selectedCurrency + } + ), + onClick = onDefaultUnitClick, + modifier = Modifier.testTag("UnitSettings"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__widgets__nav_title), + iconRes = R.drawable.ic_warning, + onClick = onWidgetsClick, + modifier = Modifier.testTag("WidgetsSettings"), + ) + if (showTagsButton) { SettingsButtonRow( - title = stringResource(R.string.settings__support_title), - iconRes = R.drawable.ic_settings_support, - onClick = onSupportClick, - modifier = Modifier.testTag("Support") + title = stringResource(R.string.settings__general__tags), + iconRes = R.drawable.ic_tag, + onClick = onTagsClick, + modifier = Modifier.testTag("TagsSettings"), ) - SettingsButtonRow( - title = stringResource(R.string.settings__about_title), - iconRes = R.drawable.ic_settings_about, - onClick = onAboutClick, - modifier = Modifier.testTag("About") + } + + // Payments section + SectionHeader( + title = stringResource(R.string.settings__general__section_payments), + padding = androidx.compose.foundation.layout.PaddingValues(top = 16.dp), + ) + + SettingsButtonRow( + title = stringResource(R.string.settings__general__speed), + iconRes = R.drawable.ic_speed_normal, + value = SettingsButtonValue.StringValue(defaultTransactionSpeed.transactionSpeedUiText()), + onClick = onTransactionSpeedClick, + modifier = Modifier.testTag("TransactionSpeedSettings"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__quickpay__nav_title), + iconRes = R.drawable.ic_warning, + onClick = onQuickPayClick, + modifier = Modifier.testTag("QuickpaySettings"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__bg__title), + iconRes = R.drawable.ic_bell, + value = SettingsButtonValue.StringValue( + stringResource(if (notificationsGranted) R.string.settings__bg__on else R.string.settings__bg__off) + ), + onClick = onBgPaymentsClick, + modifier = Modifier.testTag("BackgroundPaymentSettings"), + ) + + VerticalSpacer(32.dp) + } +} + +@Suppress("LongParameterList") +@Composable +private fun SecurityTabContent( + isPinEnabled: Boolean, + isBiometricEnabled: Boolean, + isPinForPaymentsEnabled: Boolean, + enableSwipeToHideBalance: Boolean, + hideBalanceOnOpen: Boolean, + enableAutoReadClipboard: Boolean, + enableSendAmountWarning: Boolean, + isBiometrySupported: Boolean, + onBackupWalletClick: () -> Unit, + onDataBackupsClick: () -> Unit, + onResetWalletClick: () -> Unit, + onPinClick: () -> Unit, + onPinForPaymentsClick: () -> Unit, + onUseBiometricsClick: () -> Unit, + onSwipeToHideBalanceClick: () -> Unit, + onHideBalanceOnOpenClick: () -> Unit, + onAutoReadClipboardClick: () -> Unit, + onSendAmountWarningClick: () -> Unit, +) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()) + ) { + // Back up or reset section + SectionHeader(title = stringResource(R.string.settings__security__section_backup)) + + SettingsButtonRow( + title = stringResource(R.string.settings__backup__wallet), + iconRes = R.drawable.ic_warning, + onClick = onBackupWalletClick, + modifier = Modifier.testTag("BackupWallet"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__backup__data), + iconRes = R.drawable.ic_warning, + onClick = onDataBackupsClick, + modifier = Modifier.testTag("BackupSettings"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__backup__reset), + iconRes = R.drawable.ic_warning, + onClick = onResetWalletClick, + modifier = Modifier.testTag("ResetAndRestore"), + ) + + // Safety section + SectionHeader( + title = stringResource(R.string.settings__security__section_safety), + padding = androidx.compose.foundation.layout.PaddingValues(top = 16.dp), + ) + + SettingsButtonRow( + title = stringResource(R.string.settings__security__pin), + iconRes = R.drawable.ic_warning, + value = SettingsButtonValue.StringValue( + stringResource( + if (isPinEnabled) { + R.string.settings__security__pin_enabled + } else { + R.string.settings__security__pin_disabled + } + ) + ), + onClick = onPinClick, + modifier = Modifier.testTag("PINCode"), + ) + + if (isPinEnabled) { + SettingsSwitchRow( + title = stringResource(R.string.settings__security__pin_payments), + iconRes = R.drawable.ic_coins, + isChecked = isPinForPaymentsEnabled, + onClick = onPinForPaymentsClick, + modifier = Modifier.testTag("EnablePinForPayments"), ) - if (isDevModeEnabled) { - SettingsButtonRow( - title = stringResource(R.string.settings__dev_title), - iconRes = R.drawable.ic_settings_dev, - onClick = onDevClick, - modifier = Modifier.testTag("DevSettings") + + if (isBiometrySupported) { + SettingsSwitchRow( + title = run { + val bioTypeName = stringResource(R.string.security__bio) + stringResource(R.string.settings__security__use_bio) + .replace("{biometryTypeName}", bioTypeName) + }, + iconRes = R.drawable.ic_warning, + isChecked = isBiometricEnabled, + onClick = onUseBiometricsClick, + modifier = Modifier.testTag("UseBiometryInstead"), ) } - Spacer(Modifier.weight(1f)) - Image( - painter = painterResource(R.drawable.cog), - contentDescription = null, - modifier = Modifier - .fillMaxWidth() - .height(256.dp) - .clickableAlpha(1f) { onCogTap() } - .testTag("DevOptions") + } + + SettingsSwitchRow( + title = stringResource(R.string.settings__security__warn_100), + iconRes = R.drawable.ic_warning, + isChecked = enableSendAmountWarning, + onClick = onSendAmountWarningClick, + modifier = Modifier.testTag("SendAmountWarning"), + ) + + // Privacy section + SectionHeader( + title = stringResource(R.string.settings__security__section_privacy), + padding = androidx.compose.foundation.layout.PaddingValues(top = 16.dp), + ) + + SettingsSwitchRow( + title = stringResource(R.string.settings__security__swipe_balance_to_hide), + iconRes = R.drawable.ic_warning, + isChecked = enableSwipeToHideBalance, + onClick = onSwipeToHideBalanceClick, + modifier = Modifier.testTag("SwipeBalanceToHide"), + ) + SettingsSwitchRow( + title = stringResource(R.string.settings__security__hide_balance_on_open), + iconRes = R.drawable.ic_warning, + isChecked = hideBalanceOnOpen, + onClick = onHideBalanceOnOpenClick, + modifier = Modifier.testTag("HideBalanceOnOpen"), + ) + SettingsSwitchRow( + title = stringResource(R.string.settings__security__clipboard), + iconRes = R.drawable.ic_clipboard_text, + isChecked = enableAutoReadClipboard, + onClick = onAutoReadClipboardClick, + modifier = Modifier.testTag("AutoReadClipboard"), + ) + + VerticalSpacer(32.dp) + } +} + +@Composable +private fun AdvancedTabContent( + isDevModeEnabled: Boolean, + selectedAddressTypeName: String, + onDevSettingsClick: () -> Unit, + onAddressTypeClick: () -> Unit, + onCoinSelectionClick: () -> Unit, + onAddressViewerClick: () -> Unit, + onLightningConnectionsClick: () -> Unit, + onLightningNodeClick: () -> Unit, + onElectrumServerClick: () -> Unit, + onRgsServerClick: () -> Unit, +) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()) + .testTag("advanced_settings_screen") + ) { + // Debug section (only if dev mode enabled) + if (isDevModeEnabled) { + SectionHeader(title = stringResource(R.string.settings__adv__section_debug)) + + SettingsButtonRow( + title = stringResource(R.string.settings__dev_title), + iconRes = R.drawable.ic_settings_dev, + onClick = onDevSettingsClick, + modifier = Modifier.testTag("DevSettings"), ) - Spacer(Modifier.weight(1f)) } + + // Payments section + SectionHeader(title = stringResource(R.string.settings__adv__section_payments)) + + SettingsButtonRow( + title = stringResource(R.string.settings__addr_type__title), + iconRes = R.drawable.ic_warning, + value = if (selectedAddressTypeName.isNotEmpty()) { + SettingsButtonValue.StringValue(selectedAddressTypeName) + } else { + SettingsButtonValue.None + }, + onClick = onAddressTypeClick, + modifier = Modifier.testTag("AddressTypePreference"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__adv__coin_selection), + iconRes = R.drawable.ic_coins, + onClick = onCoinSelectionClick, + modifier = Modifier.testTag("CoinSelectPreference"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__adv__address_viewer), + iconRes = R.drawable.ic_eye, + onClick = onAddressViewerClick, + modifier = Modifier.testTag("AddressViewer"), + ) + + // Networks section + SectionHeader( + title = stringResource(R.string.settings__adv__section_networks), + padding = androidx.compose.foundation.layout.PaddingValues(top = 16.dp), + ) + + SettingsButtonRow( + title = stringResource(R.string.settings__adv__lightning_connections), + iconRes = R.drawable.ic_git_branch, + onClick = onLightningConnectionsClick, + modifier = Modifier.testTag("Channels"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__adv__lightning_node), + iconRes = R.drawable.ic_git_branch, + onClick = onLightningNodeClick, + modifier = Modifier.testTag("LightningNodeInfo"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__adv__electrum_server), + iconRes = R.drawable.ic_warning, + onClick = onElectrumServerClick, + modifier = Modifier.testTag("ElectrumConfig"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__adv__rgs_server), + iconRes = R.drawable.ic_broadcast, + onClick = onRgsServerClick, + modifier = Modifier.testTag("RGSServer"), + ) + + VerticalSpacer(32.dp) } } @Preview(showBackground = true) @Composable -private fun Preview() { +private fun PreviewGeneral() { AppThemeSurface { - SettingsScreenContent( - isDevModeEnabled = true, - onGeneralClick = {}, - onSecurityClick = {}, - onBackupClick = {}, - onAdvancedClick = {}, - onSupportClick = {}, - onAboutClick = {}, - onDevClick = {}, - onCogTap = {}, - onBackClick = {}, - ) + SettingsContent() } } diff --git a/app/src/main/java/to/bitkit/ui/settings/general/GeneralSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/general/GeneralSettingsScreen.kt deleted file mode 100644 index 1bafb0693..000000000 --- a/app/src/main/java/to/bitkit/ui/settings/general/GeneralSettingsScreen.kt +++ /dev/null @@ -1,182 +0,0 @@ -package to.bitkit.ui.settings.general - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.navigation.NavController -import to.bitkit.R -import to.bitkit.models.Language -import to.bitkit.models.PrimaryDisplay -import to.bitkit.models.TransactionSpeed -import to.bitkit.models.transactionSpeedUiText -import to.bitkit.ui.LocalCurrencies -import to.bitkit.ui.Routes -import to.bitkit.ui.components.settings.SettingsButtonRow -import to.bitkit.ui.components.settings.SettingsButtonValue -import to.bitkit.ui.navigateTo -import to.bitkit.ui.navigateToDefaultUnitSettings -import to.bitkit.ui.navigateToLanguageSettings -import to.bitkit.ui.navigateToLocalCurrencySettings -import to.bitkit.ui.navigateToQuickPaySettings -import to.bitkit.ui.navigateToTagsSettings -import to.bitkit.ui.navigateToTransactionSpeedSettings -import to.bitkit.ui.navigateToWidgetsSettings -import to.bitkit.ui.scaffold.AppTopBar -import to.bitkit.ui.scaffold.DrawerNavIcon -import to.bitkit.ui.scaffold.ScreenColumn -import to.bitkit.ui.settingsViewModel -import to.bitkit.ui.theme.AppThemeSurface -import to.bitkit.viewmodels.LanguageViewModel - -@Composable -fun GeneralSettingsScreen( - navController: NavController, - languageViewModel: LanguageViewModel = hiltViewModel(), -) { - val settings = settingsViewModel ?: return - val currencies = LocalCurrencies.current - val defaultTransactionSpeed by settings.defaultTransactionSpeed.collectAsStateWithLifecycle() - val lastUsedTags by settings.lastUsedTags.collectAsStateWithLifecycle() - val quickPayIntroSeen by settings.quickPayIntroSeen.collectAsStateWithLifecycle() - val bgPaymentsIntroSeen by settings.bgPaymentsIntroSeen.collectAsStateWithLifecycle() - val notificationsGranted by settings.notificationsGranted.collectAsStateWithLifecycle() - val languageUiState by languageViewModel.uiState.collectAsStateWithLifecycle() - - LaunchedEffect(Unit) { languageViewModel.fetchLanguageInfo() } - - GeneralSettingsContent( - selectedCurrency = currencies.selectedCurrency, - primaryDisplay = currencies.primaryDisplay, - defaultTransactionSpeed = defaultTransactionSpeed, - showTagsButton = lastUsedTags.isNotEmpty(), - onBackClick = { navController.popBackStack() }, - onLocalCurrencyClick = { navController.navigateToLocalCurrencySettings() }, - onDefaultUnitClick = { navController.navigateToDefaultUnitSettings() }, - onTransactionSpeedClick = { navController.navigateToTransactionSpeedSettings() }, - onWidgetsClick = { navController.navigateToWidgetsSettings() }, - onQuickPayClick = { navController.navigateToQuickPaySettings(quickPayIntroSeen) }, - onTagsClick = { navController.navigateToTagsSettings() }, - onLanguageSettingsClick = { navController.navigateToLanguageSettings() }, - onBgPaymentsClick = { - if (bgPaymentsIntroSeen || notificationsGranted) { - navController.navigateTo(Routes.BackgroundPaymentsSettings) - } else { - navController.navigateTo(Routes.BackgroundPaymentsIntro) - } - }, - selectedLanguage = languageUiState.selectedLanguage.displayName, - notificationsGranted = notificationsGranted - ) -} - -@Composable -private fun GeneralSettingsContent( - selectedCurrency: String, - primaryDisplay: PrimaryDisplay, - defaultTransactionSpeed: TransactionSpeed, - selectedLanguage: String, - notificationsGranted: Boolean, - showTagsButton: Boolean = false, - onBackClick: () -> Unit = {}, - onLocalCurrencyClick: () -> Unit = {}, - onDefaultUnitClick: () -> Unit = {}, - onTransactionSpeedClick: () -> Unit = {}, - onWidgetsClick: () -> Unit = {}, - onQuickPayClick: () -> Unit = {}, - onLanguageSettingsClick: () -> Unit = {}, - onTagsClick: () -> Unit = {}, - onBgPaymentsClick: () -> Unit = {}, -) { - ScreenColumn { - AppTopBar( - titleText = stringResource(R.string.settings__general_title), - onBackClick = onBackClick, - actions = { DrawerNavIcon() }, - ) - Column( - modifier = Modifier - .padding(horizontal = 16.dp) - .verticalScroll(rememberScrollState()) - ) { - SettingsButtonRow( - title = stringResource(R.string.settings__language_title), - value = SettingsButtonValue.StringValue(selectedLanguage), - onClick = onLanguageSettingsClick, - modifier = Modifier.testTag("LanguageSettings") - ) - SettingsButtonRow( - title = stringResource(R.string.settings__general__currency_local), - value = SettingsButtonValue.StringValue(selectedCurrency), - onClick = onLocalCurrencyClick, - modifier = Modifier.testTag("CurrenciesSettings") - ) - SettingsButtonRow( - title = stringResource(R.string.settings__general__unit), - value = SettingsButtonValue.StringValue( - when (primaryDisplay) { - PrimaryDisplay.BITCOIN -> stringResource(R.string.settings__general__unit_bitcoin) - PrimaryDisplay.FIAT -> selectedCurrency - } - ), - onClick = onDefaultUnitClick, - modifier = Modifier.testTag("UnitSettings") - ) - SettingsButtonRow( - title = stringResource(R.string.settings__general__speed), - value = SettingsButtonValue.StringValue(defaultTransactionSpeed.transactionSpeedUiText()), - onClick = onTransactionSpeedClick, - modifier = Modifier.testTag("TransactionSpeedSettings") - ) - if (showTagsButton) { - SettingsButtonRow( - title = stringResource(R.string.settings__general__tags), - onClick = onTagsClick, - modifier = Modifier.testTag("TagsSettings") - ) - } - SettingsButtonRow( - title = stringResource(R.string.settings__widgets__nav_title), - onClick = onWidgetsClick, - modifier = Modifier.testTag("WidgetsSettings") - ) - SettingsButtonRow( - title = stringResource(R.string.settings__quickpay__nav_title), - onClick = onQuickPayClick, - modifier = Modifier.testTag("QuickpaySettings") - ) - SettingsButtonRow( - title = stringResource(R.string.settings__bg__title), - onClick = onBgPaymentsClick, - value = SettingsButtonValue.StringValue( - stringResource(if (notificationsGranted) R.string.settings__bg__on else R.string.settings__bg__off) - ), - modifier = Modifier.testTag("BackgroundPaymentSettings") - ) - } - } -} - -@Preview(showBackground = true) -@Composable -private fun Preview() { - AppThemeSurface { - GeneralSettingsContent( - selectedCurrency = "USD", - primaryDisplay = PrimaryDisplay.BITCOIN, - defaultTransactionSpeed = TransactionSpeed.Medium, - selectedLanguage = Language.SYSTEM_DEFAULT.displayName, - notificationsGranted = true, - ) - } -} diff --git a/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt index 9a860baa4..bcf01e555 100644 --- a/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt @@ -2,16 +2,25 @@ package to.bitkit.ui.settings.general import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController import to.bitkit.R +import to.bitkit.ui.components.settings.SectionHeader +import to.bitkit.ui.components.settings.SettingsButtonRow import to.bitkit.ui.components.settings.SettingsSwitchRow +import to.bitkit.ui.scaffold.AppAlertDialog import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.DrawerNavIcon import to.bitkit.ui.scaffold.ScreenColumn @@ -33,17 +42,23 @@ fun WidgetsSettingsScreen( showWidgetTitles = showWidgetTitles, onShowWidgetsClick = { settings.setShowWidgets(!showWidgets) }, onShowWidgetTitlesClick = { settings.setShowWidgetTitles(!showWidgetTitles) }, + onResetSuggestionsClick = { + settings.resetDismissedSuggestions() + }, ) } @Composable private fun WidgetsSettingsContent( - onBackClick: () -> Unit = {}, showWidgets: Boolean, - onShowWidgetsClick: () -> Unit = {}, showWidgetTitles: Boolean, + onBackClick: () -> Unit = {}, + onShowWidgetsClick: () -> Unit = {}, onShowWidgetTitlesClick: () -> Unit = {}, + onResetSuggestionsClick: () -> Unit = {}, ) { + var showResetSuggestionsDialog by remember { mutableStateOf(false) } + ScreenColumn { AppTopBar( titleText = stringResource(R.string.settings__widgets__nav_title), @@ -51,8 +66,13 @@ private fun WidgetsSettingsContent( actions = { DrawerNavIcon() }, ) Column( - modifier = Modifier.padding(horizontal = 16.dp), + modifier = Modifier + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()), ) { + // Display section + SectionHeader(title = stringResource(R.string.settings__widgets__section_display)) + SettingsSwitchRow( title = stringResource(R.string.settings__widgets__showWidgets), isChecked = showWidgets, @@ -63,6 +83,32 @@ private fun WidgetsSettingsContent( isChecked = showWidgetTitles, onClick = onShowWidgetTitlesClick, ) + + // Reset section + SectionHeader( + title = stringResource(R.string.settings__widgets__section_reset), + padding = androidx.compose.foundation.layout.PaddingValues(top = 16.dp), + ) + + SettingsButtonRow( + title = stringResource(R.string.settings__widgets__reset_suggestions), + onClick = { showResetSuggestionsDialog = true }, + modifier = Modifier.testTag("ResetSuggestions"), + ) + } + + if (showResetSuggestionsDialog) { + AppAlertDialog( + title = stringResource(R.string.settings__adv__reset_title), + text = stringResource(R.string.settings__adv__reset_desc), + confirmText = stringResource(R.string.settings__adv__reset_confirm), + onConfirm = { + onResetSuggestionsClick() + showResetSuggestionsDialog = false + }, + onDismiss = { showResetSuggestionsDialog = false }, + modifier = Modifier.testTag("reset_suggestions_dialog"), + ) } } } diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinResultScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinResultScreen.kt index 8913265f2..020703c3b 100644 --- a/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinResultScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinResultScreen.kt @@ -32,7 +32,7 @@ fun ChangePinResultScreen( ) { ChangePinResultContent( onOkClick = { - navController.popBackStack(inclusive = false) + navController.popBackStack(inclusive = false) }, onBackClick = { navController.popBackStack() diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/DisablePinScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/DisablePinScreen.kt deleted file mode 100644 index 1c68a0e10..000000000 --- a/app/src/main/java/to/bitkit/ui/settings/pin/DisablePinScreen.kt +++ /dev/null @@ -1,96 +0,0 @@ -package to.bitkit.ui.settings.pin - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import to.bitkit.R -import to.bitkit.ui.Routes -import to.bitkit.ui.components.AuthCheckAction -import to.bitkit.ui.components.BodyM -import to.bitkit.ui.components.PrimaryButton -import to.bitkit.ui.navigateToAuthCheck -import to.bitkit.ui.scaffold.AppTopBar -import to.bitkit.ui.scaffold.DrawerNavIcon -import to.bitkit.ui.scaffold.ScreenColumn -import to.bitkit.ui.theme.AppThemeSurface -import to.bitkit.ui.theme.Colors - -@Composable -fun DisablePinScreen( - navController: NavController, -) { - DisablePinContent( - onDisableClick = { - navController.navigateToAuthCheck( - requirePin = true, - onSuccessActionId = AuthCheckAction.DISABLE_PIN, - ) { popUpTo(Routes.SecuritySettings) } - }, - onBackClick = { navController.popBackStack() }, - ) -} - -@Composable -private fun DisablePinContent( - onDisableClick: () -> Unit = {}, - onBackClick: () -> Unit = {}, -) { - ScreenColumn { - AppTopBar( - titleText = stringResource(R.string.security__pin_disable_title), - onBackClick = onBackClick, - actions = { DrawerNavIcon() }, - ) - Column( - modifier = Modifier.padding(horizontal = 16.dp), - ) { - BodyM( - text = stringResource(R.string.security__pin_disable_text), - color = Colors.White64, - ) - - Box( - contentAlignment = Alignment.Center, - modifier = Modifier - .fillMaxWidth() - .weight(1f) - ) { - Image( - painter = painterResource(R.drawable.shield), - contentDescription = null, - modifier = Modifier.size(256.dp) - ) - } - - PrimaryButton( - text = stringResource(R.string.security__pin_disable_button), - onClick = onDisableClick, - modifier = Modifier.testTag("DisablePin") - ) - - Spacer(modifier = Modifier.height(16.dp)) - } - } -} - -@Preview(showSystemUi = true) -@Composable -private fun Preview() { - AppThemeSurface { - DisablePinContent() - } -} diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt new file mode 100644 index 000000000..fa02b892d --- /dev/null +++ b/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt @@ -0,0 +1,157 @@ +package to.bitkit.ui.settings.pin + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavController +import to.bitkit.R +import to.bitkit.ui.Routes +import to.bitkit.ui.appViewModel +import to.bitkit.ui.components.AuthCheckAction +import to.bitkit.ui.components.BodyM +import to.bitkit.ui.components.PrimaryButton +import to.bitkit.ui.components.SecondaryButton +import to.bitkit.ui.components.Sheet +import to.bitkit.ui.navigateToAuthCheck +import to.bitkit.ui.navigateToChangePin +import to.bitkit.ui.scaffold.AppTopBar +import to.bitkit.ui.scaffold.DrawerNavIcon +import to.bitkit.ui.scaffold.ScreenColumn +import to.bitkit.ui.settingsViewModel +import to.bitkit.ui.theme.AppThemeSurface +import to.bitkit.ui.theme.Colors + +@Composable +fun PinManagementScreen( + navController: NavController, +) { + val app = appViewModel ?: return + val settings = settingsViewModel ?: return + val isPinEnabled by settings.isPinEnabled.collectAsStateWithLifecycle() + + Content( + isPinEnabled = isPinEnabled, + onEnablePinClick = { app.showSheet(Sheet.Pin()) }, + onChangePinClick = { navController.navigateToChangePin() }, + onDisablePinClick = { + navController.navigateToAuthCheck( + requirePin = true, + onSuccessActionId = AuthCheckAction.DISABLE_PIN, + ) { popUpTo(Routes.Settings) } + }, + onBackClick = { navController.popBackStack() }, + ) +} + +@Composable +private fun Content( + isPinEnabled: Boolean, + onEnablePinClick: () -> Unit = {}, + onChangePinClick: () -> Unit = {}, + onDisablePinClick: () -> Unit = {}, + onBackClick: () -> Unit = {}, +) { + ScreenColumn { + AppTopBar( + titleText = stringResource( + if (isPinEnabled) { + R.string.security__pin_disable_title + } else { + R.string.settings__security__pin + } + ), + onBackClick = onBackClick, + actions = { DrawerNavIcon() }, + ) + Column( + modifier = Modifier.padding(horizontal = 16.dp), + ) { + BodyM( + text = stringResource( + if (isPinEnabled) { + R.string.security__pin_disable_text + } else { + R.string.security__pin_security_text + } + ), + color = Colors.White64, + ) + + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) { + Image( + painter = painterResource(R.drawable.shield), + contentDescription = null, + modifier = Modifier.size(256.dp), + ) + } + + if (isPinEnabled) { + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier.fillMaxWidth(), + ) { + SecondaryButton( + text = stringResource(R.string.settings__security__pin_change), + onClick = onChangePinClick, + modifier = Modifier + .weight(1f) + .testTag("ChangePIN"), + ) + PrimaryButton( + text = stringResource(R.string.security__pin_disable_button), + onClick = onDisablePinClick, + modifier = Modifier + .weight(1f) + .testTag("DisablePin"), + ) + } + } else { + PrimaryButton( + text = stringResource(R.string.security__pin_enable_button), + onClick = onEnablePinClick, + modifier = Modifier.testTag("EnablePin"), + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + } + } +} + +@Preview(showSystemUi = true) +@Composable +private fun PreviewDisabled() { + AppThemeSurface { + Content(isPinEnabled = false) + } +} + +@Preview(showSystemUi = true) +@Composable +private fun PreviewEnabled() { + AppThemeSurface { + Content(isPinEnabled = true) + } +} diff --git a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt index d56085155..81d8b823f 100644 --- a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt @@ -7,34 +7,58 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.net.toUri +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController +import to.bitkit.BuildConfig import to.bitkit.R import to.bitkit.env.Env +import to.bitkit.models.Toast import to.bitkit.ui.Routes +import to.bitkit.ui.appViewModel import to.bitkit.ui.components.BodyM +import to.bitkit.ui.components.BodyS +import to.bitkit.ui.components.VerticalSpacer import to.bitkit.ui.components.settings.Links import to.bitkit.ui.components.settings.SettingsButtonRow +import to.bitkit.ui.components.settings.SettingsButtonValue import to.bitkit.ui.navigateTo import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.DrawerNavIcon import to.bitkit.ui.scaffold.ScreenColumn +import to.bitkit.ui.settingsViewModel +import to.bitkit.ui.shared.util.shareText import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors +private const val DEV_MODE_TAP_THRESHOLD = 5 + @Composable fun SupportScreen( navController: NavController, ) { val context = LocalContext.current + val app = appViewModel ?: return + val settings = settingsViewModel ?: return + val isDevModeEnabled by settings.isDevModeEnabled.collectAsStateWithLifecycle() + var devModeTapCount by remember { mutableIntStateOf(0) } + val haptic = LocalHapticFeedback.current Content( onBack = { navController.popBackStack() }, @@ -44,6 +68,47 @@ fun SupportScreen( context.startActivity(intent) }, onClickAppStatus = { navController.navigateTo(Routes.AppStatus) }, + onClickLegal = { + val intent = Intent(Intent.ACTION_VIEW, Env.TERMS_OF_USE_URL.toUri()) + context.startActivity(intent) + }, + onClickShare = { + shareText( + context, + context.getString(R.string.settings__about__shareText) + .replace("{appStoreUrl}", Env.APP_STORE_URL) + .replace("{playStoreUrl}", Env.PLAY_STORE_URL) + ) + }, + onClickVersion = { + haptic.performHapticFeedback(HapticFeedbackType.Confirm) + devModeTapCount += 1 + + if (devModeTapCount >= DEV_MODE_TAP_THRESHOLD) { + val newValue = !isDevModeEnabled + settings.setIsDevModeEnabled(newValue) + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + + app.toast( + type = Toast.ToastType.SUCCESS, + title = context.getString( + if (newValue) { + R.string.settings__dev_enabled_title + } else { + R.string.settings__dev_disabled_title + } + ), + description = context.getString( + if (newValue) { + R.string.settings__dev_enabled_message + } else { + R.string.settings__dev_disabled_message + } + ), + ) + devModeTapCount = 0 + } + }, ) } @@ -53,7 +118,12 @@ private fun Content( onClickReportIssue: () -> Unit = {}, onClickHelpCenter: () -> Unit = {}, onClickAppStatus: () -> Unit = {}, + onClickLegal: () -> Unit = {}, + onClickShare: () -> Unit = {}, + onClickVersion: () -> Unit = {}, ) { + val appVersion = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})" + ScreenColumn { AppTopBar( titleText = stringResource(R.string.settings__support_title), @@ -62,33 +132,72 @@ private fun Content( ) Column( - modifier = Modifier.padding(horizontal = 16.dp) + modifier = Modifier + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()) ) { - Spacer(modifier = Modifier.height(32.dp)) + VerticalSpacer(16.dp) BodyM(text = stringResource(R.string.settings__support__text), color = Colors.White64) - Spacer(modifier = Modifier.height(32.dp)) + VerticalSpacer(16.dp) - SettingsButtonRow(title = stringResource(R.string.settings__support__report), onClick = onClickReportIssue) - SettingsButtonRow(title = stringResource(R.string.settings__support__help), onClick = onClickHelpCenter) + SettingsButtonRow( + title = stringResource(R.string.settings__support__report), + iconRes = R.drawable.ic_warning, + onClick = onClickReportIssue, + ) + SettingsButtonRow( + title = stringResource(R.string.settings__support__help), + iconRes = R.drawable.ic_warning, + onClick = onClickHelpCenter, + ) SettingsButtonRow( title = stringResource(R.string.settings__support__status), + iconRes = R.drawable.ic_settings_support, onClick = onClickAppStatus, - modifier = Modifier.testTag("AppStatus") + modifier = Modifier.testTag("AppStatus"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__about__legal), + iconRes = R.drawable.ic_warning, + onClick = onClickLegal, ) + SettingsButtonRow( + title = stringResource(R.string.settings__about__share), + iconRes = R.drawable.ic_share, + onClick = onClickShare, + ) + SettingsButtonRow( + title = stringResource(R.string.settings__about__version), + iconRes = R.drawable.ic_stack, + value = SettingsButtonValue.StringValue(appVersion), + onClick = onClickVersion, + modifier = Modifier.testTag("DevOptions"), + ) + + Spacer(modifier = Modifier.weight(1f)) Image( - painter = painterResource(R.drawable.question_mark), + painter = painterResource(R.drawable.bitkit_logo), contentDescription = null, modifier = Modifier .fillMaxWidth() - .weight(1f) + .padding(horizontal = 32.dp, vertical = 16.dp) + .height(82.dp) + .testTag("AboutLogo"), ) Links(modifier = Modifier.fillMaxWidth()) - Spacer(modifier = Modifier.height(16.dp)) + VerticalSpacer(16.dp) + + BodyS( + text = stringResource(R.string.settings__support__copyright), + color = Colors.White64, + ) + + VerticalSpacer(32.dp) } } } diff --git a/app/src/main/java/to/bitkit/viewmodels/SettingsViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/SettingsViewModel.kt index ba3e42286..022fc0ef9 100644 --- a/app/src/main/java/to/bitkit/viewmodels/SettingsViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/SettingsViewModel.kt @@ -166,6 +166,12 @@ class SettingsViewModel @Inject constructor( } } + fun resetDismissedSuggestions() { + viewModelScope.launch { + settingsStore.update { it.copy(dismissedSuggestions = emptyList()) } + } + } + val lastUsedTags = settingsStore.data.map { it.lastUsedTags } .asStateFlow(initialValue = emptyList()) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5e85b445b..57b657b71 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -484,6 +484,7 @@ Choose 4-Digit PIN Please use a PIN you will remember. If you forget your PIN you can reset it, but that will require restoring your wallet. Disable PIN + Enable PIN PIN code is currently enabled. If you want to disable your PIN, you need to enter your current PIN code first. Disable PIN Please enter your PIN code @@ -570,6 +571,7 @@ Are you sure you want to reset the suggestions? They will reappear in case you have removed them from your Bitkit wallet overview. Reset Suggestions? Rapid-Gossip-Sync + Debug Networks Other Payments @@ -587,7 +589,8 @@ Bitkit failed to back up wallet data. Retrying in {interval, plural, one {# minute} other {# minutes}}. Data Backup Failure latest data backups - Reset and restore wallet + Data backups + Reset wallet Failed Backup: {time} Running Latest Backup: {time} @@ -653,7 +656,9 @@ Classic (₿ 0.00010000) Bitcoin denomination Modern (₿ 10 000) - Transaction speed + Interface + Payments + Transaction Speed Default Transaction Speed Set Custom Fee ₿ {feeSats} for the average transaction @@ -680,6 +685,9 @@ You may need to restart the app once or twice for this change to take effect. Rapid-Gossip-Sync Server Updated Read clipboard for ease of use + Back up or reset + Privacy + Safety When enabled, you can use {biometryTypeName} instead of your PIN code to unlock your wallet or send payments. Hide balance on open PIN Code @@ -688,9 +696,9 @@ Enabled Require PIN for payments Swipe balance to hide - Use {biometryTypeName} instead + {biometryTypeName} instead of PIN Warn when sending over $100 - Security and Privacy + Security Settings Failed to complete a full backup Backing up... @@ -722,7 +730,8 @@ Report Issue Please describe the issue you are experiencing or ask a general question. App Status - Need help? Report your issue from within Bitkit, visit the help center, check the status, or reach out to us on our social channels. + Bitkit was crafted by Synonym Software, S.A. DE C.V. ©2025. All rights reserved. + Need help? Report your issue from within Bitkit or visit our help center. Send Thank you for contacting us! We will try to get back to you as soon as possible. OK @@ -734,7 +743,13 @@ Support Widgets Show Widget Titles - Widgets + Reset Suggestions Cards + Reset Widgets + Are you sure you want to reset the widgets? The default widget set with default configurations will be displayed. + Reset Widgets? + Display + Reset To Defaults + Show Widgets Own your\n<accent>profile</accent> Set up your public profile and links, so your Bitkit contacts can reach you or pay you anytime, anywhere. Profile @@ -848,6 +863,7 @@ Profile Settings Shop + Support App Status Wallet Widgets From ac561b0d6909a9256b418a6e5cfbecd7985ae25a Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 20 Mar 2026 07:55:53 -0300 Subject: [PATCH 02/85] feat: implement swipe --- .../to/bitkit/ui/settings/SettingsScreen.kt | 113 +++++++++--------- 1 file changed, 59 insertions(+), 54 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index f4001e640..e5f22e351 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -3,14 +3,15 @@ package to.bitkit.ui.settings import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource @@ -19,6 +20,7 @@ import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController +import kotlinx.coroutines.launch import to.bitkit.R import to.bitkit.models.PrimaryDisplay import to.bitkit.models.TransactionSpeed @@ -218,8 +220,9 @@ private fun SettingsContent( // Navigation onBackClick: () -> Unit = {}, ) { - var selectedTab by remember { mutableStateOf(SettingsTab.General) } val tabs = remember { SettingsTab.entries } + val pagerState = rememberPagerState(pageCount = { tabs.size }) + val scope = rememberCoroutineScope() ScreenColumn { AppTopBar( @@ -230,62 +233,64 @@ private fun SettingsContent( CustomTabRowWithSpacing( tabs = tabs, - currentTabIndex = tabs.indexOf(selectedTab), - onTabChange = { selectedTab = it }, + currentTabIndex = pagerState.currentPage, + onTabChange = { scope.launch { pagerState.animateScrollToPage(tabs.indexOf(it)) } }, modifier = Modifier.padding(horizontal = 16.dp), ) - when (selectedTab) { - SettingsTab.General -> GeneralTabContent( - selectedCurrency = selectedCurrency, - primaryDisplay = primaryDisplay, - defaultTransactionSpeed = defaultTransactionSpeed, - selectedLanguage = selectedLanguage, - showTagsButton = showTagsButton, - notificationsGranted = notificationsGranted, - onLanguageClick = onLanguageClick, - onLocalCurrencyClick = onLocalCurrencyClick, - onDefaultUnitClick = onDefaultUnitClick, - onWidgetsClick = onWidgetsClick, - onTagsClick = onTagsClick, - onTransactionSpeedClick = onTransactionSpeedClick, - onQuickPayClick = onQuickPayClick, - onBgPaymentsClick = onBgPaymentsClick, - ) + HorizontalPager(state = pagerState) { page -> + when (tabs[page]) { + SettingsTab.General -> GeneralTabContent( + selectedCurrency = selectedCurrency, + primaryDisplay = primaryDisplay, + defaultTransactionSpeed = defaultTransactionSpeed, + selectedLanguage = selectedLanguage, + showTagsButton = showTagsButton, + notificationsGranted = notificationsGranted, + onLanguageClick = onLanguageClick, + onLocalCurrencyClick = onLocalCurrencyClick, + onDefaultUnitClick = onDefaultUnitClick, + onWidgetsClick = onWidgetsClick, + onTagsClick = onTagsClick, + onTransactionSpeedClick = onTransactionSpeedClick, + onQuickPayClick = onQuickPayClick, + onBgPaymentsClick = onBgPaymentsClick, + ) - SettingsTab.Security -> SecurityTabContent( - isPinEnabled = isPinEnabled, - isBiometricEnabled = isBiometricEnabled, - isPinForPaymentsEnabled = isPinForPaymentsEnabled, - enableSwipeToHideBalance = enableSwipeToHideBalance, - hideBalanceOnOpen = hideBalanceOnOpen, - enableAutoReadClipboard = enableAutoReadClipboard, - enableSendAmountWarning = enableSendAmountWarning, - isBiometrySupported = isBiometrySupported, - onBackupWalletClick = onBackupWalletClick, - onDataBackupsClick = onDataBackupsClick, - onResetWalletClick = onResetWalletClick, - onPinClick = onPinClick, - onPinForPaymentsClick = onPinForPaymentsClick, - onUseBiometricsClick = onUseBiometricsClick, - onSwipeToHideBalanceClick = onSwipeToHideBalanceClick, - onHideBalanceOnOpenClick = onHideBalanceOnOpenClick, - onAutoReadClipboardClick = onAutoReadClipboardClick, - onSendAmountWarningClick = onSendAmountWarningClick, - ) + SettingsTab.Security -> SecurityTabContent( + isPinEnabled = isPinEnabled, + isBiometricEnabled = isBiometricEnabled, + isPinForPaymentsEnabled = isPinForPaymentsEnabled, + enableSwipeToHideBalance = enableSwipeToHideBalance, + hideBalanceOnOpen = hideBalanceOnOpen, + enableAutoReadClipboard = enableAutoReadClipboard, + enableSendAmountWarning = enableSendAmountWarning, + isBiometrySupported = isBiometrySupported, + onBackupWalletClick = onBackupWalletClick, + onDataBackupsClick = onDataBackupsClick, + onResetWalletClick = onResetWalletClick, + onPinClick = onPinClick, + onPinForPaymentsClick = onPinForPaymentsClick, + onUseBiometricsClick = onUseBiometricsClick, + onSwipeToHideBalanceClick = onSwipeToHideBalanceClick, + onHideBalanceOnOpenClick = onHideBalanceOnOpenClick, + onAutoReadClipboardClick = onAutoReadClipboardClick, + onSendAmountWarningClick = onSendAmountWarningClick, + ) - SettingsTab.Advanced -> AdvancedTabContent( - isDevModeEnabled = isDevModeEnabled, - selectedAddressTypeName = selectedAddressTypeName, - onDevSettingsClick = onDevSettingsClick, - onAddressTypeClick = onAddressTypeClick, - onCoinSelectionClick = onCoinSelectionClick, - onAddressViewerClick = onAddressViewerClick, - onLightningConnectionsClick = onLightningConnectionsClick, - onLightningNodeClick = onLightningNodeClick, - onElectrumServerClick = onElectrumServerClick, - onRgsServerClick = onRgsServerClick, - ) + SettingsTab.Advanced -> AdvancedTabContent( + isDevModeEnabled = isDevModeEnabled, + selectedAddressTypeName = selectedAddressTypeName, + onDevSettingsClick = onDevSettingsClick, + onAddressTypeClick = onAddressTypeClick, + onCoinSelectionClick = onCoinSelectionClick, + onAddressViewerClick = onAddressViewerClick, + onLightningConnectionsClick = onLightningConnectionsClick, + onLightningNodeClick = onLightningNodeClick, + onElectrumServerClick = onElectrumServerClick, + onRgsServerClick = onRgsServerClick, + ) + } } } } From c57a54fcb30ba13986c8c5b348954d9f6fc50d64 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 20 Mar 2026 09:12:23 -0300 Subject: [PATCH 03/85] feat: icon color --- .../to/bitkit/ui/settings/SettingsScreen.kt | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index e5f22e351..33d9ed16c 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -53,6 +53,7 @@ import to.bitkit.ui.screens.wallets.activity.components.CustomTabRowWithSpacing import to.bitkit.ui.screens.wallets.activity.components.TabItem import to.bitkit.ui.settingsViewModel import to.bitkit.ui.theme.AppThemeSurface +import to.bitkit.ui.theme.Colors import to.bitkit.ui.utils.rememberBiometricAuthSupported import to.bitkit.viewmodels.LanguageViewModel @@ -324,6 +325,7 @@ private fun GeneralTabContent( SettingsButtonRow( title = stringResource(R.string.settings__language_title), iconRes = R.drawable.ic_warning, + iconTint = Colors.Brand, value = SettingsButtonValue.StringValue(selectedLanguage), onClick = onLanguageClick, modifier = Modifier.testTag("LanguageSettings"), @@ -331,6 +333,7 @@ private fun GeneralTabContent( SettingsButtonRow( title = stringResource(R.string.settings__general__currency_local), iconRes = R.drawable.ic_coins, + iconTint = Colors.Brand, value = SettingsButtonValue.StringValue(selectedCurrency), onClick = onLocalCurrencyClick, modifier = Modifier.testTag("CurrenciesSettings"), @@ -338,6 +341,7 @@ private fun GeneralTabContent( SettingsButtonRow( title = stringResource(R.string.settings__general__unit), iconRes = R.drawable.ic_coins, + iconTint = Colors.Brand, value = SettingsButtonValue.StringValue( when (primaryDisplay) { PrimaryDisplay.BITCOIN -> stringResource(R.string.settings__general__unit_bitcoin) @@ -350,6 +354,7 @@ private fun GeneralTabContent( SettingsButtonRow( title = stringResource(R.string.settings__widgets__nav_title), iconRes = R.drawable.ic_warning, + iconTint = Colors.Brand, onClick = onWidgetsClick, modifier = Modifier.testTag("WidgetsSettings"), ) @@ -357,6 +362,7 @@ private fun GeneralTabContent( SettingsButtonRow( title = stringResource(R.string.settings__general__tags), iconRes = R.drawable.ic_tag, + iconTint = Colors.Brand, onClick = onTagsClick, modifier = Modifier.testTag("TagsSettings"), ) @@ -371,6 +377,7 @@ private fun GeneralTabContent( SettingsButtonRow( title = stringResource(R.string.settings__general__speed), iconRes = R.drawable.ic_speed_normal, + iconTint = Colors.Brand, value = SettingsButtonValue.StringValue(defaultTransactionSpeed.transactionSpeedUiText()), onClick = onTransactionSpeedClick, modifier = Modifier.testTag("TransactionSpeedSettings"), @@ -378,12 +385,14 @@ private fun GeneralTabContent( SettingsButtonRow( title = stringResource(R.string.settings__quickpay__nav_title), iconRes = R.drawable.ic_warning, + iconTint = Colors.Brand, onClick = onQuickPayClick, modifier = Modifier.testTag("QuickpaySettings"), ) SettingsButtonRow( title = stringResource(R.string.settings__bg__title), iconRes = R.drawable.ic_bell, + iconTint = Colors.Brand, value = SettingsButtonValue.StringValue( stringResource(if (notificationsGranted) R.string.settings__bg__on else R.string.settings__bg__off) ), @@ -429,18 +438,21 @@ private fun SecurityTabContent( SettingsButtonRow( title = stringResource(R.string.settings__backup__wallet), iconRes = R.drawable.ic_warning, + iconTint = Colors.Brand, onClick = onBackupWalletClick, modifier = Modifier.testTag("BackupWallet"), ) SettingsButtonRow( title = stringResource(R.string.settings__backup__data), iconRes = R.drawable.ic_warning, + iconTint = Colors.Brand, onClick = onDataBackupsClick, modifier = Modifier.testTag("BackupSettings"), ) SettingsButtonRow( title = stringResource(R.string.settings__backup__reset), iconRes = R.drawable.ic_warning, + iconTint = Colors.Brand, onClick = onResetWalletClick, modifier = Modifier.testTag("ResetAndRestore"), ) @@ -454,6 +466,7 @@ private fun SecurityTabContent( SettingsButtonRow( title = stringResource(R.string.settings__security__pin), iconRes = R.drawable.ic_warning, + iconTint = Colors.Brand, value = SettingsButtonValue.StringValue( stringResource( if (isPinEnabled) { @@ -471,6 +484,7 @@ private fun SecurityTabContent( SettingsSwitchRow( title = stringResource(R.string.settings__security__pin_payments), iconRes = R.drawable.ic_coins, + iconTint = Colors.Brand, isChecked = isPinForPaymentsEnabled, onClick = onPinForPaymentsClick, modifier = Modifier.testTag("EnablePinForPayments"), @@ -484,6 +498,7 @@ private fun SecurityTabContent( .replace("{biometryTypeName}", bioTypeName) }, iconRes = R.drawable.ic_warning, + iconTint = Colors.Brand, isChecked = isBiometricEnabled, onClick = onUseBiometricsClick, modifier = Modifier.testTag("UseBiometryInstead"), @@ -494,6 +509,7 @@ private fun SecurityTabContent( SettingsSwitchRow( title = stringResource(R.string.settings__security__warn_100), iconRes = R.drawable.ic_warning, + iconTint = Colors.Brand, isChecked = enableSendAmountWarning, onClick = onSendAmountWarningClick, modifier = Modifier.testTag("SendAmountWarning"), @@ -508,6 +524,7 @@ private fun SecurityTabContent( SettingsSwitchRow( title = stringResource(R.string.settings__security__swipe_balance_to_hide), iconRes = R.drawable.ic_warning, + iconTint = Colors.Brand, isChecked = enableSwipeToHideBalance, onClick = onSwipeToHideBalanceClick, modifier = Modifier.testTag("SwipeBalanceToHide"), @@ -515,6 +532,7 @@ private fun SecurityTabContent( SettingsSwitchRow( title = stringResource(R.string.settings__security__hide_balance_on_open), iconRes = R.drawable.ic_warning, + iconTint = Colors.Brand, isChecked = hideBalanceOnOpen, onClick = onHideBalanceOnOpenClick, modifier = Modifier.testTag("HideBalanceOnOpen"), @@ -522,6 +540,7 @@ private fun SecurityTabContent( SettingsSwitchRow( title = stringResource(R.string.settings__security__clipboard), iconRes = R.drawable.ic_clipboard_text, + iconTint = Colors.Brand, isChecked = enableAutoReadClipboard, onClick = onAutoReadClipboardClick, modifier = Modifier.testTag("AutoReadClipboard"), @@ -558,6 +577,7 @@ private fun AdvancedTabContent( SettingsButtonRow( title = stringResource(R.string.settings__dev_title), iconRes = R.drawable.ic_settings_dev, + iconTint = Colors.Brand, onClick = onDevSettingsClick, modifier = Modifier.testTag("DevSettings"), ) @@ -569,6 +589,7 @@ private fun AdvancedTabContent( SettingsButtonRow( title = stringResource(R.string.settings__addr_type__title), iconRes = R.drawable.ic_warning, + iconTint = Colors.Brand, value = if (selectedAddressTypeName.isNotEmpty()) { SettingsButtonValue.StringValue(selectedAddressTypeName) } else { @@ -580,12 +601,14 @@ private fun AdvancedTabContent( SettingsButtonRow( title = stringResource(R.string.settings__adv__coin_selection), iconRes = R.drawable.ic_coins, + iconTint = Colors.Brand, onClick = onCoinSelectionClick, modifier = Modifier.testTag("CoinSelectPreference"), ) SettingsButtonRow( title = stringResource(R.string.settings__adv__address_viewer), iconRes = R.drawable.ic_eye, + iconTint = Colors.Brand, onClick = onAddressViewerClick, modifier = Modifier.testTag("AddressViewer"), ) @@ -599,24 +622,28 @@ private fun AdvancedTabContent( SettingsButtonRow( title = stringResource(R.string.settings__adv__lightning_connections), iconRes = R.drawable.ic_git_branch, + iconTint = Colors.Brand, onClick = onLightningConnectionsClick, modifier = Modifier.testTag("Channels"), ) SettingsButtonRow( title = stringResource(R.string.settings__adv__lightning_node), iconRes = R.drawable.ic_git_branch, + iconTint = Colors.Brand, onClick = onLightningNodeClick, modifier = Modifier.testTag("LightningNodeInfo"), ) SettingsButtonRow( title = stringResource(R.string.settings__adv__electrum_server), iconRes = R.drawable.ic_warning, + iconTint = Colors.Brand, onClick = onElectrumServerClick, modifier = Modifier.testTag("ElectrumConfig"), ) SettingsButtonRow( title = stringResource(R.string.settings__adv__rgs_server), iconRes = R.drawable.ic_broadcast, + iconTint = Colors.Brand, onClick = onRgsServerClick, modifier = Modifier.testTag("RGSServer"), ) From e55e01c9c85fddaa7df68969e41fc9c2cd5a477a Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 20 Mar 2026 09:22:52 -0300 Subject: [PATCH 04/85] feat: selected tab color --- app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index 33d9ed16c..c91f61626 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -235,6 +235,7 @@ private fun SettingsContent( CustomTabRowWithSpacing( tabs = tabs, currentTabIndex = pagerState.currentPage, + selectedColor = Colors.White, onTabChange = { scope.launch { pagerState.animateScrollToPage(tabs.indexOf(it)) } }, modifier = Modifier.padding(horizontal = 16.dp), ) From 85fa8c83cf6f68c8d01fb7d18c9a0011d8912b8d Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 20 Mar 2026 10:02:23 -0300 Subject: [PATCH 05/85] feat: set icons --- .../components/settings/SettingsButtonRow.kt | 84 ++++++++++++++++--- 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt b/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt index 4e2fadc25..680a7ee49 100644 --- a/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt +++ b/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt @@ -34,7 +34,6 @@ import to.bitkit.ui.shared.modifiers.clickableAlpha import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors -@Suppress("CyclomaticComplexMethod") @Composable fun SettingsButtonRow( title: String, @@ -49,6 +48,77 @@ fun SettingsButtonRow( enabled: Boolean = true, loading: Boolean = false, onClick: () -> Unit, +) { + SettingsButtonRowCore( + title = title, + hasIcon = iconRes != null, + subtitle = subtitle, + value = value, + description = description, + maxLinesSubtitle = maxLinesSubtitle, + enabled = enabled, + loading = loading, + onClick = onClick, + icon = if (iconRes != null) { + { + Icon( + painter = painterResource(iconRes), + contentDescription = null, + tint = iconTint, + modifier = Modifier + .size(iconSize) + .padding(end = 10.dp), + ) + } + } else { + null + }, + modifier = modifier, + ) +} + +@Composable +fun SettingsButtonRow( + title: String, + icon: @Composable () -> Unit, + modifier: Modifier = Modifier, + subtitle: String? = null, + value: SettingsButtonValue = SettingsButtonValue.None, + description: String? = null, + maxLinesSubtitle: Int = Int.MAX_VALUE, + enabled: Boolean = true, + loading: Boolean = false, + onClick: () -> Unit, +) { + SettingsButtonRowCore( + title = title, + hasIcon = true, + icon = { icon() }, + subtitle = subtitle, + value = value, + description = description, + maxLinesSubtitle = maxLinesSubtitle, + enabled = enabled, + loading = loading, + onClick = onClick, + modifier = modifier, + ) +} + +@Suppress("CyclomaticComplexMethod") +@Composable +private fun SettingsButtonRowCore( + title: String, + hasIcon: Boolean, + modifier: Modifier = Modifier, + icon: (@Composable () -> Unit)? = null, + subtitle: String? = null, + value: SettingsButtonValue = SettingsButtonValue.None, + description: String? = null, + maxLinesSubtitle: Int = Int.MAX_VALUE, + enabled: Boolean = true, + loading: Boolean = false, + onClick: () -> Unit, ) { val alphaModifier = Modifier.then(if (!enabled) Modifier.alpha(0.5f) else Modifier) Column( @@ -57,7 +127,7 @@ fun SettingsButtonRow( ) { Column(modifier = alphaModifier) { val rowHeight = when { - subtitle != null && iconRes != null -> 90.dp + subtitle != null && hasIcon -> 90.dp subtitle != null -> 74.dp else -> 52.dp } @@ -67,14 +137,8 @@ fun SettingsButtonRow( .fillMaxWidth() .heightIn(min = rowHeight) ) { - if (iconRes != null) { - Icon( - painter = painterResource(iconRes), - contentDescription = null, - tint = iconTint, - modifier = Modifier.size(iconSize), - ) - Spacer(modifier = Modifier.width(10.dp)) + if (icon != null) { + icon() } Column( verticalArrangement = Arrangement.Center, From 1e96e0f74ddadd2d6db106212bd1e7e9c8d4fcf9 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 20 Mar 2026 11:28:44 -0300 Subject: [PATCH 06/85] feat: set icons --- .../components/settings/SettingsSwitchRow.kt | 64 +++++++++-- .../to/bitkit/ui/settings/SettingsScreen.kt | 105 +++++++++--------- .../ui/settings/support/SupportScreen.kt | 4 +- .../drawable/ic_arrow_counter_clockwise.xml | 14 +++ app/src/main/res/drawable/ic_bell.xml | 22 ++-- .../main/res/drawable/ic_bitcoin_modern.xml | 9 ++ .../res/drawable/ic_caret_double_right.xml | 14 +++ .../main/res/drawable/ic_clipboard_text.xml | 29 ++--- app/src/main/res/drawable/ic_database.xml | 18 +++ .../res/drawable/ic_device_mobile_speaker.xml | 19 ++++ app/src/main/res/drawable/ic_eye_slash.xml | 26 +++++ app/src/main/res/drawable/ic_file_text.xml | 9 ++ .../main/res/drawable/ic_hand_pointing.xml | 14 +++ app/src/main/res/drawable/ic_hard_drives.xml | 20 ++++ app/src/main/res/drawable/ic_lightning.xml | 17 +-- app/src/main/res/drawable/ic_list_dashes.xml | 30 +++++ app/src/main/res/drawable/ic_lock_key.xml | 22 ++++ app/src/main/res/drawable/ic_question.xml | 9 ++ app/src/main/res/drawable/ic_shield.xml | 14 +++ app/src/main/res/drawable/ic_smiley.xml | 20 ++++ app/src/main/res/drawable/ic_stack.xml | 15 +-- app/src/main/res/drawable/ic_stop_circle.xml | 14 +++ app/src/main/res/drawable/ic_translate.xml | 30 +++++ 23 files changed, 427 insertions(+), 111 deletions(-) create mode 100644 app/src/main/res/drawable/ic_arrow_counter_clockwise.xml create mode 100644 app/src/main/res/drawable/ic_bitcoin_modern.xml create mode 100644 app/src/main/res/drawable/ic_caret_double_right.xml create mode 100644 app/src/main/res/drawable/ic_database.xml create mode 100644 app/src/main/res/drawable/ic_device_mobile_speaker.xml create mode 100644 app/src/main/res/drawable/ic_eye_slash.xml create mode 100644 app/src/main/res/drawable/ic_file_text.xml create mode 100644 app/src/main/res/drawable/ic_hand_pointing.xml create mode 100644 app/src/main/res/drawable/ic_hard_drives.xml create mode 100644 app/src/main/res/drawable/ic_list_dashes.xml create mode 100644 app/src/main/res/drawable/ic_lock_key.xml create mode 100644 app/src/main/res/drawable/ic_question.xml create mode 100644 app/src/main/res/drawable/ic_shield.xml create mode 100644 app/src/main/res/drawable/ic_smiley.xml create mode 100644 app/src/main/res/drawable/ic_stop_circle.xml create mode 100644 app/src/main/res/drawable/ic_translate.xml diff --git a/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt b/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt index 797ab09c3..bb8a86b7e 100644 --- a/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt +++ b/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt @@ -39,6 +39,60 @@ fun SettingsSwitchRow( iconRes: Int? = null, iconTint: Color = Color.Unspecified, colors: SwitchColors = AppSwitchDefaults.colors, +) { + SettingsSwitchRowCore( + title = title, + isChecked = isChecked, + onClick = onClick, + subtitle = subtitle, + colors = colors, + icon = if (iconRes != null) { + { + Icon( + painter = painterResource(iconRes), + contentDescription = null, + tint = iconTint, + modifier = Modifier.size(32.dp), + ) + Spacer(modifier = Modifier.width(10.dp)) + } + } else { + null + }, + modifier = modifier, + ) +} + +@Composable +fun SettingsSwitchRow( + title: String, + isChecked: Boolean, + icon: @Composable () -> Unit, + onClick: () -> Unit, + modifier: Modifier = Modifier, + subtitle: String? = null, + colors: SwitchColors = AppSwitchDefaults.colors, +) { + SettingsSwitchRowCore( + title = title, + isChecked = isChecked, + onClick = onClick, + subtitle = subtitle, + colors = colors, + icon = { icon() }, + modifier = modifier, + ) +} + +@Composable +private fun SettingsSwitchRowCore( + title: String, + isChecked: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, + subtitle: String? = null, + icon: (@Composable () -> Unit)? = null, + colors: SwitchColors = AppSwitchDefaults.colors, ) { Column( modifier = modifier.heightIn(min = 52.dp), @@ -51,14 +105,8 @@ fun SettingsSwitchRow( .clickableAlpha { onClick() } .padding(vertical = 16.dp) ) { - if (iconRes != null) { - Icon( - painter = painterResource(iconRes), - contentDescription = null, - tint = iconTint, - modifier = Modifier.size(32.dp), - ) - Spacer(modifier = Modifier.width(10.dp)) + if (icon != null) { + icon() } Column( diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index c91f61626..d4ed365cf 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -1,19 +1,27 @@ package to.bitkit.ui.settings +import androidx.annotation.DrawableRes +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -29,6 +37,7 @@ import to.bitkit.ui.LocalCurrencies import to.bitkit.ui.Routes import to.bitkit.ui.appViewModel import to.bitkit.ui.components.AuthCheckAction +import to.bitkit.ui.components.HorizontalSpacer import to.bitkit.ui.components.Sheet import to.bitkit.ui.components.VerticalSpacer import to.bitkit.ui.components.settings.SectionHeader @@ -325,24 +334,21 @@ private fun GeneralTabContent( SettingsButtonRow( title = stringResource(R.string.settings__language_title), - iconRes = R.drawable.ic_warning, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_translate) }, value = SettingsButtonValue.StringValue(selectedLanguage), onClick = onLanguageClick, modifier = Modifier.testTag("LanguageSettings"), ) SettingsButtonRow( title = stringResource(R.string.settings__general__currency_local), - iconRes = R.drawable.ic_coins, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_coins) }, value = SettingsButtonValue.StringValue(selectedCurrency), onClick = onLocalCurrencyClick, modifier = Modifier.testTag("CurrenciesSettings"), ) SettingsButtonRow( title = stringResource(R.string.settings__general__unit), - iconRes = R.drawable.ic_coins, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_bitcoin_modern) }, value = SettingsButtonValue.StringValue( when (primaryDisplay) { PrimaryDisplay.BITCOIN -> stringResource(R.string.settings__general__unit_bitcoin) @@ -354,16 +360,14 @@ private fun GeneralTabContent( ) SettingsButtonRow( title = stringResource(R.string.settings__widgets__nav_title), - iconRes = R.drawable.ic_warning, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_stack) }, onClick = onWidgetsClick, modifier = Modifier.testTag("WidgetsSettings"), ) if (showTagsButton) { SettingsButtonRow( title = stringResource(R.string.settings__general__tags), - iconRes = R.drawable.ic_tag, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_tag) }, onClick = onTagsClick, modifier = Modifier.testTag("TagsSettings"), ) @@ -377,23 +381,20 @@ private fun GeneralTabContent( SettingsButtonRow( title = stringResource(R.string.settings__general__speed), - iconRes = R.drawable.ic_speed_normal, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_speed_normal) }, value = SettingsButtonValue.StringValue(defaultTransactionSpeed.transactionSpeedUiText()), onClick = onTransactionSpeedClick, modifier = Modifier.testTag("TransactionSpeedSettings"), ) SettingsButtonRow( title = stringResource(R.string.settings__quickpay__nav_title), - iconRes = R.drawable.ic_warning, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_caret_double_right) }, onClick = onQuickPayClick, modifier = Modifier.testTag("QuickpaySettings"), ) SettingsButtonRow( title = stringResource(R.string.settings__bg__title), - iconRes = R.drawable.ic_bell, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_bell) }, value = SettingsButtonValue.StringValue( stringResource(if (notificationsGranted) R.string.settings__bg__on else R.string.settings__bg__off) ), @@ -438,22 +439,19 @@ private fun SecurityTabContent( SettingsButtonRow( title = stringResource(R.string.settings__backup__wallet), - iconRes = R.drawable.ic_warning, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_lock_key) }, onClick = onBackupWalletClick, modifier = Modifier.testTag("BackupWallet"), ) SettingsButtonRow( title = stringResource(R.string.settings__backup__data), - iconRes = R.drawable.ic_warning, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_database) }, onClick = onDataBackupsClick, modifier = Modifier.testTag("BackupSettings"), ) SettingsButtonRow( title = stringResource(R.string.settings__backup__reset), - iconRes = R.drawable.ic_warning, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_arrow_counter_clockwise) }, onClick = onResetWalletClick, modifier = Modifier.testTag("ResetAndRestore"), ) @@ -466,8 +464,7 @@ private fun SecurityTabContent( SettingsButtonRow( title = stringResource(R.string.settings__security__pin), - iconRes = R.drawable.ic_warning, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_shield) }, value = SettingsButtonValue.StringValue( stringResource( if (isPinEnabled) { @@ -484,8 +481,7 @@ private fun SecurityTabContent( if (isPinEnabled) { SettingsSwitchRow( title = stringResource(R.string.settings__security__pin_payments), - iconRes = R.drawable.ic_coins, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_coins) }, isChecked = isPinForPaymentsEnabled, onClick = onPinForPaymentsClick, modifier = Modifier.testTag("EnablePinForPayments"), @@ -498,8 +494,7 @@ private fun SecurityTabContent( stringResource(R.string.settings__security__use_bio) .replace("{biometryTypeName}", bioTypeName) }, - iconRes = R.drawable.ic_warning, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_smiley) }, isChecked = isBiometricEnabled, onClick = onUseBiometricsClick, modifier = Modifier.testTag("UseBiometryInstead"), @@ -509,8 +504,7 @@ private fun SecurityTabContent( SettingsSwitchRow( title = stringResource(R.string.settings__security__warn_100), - iconRes = R.drawable.ic_warning, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_warning) }, isChecked = enableSendAmountWarning, onClick = onSendAmountWarningClick, modifier = Modifier.testTag("SendAmountWarning"), @@ -524,24 +518,21 @@ private fun SecurityTabContent( SettingsSwitchRow( title = stringResource(R.string.settings__security__swipe_balance_to_hide), - iconRes = R.drawable.ic_warning, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_hand_pointing) }, isChecked = enableSwipeToHideBalance, onClick = onSwipeToHideBalanceClick, modifier = Modifier.testTag("SwipeBalanceToHide"), ) SettingsSwitchRow( title = stringResource(R.string.settings__security__hide_balance_on_open), - iconRes = R.drawable.ic_warning, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_eye_slash) }, isChecked = hideBalanceOnOpen, onClick = onHideBalanceOnOpenClick, modifier = Modifier.testTag("HideBalanceOnOpen"), ) SettingsSwitchRow( title = stringResource(R.string.settings__security__clipboard), - iconRes = R.drawable.ic_clipboard_text, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_clipboard_text) }, isChecked = enableAutoReadClipboard, onClick = onAutoReadClipboardClick, modifier = Modifier.testTag("AutoReadClipboard"), @@ -577,8 +568,7 @@ private fun AdvancedTabContent( SettingsButtonRow( title = stringResource(R.string.settings__dev_title), - iconRes = R.drawable.ic_settings_dev, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_settings_dev) }, onClick = onDevSettingsClick, modifier = Modifier.testTag("DevSettings"), ) @@ -589,8 +579,7 @@ private fun AdvancedTabContent( SettingsButtonRow( title = stringResource(R.string.settings__addr_type__title), - iconRes = R.drawable.ic_warning, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_list_dashes) }, value = if (selectedAddressTypeName.isNotEmpty()) { SettingsButtonValue.StringValue(selectedAddressTypeName) } else { @@ -601,15 +590,13 @@ private fun AdvancedTabContent( ) SettingsButtonRow( title = stringResource(R.string.settings__adv__coin_selection), - iconRes = R.drawable.ic_coins, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_coins) }, onClick = onCoinSelectionClick, modifier = Modifier.testTag("CoinSelectPreference"), ) SettingsButtonRow( title = stringResource(R.string.settings__adv__address_viewer), - iconRes = R.drawable.ic_eye, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_eye) }, onClick = onAddressViewerClick, modifier = Modifier.testTag("AddressViewer"), ) @@ -622,29 +609,25 @@ private fun AdvancedTabContent( SettingsButtonRow( title = stringResource(R.string.settings__adv__lightning_connections), - iconRes = R.drawable.ic_git_branch, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_lightning) }, onClick = onLightningConnectionsClick, modifier = Modifier.testTag("Channels"), ) SettingsButtonRow( title = stringResource(R.string.settings__adv__lightning_node), - iconRes = R.drawable.ic_git_branch, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_git_branch) }, onClick = onLightningNodeClick, modifier = Modifier.testTag("LightningNodeInfo"), ) SettingsButtonRow( title = stringResource(R.string.settings__adv__electrum_server), - iconRes = R.drawable.ic_warning, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_hard_drives) }, onClick = onElectrumServerClick, modifier = Modifier.testTag("ElectrumConfig"), ) SettingsButtonRow( title = stringResource(R.string.settings__adv__rgs_server), - iconRes = R.drawable.ic_broadcast, - iconTint = Colors.Brand, + icon = { SettingsIcon(R.drawable.ic_broadcast) }, onClick = onRgsServerClick, modifier = Modifier.testTag("RGSServer"), ) @@ -653,6 +636,24 @@ private fun AdvancedTabContent( } } +@Composable +private fun SettingsIcon(@DrawableRes iconRes: Int) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .size(32.dp) + .background(color = Colors.Black, shape = CircleShape), + ) { + Icon( + painter = painterResource(iconRes), + contentDescription = null, + tint = Colors.Brand, + modifier = Modifier.size(16.dp), + ) + } + HorizontalSpacer(8.dp) +} + @Preview(showBackground = true) @Composable private fun PreviewGeneral() { diff --git a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt index 81d8b823f..5e43e2029 100644 --- a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt @@ -149,7 +149,7 @@ private fun Content( ) SettingsButtonRow( title = stringResource(R.string.settings__support__help), - iconRes = R.drawable.ic_warning, + iconRes = R.drawable.ic_question, onClick = onClickHelpCenter, ) SettingsButtonRow( @@ -160,7 +160,7 @@ private fun Content( ) SettingsButtonRow( title = stringResource(R.string.settings__about__legal), - iconRes = R.drawable.ic_warning, + iconRes = R.drawable.ic_file_text, onClick = onClickLegal, ) SettingsButtonRow( diff --git a/app/src/main/res/drawable/ic_arrow_counter_clockwise.xml b/app/src/main/res/drawable/ic_arrow_counter_clockwise.xml new file mode 100644 index 000000000..3e6f603a8 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_counter_clockwise.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/ic_bell.xml b/app/src/main/res/drawable/ic_bell.xml index 2c95aa02a..a7b0fb77b 100644 --- a/app/src/main/res/drawable/ic_bell.xml +++ b/app/src/main/res/drawable/ic_bell.xml @@ -1,24 +1,24 @@ + android:pathData="M2,1h11.91v14.5h-11.91z"/> + android:pathData="M9.46,2H6.46C6.185,2 5.96,1.775 5.96,1.5C5.96,1.225 6.185,1 6.46,1H9.46C9.735,1 9.96,1.225 9.96,1.5C9.96,1.775 9.735,2 9.46,2Z" + android:fillColor="#FF4400"/> + android:pathData="M3.005,13.5C2.645,13.5 2.31,13.305 2.13,12.99C1.955,12.68 1.955,12.31 2.13,12.005C2.505,11.365 2.955,10.155 2.955,7.995C2.955,7.285 3.1,6.595 3.395,5.95C3.685,5.3 4.1,4.735 4.635,4.26C5.165,3.79 5.78,3.44 6.455,3.225C7.13,3.01 7.835,2.945 8.54,3.025C11.02,3.305 12.925,5.53 12.96,8.205C12.99,10.22 13.415,11.37 13.77,11.98C13.855,12.125 13.905,12.295 13.91,12.475C13.91,12.65 13.87,12.825 13.785,12.98C13.695,13.135 13.57,13.265 13.42,13.355C13.265,13.445 13.095,13.495 12.915,13.495H3.005V13.5ZM7.955,4C7.55,4 7.145,4.06 6.755,4.185C6.215,4.355 5.725,4.635 5.3,5.01C4.875,5.39 4.54,5.845 4.31,6.36C4.075,6.88 3.96,7.43 3.96,8C3.96,10.385 3.435,11.77 3,12.515L12.915,12.5C12.495,11.775 11.995,10.455 11.965,8.225C11.935,6.055 10.415,4.25 8.43,4.025C8.275,4.005 8.115,4 7.96,4H7.955Z" + android:fillColor="#FF4400"/> + android:pathData="M7.96,15.5C7.295,15.5 6.665,15.24 6.19,14.77C5.72,14.3 5.46,13.67 5.46,13C5.46,12.725 5.685,12.5 5.96,12.5C6.235,12.5 6.46,12.725 6.46,13C6.46,13.395 6.62,13.78 6.9,14.06C7.465,14.62 8.46,14.62 9.02,14.06C9.3,13.78 9.46,13.395 9.46,13C9.46,12.725 9.685,12.5 9.96,12.5C10.235,12.5 10.46,12.725 10.46,13C10.46,13.665 10.2,14.295 9.73,14.77C9.26,15.245 8.63,15.5 7.96,15.5V15.5Z" + android:fillColor="#FF4400"/> diff --git a/app/src/main/res/drawable/ic_bitcoin_modern.xml b/app/src/main/res/drawable/ic_bitcoin_modern.xml new file mode 100644 index 000000000..67986df79 --- /dev/null +++ b/app/src/main/res/drawable/ic_bitcoin_modern.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_caret_double_right.xml b/app/src/main/res/drawable/ic_caret_double_right.xml new file mode 100644 index 000000000..b1021748f --- /dev/null +++ b/app/src/main/res/drawable/ic_caret_double_right.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/ic_clipboard_text.xml b/app/src/main/res/drawable/ic_clipboard_text.xml index c3f495f06..74aaf5fed 100644 --- a/app/src/main/res/drawable/ic_clipboard_text.xml +++ b/app/src/main/res/drawable/ic_clipboard_text.xml @@ -1,27 +1,22 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="11" + android:viewportHeight="13.5"> - diff --git a/app/src/main/res/drawable/ic_database.xml b/app/src/main/res/drawable/ic_database.xml new file mode 100644 index 000000000..4ef8cc60d --- /dev/null +++ b/app/src/main/res/drawable/ic_database.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_device_mobile_speaker.xml b/app/src/main/res/drawable/ic_device_mobile_speaker.xml new file mode 100644 index 000000000..a18ef193c --- /dev/null +++ b/app/src/main/res/drawable/ic_device_mobile_speaker.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_eye_slash.xml b/app/src/main/res/drawable/ic_eye_slash.xml new file mode 100644 index 000000000..36e0eeeda --- /dev/null +++ b/app/src/main/res/drawable/ic_eye_slash.xml @@ -0,0 +1,26 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_file_text.xml b/app/src/main/res/drawable/ic_file_text.xml new file mode 100644 index 000000000..e63887dbc --- /dev/null +++ b/app/src/main/res/drawable/ic_file_text.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_hand_pointing.xml b/app/src/main/res/drawable/ic_hand_pointing.xml new file mode 100644 index 000000000..71bc461b8 --- /dev/null +++ b/app/src/main/res/drawable/ic_hand_pointing.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/ic_hard_drives.xml b/app/src/main/res/drawable/ic_hard_drives.xml new file mode 100644 index 000000000..7e98a27ea --- /dev/null +++ b/app/src/main/res/drawable/ic_hard_drives.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_lightning.xml b/app/src/main/res/drawable/ic_lightning.xml index ab0f9788a..2e265992f 100644 --- a/app/src/main/res/drawable/ic_lightning.xml +++ b/app/src/main/res/drawable/ic_lightning.xml @@ -1,15 +1,10 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="11" + android:viewportHeight="15"> - diff --git a/app/src/main/res/drawable/ic_list_dashes.xml b/app/src/main/res/drawable/ic_list_dashes.xml new file mode 100644 index 000000000..92c0b1f78 --- /dev/null +++ b/app/src/main/res/drawable/ic_list_dashes.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_lock_key.xml b/app/src/main/res/drawable/ic_lock_key.xml new file mode 100644 index 000000000..b25415a7f --- /dev/null +++ b/app/src/main/res/drawable/ic_lock_key.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_question.xml b/app/src/main/res/drawable/ic_question.xml new file mode 100644 index 000000000..c1fa561ef --- /dev/null +++ b/app/src/main/res/drawable/ic_question.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_shield.xml b/app/src/main/res/drawable/ic_shield.xml new file mode 100644 index 000000000..ab8dd87af --- /dev/null +++ b/app/src/main/res/drawable/ic_shield.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/ic_smiley.xml b/app/src/main/res/drawable/ic_smiley.xml new file mode 100644 index 000000000..eb0ab1566 --- /dev/null +++ b/app/src/main/res/drawable/ic_smiley.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_stack.xml b/app/src/main/res/drawable/ic_stack.xml index 9c2d43e62..1fa1f36f0 100644 --- a/app/src/main/res/drawable/ic_stack.xml +++ b/app/src/main/res/drawable/ic_stack.xml @@ -1,23 +1,18 @@ + android:viewportWidth="13" + android:viewportHeight="14"> - diff --git a/app/src/main/res/drawable/ic_stop_circle.xml b/app/src/main/res/drawable/ic_stop_circle.xml new file mode 100644 index 000000000..4ff35298e --- /dev/null +++ b/app/src/main/res/drawable/ic_stop_circle.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/ic_translate.xml b/app/src/main/res/drawable/ic_translate.xml new file mode 100644 index 000000000..449df7825 --- /dev/null +++ b/app/src/main/res/drawable/ic_translate.xml @@ -0,0 +1,30 @@ + + + + + + + + From aa21f8ae5afc55ff792ab1072753b6393681111d Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 20 Mar 2026 13:47:41 -0300 Subject: [PATCH 07/85] feat: Support screen style --- .../to/bitkit/ui/components/settings/Links.kt | 13 ++- .../ui/components/settings/SettingsIcon.kt | 33 ++++++ .../to/bitkit/ui/settings/SettingsScreen.kt | 28 +---- .../ui/settings/support/SupportScreen.kt | 105 +++++++++++++----- 4 files changed, 116 insertions(+), 63 deletions(-) create mode 100644 app/src/main/java/to/bitkit/ui/components/settings/SettingsIcon.kt diff --git a/app/src/main/java/to/bitkit/ui/components/settings/Links.kt b/app/src/main/java/to/bitkit/ui/components/settings/Links.kt index 94728fc37..13743b6b0 100644 --- a/app/src/main/java/to/bitkit/ui/components/settings/Links.kt +++ b/app/src/main/java/to/bitkit/ui/components/settings/Links.kt @@ -9,6 +9,7 @@ import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp @@ -30,7 +31,7 @@ fun Links(modifier: Modifier = Modifier) { val intent = Intent(Intent.ACTION_VIEW, Env.BITKIT_WEBSITE.toUri()) context.startActivity(intent) }, - containerColor = Colors.White16, + containerColor = Color.Transparent, modifier = Modifier.size(48.dp) ) { Icon( @@ -44,7 +45,7 @@ fun Links(modifier: Modifier = Modifier) { val intent = Intent(Intent.ACTION_VIEW, Env.SYNONYM_MEDIUM.toUri()) context.startActivity(intent) }, - containerColor = Colors.White16, + containerColor = Color.Transparent, modifier = Modifier.size(48.dp) ) { Icon( @@ -58,7 +59,7 @@ fun Links(modifier: Modifier = Modifier) { val intent = Intent(Intent.ACTION_VIEW, Env.SYNONYM_X.toUri()) context.startActivity(intent) }, - containerColor = Colors.White16, + containerColor = Color.Transparent, modifier = Modifier.size(48.dp) ) { Icon( @@ -72,7 +73,7 @@ fun Links(modifier: Modifier = Modifier) { val intent = Intent(Intent.ACTION_VIEW, Env.BITKIT_DISCORD.toUri()) context.startActivity(intent) }, - containerColor = Colors.White16, + containerColor = Color.Transparent, modifier = Modifier.size(48.dp) ) { Icon( @@ -86,7 +87,7 @@ fun Links(modifier: Modifier = Modifier) { val intent = Intent(Intent.ACTION_VIEW, Env.BITKIT_TELEGRAM.toUri()) context.startActivity(intent) }, - containerColor = Colors.White16, + containerColor = Color.Transparent, modifier = Modifier.size(48.dp) ) { Icon( @@ -100,7 +101,7 @@ fun Links(modifier: Modifier = Modifier) { val intent = Intent(Intent.ACTION_VIEW, Env.BITKIT_GITHUB.toUri()) context.startActivity(intent) }, - containerColor = Colors.White16, + containerColor = Color.Transparent, modifier = Modifier.size(48.dp) ) { Icon( diff --git a/app/src/main/java/to/bitkit/ui/components/settings/SettingsIcon.kt b/app/src/main/java/to/bitkit/ui/components/settings/SettingsIcon.kt new file mode 100644 index 000000000..ebbbfd126 --- /dev/null +++ b/app/src/main/java/to/bitkit/ui/components/settings/SettingsIcon.kt @@ -0,0 +1,33 @@ +package to.bitkit.ui.components.settings + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import to.bitkit.ui.components.HorizontalSpacer +import to.bitkit.ui.theme.Colors + +@Composable +fun SettingsIcon(@DrawableRes iconRes: Int) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .size(32.dp) + .background(color = Colors.Black, shape = CircleShape), + ) { + Icon( + painter = painterResource(iconRes), + contentDescription = null, + tint = Colors.Brand, + modifier = Modifier.size(16.dp), + ) + } + HorizontalSpacer(8.dp) +} diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index d4ed365cf..3fe68eff8 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -1,27 +1,19 @@ package to.bitkit.ui.settings -import androidx.annotation.DrawableRes -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -37,12 +29,12 @@ import to.bitkit.ui.LocalCurrencies import to.bitkit.ui.Routes import to.bitkit.ui.appViewModel import to.bitkit.ui.components.AuthCheckAction -import to.bitkit.ui.components.HorizontalSpacer import to.bitkit.ui.components.Sheet import to.bitkit.ui.components.VerticalSpacer import to.bitkit.ui.components.settings.SectionHeader import to.bitkit.ui.components.settings.SettingsButtonRow import to.bitkit.ui.components.settings.SettingsButtonValue +import to.bitkit.ui.components.settings.SettingsIcon import to.bitkit.ui.components.settings.SettingsSwitchRow import to.bitkit.ui.navigateTo import to.bitkit.ui.navigateToAuthCheck @@ -636,24 +628,6 @@ private fun AdvancedTabContent( } } -@Composable -private fun SettingsIcon(@DrawableRes iconRes: Int) { - Box( - contentAlignment = Alignment.Center, - modifier = Modifier - .size(32.dp) - .background(color = Colors.Black, shape = CircleShape), - ) { - Icon( - painter = painterResource(iconRes), - contentDescription = null, - tint = Colors.Brand, - modifier = Modifier.size(16.dp), - ) - } - HorizontalSpacer(8.dp) -} - @Preview(showBackground = true) @Composable private fun PreviewGeneral() { diff --git a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt index 5e43e2029..08252aada 100644 --- a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt @@ -2,19 +2,27 @@ package to.bitkit.ui.settings.support import android.content.Intent import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.graphics.Path import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback @@ -33,21 +41,22 @@ import to.bitkit.models.Toast import to.bitkit.ui.Routes import to.bitkit.ui.appViewModel import to.bitkit.ui.components.BodyM -import to.bitkit.ui.components.BodyS import to.bitkit.ui.components.VerticalSpacer import to.bitkit.ui.components.settings.Links import to.bitkit.ui.components.settings.SettingsButtonRow -import to.bitkit.ui.components.settings.SettingsButtonValue +import to.bitkit.ui.components.settings.SettingsIcon import to.bitkit.ui.navigateTo import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.DrawerNavIcon import to.bitkit.ui.scaffold.ScreenColumn import to.bitkit.ui.settingsViewModel +import to.bitkit.ui.shared.modifiers.clickableAlpha import to.bitkit.ui.shared.util.shareText import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors private const val DEV_MODE_TAP_THRESHOLD = 5 +private val BrandColor = Colors.Brand @Composable fun SupportScreen( @@ -144,60 +153,96 @@ private fun Content( SettingsButtonRow( title = stringResource(R.string.settings__support__report), - iconRes = R.drawable.ic_warning, + icon = { SettingsIcon(R.drawable.ic_warning) }, onClick = onClickReportIssue, ) SettingsButtonRow( title = stringResource(R.string.settings__support__help), - iconRes = R.drawable.ic_question, + icon = { SettingsIcon(R.drawable.ic_question) }, onClick = onClickHelpCenter, ) SettingsButtonRow( title = stringResource(R.string.settings__support__status), - iconRes = R.drawable.ic_settings_support, + icon = { SettingsIcon(R.drawable.ic_power) }, onClick = onClickAppStatus, modifier = Modifier.testTag("AppStatus"), ) SettingsButtonRow( title = stringResource(R.string.settings__about__legal), - iconRes = R.drawable.ic_file_text, + icon = { SettingsIcon(R.drawable.ic_file_text) }, onClick = onClickLegal, ) SettingsButtonRow( title = stringResource(R.string.settings__about__share), - iconRes = R.drawable.ic_share, + icon = { SettingsIcon(R.drawable.ic_share) }, onClick = onClickShare, ) - SettingsButtonRow( - title = stringResource(R.string.settings__about__version), - iconRes = R.drawable.ic_stack, - value = SettingsButtonValue.StringValue(appVersion), - onClick = onClickVersion, - modifier = Modifier.testTag("DevOptions"), - ) - Spacer(modifier = Modifier.weight(1f)) + // Version row — no chevron, value on right + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .heightIn(min = 52.dp) + .clickableAlpha { onClickVersion() } + .testTag("DevOptions"), + ) { + SettingsIcon(R.drawable.ic_stack) + BodyM( + text = stringResource(R.string.settings__about__version), + modifier = Modifier.weight(1f), + ) + BodyM(text = appVersion, color = Colors.White64) + } + HorizontalDivider() + + VerticalSpacer(32.dp) - Image( - painter = painterResource(R.drawable.bitkit_logo), - contentDescription = null, + // Part 1: Logo with diagonal orange crossing through it + Box( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 32.dp, vertical = 16.dp) - .height(82.dp) - .testTag("AboutLogo"), - ) + .clipToBounds() + .drawBehind { + val path = Path().apply { + moveTo(size.width, size.height * 0.1f) + lineTo(0f, size.height * 0.65f) + lineTo(0f, size.height) + lineTo(size.width, size.height) + close() + } + drawPath(path, color = BrandColor) + }, + ) { + Image( + painter = painterResource(R.drawable.bitkit_logo), + contentDescription = null, + modifier = Modifier + .fillMaxWidth() + .height(100.dp) + .testTag("AboutLogo"), + ) + } - Links(modifier = Modifier.fillMaxWidth()) + // Part 2: Solid orange background for bottom content + Column( + modifier = Modifier + .fillMaxWidth() + .background(Colors.Brand), + ) { + VerticalSpacer(16.dp) - VerticalSpacer(16.dp) + Links(modifier = Modifier.fillMaxWidth()) - BodyS( - text = stringResource(R.string.settings__support__copyright), - color = Colors.White64, - ) + VerticalSpacer(16.dp) - VerticalSpacer(32.dp) + BodyM( + text = stringResource(R.string.settings__support__copyright), + color = Colors.White64, + ) + + VerticalSpacer(32.dp) + } } } } From 6b04ffb660c1f2dfaeeead2767c0b669b3919f4f Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 23 Mar 2026 07:40:21 -0300 Subject: [PATCH 08/85] feat: Support icon --- .../to/bitkit/ui/components/DrawerMenu.kt | 2 +- app/src/main/res/drawable/ic_chats_circle.xml | 19 +++++++++++++++ .../main/res/drawable/ic_settings_support.xml | 23 ------------------- 3 files changed, 20 insertions(+), 24 deletions(-) create mode 100644 app/src/main/res/drawable/ic_chats_circle.xml delete mode 100644 app/src/main/res/drawable/ic_settings_support.xml diff --git a/app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt b/app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt index 759e8731b..d24631209 100644 --- a/app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt +++ b/app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt @@ -201,7 +201,7 @@ private fun Menu( DrawerItem( label = stringResource(R.string.wallet__drawer__support), - iconRes = R.drawable.ic_settings_support, + iconRes = R.drawable.ic_chats_circle, onClick = { rootNavController.navigateIfNotCurrent(Routes.Support) scope.launch { drawerState.close() } diff --git a/app/src/main/res/drawable/ic_chats_circle.xml b/app/src/main/res/drawable/ic_chats_circle.xml new file mode 100644 index 000000000..2da16036a --- /dev/null +++ b/app/src/main/res/drawable/ic_chats_circle.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_settings_support.xml b/app/src/main/res/drawable/ic_settings_support.xml deleted file mode 100644 index cf77bfb38..000000000 --- a/app/src/main/res/drawable/ic_settings_support.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - From 6800db465e8dbf16708b5571a02253d8cf6cc593 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 23 Mar 2026 07:45:29 -0300 Subject: [PATCH 09/85] fix: fill height --- .../main/java/to/bitkit/ui/settings/support/SupportScreen.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt index 08252aada..e93fe686a 100644 --- a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt @@ -41,6 +41,7 @@ import to.bitkit.models.Toast import to.bitkit.ui.Routes import to.bitkit.ui.appViewModel import to.bitkit.ui.components.BodyM +import to.bitkit.ui.components.FillHeight import to.bitkit.ui.components.VerticalSpacer import to.bitkit.ui.components.settings.Links import to.bitkit.ui.components.settings.SettingsButtonRow @@ -143,6 +144,7 @@ private fun Content( Column( modifier = Modifier .padding(horizontal = 16.dp) + .weight(1f) .verticalScroll(rememberScrollState()) ) { VerticalSpacer(16.dp) @@ -197,7 +199,7 @@ private fun Content( HorizontalDivider() VerticalSpacer(32.dp) - + FillHeight() // Part 1: Logo with diagonal orange crossing through it Box( modifier = Modifier From e5037a3f0a5ab1ac8ce7dcb45a8c64ff7b42efab Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 23 Mar 2026 07:55:50 -0300 Subject: [PATCH 10/85] fix: align footer to bottom --- .../ui/settings/support/SupportScreen.kt | 185 ++++++++++-------- 1 file changed, 98 insertions(+), 87 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt index e93fe686a..68fdf4b4e 100644 --- a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt @@ -143,109 +143,120 @@ private fun Content( Column( modifier = Modifier - .padding(horizontal = 16.dp) .weight(1f) .verticalScroll(rememberScrollState()) ) { - VerticalSpacer(16.dp) - - BodyM(text = stringResource(R.string.settings__support__text), color = Colors.White64) + // Padded content — setting rows + Column(modifier = Modifier.padding(horizontal = 16.dp)) { + VerticalSpacer(16.dp) - VerticalSpacer(16.dp) + BodyM(text = stringResource(R.string.settings__support__text), color = Colors.White64) - SettingsButtonRow( - title = stringResource(R.string.settings__support__report), - icon = { SettingsIcon(R.drawable.ic_warning) }, - onClick = onClickReportIssue, - ) - SettingsButtonRow( - title = stringResource(R.string.settings__support__help), - icon = { SettingsIcon(R.drawable.ic_question) }, - onClick = onClickHelpCenter, - ) - SettingsButtonRow( - title = stringResource(R.string.settings__support__status), - icon = { SettingsIcon(R.drawable.ic_power) }, - onClick = onClickAppStatus, - modifier = Modifier.testTag("AppStatus"), - ) - SettingsButtonRow( - title = stringResource(R.string.settings__about__legal), - icon = { SettingsIcon(R.drawable.ic_file_text) }, - onClick = onClickLegal, - ) - SettingsButtonRow( - title = stringResource(R.string.settings__about__share), - icon = { SettingsIcon(R.drawable.ic_share) }, - onClick = onClickShare, - ) + VerticalSpacer(16.dp) - // Version row — no chevron, value on right - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .heightIn(min = 52.dp) - .clickableAlpha { onClickVersion() } - .testTag("DevOptions"), - ) { - SettingsIcon(R.drawable.ic_stack) - BodyM( - text = stringResource(R.string.settings__about__version), - modifier = Modifier.weight(1f), + SettingsButtonRow( + title = stringResource(R.string.settings__support__report), + icon = { SettingsIcon(R.drawable.ic_warning) }, + onClick = onClickReportIssue, + ) + SettingsButtonRow( + title = stringResource(R.string.settings__support__help), + icon = { SettingsIcon(R.drawable.ic_question) }, + onClick = onClickHelpCenter, + ) + SettingsButtonRow( + title = stringResource(R.string.settings__support__status), + icon = { SettingsIcon(R.drawable.ic_power) }, + onClick = onClickAppStatus, + modifier = Modifier.testTag("AppStatus"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__about__legal), + icon = { SettingsIcon(R.drawable.ic_file_text) }, + onClick = onClickLegal, + ) + SettingsButtonRow( + title = stringResource(R.string.settings__about__share), + icon = { SettingsIcon(R.drawable.ic_share) }, + onClick = onClickShare, ) - BodyM(text = appVersion, color = Colors.White64) - } - HorizontalDivider() - VerticalSpacer(32.dp) - FillHeight() - // Part 1: Logo with diagonal orange crossing through it - Box( - modifier = Modifier - .fillMaxWidth() - .clipToBounds() - .drawBehind { - val path = Path().apply { - moveTo(size.width, size.height * 0.1f) - lineTo(0f, size.height * 0.65f) - lineTo(0f, size.height) - lineTo(size.width, size.height) - close() - } - drawPath(path, color = BrandColor) - }, - ) { - Image( - painter = painterResource(R.drawable.bitkit_logo), - contentDescription = null, + // Version row — no chevron, value on right + Row( + verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() - .height(100.dp) - .testTag("AboutLogo"), - ) + .heightIn(min = 52.dp) + .clickableAlpha { onClickVersion() } + .testTag("DevOptions"), + ) { + SettingsIcon(R.drawable.ic_stack) + BodyM( + text = stringResource(R.string.settings__about__version), + modifier = Modifier.weight(1f), + ) + BodyM(text = appVersion, color = Colors.White64) + } + HorizontalDivider() } - // Part 2: Solid orange background for bottom content - Column( - modifier = Modifier - .fillMaxWidth() - .background(Colors.Brand), - ) { - VerticalSpacer(16.dp) + VerticalSpacer(32.dp) - Links(modifier = Modifier.fillMaxWidth()) + FillHeight() - VerticalSpacer(16.dp) + SupportFooter() + } + } +} - BodyM( - text = stringResource(R.string.settings__support__copyright), - color = Colors.White64, - ) +@Composable +private fun SupportFooter() { + // Bitkit logo with diagonal orange crossing through it + Box( + modifier = Modifier + .fillMaxWidth() + .clipToBounds() + .drawBehind { + val path = Path().apply { + moveTo(size.width, size.height * 0.1f) + lineTo(0f, size.height * 0.65f) + lineTo(0f, size.height) + lineTo(size.width, size.height) + close() + } + drawPath(path, color = BrandColor) + }, + ) { + Image( + painter = painterResource(R.drawable.bitkit_logo), + contentDescription = null, + modifier = Modifier + .fillMaxWidth() + .height(100.dp) + .padding(horizontal = 16.dp) + .testTag("AboutLogo"), + ) + } - VerticalSpacer(32.dp) - } - } + // Solid orange background for bottom content + Column( + modifier = Modifier + .fillMaxWidth() + .background(Colors.Brand) + .padding(horizontal = 16.dp), + ) { + VerticalSpacer(16.dp) + + Links(modifier = Modifier.fillMaxWidth()) + + VerticalSpacer(16.dp) + + BodyM( + text = stringResource(R.string.settings__support__copyright), + color = Colors.White64, + ) + + VerticalSpacer(32.dp) } } From a4b18bfa35ce731beeb2f5b43343dd5c51203595 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 23 Mar 2026 08:06:37 -0300 Subject: [PATCH 11/85] feat: brand endorsement --- .../ui/settings/support/SupportScreen.kt | 25 +++++++++ app/src/main/res/drawable/ic_synonym_logo.xml | 30 +++++++++++ .../main/res/drawable/ic_tether_company.xml | 52 +++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 app/src/main/res/drawable/ic_synonym_logo.xml create mode 100644 app/src/main/res/drawable/ic_tether_company.xml diff --git a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt index 68fdf4b4e..455c3845e 100644 --- a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt @@ -3,6 +3,7 @@ package to.bitkit.ui.settings.support import android.content.Intent import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -256,10 +257,34 @@ private fun SupportFooter() { color = Colors.White64, ) + VerticalSpacer(16.dp) + + BrandEndorsement() + VerticalSpacer(32.dp) } } +@Composable +private fun BrandEndorsement() { + Row( + horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth(), + ) { + Image( + painter = painterResource(R.drawable.ic_synonym_logo), + contentDescription = null, + modifier = Modifier.height(24.dp), + ) + Image( + painter = painterResource(R.drawable.ic_tether_company), + contentDescription = null, + modifier = Modifier.height(16.dp), + ) + } +} + @Preview(showSystemUi = true) @Composable private fun Preview() { diff --git a/app/src/main/res/drawable/ic_synonym_logo.xml b/app/src/main/res/drawable/ic_synonym_logo.xml new file mode 100644 index 000000000..49a7d4da3 --- /dev/null +++ b/app/src/main/res/drawable/ic_synonym_logo.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_tether_company.xml b/app/src/main/res/drawable/ic_tether_company.xml new file mode 100644 index 000000000..e689f1c48 --- /dev/null +++ b/app/src/main/res/drawable/ic_tether_company.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + From de8bf0ce63d2e39e7fc1e35d4d9892a6b3c37373 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 23 Mar 2026 08:15:51 -0300 Subject: [PATCH 12/85] refactor: simplify Links component --- .../to/bitkit/ui/components/settings/Links.kt | 118 +++++------------- 1 file changed, 29 insertions(+), 89 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/components/settings/Links.kt b/app/src/main/java/to/bitkit/ui/components/settings/Links.kt index 13743b6b0..1c3c49458 100644 --- a/app/src/main/java/to/bitkit/ui/components/settings/Links.kt +++ b/app/src/main/java/to/bitkit/ui/components/settings/Links.kt @@ -1,15 +1,14 @@ package to.bitkit.ui.components.settings import android.content.Intent +import androidx.annotation.DrawableRes import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.size -import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp @@ -18,97 +17,38 @@ import to.bitkit.R import to.bitkit.env.Env import to.bitkit.ui.theme.Colors +private data class SocialLink(@DrawableRes val iconRes: Int, val url: String) + +private val socialLinks = listOf( + SocialLink(R.drawable.ic_globe, Env.BITKIT_WEBSITE), + SocialLink(R.drawable.ic_medium, Env.SYNONYM_MEDIUM), + SocialLink(R.drawable.ic_x_twitter, Env.SYNONYM_X), + SocialLink(R.drawable.ic_discord, Env.BITKIT_DISCORD), + SocialLink(R.drawable.ic_telegram, Env.BITKIT_TELEGRAM), + SocialLink(R.drawable.ic_github, Env.BITKIT_GITHUB), +) + @Composable fun Links(modifier: Modifier = Modifier) { val context = LocalContext.current - FlowRow( + Row( horizontalArrangement = Arrangement.SpaceBetween, - modifier = modifier.fillMaxWidth() + modifier = modifier, ) { - FloatingActionButton( - onClick = { - val intent = Intent(Intent.ACTION_VIEW, Env.BITKIT_WEBSITE.toUri()) - context.startActivity(intent) - }, - containerColor = Color.Transparent, - modifier = Modifier.size(48.dp) - ) { - Icon( - painter = painterResource(R.drawable.ic_globe), - contentDescription = null, - tint = Colors.White - ) - } - FloatingActionButton( - onClick = { - val intent = Intent(Intent.ACTION_VIEW, Env.SYNONYM_MEDIUM.toUri()) - context.startActivity(intent) - }, - containerColor = Color.Transparent, - modifier = Modifier.size(48.dp) - ) { - Icon( - painter = painterResource(R.drawable.ic_medium), - contentDescription = null, - tint = Colors.White - ) - } - FloatingActionButton( - onClick = { - val intent = Intent(Intent.ACTION_VIEW, Env.SYNONYM_X.toUri()) - context.startActivity(intent) - }, - containerColor = Color.Transparent, - modifier = Modifier.size(48.dp) - ) { - Icon( - painter = painterResource(R.drawable.ic_x_twitter), - contentDescription = null, - tint = Colors.White - ) - } - FloatingActionButton( - onClick = { - val intent = Intent(Intent.ACTION_VIEW, Env.BITKIT_DISCORD.toUri()) - context.startActivity(intent) - }, - containerColor = Color.Transparent, - modifier = Modifier.size(48.dp) - ) { - Icon( - painter = painterResource(R.drawable.ic_discord), - contentDescription = null, - tint = Colors.White - ) - } - FloatingActionButton( - onClick = { - val intent = Intent(Intent.ACTION_VIEW, Env.BITKIT_TELEGRAM.toUri()) - context.startActivity(intent) - }, - containerColor = Color.Transparent, - modifier = Modifier.size(48.dp) - ) { - Icon( - painter = painterResource(R.drawable.ic_telegram), - contentDescription = null, - tint = Colors.White - ) - } - FloatingActionButton( - onClick = { - val intent = Intent(Intent.ACTION_VIEW, Env.BITKIT_GITHUB.toUri()) - context.startActivity(intent) - }, - containerColor = Color.Transparent, - modifier = Modifier.size(48.dp) - ) { - Icon( - painter = painterResource(R.drawable.ic_github), - contentDescription = null, - tint = Colors.White - ) + socialLinks.forEach { link -> + IconButton( + onClick = { + context.startActivity(Intent(Intent.ACTION_VIEW, link.url.toUri())) + }, + modifier = Modifier.size(48.dp), + ) { + Icon( + painter = painterResource(link.iconRes), + contentDescription = null, + modifier = Modifier.size(24.dp), + ) + } } } } From 62ebfbbb4448cbac6559e68d3214af6cdfc229b4 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 23 Mar 2026 08:28:17 -0300 Subject: [PATCH 13/85] chore: preview --- .../main/java/to/bitkit/ui/components/settings/Links.kt | 1 - .../java/to/bitkit/ui/settings/support/SupportScreen.kt | 9 +++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/ui/components/settings/Links.kt b/app/src/main/java/to/bitkit/ui/components/settings/Links.kt index 1c3c49458..11a2e03b9 100644 --- a/app/src/main/java/to/bitkit/ui/components/settings/Links.kt +++ b/app/src/main/java/to/bitkit/ui/components/settings/Links.kt @@ -15,7 +15,6 @@ import androidx.compose.ui.unit.dp import androidx.core.net.toUri import to.bitkit.R import to.bitkit.env.Env -import to.bitkit.ui.theme.Colors private data class SocialLink(@DrawableRes val iconRes: Int, val url: String) diff --git a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt index 455c3845e..2c4a1b9a1 100644 --- a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Devices.PIXEL_TABLET import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.net.toUri @@ -292,3 +293,11 @@ private fun Preview() { Content() } } + +@Preview(showSystemUi = true, device = PIXEL_TABLET) +@Composable +private fun PreviewTablet() { + AppThemeSurface { + Content() + } +} From c8cab0289ce9f9b8f5ff0f9e04cca63dbf0e247d Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 23 Mar 2026 08:38:23 -0300 Subject: [PATCH 14/85] chore: settings preview --- .../to/bitkit/ui/settings/SettingsScreen.kt | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index 3fe68eff8..5b0336077 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -221,9 +221,13 @@ private fun SettingsContent( onRgsServerClick: () -> Unit = {}, // Navigation onBackClick: () -> Unit = {}, + initialTab: SettingsTab = SettingsTab.General, ) { val tabs = remember { SettingsTab.entries } - val pagerState = rememberPagerState(pageCount = { tabs.size }) + val pagerState = rememberPagerState( + initialPage = tabs.indexOf(initialTab), + pageCount = { tabs.size }, + ) val scope = rememberCoroutineScope() ScreenColumn { @@ -632,6 +636,32 @@ private fun AdvancedTabContent( @Composable private fun PreviewGeneral() { AppThemeSurface { - SettingsContent() + SettingsContent(showTagsButton = true) + } +} + +@Preview(showBackground = true) +@Composable +private fun PreviewSecurity() { + AppThemeSurface { + SettingsContent( + isPinEnabled = true, + isPinForPaymentsEnabled = true, + enableSwipeToHideBalance = true, + isBiometrySupported = true, + initialTab = SettingsTab.Security, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun PreviewAdvanced() { + AppThemeSurface { + SettingsContent( + isDevModeEnabled = true, + selectedAddressTypeName = "Taproot", + initialTab = SettingsTab.Advanced, + ) } } From da8c4807c1b67cbd12cea7188a3649173db3c707 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 23 Mar 2026 08:44:56 -0300 Subject: [PATCH 15/85] fix: drawer icon color --- app/src/main/java/to/bitkit/ui/scaffold/AppTopBar.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/to/bitkit/ui/scaffold/AppTopBar.kt b/app/src/main/java/to/bitkit/ui/scaffold/AppTopBar.kt index 8741a9bb9..2dd1e8751 100644 --- a/app/src/main/java/to/bitkit/ui/scaffold/AppTopBar.kt +++ b/app/src/main/java/to/bitkit/ui/scaffold/AppTopBar.kt @@ -30,6 +30,7 @@ import to.bitkit.ui.LocalDrawerState import to.bitkit.ui.components.Title import to.bitkit.ui.shared.modifiers.rememberDebouncedClick import to.bitkit.ui.theme.AppThemeSurface +import to.bitkit.ui.theme.Colors @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -107,6 +108,7 @@ fun DrawerNavIcon( Icon( painter = painterResource(id = R.drawable.ic_list), contentDescription = stringResource(R.string.settings__settings), + tint = Colors.White, modifier = Modifier.size(24.dp) ) } From a97ad9bec1a05560ec5f3286343616e9397823b5 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 23 Mar 2026 08:51:58 -0300 Subject: [PATCH 16/85] feat: settings value color --- .../java/to/bitkit/ui/components/settings/SettingsButtonRow.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt b/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt index 680a7ee49..7210cd079 100644 --- a/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt +++ b/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt @@ -183,7 +183,7 @@ private fun SettingsButtonRowCore( } is SettingsButtonValue.StringValue -> { - BodyM(text = value.value, modifier = Modifier.testTag("Value")) + BodyM(text = value.value, color = Colors.White64, modifier = Modifier.testTag("Value")) Spacer(modifier = Modifier.width(8.dp)) Icon( painter = painterResource(R.drawable.ic_chevron_right), From 6ebfb7e99b7a1179d67c59e2d2f07ddfe8dd5221 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 23 Mar 2026 09:06:40 -0300 Subject: [PATCH 17/85] feat: show widgets state on/off and tags amount --- .../to/bitkit/ui/settings/SettingsScreen.kt | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index 5b0336077..f6bdb93e7 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -79,6 +79,7 @@ fun SettingsScreen( // General tab state val defaultTransactionSpeed by settings.defaultTransactionSpeed.collectAsStateWithLifecycle() val lastUsedTags by settings.lastUsedTags.collectAsStateWithLifecycle() + val showWidgets by settings.showWidgets.collectAsStateWithLifecycle() val quickPayIntroSeen by settings.quickPayIntroSeen.collectAsStateWithLifecycle() val bgPaymentsIntroSeen by settings.bgPaymentsIntroSeen.collectAsStateWithLifecycle() val notificationsGranted by settings.notificationsGranted.collectAsStateWithLifecycle() @@ -105,7 +106,8 @@ fun SettingsScreen( primaryDisplay = currencies.primaryDisplay, defaultTransactionSpeed = defaultTransactionSpeed, selectedLanguage = languageUiState.selectedLanguage.displayName, - showTagsButton = lastUsedTags.isNotEmpty(), + showWidgets = showWidgets, + tagCount = lastUsedTags.size, notificationsGranted = notificationsGranted, onLanguageClick = { navController.navigateToLanguageSettings() }, onLocalCurrencyClick = { navController.navigateToLocalCurrencySettings() }, @@ -179,7 +181,8 @@ private fun SettingsContent( primaryDisplay: PrimaryDisplay = PrimaryDisplay.BITCOIN, defaultTransactionSpeed: TransactionSpeed = TransactionSpeed.Medium, selectedLanguage: String = "", - showTagsButton: Boolean = false, + showWidgets: Boolean = true, + tagCount: Int = 0, notificationsGranted: Boolean = false, onLanguageClick: () -> Unit = {}, onLocalCurrencyClick: () -> Unit = {}, @@ -252,7 +255,8 @@ private fun SettingsContent( primaryDisplay = primaryDisplay, defaultTransactionSpeed = defaultTransactionSpeed, selectedLanguage = selectedLanguage, - showTagsButton = showTagsButton, + showWidgets = showWidgets, + tagCount = tagCount, notificationsGranted = notificationsGranted, onLanguageClick = onLanguageClick, onLocalCurrencyClick = onLocalCurrencyClick, @@ -308,7 +312,8 @@ private fun GeneralTabContent( primaryDisplay: PrimaryDisplay, defaultTransactionSpeed: TransactionSpeed, selectedLanguage: String, - showTagsButton: Boolean, + showWidgets: Boolean, + tagCount: Int, notificationsGranted: Boolean, onLanguageClick: () -> Unit, onLocalCurrencyClick: () -> Unit, @@ -357,13 +362,17 @@ private fun GeneralTabContent( SettingsButtonRow( title = stringResource(R.string.settings__widgets__nav_title), icon = { SettingsIcon(R.drawable.ic_stack) }, + value = SettingsButtonValue.StringValue( + stringResource(if (showWidgets) R.string.settings__bg__on else R.string.settings__bg__off) + ), onClick = onWidgetsClick, modifier = Modifier.testTag("WidgetsSettings"), ) - if (showTagsButton) { + if (tagCount > 0) { SettingsButtonRow( title = stringResource(R.string.settings__general__tags), icon = { SettingsIcon(R.drawable.ic_tag) }, + value = SettingsButtonValue.StringValue(tagCount.toString()), onClick = onTagsClick, modifier = Modifier.testTag("TagsSettings"), ) @@ -636,7 +645,7 @@ private fun AdvancedTabContent( @Composable private fun PreviewGeneral() { AppThemeSurface { - SettingsContent(showTagsButton = true) + SettingsContent(tagCount = 3) } } From 219b05c8de9dc614cea659e8491e52f6779e9703 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 23 Mar 2026 09:11:25 -0300 Subject: [PATCH 18/85] feat: update transaction speed icon dynamically --- .../main/java/to/bitkit/ui/settings/SettingsScreen.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index f6bdb93e7..0e270046a 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -386,7 +386,15 @@ private fun GeneralTabContent( SettingsButtonRow( title = stringResource(R.string.settings__general__speed), - icon = { SettingsIcon(R.drawable.ic_speed_normal) }, + icon = { + SettingsIcon( + when (defaultTransactionSpeed) { + is TransactionSpeed.Fast -> R.drawable.ic_speed_fast + is TransactionSpeed.Slow -> R.drawable.ic_speed_slow + else -> R.drawable.ic_speed_normal + } + ) + }, value = SettingsButtonValue.StringValue(defaultTransactionSpeed.transactionSpeedUiText()), onClick = onTransactionSpeedClick, modifier = Modifier.testTag("TransactionSpeedSettings"), From a33193f5e61d4a0db34d0aee6455d0a37e9b2002 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 23 Mar 2026 09:21:06 -0300 Subject: [PATCH 19/85] feat: quickpay value indicator --- app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index 0e270046a..8f668384c 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -80,6 +80,7 @@ fun SettingsScreen( val defaultTransactionSpeed by settings.defaultTransactionSpeed.collectAsStateWithLifecycle() val lastUsedTags by settings.lastUsedTags.collectAsStateWithLifecycle() val showWidgets by settings.showWidgets.collectAsStateWithLifecycle() + val isQuickPayEnabled by settings.isQuickpayEnabled.collectAsStateWithLifecycle() val quickPayIntroSeen by settings.quickPayIntroSeen.collectAsStateWithLifecycle() val bgPaymentsIntroSeen by settings.bgPaymentsIntroSeen.collectAsStateWithLifecycle() val notificationsGranted by settings.notificationsGranted.collectAsStateWithLifecycle() @@ -108,6 +109,7 @@ fun SettingsScreen( selectedLanguage = languageUiState.selectedLanguage.displayName, showWidgets = showWidgets, tagCount = lastUsedTags.size, + isQuickPayEnabled = isQuickPayEnabled, notificationsGranted = notificationsGranted, onLanguageClick = { navController.navigateToLanguageSettings() }, onLocalCurrencyClick = { navController.navigateToLocalCurrencySettings() }, @@ -183,6 +185,7 @@ private fun SettingsContent( selectedLanguage: String = "", showWidgets: Boolean = true, tagCount: Int = 0, + isQuickPayEnabled: Boolean = false, notificationsGranted: Boolean = false, onLanguageClick: () -> Unit = {}, onLocalCurrencyClick: () -> Unit = {}, @@ -257,6 +260,7 @@ private fun SettingsContent( selectedLanguage = selectedLanguage, showWidgets = showWidgets, tagCount = tagCount, + isQuickPayEnabled = isQuickPayEnabled, notificationsGranted = notificationsGranted, onLanguageClick = onLanguageClick, onLocalCurrencyClick = onLocalCurrencyClick, @@ -314,6 +318,7 @@ private fun GeneralTabContent( selectedLanguage: String, showWidgets: Boolean, tagCount: Int, + isQuickPayEnabled: Boolean, notificationsGranted: Boolean, onLanguageClick: () -> Unit, onLocalCurrencyClick: () -> Unit, @@ -402,6 +407,9 @@ private fun GeneralTabContent( SettingsButtonRow( title = stringResource(R.string.settings__quickpay__nav_title), icon = { SettingsIcon(R.drawable.ic_caret_double_right) }, + value = SettingsButtonValue.StringValue( + stringResource(if (isQuickPayEnabled) R.string.settings__bg__on else R.string.settings__bg__off) + ), onClick = onQuickPayClick, modifier = Modifier.testTag("QuickpaySettings"), ) From 005794153ec9f52b917ecea87cb2be60c82676b9 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 23 Mar 2026 09:31:16 -0300 Subject: [PATCH 20/85] feat: shield check icon --- .../ui/settings/pin/PinManagementScreen.kt | 6 +++++- .../main/res/drawable-hdpi/shield_check.webp | Bin 0 -> 14540 bytes .../main/res/drawable-mdpi/shield_check.webp | Bin 0 -> 7594 bytes .../main/res/drawable-xhdpi/shield_check.webp | Bin 0 -> 23304 bytes .../main/res/drawable-xxhdpi/shield_check.webp | Bin 0 -> 51084 bytes .../main/res/drawable-xxxhdpi/shield_check.webp | Bin 0 -> 46004 bytes 6 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable-hdpi/shield_check.webp create mode 100644 app/src/main/res/drawable-mdpi/shield_check.webp create mode 100644 app/src/main/res/drawable-xhdpi/shield_check.webp create mode 100644 app/src/main/res/drawable-xxhdpi/shield_check.webp create mode 100644 app/src/main/res/drawable-xxxhdpi/shield_check.webp diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt index fa02b892d..ca2f7fb58 100644 --- a/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt @@ -101,7 +101,11 @@ private fun Content( .weight(1f) ) { Image( - painter = painterResource(R.drawable.shield), + painter = if (isPinEnabled) { + painterResource(R.drawable.shield_check) + } else { + painterResource(R.drawable.shield) + }, contentDescription = null, modifier = Modifier.size(256.dp), ) diff --git a/app/src/main/res/drawable-hdpi/shield_check.webp b/app/src/main/res/drawable-hdpi/shield_check.webp new file mode 100644 index 0000000000000000000000000000000000000000..a5f5155297358c8ba393d2b26c1e5e80daed5ecc GIT binary patch literal 14540 zcmV;-I5WpmNk&G*H~;`wMM6+kP&il$0000G0001g0RVpi06|PpNY5Am00A5YZQDpL zf7sjZB@hu4kc4cq(wp#6wR9R&t8LG=ZQ0g&eY3yJDGmr8Kn2La2QqMh2{hn+qTBGZQE8)kLT{O?P-l|wQXzIwq1zx+<#ox z6`7S?5oi3mJ0d24`}}{O|NlQnX}TAJi0PdKtE>PqtrI-_a~-r(!zs0EVi9XegkRh7JS;n;?7*|egrXP3!b;M6!&JIyD^%o z1v_2p>=CoSq0kgfvG9)`deo8^Kb4rG1#j@sqi3JAhBP%3+h6583unKRn3hR{!<;?4 zi?>ir$%5zIlgq4J{3$dY6YF2-?76sRdtxdU9O&%1%>IHj4GSK0Qz>&(uG@o{en~6; z$=P#T{EejPSK^I1d#=h&_a~MI{!1g+;d0NJvwyKbnlrzmm;Q@En)$J_=e(3fPbKE8 z^3Jc}zZ!#AFD_;7oPEq%QjhwCP+2%ScHqA^j_ zXD_2GcOkk&uaL6PUZqkNN|iFHEc};&BL2_W%Q*WFD@kh2Qk5!&)=E;e)qhzgo^xj@ z%c^9*hoDm+jYMf)h-|bJ&GJh17wqz1XD{Fds(r$hf{H3HEsBo#`DQYn%Wiq9Ezq7A?O(IfDX!H>BehN}q78@bdr_mIOiV4;_)TMa7XZzQCKtu;e zl(%hctEkd!(H>#+pk_ntakO*s{4d2feq|?!6zXbs9HE7fWL>EM5)!jihk|wA_}!<^ zN|m61XZ_FF_s(oz{+V5Y6e9Ww@}C(cK}bR!YKd;aLr$`9x*suGB?fHvwMEXpx0Y<* zc<>Wf13km9v?)RHH&AL#)mY=%L&c64TTOxX&)hNr zN=*|AwId=V#j=o)Xek1=`||D1#iKlsG$}+~UCf zcKg~BfAEgSZL=!9DK*WYNll9WR&=H&1;9qH_}y)GDIUG}?!8D8fq22Kw*BA#+YD*` zL;!ny;S#&#;&EAW&Tf|*&iTVv-~HSN@3`Iy%v#;_ue{qUe|)L!Ts+(2+jk`ZY0L$? zUgxF5VYV-NCTM;r1PdQ~&`oyE9n}R)x8MKdyI%3k$2?%SZ8lze)fE>G zQV0~y!d2GUdcUW=7Ey7bPQuD;}=b51$#sK5X9Pk;XF5yzf(!KF7YcDoF-=d}3h%_o9jn;S}L*}3=& zNm@3F09JYYw=c0hEFSZvi2*D-*2&K-e?L&+vMFj1ct)KRpV{fvZb8!V`9~DSrFL?0XE~7a6%*|=kj))YZtu=Rk?#KUp zS+-qraUEv=j!7_hg^Nee{@ePDJhQ>>&)WasQ?DtuopW(D4t+2INaOC4&a-g#-}YW@ z;er)b+i1rJKl@!@|KqvWFLgWTTwIgcUn0b;HFn^qvvct+XCHsuNoW7}hDGi!IhW$v z%>E#We)Wu9F>~Y`IJ>{(oO3Cz((E^p1d;)*Ty3tFOEV=CD4a+f)% z;^M`RB^dj`?l;%lE&Ft6+&%1`TFbtCtCm3HEb+8lZx^gbAo?|~Uhd*rJAP%v$Qn~` zuePCon+Qn=5aT-WVdrZ59+HHR=+*cMK3j1I5(q$mI>vDDjcUt&YaaqopT>$fsM`Fm zf7zp1(npJ**IMyE{@Fy!&w}6A+duqi17#|oB;p^nmi_mCyh*Bj)68-8_IJO}93~~@ zGt{xdsrB}^&lD=zeXCgc>{=W8ZyM&3(kF}6FQ~QbzwRL|P|AsqSnFRdwf0w&meMzi zb^ljs#r}(aF;^;*4_g1SYP#ca{ zQ+L}0ZGFb3*VNk0`5sR-Bn_N|A+5g$Ru|u2mtzzS9J7M)6V^h%Dcc``D6?HcB zPm*CD%IaH#^)IWn&+r&yLsWbTK*73~)LHhM_{Ffzx)q-g0E% zlnd$5N`OfJsI#m0=omlH(v8-pnMp!_s0GCvoMS? z2@zdgiA~~zRrcE^rSyyghoM|dDKUw}yId;l%MAK3yo}^7?DkW;;Y}4*{7z>2hT%Ac z?kpx$Nr;A*+oiIKmp-#Kp2c8nLM7!++X;|JJhxQXP4{Dv(lhpqg%tN%nsV6?Wt*(eaEi3~y_pTayx^<3V>-*rCxhDLv14#uyA^)k3`O|yh02uD zOf95f&9m$4Z!5;2N$tgKojKfOk4jnBv+{|xHS{+$Xj)@D<lPPAii8B$YYRv*4)O z%HE%u)H`O=*r0S<(=6pU6o0C%;ukQ}G$ReCLAgzoEa*hRFDt8f(Sw<3Qs?*q!iAQE zvaIir>bh=gW}2qs*^IF(%gJ?0$;-soD=YilH8toNOT#uu;UX54M6W(wU4I=kvoOZk ztgCcc6^Y(7e6YH{#Z1%QjWHZXsNAKZl$Q|=Z>y~0{aMg7O=^!ZjNxT;SCy!wUMLz~ z?NU?4<@wC?J!6c)I3rQH#8yhAUOcb1?s(uR<7ZMzvwN()j%avtajB^6%eQHzQN}Pf zZ5`ZFStT7r>*0$l>-05a&@@fQGoCSqVXT&+>uO2lI1>BbQCYulnnBZ~_BeDHy0Xf; zT|{fg8!PKu%rs5Yw4R-#luEgUs8XeL5RusI%F23gN1CIiH0zp_%ux=c);j-ouBhVY zvtWd0VT=tCmuZv8akN?W+?p!B`ym}oO3!%4>IbL0s#2&EMJQNsa$Q}uH8VZWW-xZC zg}apUqoIg6(!+YnK6|w^XqqOCHk_8ITm{j)2t_1*TT?^-B?g10Nli-Q8A}J5tCd@z zRMb&~A{2?k>gwCfG)>c_&fQpTi|)E9%T}Ec4d1A%16VKz4x>}oSetVgiq?Qn)>QF} zS=cnGJ;vnan|m@vM?;GCfCDP3c=1D7(D#g`vB{juBAcKq|UMRy4-|mMlYlY#eiqIRMZhO%^ZwfDCx#ZIuW6W zhyjnzF10lD!OS#GN-0GeTia+US2Def&W8KlQ%~6kvY;!)cvfSBNGQ386zMF~v-7R> zRQ!4tG^ri^j7?;2?H)5qok<;AURO=U<;g5av+y>mOxH9KavssO!KKdCbjQBTG(FE^ z7-yorOC)O2n|5N&3#%#n(k&SDq%npujFTu)(M2^+$D=o~^63?I%4*TqZ4BE`s&r*q zbf6B+98*n0|2CQ?rIey?3~wvlRz-yzM`zL>J*=j$Gw4VWF^pl@hAKsJ3#~Ufj?R?$ zc}4BdOd4TK<4CE}jiuCX2z5lm_o^xTMP1QhFuYAyMnR539Rt2xOU30;T`5v@dmj^b zDOGYh`UiZXo^IcTK}s?5QThqjn4J%yt{L9vTu0fLZ9tP!q#nlDwCUmq?S?{9$D8Wu z^p(`4NYQPK;V_YMB@3Yy(L3O!F17UgNU29L#&A5P*=3YO(1FzV47*g)*BSIh3`a#N z7g|c>bVN_XV@ow1z)Xrh<7nHG%cz1x8xiVx@S;j8ei?&E(R-8#Qgq{KZZ|^Fv)Ao) zRJ`PI3{s5hWRCW7n>MG>+pxp+wRH2IqZwnTNhuRuR<-CzeOp}aTtnHHY)%txMX0-# zh@^C3h@3}vV*QJ2>9jRzQY%8yb=uV_2|7@y)v)>*we-gYk)l&lj8!3|OzvW;>}VA| zNh=)dVI6&+6rECNmMIn89Fc7(I#V=%t)&BLx?)y|s9Tw2u8pKnEAgv3vR^ksXw+J? z*t!o|*@dDrH5^<;#lxq~5emxg(pv2MqTy?mbld&96dJWkBvs0tDCj_=e6otNuh@nZ zp$LseR20)~EhH(YA*nUs{grgix~-VavMiQxm8E8K8dCJUxsncFsE7!S8YxLxUEEJb z*UOwMXy_j+k|H8HSgFj#nX4$J&<`{`)1`v4KTDEA>XN2}(pp(uSJ3e}_rD!TjOG$F-ELDfR((g$y&JF(Rb zRdn4>q|nGIr8T+MQbZ?G&w7_tQ1-xqqzL_?ioQn7riIq}NarDi6p8OwQ1JmI%{FPR z%PWa=q^^cf)lYGG#jKi2bbpm9Qzs#X2E46??tIKB&|2$DN%AtH;RP=BbIop}NJ0ww zOjFbA)*}|VR8IB2}yuP@HI-6k~-;1yxyf= zitlx@c2b8Rg(Ri2K8vHnbtxekbqXO#ebZu zr1-rA6DSZ;OD!?;>pCgE`ynKx(KM!pRAAK zC66OIG_in$kd)Z*@{%hf`;665$N2VR8if`}e9W~m^Z}%C_w!O8sDsc3=Q~$L@f|x5 zsHKU6k!T4eUQ%+YiR|Afm`nwzOGrXUiowsFE25O8Pa#bx%a#z5K<&i#S3B23_Q@*~ zpgv6^2tWV=C0^-Vs-gG|1QTyZrvL>%cCLk7&RtWQXaoWfAgM*L<-eV4!S5n535&J> zgc8rZr<5wlK7T`LBI*@@Q1E#VYoL_!CW6UX0uT^d`xxg6$Ub>pO*(3+t6vc8dAak% zSMs8#5=_b(1xdk+mzFZ=Wgj|_CKj(o6cSRzXPwJrSNz`{2|y!ha(F5A2>}wps()~v z=t^1g3W5n&J)HsxK!6CgzrcBt8~R5JLzB*IMYkjniAUY&GPz}+w>iOt-XRii9F{Vv z72oy*g2_z+6wT*6oYZo8C!vW)^xti1_Uhd{wA?{S`VO7Y>_6QWLocP=euvMKft*CVJ@ zDfn2)Ws=EW{C^EWb*c-b!RMXJeu zlE}{A*^op%2P=HbrA!PZ``R}p5;Ywx{H$Fjglr%6UQ$@b-++6qFTsRIFzzrdv|ze={R|N1Tv zVoE8o#ld!7PUUQ0^Tl03Vu~3oc;`*dWjU0yec7jX2L;p3fXANYQkF9%+y2+bcLD`d zPl-)_Xy@fg8QMPmU7Lb}sc2&6tv5QCPis7^furr7uaR~m2CTxuiXzwVyY{##UVrIc~-KWPkYbi zK!~YtV&PkEaxU{IXM6FHFJBiFOoapPe}tXqNy){&_Mk^C1c_;}g0(+$mvfm1CENbz zr}hpAF-;D5*7Rwdg@PkP%{K!_=`#3qLn50^>K_N~8s`l_H1Q|QFtHP2a@GA0mmVxq>y`_fL0U zdD!Dugb1eV0UN&Phew_K_iwy-YaqnbT>-4P>Ow#vrt%5^MKHye#C`t1&;R%N|Noy0 z09H^qAkH5E0I-|@odGI<0e}HMkwTnHrK6&!q*OXc@Dd4WZv1EXW4AFhTo2k0>Yt2% zt$tMhi2HBJKhytQy|Azmg><9d_`fvTe?Z4l>&A;gXVfmTubM%k$-}nE^ zd;xzW{-6I-`@i`QeBWFDwqN}H$^TEk=6-*YV6`w0GmMxAnZ`^5%;P2j=4Ff832?+$ zPSn6W&N5&fj+rj7-ZhDguQd`gR+_?mJZhf4F-Y`@rdo;*GmMxAnZu9Pyl=Ib!~rHb zTWEr$&yD<{`rdUjDUty!4$~Soyf+S_onGoIzUaH(5Ns) zq*a#wcBTO8kc<($7TcC}@o<0mT!m+yZQzKmj(N#?7VLlIV?^=|^nv%)$kL=g06%t= zshrKrym{nb#YK{~Q-2t4bYIOfx6O~zmU{E15#V4pGp3D8&LwaedDtW0`0!W&tHUD` zThvpFkHnfNY@Wb}ymdG{}zW8<|39uE_P` zevybS04ju9XP$-sgEhktW}B+<>6^SeQhG{9h^@U^J|&MMG^vC9Yd~ER18uhxn4Nhc z@Yt=lshHdC-~*>sN!VgDy^e()YY(Xf1MY&Aab#-Re*P~gBcYV03LCKG&?pmu&4 z`_*i6VG$ShdEzepHw!EN-SnF(8%zv%LR^M`4#vQV6mP3a;=V1T7;89|w}O0^e)ILEvThRP)05Qz0M*yJM^pq>r=RtlF&Gzw zMfC;+vMY|5E8AqISNkVaf_hm!B2e>Xh83+2gF_B`s#=7Q++Ub9Ja@v(FvS*T(L!O3 ztRfHzrqw~)3;NNU97lf3VTBBloV>2H_ z?AB&8XGOx0EO#0Ha5X<}lJWHHD2)5wmP$82piF$->+l}it6&MBr|W{b>+zZEQ?T47 zZABHd+Oofm7-Y?05~}Xm!Hq18a4CSp6~DPQ9|o?F21djhSGHcbJ_+_WGSdf8XG3sh z^YP`yH2JlxC&XV_F|?KuizH23al1KUi&P$6O&uG>zSTaME%SNT4>hm=wIn5w;l3?Ff>+?r|vMOfkZ924nSx+WsJcBS>F zIe>d>R&iJq8f=XRJ9A8z-sZME(G{lb=m-Abz3)HplxjunO$DR3v=*iT=5dnw$-?b- z22=s_?GyH_y1DUuIybNP7qn!_cxi~PovDC%oMP@j0+)EmU7wWp;KrMP?!4*RmUfhX)vFJ_SdPhAJ}@fyZ;%Q zuML3nbzhWfh&Xp~@nM3i4CyvgW3g!+4g;%P(yO&pF@qa&qD`1$tNy~w3(a9i0;jJ2 zj0vYRux~sB5<8-YnQ_0LOA3HZ{)bJvR)4@z30A*S>@^QJ_#7UXK2z{%aVKmtcIhR5KAZK>!nZ1J zCkzlFcnF|s3Cko|8ra*uypd+Gyb`egLSy5ePeUT9ml#Fu5)3MA|0J0qKyWmK(P{e# zXS>u$dHP;wItf`?>)i{XWTk>U?=l>wJTcU(?%Q5}9gqcI5}-F@!_j*bV|PC68!JZ` zl=WFVT6ZcdhbMvqpLRCS&g*TdaZ_90`DAW(TL?hsvQ(6T6QbITa_Em)^H@(JQCeZ^ z*Y(Bzq+mgxO)?p-7VoOY7|7HM{2Ol7q0RaoY@8=fMGLJQ)>@KbHf<7);cUpUAFs*F zZ<`~fRwKq-cD`ZyFx`iXAg8pbZ`+@r(v6eTf$B8QLAVFuItaT6ve^pF+|C}WAgJ!! zu?qenpG^-+x4N{HB95!PwUxLvQ@U2TtY{7t8_sdc*~mxv*{XjaZ4)gV^d&F|l!8Oh z9gWW4?e+J4Of+3@w5`{Y@yp>IEQo>P714@go8F<}&gK(L3Zb`%Pa<-(%Vf0;)6pat z(tRB7aAY<-iGO6dV}Fp{n-j&g-Us{g#5?u8D#Q9E??~&r&+`o8u9WJ5dv-c76=dmY_Phd9m8=^`Xwgd>WF{k-U$vK^?6KMf&_5GrS;lYD! zquG#7D{KKaR?P-cQv(z|NjRd$=6Y6O^6#bU>wivSx_iZ_oidUMbUlW)CL>_dwy%_H zm-;M~dmz-d2L#R-I^3@2u4rfp#jqLsje4+~Fn6duIxFH&oxXI-7j~bAO)H4hSp*4) zha2!rN-4s=J*&TMC_OMQB?=Kvd=ZqM*{n8Rs6tDC?5Inqxf^FMT0P@7#ydTQ_165z zSmq}@_A*dm4jQ~{LB^q^PmCmLVSP3%h;nxUXP?v7n@np1cj(`onBN4qEr^v=KMCrb zTNA1RwY5%|hBj;XIg(Yl4T&zCKv=*79d`pmlrj=8wllwYr?5x~u+ zV5evUE9sSC1j{`B)hpK~@2&ogaFO!LS?-Td>{1r80cENXkm9mY(muqFHuf!zWj!Hf z33Go^^U z{)ania{yaze^Og48vJ{NE=w68EBq3kq*T}Gjo9Yfj|*~CjD%hsT27JJ+8J#Yv>jD!E*2)`#=ZH;w6-&r7cQ}w zDN?b%!D~P?yJq1_1f~$b@bMr2`$|!CFVx6yo?L4qncF??bgJ)`wB1+Zol6V;wR(*; zHtuvMmW!lzES;dJyc)mL*N3X&9jzCcAG7bXPH_NoeuPS94#oP2%j{@G+Oe(UICv|# zY3b!vifysm&}<(qpT)%cy68!)9Ym=@;Ns1AP_uPI0i-Smk`YYOh+l%u0r~Vug6DAK zNXUnBj?LGP^OZ;2JsTu7+whyZ*cz>xW>C1!S0(Z6UhzpMd-kDuk}hu%^QT6EkQ2T=$qmJN0pl(dTp>Xx7%cUfs!chPMk}qZ*{^r znLS^tU=*bN!Zk`z>i7RSzq69ix18P*mAnu1b%k(0B22igibllW!=)^$@odQ-kCa(E zj#?al{Ra1DN3>_D;_%uXy`**u?iTl{^Eai&FQzAhngE^g0()w>34b)y6BxI0|DwM%c7Sx3)k%`B;x!`yFAK=XRX6*syW_Cl!pX=hs$yB z$bh7A_oNrp5{(DziJIg-gAv+3pS&TYiEQYoxT%T-&+Qe98*HWwb?^hG_w?T)pb7P& zG`LMYM&=`PPfQvcaD0WFEWwH3n^8ro(4uDB^~SFH!ZeFj&imx+n=SANuz` zV?g1qdgyQyr{ePp$J;5I)g(klKp#RTxnGeyF@nuAUO+j8A~HH&(Z%ig=ddi_9h{Lm zT->BgXhzO*ba5T`g25w6gS4UQ5-%GrbNN#`r!muE$>hYkAacw5Mv}cOD4qt{EIrgV zLstqBNoPje7}#_7wgMsDk65n$nU&HhF(Vf=d5JYOusf1PBN|>!|GSi&3+FwA@Aj=2 ze_k-NaEJ-w=(1|)SE#Wq|LJ?6DU{BjBKfj6_;u_2L%ClvG_K40&Ws!C{$Dt^HqD)7 zWHqqb+qOf2x5&KFKf1VSKZu0Pu(bn^Ux$u10@6R=VyZbyZ9P0%TurjCEKR$cyD0ky zIT1^#EPlwUhuS)e*!W9J4RMK|#_g-VuGnB(-y;yZU62;}gR7|J&lyisVzAY0q~~4H z#xRE+dh}ZwP#h2yxd85tj-;4j(4{w-UdBgPuBB+E$%nw4kt-gj?kIL+b#Zg)-rX8t zQ858dB#<@wcI=KY@S_+p))H4yQZ&=vh7~X`uiPTv6+CH8`)kP}9FCIP6Q}@nBnM_9 z+HLuxG#xq-OBbk-Y9)W1rDhca^J~YtD8%ny1c9BYgwgSwDB~>ubn% zrjmbE8reLN5tO)ake}vOj>js3mak5Teppq;+--Yol2~KNG*)?@b?71k8_aEknh8=W z8E%{R&}zuNK#*?l)g_)W`SB455XMxEHhWXv9482Tqd+P?*Ck}JU1u`#@MTzv< zP?rb(9eUbWjWYMr&>T?4N|3)b#A&uD#>(OOo2x_CAsbCy##g&(VFnzmjqxRcC1+}U}L5X3(OOcAm3ga0@kRyPmp(8Aa~kM zON&+W!{K>HHeL%>K$)p641h>Hb9_S$$3)<`;lFYl05($qNqo#fFjNNK31Gnk>+9r} zM@jZhKz~L=`>Cj2H1dxfy2a@`5l=4~TqvDv4Xx@yq5w;McPk#iv1-3D>^BOe)g*Yo z@F^fk2J+S_Q~Qb8hdDd?-D3#DNDW!B0KiJ1r;=fYYu~z0qs6H^>tJl-&*EaQg` zuODWldc-p7X+97(ZG4G))pVSCMsW5^;&|<{GMQ8%)~P)5g$%7^&EG(p$cU7$QezpQ zaSS$ffI}O>4B=3I9Y^C|#E;8nTTZkVqu+&%38qN>TY2R+xN?wG$dm6?htNzXV(_sm z8`Dz(>M=y6O<#4Wm@?IY%X>&PlgUopD=|r_6!!Mi zD=>Z=>B?CUu(Jd2#1*;!Y{n}=IgiiCMe>EP)K2UBVkRnQ5^6#G{pcR^wiN=J2<>Bg zEA^ZLf@B|kQKtI_#4L87Gd-~8_C#>gkzdoNrEQf z`vSK1^E0@BHL2q{yCHRIu_EAn0X(^y-HHY|NZtI&{ekC_rh!);d8Yb+3G!_w;P(2IznUq>JLe_t{5sQftyI^4kZK74=A3?o)K)f&|a z5_*CA;#(mbT~qrgIr|+Z-j25r98R=U$C4!&)@3R_px!(_9u4jJog}HS4AJR;<9l+_>NLm z5H(%4GU=3!@adPAwRGQ99-9R*61I;XhIn^O?ZychOtoYay(E{yYrUX#dxG$==mz z!+LmHg_-5Ewhw+7-S7Jh`}WRJH9+%Ma4vV-%7jyc(R7m_;-uelNbH-h@lQ`GESr03yndbs=DvDB zNo>rT2x-%uE9=fK$QH>aKn5w;ik)+u9tiPZ(0$z^48nC#P?k+ClE#-H?4Gp<1AwYb zNao{@<3fpdrifx~iU;hp>QV!rCh(&Q%;0M(puDMfUO)@FZ1g-6SXFCWW`omQ>5NyN4c9N=@HU5FQHVrv&dy<2CM}5}ClRAY?j}3@?}h zeGKU+_rQ*H7cHvGyxnA8-C29jd{7i;+Lwsy`kOKV&&9Ox= zD>0U{UKEwu8s$Lfv-VtxQtZhqcQ;BnK6(n ze!1g=zpG2|Gsr}(^7IW1kT*1{E3YZ=07kRJoS$6wpn2``f$*;e>a*ZS6O(f&li_*T z47EceTN%-q1-NlA#L55gEZeIlI^Fsg9&y`5Iq{prb)%@*Z7B0xu{~g zBhx|-S=Z15>7xX=S2QwB^72B?8Pg`w+)0DtUPfQ&25(;RSJf&?IHt+(TU1= zv&UxK^QQ|g$dll*W$>~Ilk>m5D*MrWfd`p^cvsz8$T_Xvf|KVF54PR|Y1n6vU(}rd zKD5#a=hj^MmXY=CzSSB1b2XbU=_bmm%7LAFv_zZ6_Kes&sD|`-9SRZ!nhB=)_Q*1_ zEc!&p@bdeu>fZqD+d`GaIMpKEfD7LU5qd$Kg`}o{d0EU@+4CjIB}{9cB7Zymn@z_d zMcg1o0bL|>i~QgW0NP2G1P5Q>c-8|WM?n5d@@@_(hTNwDmrXLlX(u z`fvC5M@i@{X%p$i$cf^Nb(zxvKVjb_z+4)v_Nj1H>`4=Pl$SjMmAhMR=K-1zEl{$& zAF=-xq<$yUUC?#-`sN8b3YJ`Lc`NBWrokFBi@4KNy(i<6xEt6{YhGdR zcbT)6q-p3=O$!{)%y&ew&DA3lcLsozSXanPQ?m~4@tzRooCB;e7t0UJtBwf>nO{aN zjSLCu2oWplZ#@4K>H3F@KD`-W9>NIv39Spk3-Cf;{5n9y{CL;@_LZgnH@>+h=F42v z2v0r?`mon`npkJLTLM^E_8fG$eHh-Q`f{$^K{m1VrJ9ZxG|j@60#&)B9O68PVlMW4n-Xkk2`aY zZa65KQyZfaiYQkh0kXpKMq+I9Kjk;OlY`2vWz#+7oYQW z*$w(r;)^l%Unoty2|EFG)2<#@D$+KfSIx+(&gpt7_F5aUpL(P%kbT{VwIjh2s~ ztCAEnP9fEO1pOk`2r~WKkcmVFBXb2xw6$NjJSg=@I1eLSTm+tAp+0F;lu1?Nga@1m zA!%}3BBpv9$a`;{W1Z0M!h3JqteI?qe&#pCI~gV~WrbL-s`1f`*4y?OU0nXjC)zXmWI;4>+rS*NLKCXGvL$buroQFU ze|}lnUOeYXz8o9GV5zyJlyUJtgSB_F4FRFj@+IAbs6CN9j;J+1&L-))G>}F-0U0mb z3I?f`S@dY;U}m^2@5{v>jMD1Hae2I{B41tFgum=f`bs2ozT!SS~DIN6A%^ogva>s9;0!pF94+cx%zW81cE`<$?j!V!D*-rJ9Xwe}uk_WAFeh?oHW z@Beu&H!XAqU<;hFU&xG`UQ$xTN}RSgwtyLX zofbJFVkJIfriDwUPezK)m3R=ga5?vh6y3R;wTEoMGVOI%$vu(cl`LA$J0nGZDd%4s zTd15{7#DCb-aAHWa|0MwzHGxq#j*5s^@6l+{^+>e<62?QVs2_YdNBv6MC zsxLV&h!ksH%F5kwL#Pb`)Z2xGM89gwjHgE`YZs-gJPeu^>O?}KOG3S|crur~= ze&iAxvqZdUJ8%sKBs3Itf`mYHYZWG{Q_fazi(F#kmK?voLj#Qs5L#`IBt%;st8}>H zQe~L9_O~O)#!nGnKVb&mSkKzhA^N430JWUG-h2KVrHU}a9&&D^*c5Y#`0*q50vflq z6GB2lYIU~&HQeB@5tm&Bs|7h6^4W+Kn`kZ(XP$i1&4I>kt%f5>h+Y$S{%@@0xD{Rp za=7vvE{j}Z)6F>|e)*h(8c^f5v5*8U0czmMC32*=7flVwVfW{p6Om%mE~!LZ_T4A# z-vCYHzII56F2Kd!ABvJAQaobPWZE>$VW%gb5|K)5`Z-0!rQdn>^|u6QW{t@tTU8q& z3CuzFJ|%LA)?=s40Jn{24!b|~9}%g}O_sc3DI!F^wgs6y4|l?Z=;g_p6`%nET!Fn42Db*ue)K zeAo?dbN45{>6_<8M9wiV#l2gTY`iNrL|%z_3BEd+3%KS}ei;#|#FUnjQp&N~oO1N$ zRAO+BJK)nKc09Y}h{!3Pf)A0o8^GT8{N(u&IhB~=QgTjdWhJGYOLUf;(n>0^+7h># zD(4naqFquxo<@?)+>Q3;`#<#K=SD=zF&9fa*8IrndRnB8$Z_8O3~cti!NcG2tIH!I zrxJ6&#D;yQ%6MO2NZiLik?j)X_D?7xsD!815%eJ<^GXssKiOQK)Bs)*7q>HYGX`-HANoVZXA%Ujm6;+humrIx=(blxQp^8#`36q2*Cf0dtCA|Ze zKuCZlOVrT8RnW&;FhI$l;q zuVOYx64Tl6yb8+k@D7_Cbv&bjO5CPH0}*Hf+6_;vpAr}E(_uYG(gdjIk@b_~lwD}d zOx7g{iAfX<52~LO-RJhy(H$nqLU z(r5%AplheoBG*lc+q2Z8L5yCV#@2tXoDv%j>2PcJ2uY(-zp?ID)pOP!%vOiQ==#T8iYil5*6%rEa=j)NGCdH>&S_34Fr7aj>e7bsG%klv0kw6*`67)FlubxM( zl^Cr64QzOG<&-$S%gs!K8ME4S&3Jj;l(=j^mIhcKXms5+<5{(H#_r7A-L0)|T0&z` z2Lm+Y(RGvKr|Z0F+}&H&)ya5;`b{(L6S-zme2Q6zwbjd51qcB>(u|X8=hZAX-fmfs z&^UU9dd(Vc9;I#`$#T>5Y3PU~i5{uph^X2U$FkhGyW22EtQP7vvxa?EqSQ-?mHk*6 zAnTEk#-J9Uq0UtVaoK67{LoB#fg}Kp21Jj^Iv!FnEAdj6 z-L0+Rj9Nn7)}C>Xib?SZmfd>$kPx7e2sGGcoEW)Ua-7JL`)WXpT7o_^vl%ywQY$6$ zq0Fp90~x=Dnpwl)B}%pY`-(KyIYbD7MxmAlTDxJN%c~{FpSH%ktP2T=QLD|ct6fkn zDZXvK1{#;3)!TCRIJaUx#H>TZ8MV;xjvf9HxmsSyl9`!gLm0bQ-R8fmn8z|RGnx9K zrKzwEYkpiUCGON=!$9MbpieDxz7eTTN|d8%TIvQNG4?fR_)NWAa19!n1|lX=qEBkQ zzgBXbvJ-A*1JtF_F~rPftdEsi`N>RXrhY(T0)1~+FRhg`_rL})WxJnUCpmt(`2e~#5rS20@w>=1lHzly z7lD{gp}}VQdZap8k9X0Y5z0ZqM8#(^44JHJd zB7s0iAQ`joMXHSyAHhVoCP(y2NRqR@MxH>^H___oY6Q_{8q*8mwGE~zGR+?i$$bV?vK+#zyBlsIoUY+e9D#x{Q{xgJt{0Mq=C zkQyEn6_Mk1ICBIc({_J|Tnjn=wiTKqAPMIVk!m5ui*e=*lJk*Bbx_L1*TUuuNTw^E zQ*srg_z=cCZn#UNDk$lQIVI;Ekt!g?ClT}6w8I}F&%Kf_KOCP^I7eNS%e+hRW{f#4 zH9RuXoXh2mz2=U31ajUUX|9zN_rRIg0@JoXj5N%V@CB?IFD$s2C3z6p0O036K0~kC1D$@K} ziTAjPI&`jmX5=|jQoMIrMlCuwx+uzADLGD_VN|0U?y{209LW(ckVHk`JUnul8!0wC z5{asm^NdJkPNX>Z7)(^AoR`K*nFl4sFZRKyPZ_U^RHiSN_{fepH7e7aVx>%7iu^3Y zsZ*1^DN>oVlH#lrkWs6WULUzkSdRGWHE~g|5-*KhCM!jL@#Z*HEAiBbJWV;rpKb$2 z<;r-#<&mZ+MZ9x&T-2{~+=kQDKo zW5KCs$DbFGrXrWP@VQ%KMok;s>6}=Zgq$NjwjVf^E$2GliBd+FQ^XIB2N(72Z1MVt zG_IT?{`jEHkWu9Z_xx9+5#JxVWr`Ba$?^{vINOF$&>yvi|7ZvYp^_qw@f|5$a z?;f)gxTt%BJD(G&Y+@xv#P{#B6}YH=8Q1(;l)NeB6tVKDW0t_hLg+NljYykJ&Jq7v ze-PkeQ8YN_uaU~8P*REb%d@Wna3>G0_DBbAMkQ^aK- zyY&p*SU^o2{*x$q%}UA<=f3F>z{O%JXX{r-q_rt2B7XmrD}Y=qt`4{Td!(|uoFdBC z?y)VnvDnJk`y&x)6*)y*^zNGgZY;hI5B+zf(pOT6IOT=c1Y9h{CieYiL@KSEBH||x z-5Jcq!t69pxhNu~R3a|@=xq$#Sf~vS`qYMqh*Ms50N`TLmczkMdiAr8*#YEY5jWB4 z#=>sy?&AOc|DRz209H^qAj}T{0I*R2odGHU0RRC$kwTnHrK7AYqtP1qkP-=LZv1?g zzUxnly|RCT{EzH>_t&;O-kO2@k6qu+cjv$_*Bi{>6YCG2f7*Gg_Akwc#{0qB(tUtG zkpEo&xBY|J3;v(>Ka}3wey0E3{#W1+`DgV%`Tyd*tNnL==KB)!}(RI%UAXXoIqcwyEw6 z)f$qAN`wx@%)z<;ll?Tq@0G^HhcsFh@Y;ggaPhvsmzH?ckA?zg86O-(ZKWqNjwAoERs5Xu z5gS&%#Gc{vOx!b49PR1JyD;P0@0LAuhsCppJ3oXy<;TE5MuF_?_p{^)o+hWuoC0^v zpr_1OlqUR9mFoGu7s4~SxA3Ds)iWx<4on&Yfz!rngKKmz2tY<~@r&Tn@;%42#;k~W zxmzpwauS=gYj)Eb|7bCLdv25X^9wV$f_+r4M7*1VK#StzRQAGO?K(IN0R?d+Q(|2rDR0Jz?+(8$>>Pc^{lbkFsjTvH4#1bMUdC?*zHcD+#7#9I8oQ^X6BKlOVP{F)vi3rsc;6?7^mZ8L>>8^k z6`7NoDAo+N#E^(mv-`E^9SDua`_$i#^`Z2kRRSC~+f;8*P@vGisX@+{U?Gg2N&VAS4EE%Vw1)nmIi3Qr&iCr*b}anv<-u!-{K;E~b|i9dh$ z7Ltq|(Y@E!*UiTtFmy$*@)X-J$esep)ewRK0;0b;g_3=dYY;s4sxZl9Z`eO)yrAD^ zpe9FtB@5o@KB<~%sNs&b_H@r}-u+}UX zH5EvMoiV441)Bsw94koa+;z3e%(!t};o9FtfoH;7)~I|QlDZ+MVNXQhg)HF#aIZYx zyQjQ8%>63v8c&m;4j1!~&UhM*QaHUKLb|%^Y2kb(1$}PQ#@q4l&^R+Vx^r8<*=b5e z;YYMKW`7az@Rx_N;82FP9nmfsBKNWG)H_Uh$nGEmfZx(w_o?Ew>mW3qpUjW)cfyy5 z7Z7(2bv2I&Ark9`_Q8S~vF*j!&{3`tBHCO{ebIX%LCVQ=-KI5*wbKTtRg7jc4-35+ zpj(pnmJH0Id;Qu#IhgP|5@L!TlfXNktZL6^`Qt6@wsH4aIqxN+UTGrb%M>IAh*DY1W46H?H#(swo)3=V`vFfNnn>RTXzpVMZer2M?(2y3u1{HAL*Q7nY%6AFp9O+&Dq#GQy@+5uzvmYc}9+yvXeZ1{# z3sFF~C?cd6;4_K1$pRNEYB-}ozw`~7E+I_yd<+)%g&B%_oo1=Yim>#&a zlj&qkbq|yJP;ck=+YT(i>jMWDbA5TYM0XVf*7qMp2p4-v1ZuuQ>4fC~NYc%5DRpCA z(xs%$m@X6#`z5~eQN=AHHa#<)>61|Riyyo`g7tyJ_Ap6VQWFPb)gx3DLc|wqEus)gU6Q9H4p<7N*MDlX z*~0_y?XvEsppLMX)xak;zaGfVY2-Y~I4crO8rGymYX)E#epISm9_35=+mIB+l!9U~ zXc`@i`(Q$By@B7r|vVDp}<8uw*{2~*pO3f%Toafm;joay&@OV zBx!GnvG1QUc~JvqaTp+mfRykB?qiTc>V6m-t0Em%{koA;x7oLeI zx!g}BocmmVW9fVcAX4Y0iR}aNE&?S}p`s1F#`CfZ-3;4Ix1z7zK{)m^KBG9jdFOY$ znD=ZYRMlJsxFR%!nOiN_(?0o|?*KqX!4x1eN}<=(7^2~mvg;2+`dMjEEUGDt)oPY+ zn*r=m&)3MZ#Q0I?UTs~lX=n!tv$+_s=sSD+^j>jwZ!rSR^+KsP+z%uR&UAN1gwqYL z%KWr_1Xg|I?n$F2aoz>n%%WPmnJwJ-UcT|2g#n zFO;~m@?@YxM*Rp3&hkK59?e#C;elH_m`@5Ash=^=>&{^UP2jo(zq65S;7kXNDjkR|@z;v4yl3&l0O3V16=Vj3ro1@-SbKNw07WOJy}CzT)AZRgDJXwMO|Kk#J(LO<;rqGYh_jxnAh zu=?ziDZZkzYcnwO7}RlG4ko%&nPPyGBj2aH@{?OJ<0m?sM$xGf?)|Zl&Yu3#(?!Ec zybKN8jKa-to6AxDW0`c!7}h9V5r<)6705XAv(p0&1MStJ&z~wg$H$@ld+*-l-&a>a zl_29c*#ga@wse`OHOv;Cru@x#;n1CX#?xxX1DlDhn8rk8e?;V<5@8sV7=eqyY8m_5 z2cPix*`L4{QS%BCCqckPkyFjPww&#~!D~&7g)Q2=YJMBG%sao=cHM^z7)$=?)c)~A zMw#jN&|%_$1kO_Kx#v?WJbs&v`>LmE#^l8SLkH+zQeZ+KB`k_^<>60YT3CgI{$X`?7Z<|TFZs$z|0Y5D5DlR?hKXG?}=iv{|&L4>3af>uVfD`5rL$hM&i zhF<1F-!Eu9-v4XMKRz`%N{NvyA3fg(MF}btpd)70RuDOECZ-dVBSX8y>#nUAN zVLmsMAY?_c6JPi5Z}e#RL1g~pFQX?*tHc2Q#o9_26u^43615j7?cVF=Vy-m11rGX$ z4bf{##Zu?Q;>3dcml`WTOq?cU5 z=YnZS6hF5vyzgbR7{%6ZqK4u&?q`<|NTt0$nyKRhi*Cm6ETXfkoh|eoUa5`33kTYL zROC1UElPzFj=EU+EweYKUw zvc4p9u4HB=0mw;9L+C?5XiJH!YN_(-AVNSup#OWpkpFqa6jf!jsUaXBiN4qq@^J;O z@C1-WMInruBVbh?FI`Wm^qdj<*Tt{3n5Pq5bX^|RZZ^%deIfAlr0@!nLSPF)3B~ec zig_brW4f&4&=DjL8NwoiQX*^PaeLaD_6K{FR@I)a-XUPHLHd}jd)TjQ+2FtV)iy6} zzmqaEf4h+l(h@d<1Bep9`z1p}&!is;v%M0zZvM%S4aLR4`rY^$xCEYl1?z&rBEIG{ zSu|IYjhu3p_bvSIAGE!uTr(xPI(rMfld)d6BXHDqn9Aq3>L7Sf=G_kVY)Q_2OlNkFbe(Wj0V z(*nuz&(LOgQBGO=WKnT4(IGN(RxWINjjZpQ45zlKD`!_pK=M1kRksD2=tEcnDd?g! z4F-EesWb-rdql#`$A^)XQ`{t=$y27UsR|i6IRRC6I7A_Vc#y{$Ho}2;C&e7~`g@GF zy!nvM$9lehArc4QBM4USa7}TI z%VD`=sSk;Iyu$eCtX~Kxl5bagqz08nPL|WHtT%D5f#-XAjNGzMDRf^Gvxk1O*C?LA zGbjnjc0%GB=r&rFn9MI82}$Bs$TIf@P@4MvFoqFVe7bu#A+om>pMf9*d+SZri%D2$ zcML3Ss`ATYW>U)Dz`8^Nc)HUnCHb8{JcM5D;^E@vS|_(dM)Cc0|Dr5d zG1f|bK{bZCbdGumJyf*D`vz-AcqecjnGpxJI*(VNAINsbNAVdj#HfW;Viw!~oi0+$ z{Ez0yxP{$qeDpOu$kI@qi7MS^wY4CDDAr^ezSezl0!CasDp)4>`W<3LINDa1{_~EX7ha}c>ny>$= z`>|wGi!Bw<02?3MT;q7~%~dT5(Y)HKWc&SZXj1XOqPnVXqHz{zd_&@5K)d+$Fh5Ki+cP2Wkkh0UiI zeSfeT2`}mWSu@dRihUqnNpV(R(0gG7@Ad{<5rQ0AS&UH4zL`&}Y~${bD13j4%6xtT zO$iuJ){l>5fSL)HO=}^~%$w4R5{nwNKE_4fJh%O>a<1H;o;PDkMMF+lKR3GnDJkjk z6csfR2wn~R<~Zq)iX-F{gHU-)jn z9s0JqzI%j|Pg-#@lbHSL58e*=M&ZkJB4i1Qvb=oX83Ap(U(iEnJyfM8bIDsIb7i`p zVTFa6(L3h#GpyXD^;) zheV>p_pw4n(CsWk#A~#UkVSjeu5uN?&M{^uYB2^;W*0E=L>dqOglCsv#I#vPE>GkX z$a$Ev-3U8ZL>X$6(+JKSXdIb2$)|N$xTEykxL%CicK;jOqVM}sDk+my%1~OxR+?Jn z!#=yBe-?iAIh9AUvkIKy3*fAkl1Qz8AqDoIVSEm3ip54Ke!B?I_~f7vjL`ZibN38v zbZmc&CFQ%QjqEZzs^!UT9&ecUHT|ugZe;rgt^9ni1d^6)8-bd*5mDERu%b7>!J0kh z+wH-!_#q`4v?OwPT23M8i`;(6XZdu@D}1!$>j1t$Sq?aJ%`oztsx0q1n{?!TjT%4` zI2TFaJ&bo0d5KdHzS}hVxTuegf}g&u7j^Y3Cg_Q?zk1mCiothi*d?epO4WzhS#Y;b zl;ny$dC7Maoht;JO*=e7E6D#Q`Ka@kPneO-E>^w2*fznhZ_a8b3HjLGZG-1BKI)t; z&R6J2a5l5CQ>NN?3JVL1uX98^YmYrFf|2dQ(m(8NL%qBxTmthq!OxC+&wu+SBrSPG zfaINk6u}CN9Cx83K5c3}gVxcclEIEq0 zXh1LnOh<}}Q2nxA~P6%F-LkMAaBk;Awu5<^z|nWdg6IE*F}WHXoOw$N2{zre^w zK0D@1xy7fJO0q!=E$4Pn8$4f3cJw^LjaS4X+Psyg@DI~*c|b!31?r@Gk@nA=ik7`N zCT7!3S=4E@`AH5(lB7Y=9bx0b)1QB`qi9NCS1q;@O9NHYr_#eDS**ei@@AT?mr&oz&+zLzA+eGL#k16TPabzch-SlUN7JLY0_U?*)D>?ftn; zMa6%+WasWFHtxK~P$32toZWGVvU<}~bT7i8O1yxmI^KC35*aghgjs|VEK1PI!&yI4 zEOp`s2&F=YS8eUae-l(o5Zr}Hrs}8=J1~Ca?s8L>%MOH*Td+!57|ru!s|Rl+->(Fq zU}Cz@*70!NoW4+>7zre6lsA@m>7x=oh1{3N>rsWlB%^m{0Wv~q<>yqi2W(_#p!i%4 zW;VbA9By8(n(!4TAjpKffg%a0mb+S(Q5Qg_7& zCE2HmUSsM*$E|0{W@V5WeFDK@n7$%vado=ER9redGfhv14E0CFL&x(#`K1+*6>XIl zrR@2mEdK4NW#YsH701JRjjl?~Csm?s*WW(C{k}6-mUR$<)D~oA1dQ>yTOfVM)q%zm zF9dZtJ4hfQm~CP!Dr? z_eNq+s2rMrRV8G|F9wjx!lTRL{Q-BOe^|LEH!Jw3wO9pGG|Q6NU?yk2>{0CBw3};` z(qvgDs%aOGSk=gJB342U-rFM0t^zA3VX-uD1(glNQL4xn(_bABZbuyfX@FBx1Mwz& zSrwLG7^vc>2lo4Y#>!;(ia#|-F(r{1iB{;NvPN_gU>`#XdFIm+Xq9qUDs)-0Rod~H ztEDn|dg?Q}tXDP68$qaG7=y&bXIcCX3yV{k2GmF?Aq4G7OmT7Xi|^=PR`%32ZS0cF z$^vp!RZ0jdw!|lSiy_*N<}398wJc|t8j{o1sF;`!u!+tMk+=n!uE^cl)V3K}HTv|; z-UPF1TnNG0Tik{rmw|HX4O7Xwu^Ow7<;f6KRKZk*gU)vfGe(|v4QAnw9l(7Br2hzKe_MYj{mq$=y4w zTe?A?ZG1q@#l(N|m&mHg^7jCI80_z37hjHF7f;-e8EhFls`86m7NrGZY~J=nBw2(D z^0TTK7HbVo(Z!@xPWz?kH4p6{e0i{cH2P{PoKlS`8ngnw7UX)wb59bTR~vz)PHyl5 zIl8N=2mm%V8opVL+Cp&ivcvq(LucdD#jIz>>3}LZi_=a(^z_yD%G7NB*Q~4b0)f`r zQV-k401Du;pM?XRYtGpsD6sZ^|M?>ORI_q1dt$+ON#k+O>NJHTPICY9R7O}ope`%R zM^lrF`mAEgTAlCZozJ#_Uxx*WRY+U2yGp*+qEaf4GEH3N7=~e-Q5!d-0F7;lUKg&h zrJG_>u>Sz|q^P(N5!6ZgC$*}os*JPE#o)A209R_(t%IL>yzbvQYf{OwRCQ_2ialGd zvRa{0X?Qcc?&7ujvW%*TAC>hm+focCX%0BtFeD`wGV}@P639&~1JuF)zLPK`fS<<$;cIp|71syJ-@o9>{)EiWCEmW3roX1t^o~Y&2=~U^?l#+d$m|#0J=tf?RaY(85O-=ucigS!@ShA5EP;Wu zV>f5b_|;Vmno9pPOU#D z;9(sM13zb*N>V2PIa&NXlBF+=>S{4_va~w3wQf?Y-fr9J3#m31G#qE*YRLC5z=4w8 zCB3t~P%r}@-{nSny`erZMb2MVeh({^pP_Zx!GGs|l=|qfS^r-yuiK!7!aFO$ApiA( z_16-vGK0Xj?v#&a`GN(FuEN&?Ib}_{C)_h6M|uI$Y9D_c8TYJet0&|lGp1JD0gN5$ zqi#iO*~xVN%*yE)yyyF`l0K^qORxR58{I2jL{tCyEmHr#kgQ4|U1^=4PgTWvdnfD? z?jteCbdSxGb=XF85k%D}JRCC&=ah>3)8wdJVJb9d-d6U)-onB@QWN*4-M}G@Z}Znb zSZ+{S?z*-n^FL44jU7|Wi;*u!ONuiW(W%)9MY&?-6UoZw*KK^YQ-+0Kieq)sbkF}yJ^z4wi`X5?}lYocf}Q`o7 zmBnWY1ACe|pV=wS=iE0yRym)&>!L+&2xt1_yp$RFk{ zqhv9JMM@{y+Y=asb)1B2{w?uBp+T~){(^DUJ)N~kLwYsI6+9~^2JJU3O?pkPe<9xN zG&!c?7{huQhtEg=S0>Uo=_Zr6sd5&OQ+ZXiFq5L1*y)Knu4Y5B9a+zyNeqaUTe4@$ zdpl<^@jB33+5m#v!)Zng*4@u4AkkF^jWgYJIZsZ@{O#rbx*L;Tr|ODz&am7vU9sbf^8<6wJgR?1+q zZ6{@jCkdyKw2Uq0iSUi~c3S>-Q{P~yq9c*UMjQ569SZC)a#)7Q@j zZWP5Ur#CK7v2yU#DW}zR<{UJ%Ad-lgGpb3_F6k*7vX>9;KYy}oSey)ZU?Gmra5!YF ze^1b%^%d4L6|B=62grLonG-V>Y_|V#@%Vnz^)^1CD<7&uJD%@yq)R_eETg}T-QL#r zwth^lVErrofY&!C8sLqNDBhD64+fa-W`$M)8l=}v9$T!ovpvu-FkJ%kv}#-xlDw^q zeC%zR&x{i^7&>04Vj}qWC^Tzc8=+G6NZ02TmZtP<4XC-ZQCNiiv6``0&rD6SF#i}3 zV|_VHf2gS0)LU}l@ui>EAb0U@VPUc!=82hjsBY3hvh_AeU9$r*&8E+r*P1ub7cjCL zWmls9(YwqLUnmg_Wv7cdo=}s|z{LY0mFt}DTezzYba{uZYQV}M!C~V{voMp~IM{Bc zYs0U*BFP_4ExA|m)(%d*p#kQ-Xy0(sC=PU4h;!@QO|mq*$_E8oj0pd1J{G3>P}GeZ z%HRwb-V9&QB?BLoQRW&pn4#@ci|*U#j?& z`=+U{Js43P>9Ko1>;b~T1*yx8nbFv8enV|uL=p_6r(@kf&e_t|6QoI?{O8vkz!DcW z082iAUJ~F*RC6~yT6!?1{V#tuJCn8NmU2nT_fjBtU^-*J$rVYK-lK)3XqLoKi(~_222JNXdq!$;RJ7y9jAT*8`!{Ru7IYy;U{*4x?0n{Jo!*Xw zWLw#?K2Uma_)s65Xr(b%tS*nCbY#ms#?X>)pjT(u+`E?j)h7Tujbb6bIZ@X%Pb58E zW%9hZWlr#(h24xbuTL==JCLBgL^5~r3~tuS3p&fAWk1a#J@aO=XqOFapZUs?+I`EC z7~EyCsOjJJOYrj*$0`QC&|;E|<>h zLD5z6ZW5cm^tE)^_lJc>O9X$hHJEmMeA=RU#!dJ8wO59Xl{O-ArAhf}lEV0Mx-|wv zOp8Iu_*f{TCPhmJHE=UWA!bAhsPvUiHyuhfqNWWK%OhSSMI2?BD&Py4Jwx( zFW`2Rcrf)*kH)GJkU!@XaG@W&8PNQ;mJHa+pev$lnTq3bpra)z$Pf9Ktz-8Hfr@j_ zIROJo^G&SSSe{_?WEjkfn8~!s)w|PFL~aBr8TLGEL;2k-@SshyWTSNG7C?Sa`h?2) zxa%6=vhBbO!BBLc-w}mSmg3?{ zMT|r@M$m@P-CU0BHxtsLtdiYayk4SK*G(1{$u#RKy6D$d-KvUjPWg5Y_D22`XTyk# zMy_?S@ssm$w}5>SUMl1ON>Ti9Kc7(JU2?`z(#UsE(Y4vm;dCvY&t>P^5 zxuX2C|Csm9rsyozi;*a`)}T&|eT!41%grzF0QS}E#&=eifm%{lRiW#+uYZT+2oFlK z7=qPTLpo-IeR_oH~+aSXKcV73rMgf&docgwFaM9WN(dLEEk7)LT*LPJ3PO{tSY zUfj=G(S`uQz>PW>=9CQBT_DBsh2*IV9=K3T4!J<}==-e;x$Ov~EJKM_fg07pl*fN* z2)kR*Ng=HrOn@E_1vxx!g!}~cSJ-)NVRlfgSQLd*c37)7`a6WnyAAQ+Xtl7YyTISA z>!j(udP;OkD%LY~@#Ntx5mvwZw+)x#p;m_Af3-dYVAda+4H*H@_GOTjh6n-i!rTfC zMEwaGu*o&>zAax*TH#WTELgON#q61Redxp9=oUpA`*g+5#+5+j&>@VrqKXA0E} zj{uAmi3FMQ1+<`=!r&59Lk?GyB9nHzyaUnV)bwml>Cnk4`K^_oE7JA~ixZ`*AY(eh zGCh(yL0Tl!T^w?8;{;(;5D33@Ue1za7@l5^RHWjrG4ssrJk<9w#)X7ip-fAPi(&9O z!9uskseAg3-=wV`sDl$BX6g2tpTa@%B-@5a^K{D_GY;unw@q(W`!@se|HOQ$!YSDb z#}LLG0=4|cy9w9pAYk;f>G$7v23^^OSTFoxM7IrKysg-~G}?wJR*k6*B=K&#$$akb zz#E$L>a$Oh!3Xh?8bYg4ghRxAfN{Lt#%}g~GsM2iBP41@Zen?B+&nL@my?f|9TPvf zJ14G=Fp$&eX1q%w&{O1!bx`V*!&*SO{;>s*+@YcTLC zD0;V&TU7AKbw`-sAHb?B`j;j`(*stOFc8G0^KuTBv`kuTKs=X-$d2&6_aN&dcO0f| z&0D(@>8;>!Zv%c5JEPVopbgFS(s}w#dyuYWxJo-A63vn1^GsVSD#W(NW5{ctd{XGp z&)SzNrcUQ@<7G!8OJ?I`N)x?QTk+#>@s331NY|O6na+!tqW{}RQY7<}*WV2k(NyF5 z1q{zfG;I>+`CtV~#QMM3frw8#V?rd|cIxOuI;J4f$hTjqlmWiibF!&s^#{w8ess4) zDhBP;c;UJ=J}(?ekw1u>&rH;l=%$97S~qcp&)Y^QaetypM{`##Z(*!P;!k6UZ1hG( zvoqJ%0uW5h5?bA z1eX#az7xGCwYDK@GhYM@&vpiadWj0)2^5+xPMoU@XFMJG8ja=Gg1n<)_m{E!{pMnQ#=C9D6s8eJHp$k?#`DIgm z>leg&JCy-Ty zjj4wYB-~LzoW{5d^j%xRmQ}<=Z29m>=#so9V!MjqakL|2^QNmG9|K7~4&@yY-RygU zGQY?wvLG5huEQJEw95>B6l~fukkOD*FMU3DjmEP6YcXeVv~qo8u`Go>-apqEYLmSTql!!z*YM;ZiHN(-iK?#-2x(XpUR9Qa_aTN?&HrPFjgxHsWC ze0-B~*zxo4U0KLr$xq`vg?#v@@Da-0;Gd-Ruw-obOl%)3BH>5j1C5d3j}%rTd^6Jq zonHeyl(*u4|8?u#hVLwc2F)4$MK{DPUX62c|&uF>|ibBO`+ z1OGOMj2^Sc2S#q-C3Q~<$qW)_Ty}G2vLUdn>?Xre3U+oL5nN#@c@jFx!P=?FO)X0FWcIZC46sQp({>JvgGRqJx z=c8*Ncm}ro0M~#E9>D*deFFCu{hQe$6Xk+{NW6o{gJgh!f`AfElFU^oEv}?3#=*ZK z!$h!lcqd=UJ3cpQ6;^RW{0#q`eXe{XKTvrQ87(zW;prc~XM%ZRH>jnCU2&KJ(KDICOtH5aBj5ly~fjQWy2ckCz ztO9QQm|h3n1fGM1gs6J%KFdFsU#5dZ4oDXQPXj%`SZ@mViyxjJ+jj*Az4jk<0l}X# zV6uoz&6J++=b<5fs5k;>2q%Z_-+KK{MAMI^8ccv#DIc9Ct1FH z*ym(v?cZy*a zk*59!{+Q#>PPK=y{JK~7iFZ5L+?5I~@ki+E!(C{7U%EBo7Tvp0${Y7Mj1UUR2$`AB zm9IwNOa+8~7w=jdhI-SB2I7}-8z$$;vUzhVclJqrIS4nQ^87ngmb( zKZ+@ZpiN>FE#dqi;OOu@$fUZOS?g z+Gy$o$%m|dKCroT%XT34->;2)Lc_Fnq?_VLyS)o2pZ;vt1;{TN$l&w>1@`J$3kiV{ zsa8AGQwG6Zz=ik|-_r91}%uRrPUDJA)e<4_f@=!@_y-qbO*=Lt3pl?Xus! zt5}=udvUVxnTr_O03rz~Q^S7QPdqoCfTM=x*{fqzhqztXi5bAXD5|)#g@SiGa5hFq zGuW^Sn;Rw}5w9hCcDT;QEScZ~Xpj+a_)Qh>&h!1*vV|GOr0}7z`6HPsSshZxR} z!i|NhU20+84l2%{u&f2aYoZb=GvT8Hz9>hRo`yEkVKv2d`G@szW%O$D8dpbSfN9jR zBAVw@?D>B+ikMijWF~QIUrOR9U=R}?rB*Tn16EM3#{`dTNV7%jvCbCL_=<{0v{Cxf z^2*q%7kFBqyH9l2W)yE7K3B?JNom~{eQ!jM@m-yK;hEykCP@x~;j%Cw1rV_P_G8C0s|gA}NYvFi zwhto0C9nApDu{JjxV!-Pm;0MGhKqEp%}kwwQ;0HMll<*y5OOiik4onU@jLcPrsG_rHtdoNl_T#5+_fx{{Et;e{EUlMLU9CWWNN3?AAxCf2*immR~7|HHu&+v?;CidY1I4 ztKa?h(n&T0i3g8;jn|n+p;ONJhefobc9^F6{8?IicZT=PP47TRHsatpbexY$747-* z5|kWYD9W6A@{PMni&^?tH7Y%YniF~IY^BS;GcZ~EC_UFz?V1TMteY(UQYbCf-RxE$DlcLILXlnq;il~tJc zX5}poB3(FMtO=0g$LR`gNfPu_AqQBL6U|pAQpb(7%BZG$ zTU@v{h7HM)h+WE8=P>KH9{X?XrfR4xv1N0NwZSGaj@BQL`A+@A#Ik;U!%juV@ zseZX&6~9D(g52hRX>PtL`HjT5%4K(BFLs*Thz)9ob^P;TF#<`uBt=I7hhB6(oRF=( zvYiH_NKa1qS(;$f00|Wypjo%PAT;Nc~Tiee16{khJbL>|2T)8PxZ;_ zs;WtHUJUb)e*IzXz~ql=Dio^*n0o0Udm@fE53OsbaqJZ4)=DA?rBd_Da;7}=y0`PntrX>xM!8*|4%20BqIPYdMSkMFvlLDTM4&N^L9v4`bIQE1mW6NrCIdAm5Z9FiX zv@wD(3Bjlp*`eYmxEonWr^Ajm%r>})%K!d(pElggm2qi?~OB=&0`UX>%cbwwWa*X))h@C-bz+2c~BP6k=;$~Ifc8brV z3=zmC-KpQ);Z=`ZG8M@AREM17?5#%qWj`pMD3 z5kTQe_2@L@!#O+|)nC^!yGNS{Gor=12>DVEI5iA-H=ci76w`GL>2`nM;^pNpORRl6 zhKF>KSy`7R_QI)lqmga(5h~H3SnQsjRD-|qB-Nu{4fSESy;+a+yOY8uOqY`!$W>WG zZ*#^~*GaC89OGb>LhUhI0lo(*JAlPjl`|cmVu@`=?b!8Zuniw(iUijZK$VQNPng+e z(hpKjDLatHu*29qwVF_0wXO~$UNltKCSZm?N*VC~styk|U0+1?KVEX#Gh|ONU5l_E zt5}Tb7AspohNt{`-z$82-{JKBDr39XeB)(K+1=2$k&9gFC*0@8K)j|GkKX_3*wmK4 zRgQ0Fk91N9+cpHl4>vK6ZP6yB7gG5EzflOuz$zCIUz1B568$Yj^RQA%XZ9)Au;M%? z3FB|vxMfQx4~!@Bt)bU^apRAHtkl*ny*E6xJ3{&70yvh2=PMuP3sf0gd&!1Hnnw4D z!4D(8r+DXk6NO(_MB#HhDKH~`(jCSne!kBq?OHL2F&pigDllW&Pqn44L5q|V4s#Rr zaNig04-BR%BaTIt$A48R)wtJygJBpa&BF88F_N4W-W{a9F9}$j9fdgfq4xt6xcln% zx`9Nj4@@kGspK!8wvK3%?aJdW$nUgb$)3M_l`nYFqbE#~hJv-5RO+)>MOyRhHYe-% zjOcZSm)zX3Wa{mPAA=%x1*6Zc^7Oz1}{Y|_`qBd4I@+EThi#0;|%*J{KLd|4pg z?Q$D6zeB6BUjhf%db*XxGw+;Gc>O=wdx3w0;3^CqG_&z(p(2P*dfSE* z$VK4-B3Oh^Y9p>~ZYH&HHGH@yc6Fa_xN)VAI9|)2T9xMt`qP5?^B{i(IH2cUBx&+P z3+WL{OR;L>Wf;l`w6{ZL^WIjEPtcAXJ-*AzOV=cM*etk9{u>%IzjV`lcFuue2UdP6 znPctxF)u5ek_16FK_F%H>Tco3p5UJ?d*JBAcgm}uh@d~8HC@5@0J8mO`$}Q16FZ?_ z5pLpic(BW(~5<`Sw+u*!2i1p|qp(=-`Gg>8O*+-3Lh|F#oD$J0OId5Sl${|0ID_r|(687tztYUlVYGs1f4n;2Bf<1y&>_(tOomI^*@x89X;=B7Y7EIf^ zqOU#605TfPtV8yw{3d->aE5oQACm?PM(sPd#AMj+C@mj_>9J+1d!J@93W0Q7TiQDZ znLn2ZDozL~k2WOnVMEaR4{b+w27U=^X-1UWrF)n?s0YvKpYm7&B&J9~qjkY^c&!dZ zvK)C;Q(W~VB6ND}OE#s^+}6_GDl(06v3h;}b9nb}EPs(Hl4_G)`gQ~GOCvTihYL}1 zra}#K1*Ky%=x1R@b37ik6QttMDSAxOF0z`Pac!Q^B=H5q1t9BK@^C?1OuzmvetgVO zXm-fiwqhiAc$$|oe5eccV_4y;ZM~J*hRlTH;Sn(YKry*Fk%0ZgL%*qQ7ysDG@x{32 z`+?;ez2p$oS6YPal-g&Sb5gm+;BqMPlF5D&AK}koby$e^f^#y3kxxoczx89?*uG0|k`auN`OlE;bJg>cfswBc@o6B>uuysBDVlW02!0AMOfG;yHU zs3|hdu2^SSI03rfwul6SmNj*!js%xyjXA5O-ihUFLi_|4#}}1^3~!ffNAdY7ni@@7 zGRhUPc{L}J5ndT{CD#J-U-o7{eq_>e(@^%6s=f0|{d5khsAx;AgezX?&{P#n#I$!@ zR^!m+!+&v3hRqzDeTeb{c*S%R>`L(kVowEaeJ{HAR;6X`d8rB;m zaypV}Ogo^=p(5L<%R9`La+OL*cE;e>2Dcx*Y*DojZkY z8^26shr+hrFO;}WVaOR*@`ka4%$R%^5js*26IdXbD1c+pe@6|pIj?JxN zfyek$Z-x9YsLo&Tal8^g(<}Y3>lizpmSc<^cIgR;(e}hK8kIQ$Tu`0pzo%P-j*nf-nQnJ>C+tFkkPxjj(c1bz5xn!>>RArkqO_c5}mJkcq0 ze&``3XU@q@%a$YlkB7iP@gX{`iXlAA)HGT52QZhp3OlNc%RH#3{fV@uy6+b*7GHN|$vr7#sJ6oc5cc5_8Y5NAd0sp1lgrOmVRT=%AiTs8U zxisJX3j-%C+cGExi+EWbsdf6&{Q1kU2adka{61a^=QvZX+3&lF$72)@Hi|l5f92m# zTrUVeUozZi2P~4LzK?c+`^Vg$#2s(se6#AKL^Od)@_dcPvkhO$FJnIO8mA4vX$lymCu^wI-6mWxZeV*y()ozmm;i zb#pkq-4@R8#w*m4Z2px`-~Z68db;v^Y^ox^ARR4yWb>+FDLk+ntCd?;ltgS}KzHW^ zQmRW#rvz)3J7-GMP{;!*DCKQ`$QKtjXyh0#uw*yDCg@dQm*=@U(4@A^T}0Cs zxmVIs227RZUZ*lU-s*!^GfJC4ViVUJE{ua4gRtP`5okPHzjUi>=3&Q*s{)O zK9?DO7D+=arL!V8)-J@=qlyw$e57g^g&V2li9%DDK%Gv$jRM)Pd`E&g#0#+M2bSSN+-`ed7 zQ;m}MTHUCqMW8YRy$S#go&jOkT-P0dnWy?kj3$HbzP&Cs3B z#rx`Yo!+~!mEEx@*J^A|VHt5S7Jxfl%3z7UBw#5bYEDWZF@{?=zZ6@@yrSW|`Ot@* z33xwQVMMfNdOKsv;ydmYdY30n=uq8z>4`VUU=|1@L$!5+ChyKh)%U%i&n!#e3hFMp zliy~GvzB%=R5ilv3B^A(d?f$*uBRB$8m#T8Mlsn)_?j)O%m!GJeENQF?N*Z0-X~^m z#iW3{@Zq;~ykBPY1HUDD?cdmlPNHO3*zRBX`U&Vg1^q(31ITDAv_8)>V7&yl6-3!?Q;&#u>`8Acu}5I08a2ulOb7c%5# zx0|;^04;Fb)xa z||P_!)?P+oVV-RWgRfpYJ}@of5S14WWinB^QhEA)Y+ z>oK-^v^JZ~tr}u+e@j2fEaIm({&ur>=#01IDfWAf${{-%`Zg`s<14!7>~DZT-3M{m zlU=sj&ThLWg~GGHmh(Ii=~wO^w;)c(Ld03=ah#P9^PH<$M3>mtQj-tfT{MXf?if=b z?%9q`=V} zTGmD|4s+XCpy6%A*xw4AT-Hn_gz~SE$T<{b?u*C>GNcZUd91bTqL1(E zHWmpC1@yKqc9w3X6XSQ{IqEoa383oTTcg&LkSpD%@4X3DwB}7f^pYY=p`lWAa$lo> zNjlo$bPYcaRi($v z;*3%vS$9h5G`o(F9bLt~x0Y4WTE9y}e}B|hj->u<%QL1cPQEiCl>FSG`4R^Sx_M$1 z9T#Rk3*TZ5G#6hNQVZRy4k=-tOVN#)u&n6#$(-8i2w{HdMOiNuukko!_x)$a%=!u- z53UCk&_da;v#3-Z@GSvbNyQL0L+lbsaB*JkU^gX;3%N`0UFT66x2Qq4RX!FT64Vqp zLicH6a!|YMdLjljhG3dl`gd$lwaicqny$|%2;1mmfYkVBlZD`9+j32b93_Foh5C-! z{BqxG+#!D=`m>xatlutl>P9j++U{pv1^3-gC#y|HZeC3G<22q1r`TE(wY%Q76Dmad zT^%Ip2EMegTaH{5ET-qe-Wc|)rAXL$sC;oEN-DlXGD|1VD(u;sn< z_FcqQJUxIASv18Z#6*QdOM9Im9IHrpz$h9C-svuC!&TcU3bMzWx0~OB?itEsQF$pw zk~>(i1pgA^_bJi#Nyy6&!Why+{QHrbLM#7Q4H`!|?_jhfE4KW?BPgDdy5uP_}!zw(*)zV`qhrZ&ODvw?%Of&8>GnFdweTb7IlFqjl4 zY0RXTXY)8uCrVsIlrFQK~7K1v?ovY>IHhsw79%eT%-6fSy$ z}6`ZnYORyvZt&Ndha(g7!I zgP>+&`B(QYo0yB_RQL$T8j^rb*M- z93rH!!yhh1@BPT_6Ap7nxm1&S?uzYw%GhN=I}=Cf>*3X*uc9=rU*kQ+cI+yr2xua~ zW*@&H;ECc(xVf)gUNQRZ)ZhEp9fv%p-Ifiq;9dC3srmH?gCb(8x0be?V*n#~X?FiN zO9>`3z0-x7Y%elWyG}yb2|7kq+YqexQ$!+?-cdL9t=Q}^t_}%S|P z3!@+1YDn~?(i6!}s%I@V>ArJEA|g+P4$a);ORKN64PNh_+}=^8L;*^qw^fx(65;#Z zVo|KJXd0QD{(O0)z5zLp&8L7&^5a@XO(mpRJ|T&Q1a{g5`4TN|e80h5%!(ks79BeP z{m;Ra13LDT9m&+PnWW$eOO{MWaXHFX25iP|J1}x^$fqdIEAtOCGjCAWMDO|FUmx@B z5CypNTKnC0y~MI0t2lBiJT44>UWhBEDt(h&eC97=UmpSqv>p0%k3?6-uM@x>Ae2{B zN7Cyv?+@I5NMx6}36$k`X+Bb-ysG^~p0K7Jr*-hP&?2}|6pF&N`=%5+VP4|{YnJwUwNNvi|42n0>#@`rPj4`d;n)q=1pRW|lWH zQlCK9x!O&YV2hUO3@3ZAymhNv|~tXo-tcFa>xMM&%nH=?JKRO1Q& zUwOD@07(5_BZjUL>D!76YBU#bn3?3i>dHX&Y?y61Tx0||B!B+Y1B$a!;yxSE_rAxv zTJhB774sZ}`h`H)1Vr(T=}L-CDHf7Yk`MQxE};7a!VuJ2Df#>P|=;2<88AT?bwrXi0VZ_QX|UZS`(d$oh=(!v0~AU{({j#fLQ!=Dk7!4 zcK8T}wt(PPd)}FD@@5`sJ+AKOuV1grf%?rsH3LkKW=jO?>}ZRSdq9$N#uE|T#4W@# zB{1Rpx;6~e=-%Vn)2F2zPes8M6dS-1gJE`Tu^93a%wQyBnTi2dOc#<4vK5Qg*SPBI zu9D5I?b!A_XScdlh$LFk$a&ul<==Zf5PsMm!^xugivSP) zum8oA2uJIug~a&7u8ijD#io926dy^>7sOoIpdl>%nM*(r-l5!wRh`SV^nXXBrJ;tiEYRO!t7D3@ZbPfkJ#KK_{CLlZC{MP zW1aetLgezAztqxM*%n!IYZt_btkh7}_rd3UN0a{v29RS&A_oAmn%^;OOGSvZ@nK=u|ysI^J3NKv{E0pOTSo^P2d{pR~ugs1x!pUI{rt9Wt7#b`s`^g&LnDjEG+Z`3g}L$?%EmK zIrsDofM}^0Eux27WPFlGJbZ9828AXX^R``l1WfWm0!tSmY<1(}byRIrPJ5ss=$X%h z_=~4`yRrL*147q{SDOwUNt;H|xbuK2XP^uyn4u7FC3&_j2hVIOv!$-ZZ2iYJNNQ8b zTziuWE3e$H$YRa`o{(ot&#!&LoO%| z1^LEEL}%jaThG(dK&0;WvUIwV@X5;KsSw|D5x*k!%ipchuvRjQhy+>ooo8AQRJ6hN zrP+Wg9EHe7>lOGBf+JX^SM0?E2mcK|7h+(2mUD&J%^Vws5p=}KP>tzU_)5D+yLi0y z1Q#6WX2l>@-EyBswERfg#CnLH>q66=VcgKZ{$dh2+%d8H70`YS;hv>7;t#d?pe!jW z-PHM9y+@nF>JVM%rB=>#y2Vm<)vakT7Ofouu) z2Z5Gh$_K?01k_WW;*tj}EM}5y48{KzmNK=;Xo7dZZ{si1wfM`5351uaY4a0ND(cn+oX(XS;n(t{z<`t7wG za@@8nBl|{y_Vh46RmX|>>9aK(g@b6 z_=9^*J|UjVlf`>*coB#cwgsz!C<=nzIsrO3$Vs#4i ztWnuZt(g14)h$T<6HdGMk$Jix(4}yEx=|sgWdJGR)%qZtvk$S0(ZBT2@c4UH9f7=dB;{wqnhFD z`R7gqe0W(viHbSL2|jM|{gG@=isyK@5Upi6IbTpN2dG|aiV9P`<#>w6PQ}vK!t+=p zqOQt?p;y@vdAP^CF3+Hc@KQ+0FpO6``oQjrIRZrA@HZDIpv zIt>kpal#`UFWHni@e1vs_ccdipNk$1z&?YXYr;vsEgBeT_5nlVmIJGzaigMNP!xJb z%4I-d)KgH>L72)LET3 zyD$Ian_8d5zsQ&fV>u^4$anO1dzI+CvsHC|JDpu6ecb6vYDuQH)j=pmM#w@VcQVX`-dp@%;-N2NghrR5@_m z8ddR$TaNgo&o+afg{woe4cTh#6q|B5v`9v)SMOSzStIY^XpE}fy|A@YU%eF>YV^d! zb`=`k6=VaZP#0?BY4VhTm7Ws7sO8#H7SJdtx#GM9g_G`WL>O`So2t@0o|k)Q}sSqFc(&R!c7AA$SB`^(Cv1=AS~Z(Vx8V|9j|b-O?)ja z4Q!;1sH6RD*x-sDK$)WWqa!r@%N*`9rZ(kqy;cZf7HS#PJ>g@$Q6*&2w4KCcdzwhU z>Nu*LN8r5>;Hv7xK{J85IaJ$Z=3c#4pjZ7C?>+o1a=}>U@+yJ{tL!jrU{abPMgozO zO!a_G@F{v8Z=&Jeegp5A z{zr<85`e@1gZ{;6o~=gvgbqYQToV!K6AEnVfys^?&${d5_u54y+&HH-{qTYoTZG!r zJl{;-wBzREY?KvZeeTJijZ1bl=0}^EQCz10*&}R-8G%Sb0Oecj<~9%!MZTv!MqM)D zkD2AYiLFz9P6G3^8WS)>tq5tyx=5h10nD|F@eETRvC)L;(PG|Sk&YLsf*KzMHJFKoa zO;f11a#owm0Vn^LGXq*LJ&M)KN=N#=l1UHFA1m2;{PFM_OTmy5m?u;I88(8(d}sMU zDbH!R9iBIg)%q*Uh!GS@Z;c&sY(v@;cWy;qR{J4RsV#|+C!etYqL8Vjm34G>gIGnf z6#{w0Pj{QCZl+F70ekj*GC431lbKiW@%?Jm8Zok!0)NkyBeYU@^{jp7QWs`FIL8a& z78W=xCM9lk8h+hMAIY8}WHB>mPlEvKOw6nV_w+>_*qhoC;Z)Le3ByIQ$|_cu#Ft={ z6~g6=ef?72dN9Jy!?&*$~(q|o7F*&zT3BiY&L zxz|*Efu3awEu87-aof+uGfGXH4zp1t68KjYJ-+gQt@V<~7$#CwG`pi(96O_}2Bp`P z8lR|0K1^F<^(7W=qznlaTO+f;RxUGg_1wwg4qx~In*~vBIK%5*uzMV84u0#2C5r!$ z*w!iO?`7z)w!1eOKu=@>^&LI0kQblt@*4}x(&u*09@b)5a4mlC2a!7;AM;rq@wO{ynE>B@Bc7tN-By+mw?3=VNmz!Vu6zvDu2 zvsD2@^jH5|yT3yFd_gRX3fYl-d#0yFySj@T~K zi6p!Y#TpTF-ru|R!ivo5Y?tYAUCg-VQ6*>xg$5l9EW>cCVd;(%I)5K>U;pg|k>ULi z?u8XSw-_$247!tQsG?66f=fa_8bVsTydRQWY#a<3r9I1cCG-_e37Jmb(-QIvhNU!j z3%ZPk7`m`D9DpUP@lC2U_=ySK>$S8t(5$e!8pph#kgwMkf!wr(8J2A5Cpr7q!O+yY zR|>_{V=Vg@IQ`;bjQJMGV@&8#?qt?w{>?GouVS`*wZf!I^ZkBRxzguMGd(eXKR%Ql zn+&oq#0kzz{JGfqaZI=z@^*Rc^`F4t`BJrM&hdP~% zfn%T`@paIsF9-9x904(HcRg{Fo_ooPC<9^@h}s@oQl`)4!(lvHgTvCCtJ!q-S3a<>p2mP|+b+AUl7$ZQ01} z99JZ3SnU%^X`BCjKky#lpnT*c>-))M+9X)LGX1Akv_Uvs-m)il#Kx--eowaCEK@;M zF)FzHPq462or=HJPQ}`%@A1f7n6e}9#CgDpX>dU{BJr945j0}VS_Eo3{)7m%-3T1I z!7r-K(3izYcJS@fky466Duj{#6)s zfsT4{jexxgSI(<3T;B-xK^a(ZD7XqZ#}*kThF1S29=!l@8IloNh(^@yq@Bw$+~x$s z#2F|1d<^j-o{<+>@Py08Lh6Mltf~NVq^Mz{Il?h}MCa+anRE-C8WK?Qn`4~nAsUA*M5n8==@UxTF{PUJlh4W)UfIA7 z?KD?c3U93C^Cyfo2RvEU5FI-sLv(M1Id2bQNZ0|*(EXsApSC8#ny#UDU*+XY)2XBD zlN>>tGPwgC7$yJUOT`|7GYvmb;-=$GL&1KX;F@Z1H6^+YcYi|) zmX&U&kT7sF{@;q1s2r!<9+Rkla1P&=y{kwjozt;^szIOlx%l)}I+=P4cf$uT4(F8W zzEJ+NrgS9}f}`;)24|uh?|R+vpj2uT>qDf_Du0sJ@jgdp9yMOH7<5?Wf*}cEA^FKs z+*u$~TC{T>3YiWwPN;!f+nS|FXh8bBe+e{@_#H+i4Wh&g07-z|7bVv|`8?E!qHF?J8G? zy_DcVA_3eNR_j0t?^MaU(~V%!FX4{$9KpROD(_E0+-D*x+LX_|))G6geBByGI8S|5 z$q-Jx%0qu%J)$XNLw?=KBX+Gu>}PY^vA19ghyP{3zqj$0)>=GRyQP)*@8$RDi4i7q z&I5*$i2yE40+hLoz(|>8vH+V?9K_QpO5@NDqLPSIAxIlorA$AI2U#|;{*u?+vaoHB z;vQ2n!K1(rR0V`W(<9Z-kZL~(!M4kuO)n8)&#=KGZOQjCGh(rAwkv19UgubpVV}B( z9y8h}V7WgW!(CV;JNc>`Dos<#isqjWGcNqo(lrSI3AN+QiC`qFcq&|Gz5>a`1IR>%Qb{nTsQo*?rnkO0`mt=R2#J4jR z;?Ve$=In%Zy~kHp$7BXXdIj+`K|K^c)D4vr5Z20%ZWo?9R}vSn6w{EkK{`ar_${Mq zNo~@xK5S9=OE-pyEAZhc^H38!K<5h8ZYnJm;7YUmHuv7goB#Np1_BH@<@vY~Uv{>b z0z0Qi0L9og=o~!CS*=vGMIyHZ05-uodDC+Q^Fl$mUqYpTIl}vYn1yI^`+~6S=|M@2 zkvN&h8>xQ|HYL4G>K%FUwV2)1#UhAmyybTfKJgtcdpYIq`O~^OwQBjS?U*QLX#x&S zv-ZhqbnA%<7KMC-1CSf+&l_!jrjJ+)3I6PnF|azpdZ9>rN3Yppi<`Sz ztl2xam*|HTmP2vsxE`159Oz%nic2F4kzdiCcmG#;l+bjiTRQ*W2Z@zF$Mc%xIe^u6epzYmt!tzX2U zBs=xQ29K6bJC}jPWD%je!{>^{KdzaeH&6sdpX>Rv7@>Vy7H6)!Cz>6vDTYQ&@qj80Ne}!krB>w@hjFYc|{n_SHdvkqo;4`m9 zQJDU<8-eoC;u&iomfGBg_ms$ktjHOQjaIv0JzJ%{0ZWv33-ExONJ^3ye}tqDmNtv~ zrLw6RztL82DtC@CtXHk*2r{ZQvYqw#8el_cV?1K23yFXdN7*gyGKM$ z3&k~|Jfh}fMQS3l{b=$c$cT?u4;6dY)ZKOuh zl80!I?Z06l(Nz%9{|Ue^?U8iWTJG)_8e?vEl9Dn_gkp?=RJJk$B04YJ$3waMg@{*RznQ{YtJO43t-QQ2t5FHDAKFH zavZj88%Vai|J3KCE&d0HhzU^lQ*I^P2|y}i0FngYqYmM)*qpnc>BlMRrcy zr6YCA*=c=G*_@_n?GEbxNo3o$WXM*orV%z4EZ9H085s^`Mj2*`j69B=DBuP=u z0DTlqipuPo+2aEk+(;56Ns`O|A00r2s+vVMRs+c0yNn25kZsp(ZQE8IvP?n%R9w|6 zKuKdhzJ34yWJuPweG%2&!^Oim?aQ`pZC&?2z)f?Td$w)gr)?Zu_FChaw#${@*Q+Zc zt7q1%H648`B3#k76(!F?K)@T`%ivIbW1A#Nwe3ok``~3}u`USji20##n3;LzH#0No zp>(CQ(`d;+YF9@qH>UFY!Ga7gpV5fMtEI)mOj!!|%N0j&5ALKO!vO-~8N< z{O(UYzh0xff9=yBeNZglJ@5Skzx};y5`sJW!aw=!vfWqzp+EjB=LxKF-2aRJeYw7m zji387KeFQUfq3`t{_}$+x^4c%AN%<_mv5iv(LeZS&9C`v>p%KOe`~c84EX-v`6uut zzkT-~|D8*iLt^g!U-*tcyx`Nn_h{AB4D;v*=V&RuI{W)CPR=jjlkZ!rEx{9=`S_c) zoSt4@dB+---#1&_|JCBwG}Okco5j-GZvV{n!aNmUeuXk#YQK5z*Iq5If4wKs%6py*nU~n>7k+;2X@xgmnk=!iEKdB+qw8C<_Hs}xuiy3dPm)-hZvDK= zm)2<>`~9s$crrhCF?LI8yZAFN)?v&jSbaW7meW@pw%>i{kUmr8wOZzQ8Eub!a_P{% zn%=BWm(i;epFDeTSPtd&%W=-j=k?1!`xy@nvhjSqyL=9w`Tg_DLp15erJ)O!%XyrP;lpR@a3k1s*;3gSuf5O0;a;b0UP$8Q z@+F7shy2bIT%<90Y5YF%!Hb9ePR#4;MgH`y;ID=F(zmt`9~QxCed74^iGf`{_;Lw6 z`|FP^kARlfFT3bC#se1C;;G0Wi$4OM{MCFr!iakFqves@Lewrb+BlBEUd)6+d@%Hj zzv%jh$@&85vESJ_qC4o-=P8>*2Sf@Ed@2h(%h(d;n>6P7&)lv$n&6Ai^oAKUc?~QktN2NA2E+@$>FFz&9%zmNpRIFDoDmybOv3baxX9`uQ0Lyie}qq}J;Rxs>w`hK)(IeF818$; z39RbOL%#whfyj9tIJ`cGufS%6+Wz33gCmp`^Mz;EGe7@r?2`oS3SUt9x7E#uO!_Wt zKrCm=`JL|LeuA?9@NOn_gK8yk1>4z&D76i2jV-cwG3lH+n2d@;bL%nQivS6$wZ9SN zkJ*a-ZyXWIcZF71=JUZIo=b0TlRy~YqaUYBV9YIVByB;nubpCz2ad_pmP@zy>v-JQ zv-{U(D?k(sVIWOxkD){&5FrLSm1iWUNNWs02<9>PHUf3w2ruN0$<4=)9whX*4b=bo zR#Bm$>QNX&rDN$L2pbBiUU99bKnf7(qA~{>zXayUbDVb9VJfT`9E7KUdW`Pd!5DbW7&es$#7KZ+bgX&t%$W9ur-OQY ze?KFXZiPsyKBUfvYMd!X95~EgNNnyDU85cmATaUh08Hd}ZFd33^jXMbpx#a^J|!QZ z^>({JC5L5b4t9f11-5F(^l$1x=mH@k3KX|-)AY=n94h6j*F$ZqsK?Ilo71hnJ2evC zKN`7t1;!p;bCr&0910MaP{|x>5d&hn5EB~15pW>DC_!sUltDOk;5*c-%VTGNx9%4> zHKVzteRshA55XRGD>y6xcvJ?sPZR(V*dd}NXUNP&NIV)HBfMRUgjjz9LHLiqWqGk5 z@~JVRw0Ynvs6LCY`c%pgD8eEO{pHb^VFp5u^bSNb1pw!V$DFlhqNW1J;)q6*)Acc2 zbHswWJ`qU%GK+ZPN~t(XH(dp}D~{uWQcsVj zgy#UvCM&(zofN0#^2rPI+vn6yF#MqwKyGlWW#Olak0NJxPRBPO1CBy0H=eBn2qdQ!T&aDRUgl;$P6 zhvP`IO+XoN>I6eK*kS}3Hk9xs2lSF~80l~{D94d;7L}duR@_ zv5vdV6oU1Ttd|{-1b}EM?a8==Cg-XG6ioU)TUo?o5Hw?PFe+sSqW}(?q5`8*#6X~A zw%eZxediBPJVak@&<4h%UM|QgiCW2czYH@+MdJtpbq6MLv5&b}${7|1LMiUxkdU67 zxdA|9UGz`^fi=T?+f9|CRRd~pb zb^XG0m?S7g1YZXcj8r8rQR9I?a7ydJ$>_}PGSj(v6H;z`2w(_ENTw1HX*brUJ>#H` zw{_{QpL+X)?|=E>^_a+19$@R_OF#eEmk;5mT){>?$filoCD%Q2vss3qDq5sJgY8%_ zf&tHglRXrtp%HO{8yE=?TF-!#H<>js1xj=o7(|Qx$A9uWmv(6FgO~0+aL282bLMU|>o&6b=G?5MDllN()r-AZ@BZ za0-OcdQ!H=Sm6b_Km7x8GyGAv&Ydf-Uq2Y;1j6G;`xv!4`a=Nl?y3~Ppen{x(^JoV z^y0xXUdX<>0iIu07NPpLmUZf-`==jlkW5SuZhrNf51W~2&+(X5OswVTS5<{hBL~G_ zt9A)k*u1TU0Km-?5V(8)=bvqky8is8M8$JQtPdW`I{oywZ+togwh2PZVDR9|8`p1( z>(Pu;_`$TCSXM8%3sSEBa*W|bS?eqVsfPfbIlpqXEeP$l@BUZL_l)xAh0>k3s zFMqH-4lMs%ygX-jV+rHVBX2zRqX%RyHcdW=;id-G*m(I#x;yu8y!Nm)B?^Q(biFB==>CI9OG7K@BxLB!HeJwc zF}OP)I^Ur1V?jUyPrdue?YTo-+4Ouo4Hj1L5qavB*UtT~$YP^i7nZzEhFdqTUfbGD zXAwNJs!cvFDvs2O*Sxq@_oz2PS3LfBC!nlWA+L=nu|i zydhE$1s7j@?ZyvH5}WWRAMf9PHqtD$qiY8QX6?P*tvh}?w#Ev1qDhM zQW6zCgM!1LV~D0&ZNfG{l-RHgLx6&<%PbzhTe$b_cP}rOvnP=27CF<(T)X@ozwkr< z_u1zCFdQx!G#}i)d*`qpOP)I=H`e-Op%4kcFGT@jj%r@Vib_6gD+u|Z0@Sz&tex}U z`S9Li{*nrTv^hbc?Eh0w>+k!iUwCh@u@je#kGAi=dG%-z&v+lmJ9&hzN)v2W8Wa76 z#^V80)obJ#CiSN~5Yn(#2tjiV7EA8k`S`nMmTR46b$HESTwZAS_Fw;{_jy{`jk_p& zo2b9{@adz9|DHI$hg<<Pae6aop7Xl_L09YWpAUu>OfLEURfnWWB zS91rZv*GZgbhBi9w6nAI@KJkOHYf0QBT_j&BX~_na_#9Y?7)ryX2S)sed^wOZ{JxQ zkZTJF1&OJV{t#FQMn8#wpCp|VUHa}{{plB-px{}vxMbYi-FtNJ_Te}usHZB)>HflB z)1IMIsi4E1<>Xkx?Z%lGKX~uiy(pr!icbU`)VB7Ye(Cu~_C9;1LBNv`A!nv^?k9ft zC!Rgi4ssR_j&>g2fAFxIrn0tcqGy3ro9C@$M8}T}sF4^LHrjsl_4nSnd-{;h9~U`s zd8lzhD53Fu`_F&o*~jnx#4CUMFZ{;KOn4GLD8TRd$v^m0HIgaLBm=1mjl=HNquY=6 z+mkRUd?Q>abd1s^5Rpw4y_w)2hV={ul?!=5`&jrzk@r_~w`hl9~HO0wL|MRFE_{D z*=vtHb^Ddu7f&4a_mz1{T)_ikuMi=4wP$|ud>HotYP|gYA9<#6DoyL`+3)$epZKqsl60g-2-f`BZAV0-WMUVx{!{Ib497K)hf9bb>?45!tXYUb)l9dlg5{J{t zWHOyj!s$4iDe#A%{rIy7UpzQi9vswi$)e(dGF@Y9YiIw&*>hJeU%q+m+KtO+%CPG2 zUr=2mXZW(%+ZtJlvT zJow^^2dkeizx?uG<-ifx-rC;X+1o$4ck;xk{gWs60{)SlzBKWO?F5U1t}&kc*-JBN z1!@=9!KaLB!G)K;>lc6MB`O8zjXCR!gD8oK=Q#4?eYq@BLf#jPKsRpOk8bEngbCn_ z!H;=;Kw<{RonOyaPSL01VEyHHec)BzbOfqSu_$QJlg|bvtYK6Hs-p{)S4vuc&NEw# z-+pTLEY{G@aVmsehJwwPKlsjPrkOgfi{WzA0qEn8DsBlD#on*3k?k}uvd11H4)DgS z?|a{i^+=5{h-_eUb$QbCPCfC_!E~aT@@)M02NS{i^Y8n}ORH0XqAc447A4+tJzP-l z9)Ql7x9_au8cfa?)dg+zt zF4afV8D<${33IVL|53Rko=boN!-IFemyTzg{LIPueHc+sMV%#)TJNe#c9z9OddTmOgV>9u}Gu zT5TR2YQePf!HdW5rEmYOe>*rHbSl8k>C+dlJo4)0T0D-OI+y`jWC3NG}iCw4QIb*yYh=OGE} zJ)eF4L@GIc`>%d_j-BqFK7aY*#j7{3U%Pz%?1|lN_aYf+Nyb87T)oYY039=Z0KN3= z3E_MDzkOqS6vHvLclS@7zHsj1xeJ#rUb=Ah^#0Cbp~qln(n1za6iGH23 z62Exqgz&lkesdHSj<|((&MVPc%Ux?CmGYMyc-_n@CHH z5(hKbYYQCMJZEuBhM8YjC|$WT5iPY%bZ&zT9>@lT*jC1ZD|4X3l-Gtdh?21rO7jVg zXCOQ(GZ{$4n#C;~cz)-h%m*n!KAZdC;JnFgDt;7Aco|~4U4|edAr(xG&4$G-3ZZ2t zwgCDbY|I%Y&5SFXHG3P=H1bWaQLQeB#p=15#huB-L8^(_GXPW4QcxhY6PVk;7$>q^ zK=&vDgZK3pDu4U1ah?sn&U+~ZvZtA)_C}R5^m#-Ia&;pxxMhQs!A!FVe@`RwVeax^ zP?Qt0W^pGSZ5N%4Sgz$tg+w1zCa{db`*q@3 zl7J0<*}0#QlW!(fp4vnCI_x%^_SLy}54;js{IYXDX9Ab+a)&A}5u^%L40XyaUg*rR4MpI)2dR3lQE&QxZ%}P5u((Y?1QDgr zwX(n=GI{$LLN=duEg!_%s>PG^B2}Jtifb!PBaV{OIYR~@*Uz)L?C0Ln<; z+OT-6zIhz{WV0K)UL;gj8v79F2u;gdSiD|Fa(*SNn7G0AcCv036dHuqj*P9;(o2-& zV()~%VuMwC8z;R?&ov;^LG>?Q8M3O}{$ts7X~5iTiUuy2y`YK6ApK^bBH2{2P6Kc9 zBGl?^@TYEuT4rGK-gjE2lj4&52aoV7m1=VvkF>Zy~+K)b_<5jfA;`$kWqqb3DL0T0xn=O2 zVLwsq0}G!Jnw)NnfahK#q@crRP4GHzLRc!+;D5OwtKgm70Yr@IjwEG{<^+OJUL*pA z!RD`VFF(#yl%YI_xm-oB@QB;!*$>QZ!-8cm1NP))4T2%k*g=jOFZ1pT@U=CzHm2U- zARCJ)!H_Ol5C)s$MXpPdk7OqSHn#)HdQ(EB=FhyFlC^wbU8@gg#Hdml%>A9yy{OP= z%`gxYiBSMDQ)(2L;=+@rYFXAZv$buIOlT5z8=`N-iW1+E^3VHx_Xcx2VJDlv)F5<$ zV+Wct^6!=huxCO0imI2*jI(z1sFR@qfi;l35nVGdbO+*^3Yl2P(CjLU52ezPwQ~;p zMIkN1Y=%O-$O1yYv7BF^3kUFj-HBTae>K&m zd=ywuVLpU}07v}*egXbT+i^AjXc#OQ1rHW!7wsF^x3RFbLlhs(OmDHhAg^+AW`S`W zluH#0$0AlnFnBf!4`Zfm3tb{~ICQ8?;N!e-!@Mo$;$b!+>R42A;p~0awNjdt2PK+P zi8xB55sdTE^D=%GBZA?tyA6j%WK3-+HHax0l`DA?B4>lZ}T zX!|6XET*MOuId0m=QrEqV?GSd)+=`HNl8PFB>`w2vPl6#trCh9=HIuAZcDpxiI z-LDHvq0&Qy3dgq2*-3n?Obbv9faUYeeQ$V%;i9Q{fTSuI)^;%NLMkU{FRLzB3^nWB zl((nOQ;S$81)JN3eJwB6M1LR=ATBLV2>KT%wceMrZ-5DMwhp-7fECutHaDiY1)Z??MknG5(ripsg_VmZ%eYgoc@YlsU?SrUVxOX$(uv?dqlNQLSZO1Yg#6&lOD_~?IbGYkcePyr|7qGAutwXrO<4ml!T?bJoQX^ zS$oeXbcdEk!LtLG!O-UVUE&HRG8V&`JYivdhP*-1@+*fKAO%rfb_QAdzEhAJ3_M&U z)*Q4CvIUv^h$Pzm77Z?$i^Alsw8GTcvhL8!Oxi6q*o#(4xVZ#OVJNC0SStj;2Mw96 z|8MTp-wi<@tPX~MJT@Le!ODA;jyXFEe|W@e?C5DbQj-m=dCd}9G;2a(L#HBc6Gg-b zR*o=Q2ifbE9Z-=vL%+tYG?2oQxrI%L7(*+9D$-KP!YMZ2-S!njm=Kr^gXf%sp&h1C zH`n}w=`qXGNC1R}Mx$oc00L#JPn#G3GROgc%mWRUPJ%(#5qzz3u+S9ZDvDXGaMl== zMg#@FoFu)Il2r{3Hp+#Wkc zLh7snFt{rD3F$-Dpx8uK4FMIeHwOqYOL&CdEFA#Fort`8g1;fL!R-`wfw@0-0M%ml z%$4LbG`h^xcikm`iZLrmJt~P2QE`mQJ9DtjG_R~U*pOh-IT>JZ+0bMK>|9nS58xr0 ztg?}ASc;vvVufG>2JCc5M2QQck|{4N{fqON?ov#nEHhXm+{!K-?u4yx!265I!I>+B zQnfj<2TH(JzU+WruRytIP|QR$V>c1%iHL>GL_8v6lo|Ml!Z3*MvLJn6nmJYufU?dQ zfk!Paz;4tviE@P}8jUtfYz0>sb}V6>&s)A`7;%v4`nQ#(I6AI=L9`saxnj6^%nzS<}f(MF}r0Ct%NERVBmB;wp z8xf=(G9-qfnVkz7^m0i&YCCZiS8M28MVLATOaYM93*Dxi)(bL?TM3qq0Fhfcz|~| zRDrfEbm@*YR{9ORP2?~h;Xtt0T5Tp7E0P%5A)2-lct{uwI1o5|ra@@X)&WC-it$e-3>?56G0ubDoKol^#LBGp})#4xlr^cYvVG z=7%EFV8-mXHewM&NJ4yDm?burwM$v#Y;0+3L{Plr&?Mu11_8E91#2XMAm$j|rkEE7 z?H^n%-)fax`N8|7H3?)R448YY9s@*bZ>>ZFN{4Ph$| z95gQ_dT}#N+*<@7gECHuN#~GsrRirR-&JMA6)SVopm*7E&15B`*KYu@fQZ-a{9ujYY zm=p{oqKWc~)WS(XuG1C(Jp3G+T7^dWdY1vr_38+96r362<{)tQG~RiLYY7ye~vb|G)7T}CXQxwJ0QR-HkGT`OKcVO zs}Nvd8wIA=K~d*uh8V0t+td;d4NI7jFFU}?!=frscAzW0ctLp`3?0`TlYnJhMF`16P~;YEfj?vJ>|#^F0AGD&sf{g>eDu@Qwdc#oL8Y;ciltrj za<4PZsF{^?1GJ`mo{_3(>ke6Zf1^7axHsL03=09e12Y?h z$dU;5H5sXiongmGHnMyxfT9dzG0!08G7v(cl2r<9B%5iz)Gy38^d3xEFN!w zedQO;@65G|j?B71w^%?#Q3;F)E9NR24U9QM!f-AGLJdbc77payp<~HCAuJ%MPUWaX z2pmPEVSUU&Mh$MDfM}qROq)>=sA~+`v9R@~kQ7v`W3>;v$}*O)V)X_uR;U~7E`t+X znGr%}Eq|e{uIMBxtZSh7FxHIoOAQF>qls7a7#18sptFF>H0KoF_n1Tj5SpoqRe>1x zbtb!s5DPTJ#8UzRfiRzm9>W$41Vj+`CMLn5$`*t_f#t?OZWR>Cudc~IfH4SFl)ww^K^Oq2jG%d5g}e!PsKkM_ihh=Hu_N<(J7ec1WO2eVm~|0YGbYd*J%+9{X*--|R zoD&g3Nn?$1fDRBrtE|tYZgRhDsyHfx{S{V2{M+-?zEwykZ9_~RHsgk?sutB6g$=Oq@`ue{w{87Sih5I4EF10AxuFN;CV~R1=O#D2xLF$zpal zjff#&YBvZjZIm`y_ZbUmIxVn#Nl%hIFRX5OBJ(=MK@>o=y0dsdDu;QiX){7VnXIi4 z=$aFhk)=rc`A`0LGb^Aq873+#HL-m-paFTtWlD&&+J4r37eU$w(3sHuk+a+28h57#KMNe|pl zUZ9F>FV-M0R)Wf)&7LVtBjuLP5mFAMe^g505p|wf*Ia)@N?Zh$6yA;k(R(U55mt|? zY;D(M&cUrDarwwc{+9YtdhP^0HYvNJ>`aB zqgM;DG2{o3gmcEB%m9_H3vFvfcffMCSxSqcSvN3(Ox08_TqG}s&!vFP_keNeu$2ur zHlB0~P*~g~Gp)Jaz{4=E9CDx?i3z8oLo|RE^hq0tKhY>c@;H_R$`UGG)CfdLJd2-N z)<9`z5)f+?@CXYtpI#0Xniv6&2TaFlBkme zGVG=1ieuV05l%owsp3S12pX>PZhP{Kkh;Mj8Ri_J0?F)5>kvgfHy2fN$aJGM5)0rn zkm;Eah*(hOfI~EelyZa;vPXr$DKJcJd*j8ZR}f3)dB;xhZ9c!C!q;%Zk+wNmRBoxE zh)>!NvLKnJ&YGU+n0cKB#OkUa5qesQv6l4wfa@ zEjADXl7hnK{pG!WSCbQ70F1loKZ1h0ZPipEA$-b)gxX`?_+K+5j(O6|Ua({lvdP^h z&~SMKBEveMqB~|N+Nr^}IH|P_{Ble5Y7`7@s~V#^GCdB1xX(1d&^1dAx|FF31qmlI zte?|%sF|lZr`ik(EbTd{QUOevC<@gEOt4vpyJa~QhB-`sH)Puf58`$TXpu2X05%gD zi|Lc2JSj80sggI}SI&R;Dv~)2NhOP2hh@z}RzI?YVAzN5n+27m6TAaFq9ROwDnX2A zKvA(;2_f^nm0oKaiBf7hQXPnhv|PGn<%_)(5wT(KI&C`L#=--j+)*Y=go(txIP4e! zK?4*xDd#3hBN^ubgJMuJMK|Kaz@aL%Fs;)ac-mUQLrpi;VGCF!7&% z3l{i#ARMp~tBZ;BD42O4o%)?gqi$UoFa=lNI(*6MPOITBoLt( zJG)+X$oC4(HlFuV@t|SDBb98TDP@8RwOraXaTaydAOZ|ZQbt%7HJ>5T5V!ym^*b%t zs)qQ$Ig+F+DF}}MVPIhsx*9b=q$dK$oCTP1Vb^eU=p`w9 zvO>^aP$Z(BJ0TlNr}++TkwEQJ0*n=BSs^fF+&~l@Xk=k-SDUuk%S{jU&{%9Jb!H|} zRZ>f9pkXTL^9{5(RvcABx(Ut_l1h>p_B%%hP}-0*oI_fK^n~G{DO*w;DOelqtG3w? zpiLCgGY%o%c4V5`)tb|Q<`4|S6_p}E#TclJ2Ci60bB}=9rwMb5)}kbnsV*Zd7+y6- z8H}2m^8E~~2*;&Y4Z^XaFps&n7>8w(rqFq9hr;FwF(Mq@?UJ#gCKG|G%@}+Tk`z{T zvYm{fhE`DgEMdU(r~;LqCbSGQWfpCsuX3tJ5SNXR(wg&=R3v+m8}^qs(nH6RXI`;j z!Zm?|a1Q6BV4!?$#ZX8DD^O3(SG2^TeJZp9gFulau&PA|V3sAQe5h)2i9u3~23(QC zh#+cKj$qo&Qw8nE-G0<5&#&Zej+A{flNl~#Q?uHrspJGfI88$gHwuHTxmZVFkk}Yb zXXs`qIz6MV5$Ce@RXvH2rffk-56SxGJT$8fMkKF6dH4uLLzAO2b*>r#HK$>u4>Exb zW46o?OoMxDu5@G`T-LvcmwGF2JhG?36364e`ht&Zxw#0hXJ$;B()sC&|jC+zRYG=3tqXQAMoMk+qbvU)EGa75Ifp{*e60X51b%v%;NH~wRABwbi zyICVyrl)%@plaUJKxHO1rj9a#4~!22(?Wk)4h$BpQQDJ9*5cIA0O1}M(gQPv%+)Z= z`pZjbDT9ZhsA4VERBcH~C>daE9uTZh!VQj+L*bH97pV1`0%+#_U&99oGwfvtB`!#c zq2dj_4`C>QFaqkSL186W$7Fq`Ekcb(0qQG2;sJYRQ-N9kdRfwqxolNgG_ititkt7- zq(L~1+7Li$DtwtuU??&+Sg8cKveiapQh^p895Cu%4+2H~Owvu|(|QXwatR_pAu3ul ztnGTJE;=LvHA}Nw){pHa1W{FU!eXO-=V-s+a!>^?#nijAU^hbRm~H<_rJ!i#OIGc3 z0{Ff;k{8oTG|3hOK#w*>zAPa&=}#{8Acdk|eiT&ua6-g3c2ndj4C9%wH;O=gt6_2k z?ll`ja!zS?UktDhO=8ps588bX*Sw5Ggn%elLv1!*5vCT!Qa*>-B>hGo+@vJyN1pA2 zMI8!Is=D(KCXMcl0s_21DpY1Q&(KRM!oqH6*)-in( zwMK)M`cZ;XuMK(LDsa=nqE2@p#u-W_Vz>b>WrzBb0F!X-f<+KwcZhgxx<_3qt!wo#5d_GR zd}gEB36-M>R$QybWg?uqG#`C5dJc3OX(!no?@CQ6#)3WwWAabiDl62W2BOxK znIWQ4#xfxAnA;%r5~vK)1zn1`17xuT4!esbO$y?ps1VRlAgVOU?db&6%^4Y}7nesOqt?$?evNdvndR!%ATT$Q&J46-0Gbz)Dhsrc62xsOXC{43}rt z9?G9qZ`8^GA>KS@Gt2U-Krz%SdZyAkS)lSB5*XE{Ex|HDH}&_EJY*w+C{45!F)E0? z7st{FSu>(;8tO5AHUTUPA`>!N*8j>9gId^`89WLpnJ96%8W}BQ*I8=L5hiC$TxnsG z#KHhVyc8j9V?dH~p&B7C=qNO3l5s2W>jF|okUo~LHLxbxd>L~K?Kf3=kzUaYUtk~^ zgr-srm_l#!Kmps-8IHVZ0Z+7enf@HaL~5e+PEK*Btm+5ODSy(og(B2BI1!*`pP(yF*N#Q#HhWXAJCa2ahdWL!m zZA)ElCKUkUrMg{F&3O!S2BVfoW{R7TnwckWi8u+7;#iY?CS%GaZbCOM1hA99sJMs&ObbrP_q%(o#C{u8!miFw962Yg*RLSWppFjWkP6O0Al1S@=@x%k>tk585jD1JdX*jy4!b^#DF_i!lbKZCt z_8pg6^3e`jTnynObbxb!K_{usJmXC0Sr|iD;=nwl2mzq#TU9p5qBa>h_EM{aAP88x zd9DenG$&U094Az>N043tG-L2x1eVDrJCTFMk*KQ2I5y^GHw-4Fz*wn_ODGTkG!TmT zkO~L`!HY4Ct=9KoUP+InX;7Wmi$w zGp)x7%~@7llFxt5P5<_)|4mV-g||a5uKdyb-2>gecVkErMvi?{Au_5!0)ih(5)&Kq zshb3)PmoJ6P=pjAZCh6_Ni|HiTcxXg>qc5C@<>vZL+j2!$|E9W2xQiD;z1Tbu&~_3 z5F+%px1AmZi7HBIJ8UB%f5HqSb>4Pr&aRN;w(F1HDR3*A&;^+zWAhmm=k5%A0t zCC0q$5Ld|y#PU~Ht79tIZ6JwOJ!tt_#?4h&(iHl?lT4wYT~3hExV1?N+=M>G?LVo&Sa2j4GI?W%r7OR*l0v$ zR!a}%S?x0+kmPzVGD8ATcQlcbQT?0rI!dTYV<>hZxP+({KC)ild^ic!YvgHmEb zYDwdKmZ~8B5-4suEE=Ss6!%TsllKOuoVqF z?Pu`LBxFhO4!IQpArwwQ@_)4HNtsvaI2e&2H19I*fWpw89ucF#z?RYB&`D;AamSoY zg{`RxB^ZD%41wKseitRza@tCK7I>6k1(Ua_AOwsVh}@PoDkP=AGAnU~a4=@2ph^hL zfS4GZ1xDDS+FWmu2&`Q!1*oOOT$b9LPtQmc`ir`!GmK`mZU>r*DEU_PD%dTy&NPUU zgn)z+4J8`@I?ZXT1~F4IiH1aKlACJe>6bOs!jw*b0H4+X&*>HNs zir=wIu*AkjAW?IWi>V_(b*=u z(vouNT3&lVCLv0A56Cz(+gJDoMSkg>YpX<0yV@!$Jn}e-9 zE+9)jc$Sry9rP@+(#M*eY-XAXwxoJ3qzy8&AUqgyM@ym-+ib0|z?2R+>86rafRC8a z>0mWgpa5F+28vY3Egv8am42Yx(GkE#jrGj3^H`$FwP^L#s2h+ow^1fdG+@3pS#Y z46>4r)pCU5qZo(C)rO4D$!+U;jba@}qg>E(j&`2a)D#LDpcUu_nGsV`*v%OXBFw{1 zK=4tw8Br_a*=NPxqjj2E3Wts|M8pFWDpIM6L2AY5sGm{QcEvt#I;c`}XVTl|9&3_g zL%sxH71T}Ilc$;iQqN|Wh`3|L3(pu25HNevYPkU-tMs}sQ$0fgYT9kePA7(jOhR7O zh2xD#OtNK?Sa{}{;TR-FOqq?Q<=Iz{iw5c+l2XHgMQp{gd9)smed69;56fe}-7NKtyBBB?=S2! zQ_g%<3ZP7 zY>?Z~nf&$lgZA7m#CR1wsubh@QpWKRV};P~*BC+q!7+Q|5h@V-9#=Kuh3C=V#?KE; zZe`M=07yOaVV37c|FS)LIDMCRFcibMxMo27s}hMQCOfiVQ3%}NA0CLjY)Wwj22nEW zu-`>upz>JMuz5YvGc(z^;DZJNN>! z{+MncL16CsWele_yzDUfkL83O6~b6nG%Xm`)OACpV5P28Ka_3bCNK+%ZU}BPwBcRX zlm9}#5R(efJ5Jd_`rQkt{E#DgF`OkTYMpR~SaD+30>gdgbNhDSuh&V>u>md2{x3eQ zWYvzOyV6C+9glTJMOXN;#g-~sY0VvF5YYO_g$=ShA8+LG5vKFRE z7skTP8w(G_JD7>W+OEA2 zSjAI;M~#G0kgdM`Y7Z2uPx9_F2o!k998I+8=w1Pn(To{9Y*suI2HHT)A<2}?aCojc zfqje-(H@_L%cq#k=ya3tnUjlIUo2JuvzUZ>jvR|qv_ULF@H%#xJCO3J2$1TE3u71< z0IJL>K%`-(TE69n?H);|(9K<^c&lLJ$Q#tqL+sx-9b%R|M8oE|M?AZ}0~cGtn1N3a zQ?W=0Wx&!9QY_wodw~r*xbwe52LTgc71Vec2~b|wQa{IKjz$=GdD;(Nic>yskpEuF zi+AmqxTtM|jcA{tYd)zrZhnD#O(3127R55xcNAeNRIVvBuqm@UMIPWyp@|Q%N=ad$ zrP+{=?;mZFZl{T~YN66C$%}%^75mdWK|#PH&96_7T|y4&yrospFr%Cgl_0YDCRA)4 z$8>?R!C{lc@P~+Zz9bs?p_Qf(@k1DC>xMSkh>SuFpD8nbF$8$X%@e;vcqS+tO2rDM z`5wd+EQn&nVFSRC9f!r&j*fSVg8=nl=HB(O(sfw`DNZb>X95>2+*Fny80U}JXvoR| z0Yc|Cng`{T5ly;@&2?cPodElZ67tZ6NAf5>&hz>^GF}6EujG zTS_*A+~R;vM~G5>0eq#f(LlJ9s9)F=OX5eR7Y(>`AF)Rux1;WQZk7Y6L~@|9L=%Wr zC_Z$=Fb+rYxoPI|Xjo{ngVk84wBe0*Cjf2o92b&iKpR9CL!lMS=Js z(R5{0DnT@=uW=}Yn|{#30vzU;;7PV*=7YUp>D15^I*F@8U;VQh7o~;Ku}ZxFIP}R) zKM+njlBU`B&;wR};T??9`7H{LO!s#-)|4a_Z=U4zIk&?vn3${P_kp1-e1Im}(D1}CgcSt&+)4S$J_874mEFgtnRHwp!njJd~VP~d1cuu@oJ9E7! zNK*hqF$xL^wBJVGL4=ux55f@h2dEDvOqxSi*9cD?N?5^P1 zd0eExia1dujSUTFo3k5l%(j<2Pip}Lp{mlFG01QjGcYhv{VKksg@DUYQo^`#e&05O zh%v)+1NL}EQSBLcTY&*LIe^Na!>UdOdtf<01r@17;7(UU*$yfkN8sUA4X$mq6BHfJ zFtm?V9NP5Vc&wytT$*(n(N5VbV2%!2>|11A3EvUU%_T$lrJqWtwYMFVuWF+>>vTG~Y=XZtJX19H{NQ93X?a*GwB)s}-8GLnd>~N&W z21km#q>7_kz^)R60M>6i33nQM%5)=!p4Cjswi*vWR_Gs_Lp36&oAuLmh5FB@%PhGX z>&VDI7QrNjI)e7KkDCADTM3G2Wo+x0BB#*+3xO@+uUPUIZZT=64O~e8Mp`)mm6?-R zB0tdmd(S-9RXDOU*=C*SMn2%%^RW6?@q zB0%Zjq-+H)D0cGU!>!fYc%)B#8@O|PA=$WiWHk7iytXUUIOHl-Wb$f;K%2Bg=?7u5mz__3e4SyK3!JSmN&>j}>HwVJo z2~4)tHsD_EZaRHNS&M&Rsi38*mn4*Q4lTR7A#wXxGL}01of&s3z1fcZfbox@2p}(j zrbRGj*V#B>m+$$WNdZ1alqW@m0A~T3i%(4)hLph+&9Fr8Haikvi)2iYE8RlEBs6{; z2%Q^F3&Ne$O_cs*m25=taj`i}E=`tA7D(pysb-M=<{&!d&8H0%)*nKBwRG+J5caXk zkk~am(L4QNf#)W3|FED6FXCNz5 zZ_mo4i3_Zzq6-;Y?JKqQaPiA+-|I+9Mqg5%o!k|xtpiPDekX=*~4Q=h&hADOvh)_NE}=9za)Gw3EB3}$ z4!-nS-SMe^fhJ;m<}<#Wm|(>bDZ{8^Hw@P(47%Y!g;s-!=2Ifo)NYwtMxHNskp$xe zzA4m7_e+c8_Wr=I_4XaM;jbD+;wlKbmz2fCOq+o|f9(AO{*K;X*FhIt|M<-Jyv^fh z;LFZ`?lj3XWiG&2CePJ7AtNFN$`TIv453xLK^_YkL;dhGKkdU&9{-s6R{%aU1!N9{ z9;yY{`{2ye!9&~;;JN7q&r>NN^zC;o;7pEdo`CZ~2BAKh7!cxn-DUjg@1pzlagt{0 zoja_Y_EN&V24cezveyr|+GW@g@WcBRa1%+1gF33$QszR73Sa7+nnq|@Ikba$?zosh9*6luEt4Y@dZ#fZICz?cm}-4%i-kQo^iKYrhl1!R z87~@nCPKz^F3t-y^X$dnJU3KCa2k3s_xF2;(F*bTKr8E| zlA)<|`lLX@c0gX<`g?AV!JBXv#rAd1df`&GMk)e^m)SRt4;w%zx= zM$G(kvBn`$K&%O1R9z2Da2OY~2(+{G&3)e?x3sckz#?~rgIGJglHFXo*Cu}U|M1rs z>L)jRkOBdxi6Q&b{2&-+Lnp@&L0X)?cVsH*UySs;i0`DpK#>q&Ofk3m*IWl-?QCPS zIi~c&g3NF&Xh;#{U@DCKXsYwxXpG~!xQw}{nSas=(+F%@e$HnKwe8SAo|R$AAG z!!lFR$w=cDYCNV^%qpi1Fn#Z78#GYmt{ad#)gMoCxZtqmV4V6>%DJjSfBavg;cv+` z6y#kmltgqT5LFRslyoLAAKx5R4yZnXcT?idw@*XZfnjb;i4uOVUcnoFYZ8%kbtFku`FE%(!+)tVd=7y1Q_gn49DQcPY`} zJ`h7f9!^c#bg|rJo`BPyQE7Shstegz7ZXIV=b$J8ef;HZk+b5X3(4EuoJOU22#d|> zqD{xHutcz&pJjbuVCfKRgzqU=+c6Eb?OrhW6O_H~`je=SJ%LGTr~0GTq_(SsOr7`O zeud3ObG;qFJp5DBnDTKZLk(Rg4GzBfi@DRoN=)Z+(pc zfuz(Hj@ot5!M=K@wH+`vp=iaUx=QMrNZX?J~p zCL=?Etb1IRSr=uB_G#Ey_#QKPyH?2kAXRb;hTJSTid{vLwq?V_O>4V(7vb2e^Fr2v z((V!_VG>Z5)~RmbYqBpP86x+i3uGv8>uv<0<1yWEvg|sYDrd^`jGej)Hj#>vFTvgE zNv{?irDR<-`s>E}APmjq6cgJqF~37Zu9iE@dNNpD9W&*`bMr`1cO|pt)Oc_hkA#Kx z6e3=l?eSIHW324Kw)Y0v)cO}uy1ppC6FgMN$R&c4VB|i~XvX7j;QAxNz10Oq0xEi4 zk88QN%&6VOc{rhH|Di#z6<+%JkWVf&u#81sMJheSPS7s?wwQ@y6PWy>8akP8PS+p4a@2@kOZnQH3FKrH$H>PzZc|)eU@I?)N7dMOLuXJ_ypU(mBT&o#ZeMZjzW*)dg zYCql*woidPa#fEmtS7pP(YRDyJFedQzPx2(58cLB6qxWJeamLrJ`7(iBiMX!wNaqxo`~#h&39PydIWE!s8C#ZL{)`)ux&90$V0{aS?|%k5{xH z47{(S-y4+=-TEx3@$2@unyKQxzs{UarrUfR{cozH66bOt;Kz?7&t z50X+4W87S&q_cY>jHbv}e}C*=`j(71)K+Y9lQmpAIHDzirFwt#5uC4f%A*%}C>bt=i+&a?tm?N@~Jw2GETR z*cx_{_-n?U)-J?h3KHy1Hl_b-rpx|!rkdZez^#$oNUR#Fgqhb3P&1=@cziw;08klQ zl#$Q;h*DN|NOKR)(}Ux8KJU{B!4m$7(Q-h*EH>MumuG_lDY6;{u1wGR+V<&i$o(xY z_4E|<%kuN#x$b-JU?gP!m1$n*XU=Orx@g@v?UUd6&mup~Kz66MPn|FIraqF;_hh5V zoUrM!F>tW0ESaMM}w^TAB*v99--2+7B} z*-W>et+$b%KJTiR@v0l((JYMzRA9b^@}nYtPW}*KUhjKg>O(@}Rh(L&UntF`onG6Q z&Uq#!f)`_AG+TStYV&h=DRl6rsX#B%g*2lpkp(QZ^_ zA>$$B<3;p9)u-(IZl`uA?p-hW+S~-W#m+Jfy8Mq_Xhd7vfvoM+JKtsFf-0QHGa|~K zTNHMSualD{Z9uES-?^~EeGkk5Yj4Qmme^o)F z8@ya8Nnf=7mbJT{Iy`(z2+3}cJMLU#pzZzh^ThLBtwUtswDRg=a`6UyrDwc4mq6Vu zgO*>r4q(Cp79^X3eA>?zWEpE7Of&5an`VJI{g$@}9j^%RYB&sj&5oePfhjBD>G!w{ z+WOFG&)If#QC zc=`Y1XT1N@0tOQ~h-=vCoR>BO%PN0f>RC_NBn6hjx%+OIo0UkJHbbBTo#*p%GRX`l z7ZS1RKv8{Pc2WAsBI|yB9B0%kg%mwgYdifTAzOz<)iCh>*CFAaM8&?|;aTh_?CZDU z@}Vv^f9zSinz;u)gmxWpsMC1SQR&@rElmM$Q&acu57m|7acj8UdDd0wz5WHG^q3ve zEEU67K#~Rb(&`ZtBL_S%-{?zCk-J^|l`Bc>#e@I!V5t$A0jpER7#YE<*_R6W8mr)kAQ_tO@KwCR}vS{UqyQ?{YzCyNM;hDs3>1Du0z|D6q;OidrGvM73FncX{+UM+_`Caqf0}%Y;yeV(VpA#Gj zIR7RG6d%F9VLm2&d~XX*ek=zJet!Qn^67tD0_tzu=K9WOKkH||zYc9OmBxi+;)VU1 z2VZFz%G6kpP>K@`?U{OEVk%SNLcuDIFgN8GgNrWAMTvoFxWS(nVfQLJ{|`6mKI5Kq z992>HE&f%sNiX?-n@=`}S;{WQqof0hCOJ`+okO_!Z_ax@xcJg!xCn@v3+#0n=76$0 zOCj_xwt5nH9+AP|Y!c;e2`+8`^Nuaw9WW+IL3G(wRCoEkUA`W&DSb31#E2JK`#X6R z1cY@s2Vz^rC|y_PX#Ko_f>Tr(;i6S4TesT!AAu11|2=VHU@Gp>{L?GN!seIJFWVaa zjUhI-LIj%t7PxWKD`M}B&!zITf1CBykRqzj`wC6PBVtRi+h3!XS8W$==SxCc(L=K9 zrM)7{>YRS>#RQ~9A0fcNyzHdF{5w8_ZRK9Bd>h3#W<=NT1-mVoPaA!m-i%rQQXh*6;CrIKE_d#fG5EG=XoVEmt)e5l^9tEt&Pqp6 zh|r-jZL!xm5`jRk?SLPtm^jdFgaf?iqIk`BxXFEdZSlD|MFUUh=3wEtp3OS$BFD<2+! zYn0~xD3B~G3rbdGDs}6Ew-+sMhg0oC^M*5#+U|x+URP$nC$h5Rs^oM57M%uU%^#V= z!EuVu47W5NmVMpmJs=Lu6O=uX*~;ULEpb|LWLH%X_wQ^8D3nSA%SU(%?u88+J%`YF zp_;mY2o%jS&8q3ffw))pkFtR;r;T+ABD5W!1?~VoqAEvoBYUFCAv>;BU-amN-mJV? zZ-ZWA9F%AqW|szr!^GIVCF4wj#mggHDzyN0{GWtag5E}{5HQguZOq{$r5IShnSt~i zUSqJ@gQ}914Gx4o#VJFHYtnih5?HScOWpmAPptlvPsUeTX5D`71_)HD+&>?8Vlbm* ziGS3Z01<_SEm(v34pu&~?+R)R5Y(~pq*bEug&QZDMnZ zD!-tebR#z)`V# z*v$V1&UFmgZQstKVlXOJKMsO9%4ec5x+rb#*h&D~UJe4qL+188rNQ8Jb8zynU{LVC zpE5C^o?QB=6j@a6G=bjJg;u72JZ$?|H_2KJ%jsOHl$#NJf3cgXCY}K&?&Y$EK2HwI z-UWAELuoj)FnjY-^_yJ*(ZpbABMZOl`O04sv8(NwAr;Ahj2Z0FMqn zC4%K9fd-SW*x?N9k>X(nF)F#g<2U*c1^=xcaC5aE!paOiETKKui=o+RW+F z6KR5aQN4hB|3`eVr8myLxF2g?SPlk6i~Bn@HD&MnrQ7(pK50KFw+lyNcuzTHX&moY z-5oQE1R}X)-+PEHMA*$bRfe(gEuHc=yvaT~0UV~;W`?B&2qk2kXx%V`x^jJ7_I@qY zFa$dgneK5CYUmAe4TTy>lPHWSIN5ILa z?ql{|AXz>}{m0~#10!Qo{lPx{VWPlmRzBqZ>R}8z%31 zR?@&Pd)z{(j`bS=*(vMY+TWq4QQ*xHCuA|E`M+c#K@*G^_L=hK72q`arHzEF_D5h9 z;nm<61Tbe?dm29i+S}K;(|aEC}Uc6U%pi%|IB`vkt6v^ZdcZ5GZmB{~^&zEtnXu zzO_st2>azR?GFoMLX)-PyDqoAFGs(Fp1-2Exp)Nq!->4uTF5ET9X1j|XQ$E+DG0_n z0=f1P-@)C_B~nkP84$MTVV>Sym5;;s+}c-#LSU>nC47&VV}vGJ3c zr%8`MR|LU;n8wm3%QvK6Bv{VXqc+=128S*2n-Y-5af?3?K#&F(+~oS~wp4pixtza{ z-|91q#UhIeI)D!d_I?XOQpwgH6ZOWljRkB^yD6|eNQ6tE-ztOfXS)$c;R?UKd>pU{ z`_&TAX59O{m~BDLatEK89h$UTZ?ek^+4`|@$L=$}zdR%$KQUMcg2}J4>27igmT*Doj9pkN29TG-SNT zc!ohcr><81+yp@vJHcwM%V5UsYaYI71TS7A7CWUThox#`c|qf0m2mC^Q8LlY38@ge9fT|JRTxJt+f`n8y?8kX_S=i! zEzeBTWH*P9&yK~QV`ncU>xi*6L(cAYW*ZS~G$W5SN134_ZpM=h*ELnT0~8Do0(gDz z*NcOj5L59=AXmdPkVf%>bO5+Db5dNejd1zdL@dG;u%No!D_;(d>xyNkyW8qC3-D0e z!QLkAz9^A>2vihaZsnbt9^5nR2qN#FvN9K-Vd*uA_N2ZFH_&N6-RJZzj8~j|yD~>O z`iyq_FrWD*Y*d2oI~{lXwMIK(0e4mlngDTi!}Ay4X=WG+#DKs;=fxH9reRU2X9_eQ zQ81YgEAs!q$Kdh}+SwfDjlV)*5m@GtXF#0i*jiIa@H?rRVxBsrNJkO%0qc4dF+4mU zqm#^Q`pkC?B7p=S>?Wj5JVYcZ19t0`2#8H0QaeArdB!dNo)o} z1ipenh{e@Em=Fk|cvis+ceO}!8nALiwaDCU1DI*ranOU8wdD<=2adWT{{_NCenDrR z1tnnpl7E7L0_zYBuRrawr9j(;X=|`bV{^qKB~Yn(yB9nYLT6Q_d$8DFv(N>=kkg15 zC0JUe;C+@g5-hSQIdzX@65Qig*(VRTzAnes!2XL6p`a%D(%6cca)+2Mm~Wr`3Jj($ za8B*@7j4~lqmMHm7co;h_{t)u=m>8nLOMf~&!jl+Wo8xl1lk519w0pBs+XLueiSCZ z8p--zf<>-_Ywc727j)kvQP~OasO#xjLN+*35b*poiP>xIpG^qH&{I94Z-sJpr4 z@axTiK{}LWd%t-Xj+UPSNFh@^eAem# z8~pmNgF!50szrz^}m&4#SK)ncdv@2S!~R8Cym^80-b& z(smR9GlbxTEA3Ilm)-v>R?t82vo*u;=e0~gyO)5EL06wja$g=H(|4JvV}P#AhhhIf zhyNM!KlG+(r1C##=l>ti`u|AIN`-&`fNiFqvxb0xDnPe75~xkao4CRKYK}X*k|&=q zfJi{vrA680%Z#uMT5Yp-CCm<%?H>RMVxHCg3HmbBy4g{6@CIV~~yY-596psgvBW$EMEl zmC~F)A~|k-2o7%9SsQ>QKY!|t%cee5LxioXpx>!#isfPbwo z{#9wGy z$Kc`EQ};!Tie9l2Nn#Ak0U;^2|A)kbniLYCEg04TRF-t={sh~35HF8dig{`La>zBa zm}UB;U5ay7R9`t_@mxeDf4xFP6S3a>S^;kD=T=s@(YDXOEzrBC_0Q8ZjX34Wjp3to z&b%Gdk`Jb~sN<~wj6)->R3&wGy4&zk31SowXRcMjcrv`b4+96*U>5lfOn$K3VTNiZ z?MLxS{kr_j;&`zu(7t&HUyiEMhkHI(!fQj4wpg749ONF?OCJ8wA24!2Xm|(IsBWfh z??kBd&~EILYWZxHlu)cowywf4&3k^S-yaRopmJj9KSL{5 zqO^ycTkEOOQ1I+_88)v2X7ZE~fEj~O6B4Bvv&3WR&DYRnaO7yr%hv4kAZwCe8LO~zWdv@nV>A5`-8I=M`V43GnWwN5M%`Wu zIM(C(1W5UJa^F?|)l^*AY2%$@mbM6Zs*hPpc1qugev^N;MnlN8v48L<34V4i^{AZr z@M095o#(75n3O~AS~4)WUB(QU;<29K`wj$x%HM-@VEG7hZyy!f>%5D$t>vNNN!3qj z{;}Af^nJJEhhR{u=H~D{m9}0a#qL-guM-yfLun&83*1!$#Ulz`L3HH>VTP)cdK37# zaV~IO^JIjIQT=K5nq4uqU`@OGH?~D&5jC#XNLQcZ=GMSiNr8SbQ^M1=P1SgT{&Q!_c^-f!{lSBl(T7v1NHo*VYep|V4 zMj~Pv9#+JBwzE)r(49^eg)2@6Inm5_31gOU?zC^}dA#31+xJ+DxZ3}E5IAZrhDc>l zsVL!un1I7{Rn4^e-gTgGGwXKM`zTIre~v&YR} z9Vh7T%vZoBmlfJfuXK!%$=MBx1j#W1+sPSzq&oD@&DjtHBK2*WQw(FJHBQdVd8>4! zcLrvkqP=%AsUA_G<09pKZz!^5wzM*lrCQqm=6wxoL*=5cEaY;x*oOkCkn@Fo>WQ5j zq6ClZErK!h+B0m8J^6duI|Q=hTjV?N+#5zfwb0D)BTr&f4r!3*8D(R%X+Yy9j^3O=SQkoKZ&EB=jm`;%(iE!(kBPfQ%$WN5FM* zx%Q1#moxmPeJjhk%es0HA+to7r6!bNNkebNU%s(??OU7+6ZDa?2A1S!FR9E{oP_(L z9nXA_j7xl$aMCg<%Z#t%D5hq*IW9JpESmh8&n$eG5dm)NxJ;_0xfn)YSyz13hPj5L zFL`SH7U`VUhy;46lN8HRchStV{DHZ};c(mvoFOQE@w#e0zwARIsCn`=t}zuLdvfl+ z0CY5`Y(K4nVb2^B-}V4PHA-@`1zYjo?;{4r+@h9}yLIkF`vQaYS}3QsnMqI(3&-Ic z45kiMKJA@Z%pSy*@s&GpNH0Jj+H`3;dC2G)c+L~dU8mA1=mH)|&XZ^H#_xCrXR_Ml zwqJA!;-)mlNv*%xF@IBOwd%vz96kvIy~VAK8_f1!_SoXZeSmBd{GRCPvGM88N2Dkw zzUF{SVBn*HQ_{rYzB#PoEH;m7fJj}Hpd|=Bl`Lb+BQS+B7c;>(=c^)bw_;&$hYr)A zhS}bZDwfzgTQJwnFih_wDOiX-R>1+y38czsG2-5K>q*&fQ^xc>sDKGU;bhlLY#ZsO z_>@Xy>5PpUn%L=DtnMu-$6zc#anhp`XTcqeIGK`>sc^E34Q4}FcAlsC-58$YBlnC~ zE}_6gAdc4QmB2IF@|o~(f=K_O!~(xexWO6?Zj0rnf|cXN07khz8zKx3+i z`u#y<0#TtcrZ1e#ZrV1Km!e}~{mFmVuIBzn#Y7F+f-7e#X8A~eCVG`XOk3TSLd|N( zK1EK4cM$4$uz40ZnL}HBzZV&ez~}I9 z$rKZ3fFx^VrFVsIAziUlQFLNT5sClxZNS})p)JLXK}Rf=1NCnp8ExMQ5}u6cNYS8n z+`6@#{C{9W)443gc`8vlKW;;|^C7D@p#SE!A8Jqp-U?}zd|?G4J^mF>)?n@VQ*HS> zGWF4vt{0>22x3Rz^M0h+LrSNjV2V`b0;$z!5b_IAIAS>+Dj)_f-wwP1h^Yo)0!d`b zH}4l5Vp|L>)zO|U?dYf)V+fplE3Gw*H}KO%*VU!S7G-GV<}oS1ND{44l1P6W4u8)J zjOjVvYkkuP``FkEz(#NOrKL}y{CER;p`z>2n*hvL6}q!{K1i=I+VybZvkGxd6r*os+#D%u7XH z9{jr@0_pSd#^fZ+=I!dfmlbTm%Z-c_SsWrljs&OLmLG04R->|eO7+@3rG+gesz#!R zk5R9FvZQab)fWK|F-vwW01)uG+pUMrdf2PB92g~rbqj>zRoK8!1Kn*gkM!bQVKQF| zB6ixvjeke#LR5QMEOnq+9d0LlgUla$^0yW*BM-(Lw>W0uf(EBnFPW*c9Ff9M;`)tu z3y`9`GdxcSo>?Xr=bk*G^HoHObE~$b7QPq-KjUke`xkfHXi6k7k?B?OJ4DX2nUQ$& zVy|;hnjD7Ewxr=lGu9Ku%=q;n_!os^q!DJ9gR;qfBW+cRX>*6ug4< z2qJzPc3+7W3};4NQsLJxwp+#_E(f}@$;w&tRRkAfurks&?`ojVr2etStg(U zKDrw~7-?kUl13Q3`1jA$KSlzftcO~5gufZU4Vr=TCWcPw!n@c-+>x!aF;*q0;y-50 zN`zskxArJ5PuY%>bI_G^xxar@j$mFtEY=GAED<&cS@~+{98WPq*;iqCv5G8O9366O)k5xGJEx9r_+bp5)mrkf62DMTi1U?AsiI$jH z{Vuy`Vv%H_DT_a$Z<3;7?7`ekMubYt+((gfjIB%E0;wo9E0ud_>|a@(GSEQj_!*Q_5{ho z+9^+kAY1+ckBH7DRUbb#>$%dQ6tJ#*dRBJGK;|Fk9=tHc%8)8nk1H-Aura_)D!y<> zdQPu+B%n2Lhl&AW+Dn@L+Te3d=A`Z;P=7gwjit(UIyU%{25SCPkTqYUB$0cqpPG9a!`lh$szBai>-g2&>57@fQh|p@qd3 z@Vx)ZqZZwipO8c(P> z5rE}d@E7T>wIkf?2bAH#IH`0qfgYE=8!HiU2=upv9r?|dYL5~Ft9=MJGea0T4jJ5; zIX6w|#S;(GtF)}?c&?^)B;#dbigEM$>cqqoe#kJDiz-@|>(hk#J1)>YdFTJZrg&%4 zof(Xf4tmY{c0I~e1*_@ageLvG4OEb%huu3V{%e`{BCo+1R|0YFC46_eIEv@S^gdOw zq8QiDcm?t2A&y7(*C%IQOjt1_8)N>D`{s)oNkDgX60$8H?-ij~{-cUweHP_k9*kJo z8CNo8UCVTh5uE8~2SQa9#gL9mZ#j%#bMPEa!%=VA)|WnX?=YBIZDn=|Ecluo12pa1 zez}35=RBTKoZHH*XTM~U$vEEy9kR*isHWZ1@NIhpWDi9-&qHh0vW2n+hhLP7x_Ecj z)#)%NwhXZ@<=W)yq8Q*QZHpa6;VW*rv#r`Lp!<3kyQx@~$$-tP8##3iSiC%3G!_L)AyUFrJptO4%M^N>gf^RhqB1}rl!qB7YbkEu?H^1gh)M49HCm$PlG&|Ht_;nEGx54L|K&}& zGNqj6)4Afd%)0GHtp=jb3Kt=N>3TiuKSAYsZV88*uV4-F?KPN!u*%YIc}BY;a<`hf zwrcudP~G_NOhdrCHj;fF=z&bqlA+j=>F^%$0uoYWBo4dzjca+@lF64!jLAO@=lUgtb`bH|TRIH^Q zscQ8~{&^NuAZzQcR-%|s)W5_NKJhZ%6y$ac`bNx=_M)&4=lVPnt81hrLP{0Txk0M- zHOF~4ZwGKr)y~=d*971Z;l58G_5>+zri<9Uz!GkJW8$#WJBvwBC8g5QiIR#1%E+Jd zawwYtHrf0r@r``YK6Fn+O?e*%{}eU(aPjU2y}dpvKrzJ^naEnRll$1QOy(h%0CK2@ zuxo@XNFErSB|f9)s(ez~ir^?+CouO0*AfHI#E3@5P#h|FQ)ZfdtHO2j3Nw8U(-CbI zYe~byMrg~hUs~MHT3t29jCzPX+j}{;*}}5H=w9*el0<55V7P>OpQT_WnJuOF+n1(Z4u`9_UTVb6tzVoNcjCZX$Hee>=~Qf zk4{Hy%Egd~UA#C2;n#YC-I1S2aR+i7HVLpFQMN-h$70^GA_V}!cm>(KcsHDmC3G@L zFWG5>pgT8FQpb$a)@pom=Dw=lSc0}3Z;wJ(H6Mo;?l~W>MkN1$pc?*lD&wbUFESg~ z_2O35Vw&7l=fTO&-}U1|NkyB?U{t3HK7RUpQ#hoW1cmGk$~Atm{I(N;hf(ElA=W3& z?5b&8l$G)$bw0A|F`+wQIcm*3f3F42mDZ271EgFZz}V=(V>Cl30{u>jJx)pBr!GWs zquY0+JN*TpIP_(H5BL=MWLX-|OM7o8Nw6{YQP`6hs@%4AjC9yDm*;h&ggaCFDYmn>daGeYH|yHa;BnQ@C( z4652gba7IIH~Q3DRSa-$24wVwq)a!Jo%aU6!Z6u_p-j>xJFIRxq)IJ+Sc+)X@uV0Y zfvg~?_)#JyBy0@VTz9_id7E;+dI{{}huW0T0>!cAaHVAO)Gs*2w{+WT=>k`KNS0Z= zTm(7suMlC*YO7-*w&4WckCBh@{P-+ zh>v}kY>I4iN1{=#T42b>49k!QQHo-(p8UA^CyJQ>LtmMEn(iK9zkoEbc73Wc{65Tw zXem%_^Ui`!+Vv2?-PT1Mb`tKyS;FVPP~akNNrYpRaAb5;SYZK)_cth zNR0e1bFIgRjkWTBl2EXOGGBk_`(sshKc&%N0UO!&d zh;rmp_CQ0D>54*5>d4mN;~=YmvGjlgkN5zH{LYyFu1Z#B3bQw3)Yde-(+@E*dz98+ z8Q^YNIN*D1yqB&e1T;LZ&ZR2#I*@N*E5CjT&>uO_dvqh}|8RkXcHX8!L3L8$PQ~Y= zs=jwzw)Vq11KrZ&9uAT6!bCRlWgHNWz*p$J^Z8f*mT1untF)5vcUr-t%V?V@oZJCK zn6uDK&e3`+lU4`ZP|CYPS-ey znl&c-X!kxv03=L=EmK4M%t|m8&iQUNb+(wA?-AJuZn@vM> zO`7uT^FDS^*5Xb>tT~pGY>bm&u@CIfDp6e-DNXBNv}@5_kN>ybM z)z0sf!Z$K#JGsf-L#tRXRmenpEC->4bw%w2_uMwM+DlV0zxNPzz*9q6@YFHgnJ4iJ zq6jYOX_k2DvH-dQP!Sed0?`2KceU6=2zYy>&(fOuxZ4RmL=(czD1JwzrSqi%W!q|= z_$7)@bCMNu`3CQ4o|^3E|4QAo1ud6wu;g7F1yH0+P%`@hy3hzOcmyq}*Sn^zo07z~ zOAC-}x(cH5*?O~l_!>X2;>FKz1uG3VnfhCtySlrC6ZY4T5m$$c*NP<>og^#0bfb9u z+Jk&(8w!3iNfuav5mBboic3))AdxgiLR7}8aS$zFr!C>jE;ni$gt|5f7l2LTmjp+K z;1vRW#Qgo^FT1(N??t)xg$$w1yw`NmD_-(YFVUv{GeRE$j_VV@**&C^4zD3n(7Vm` zaZz{9nWv=YI6P!Oj6M-j*1;c>GD1pXn^C>NY6Uc_S<_7&59dK`cm}7+WWiYG9ucv~ z!(l-WjhXHkU!&lyrF!C|cA3)ggiOD~ZR@=qdQtdT-r!MRMECIJTG0`U_P+BxKo<(` z&e63{U%lSuasKl~{l)x)Af1x&3%2dGQ6Yd*LhW4Y*3=Q96s&v3qsuucJ0BBOw`U#l z%ZTU7?kzRHnMl)$t9Tfr0VliBQ^eF$uMBb|-Toa}&-ehWuZ+1+H(N@r^GK(-T4L&T ziuQ?j0QVuZgs={9T#{ve{e&sl)iJU+MA_1dlg7!xcVuv<0SyKeEeeN!JHJ*4+^~Hl zXiXaYw^6x&+yla4#S)R*VI^Lm?W(O5Y-nnaM*(utZdk)Axe|xFj$8r4?;~?sU(GXH z9;AgQ?>@CZyK0SWnA+JTF-B5XltdSpb-yV~W<;dFp(*KM`*8Iwu!iE<4GzsTdFCX9 ze5CG`0s`XcUYAB_0kWtD2b}l;h@;z~5g)qr28J{yHyii+G^1kQy>xwg=4YP{0`p+y z;|x8CbkO`4Sn^(jwf>SJT6l0ioI0jXT8(PtKQxu;nGT+E7?5EI|3kBf7Nh}DfqHdDBdo&o z*3?spNysjLSzxNHqKRg_zxRaeFU^PJl*MoCD&=_F*8{!uminz8@drzrOCN#-EpL}y z`JeBFtsFJ1WyHn6U!y7;B|X0TCSAz|{3f#Fa=DW10u==`p4apS?>!Gs+Ho1Uk<*FU zt3F{%B}G1-2Gfm{JcgUU3~=|^{0o=UO>m2!7c0c`vD0Ro6?BWjIxNBftFG^~w%v2# zMq6@hzTC<%h+Odlr047#l)BIvC@v`9+(_kr>(L{M6yN-%yO`4}y+*(1gWR?}LHlir zT-*`VECvpAQ*2e$e=YBw)1P!of5@|S1Ws90zYiu04yE_DQFhf9t{_iz%A_{a>`Bu# zymHh9o_*rW_D6;_oYP~1=)wo8J?;^t36s+2poWzVL~rD@uD0L^Y+aL(inNQ%2Yve5 zYiqvfhH;3NHdqZMM)$9p`ZQc~S%PWkk}Uo_*b`tCZt%`fzxX7IN}~!p)HdH87*M2m z9cwoHe2$M|7X~?w4j-q)8j9J1I4AFs1wvmR{y3+G5TYROagPOIXtM4N9^cV+&h0RO zCJk{H8KEU+ReBFXlR{Q_6>SuCP~$eg)JOxTIYs~MfWanfR+6%seoTUeeflqHjzLL& zaPnQC$hYiR<>)VWbJ^q{HAdsR=DKa-mv4>N(Q}+h_lYGc5ld?KCugt}P33376|;{A zm>u^{Cbq=}E3aZR-KxaMxOEQ5hr|)%?t(V2x-S@cfwWw_6_ANcFX*5vbCJ8~6yF=hr=UoO$`@k)-Y_OV*Jk zr;tDdPmfZIv_Q$&gw!01?Mk<@si>jK_9Lpv=hN5cQ}kb-Zl7M^WO-pWTIMj}v;Bvm zh(O&=$2pl;u|;sr?^&EviC9_Tm>ex?=h~HTtYS_=-KCz%ChE@ zgJ%Tiy_$ye3?mpI;9k{q`RS|I4LqK$#LQuvAgIZw)HxJ@A6&|)x_HV+RlU0p^K{QY zCSW11`aq=%#Sb8V2RbK&&#eAu@fgE?nt`Rhp}&2$Xg(Xa1ytq z;9b?6(oC6|0#3_v`%&Y6VC4IXZi>c=NnHj$t~Uhs%c_>?XP~)&};~Rn(Yjic~j)ZX8j9U zVqmK?5KSW}EV~kd5pPKo*P$;)g}CSRoEs&ISn>{#92l7{ zvQ!cUiyUbdu|`}Q<0jzgYi2fLQppuQ?eg0X%JxGOitri4-OwJY@);7&@; zn{ZgomU@;CjX8cC9bjL?nc__C9??U{LiPZy69RPZyIND?ZKdxVCL{9_Ks$G7SJ?%S zVfTQs_eXi7H3%Em@0)lX!s6il_Qy}}zO#|SBcYevqujREHT?@E=ksn=4B;qz4HysP zv%0SvTN;r(7 zWhy}?ccNHt88yQ*dGRIPp3$;8_(ZwLIznBhG z72bdaimM=!Tn){@^+NQYP@N17;Ch4ZJA9N+U8h@})=v8N&oo662bH4Hrf+{V{(vkp z2*;kr`TNFtAp#RcTYvTzsdG=xi4FMm6pt|099BI#lBZjkvw6J%6W!GCu(ta4R7Xv$ zbGbFNE+vLowr6I5We-$;vss^*yT*&d^+a&U*6jHr^^ETK8q$UQo$4vre;t$La&}cb zjXxtsC~*sM+H+)ep6lV-8eyFm2ZRM)%8T9AS;}4H7G6gCXp2)|P}vYIWno{AI|X~?U?^GKALCFI& zAep03;r`U4&a#h9upPIAvpRclHt7%-M1!&*tmh57c|2^o{9B=@AJC9Q)>tdt)=<6V z)Fh+$`(W|ShB8cW0lg}(_%b;nc&11}uk>B%1`rj?lfvS3bu3r8_2b38Z0ylQ`PAl? zaVekyB5*R$iq8=9zrSJBm@Xmn1wLUq2qZc~t+&;gkzVl!kR6I*vX(6@r3lPq)phA+ zK9=1gtOh|nQVCGXP!r69jEh{3_Q6b}XcoJe0P8ODo&YXkG1a%%v@l#v4eD9HViK0G8LF2tw-bwd55#qR{y(Aa z+xgYnpa=`hH{Mk}Yh}8WW90<&X{>msoLJj*5;~SbszHe9{k`sJ2zxCgXh0AOx zt_1h~#0O&bP#;}oV5RZA@VK&lp(F^|6GR5AGDn^!LXDv^;+qOMmkZ&Lx1qnx;Fd7B zHtn=#Qg1JldK3!XYW0<7Mg}0_cKLCPC4=r<9_2D1# z8?l!4iS*bX$(p?Bp5$083&k}5O8gGe%{jorax>SboD;uZ1+e%K?L8kFLjFnVV&kPY z4Ren|MqBPN%|{^HEsskH$Z!VdJyov+KA@+iI|Na?Jt#|E-OnjyvvJ%_A3@NZT(p<) zQkPH01ft4QHe{2S$9gZMZZd8t@kbWqR`0H^@1^FhNJXQGuL@6vT%R!9?01#d>Q=pl zHouzcIC?<@n{1o2gzH;Pfq|2~w{WnNMD%7(yE44_!;a7kEjEu$jSV8MIrPQ&@DIWDemqN=ysKO)*{3I`1pBhpx4 z^W(lqt(<7E#;UQe=Wyi$Igo<@p}5IAfC5C-kt#tp-=Fk;LQwcyrIshRhJz%Yzi}~1E=;^w24Syr3fy`g?f-q1ZsfNDa!W-0&EvL&6nfVP~*#9Fyei zvA~C;=Zmb*6Pg;NNR;Y=%&W*U!IOlhG#**^sMuI3HR9O^2ktgx)+_JFV0B9imOivQ z(xQk(=1r4hOFp2UcMmAcAr}SD26n9>YT;_%WrZ4`!)eE$s+_#GMNgi8|c zBLD=rq_>ctpe4nNF(GW7XEHU}vRZFRuKqZ|zrOy}eb1q@Q4i9b5nvBP)qqPIx+bjq zPAKz1bLD}kowur}zDz{rT6)y|=Y9nT=nEBYH&QV3R%>G{WVzWIC~ih_M4?*o#lv-cXC zwN=Ptzs+EL*#+FztB-qeCM#)=osS3}9(>WSv%&_Jfad|4$NKfSA^|VFAvyJpVu+WX1so+_%3$Sb@J96n3P+#9!pp=SanK ziaN|SkKMdkVj<_;9?@_B2-}mcQ6!EF_&YugVTl7}^`s{iRZQ}XiXEu5{2`FSlzQl9 z(L%nfx^ipSa<@&xsLUsHMSql}0jN*1zw2?2ZqlL=4Vft-QIfPOr+q3Yv3FsV=0j9i zQ)tpDG^v`cfUsE#O0MfI`K0c$c$~`ty<%)zm;_yjde<3Xa}m4@B}BQ@gSx~cTJ^Xh zuU3^ipo5l8?UM(o0;Lm-7=pkST4LKaW}kJ^P)S<;m_x7BZ~>zHfj>z4vjqvG5B{}~ z1;R%`ppyzX-&GG@mxE%vFdsUY1$P7?^dFLgU7_h*nJ834Eh$><{PLV;9<)wJteBlU(MM< zC?*C^7b%>;Kxf21aovT3YFdCC`lhb=EV%n`uV z3I}2IW|4>ubkqcf7?C zqw>xjypLHP_+-Q~CDl~8gq@Pko~mH4hPxrqwGIx&dibcbKDq>5D~V-&p|bpJr<)R) zwLqvQ^Q!r^L>Uf`WE8(EOMF?`s3O|{0VDFC5)e|CaaCzkMq14%YOcWd16leR@eo7) zi8(D?1ffa z;>N+`Y(GH_`_E0{yc{NC04QVa8sVP zP1)hYfXQSkNYCPf94aMOKJBlahswwuzTX5=R!SzQ3T@JmDEo>AY(v$%fNV>vD%7R! zsy%bi_&5NqyyTDvq>zOw0SaU5{nqIZ^P(hv>UdS98N-+IsB^q9r3uMb5Jhm09LYoXdJ|@rL~Z(3}finz{jQK zf@3_6^j9TBkgSCL&fTwQm+w~yHO~QG#a&pkaj%6hpJ}j9K*0OkUbF#yUj;_jgGKQs zt7s|{FuY<;l&%t$nLKcDhu;p6)VIc>{E zxo$2`TH=>0z`6>PG?}i$L%t4Whc)wcGYU-(oavV8ovo(_?9m>hUEk(7%?(%KP2lB3 z)@=3eSZ3dYqMe%74s)Sc?^%0Cocgx}?eEC1WU)u*E|rOw%%m1r+5x+l_$pz(#Ja<9f?^sU85>zU*en)JDrd6KFwAjY|T3 zC^@7CrcxEq!@9NFSHz6#E6z$7b+(Ls z3&>C400S~eh2VE;`L*+88UPIN4gl@uA!9x#1tb*YU|mV=X#SkpL?Xu;G@lzkp|DK^m; zLO1k_Y|7!2+m?#S?PWhu5<`fezXe}!^f9t3CL`NAdax+L_(%tbbP>-#{|H_$5n%y2 z0*iJRk0_cbS!VPV`lct#rDz$Sm#U(%wiOSXw|QxM(DQT$<~y+Dm_i$$9kDg^i%vUA zKs~z^$A#@_XSM0gQhroFJufT_z4tcyyEY~{L=G@oQ#@T<~_JQ~5ssLlyYagCe z`*$>&#zeivVOPerA)XjmkS@(Nkvy|FOvv=ArB&m;!iCU0jnv)$^$&J)n8l-TgLgwK zIizi1r(0lRgzM0f?8`3!#&)M1R63bUNxrvZtR}_S<^sN^H>WirI9d0@!o5!L338s! ztQDjp(1yE0g}sIWN=63vwA0bCV_{>Z1d4C^@#a&LjLS=Tk}YrzkiaZ&J_ zyrq%+_oNiptNt~#4tbLomThz^-e^9~`re+WBCgPdST3GURxPrpN_WgANjLGZkzGVY zD;|78-)tYk_t_git`C3x}J#03juP8tSN>$_O zjGX>@Qu_pB$IjH+x+-|v5YmHR{|}21(dJK2=n> zxE3Tjhk@CDg21)1!o=43=W$CJlUJB#h=g3sByN;@AxJ5nOAF%lo;4zNxY>J^yQjz* zxb$v#x&flq zCdIM%6=j*JEQNVGus1t@`|4Hn3_~TJtc$A)u8EQVIm3}ex>3JWGg?qRrvgV45S2wk z&=q^Av=@xrE`WmB*QQW;k4j`%Jp~eN#T?Yz7~TUgg_rSAs1@kR;JYpSOO+ z9G`nMhJZglZd&`-jq~h6Jkp4TvLkHu;d+PI*zc>&+U~~qgIv?&-bj&7jDLBz=HbL>WpF+oS2F?vpCAjawIZusY?)bST{TzK9A3D zafYXuB3t2l*&gnpWJ!f*48Fu9ULMpcBjn8V;sAF zk8rU19&?_P1UJDlfg}#QoQz)X)U5AIFE_S+Z|1|-w1t>01%W?FnO9SjSCH5d+_G!> zHj`iiPG`I1Pq*%1&~te-8EEKw1AkfstB$^nFdlT{IJi~2qI@af*0yS5N;`b@hUFZB zMbW4>7=1i`NMiRGZ&Egj{pIP8+BbIWp?wjWNl9g_8EErw%E{~!+h(SBkmgO@IT?nx z7!w8b#}9;ip}JrFX0~eY%o~rbZA?8`0glYdGLrHzH!_f_fZHXig#u?eNn=+{7Ea=C$2E9QA~i1eQOhud%om23#UdoqU#=ls3t z3Z~}+iLHi}T`5kcp$QU5#TS!sz<>YiO(7y&S&$sW)X5#NNb~*+ChTQ}+%-F!sqaJV zv8nZDi#y5^m-uh1%i;0#v(o?Y?y6o(-4|>`hkgH_`)fDo?=K1E;NkaiuOLBqA)4bU zW1P-2;|jmxmuM79{kSw;@8y$Iiq+0dl{Mh_ucO!`O>Rgbe!bOGHr9pI=sGnx9qW`{~<#Og*`cpi7j@bpI4J+vat3TVNi?mo5q1 zBz$oID$Ay+z6v5M-2mGMOb~3ApJHi)bS*V&uvSlg7_XBPxmzwXQ|DRhSe-PX;-Ad& zQi@uCoNJrH!0N~&f@}ThTL(AVyBmyfRv&<&KRK;jTlOYP_xsN9_!ppX!)4fiMXJbx zk}~uHHMGSd)$y3-Z8epJGP-9EZ0Ur>(G-~#Hnt5t-|tq8h5nVKwrT!mb`!%ZSE$4M zNxs+SlOjn4C89#cn|{gXVK;X`AJzT~^MYVb2f;i%HeN56vRa3U_DwFunk@Y(c@=i8 z23c!rEijZ0pH!fl_cs(tiZ)LiN`LQN(CeYk!|pDnwZjoHCz~FP8J*ukd-3=N6dG+= z0CDHJ^f;J1kqm;7bCTX`KXA@Uay@zI-?)~%mY&q%LHdb2j*e6ZWBWJ(V@Y5%%)vfm zpV8wnpi5k|-`tIqbc@yXU4E37V;$fPD5~06QkPw99zu>B>GFxt$S8}Y z>W^38pdOqRx_M|PWnuA$tIS#P2sClH+uBh7;vY;DZwQgW+!iJ+<34j-QWrF{;*0d0 zaPBqXAgIsrfTXk1UcWp-e~)sS3;d)hYgo5AJQi z+GHrDYaF{6DZ}#AnS@YVoVm)0`DB}$6G4CWlXS|Lf$^UWmXTBRk9!cfe*yvxN+W`k z{>cUiBZ~K==ggPx2hQ+&Y1=$$!VIlqhl|(3uuG(e9QmX4y~OhYH+UO@J%w}n**$%B z&$2i}c|DqNAxaoB2f;BJtA1@DetaJR-O()NoWQ?z(bcox`YU42B%py3n4WAa6um#3 zza>TG%!BNl`K~~?1O?$+-2T^(t<^vqR-SF-SU8-q76baveZP+T3H8M8{W-(N79Tr| z##WN`Fwn+_h|PjBkMWDmf=Ya_UhA?-BhJi`Re7^^C$u*5}j5y!A_21s|;r z;ro$gjfQmTl>!ko z{0+ov2ASbW6W(HpR3gmTwXQo@Z~AQ9`pp~Bj zHL^VZfARs=7bDZNR{>*EWK=_B$tJ^Hi(@YlnJuhq^Q1RB<>3Ixae$^D%3yt3(lPir zidjibmoJI1c8ALkx&1z|j4YrH|Gn|6ljT$2ap4!N1hcu+SXCGDC%YvvagMkERW(Gd z5}C3I_tbSqk{Q7IznynJ^|e*!SfarH|K6IZ`zW9Y?=S>FedO9mpj?ZpZ;XD}-2hei zMf`L4u=x;-+;?uL=xU$AnT?AQDgSTcY$L~DO-yp`xl0r!t;{u*cZmFfyd(=Fg}Usf zib+Lzi9Lnm^jkP))8{lsV)zC`1c}nudij=q(5L}Ld z+Xu1nF1pdPm^^SztAHrEtTeeqgKLmOek3{%y$SpQslkJ=4kX!hR57m_q@H2IF7cVH zu6uR!#&=ZI2)_r7u2jqB)Uqy--YALG$@$^D*Q3Q{ER|Y^O4AX0%+xW~2!b?7Z0aADzWywJ)x_6Nzb3usLeCK@!yaNG`rZE##9h1Adi#3`8giQ+ z3kXK8Wgv#Elcnv#MZHxmb+7$xrI>(E&fE){OtT=P?Ir8)K0tOAhl)V%Y!65IOdGzp zujz6enUNYJ54MY z{uC>NPLQ+24J69-aV3Gnd)=Hh-8okpoS+s579R)fwWYDI!GzYnvf(VcImI=q=_t-i6{7WtUmLE`;hRX@ z?%~osRhy&6%CV(cJSmmkjAlilT92##AfP1EHhrooTcRQ4@BF>2zwLiickV1}s_Rwf zt-`AiMzFl&EwkwUZSB{jTz)%5Z7kx@n~xgNS%k+(1o7-0;FOV-!HX{46x>iVhSn8? z1-U^Cg%uP6{egZg;Poj6Gp859>LKuE(l9e_sgrdOe;UM;p(|>9^qud2-Pi8# z_&s#)Vphj+v~2>veI96{hYS>VcRT>!5=QiL?rEuec#Hz#V$NEC87Cz316_uZ6m*)4 z`x$2aYfJQKj9IZNbiN9mw0${3UFM;ni}T$R5~P?ACB44BGkM*~uYDxzk@);Us2R>i zjq{Q8I`~cwL)V#(IG)YU@`iSNkA(3u+D4<@RpX^%_vjD^iNcPEE_p9l40(51O&0or zBXs6+#X9mXI-V-%m}XRee0r2mQ~Gi*L>EJe15F-xOhMIL4BD)O42T*7SKu^@vY0q1eRapXOL%lDsiK3Bnmg9s>FI_e)#V>WL*}w$)=a``uL(huuSx-604k{<}^5*g~=K+kWGH zo2Jfz?Sg~z4_1*NU8vCG*2NIT1zcDqy|3VNLJtpDFSAqGQQ_U!zPmFdnPD01KQE{@ zlx9HLl#PsJEv@sZD$u4hT@9PHS6K{BhzlnOsX9q1)_Q5pR2kYB_+gK#S$&PE&P$OH zMGJctIahI4W?BH?w!Pti8d09tZA9dc*shs&&ihV7|UK(o7-mB(74I864xle)sr`V%(W>aH2> z?R(`I{0A)J+5R1U)pVm71dRLBKdcY^mU0I4H#R%&M4E5n8-u4&btE=g)|QJMEA(j? zsY+(lrHjko7XUnlao}D)u6mRtT;mAL?uIDq{L+l807;{oEZzm8k3>hs?aB-_uYMlX z$AesLvoG8>rv0sQ4zy`!@Uh}aE-5?lr#rdd*oub}=Wok9B0k(CX14!3^P{NM6fvx_D$yd?Y{QzXSzJ5v-GCHwitv6HIkZs_zSQp-0*66!W?c3>57yC%gICqrf!DVt#n16q5)mv&& zM%t5JKmUp+i40g&>$?F2myI;U3vIrxP`F^$#v8jx73A!ZXTi zlV5qK%hS4`7ehpO55$i9+lsMCUsyD)zGQ}dz|Oo^=5c8$!w*tCKhTDQvg*8Ha>?LF zGa3fe{uJpm9;FW5uroCWbH*cP?|1i!PE6h-Yuke#MvpkONf(rAqNZz8%WAGWD_ zDqen(VkgpOsV%cBA&HkWf>xSlpJdDNsv^^* zgk{8E(?|HW&%F33_i4P}v-!EOM}bAgA;_tc{oKTiibX47jds-cZ44IhNB`TW5303-LqL?2BZ zMow3_J`!8zegx_V@*!UB38t zg_F^N%93M$gRG??3q90QDdw;{hq(RK}fn1^~qxq?Dk!-|M1|2ueIVYNv&Bf%M zc!W}nz#dFGz=~+2MX>i!8l2EFoGj=Gk`+HeLv#kW!TD@i^)@$RGnnd?!kuSdwGhh3 zF+@IvkQ>J?-fYxDp8Yk%vUi&DblCiS{upu}rxjgJ0C$CM01E%6aZ{!Y&ipI6?7<^N z$w~hYuAWfVLAu5iy*k99g&3GLnTYn}%m&(6rgj)!65+w1!XG=VQjYM+M9 zfKF;8P8i%^U%^MQFXBInp&JK!exnSLA!6x=Jdtv-RQN8vC~5&=cTPbC znONs{MIDr+04Edm@q>gQvBXZW9^?b?$)&QjHrkHUo0Wq?q3AwD_d_$KJ?T7S=d+M& z)L!Zy`-9f`H!WB|7K_FnAd);zb-Q`VK+O!N|5O9=8+$`_;6(^%^sK%vyk~T%$qQs+ zJmT@V2clHPyq?9ACo%ZqnN*>&h9&6<$9B>!HQ z!zT9luaOo*eThEG@_czUHtmU=`C8gGkQ-X`cI z0hPf`b)yJW>>cZD!U;r!?69!a=cI)MHhkW56Mu`T^x={=5au_8 zxKfuK;W&Esk6y&;UCV8qcvGs&TR4r5*fH>0RHUU?R{s$eCgHx)h^e3YSAYKtE&mJT z0N7!Pe#qGbdAnN;#&XG|tB^lBo2%={pU5A4+uBVyRG%sRE-J}Qkx6r{ zIXA_F)Y5rLOurL1i%6|LCHV@QvMW$ww-BS4{ftzwg>F(~RtiGz` zY(`>5!T1Gc`K;LHB-{e?=?io6-kdS-L4(aa9R6MDmkHq?~mA z4kN&#nU8|ST*bQ_aTP*vJDhJ6E)(J?=L!{PemMVW?G5459qR(om0>zwKLyP)G%lvI z0KIgpEkW1H#x=YNjK2Rws%3>h*QRA8!&*oa|L53in&O}zHnH0x@(dQi@*SG^K&&5e zJK5))JWY#7n6GY$$fZdA-;S$>`K`w&`uGEftLKWVQWzYQERhX1A&oKpJD4}+dO#6` zUzBhDm2&mAhzD&bYY4890eRWys}2AKicn}wPoa=ZqPz)(bI^E8~uG^Cf~XmE7pqm{?UdDmg=Z+3rDkC{gs)B zn5IATZhNor*BY^^n}F3}@0;9%8fNI9<-6TEXFQieXGf~vrp4>O>SLqj)u0Tqj7K+S4Ls;!SlWxM-F4N{ zM;2+pYdRa1K0^IHd>_T;ZE^X>dQN*PasYU3BZ62*}G`^Vy z+V&b8VjVKxjAa-MoslsM@#P%ecDkuT2UywmGOh=G+=gswLOC^Os5gmHk+ZbNYyq$3 z1IbtEbVg?wkg)_j?8m(#7;DF8BO{YFeDzLw<-tCxjc^HT{xs&Thv@g( zF@0EQXJB{aoNC&cT8cE_X6eIGm4juM{F=Alr@%=?pq1mCT&VPwSBPJ!o zvqc00)_K(Vc4kH9tZ{)QL|>9AY5xz9r&mssWRw z^ru?(LN)3$)38hyT-HrSz3v&q1y9@gKB?P!aE!6tHt4@)Dv{bO!APNMC?P*GmeB=TYE$ z)Xfq0WG(#-nu1a*gO43n*ax{tH@RaQGz#cc>uX1Y9uct(XB`thI6E6c0ImRbB`=CJILK{Gk#Ld!h=z45Wd9H}`>}dSm68{<~@^I<68kDk1kLv}a!&ib|oe18(jnnt>iX*Fvk( z09E(1`Sp}go-c@u}$e_fZ68jJumsvpPIlQ-IDG-6h^A>xtCu1L4q) zjtfDBg_l$ZbwqQH0K0)sqd5SHx4d>!gYi~M8Wlwf%EP21?>inKRjrcoNHhat(u+41A?K?TmCU! zeZk)d3#)NNTx(irL?rvS8B9h05rSkjD3&GK6n$hA9R#Y&R-I=c129Hk9Z^Zh4fGGs z0<07xEicwM%y!$ZE@j#@UUNjfU~(EWGaf8%=qIVPr^eUZYx({cg-G@1Zh1gZVR%lyG^j zxQD|U@%Vgl2SNb|S~PD-#(C@Tu6*pW zemT6zAvt?vDjw@566vfU%nV)XjaJ^t@fEM!vaaExtkbo?UA7A}YnHV*7 z`)}gSs~edYk4}lANsgcxyJ^eCG3sQ%^}W)3)IrJ=y8)du@YcIjdTbu8(+NvnzGR^o z*5(Oz(T~Bp42T95zvM@muUR7xN%X2DeLtYanj1_Mi%q8oNi)eZn>%eFzb%5}{u&?3 zb^{bCw|9iH(=w^JFZ`txin73X5F7|FqN|@6he3bzlK=i2EB+i%lb$*V_@mli9|u*k z(sUI4j5M1xz51jx4JKLnV>nrT0Ey4E;!U^s%T#-QvaW{SJ67APX!7b=vzIG*NW_rt zVk+Ok$G;rf8u&3rQU*>467j%wS(q$5Y>?w`E$tc5uRNE9FZr!qjR;RBRhfkA+*Pmq z7ifSUWoxO4ll~thI9Z&5yU7yUPe`uM`6#Cmu9IC zJ(n_0f|A$|X=|b?-U#BL8AYl45r@Fm zxK!}&>m93T3($sLTpfX&5hBNpR1#^hfSt9^cTXUW1}&B{9jHh+{TwT6MU$b)(hBFg z|I*x;6ksG8XQsP3|H^cRQ=?6B70La-RTY@2O4E5FxJLAdOdwUqcn`&gF8?QYJI3O3 zU;-{$<(U;0XINnIU7c>7%fb=a`%$W|l2cfxQ&d*r(UOO1;)atg`WHUL@K0WanpPHmn^T(SFLZm-YRV4MzCZxCptI>y7CtZ$uM6C_jt;O5dK!Y%lEjS4^5cZpW&Vw`K^LU%!R((JAXL#WLe*SOMGo00LT)2ey_n+OI!vdKdlPlOOqO^`>+a zjfsJ?=KU_u*#Bki8V-9YDERn44_S|&?K7w(`*cKkz=a8t_86y0uUY~kLnP4IyOv_* zbPAiGS>-KLBk&dFX+{)Fq)!>zYEm<%rlU_ckrXQ6hsX0!(Ps5#C}a>}YMv$8!)g8$<8^diD~ zOu%d<`kc`j^M&kMZ1CnBStD~_;>cV)=R)S*XA?{b_B#!>_U;3QynzPAO%?wF!j%)# z7GOB&twFM;{~utn9JFxeR1&1i{;;=zLF&$UL?QpbcL&r5mk8Q5y3Sonfa29`2lkD6 zdE2Glp)IyHM!}5X_abUE$!@esyOA_<$E8{p{%MrYPk$R^sh2K3&c7sfIkRIllbIAQ z@8?Hx!g9;elSXIcCNz_ZwU~eobg~l(m>*+=B+Wo>^!r}iPzV(-L{EW0s7T*k`}(xY zAz4--$g$o|8L1Az_w3V`jDX;Ik_f>gq^43m?5u)XZ#O`a9-CwUK{524oycGGT7FRQ z_z5h9gPgy&zix4<&{}llnQ9A_L+DDtn6Q3|@CDXkowq?81FVHbV=;==4F<9tgH|m2 zyS30KfHh^(^7C=H6M#j3x$JK?pq(s7qC6ijXFB_rfMM^^Ez?m z!!KB$ti(k2JIyt%PcT&6gls=CNCCNhNE*OC6vcsb5>Ri!0^6)?wr@h=LqDuDISnET zHSJ~`GmMIHnni}Ps`)`L;n5dKXp=;gPFBn#n&9LpvZ-Ynl+;3D(~pec>=6(^O@kD< zeY+OI34_PbXSd^mP@V!Foa*#H(P~P5vDM+=y zJ?z_Vuj1eOI8>CT>zZ65mGLZS^KQ^njhhvaxZa5Ki9Tz0q59m*^qsF6`B+nrKpbUT4gjrVYhKJ{cEUCp=^6rx2~ sCKnP|0O~qH_XlggQm62BSX+vJNr%U`9~5YdPyhe`00000000000Ma7_aR2}S literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/shield_check.webp b/app/src/main/res/drawable-xxxhdpi/shield_check.webp new file mode 100644 index 0000000000000000000000000000000000000000..706c8979d0787d3af7c4c6d6592ceb3fadd340fd GIT binary patch literal 46004 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 Date: Mon, 23 Mar 2026 09:34:52 -0300 Subject: [PATCH 21/85] feat: update button text --- app/src/main/res/values-ar/strings.xml | 4 ++-- app/src/main/res/values-b+es+419/strings.xml | 2 +- app/src/main/res/values-ca/strings.xml | 2 +- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-el/strings.xml | 2 +- app/src/main/res/values-es-rES/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 4 ++-- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values-nl/strings.xml | 4 ++-- app/src/main/res/values-pl/strings.xml | 4 ++-- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-pt/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 16 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index c422599da..453b313a5 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -441,7 +441,7 @@ أعد كتابة PIN الجديد يرجى استخدام رمز PIN ستتذكره. إذا نسيت رمز PIN، يمكنك إعادة تعيينه، لكن ذلك سيتطلب استعادة محفظتك. تعيين PIN جديد - يمكنك تغيير رمز PIN إلى تركيبة\nجديدة من 4 أرقام. يرجى إدخال رمز PIN الحالي أولاً. + يمكنك تغيير PIN إلى تركيبة\nجديدة من 4 أرقام. يرجى إدخال رمز PIN الحالي أولاً. تغيير PIN حاول مرة أخرى، هذا ليس نفس رمز PIN. عرض عبارة الاسترداد @@ -664,7 +664,7 @@ عند التفعيل، يمكنك استخدام {biometryTypeName} بدلاً من رمز PIN لفتح محفظتك أو إرسال المدفوعات. إخفاء الرصيد عند الفتح رمز PIN - تغيير رمز PIN + تغيير PIN معطل مفعل طلب PIN للمدفوعات diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index ca8c7bd89..9d9d5229c 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -664,7 +664,7 @@ Cuando está activado, puedes usar {biometryTypeName} en lugar de tu PIN para desbloquear tu billetera o enviar pagos. Ocultar saldo al abrir Código PIN - Cambiar código PIN + Cambiar PIN Disabilitado Activado Exigir PIN para pagamientos diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 5de9b0ea5..edb07e3f3 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -664,7 +664,7 @@ Quan està habilitat, pots utilitzar {biometryTypeName} en lloc del teu codi PIN per desbloquejar la cartera o enviar pagaments. Amagar saldo en obrir Codi PIN - Canviar el codi PIN + Canviar PIN Desactivat Activat Requereix un PIN per als pagaments diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index b696d62b3..aad8ff426 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -664,7 +664,7 @@ Pokud povoleno, můžete pro odemčení peněženky nebo zaslání platby použít {biometryTypeName} namísto PIN kódu. Skrýt zůstatek při otevření PIN kód - Změna PIN kódu + Změna PIN Zakázáno Povoleno Při platbě vyžadovat PIN diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index fca4e9751..1619ce845 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -583,7 +583,7 @@ Lesen der Zwischenablage erlauben Warnen beim Senden von über $100 PIN Code - PIN Code ändern + PIN ändern PIN für Zahlungen anfordern Aktiviert Deaktiviert diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 9fdc4d61b..2a0e752f9 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -664,7 +664,7 @@ Όταν είναι ενεργοποιημένο, μπορείς να χρησιμοποιήσεις {biometryTypeName} αντί του κωδικού PIN για ξεκλείδωμα πορτοφολιού ή αποστολή πληρωμών. Απόκρυψη υπολοίπου στο άνοιγμα Κωδικός PIN - Αλλαγή κωδικού PIN + Αλλαγή PIN Απενεργοποιημένο Ενεργοποιημένο Απαίτηση PIN για πληρωμές diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 40dbb1df6..f47eaa9bf 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -664,7 +664,7 @@ Cuando está activado, puedes usar {biometryTypeName} en lugar de tu código PIN para desbloquear tu monedero o enviar pagos. Ocultar saldo al abrir Código PIN - Cambiar código PIN + Cambiar PIN Desactivado Activado Pedir PIN al realizar pagos diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 09c0ae2bb..1856665e3 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -559,7 +559,7 @@ Leer portapapeles para facilitar uso Advertir al enviar más de $100 Código PIN - Cambiar código PIN + Cambiar PIN Pedir PIN al realizar pagos Activado Desactivado diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 5a8e09cda..5e9f4d459 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -499,7 +499,7 @@ Afficher la phrase de récupération. Contacter le support Effacer l\'application - Modifier le code PIN + Modifier le PIN Vous pouvez remplacer votre code PIN par une nouvelle\ncombinaison de 4 chiffres. Veuillez d\'abord introduire votre code PIN actuel. Confirmer le nouveau code PIN Veuillez retaper votre code PIN à 4 chiffres pour terminer la procédure de configuration. @@ -566,7 +566,7 @@ Lire le presse-papiers pour faciliter l\'utilisation Avertir en cas d\'envoi de plus de 100$ Code PIN - Modifier le code PIN + Modifier le PIN Exiger un code PIN pour les paiements Activé Désactivé diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index a9f9090b3..9d75fe43e 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -664,7 +664,7 @@ Quanto attivato, puoi usare {biometryTypeName} invece del tuo codice PIN per sbloccare il tuo wallet o inviare pagamenti. Nascondi il saldo all\'apertura Codice PIN - Cambia codice PIN + Cambia PIN Disattivato Attivato Richiedi PIN per i pagamenti diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index f3c9a434b..cee201aec 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -442,7 +442,7 @@ Gebruik een pincode die je zult onthouden. Als je je pincode vergeet, kun je deze resetten, maar daarvoor moet je je wallet herstellen. Nieuwe pincode instellen Je kunt je pincode wijzigen naar een nieuwe\n4-cijferige combinatie. Voer eerst je huidige pincode in. - Pincode wijzigen + PIN wijzigen Probeer opnieuw, dit is niet dezelfde pincode. Herstelzin tonen Herstelzin bevestigen @@ -664,7 +664,7 @@ Wanneer ingeschakeld, kun je {biometryTypeName} gebruiken in plaats van je pincode om je wallet te ontgrendelen of betalingen te verzenden. Saldo verbergen bij openen Pincode - Pincode wijzigen + PIN wijzigen Uitgeschakeld Ingeschakeld Pincode vereisen voor betalingen diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 225d6bb9d..f50d68126 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -499,7 +499,7 @@ Pokaż frazę odzyskiwania Skontaktuj się z pomocą techniczną Wyczyść aplikację - Zmień kod PIN + Zmień PIN Możesz zmienić swój kod PIN na nową\n4-cyfrową kombinację. Najpierw wprowadź aktualny kod PIN. Wpisz ponownie kod PIN Wpisz proszę ponownie 4 cyfrowy kod PIN aby zakończyć konfigurację. @@ -565,7 +565,7 @@ Odczytaj schowek dla wygody użytkowania Ostrzegaj przy transakcji powyżej 100 USD Kod PIN - Zmień kod PIN + Zmień PIN Wymagaj PIN-u do płatności Włączone Wyłączone diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index d0eff952f..1080375bf 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -664,7 +664,7 @@ Quando ativado, você poderá usar {biometryTypeName} em vez do PIN para desbloquear ou pagar. Ocultar saldo ao abrir o app Código PIN - Alterar o código PIN + Alterar PIN Desativado Ativado Solicitar PIN para pagar diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index efff4b558..3157985e8 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -564,7 +564,7 @@ Ler área de transferência Avisar ao enviar mais de US$ 100 Código PIN - Alterar o código PIN + Alterar PIN Solicitar PIN para pagar Ativado Desativado diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index ae57a94ee..a31f5244f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -680,7 +680,7 @@ Когда включено, вы можете использовать {biometryTypeName} вместо PIN-кода для разблокировки кошелька или отправки платежей. Скрывать Баланс при Открытии PIN-код - Сменить PIN-код + Сменить PIN Выключен Включён Требовать PIN-код для Платежей diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 57b657b71..74652d9c8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -691,7 +691,7 @@ When enabled, you can use {biometryTypeName} instead of your PIN code to unlock your wallet or send payments. Hide balance on open PIN Code - Change PIN Code + Change PIN Disabled Enabled Require PIN for payments From 7bfc99dc4867564829bfe1b55b3b342dd2cc39e9 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 23 Mar 2026 09:45:27 -0300 Subject: [PATCH 22/85] feat: security__pin_enabled_title --- .../main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt | 2 +- app/src/main/res/values-ar/strings.xml | 2 +- app/src/main/res/values-b+es+419/strings.xml | 2 +- app/src/main/res/values-ca/strings.xml | 2 +- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-el/strings.xml | 2 +- app/src/main/res/values-es-rES/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values-nl/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-pt/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt index ca2f7fb58..d6c58f28d 100644 --- a/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt @@ -72,7 +72,7 @@ private fun Content( AppTopBar( titleText = stringResource( if (isPinEnabled) { - R.string.security__pin_disable_title + R.string.security__pin_enabled_title } else { R.string.settings__security__pin } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 453b313a5..e0ce3f2b4 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -478,7 +478,7 @@ يرجى استخدام رمز PIN ستتذكره. إذا نسيت رمز PIN، يمكنك إعادة تعيينه، لكن ذلك سيتطلب استعادة محفظتك. تعطيل PIN رمز PIN مفعل حاليًا. إذا أردت تعطيل رمز PIN، تحتاج إلى إدخال رمز PIN الحالي أولاً. - تعطيل PIN + PIN مفعل يرجى إدخال رمز PIN إعادة التعيين (يتطلب عبارة الاسترداد) نسيت رمز PIN؟ أعد التعيين واستعد محفظة Bitkit باستخدام عبارة الاسترداد. عيّن رمز PIN جديد بعد إكمال الاستعادة. diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index 9d9d5229c..99404a1e6 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -478,7 +478,7 @@ Por favor, utilice un PIN que recuerde. Si olvida su PIN puede restablecerlo, pero para ello tendrá que restaurar su billetera. Desactivar PIN El código PIN está actualmente activado. Si desea desactivar su PIN, deberá introducir primero su código PIN actual. - Desactivar PIN + PIN activado Por favor, introduzca su código PIN Resetear (requiere frase de recuperación) ¿Olvidaste tu PIN? Restablece y recupera tu billetera de Bitkit con tu frase de recuperación. Establece un nuevo PIN al finalizar. diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index edb07e3f3..930a6e2bc 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -478,7 +478,7 @@ Si us plau, utilitza un PIN que recordis. Si oblides el teu PIN pots restablir-lo, però caldrà restaurar la cartera. Desactiva el PIN El codi PIN està actualment habilitat. Si vols desactivar el teu codi PIN, has d\'introduir primer el teu codi PIN actual. - Desactiva el PIN + PIN activat Si us plau, introduïu el vostre codi PIN Restablir (Es necessita la frase de recuperació) Has oblidat el PIN? Restableix i recupera la teva cartera Bitkit amb la teva frase de recuperació. Configura un nou PIN després de completar la recuperació. diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index aad8ff426..3ec6dbb6e 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -478,7 +478,7 @@ Použijte prosím PIN, který si zapamatujete. Pokud svůj PIN zapomenete, můžete jej resetovat, ale to bude vyžadovat obnovení vaší peněženky. Zakázat PIN PIN kód je aktuálně povolen. Pokud chcete deaktivovat svůj PIN, musíte nejprve zadat svůj aktuální PIN kód. - Zakázat PIN + PIN povolen Zadejte prosím svůj PIN kód Resetovat (vyžaduje obnovovací frázi) Zapomněli jste svůj PIN? Resetujte a obnovte svou peněženku Bitkit pomocí vaší obnovovací fráze. Po dokončení obnovení nastavte nový PIN. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 1619ce845..7136efe9d 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -463,7 +463,7 @@ 4-stelligen PIN erneut eingeben Bitte gib deinen 4-stelligen PIN erneut ein, um den Einrichtungsprozess abzuschließen. Versuche es erneut, dieser PIN stimmt nicht überein. - PIN deaktivieren + PIN aktiviert Der PIN-Code ist derzeit aktiviert. Wenn du deinen PIN deaktivieren möchtest, musst du zuerst deinen aktuellen PIN-Code eingeben. PIN deaktivieren Bitte gib deinen PIN-Code ein diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 2a0e752f9..d2a7b5886 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -478,7 +478,7 @@ Χρησιμοποίησε ένα PIN που θα θυμάσαι. Αν ξεχάσεις το PIN σου μπορείς να το επαναφέρεις, αλλά αυτό θα απαιτήσει επαναφορά του πορτοφολιού σου. Απενεργοποίηση PIN Ο κωδικός PIN είναι ενεργοποιημένος. Αν θέλεις να απενεργοποιήσεις το PIN, πρέπει να εισάγεις πρώτα τον τρέχοντα κωδικό PIN. - Απενεργοποίηση PIN + PIN ενεργοποιημένο Εισάγαγε τον κωδικό PIN Επαναφορά (Απαιτείται φράση ανάκτησης) Ξέχασες το PIN; Επανάφερε και ανάκτησε το πορτοφόλι Bitkit με τη φράση ανάκτησής σου. Όρισε νέο PIN μετά την ολοκλήρωση της ανάκτησης. diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index f47eaa9bf..da65e13cc 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -478,7 +478,7 @@ Por favor, utilice un PIN que recuerde. Si olvida su PIN puede restablecerlo, pero para ello tendrá que restaurar su monedero. Desactivar PIN El código PIN está actualmente activado. Si desea desactivar su PIN, deberá introducir primero su código PIN actual. - Desactivar PIN + PIN activado Por favor, introduzca su código PIN Restablecer (requiere frase de recuperación) ¿Ha olvidado su PIN? Reinicie y recupere su monedero Bitkit con su frase de recuperación. Establezca un nuevo PIN después de completar la recuperación. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 1856665e3..13d1dc882 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -463,7 +463,7 @@ Vuelva a introducir el PIN de 4 dígitos Vuelva a introducir su PIN de 4 dígitos para completar el proceso de configuración. Inténtelo de nuevo, no es el mismo PIN. - Desactivar PIN + PIN activado El código PIN está actualmente activado. Si desea desactivar su PIN, deberá introducir primero su código PIN actual. Desactivar PIN Por favor, introduzca su código PIN diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 5e9f4d459..8fe4f6609 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -463,7 +463,7 @@ Retapez le code PIN à 4 chiffres Veuillez retaper votre code PIN à 4 chiffres pour terminer la procédure de configuration. Réessayez, il ne s\'agit pas du même code PIN. - Désactiver le code PIN + PIN activé Le code PIN est actuellement activé. Si vous souhaitez désactiver votre code PIN, vous devez d\'abord saisir votre code PIN actuel. Désactiver le code PIN Veuillez saisir votre code PIN diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 9d75fe43e..016188f5c 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -478,7 +478,7 @@ Utilizza un PIN che ricorderai. Se dimentichi il PIN, puoi reimpostarlo, ma ciò richiede il ripristino del wallet. Disabilita il PIN Il codice PIN è abilitato. Se vuoi disattivare il PIN, prima inserisci il PIN corrente. - Disabilita il PIN + PIN attivato Inserisci il codice PIN Reset (Richiede la Recovery Phrase) Ha dimenticato il PIN? Resetta e recupera il tuo wallet Bitkit con la recovery phrase. Imposta un nuovo PIN dopo aver completato il recupero. diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index cee201aec..2d02c9351 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -478,7 +478,7 @@ Gebruik een pincode die je zult onthouden. Als je je pincode vergeet, kun je deze resetten, maar daarvoor moet je je wallet herstellen. Pincode uitschakelen Pincode is momenteel ingeschakeld. Als je je pincode wilt uitschakelen, moet je eerst je huidige pincode invoeren. - Pincode uitschakelen + PIN ingeschakeld Voer je pincode in Resetten (herstelzin vereist) Pincode vergeten? Reset en herstel je Bitkit-wallet met je herstelzin. Stel een nieuwe pincode in na het voltooien van het herstel. diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index f50d68126..331a07487 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -463,7 +463,7 @@ Wpisz ponownie 4 cyfrowy kod PIN Wpisz proszę ponownie 4 cyfrowy kod PIN aby zakończyć konfigurację. Spróbuj ponownie, kody PIN się różnią. - Wyłącz kod PIN + PIN włączony Kod PIN jest obecnie aktywny. Jeśli chcesz go wyłączyć pierw musisz podać aktualny kod PIN. Wyłącz kod PIN Podaj proszę kod PIN diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 1080375bf..ce573db8f 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -478,7 +478,7 @@ Use um PIN que você se lembrará. Se você esquecer o PIN, poderá redefini-lo mas isso exigirá a restauração da carteira. Desativar PIN O código PIN está ativado no momento. Se quiser desativar o PIN, você precisará, primeiro, digitar o código PIN atual. - Desativar PIN + PIN ativado Por favor, digite seu código PIN Restaurar (Requer a Frase de Recuperação) Esqueceu seu PIN? Redefina e recupere sua carteira Bitkit com a sua frase de recuperação. Defina um novo PIN após concluir a recuperação. diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 3157985e8..530758102 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -462,7 +462,7 @@ Digite novamente o PIN de 4 dígitos Digite novamente o PIN de 4 dígitos para concluir o processo de configuração. Tente novamente, este não é o mesmo PIN. - Desativar PIN + PIN ativado O código PIN está ativado no momento. Se quiser desativar o PIN, você precisará, primeiro, digitar o código PIN atual. Desativar PIN Por favor, digite seu código PIN diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a31f5244f..4178b1128 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -492,7 +492,7 @@ Пожалуйста, используйте PIN-код, который вы запомните. Если вы забудете свой PIN-код, вы можете сбросить его, но для этого потребуется восстановить кошелёк. Отключить PIN-код PIN-код в настоящее время включен. Если вы хотите отключить свой PIN-код, вам необходимо сначала ввести текущий PIN-код. - Отключить PIN-код + PIN включён Пожалуйста, введите PIN-код Сбросить (требуется Фраза Восстановления) Забыли свой PIN-код? Сбросьте и восстановите свой кошелёк Bitkit с помощью фразы восстановления. Установите новый PIN-код после завершения восстановления. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 74652d9c8..6298d41d7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -486,7 +486,7 @@ Disable PIN Enable PIN PIN code is currently enabled. If you want to disable your PIN, you need to enter your current PIN code first. - Disable PIN + PIN Enabled Please enter your PIN code Reset (Requires Recovery Phrase) Forgot your PIN? Reset and recover your Bitkit wallet with your recovery phrase. Set a new PIN after completing recovery. From 7611f82ea41d1286a877c770a336f4a990b59282 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 23 Mar 2026 10:27:09 -0300 Subject: [PATCH 23/85] feat: convert change pin screen into a sheet --- app/src/main/java/to/bitkit/ui/ContentView.kt | 60 +-- .../java/to/bitkit/ui/components/SheetHost.kt | 1 + .../ui/settings/pin/ChangePinConfirmScreen.kt | 156 ------- .../ui/settings/pin/ChangePinNewScreen.kt | 113 ----- .../ui/settings/pin/ChangePinResultScreen.kt | 91 ---- .../bitkit/ui/settings/pin/ChangePinScreen.kt | 183 -------- .../ui/settings/pin/PinManagementScreen.kt | 3 +- .../to/bitkit/ui/sheets/ChangePinSheet.kt | 424 ++++++++++++++++++ 8 files changed, 428 insertions(+), 603 deletions(-) delete mode 100644 app/src/main/java/to/bitkit/ui/settings/pin/ChangePinConfirmScreen.kt delete mode 100644 app/src/main/java/to/bitkit/ui/settings/pin/ChangePinNewScreen.kt delete mode 100644 app/src/main/java/to/bitkit/ui/settings/pin/ChangePinResultScreen.kt delete mode 100644 app/src/main/java/to/bitkit/ui/settings/pin/ChangePinScreen.kt create mode 100644 app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt index 009f9ffbc..f5dc1a4bc 100644 --- a/app/src/main/java/to/bitkit/ui/ContentView.kt +++ b/app/src/main/java/to/bitkit/ui/ContentView.kt @@ -148,10 +148,6 @@ import to.bitkit.ui.settings.lightning.ChannelDetailScreen import to.bitkit.ui.settings.lightning.CloseConnectionScreen import to.bitkit.ui.settings.lightning.LightningConnectionsScreen import to.bitkit.ui.settings.lightning.LightningConnectionsViewModel -import to.bitkit.ui.settings.pin.ChangePinConfirmScreen -import to.bitkit.ui.settings.pin.ChangePinNewScreen -import to.bitkit.ui.settings.pin.ChangePinResultScreen -import to.bitkit.ui.settings.pin.ChangePinScreen import to.bitkit.ui.settings.pin.PinManagementScreen import to.bitkit.ui.settings.quickPay.QuickPayIntroScreen import to.bitkit.ui.settings.quickPay.QuickPaySettingsScreen @@ -163,6 +159,7 @@ import to.bitkit.ui.settings.transactionSpeed.TransactionSpeedSettingsScreen import to.bitkit.ui.sheets.BackgroundPaymentsIntroSheet import to.bitkit.ui.sheets.BackupRoute import to.bitkit.ui.sheets.BackupSheet +import to.bitkit.ui.sheets.ChangePinSheet import to.bitkit.ui.sheets.ConnectionClosedSheet import to.bitkit.ui.sheets.ForceTransferSheet import to.bitkit.ui.sheets.GiftSheet @@ -394,6 +391,7 @@ fun ContentView( is Sheet.ActivityDateRangeSelector -> DateRangeSelectorSheet() is Sheet.ActivityTagSelector -> TagSelectorSheet() is Sheet.Pin -> PinSheet(sheet, appViewModel) + Sheet.ChangePin -> ChangePinSheet(appViewModel) is Sheet.Backup -> BackupSheet(sheet, onDismiss = { appViewModel.hideSheet() }) is Sheet.LnurlAuth -> LnurlAuthSheet(sheet, appViewModel) Sheet.ForceTransfer -> ForceTransferSheet(appViewModel, transferViewModel) @@ -527,10 +525,6 @@ private fun RootNavHost( advancedSettingsSubScreens(navController) transactionSpeedSettings(navController) pinManagement(navController) - changePin(navController) - changePinNew(navController) - changePinConfirm(navController) - changePinResult(navController) defaultUnitSettings(currencyViewModel, navController) localCurrencySettings(currencyViewModel, navController) backupSettings(navController) @@ -1026,34 +1020,6 @@ private fun NavGraphBuilder.pinManagement(navController: NavHostController) { } } -private fun NavGraphBuilder.changePin(navController: NavHostController) { - composableWithDefaultTransitions { - ChangePinScreen(navController) - } -} - -private fun NavGraphBuilder.changePinNew(navController: NavHostController) { - composableWithDefaultTransitions { - ChangePinNewScreen(navController) - } -} - -private fun NavGraphBuilder.changePinConfirm(navController: NavHostController) { - composableWithDefaultTransitions { - val route = it.toRoute() - ChangePinConfirmScreen( - newPin = route.newPin, - navController = navController, - ) - } -} - -private fun NavGraphBuilder.changePinResult(navController: NavHostController) { - composableWithDefaultTransitions { - ChangePinResultScreen(navController) - } -} - private fun NavGraphBuilder.defaultUnitSettings( currencyViewModel: CurrencyViewModel, navController: NavHostController, @@ -1490,16 +1456,6 @@ inline fun NavController.navigateTo( fun NavController.navigateToPinManagement() = navigateTo(Routes.PinManagement) -fun NavController.navigateToChangePin() = navigateTo(Routes.ChangePin) - -fun NavController.navigateToChangePinNew() = navigateTo(Routes.ChangePinNew) - -fun NavController.navigateToChangePinConfirm(newPin: String) = navigateTo( - Routes.ChangePinConfirm(newPin), -) - -fun NavController.navigateToChangePinResult() = navigateTo(Routes.ChangePinResult) - fun NavController.navigateToAuthCheck( showLogoOnPin: Boolean = false, requirePin: Boolean = false, @@ -1608,18 +1564,6 @@ sealed interface Routes { @Serializable data object PinManagement : Routes - @Serializable - data object ChangePin : Routes - - @Serializable - data object ChangePinNew : Routes - - @Serializable - data class ChangePinConfirm(val newPin: String) : Routes - - @Serializable - data object ChangePinResult : Routes - @Serializable data class AuthCheck( val showLogoOnPin: Boolean = false, diff --git a/app/src/main/java/to/bitkit/ui/components/SheetHost.kt b/app/src/main/java/to/bitkit/ui/components/SheetHost.kt index eabfcca0b..0fc1ce39e 100644 --- a/app/src/main/java/to/bitkit/ui/components/SheetHost.kt +++ b/app/src/main/java/to/bitkit/ui/components/SheetHost.kt @@ -39,6 +39,7 @@ sealed interface Sheet { data class Send(val route: SendRoute = SendRoute.Recipient) : Sheet data object Receive : Sheet data class Pin(val route: PinRoute = PinRoute.Prompt()) : Sheet + data object ChangePin : Sheet data class Backup(val route: BackupRoute = BackupRoute.ShowMnemonic) : Sheet data object ActivityDateRangeSelector : Sheet data object ActivityTagSelector : Sheet diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinConfirmScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinConfirmScreen.kt deleted file mode 100644 index 99934e17f..000000000 --- a/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinConfirmScreen.kt +++ /dev/null @@ -1,156 +0,0 @@ -package to.bitkit.ui.settings.pin - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -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.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import kotlinx.coroutines.delay -import to.bitkit.R -import to.bitkit.env.Env -import to.bitkit.ui.appViewModel -import to.bitkit.ui.components.BodyM -import to.bitkit.ui.components.BodyS -import to.bitkit.ui.components.KEY_DELETE -import to.bitkit.ui.components.NumberPad -import to.bitkit.ui.components.NumberPadType -import to.bitkit.ui.components.PinDots -import to.bitkit.ui.navigateToChangePinResult -import to.bitkit.ui.scaffold.AppTopBar -import to.bitkit.ui.scaffold.DrawerNavIcon -import to.bitkit.ui.scaffold.ScreenColumn -import to.bitkit.ui.theme.AppThemeSurface -import to.bitkit.ui.theme.Colors - -@Composable -fun ChangePinConfirmScreen( - newPin: String, - navController: NavController, -) { - val app = appViewModel ?: return - var pin by remember { mutableStateOf("") } - var showError by remember { mutableStateOf(false) } - - LaunchedEffect(pin) { - if (pin.length == Env.PIN_LENGTH) { - if (pin == newPin) { - app.editPin(newPin) - navController.navigateToChangePinResult() - } else { - showError = true - delay(500) - pin = "" - } - } - } - - ChangePinConfirmContent( - pin = pin, - showError = showError, - onKeyPress = { key -> - if (key == KEY_DELETE) { - if (pin.isNotEmpty()) { - pin = pin.dropLast(1) - } - } else if (pin.length < Env.PIN_LENGTH) { - pin += key - } - }, - onBackClick = { navController.popBackStack() }, - ) -} - -@Composable -private fun ChangePinConfirmContent( - pin: String, - showError: Boolean, - onKeyPress: (String) -> Unit, - onBackClick: () -> Unit, -) { - ScreenColumn( - modifier = Modifier.testTag("ChangePIN2") - ) { - AppTopBar( - titleText = stringResource(R.string.security__cp_retype_title), - onBackClick = onBackClick, - actions = { DrawerNavIcon() }, - ) - - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(horizontal = 16.dp) - ) { - BodyM( - text = stringResource(R.string.security__cp_retype_text), - color = Colors.White64, - ) - - Spacer(modifier = Modifier.height(32.dp)) - - AnimatedVisibility(visible = showError) { - BodyS( - text = stringResource(R.string.security__cp_try_again), - textAlign = TextAlign.Center, - color = Colors.Brand, - modifier = Modifier - .fillMaxWidth() - .testTag("WrongPIN") - ) - } - - PinDots( - pin = pin, - modifier = Modifier.padding(vertical = 16.dp), - ) - - Spacer(modifier = Modifier.weight(1f)) - - NumberPad( - onPress = onKeyPress, - type = NumberPadType.SIMPLE, - modifier = Modifier.height(350.dp), - ) - } - } -} - -@Preview(showBackground = true) -@Composable -private fun Preview() { - AppThemeSurface { - ChangePinConfirmContent( - pin = "12", - showError = false, - onKeyPress = {}, - onBackClick = {}, - ) - } -} - -@Preview(showBackground = true) -@Composable -private fun PreviewRetry() { - AppThemeSurface { - ChangePinConfirmContent( - pin = "", - showError = true, - onKeyPress = {}, - onBackClick = {}, - ) - } -} diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinNewScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinNewScreen.kt deleted file mode 100644 index 79e9620d9..000000000 --- a/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinNewScreen.kt +++ /dev/null @@ -1,113 +0,0 @@ -package to.bitkit.ui.settings.pin - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -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.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import to.bitkit.R -import to.bitkit.env.Env -import to.bitkit.ui.components.BodyM -import to.bitkit.ui.components.KEY_DELETE -import to.bitkit.ui.components.NumberPad -import to.bitkit.ui.components.NumberPadType -import to.bitkit.ui.components.PinDots -import to.bitkit.ui.navigateToChangePinConfirm -import to.bitkit.ui.scaffold.AppTopBar -import to.bitkit.ui.scaffold.DrawerNavIcon -import to.bitkit.ui.scaffold.ScreenColumn -import to.bitkit.ui.theme.AppThemeSurface -import to.bitkit.ui.theme.Colors - -@Composable -fun ChangePinNewScreen( - navController: NavController, -) { - var pin by remember { mutableStateOf("") } - - LaunchedEffect(pin) { - if (pin.length == Env.PIN_LENGTH) { - navController.navigateToChangePinConfirm(pin) - } - } - - ChangePinNewContent( - pin = pin, - onKeyPress = { key -> - if (key == KEY_DELETE) { - if (pin.isNotEmpty()) { - pin = pin.dropLast(1) - } - } else if (pin.length < Env.PIN_LENGTH) { - pin += key - } - }, - onBackClick = { navController.popBackStack() }, - ) -} - -@Composable -private fun ChangePinNewContent( - pin: String, - onKeyPress: (String) -> Unit, - onBackClick: () -> Unit, -) { - ScreenColumn( - modifier = Modifier.testTag("ChangePIN2") - ) { - AppTopBar( - titleText = stringResource(R.string.security__cp_setnew_title), - onBackClick = onBackClick, - actions = { DrawerNavIcon() }, - ) - - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(horizontal = 16.dp) - ) { - BodyM( - text = stringResource(R.string.security__cp_setnew_text), - color = Colors.White64, - ) - - Spacer(modifier = Modifier.height(32.dp)) - - PinDots( - pin = pin, - modifier = Modifier.padding(vertical = 16.dp), - ) - - Spacer(modifier = Modifier.weight(1f)) - - NumberPad( - onPress = onKeyPress, - type = NumberPadType.SIMPLE, - modifier = Modifier.height(350.dp), - ) - } - } -} - -@Preview(showBackground = true) -@Composable -private fun Preview() { - AppThemeSurface { - ChangePinNewContent( - pin = "12", - onKeyPress = {}, - onBackClick = {}, - ) - } -} diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinResultScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinResultScreen.kt deleted file mode 100644 index 020703c3b..000000000 --- a/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinResultScreen.kt +++ /dev/null @@ -1,91 +0,0 @@ -package to.bitkit.ui.settings.pin - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import to.bitkit.R -import to.bitkit.ui.Routes -import to.bitkit.ui.components.BodyM -import to.bitkit.ui.components.PrimaryButton -import to.bitkit.ui.scaffold.AppTopBar -import to.bitkit.ui.scaffold.ScreenColumn -import to.bitkit.ui.theme.AppThemeSurface -import to.bitkit.ui.theme.Colors - -@Composable -fun ChangePinResultScreen( - navController: NavController, -) { - ChangePinResultContent( - onOkClick = { - navController.popBackStack(inclusive = false) - }, - onBackClick = { - navController.popBackStack() - } - ) -} - -@Composable -private fun ChangePinResultContent( - onOkClick: () -> Unit, - onBackClick: () -> Unit, -) { - ScreenColumn { - AppTopBar(stringResource(R.string.security__cp_changed_title), onBackClick = onBackClick) - Column( - modifier = Modifier.padding(horizontal = 16.dp) - ) { - BodyM( - text = stringResource(R.string.security__cp_changed_text), - color = Colors.White64, - ) - - Box( - contentAlignment = Alignment.Center, - modifier = Modifier - .fillMaxWidth() - .weight(1f) - ) { - Image( - painter = painterResource(R.drawable.check), - contentDescription = null, - modifier = Modifier.size(256.dp) - ) - } - - PrimaryButton( - text = stringResource(R.string.common__ok), - onClick = onOkClick, - modifier = Modifier.testTag("OK") - ) - - Spacer(modifier = Modifier.height(16.dp)) - } - } -} - -@Preview(showBackground = true) -@Composable -private fun Preview() { - AppThemeSurface { - ChangePinResultContent( - onOkClick = {}, - onBackClick = {}, - ) - } -} diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinScreen.kt deleted file mode 100644 index 85c727ed0..000000000 --- a/app/src/main/java/to/bitkit/ui/settings/pin/ChangePinScreen.kt +++ /dev/null @@ -1,183 +0,0 @@ -package to.bitkit.ui.settings.pin - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -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.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.navigation.NavController -import to.bitkit.R -import to.bitkit.env.Env -import to.bitkit.ui.appViewModel -import to.bitkit.ui.components.BodyM -import to.bitkit.ui.components.BodyS -import to.bitkit.ui.components.KEY_DELETE -import to.bitkit.ui.components.NumberPad -import to.bitkit.ui.components.NumberPadType -import to.bitkit.ui.components.PinDots -import to.bitkit.ui.navigateToChangePinNew -import to.bitkit.ui.scaffold.AppTopBar -import to.bitkit.ui.scaffold.DrawerNavIcon -import to.bitkit.ui.scaffold.ScreenColumn -import to.bitkit.ui.shared.modifiers.clickableAlpha -import to.bitkit.ui.theme.AppThemeSurface -import to.bitkit.ui.theme.Colors - -@Composable -fun ChangePinScreen( - navController: NavController, -) { - val app = appViewModel ?: return - val attemptsRemaining by app.pinAttemptsRemaining.collectAsStateWithLifecycle() - var pin by remember { mutableStateOf("") } - - LaunchedEffect(pin) { - if (pin.length == Env.PIN_LENGTH) { - if (app.validatePin(pin)) { - navController.navigateToChangePinNew() - } else { - pin = "" - } - } - } - - ChangePinContent( - pin = pin, - attemptsRemaining = attemptsRemaining, - onKeyPress = { key -> - if (key == KEY_DELETE) { - if (pin.isNotEmpty()) { - pin = pin.dropLast(1) - } - } else if (pin.length < Env.PIN_LENGTH) { - pin += key - } - }, - onBackClick = { navController.popBackStack() }, - onClickForgotPin = { app.setShowForgotPin(true) }, - ) -} - -@Composable -private fun ChangePinContent( - pin: String, - attemptsRemaining: Int, - onKeyPress: (String) -> Unit, - onBackClick: () -> Unit, - onClickForgotPin: () -> Unit, -) { - val isLastAttempt = attemptsRemaining == 1 - - ScreenColumn( - modifier = Modifier.testTag("ChangePIN") - ) { - AppTopBar( - titleText = stringResource(R.string.security__cp_title), - onBackClick = onBackClick, - actions = { DrawerNavIcon() }, - ) - - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(horizontal = 16.dp) - ) { - BodyM( - text = stringResource(R.string.security__cp_text), - color = Colors.White64, - ) - - Spacer(modifier = Modifier.height(32.dp)) - - AnimatedVisibility(visible = attemptsRemaining < Env.PIN_ATTEMPTS) { - if (isLastAttempt) { - BodyS( - text = stringResource(R.string.security__pin_last_attempt), - color = Colors.Brand, - textAlign = TextAlign.Center, - modifier = Modifier.testTag("LastAttempt") - ) - } else { - BodyS( - text = stringResource(R.string.security__pin_attempts) - .replace("{attemptsRemaining}", "$attemptsRemaining"), - color = Colors.Brand, - textAlign = TextAlign.Center, - modifier = Modifier - .clickableAlpha { onClickForgotPin() } - .testTag("AttemptsRemaining") - ) - } - Spacer(modifier = Modifier.height(16.dp)) - } - - PinDots( - pin = pin, - modifier = Modifier.padding(vertical = 16.dp), - ) - - Spacer(modifier = Modifier.weight(1f)) - - NumberPad( - onPress = onKeyPress, - type = NumberPadType.SIMPLE, - modifier = Modifier.height(350.dp), - ) - } - } -} - -@Preview(showBackground = true) -@Composable -private fun Preview() { - AppThemeSurface { - ChangePinContent( - pin = "12", - attemptsRemaining = 8, - onKeyPress = {}, - onBackClick = {}, - onClickForgotPin = {}, - ) - } -} - -@Preview(showBackground = true) -@Composable -private fun PreviewAttemptsRemaining() { - AppThemeSurface { - ChangePinContent( - pin = "1234", - attemptsRemaining = 5, - onKeyPress = {}, - onBackClick = {}, - onClickForgotPin = {}, - ) - } -} - -@Preview(showBackground = true) -@Composable -private fun PreviewAttemptsLast() { - AppThemeSurface { - ChangePinContent( - pin = "", - attemptsRemaining = 1, - onKeyPress = {}, - onBackClick = {}, - onClickForgotPin = {}, - ) - } -} diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt index d6c58f28d..504cf0f9b 100644 --- a/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt @@ -30,7 +30,6 @@ import to.bitkit.ui.components.PrimaryButton import to.bitkit.ui.components.SecondaryButton import to.bitkit.ui.components.Sheet import to.bitkit.ui.navigateToAuthCheck -import to.bitkit.ui.navigateToChangePin import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.DrawerNavIcon import to.bitkit.ui.scaffold.ScreenColumn @@ -49,7 +48,7 @@ fun PinManagementScreen( Content( isPinEnabled = isPinEnabled, onEnablePinClick = { app.showSheet(Sheet.Pin()) }, - onChangePinClick = { navController.navigateToChangePin() }, + onChangePinClick = { app.showSheet(Sheet.ChangePin) }, onDisablePinClick = { navController.navigateToAuthCheck( requirePin = true, diff --git a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt new file mode 100644 index 000000000..1bc41d30e --- /dev/null +++ b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt @@ -0,0 +1,424 @@ +package to.bitkit.ui.sheets + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +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.platform.testTag +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.rememberNavController +import androidx.navigation.toRoute +import kotlinx.coroutines.delay +import kotlinx.serialization.Serializable +import to.bitkit.R +import to.bitkit.env.Env +import to.bitkit.ui.components.BodyM +import to.bitkit.ui.components.BodyS +import to.bitkit.ui.components.KEY_DELETE +import to.bitkit.ui.components.NumberPad +import to.bitkit.ui.components.NumberPadType +import to.bitkit.ui.components.PinDots +import to.bitkit.ui.components.PrimaryButton +import to.bitkit.ui.components.SheetSize +import to.bitkit.ui.navigateTo +import to.bitkit.ui.scaffold.AppTopBar +import to.bitkit.ui.scaffold.ScreenColumn +import to.bitkit.ui.shared.modifiers.clickableAlpha +import to.bitkit.ui.shared.modifiers.sheetHeight +import to.bitkit.ui.shared.util.gradientBackground +import to.bitkit.ui.theme.AppThemeSurface +import to.bitkit.ui.theme.Colors +import to.bitkit.ui.utils.composableWithDefaultTransitions +import to.bitkit.viewmodels.AppViewModel + +@Suppress("CyclomaticComplexMethod") +@Composable +fun ChangePinSheet(app: AppViewModel) { + val navController = rememberNavController() + val onDismiss = app::hideSheet + + Column( + modifier = Modifier + .fillMaxWidth() + .sheetHeight(SheetSize.MEDIUM) + .gradientBackground() + ) { + NavHost( + navController = navController, + startDestination = ChangePinRoute.Validate, + ) { + composableWithDefaultTransitions { + val attemptsRemaining by app.pinAttemptsRemaining.collectAsStateWithLifecycle() + var pin by remember { mutableStateOf("") } + + LaunchedEffect(pin) { + if (pin.length == Env.PIN_LENGTH) { + if (app.validatePin(pin)) { + navController.navigateTo(ChangePinRoute.New) + } else { + pin = "" + } + } + } + + ValidateContent( + pin = pin, + attemptsRemaining = attemptsRemaining, + onKeyPress = { key -> + if (key == KEY_DELETE) { + if (pin.isNotEmpty()) pin = pin.dropLast(1) + } else if (pin.length < Env.PIN_LENGTH) { + pin += key + } + }, + onBackClick = onDismiss, + onClickForgotPin = { app.setShowForgotPin(true) }, + ) + } + composableWithDefaultTransitions { + var pin by remember { mutableStateOf("") } + + LaunchedEffect(pin) { + if (pin.length == Env.PIN_LENGTH) { + navController.navigateTo(ChangePinRoute.Confirm(pin)) + } + } + + NewPinContent( + pin = pin, + onKeyPress = { key -> + if (key == KEY_DELETE) { + if (pin.isNotEmpty()) pin = pin.dropLast(1) + } else if (pin.length < Env.PIN_LENGTH) { + pin += key + } + }, + onBackClick = { navController.popBackStack() }, + ) + } + composableWithDefaultTransitions { + val newPin = it.toRoute().pin + var pin by remember { mutableStateOf("") } + var showError by remember { mutableStateOf(false) } + + LaunchedEffect(pin) { + if (pin.length == Env.PIN_LENGTH) { + if (pin == newPin) { + app.editPin(newPin) + navController.navigateTo(ChangePinRoute.Result) + } else { + showError = true + delay(500) + pin = "" + } + } + } + + ConfirmContent( + pin = pin, + showError = showError, + onKeyPress = { key -> + if (key == KEY_DELETE) { + if (pin.isNotEmpty()) pin = pin.dropLast(1) + } else if (pin.length < Env.PIN_LENGTH) { + pin += key + } + }, + onBackClick = { navController.popBackStack() }, + ) + } + composableWithDefaultTransitions { + ResultContent( + onOkClick = onDismiss, + onBackClick = onDismiss, + ) + } + } + } +} + +sealed interface ChangePinRoute { + @Serializable + data object Validate : ChangePinRoute + + @Serializable + data object New : ChangePinRoute + + @Serializable + data class Confirm(val pin: String) : ChangePinRoute + + @Serializable + data object Result : ChangePinRoute +} + +@Composable +private fun ValidateContent( + pin: String, + attemptsRemaining: Int, + onKeyPress: (String) -> Unit, + onBackClick: () -> Unit, + onClickForgotPin: () -> Unit, +) { + val isLastAttempt = attemptsRemaining == 1 + + ScreenColumn( + noBackground = true, + modifier = Modifier.testTag("ChangePIN"), + ) { + AppTopBar( + titleText = stringResource(R.string.security__cp_title), + onBackClick = onBackClick, + ) + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(horizontal = 16.dp) + ) { + BodyM( + text = stringResource(R.string.security__cp_text), + color = Colors.White64, + ) + + Spacer(modifier = Modifier.height(32.dp)) + + AnimatedVisibility(visible = attemptsRemaining < Env.PIN_ATTEMPTS) { + if (isLastAttempt) { + BodyS( + text = stringResource(R.string.security__pin_last_attempt), + color = Colors.Brand, + textAlign = TextAlign.Center, + modifier = Modifier.testTag("LastAttempt") + ) + } else { + BodyS( + text = stringResource(R.string.security__pin_attempts) + .replace("{attemptsRemaining}", "$attemptsRemaining"), + color = Colors.Brand, + textAlign = TextAlign.Center, + modifier = Modifier + .clickableAlpha { onClickForgotPin() } + .testTag("AttemptsRemaining") + ) + } + Spacer(modifier = Modifier.height(16.dp)) + } + + PinDots( + pin = pin, + modifier = Modifier.padding(vertical = 16.dp), + ) + + Spacer(modifier = Modifier.weight(1f)) + + NumberPad( + onPress = onKeyPress, + type = NumberPadType.SIMPLE, + modifier = Modifier.height(350.dp), + ) + } + } +} + +@Composable +private fun NewPinContent( + pin: String, + onKeyPress: (String) -> Unit, + onBackClick: () -> Unit, +) { + ScreenColumn( + noBackground = true, + modifier = Modifier.testTag("ChangePIN2"), + ) { + AppTopBar( + titleText = stringResource(R.string.security__cp_setnew_title), + onBackClick = onBackClick, + ) + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(horizontal = 16.dp) + ) { + BodyM( + text = stringResource(R.string.security__cp_setnew_text), + color = Colors.White64, + ) + + Spacer(modifier = Modifier.height(32.dp)) + + PinDots( + pin = pin, + modifier = Modifier.padding(vertical = 16.dp), + ) + + Spacer(modifier = Modifier.weight(1f)) + + NumberPad( + onPress = onKeyPress, + type = NumberPadType.SIMPLE, + modifier = Modifier.height(350.dp), + ) + } + } +} + +@Composable +private fun ConfirmContent( + pin: String, + showError: Boolean, + onKeyPress: (String) -> Unit, + onBackClick: () -> Unit, +) { + ScreenColumn( + noBackground = true, + modifier = Modifier.testTag("ChangePIN2"), + ) { + AppTopBar( + titleText = stringResource(R.string.security__cp_retype_title), + onBackClick = onBackClick, + ) + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(horizontal = 16.dp) + ) { + BodyM( + text = stringResource(R.string.security__cp_retype_text), + color = Colors.White64, + ) + + Spacer(modifier = Modifier.height(32.dp)) + + AnimatedVisibility(visible = showError) { + BodyS( + text = stringResource(R.string.security__cp_try_again), + textAlign = TextAlign.Center, + color = Colors.Brand, + modifier = Modifier + .fillMaxWidth() + .testTag("WrongPIN") + ) + } + + PinDots( + pin = pin, + modifier = Modifier.padding(vertical = 16.dp), + ) + + Spacer(modifier = Modifier.weight(1f)) + + NumberPad( + onPress = onKeyPress, + type = NumberPadType.SIMPLE, + modifier = Modifier.height(350.dp), + ) + } + } +} + +@Composable +private fun ResultContent( + onOkClick: () -> Unit, + onBackClick: () -> Unit, +) { + ScreenColumn(noBackground = true) { + AppTopBar(stringResource(R.string.security__cp_changed_title), onBackClick = onBackClick) + Column( + modifier = Modifier.padding(horizontal = 16.dp) + ) { + BodyM( + text = stringResource(R.string.security__cp_changed_text), + color = Colors.White64, + ) + + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) { + Image( + painter = painterResource(R.drawable.check), + contentDescription = null, + modifier = Modifier.size(256.dp) + ) + } + + PrimaryButton( + text = stringResource(R.string.common__ok), + onClick = onOkClick, + modifier = Modifier.testTag("OK") + ) + + Spacer(modifier = Modifier.height(16.dp)) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun PreviewValidate() { + AppThemeSurface { + ValidateContent( + pin = "12", + attemptsRemaining = 8, + onKeyPress = {}, + onBackClick = {}, + onClickForgotPin = {}, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun PreviewNew() { + AppThemeSurface { + NewPinContent( + pin = "12", + onKeyPress = {}, + onBackClick = {}, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun PreviewConfirm() { + AppThemeSurface { + ConfirmContent( + pin = "12", + showError = false, + onKeyPress = {}, + onBackClick = {}, + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun PreviewResult() { + AppThemeSurface { + ResultContent( + onOkClick = {}, + onBackClick = {}, + ) + } +} From bde63e7ba591b44207b788a0f107265470322f3f Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 23 Mar 2026 10:44:08 -0300 Subject: [PATCH 24/85] fix: transition animation --- .../to/bitkit/ui/sheets/ChangePinSheet.kt | 289 ++++++++++-------- 1 file changed, 154 insertions(+), 135 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt index 1bc41d30e..b19bac0be 100644 --- a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt +++ b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt @@ -2,11 +2,13 @@ package to.bitkit.ui.sheets import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.Image +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable @@ -40,8 +42,7 @@ import to.bitkit.ui.components.PinDots import to.bitkit.ui.components.PrimaryButton import to.bitkit.ui.components.SheetSize import to.bitkit.ui.navigateTo -import to.bitkit.ui.scaffold.AppTopBar -import to.bitkit.ui.scaffold.ScreenColumn +import to.bitkit.ui.scaffold.SheetTopBar import to.bitkit.ui.shared.modifiers.clickableAlpha import to.bitkit.ui.shared.modifiers.sheetHeight import to.bitkit.ui.shared.util.gradientBackground @@ -50,6 +51,8 @@ import to.bitkit.ui.theme.Colors import to.bitkit.ui.utils.composableWithDefaultTransitions import to.bitkit.viewmodels.AppViewModel +private val NumberPadHeight = 350.dp + @Suppress("CyclomaticComplexMethod") @Composable fun ChangePinSheet(app: AppViewModel) { @@ -60,7 +63,6 @@ fun ChangePinSheet(app: AppViewModel) { modifier = Modifier .fillMaxWidth() .sheetHeight(SheetSize.MEDIUM) - .gradientBackground() ) { NavHost( navController = navController, @@ -180,61 +182,65 @@ private fun ValidateContent( ) { val isLastAttempt = attemptsRemaining == 1 - ScreenColumn( - noBackground = true, - modifier = Modifier.testTag("ChangePIN"), + Column( + modifier = Modifier + .fillMaxWidth() + .gradientBackground() + .navigationBarsPadding() + .testTag("ChangePIN"), ) { - AppTopBar( + SheetTopBar( titleText = stringResource(R.string.security__cp_title), - onBackClick = onBackClick, + onBack = onBackClick, ) - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(horizontal = 16.dp) - ) { - BodyM( - text = stringResource(R.string.security__cp_text), - color = Colors.White64, - ) + Spacer(modifier = Modifier.height(16.dp)) - Spacer(modifier = Modifier.height(32.dp)) - - AnimatedVisibility(visible = attemptsRemaining < Env.PIN_ATTEMPTS) { - if (isLastAttempt) { - BodyS( - text = stringResource(R.string.security__pin_last_attempt), - color = Colors.Brand, - textAlign = TextAlign.Center, - modifier = Modifier.testTag("LastAttempt") - ) - } else { - BodyS( - text = stringResource(R.string.security__pin_attempts) - .replace("{attemptsRemaining}", "$attemptsRemaining"), - color = Colors.Brand, - textAlign = TextAlign.Center, - modifier = Modifier - .clickableAlpha { onClickForgotPin() } - .testTag("AttemptsRemaining") - ) - } - Spacer(modifier = Modifier.height(16.dp)) + BodyM( + text = stringResource(R.string.security__cp_text), + color = Colors.White64, + modifier = Modifier.padding(horizontal = 32.dp), + ) + + Spacer(modifier = Modifier.height(32.dp)) + + AnimatedVisibility(visible = attemptsRemaining < Env.PIN_ATTEMPTS) { + if (isLastAttempt) { + BodyS( + text = stringResource(R.string.security__pin_last_attempt), + color = Colors.Brand, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .testTag("LastAttempt"), + ) + } else { + BodyS( + text = stringResource(R.string.security__pin_attempts) + .replace("{attemptsRemaining}", "$attemptsRemaining"), + color = Colors.Brand, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .clickableAlpha { onClickForgotPin() } + .testTag("AttemptsRemaining"), + ) } + Spacer(modifier = Modifier.height(16.dp)) + } - PinDots( - pin = pin, - modifier = Modifier.padding(vertical = 16.dp), - ) + Spacer(modifier = Modifier.weight(1f)) - Spacer(modifier = Modifier.weight(1f)) + PinDots(pin = pin) - NumberPad( - onPress = onKeyPress, - type = NumberPadType.SIMPLE, - modifier = Modifier.height(350.dp), - ) - } + Spacer(modifier = Modifier.height(32.dp)) + + NumberPad( + onPress = onKeyPress, + type = NumberPadType.SIMPLE, + modifier = Modifier + .height(NumberPadHeight), + ) } } @@ -244,39 +250,40 @@ private fun NewPinContent( onKeyPress: (String) -> Unit, onBackClick: () -> Unit, ) { - ScreenColumn( - noBackground = true, - modifier = Modifier.testTag("ChangePIN2"), + Column( + modifier = Modifier + .fillMaxWidth() + .gradientBackground() + .navigationBarsPadding() + .testTag("ChangePIN2"), ) { - AppTopBar( + SheetTopBar( titleText = stringResource(R.string.security__cp_setnew_title), - onBackClick = onBackClick, + onBack = onBackClick, ) - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(horizontal = 16.dp) - ) { - BodyM( - text = stringResource(R.string.security__cp_setnew_text), - color = Colors.White64, - ) + Spacer(modifier = Modifier.height(16.dp)) + + BodyM( + text = stringResource(R.string.security__cp_setnew_text), + color = Colors.White64, + modifier = Modifier.padding(horizontal = 32.dp), + ) - Spacer(modifier = Modifier.height(32.dp)) + Spacer(modifier = Modifier.height(32.dp)) + Spacer(modifier = Modifier.weight(1f)) - PinDots( - pin = pin, - modifier = Modifier.padding(vertical = 16.dp), - ) + PinDots(pin = pin) - Spacer(modifier = Modifier.weight(1f)) + Spacer(modifier = Modifier.height(32.dp)) - NumberPad( - onPress = onKeyPress, - type = NumberPadType.SIMPLE, - modifier = Modifier.height(350.dp), - ) - } + NumberPad( + onPress = onKeyPress, + type = NumberPadType.SIMPLE, + modifier = Modifier + .height(NumberPadHeight) + .background(Colors.Black), + ) } } @@ -287,50 +294,52 @@ private fun ConfirmContent( onKeyPress: (String) -> Unit, onBackClick: () -> Unit, ) { - ScreenColumn( - noBackground = true, - modifier = Modifier.testTag("ChangePIN2"), + Column( + modifier = Modifier + .fillMaxWidth() + .gradientBackground() + .navigationBarsPadding() + .testTag("ChangePIN2"), ) { - AppTopBar( + SheetTopBar( titleText = stringResource(R.string.security__cp_retype_title), - onBackClick = onBackClick, + onBack = onBackClick, ) - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(horizontal = 16.dp) - ) { - BodyM( - text = stringResource(R.string.security__cp_retype_text), - color = Colors.White64, - ) + Spacer(modifier = Modifier.height(16.dp)) - Spacer(modifier = Modifier.height(32.dp)) + BodyM( + text = stringResource(R.string.security__cp_retype_text), + color = Colors.White64, + modifier = Modifier.padding(horizontal = 32.dp), + ) - AnimatedVisibility(visible = showError) { - BodyS( - text = stringResource(R.string.security__cp_try_again), - textAlign = TextAlign.Center, - color = Colors.Brand, - modifier = Modifier - .fillMaxWidth() - .testTag("WrongPIN") - ) - } + Spacer(modifier = Modifier.height(32.dp)) - PinDots( - pin = pin, - modifier = Modifier.padding(vertical = 16.dp), + AnimatedVisibility(visible = showError) { + BodyS( + text = stringResource(R.string.security__cp_try_again), + textAlign = TextAlign.Center, + color = Colors.Brand, + modifier = Modifier + .fillMaxWidth() + .testTag("WrongPIN"), ) + } - Spacer(modifier = Modifier.weight(1f)) + Spacer(modifier = Modifier.weight(1f)) - NumberPad( - onPress = onKeyPress, - type = NumberPadType.SIMPLE, - modifier = Modifier.height(350.dp), - ) - } + PinDots(pin = pin) + + Spacer(modifier = Modifier.height(32.dp)) + + NumberPad( + onPress = onKeyPress, + type = NumberPadType.SIMPLE, + modifier = Modifier + .height(NumberPadHeight) + .background(Colors.Black), + ) } } @@ -339,37 +348,47 @@ private fun ResultContent( onOkClick: () -> Unit, onBackClick: () -> Unit, ) { - ScreenColumn(noBackground = true) { - AppTopBar(stringResource(R.string.security__cp_changed_title), onBackClick = onBackClick) - Column( - modifier = Modifier.padding(horizontal = 16.dp) - ) { - BodyM( - text = stringResource(R.string.security__cp_changed_text), - color = Colors.White64, - ) + Column( + modifier = Modifier + .fillMaxWidth() + .gradientBackground() + .navigationBarsPadding(), + ) { + SheetTopBar( + titleText = stringResource(R.string.security__cp_changed_title), + onBack = onBackClick, + ) - Box( - contentAlignment = Alignment.Center, - modifier = Modifier - .fillMaxWidth() - .weight(1f) - ) { - Image( - painter = painterResource(R.drawable.check), - contentDescription = null, - modifier = Modifier.size(256.dp) - ) - } + Spacer(modifier = Modifier.height(16.dp)) - PrimaryButton( - text = stringResource(R.string.common__ok), - onClick = onOkClick, - modifier = Modifier.testTag("OK") - ) + BodyM( + text = stringResource(R.string.security__cp_changed_text), + color = Colors.White64, + modifier = Modifier.padding(horizontal = 32.dp), + ) - Spacer(modifier = Modifier.height(16.dp)) + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxWidth() + .weight(1f), + ) { + Image( + painter = painterResource(R.drawable.check), + contentDescription = null, + modifier = Modifier.size(256.dp), + ) } + + PrimaryButton( + text = stringResource(R.string.common__ok), + onClick = onOkClick, + modifier = Modifier + .padding(horizontal = 32.dp) + .testTag("OK"), + ) + + Spacer(modifier = Modifier.height(16.dp)) } } From 39b312d6a6c0e72c242b6bb1f7a138d81007f759 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 23 Mar 2026 10:52:22 -0300 Subject: [PATCH 25/85] feat: update security__cp_text --- app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt | 3 ++- app/src/main/res/values-ar/strings.xml | 2 +- app/src/main/res/values-b+es+419/strings.xml | 2 +- app/src/main/res/values-ca/strings.xml | 2 +- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-el/strings.xml | 2 +- app/src/main/res/values-es-rES/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values-nl/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-pt/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 17 files changed, 18 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt index b19bac0be..f3dd3143e 100644 --- a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt +++ b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt @@ -49,6 +49,7 @@ import to.bitkit.ui.shared.util.gradientBackground import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors import to.bitkit.ui.utils.composableWithDefaultTransitions +import to.bitkit.ui.utils.withAccentBoldBright import to.bitkit.viewmodels.AppViewModel private val NumberPadHeight = 350.dp @@ -197,7 +198,7 @@ private fun ValidateContent( Spacer(modifier = Modifier.height(16.dp)) BodyM( - text = stringResource(R.string.security__cp_text), + text = stringResource(R.string.security__cp_text).withAccentBoldBright(), color = Colors.White64, modifier = Modifier.padding(horizontal = 32.dp), ) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index e0ce3f2b4..20ff6d7dc 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -441,7 +441,7 @@ أعد كتابة PIN الجديد يرجى استخدام رمز PIN ستتذكره. إذا نسيت رمز PIN، يمكنك إعادة تعيينه، لكن ذلك سيتطلب استعادة محفظتك. تعيين PIN جديد - يمكنك تغيير PIN إلى تركيبة\nجديدة من 4 أرقام. يرجى إدخال رمز PIN الحالي أولاً. + أدخل رقم PIN <accent>الحالي</accent> لتغييره. تغيير PIN حاول مرة أخرى، هذا ليس نفس رمز PIN. عرض عبارة الاسترداد diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index 99404a1e6..8674301d2 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -441,7 +441,7 @@ Reintroducir nuevo PIN Por favor, utilice un PIN que recuerde. Si olvida su PIN puede restablecerlo, pero para ello tendrá que resetear su billetera. Establecer nuevo PIN - Puede cambiar su código PIN por una nueva\ncombinación de 4 dígitos. Por favor, introduzca primero su código PIN actual. + Introduzca su PIN <accent>actual</accent> para cambiarlo. Cambiar PIN Inténtelo de nuevo, no es el mismo PIN. Mostrar frase semilla diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 930a6e2bc..a463ae56a 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -441,7 +441,7 @@ Torna a escriure el nou PIN Si us plau, utilitza un PIN que recordis. Si oblides el teu PIN pots restablir-lo, però caldrà restaurar la cartera. Configura un nou PIN - Pots canviar el teu codi PIN a una nova\ncombinació de 4 dígits. Si us plau, introdueix primer el teu codi PIN actual. + Introdueix el teu PIN <accent>actual</accent> per canviar-lo. Canvia el PIN Torna a provar, això no és el mateix PIN. Mostrar frase de recuperació diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 3ec6dbb6e..b1bb35ab0 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -441,7 +441,7 @@ Znovu zadejte nový PIN Použijte prosím PIN, který si zapamatujete. Pokud svůj PIN zapomenete, můžete jej resetovat, ale to bude vyžadovat obnovení vaší peněženky. Nastavte nový PIN - Svůj PIN kód můžete změnit na novou\n4místnou kombinaci. Nejprve zadejte svůj aktuální PIN kód. + Zadejte svůj <accent>aktuální</accent> PIN pro jeho změnu. Změna PIN Zkuste to znovu, toto není stejný PIN. Zobrazit seed frázi diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 7136efe9d..16ad8a810 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -500,7 +500,7 @@ Support kontaktieren App löschen PIN ändern - Du kannst deinen PIN-Code in eine neue\n4-Ziffern-Kombination ändern. Bitte gib zuerst deinen aktuellen PIN-Code ein. + Geben Sie Ihre <accent>aktuelle</accent> PIN ein, um sie zu ändern. Neuen PIN erneut eingeben Bitte gib deinen 4-stelligen PIN erneut ein, um den Einrichtungsprozess abzuschließen. Neuen PIN festlegen diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index d2a7b5886..9f6926fd1 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -441,7 +441,7 @@ Επανάληψη νέου PIN Χρησιμοποίησε ένα PIN που θα θυμάσαι. Αν ξεχάσεις το PIN σου μπορείς να το επαναφέρεις, αλλά αυτό θα απαιτήσει επαναφορά του πορτοφολιού σου. Ορισμός νέου PIN - Μπορείς να αλλάξεις τον κωδικό PIN σου σε νέο\nσυνδυασμό 4 ψηφίων. Εισάγαγε πρώτα τον τρέχοντα κωδικό PIN. + Εισαγάγετε το <accent>τρέχον</accent> PIN σας για να το αλλάξετε. Αλλαγή PIN Δοκίμασε ξανά, δεν είναι το ίδιο PIN. Εμφάνιση φράσης seed diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index da65e13cc..0a6fabe58 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -441,7 +441,7 @@ Reintroducir nuevo PIN Por favor, utilice un PIN que recuerde. Si olvida su PIN puede restablecerlo, pero para ello tendrá que restaurar su monedero. Establecer nuevo PIN - Puede cambiar su código PIN por una nueva\ncombinación de 4 dígitos. Por favor, introduzca primero su código PIN actual. + Introduzca su PIN <accent>actual</accent> para cambiarlo. Cambiar PIN Inténtelo de nuevo, no es el mismo PIN. Mostrar frase semilla diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 13d1dc882..3278d0abb 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -500,7 +500,7 @@ Contactar con el servicio de asistencia Borrar App Cambiar PIN - Puede cambiar su código PIN por una nueva\ncombinación de 4 dígitos. Por favor, introduzca primero su código PIN actual. + Introduzca su PIN <accent>actual</accent> para cambiarlo. Reintroducir nuevo PIN Vuelva a introducir su PIN de 4 dígitos para completar el proceso de configuración. Establecer nuevo PIN diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 8fe4f6609..9250e642e 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -500,7 +500,7 @@ Contacter le support Effacer l\'application Modifier le PIN - Vous pouvez remplacer votre code PIN par une nouvelle\ncombinaison de 4 chiffres. Veuillez d\'abord introduire votre code PIN actuel. + Saisissez votre PIN <accent>actuel</accent> pour le modifier. Confirmer le nouveau code PIN Veuillez retaper votre code PIN à 4 chiffres pour terminer la procédure de configuration. Définir un nouveau code PIN diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 016188f5c..9928c2943 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -441,7 +441,7 @@ Digita il Nuovo PIN Utilizza un PIN che ricorderai. Se dimentichi il PIN, puoi reimpostarlo, ma ciò richiede il ripristino del wallet. Imposta Nuovo PIN - Puoi cambiare il codice PIN con una nuova\ncombinazione di 4 cifre. Inserisci prima il codice PIN corrente. + Inserisci il tuo PIN <accent>attuale</accent> per cambiarlo. Cambia PIN Provi di nuovo, il PIN non coincide. Mostra la Seed Phrase diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 2d02c9351..b6c2603ef 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -441,7 +441,7 @@ Nieuwe pincode opnieuw typen Gebruik een pincode die je zult onthouden. Als je je pincode vergeet, kun je deze resetten, maar daarvoor moet je je wallet herstellen. Nieuwe pincode instellen - Je kunt je pincode wijzigen naar een nieuwe\n4-cijferige combinatie. Voer eerst je huidige pincode in. + Voer je <accent>huidige</accent> PIN in om deze te wijzigen. PIN wijzigen Probeer opnieuw, dit is niet dezelfde pincode. Herstelzin tonen diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 331a07487..de9bb6c02 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -500,7 +500,7 @@ Skontaktuj się z pomocą techniczną Wyczyść aplikację Zmień PIN - Możesz zmienić swój kod PIN na nową\n4-cyfrową kombinację. Najpierw wprowadź aktualny kod PIN. + Wprowadź swój <accent>aktualny</accent> PIN, aby go zmienić. Wpisz ponownie kod PIN Wpisz proszę ponownie 4 cyfrowy kod PIN aby zakończyć konfigurację. Ustaw nowy kod PIN diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index ce573db8f..978787283 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -441,7 +441,7 @@ Digite novamente o novo PIN Use um PIN que você se lembrará. Se você esquecer o PIN, poderá redefini-lo mas isso exigirá a restauração da carteira. Definir novo PIN - Você pode alterar seu código PIN para uma nova\ncombinação de 4 dígitos. Primeiro, digite seu código PIN atual. + Digite seu PIN <accent>atual</accent> para alterá-lo. Alterar PIN Tente novamente, este não é o mesmo PIN. Mostrar Seed diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 530758102..35913bf6a 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -499,7 +499,7 @@ Contactar Suporte Limpar Aplicativo Alterar PIN - Você pode alterar seu código PIN para uma nova\ncombinação de 4 dígitos. Primeiro, digite seu código PIN atual. + Digite seu PIN <accent>atual</accent> para alterá-lo. Digite novamente o novo PIN Digite novamente o PIN de 4 dígitos para concluir o processo de configuração. Definir novo PIN diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 4178b1128..83677e0b6 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -455,7 +455,7 @@ Повторите Новый PIN-код Пожалуйста, используйте PIN-код, который вы запомните. Если вы забудете свой PIN-код, вы можете сбросить его, но для этого потребуется восстановить кошелёк. Новый PIN-код - Вы можете изменить свой PIN-код на новую\nчетырёхзначную комбинацию. Сначала введите текущий PIN-код. + Введите <accent>текущий</accent> PIN, чтобы изменить его. Изменить PIN-код Попробуйте ещё раз, это не тот PIN-код. Показать Фразу Восстановления diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6298d41d7..2cef5d6ab 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -448,7 +448,7 @@ Retype New PIN Please use a PIN you will remember. If you forget your PIN you can reset it, but that will require restoring your wallet. Set New PIN - You can change your PIN code to a new\n4-digit combination. Please enter your current PIN code first. + Enter your <accent>current</accent> PIN to change it. Change PIN Try again, this is not the same PIN. Show Seed Phrase From 184386856d4899cdd87e8981b8cfbc8a2b333448 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 23 Mar 2026 10:56:37 -0300 Subject: [PATCH 26/85] feat: update security__cp_retype_text --- app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt | 3 +-- app/src/main/res/values-ar/strings.xml | 2 +- app/src/main/res/values-b+es+419/strings.xml | 2 +- app/src/main/res/values-ca/strings.xml | 2 +- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-el/strings.xml | 2 +- app/src/main/res/values-es-rES/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values-nl/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-pt/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 17 files changed, 17 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt index f3dd3143e..dc007bda6 100644 --- a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt +++ b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt @@ -356,8 +356,7 @@ private fun ResultContent( .navigationBarsPadding(), ) { SheetTopBar( - titleText = stringResource(R.string.security__cp_changed_title), - onBack = onBackClick, + titleText = stringResource(R.string.security__cp_changed_title) ) Spacer(modifier = Modifier.height(16.dp)) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 20ff6d7dc..152b235b3 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -437,7 +437,7 @@ تواصل مع الدعم لقد غيّرت رمز PIN بنجاح إلى تركيبة جديدة من 4 أرقام. تم تغيير PIN - يرجى إعادة كتابة رمز PIN المكون من 4 أرقام لإكمال عملية الإعداد. + أعد كتابة رقم PIN المكون من 4 أرقام لإكمال الإعداد وحفظ رقم PIN الجديد. أعد كتابة PIN الجديد يرجى استخدام رمز PIN ستتذكره. إذا نسيت رمز PIN، يمكنك إعادة تعيينه، لكن ذلك سيتطلب استعادة محفظتك. تعيين PIN جديد diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index 8674301d2..085ffbb8f 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -437,7 +437,7 @@ Contactar a soporte Ha cambiado correctamente su PIN por una nueva combinación de 4 dígitos. PIN cambiado - Vuelva a introducir su PIN de 4 dígitos para completar el proceso de configuración. + Vuelva a introducir su PIN de 4 dígitos para completar la configuración y guardar su nuevo PIN. Reintroducir nuevo PIN Por favor, utilice un PIN que recuerde. Si olvida su PIN puede restablecerlo, pero para ello tendrá que resetear su billetera. Establecer nuevo PIN diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index a463ae56a..ab1508667 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -437,7 +437,7 @@ Contacta amb el suport Has canviat correctament el teu PIN a una nova combinació de 4 dígits. El PIN ha canviat - Si us plau, torna a escriure el teu PIN de 4 dígits per completar el procés de configuració. + Torna a escriure el teu PIN de 4 dígits per completar la configuració i desar el teu nou PIN. Torna a escriure el nou PIN Si us plau, utilitza un PIN que recordis. Si oblides el teu PIN pots restablir-lo, però caldrà restaurar la cartera. Configura un nou PIN diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index b1bb35ab0..671c3fef6 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -437,7 +437,7 @@ Kontaktovat podporu Úspěšně jste změnili svůj PIN na novou 4místnou kombinaci. PIN změněn - Chcete-li dokončit proces nastavení, zadejte znovu svůj 4místný kód PIN. + Zadejte znovu svůj 4místný PIN pro dokončení nastavení a uložení nového PIN. Znovu zadejte nový PIN Použijte prosím PIN, který si zapamatujete. Pokud svůj PIN zapomenete, můžete jej resetovat, ale to bude vyžadovat obnovení vaší peněženky. Nastavte nový PIN diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 16ad8a810..b8f4a719c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -502,7 +502,7 @@ PIN ändern Geben Sie Ihre <accent>aktuelle</accent> PIN ein, um sie zu ändern. Neuen PIN erneut eingeben - Bitte gib deinen 4-stelligen PIN erneut ein, um den Einrichtungsprozess abzuschließen. + Bitte gib deinen 4-stelligen PIN erneut ein, um die Einrichtung abzuschließen und deinen neuen PIN zu speichern. Neuen PIN festlegen Bitte verwende einen PIN, den du dir merken wirst. Wenn du deinen PIN vergisst, kannst du ihn zurücksetzen, aber das erfordert die Wiederherstellung deines Wallets. Versuche es erneut, dieser PIN stimmt nicht überein. diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 9f6926fd1..1976cf71f 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -437,7 +437,7 @@ Επικοινωνία με υποστήριξη Άλλαξες επιτυχώς το PIN σου σε νέο συνδυασμό 4 ψηφίων. Το PIN άλλαξε - Πληκτρολόγησε ξανά το 4ψήφιο PIN για να ολοκληρώσεις τη διαδικασία ρύθμισης. + Πληκτρολόγησε ξανά το 4ψήφιο PIN για να ολοκληρώσεις τη ρύθμιση και να αποθηκεύσεις το νέο PIN. Επανάληψη νέου PIN Χρησιμοποίησε ένα PIN που θα θυμάσαι. Αν ξεχάσεις το PIN σου μπορείς να το επαναφέρεις, αλλά αυτό θα απαιτήσει επαναφορά του πορτοφολιού σου. Ορισμός νέου PIN diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 0a6fabe58..73e1b6ba6 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -437,7 +437,7 @@ Contactar con el servicio de asistencia Ha cambiado correctamente su PIN por una nueva combinación de 4 dígitos. PIN cambiado - Vuelva a introducir su PIN de 4 dígitos para completar el proceso de configuración. + Vuelva a introducir su PIN de 4 dígitos para completar la configuración y guardar su nuevo PIN. Reintroducir nuevo PIN Por favor, utilice un PIN que recuerde. Si olvida su PIN puede restablecerlo, pero para ello tendrá que restaurar su monedero. Establecer nuevo PIN diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 3278d0abb..b26750f19 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -502,7 +502,7 @@ Cambiar PIN Introduzca su PIN <accent>actual</accent> para cambiarlo. Reintroducir nuevo PIN - Vuelva a introducir su PIN de 4 dígitos para completar el proceso de configuración. + Vuelva a introducir su PIN de 4 dígitos para completar la configuración y guardar su nuevo PIN. Establecer nuevo PIN Por favor, utilice un PIN que recuerde. Si olvida su PIN puede restablecerlo, pero para ello tendrá que restaurar su monedero. Inténtelo de nuevo, no es el mismo PIN. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 9250e642e..29e54fa04 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -502,7 +502,7 @@ Modifier le PIN Saisissez votre PIN <accent>actuel</accent> pour le modifier. Confirmer le nouveau code PIN - Veuillez retaper votre code PIN à 4 chiffres pour terminer la procédure de configuration. + Veuillez retaper votre PIN à 4 chiffres pour terminer la configuration et enregistrer votre nouveau PIN. Définir un nouveau code PIN Veuillez utiliser un code PIN dont vous vous souviendrez. Si vous oubliez votre code PIN, vous pouvez le réinitialiser, mais cela nécessitera de restaurer votre portefeuille. Réessayez, il ne s\'agit pas du même code PIN. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 9928c2943..65351ea14 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -437,7 +437,7 @@ Contatta l\'Assistenza Hai cambiato con successo il PIN con una nuova combinazione a 4 cifre. Il PIN è stato modificato - Digita nuovamente il tuo PIN a 4 cifre per completare il processo di configurazione. + Digita nuovamente il tuo PIN a 4 cifre per completare la configurazione e salvare il nuovo PIN. Digita il Nuovo PIN Utilizza un PIN che ricorderai. Se dimentichi il PIN, puoi reimpostarlo, ma ciò richiede il ripristino del wallet. Imposta Nuovo PIN diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index b6c2603ef..6239d6060 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -437,7 +437,7 @@ Contact opnemen met support Je hebt je pincode succesvol gewijzigd naar een nieuwe 4-cijferige combinatie. Pincode gewijzigd - Typ je 4-cijferige pincode opnieuw in om het installatieproces te voltooien. + Typ je 4-cijferige PIN opnieuw in om de instelling te voltooien en je nieuwe PIN op te slaan. Nieuwe pincode opnieuw typen Gebruik een pincode die je zult onthouden. Als je je pincode vergeet, kun je deze resetten, maar daarvoor moet je je wallet herstellen. Nieuwe pincode instellen diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index de9bb6c02..6adad8a44 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -502,7 +502,7 @@ Zmień PIN Wprowadź swój <accent>aktualny</accent> PIN, aby go zmienić. Wpisz ponownie kod PIN - Wpisz proszę ponownie 4 cyfrowy kod PIN aby zakończyć konfigurację. + Wpisz ponownie 4-cyfrowy PIN, aby zakończyć konfigurację i zapisać nowy PIN. Ustaw nowy kod PIN Użyj kodu PIN, który zapamiętasz. Jeśli zapomnisz kodu PIN, możesz go zresetować, ale będzie to wymagało przywrócenia portfela. Spróbuj ponownie, kody PIN się różnią. diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 978787283..38ac33de4 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -437,7 +437,7 @@ Contactar Suporte Você alterou com sucesso seu PIN para uma nova combinação de 4 dígitos. PIN alterado - Digite novamente o PIN de 4 dígitos para concluir o processo de configuração. + Digite novamente o PIN de 4 dígitos para concluir o processo de configuração e salvar seu novo PIN. Digite novamente o novo PIN Use um PIN que você se lembrará. Se você esquecer o PIN, poderá redefini-lo mas isso exigirá a restauração da carteira. Definir novo PIN diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 35913bf6a..191fa2f7d 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -501,7 +501,7 @@ Alterar PIN Digite seu PIN <accent>atual</accent> para alterá-lo. Digite novamente o novo PIN - Digite novamente o PIN de 4 dígitos para concluir o processo de configuração. + Digite novamente o PIN de 4 dígitos para concluir o processo de configuração e salvar seu novo PIN. Definir novo PIN Use um PIN que você se lembrará. Se você esquecer o PIN, poderá redefini-lo mas isso exigirá a restauração da carteira. Tente novamente, este não é o mesmo PIN. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 83677e0b6..704231149 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -451,7 +451,7 @@ Связаться с Поддержкой Вы успешно изменили свой PIN-код на новую комбинацию из 4 цифр. PIN-код Изменён - Повторно введите 4-значный PIN-код, чтобы завершить процесс установки. + Повторно введите 4-значный PIN, чтобы завершить настройку и сохранить новый PIN. Повторите Новый PIN-код Пожалуйста, используйте PIN-код, который вы запомните. Если вы забудете свой PIN-код, вы можете сбросить его, но для этого потребуется восстановить кошелёк. Новый PIN-код diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2cef5d6ab..c46464f89 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -444,7 +444,7 @@ Contact Support You have successfully changed your PIN to a new 4-digit combination. PIN changed - Please retype your 4-digit PIN to complete the setup process. + Please retype your 4-digit PIN to complete the setup process and save your new PIN. Retype New PIN Please use a PIN you will remember. If you forget your PIN you can reset it, but that will require restoring your wallet. Set New PIN From c7c7a6dba7dd22298de3d31a5ae4cdb4a32b47af Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Mon, 23 Mar 2026 11:27:21 -0300 Subject: [PATCH 27/85] feat: DisablePinSheet --- app/src/main/java/to/bitkit/ui/ContentView.kt | 2 + .../java/to/bitkit/ui/components/SheetHost.kt | 1 + .../ui/settings/pin/PinManagementScreen.kt | 12 +- .../to/bitkit/ui/sheets/DisablePinSheet.kt | 161 ++++++++++++++++++ app/src/main/res/values-ar/strings.xml | 3 +- app/src/main/res/values-b+es+419/strings.xml | 3 +- app/src/main/res/values-ca/strings.xml | 3 +- app/src/main/res/values-cs/strings.xml | 3 +- app/src/main/res/values-de/strings.xml | 3 +- app/src/main/res/values-el/strings.xml | 3 +- app/src/main/res/values-es-rES/strings.xml | 3 +- app/src/main/res/values-es/strings.xml | 3 +- app/src/main/res/values-fr/strings.xml | 3 +- app/src/main/res/values-it/strings.xml | 3 +- app/src/main/res/values-nl/strings.xml | 3 +- app/src/main/res/values-pl/strings.xml | 3 +- app/src/main/res/values-pt-rBR/strings.xml | 3 +- app/src/main/res/values-pt/strings.xml | 3 +- app/src/main/res/values-ru/strings.xml | 3 +- app/src/main/res/values/strings.xml | 3 +- 20 files changed, 198 insertions(+), 26 deletions(-) create mode 100644 app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt index f5dc1a4bc..5b4d30333 100644 --- a/app/src/main/java/to/bitkit/ui/ContentView.kt +++ b/app/src/main/java/to/bitkit/ui/ContentView.kt @@ -161,6 +161,7 @@ import to.bitkit.ui.sheets.BackupRoute import to.bitkit.ui.sheets.BackupSheet import to.bitkit.ui.sheets.ChangePinSheet import to.bitkit.ui.sheets.ConnectionClosedSheet +import to.bitkit.ui.sheets.DisablePinSheet import to.bitkit.ui.sheets.ForceTransferSheet import to.bitkit.ui.sheets.GiftSheet import to.bitkit.ui.sheets.HighBalanceWarningSheet @@ -392,6 +393,7 @@ fun ContentView( is Sheet.ActivityTagSelector -> TagSelectorSheet() is Sheet.Pin -> PinSheet(sheet, appViewModel) Sheet.ChangePin -> ChangePinSheet(appViewModel) + Sheet.DisablePin -> DisablePinSheet(appViewModel) is Sheet.Backup -> BackupSheet(sheet, onDismiss = { appViewModel.hideSheet() }) is Sheet.LnurlAuth -> LnurlAuthSheet(sheet, appViewModel) Sheet.ForceTransfer -> ForceTransferSheet(appViewModel, transferViewModel) diff --git a/app/src/main/java/to/bitkit/ui/components/SheetHost.kt b/app/src/main/java/to/bitkit/ui/components/SheetHost.kt index 0fc1ce39e..e1e7e4396 100644 --- a/app/src/main/java/to/bitkit/ui/components/SheetHost.kt +++ b/app/src/main/java/to/bitkit/ui/components/SheetHost.kt @@ -40,6 +40,7 @@ sealed interface Sheet { data object Receive : Sheet data class Pin(val route: PinRoute = PinRoute.Prompt()) : Sheet data object ChangePin : Sheet + data object DisablePin : Sheet data class Backup(val route: BackupRoute = BackupRoute.ShowMnemonic) : Sheet data object ActivityDateRangeSelector : Sheet data object ActivityTagSelector : Sheet diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt index 504cf0f9b..f288d16f3 100644 --- a/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt @@ -22,14 +22,11 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController import to.bitkit.R -import to.bitkit.ui.Routes import to.bitkit.ui.appViewModel -import to.bitkit.ui.components.AuthCheckAction import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.PrimaryButton import to.bitkit.ui.components.SecondaryButton import to.bitkit.ui.components.Sheet -import to.bitkit.ui.navigateToAuthCheck import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.DrawerNavIcon import to.bitkit.ui.scaffold.ScreenColumn @@ -49,12 +46,7 @@ fun PinManagementScreen( isPinEnabled = isPinEnabled, onEnablePinClick = { app.showSheet(Sheet.Pin()) }, onChangePinClick = { app.showSheet(Sheet.ChangePin) }, - onDisablePinClick = { - navController.navigateToAuthCheck( - requirePin = true, - onSuccessActionId = AuthCheckAction.DISABLE_PIN, - ) { popUpTo(Routes.Settings) } - }, + onDisablePinClick = { app.showSheet(Sheet.DisablePin) }, onBackClick = { navController.popBackStack() }, ) } @@ -85,7 +77,7 @@ private fun Content( BodyM( text = stringResource( if (isPinEnabled) { - R.string.security__pin_disable_text + R.string.security__pin_enabled_text } else { R.string.security__pin_security_text } diff --git a/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt new file mode 100644 index 000000000..0e1ec1324 --- /dev/null +++ b/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt @@ -0,0 +1,161 @@ +package to.bitkit.ui.sheets + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import to.bitkit.R +import to.bitkit.env.Env +import to.bitkit.ui.components.BodyM +import to.bitkit.ui.components.BodyS +import to.bitkit.ui.components.KEY_DELETE +import to.bitkit.ui.components.NumberPad +import to.bitkit.ui.components.NumberPadType +import to.bitkit.ui.components.PinDots +import to.bitkit.ui.components.SheetSize +import to.bitkit.ui.scaffold.SheetTopBar +import to.bitkit.ui.shared.modifiers.clickableAlpha +import to.bitkit.ui.shared.modifiers.sheetHeight +import to.bitkit.ui.shared.util.gradientBackground +import to.bitkit.ui.theme.AppThemeSurface +import to.bitkit.ui.theme.Colors +import to.bitkit.ui.utils.withAccentBoldBright +import to.bitkit.viewmodels.AppViewModel + +private val NumberPadHeight = 350.dp + +@Composable +fun DisablePinSheet(app: AppViewModel) { + val attemptsRemaining by app.pinAttemptsRemaining.collectAsStateWithLifecycle() + var pin by remember { mutableStateOf("") } + val onDismiss = app::hideSheet + + LaunchedEffect(pin) { + if (pin.length == Env.PIN_LENGTH) { + if (app.validatePin(pin)) { + app.removePin() + onDismiss() + } else { + pin = "" + } + } + } + + DisablePinContent( + pin = pin, + attemptsRemaining = attemptsRemaining, + onKeyPress = { key -> + if (key == KEY_DELETE) { + if (pin.isNotEmpty()) pin = pin.dropLast(1) + } else if (pin.length < Env.PIN_LENGTH) { + pin += key + } + }, + onBackClick = onDismiss, + onClickForgotPin = { app.setShowForgotPin(true) }, + ) +} + +@Composable +private fun DisablePinContent( + pin: String, + attemptsRemaining: Int, + onKeyPress: (String) -> Unit, + onBackClick: () -> Unit, + onClickForgotPin: () -> Unit, +) { + val isLastAttempt = attemptsRemaining == 1 + + Column( + modifier = Modifier + .fillMaxWidth() + .sheetHeight(SheetSize.MEDIUM) + .gradientBackground() + .navigationBarsPadding() + .testTag("DisablePIN"), + ) { + SheetTopBar( + titleText = stringResource(R.string.security__pin_disable_button), + onBack = onBackClick, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + BodyM( + text = stringResource(R.string.security__pin_disable_text).withAccentBoldBright(), + color = Colors.White64, + modifier = Modifier.padding(horizontal = 32.dp), + ) + + Spacer(modifier = Modifier.height(32.dp)) + + AnimatedVisibility(visible = attemptsRemaining < Env.PIN_ATTEMPTS) { + if (isLastAttempt) { + BodyS( + text = stringResource(R.string.security__pin_last_attempt), + color = Colors.Brand, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .testTag("LastAttempt"), + ) + } else { + BodyS( + text = stringResource(R.string.security__pin_attempts) + .replace("{attemptsRemaining}", "$attemptsRemaining"), + color = Colors.Brand, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .clickableAlpha { onClickForgotPin() } + .testTag("AttemptsRemaining"), + ) + } + Spacer(modifier = Modifier.height(16.dp)) + } + + Spacer(modifier = Modifier.weight(1f)) + + PinDots(pin = pin) + + Spacer(modifier = Modifier.height(32.dp)) + + NumberPad( + onPress = onKeyPress, + type = NumberPadType.SIMPLE, + modifier = Modifier + .height(NumberPadHeight) + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun Preview() { + AppThemeSurface { + DisablePinContent( + pin = "12", + attemptsRemaining = 8, + onKeyPress = {}, + onBackClick = {}, + onClickForgotPin = {}, + ) + } +} diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 152b235b3..231032049 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -477,7 +477,8 @@ اختر رمز PIN من 4 أرقام يرجى استخدام رمز PIN ستتذكره. إذا نسيت رمز PIN، يمكنك إعادة تعيينه، لكن ذلك سيتطلب استعادة محفظتك. تعطيل PIN - رمز PIN مفعل حاليًا. إذا أردت تعطيل رمز PIN، تحتاج إلى إدخال رمز PIN الحالي أولاً. + أدخل رقم <accent>PIN</accent> لتعطيله. + رمز PIN مفعل حالياً. إذا كنت تريد تعطيله أو تغييره، يجب إدخال رمز PIN الحالي أولاً. PIN مفعل يرجى إدخال رمز PIN إعادة التعيين (يتطلب عبارة الاسترداد) diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index 085ffbb8f..84aec5456 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -477,7 +477,8 @@ Elija el PIN de 4 dígitos Por favor, utilice un PIN que recuerde. Si olvida su PIN puede restablecerlo, pero para ello tendrá que restaurar su billetera. Desactivar PIN - El código PIN está actualmente activado. Si desea desactivar su PIN, deberá introducir primero su código PIN actual. + Introduzca su <accent>PIN</accent> para desactivarlo. + El código PIN está actualmente activado. Si desea desactivarlo o cambiarlo, primero debe introducir su código PIN actual. PIN activado Por favor, introduzca su código PIN Resetear (requiere frase de recuperación) diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index ab1508667..ed1cc092d 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -477,7 +477,8 @@ Escolliu un PIN de 4 dígits Si us plau, utilitza un PIN que recordis. Si oblides el teu PIN pots restablir-lo, però caldrà restaurar la cartera. Desactiva el PIN - El codi PIN està actualment habilitat. Si vols desactivar el teu codi PIN, has d\'introduir primer el teu codi PIN actual. + Introdueix el teu <accent>PIN</accent> per desactivar-lo. + El codi PIN està activat. Si vols desactivar-lo o canviar-lo, primer has d\'introduir el teu codi PIN actual. PIN activat Si us plau, introduïu el vostre codi PIN Restablir (Es necessita la frase de recuperació) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 671c3fef6..66d5b2c99 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -477,7 +477,8 @@ Vyberte 4místný PIN Použijte prosím PIN, který si zapamatujete. Pokud svůj PIN zapomenete, můžete jej resetovat, ale to bude vyžadovat obnovení vaší peněženky. Zakázat PIN - PIN kód je aktuálně povolen. Pokud chcete deaktivovat svůj PIN, musíte nejprve zadat svůj aktuální PIN kód. + Zadejte svůj <accent>PIN</accent> pro jeho deaktivaci. + PIN kód je aktuálně povolen. Pokud jej chcete deaktivovat nebo změnit, musíte nejprve zadat aktuální PIN kód. PIN povolen Zadejte prosím svůj PIN kód Resetovat (vyžaduje obnovovací frázi) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index b8f4a719c..87c95226c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -463,8 +463,9 @@ 4-stelligen PIN erneut eingeben Bitte gib deinen 4-stelligen PIN erneut ein, um den Einrichtungsprozess abzuschließen. Versuche es erneut, dieser PIN stimmt nicht überein. + Der PIN-Code ist derzeit aktiviert. Wenn du ihn deaktivieren oder ändern möchtest, musst du zuerst deinen aktuellen PIN-Code eingeben. PIN aktiviert - Der PIN-Code ist derzeit aktiviert. Wenn du deinen PIN deaktivieren möchtest, musst du zuerst deinen aktuellen PIN-Code eingeben. + Geben Sie Ihre <accent>PIN</accent> ein, um sie zu deaktivieren. PIN deaktivieren Bitte gib deinen PIN-Code ein Letzter Versuch. Wenn du den falschen PIN erneut eingibst, wird dein Wallet zurückgesetzt. diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 1976cf71f..66ec26b43 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -477,7 +477,8 @@ Επίλεξε 4ψήφιο PIN Χρησιμοποίησε ένα PIN που θα θυμάσαι. Αν ξεχάσεις το PIN σου μπορείς να το επαναφέρεις, αλλά αυτό θα απαιτήσει επαναφορά του πορτοφολιού σου. Απενεργοποίηση PIN - Ο κωδικός PIN είναι ενεργοποιημένος. Αν θέλεις να απενεργοποιήσεις το PIN, πρέπει να εισάγεις πρώτα τον τρέχοντα κωδικό PIN. + Εισαγάγετε το <accent>PIN</accent> σας για να το απενεργοποιήσετε. + Ο κωδικός PIN είναι ενεργοποιημένος. Αν θέλετε να τον απενεργοποιήσετε ή να τον αλλάξετε, πρέπει πρώτα να εισαγάγετε τον τρέχοντα κωδικό PIN. PIN ενεργοποιημένο Εισάγαγε τον κωδικό PIN Επαναφορά (Απαιτείται φράση ανάκτησης) diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 73e1b6ba6..cf3cc0658 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -477,7 +477,8 @@ Elija un PIN de 4 dígitos Por favor, utilice un PIN que recuerde. Si olvida su PIN puede restablecerlo, pero para ello tendrá que restaurar su monedero. Desactivar PIN - El código PIN está actualmente activado. Si desea desactivar su PIN, deberá introducir primero su código PIN actual. + Introduzca su <accent>PIN</accent> para desactivarlo. + El código PIN está actualmente activado. Si desea desactivarlo o cambiarlo, primero debe introducir su código PIN actual. PIN activado Por favor, introduzca su código PIN Restablecer (requiere frase de recuperación) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index b26750f19..0884850ea 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -463,8 +463,9 @@ Vuelva a introducir el PIN de 4 dígitos Vuelva a introducir su PIN de 4 dígitos para completar el proceso de configuración. Inténtelo de nuevo, no es el mismo PIN. + El código PIN está actualmente activado. Si desea desactivarlo o cambiarlo, primero debe introducir su código PIN actual. PIN activado - El código PIN está actualmente activado. Si desea desactivar su PIN, deberá introducir primero su código PIN actual. + Introduzca su <accent>PIN</accent> para desactivarlo. Desactivar PIN Por favor, introduzca su código PIN Último intento. Si vuelve a introducir un PIN erróneo, se reiniciará su monedero. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 29e54fa04..effb03b3b 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -463,8 +463,9 @@ Retapez le code PIN à 4 chiffres Veuillez retaper votre code PIN à 4 chiffres pour terminer la procédure de configuration. Réessayez, il ne s\'agit pas du même code PIN. + Le code PIN est actuellement activé. Si vous souhaitez le désactiver ou le modifier, vous devez d\'abord saisir votre code PIN actuel. PIN activé - Le code PIN est actuellement activé. Si vous souhaitez désactiver votre code PIN, vous devez d\'abord saisir votre code PIN actuel. + Saisissez votre <accent>PIN</accent> pour le désactiver. Désactiver le code PIN Veuillez saisir votre code PIN Dernière tentative. Si vous saisissez à nouveau un code PIN erroné, votre portefeuille sera réinitialisé. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 65351ea14..b7af8bc88 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -477,7 +477,8 @@ Scegli il PIN a 4 cifre Utilizza un PIN che ricorderai. Se dimentichi il PIN, puoi reimpostarlo, ma ciò richiede il ripristino del wallet. Disabilita il PIN - Il codice PIN è abilitato. Se vuoi disattivare il PIN, prima inserisci il PIN corrente. + Inserisci il tuo <accent>PIN</accent> per disattivarlo. + Il codice PIN è attualmente attivato. Se vuoi disattivarlo o cambiarlo, devi prima inserire il codice PIN attuale. PIN attivato Inserisci il codice PIN Reset (Richiede la Recovery Phrase) diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 6239d6060..4c7a0f8c5 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -477,7 +477,8 @@ Kies 4-cijferige pincode Gebruik een pincode die je zult onthouden. Als je je pincode vergeet, kun je deze resetten, maar daarvoor moet je je wallet herstellen. Pincode uitschakelen - Pincode is momenteel ingeschakeld. Als je je pincode wilt uitschakelen, moet je eerst je huidige pincode invoeren. + Voer je <accent>PIN</accent> in om deze uit te schakelen. + De PIN is momenteel ingeschakeld. Als je deze wilt uitschakelen of wijzigen, moet je eerst je huidige PIN invoeren. PIN ingeschakeld Voer je pincode in Resetten (herstelzin vereist) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 6adad8a44..75de8c545 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -463,8 +463,9 @@ Wpisz ponownie 4 cyfrowy kod PIN Wpisz proszę ponownie 4 cyfrowy kod PIN aby zakończyć konfigurację. Spróbuj ponownie, kody PIN się różnią. + Kod PIN jest obecnie włączony. Jeśli chcesz go wyłączyć lub zmienić, najpierw wprowadź aktualny kod PIN. PIN włączony - Kod PIN jest obecnie aktywny. Jeśli chcesz go wyłączyć pierw musisz podać aktualny kod PIN. + Wprowadź swój <accent>PIN</accent>, aby go wyłączyć. Wyłącz kod PIN Podaj proszę kod PIN Ostatnia próba. Wpisanie błędnego kodu spowoduje zresetowanie portfela. diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 38ac33de4..47f9434f7 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -477,7 +477,8 @@ Escolha o PIN de 4 dígitos Use um PIN que você se lembrará. Se você esquecer o PIN, poderá redefini-lo mas isso exigirá a restauração da carteira. Desativar PIN - O código PIN está ativado no momento. Se quiser desativar o PIN, você precisará, primeiro, digitar o código PIN atual. + Digite seu <accent>PIN</accent> para desativá-lo. + O código PIN está ativado. Se deseja desativá-lo ou alterá-lo, primeiro digite seu código PIN atual. PIN ativado Por favor, digite seu código PIN Restaurar (Requer a Frase de Recuperação) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 191fa2f7d..1bdbf4574 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -462,8 +462,9 @@ Digite novamente o PIN de 4 dígitos Digite novamente o PIN de 4 dígitos para concluir o processo de configuração. Tente novamente, este não é o mesmo PIN. + O código PIN está ativado. Se deseja desativá-lo ou alterá-lo, primeiro digite seu código PIN atual. PIN ativado - O código PIN está ativado no momento. Se quiser desativar o PIN, você precisará, primeiro, digitar o código PIN atual. + Digite seu <accent>PIN</accent> para desativá-lo. Desativar PIN Por favor, digite seu código PIN Última tentativa. Se digitar o PIN errado novamente, sua carteira será redefinida. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 704231149..c9222812c 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -491,7 +491,8 @@ Выберите 4-значный PIN-код Пожалуйста, используйте PIN-код, который вы запомните. Если вы забудете свой PIN-код, вы можете сбросить его, но для этого потребуется восстановить кошелёк. Отключить PIN-код - PIN-код в настоящее время включен. Если вы хотите отключить свой PIN-код, вам необходимо сначала ввести текущий PIN-код. + Введите <accent>PIN</accent>, чтобы отключить его. + PIN-код в настоящее время включён. Если вы хотите отключить или изменить PIN, сначала введите текущий PIN-код. PIN включён Пожалуйста, введите PIN-код Сбросить (требуется Фраза Восстановления) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c46464f89..692c88afc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -485,7 +485,8 @@ Please use a PIN you will remember. If you forget your PIN you can reset it, but that will require restoring your wallet. Disable PIN Enable PIN - PIN code is currently enabled. If you want to disable your PIN, you need to enter your current PIN code first. + Enter your <accent>PIN</accent> to disable it. + PIN code is currently enabled. If you want to disable or change your PIN, you need to enter your current PIN code first. PIN Enabled Please enter your PIN code Reset (Requires Recovery Phrase) From 68112bd6876662117b821c0ec90fd49c403239ec Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Tue, 24 Mar 2026 10:25:54 -0300 Subject: [PATCH 28/85] feat: add reset widgets option --- .../settings/general/WidgetsSettingsScreen.kt | 26 ++++++++++++++++--- .../to/bitkit/viewmodels/SettingsViewModel.kt | 11 ++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt index bcf01e555..05abf6f8f 100644 --- a/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt @@ -42,9 +42,8 @@ fun WidgetsSettingsScreen( showWidgetTitles = showWidgetTitles, onShowWidgetsClick = { settings.setShowWidgets(!showWidgets) }, onShowWidgetTitlesClick = { settings.setShowWidgetTitles(!showWidgetTitles) }, - onResetSuggestionsClick = { - settings.resetDismissedSuggestions() - }, + onResetWidgetsClick = { settings.resetWidgets() }, + onResetSuggestionsClick = { settings.resetDismissedSuggestions() }, ) } @@ -55,8 +54,10 @@ private fun WidgetsSettingsContent( onBackClick: () -> Unit = {}, onShowWidgetsClick: () -> Unit = {}, onShowWidgetTitlesClick: () -> Unit = {}, + onResetWidgetsClick: () -> Unit = {}, onResetSuggestionsClick: () -> Unit = {}, ) { + var showResetWidgetsDialog by remember { mutableStateOf(false) } var showResetSuggestionsDialog by remember { mutableStateOf(false) } ScreenColumn { @@ -90,6 +91,11 @@ private fun WidgetsSettingsContent( padding = androidx.compose.foundation.layout.PaddingValues(top = 16.dp), ) + SettingsButtonRow( + title = stringResource(R.string.settings__widgets__reset_widgets), + onClick = { showResetWidgetsDialog = true }, + modifier = Modifier.testTag("ResetWidgets"), + ) SettingsButtonRow( title = stringResource(R.string.settings__widgets__reset_suggestions), onClick = { showResetSuggestionsDialog = true }, @@ -97,6 +103,20 @@ private fun WidgetsSettingsContent( ) } + if (showResetWidgetsDialog) { + AppAlertDialog( + title = stringResource(R.string.settings__widgets__reset_widgets_dialog_title), + text = stringResource(R.string.settings__widgets__reset_widgets_dialog_description), + confirmText = stringResource(R.string.settings__adv__reset_confirm), + onConfirm = { + onResetWidgetsClick() + showResetWidgetsDialog = false + }, + onDismiss = { showResetWidgetsDialog = false }, + modifier = Modifier.testTag("reset_widgets_dialog"), + ) + } + if (showResetSuggestionsDialog) { AppAlertDialog( title = stringResource(R.string.settings__adv__reset_title), diff --git a/app/src/main/java/to/bitkit/viewmodels/SettingsViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/SettingsViewModel.kt index 022fc0ef9..bb030fcb3 100644 --- a/app/src/main/java/to/bitkit/viewmodels/SettingsViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/SettingsViewModel.kt @@ -10,13 +10,17 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import to.bitkit.data.SettingsStore +import to.bitkit.data.WidgetsStore import to.bitkit.models.TransactionSpeed +import to.bitkit.repositories.WidgetsRepo import javax.inject.Inject @Suppress("TooManyFunctions") @HiltViewModel class SettingsViewModel @Inject constructor( private val settingsStore: SettingsStore, + private val widgetsStore: WidgetsStore, + private val widgetsRepo: WidgetsRepo, ) : ViewModel() { fun reset() = viewModelScope.launch { settingsStore.reset() } @@ -172,6 +176,13 @@ class SettingsViewModel @Inject constructor( } } + fun resetWidgets() { + viewModelScope.launch { + widgetsStore.reset() + widgetsRepo.refreshEnabledWidgets() + } + } + val lastUsedTags = settingsStore.data.map { it.lastUsedTags } .asStateFlow(initialValue = emptyList()) From 886380301fbaeee0e3d05b78cc8a1ed47df00f48 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Tue, 24 Mar 2026 10:41:48 -0300 Subject: [PATCH 29/85] feat: update dialog text --- app/src/main/res/values-ar/strings.xml | 2 +- app/src/main/res/values-b+es+419/strings.xml | 2 +- app/src/main/res/values-ca/strings.xml | 2 +- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-el/strings.xml | 2 +- app/src/main/res/values-es-rES/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values-nl/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-pt/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 231032049..510350936 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -549,7 +549,7 @@ اتصالات Lightning عقدة Lightning نعم، أعد التعيين - هل أنت متأكد من رغبتك في إعادة تعيين الاقتراحات؟ ستظهر مرة أخرى في حال إزالتها من نظرة عامة محفظة Bitkit. + هل أنت متأكد من رغبتك في إعادة تعيين الاقتراحات؟ ستظهر مرة أخرى في حال إزالتها. إعادة تعيين الاقتراحات؟ Rapid-Gossip-Sync الشبكات diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index 84aec5456..74e8531df 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -549,7 +549,7 @@ Conexiones Lightning Nodo Lightning Sí, Restaurar - ¿Está seguro de que desea restablecer las sugerencias? Volverán a aparecer en caso de que las haya eliminado de la vista general de su monedero Bitkit. + ¿Está seguro de que desea restablecer las sugerencias? Volverán a aparecer en caso de que las haya eliminado. ¿Restablecer sugerencias? Rapid-Gossip-Sync Redes diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index ed1cc092d..24b138c57 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -549,7 +549,7 @@ Connexions Lightning Node de Lightning Sí, reinicia - Estàs segur que vols restablir els suggeriments? Tornaran a aparèixer si els has eliminat del resum de la teva cartera Bitkit. + Estàs segur que vols restablir els suggeriments? Tornaran a aparèixer si els has eliminat. Restablir suggeriments? Rapid-Gossip-Sync Xarxes diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 66d5b2c99..436f4b51a 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -549,7 +549,7 @@ Lightning spojení Lightning uzel Ano, resetovat - Opravdu chcete návrhy resetovat? Znovu se objeví v případě, že jste je odstranili z přehledu peněženky Bitkit. + Opravdu chcete návrhy resetovat? Znovu se objeví v případě, že jste je odstranili. Resetovat návrhy? Rapid-Gossip-Sync Sítě diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 87c95226c..9bed949d5 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -668,7 +668,7 @@ Adressanzeige Vorschläge zurücksetzen Vorschläge zurücksetzen? - Bist du dir sicher, dass du die Vorschläge löschen möchtest? Diese werden erneut angezeigt, wenn du sie aus deiner Bitkit Wallet Übersicht entfernt hast. + Bist du dir sicher, dass du die Vorschläge löschen möchtest? Diese werden erneut angezeigt, wenn du sie entfernt hast. Ja, zurücksetzen Lightning Verbindungen Lightning Node diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 66ec26b43..8e7052836 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -549,7 +549,7 @@ Συνδέσεις Lightning Κόμβος Lightning Ναι, Επαναφορά - Είσαι σίγουρος ότι θέλεις να επαναφέρεις τις προτάσεις; Θα επανεμφανιστούν σε περίπτωση που τις είχες αφαιρέσει από την επισκόπηση του πορτοφολιού Bitkit. + Είσαι σίγουρος ότι θέλεις να επαναφέρεις τις προτάσεις; Θα επανεμφανιστούν σε περίπτωση που τις είχες αφαιρέσει. Επαναφορά προτάσεων; Rapid-Gossip-Sync Δίκτυα diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index cf3cc0658..5fd669656 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -549,7 +549,7 @@ Conexiones Lightning Nodo Lightning Sí, restaurar - ¿Estás seguro de que deseas restablecer las sugerencias? Volverán a aparecer en caso de que las hayas eliminado de la vista general de tu monedero Bitkit. + ¿Estás seguro de que deseas restablecer las sugerencias? Volverán a aparecer en caso de que las hayas eliminado. ¿Restablecer sugerencias? Rapid-Gossip-Sync Redes diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 0884850ea..d2c3e8bea 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -668,7 +668,7 @@ Visualizador de direcciones Restablecer sugerencias ¿Restablecer sugerencias? - ¿Estás seguro de que deseas restablecer las sugerencias? Volverán a aparecer en caso de que las hayas eliminado de la vista general de tu monedero Bitkit. + ¿Estás seguro de que deseas restablecer las sugerencias? Volverán a aparecer en caso de que las hayas eliminado. Sí, restaurar Conexiones Lightning Nodo Lightning diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index effb03b3b..26d37ce68 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -668,7 +668,7 @@ Visualisateur d\'adresses Suggestions de réinitialisation Suggestions de réinitialisation ? - Êtes-vous sûr de vouloir réinitialiser les suggestions ? Elles réapparaîtront si vous les avez supprimées de l\'aperçu de votre portefeuille Bitkit. + Êtes-vous sûr de vouloir réinitialiser les suggestions ? Elles réapparaîtront si vous les avez supprimées. Oui, Réinitialisation Connexions Lightning Nœud Lightning diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index b7af8bc88..35c7ac2b7 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -549,7 +549,7 @@ Connessioni Lightning Nodo Lightning Sì, resetta - Sei sicuro di voler resettare i suggerimenti? Riappariranno nel caso in cui li abbia rimossi dalla panoramica del portafoglio Bitkit. + Sei sicuro di voler resettare i suggerimenti? Riappariranno nel caso in cui li abbia rimossi. Resettare i Suggerimenti? Rapid-Gossip-Sync Reti diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 4c7a0f8c5..b609805bb 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -549,7 +549,7 @@ Lightning-verbindingen Lightning Node Ja, resetten - Weet je zeker dat je de suggesties wilt resetten? Ze verschijnen opnieuw als je ze uit je Bitkit-walletoverzicht hebt verwijderd. + Weet je zeker dat je de suggesties wilt resetten? Ze verschijnen opnieuw als je ze hebt verwijderd. Suggesties resetten? Rapid-Gossip-Sync Netwerken diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 75de8c545..ec29c159e 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -668,7 +668,7 @@ Przeglądarka adresów Resetuj sugestie Resetuj sugestie? - Zresetować sugestie? Usunięte elementy pojawią się ponownie w widoku portfela Bitkit. + Zresetować sugestie? Usunięte elementy pojawią się ponownie. Tak, zresetuj Połączenia z Lightning Węzeł Lightning diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 47f9434f7..92bcc5db5 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -549,7 +549,7 @@ Conexões Lightning Nó Lightning Sim, Resetar - Tem certeza de que deseja resetar as sugestões? Eles reaparecerão na tela inicial caso você tenha removido. + Tem certeza de que deseja resetar as sugestões? Eles reaparecerão caso você tenha removido. Resetar Sugestões? Servidor Rapid-Gossip-Sync Redes diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 1bdbf4574..572075998 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -649,7 +649,7 @@ Visualizador de Endereços Resetar Sugestões Resetar Sugestões? - Tem certeza de que deseja resetar as sugestões? Eles reaparecerão na tela inicial caso você tenha removido. + Tem certeza de que deseja resetar as sugestões? Eles reaparecerão caso você tenha removido. Sim, Resetar Conexões Lightning Nó Lightning diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 692c88afc..fea931bfb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -569,7 +569,7 @@ Lightning Connections Lightning Node Yes, Reset - Are you sure you want to reset the suggestions? They will reappear in case you have removed them from your Bitkit wallet overview. + Are you sure you want to reset the suggestions? They will reappear in case you have removed them. Reset Suggestions? Rapid-Gossip-Sync Debug From cc75236bcf73d594b6fdd2cd0d8e96e02b010ccf Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Tue, 24 Mar 2026 11:01:50 -0300 Subject: [PATCH 30/85] feat: lighting connections count --- .../to/bitkit/ui/settings/AdvancedSettingsViewModel.kt | 7 +++++++ .../main/java/to/bitkit/ui/settings/SettingsScreen.kt | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsViewModel.kt b/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsViewModel.kt index 3fe3a0ede..0d8505c80 100644 --- a/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsViewModel.kt +++ b/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsViewModel.kt @@ -8,19 +8,26 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import to.bitkit.data.SettingsStore +import to.bitkit.ext.filterOpen import to.bitkit.models.addressTypeInfo import to.bitkit.models.toAddressType +import to.bitkit.repositories.LightningRepo import javax.inject.Inject @HiltViewModel class AdvancedSettingsViewModel @Inject constructor( private val settingsStore: SettingsStore, + private val lightningRepo: LightningRepo, ) : ViewModel() { val selectedAddressTypeName = settingsStore.data .map { it.selectedAddressType.toAddressType()?.addressTypeInfo()?.shortName ?: "" } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), "") + val openChannelCount = lightningRepo.lightningState + .map { it.channels.filterOpen().size } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), 0) + fun resetSuggestions() { viewModelScope.launch { settingsStore.update { it.copy(dismissedSuggestions = emptyList()) } diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index 8f668384c..ce0cb0b54 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -98,6 +98,7 @@ fun SettingsScreen( // Advanced tab state val isDevModeEnabled by settings.isDevModeEnabled.collectAsStateWithLifecycle() val selectedAddressTypeName by advancedViewModel.selectedAddressTypeName.collectAsStateWithLifecycle() + val openChannelCount by advancedViewModel.openChannelCount.collectAsStateWithLifecycle() LaunchedEffect(Unit) { languageViewModel.fetchLanguageInfo() } @@ -162,6 +163,7 @@ fun SettingsScreen( // Advanced isDevModeEnabled = isDevModeEnabled, selectedAddressTypeName = selectedAddressTypeName, + openChannelCount = openChannelCount, onDevSettingsClick = { navController.navigateToDevSettings() }, onAddressTypeClick = { navController.navigateTo(Routes.AddressTypePreference) }, onCoinSelectionClick = { navController.navigateTo(Routes.CoinSelectPreference) }, @@ -217,6 +219,7 @@ private fun SettingsContent( // Advanced isDevModeEnabled: Boolean = false, selectedAddressTypeName: String = "", + openChannelCount: Int = 0, onDevSettingsClick: () -> Unit = {}, onAddressTypeClick: () -> Unit = {}, onCoinSelectionClick: () -> Unit = {}, @@ -296,6 +299,7 @@ private fun SettingsContent( SettingsTab.Advanced -> AdvancedTabContent( isDevModeEnabled = isDevModeEnabled, selectedAddressTypeName = selectedAddressTypeName, + openChannelCount = openChannelCount, onDevSettingsClick = onDevSettingsClick, onAddressTypeClick = onAddressTypeClick, onCoinSelectionClick = onCoinSelectionClick, @@ -567,6 +571,7 @@ private fun SecurityTabContent( private fun AdvancedTabContent( isDevModeEnabled: Boolean, selectedAddressTypeName: String, + openChannelCount: Int, onDevSettingsClick: () -> Unit, onAddressTypeClick: () -> Unit, onCoinSelectionClick: () -> Unit, @@ -631,6 +636,11 @@ private fun AdvancedTabContent( SettingsButtonRow( title = stringResource(R.string.settings__adv__lightning_connections), icon = { SettingsIcon(R.drawable.ic_lightning) }, + value = if (openChannelCount > 0) { + SettingsButtonValue.StringValue(openChannelCount.toString()) + } else { + SettingsButtonValue.None + }, onClick = onLightningConnectionsClick, modifier = Modifier.testTag("Channels"), ) From 51f01498b81fc6ee2abd3ced2405d3266c4e838b Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Tue, 24 Mar 2026 11:12:53 -0300 Subject: [PATCH 31/85] feat: display node id --- .../to/bitkit/ui/settings/AdvancedSettingsViewModel.kt | 6 ++++++ .../main/java/to/bitkit/ui/settings/SettingsScreen.kt | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsViewModel.kt b/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsViewModel.kt index 0d8505c80..069434a60 100644 --- a/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsViewModel.kt +++ b/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsViewModel.kt @@ -14,6 +14,8 @@ import to.bitkit.models.toAddressType import to.bitkit.repositories.LightningRepo import javax.inject.Inject +private const val NODE_ID_PREFIX_LENGTH = 5 + @HiltViewModel class AdvancedSettingsViewModel @Inject constructor( private val settingsStore: SettingsStore, @@ -28,6 +30,10 @@ class AdvancedSettingsViewModel @Inject constructor( .map { it.channels.filterOpen().size } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), 0) + val truncatedNodeId = lightningRepo.lightningState + .map { it.nodeId.take(NODE_ID_PREFIX_LENGTH).ifEmpty { "" } } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), "") + fun resetSuggestions() { viewModelScope.launch { settingsStore.update { it.copy(dismissedSuggestions = emptyList()) } diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index ce0cb0b54..2d75d07dd 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -99,6 +99,7 @@ fun SettingsScreen( val isDevModeEnabled by settings.isDevModeEnabled.collectAsStateWithLifecycle() val selectedAddressTypeName by advancedViewModel.selectedAddressTypeName.collectAsStateWithLifecycle() val openChannelCount by advancedViewModel.openChannelCount.collectAsStateWithLifecycle() + val truncatedNodeId by advancedViewModel.truncatedNodeId.collectAsStateWithLifecycle() LaunchedEffect(Unit) { languageViewModel.fetchLanguageInfo() } @@ -164,6 +165,7 @@ fun SettingsScreen( isDevModeEnabled = isDevModeEnabled, selectedAddressTypeName = selectedAddressTypeName, openChannelCount = openChannelCount, + truncatedNodeId = truncatedNodeId, onDevSettingsClick = { navController.navigateToDevSettings() }, onAddressTypeClick = { navController.navigateTo(Routes.AddressTypePreference) }, onCoinSelectionClick = { navController.navigateTo(Routes.CoinSelectPreference) }, @@ -220,6 +222,7 @@ private fun SettingsContent( isDevModeEnabled: Boolean = false, selectedAddressTypeName: String = "", openChannelCount: Int = 0, + truncatedNodeId: String = "", onDevSettingsClick: () -> Unit = {}, onAddressTypeClick: () -> Unit = {}, onCoinSelectionClick: () -> Unit = {}, @@ -300,6 +303,7 @@ private fun SettingsContent( isDevModeEnabled = isDevModeEnabled, selectedAddressTypeName = selectedAddressTypeName, openChannelCount = openChannelCount, + truncatedNodeId = truncatedNodeId, onDevSettingsClick = onDevSettingsClick, onAddressTypeClick = onAddressTypeClick, onCoinSelectionClick = onCoinSelectionClick, @@ -572,6 +576,7 @@ private fun AdvancedTabContent( isDevModeEnabled: Boolean, selectedAddressTypeName: String, openChannelCount: Int, + truncatedNodeId: String, onDevSettingsClick: () -> Unit, onAddressTypeClick: () -> Unit, onCoinSelectionClick: () -> Unit, @@ -647,6 +652,11 @@ private fun AdvancedTabContent( SettingsButtonRow( title = stringResource(R.string.settings__adv__lightning_node), icon = { SettingsIcon(R.drawable.ic_git_branch) }, + value = if (truncatedNodeId.isNotEmpty()) { + SettingsButtonValue.StringValue("$truncatedNodeId...") + } else { + SettingsButtonValue.None + }, onClick = onLightningNodeClick, modifier = Modifier.testTag("LightningNodeInfo"), ) From 2d9959a6bb12fe9cb91a31dc3c7cc98e6dc8e114 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Tue, 24 Mar 2026 11:26:53 -0300 Subject: [PATCH 32/85] feat: widget settings icons --- .../components/settings/SettingsButtonRow.kt | 2 +- .../settings/general/WidgetsSettingsScreen.kt | 28 ++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt b/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt index 7210cd079..c8489e42d 100644 --- a/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt +++ b/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt @@ -66,8 +66,8 @@ fun SettingsButtonRow( contentDescription = null, tint = iconTint, modifier = Modifier + .padding(end = 10.dp) .size(iconSize) - .padding(end = 10.dp), ) } } else { diff --git a/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt index 05abf6f8f..326c4844d 100644 --- a/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt @@ -2,8 +2,10 @@ package to.bitkit.ui.settings.general import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -11,6 +13,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -19,6 +22,7 @@ import androidx.navigation.NavController import to.bitkit.R import to.bitkit.ui.components.settings.SectionHeader import to.bitkit.ui.components.settings.SettingsButtonRow +import to.bitkit.ui.components.settings.SettingsIcon import to.bitkit.ui.components.settings.SettingsSwitchRow import to.bitkit.ui.scaffold.AppAlertDialog import to.bitkit.ui.scaffold.AppTopBar @@ -26,6 +30,7 @@ import to.bitkit.ui.scaffold.DrawerNavIcon import to.bitkit.ui.scaffold.ScreenColumn import to.bitkit.ui.settingsViewModel import to.bitkit.ui.theme.AppThemeSurface +import to.bitkit.ui.theme.Colors @Composable fun WidgetsSettingsScreen( @@ -93,12 +98,33 @@ private fun WidgetsSettingsContent( SettingsButtonRow( title = stringResource(R.string.settings__widgets__reset_widgets), + icon = { + Icon( + painter = painterResource(R.drawable.ic_arrow_counter_clockwise), + contentDescription = null, + tint = Colors.Brand, + modifier = Modifier + .padding(8.dp) + .padding(end = 8.dp) + .size(16.dp) + ) + }, onClick = { showResetWidgetsDialog = true }, modifier = Modifier.testTag("ResetWidgets"), ) SettingsButtonRow( title = stringResource(R.string.settings__widgets__reset_suggestions), - onClick = { showResetSuggestionsDialog = true }, + icon = { + Icon( + painter = painterResource(R.drawable.ic_arrow_counter_clockwise), + contentDescription = null, + tint = Colors.Brand, + modifier = Modifier + .padding(8.dp) + .padding(end = 8.dp) + .size(16.dp) + ) + }, onClick = { showResetSuggestionsDialog = true }, modifier = Modifier.testTag("ResetSuggestions"), ) } From b8feb2a42647a35eb7796d92590ff0aa792e5110 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Tue, 24 Mar 2026 11:41:18 -0300 Subject: [PATCH 33/85] feat: default language name --- app/src/main/java/to/bitkit/models/Language.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/models/Language.kt b/app/src/main/java/to/bitkit/models/Language.kt index c590b5903..1ae73724c 100644 --- a/app/src/main/java/to/bitkit/models/Language.kt +++ b/app/src/main/java/to/bitkit/models/Language.kt @@ -11,7 +11,7 @@ enum class Language( val isSystemDefault: Boolean = false, ) { SYSTEM_DEFAULT( - displayName = "System Default", + displayName = "System Settings", languageCode = "system", countryCode = null, isSystemDefault = true From 30200a6020acb38f0f90c694dcbbfa412edbb9b1 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Tue, 24 Mar 2026 13:43:10 -0300 Subject: [PATCH 34/85] feat: display currency unit --- app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index d1bb5e5de..cceb487c8 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -107,6 +107,7 @@ fun SettingsScreen( SettingsContent( // General selectedCurrency = currencies.selectedCurrency, + currencySymbol = currencies.currencySymbol, primaryDisplay = currencies.primaryDisplay, defaultTransactionSpeed = defaultTransactionSpeed, selectedLanguage = languageUiState.selectedLanguage.displayName, @@ -185,6 +186,7 @@ fun SettingsScreen( private fun SettingsContent( // General selectedCurrency: String = "USD", + currencySymbol: String = "$", primaryDisplay: PrimaryDisplay = PrimaryDisplay.BITCOIN, defaultTransactionSpeed: TransactionSpeed = TransactionSpeed.Medium, selectedLanguage: String = "", @@ -262,6 +264,7 @@ private fun SettingsContent( when (tabs[page]) { SettingsTab.General -> GeneralTabContent( selectedCurrency = selectedCurrency, + currencySymbol = currencySymbol, primaryDisplay = primaryDisplay, defaultTransactionSpeed = defaultTransactionSpeed, selectedLanguage = selectedLanguage, @@ -322,6 +325,7 @@ private fun SettingsContent( @Composable private fun GeneralTabContent( selectedCurrency: String, + currencySymbol: String, primaryDisplay: PrimaryDisplay, defaultTransactionSpeed: TransactionSpeed, selectedLanguage: String, @@ -357,7 +361,7 @@ private fun GeneralTabContent( SettingsButtonRow( title = stringResource(R.string.settings__general__currency_local), icon = { SettingsIcon(R.drawable.ic_coins) }, - value = SettingsButtonValue.StringValue(selectedCurrency), + value = SettingsButtonValue.StringValue("$selectedCurrency ($currencySymbol)"), onClick = onLocalCurrencyClick, modifier = Modifier.testTag("CurrenciesSettings"), ) From eb7e6a1fdd175273ef3b6938007b0d42ce61d5ed Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Tue, 24 Mar 2026 14:03:21 -0300 Subject: [PATCH 35/85] feat: display electrum server value --- .../ui/settings/AdvancedSettingsViewModel.kt | 5 +++++ .../java/to/bitkit/ui/settings/SettingsScreen.kt | 14 ++++++++++++++ app/src/main/res/values/strings.xml | 2 ++ 3 files changed, 21 insertions(+) diff --git a/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsViewModel.kt b/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsViewModel.kt index 069434a60..f0d885525 100644 --- a/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsViewModel.kt +++ b/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsViewModel.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import to.bitkit.data.SettingsStore +import to.bitkit.env.Env import to.bitkit.ext.filterOpen import to.bitkit.models.addressTypeInfo import to.bitkit.models.toAddressType @@ -34,6 +35,10 @@ class AdvancedSettingsViewModel @Inject constructor( .map { it.nodeId.take(NODE_ID_PREFIX_LENGTH).ifEmpty { "" } } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), "") + val isCustomElectrum = settingsStore.data + .map { it.electrumServer != Env.electrumServerUrl } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false) + fun resetSuggestions() { viewModelScope.launch { settingsStore.update { it.copy(dismissedSuggestions = emptyList()) } diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index cceb487c8..b9ad07494 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -101,6 +101,7 @@ fun SettingsScreen( val selectedAddressTypeName by advancedViewModel.selectedAddressTypeName.collectAsStateWithLifecycle() val openChannelCount by advancedViewModel.openChannelCount.collectAsStateWithLifecycle() val truncatedNodeId by advancedViewModel.truncatedNodeId.collectAsStateWithLifecycle() + val isCustomElectrum by advancedViewModel.isCustomElectrum.collectAsStateWithLifecycle() LaunchedEffect(Unit) { languageViewModel.fetchLanguageInfo() } @@ -168,6 +169,7 @@ fun SettingsScreen( selectedAddressTypeName = selectedAddressTypeName, openChannelCount = openChannelCount, truncatedNodeId = truncatedNodeId, + isCustomElectrum = isCustomElectrum, onDevSettingsClick = { navController.navigateToDevSettings() }, onAddressTypeClick = { navController.navigateTo(Routes.AddressTypePreference) }, onCoinSelectionClick = { navController.navigateTo(Routes.CoinSelectPreference) }, @@ -226,6 +228,7 @@ private fun SettingsContent( selectedAddressTypeName: String = "", openChannelCount: Int = 0, truncatedNodeId: String = "", + isCustomElectrum: Boolean = false, onDevSettingsClick: () -> Unit = {}, onAddressTypeClick: () -> Unit = {}, onCoinSelectionClick: () -> Unit = {}, @@ -308,6 +311,7 @@ private fun SettingsContent( selectedAddressTypeName = selectedAddressTypeName, openChannelCount = openChannelCount, truncatedNodeId = truncatedNodeId, + isCustomElectrum = isCustomElectrum, onDevSettingsClick = onDevSettingsClick, onAddressTypeClick = onAddressTypeClick, onCoinSelectionClick = onCoinSelectionClick, @@ -582,6 +586,7 @@ private fun AdvancedTabContent( selectedAddressTypeName: String, openChannelCount: Int, truncatedNodeId: String, + isCustomElectrum: Boolean, onDevSettingsClick: () -> Unit, onAddressTypeClick: () -> Unit, onCoinSelectionClick: () -> Unit, @@ -668,6 +673,15 @@ private fun AdvancedTabContent( SettingsButtonRow( title = stringResource(R.string.settings__adv__electrum_server), icon = { SettingsIcon(R.drawable.ic_hard_drives) }, + value = SettingsButtonValue.StringValue( + stringResource( + if (isCustomElectrum) { + R.string.settings__adv__electrum_custom + } else { + R.string.settings__adv__electrum_auto + } + ) + ), onClick = onElectrumServerClick, modifier = Modifier.testTag("ElectrumConfig"), ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fea931bfb..e75a6cec2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -565,6 +565,8 @@ Coin Selection Method Largest First Sort by and use largest UTXO first. Potentially lower fee, but reveals your largest UTXOs. + Auto + Custom Electrum Server Lightning Connections Lightning Node From 535af31a1b73822b42f0442217be69f21888c105 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Tue, 24 Mar 2026 14:08:27 -0300 Subject: [PATCH 36/85] feat: update preview --- .../java/to/bitkit/ui/settings/SettingsScreen.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index b9ad07494..c0aff01ba 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -700,7 +700,17 @@ private fun AdvancedTabContent( @Composable private fun PreviewGeneral() { AppThemeSurface { - SettingsContent(tagCount = 3) + SettingsContent( + selectedCurrency = "USD", + currencySymbol = "$", + primaryDisplay = PrimaryDisplay.BITCOIN, + defaultTransactionSpeed = TransactionSpeed.Medium, + selectedLanguage = "System Settings", + showWidgets = true, + tagCount = 8, + isQuickPayEnabled = true, + notificationsGranted = true, + ) } } @@ -725,6 +735,8 @@ private fun PreviewAdvanced() { SettingsContent( isDevModeEnabled = true, selectedAddressTypeName = "Taproot", + openChannelCount = 2, + truncatedNodeId = "34sdx", initialTab = SettingsTab.Advanced, ) } From b1fd4d7202c8f64eca733eb0cc46866c1b063b5b Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Tue, 24 Mar 2026 14:23:45 -0300 Subject: [PATCH 37/85] feat: default unit icon --- .../general/DefaultUnitSettingsScreen.kt | 28 +++++++++++++++++-- app/src/main/res/drawable/ic_unit_fiat.xml | 24 ++++++++-------- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/general/DefaultUnitSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/general/DefaultUnitSettingsScreen.kt index 95893d2f7..4241486db 100644 --- a/app/src/main/java/to/bitkit/ui/settings/general/DefaultUnitSettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/general/DefaultUnitSettingsScreen.kt @@ -4,11 +4,14 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -26,6 +29,7 @@ import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.DrawerNavIcon import to.bitkit.ui.scaffold.ScreenColumn import to.bitkit.ui.theme.AppThemeSurface +import to.bitkit.ui.theme.Colors import to.bitkit.viewmodels.CurrencyViewModel @Composable @@ -73,7 +77,17 @@ fun DefaultUnitSettingsScreenContent( SettingsButtonRow( title = stringResource(R.string.settings__general__unit_bitcoin), - iconRes = R.drawable.ic_unit_bitcoin, + icon = { + Icon( + painter = painterResource(R.drawable.ic_bitcoin_modern), + contentDescription = null, + tint = Colors.White, + modifier = Modifier + .padding(8.dp) + .padding(end = 8.dp) + .size(16.dp) + ) + }, value = SettingsButtonValue.BooleanValue(primaryDisplay == PrimaryDisplay.BITCOIN), onClick = { onPrimaryUnitClick(PrimaryDisplay.BITCOIN) }, modifier = Modifier.testTag(stringResource(R.string.settings__general__unit_bitcoin)) @@ -81,7 +95,17 @@ fun DefaultUnitSettingsScreenContent( SettingsButtonRow( title = selectedCurrency, - iconRes = R.drawable.ic_unit_fiat, + icon = { + Icon( + painter = painterResource(R.drawable.ic_unit_fiat), + contentDescription = null, + tint = Colors.White, + modifier = Modifier + .padding(8.dp) + .padding(end = 8.dp) + .size(16.dp) + ) + }, value = SettingsButtonValue.BooleanValue(primaryDisplay == PrimaryDisplay.FIAT), onClick = { onPrimaryUnitClick(PrimaryDisplay.FIAT) }, modifier = Modifier.testTag(selectedCurrency) diff --git a/app/src/main/res/drawable/ic_unit_fiat.xml b/app/src/main/res/drawable/ic_unit_fiat.xml index 9e583dd7e..5b520195c 100644 --- a/app/src/main/res/drawable/ic_unit_fiat.xml +++ b/app/src/main/res/drawable/ic_unit_fiat.xml @@ -1,27 +1,27 @@ + android:width="16dp" + android:height="16dp" + android:viewportWidth="16" + android:viewportHeight="16"> - + From a43ab530800b7fdb27358834b413f0d7a4499e2e Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Tue, 24 Mar 2026 14:49:39 -0300 Subject: [PATCH 38/85] refactor: use SettingsIcon --- .../main/java/to/bitkit/ui/NodeInfoScreen.kt | 2 -- .../general/DefaultUnitSettingsScreen.kt | 29 ++----------------- .../settings/general/WidgetsSettingsScreen.kt | 29 ++----------------- .../to/bitkit/ui/sheets/DisablePinSheet.kt | 1 - 4 files changed, 6 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/NodeInfoScreen.kt b/app/src/main/java/to/bitkit/ui/NodeInfoScreen.kt index 9b6440dae..16216bc09 100644 --- a/app/src/main/java/to/bitkit/ui/NodeInfoScreen.kt +++ b/app/src/main/java/to/bitkit/ui/NodeInfoScreen.kt @@ -20,7 +20,6 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue - import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -35,7 +34,6 @@ import androidx.navigation.NavController import com.synonym.bitkitcore.ILspNode import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf - import org.lightningdevkit.ldknode.BalanceDetails import org.lightningdevkit.ldknode.BalanceSource import org.lightningdevkit.ldknode.BestBlock diff --git a/app/src/main/java/to/bitkit/ui/settings/general/DefaultUnitSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/general/DefaultUnitSettingsScreen.kt index 4241486db..d67cbce69 100644 --- a/app/src/main/java/to/bitkit/ui/settings/general/DefaultUnitSettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/general/DefaultUnitSettingsScreen.kt @@ -4,14 +4,11 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -25,11 +22,11 @@ import to.bitkit.ui.components.settings.SectionFooter import to.bitkit.ui.components.settings.SectionHeader import to.bitkit.ui.components.settings.SettingsButtonRow import to.bitkit.ui.components.settings.SettingsButtonValue +import to.bitkit.ui.components.settings.SettingsIcon import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.DrawerNavIcon import to.bitkit.ui.scaffold.ScreenColumn import to.bitkit.ui.theme.AppThemeSurface -import to.bitkit.ui.theme.Colors import to.bitkit.viewmodels.CurrencyViewModel @Composable @@ -77,17 +74,7 @@ fun DefaultUnitSettingsScreenContent( SettingsButtonRow( title = stringResource(R.string.settings__general__unit_bitcoin), - icon = { - Icon( - painter = painterResource(R.drawable.ic_bitcoin_modern), - contentDescription = null, - tint = Colors.White, - modifier = Modifier - .padding(8.dp) - .padding(end = 8.dp) - .size(16.dp) - ) - }, + icon = { SettingsIcon(R.drawable.ic_bitcoin_modern) }, value = SettingsButtonValue.BooleanValue(primaryDisplay == PrimaryDisplay.BITCOIN), onClick = { onPrimaryUnitClick(PrimaryDisplay.BITCOIN) }, modifier = Modifier.testTag(stringResource(R.string.settings__general__unit_bitcoin)) @@ -95,17 +82,7 @@ fun DefaultUnitSettingsScreenContent( SettingsButtonRow( title = selectedCurrency, - icon = { - Icon( - painter = painterResource(R.drawable.ic_unit_fiat), - contentDescription = null, - tint = Colors.White, - modifier = Modifier - .padding(8.dp) - .padding(end = 8.dp) - .size(16.dp) - ) - }, + icon = { SettingsIcon(R.drawable.ic_unit_fiat) }, value = SettingsButtonValue.BooleanValue(primaryDisplay == PrimaryDisplay.FIAT), onClick = { onPrimaryUnitClick(PrimaryDisplay.FIAT) }, modifier = Modifier.testTag(selectedCurrency) diff --git a/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt index 0760f71ba..585c7d220 100644 --- a/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt @@ -2,10 +2,8 @@ package to.bitkit.ui.settings.general import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -13,7 +11,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -22,6 +19,7 @@ import androidx.navigation.NavController import to.bitkit.R import to.bitkit.ui.components.settings.SectionHeader import to.bitkit.ui.components.settings.SettingsButtonRow +import to.bitkit.ui.components.settings.SettingsIcon import to.bitkit.ui.components.settings.SettingsSwitchRow import to.bitkit.ui.scaffold.AppAlertDialog import to.bitkit.ui.scaffold.AppTopBar @@ -29,7 +27,6 @@ import to.bitkit.ui.scaffold.DrawerNavIcon import to.bitkit.ui.scaffold.ScreenColumn import to.bitkit.ui.settingsViewModel import to.bitkit.ui.theme.AppThemeSurface -import to.bitkit.ui.theme.Colors @Composable fun WidgetsSettingsScreen( @@ -97,33 +94,13 @@ private fun WidgetsSettingsContent( SettingsButtonRow( title = stringResource(R.string.settings__widgets__reset_widgets), - icon = { - Icon( - painter = painterResource(R.drawable.ic_arrow_counter_clockwise), - contentDescription = null, - tint = Colors.Brand, - modifier = Modifier - .padding(8.dp) - .padding(end = 8.dp) - .size(16.dp) - ) - }, + icon = { SettingsIcon(R.drawable.ic_arrow_counter_clockwise) }, onClick = { showResetWidgetsDialog = true }, modifier = Modifier.testTag("ResetWidgets"), ) SettingsButtonRow( title = stringResource(R.string.settings__widgets__reset_suggestions), - icon = { - Icon( - painter = painterResource(R.drawable.ic_arrow_counter_clockwise), - contentDescription = null, - tint = Colors.Brand, - modifier = Modifier - .padding(8.dp) - .padding(end = 8.dp) - .size(16.dp) - ) - }, + icon = { SettingsIcon(R.drawable.ic_arrow_counter_clockwise) }, onClick = { showResetSuggestionsDialog = true }, modifier = Modifier.testTag("ResetSuggestions"), ) diff --git a/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt index 0e1ec1324..6a8b5bc8f 100644 --- a/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt +++ b/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt @@ -1,7 +1,6 @@ package to.bitkit.ui.sheets import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth From f256b48f17aceaf66044459bad03d4b9bebaee26 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 25 Mar 2026 07:31:01 -0300 Subject: [PATCH 39/85] refactor: pin helper --- .../to/bitkit/ui/sheets/ChangePinSheet.kt | 29 ++++--------------- .../to/bitkit/ui/sheets/DisablePinSheet.kt | 13 ++------- .../java/to/bitkit/ui/utils/PinInputHelper.kt | 13 +++++++++ 3 files changed, 21 insertions(+), 34 deletions(-) create mode 100644 app/src/main/java/to/bitkit/ui/utils/PinInputHelper.kt diff --git a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt index dc007bda6..febde00cb 100644 --- a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt +++ b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt @@ -35,7 +35,6 @@ import to.bitkit.R import to.bitkit.env.Env import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.BodyS -import to.bitkit.ui.components.KEY_DELETE import to.bitkit.ui.components.NumberPad import to.bitkit.ui.components.NumberPadType import to.bitkit.ui.components.PinDots @@ -48,12 +47,12 @@ import to.bitkit.ui.shared.modifiers.sheetHeight import to.bitkit.ui.shared.util.gradientBackground import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors +import to.bitkit.ui.utils.NumberPadHeight import to.bitkit.ui.utils.composableWithDefaultTransitions +import to.bitkit.ui.utils.handlePinKeyPress import to.bitkit.ui.utils.withAccentBoldBright import to.bitkit.viewmodels.AppViewModel -private val NumberPadHeight = 350.dp - @Suppress("CyclomaticComplexMethod") @Composable fun ChangePinSheet(app: AppViewModel) { @@ -86,13 +85,7 @@ fun ChangePinSheet(app: AppViewModel) { ValidateContent( pin = pin, attemptsRemaining = attemptsRemaining, - onKeyPress = { key -> - if (key == KEY_DELETE) { - if (pin.isNotEmpty()) pin = pin.dropLast(1) - } else if (pin.length < Env.PIN_LENGTH) { - pin += key - } - }, + onKeyPress = { pin = handlePinKeyPress(pin, it) }, onBackClick = onDismiss, onClickForgotPin = { app.setShowForgotPin(true) }, ) @@ -108,13 +101,7 @@ fun ChangePinSheet(app: AppViewModel) { NewPinContent( pin = pin, - onKeyPress = { key -> - if (key == KEY_DELETE) { - if (pin.isNotEmpty()) pin = pin.dropLast(1) - } else if (pin.length < Env.PIN_LENGTH) { - pin += key - } - }, + onKeyPress = { pin = handlePinKeyPress(pin, it) }, onBackClick = { navController.popBackStack() }, ) } @@ -139,13 +126,7 @@ fun ChangePinSheet(app: AppViewModel) { ConfirmContent( pin = pin, showError = showError, - onKeyPress = { key -> - if (key == KEY_DELETE) { - if (pin.isNotEmpty()) pin = pin.dropLast(1) - } else if (pin.length < Env.PIN_LENGTH) { - pin += key - } - }, + onKeyPress = { pin = handlePinKeyPress(pin, it) }, onBackClick = { navController.popBackStack() }, ) } diff --git a/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt index 6a8b5bc8f..ea6913cb3 100644 --- a/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt +++ b/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt @@ -24,7 +24,6 @@ import to.bitkit.R import to.bitkit.env.Env import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.BodyS -import to.bitkit.ui.components.KEY_DELETE import to.bitkit.ui.components.NumberPad import to.bitkit.ui.components.NumberPadType import to.bitkit.ui.components.PinDots @@ -35,11 +34,11 @@ import to.bitkit.ui.shared.modifiers.sheetHeight import to.bitkit.ui.shared.util.gradientBackground import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors +import to.bitkit.ui.utils.NumberPadHeight +import to.bitkit.ui.utils.handlePinKeyPress import to.bitkit.ui.utils.withAccentBoldBright import to.bitkit.viewmodels.AppViewModel -private val NumberPadHeight = 350.dp - @Composable fun DisablePinSheet(app: AppViewModel) { val attemptsRemaining by app.pinAttemptsRemaining.collectAsStateWithLifecycle() @@ -60,13 +59,7 @@ fun DisablePinSheet(app: AppViewModel) { DisablePinContent( pin = pin, attemptsRemaining = attemptsRemaining, - onKeyPress = { key -> - if (key == KEY_DELETE) { - if (pin.isNotEmpty()) pin = pin.dropLast(1) - } else if (pin.length < Env.PIN_LENGTH) { - pin += key - } - }, + onKeyPress = { pin = handlePinKeyPress(pin, it) }, onBackClick = onDismiss, onClickForgotPin = { app.setShowForgotPin(true) }, ) diff --git a/app/src/main/java/to/bitkit/ui/utils/PinInputHelper.kt b/app/src/main/java/to/bitkit/ui/utils/PinInputHelper.kt new file mode 100644 index 000000000..fb0d29623 --- /dev/null +++ b/app/src/main/java/to/bitkit/ui/utils/PinInputHelper.kt @@ -0,0 +1,13 @@ +package to.bitkit.ui.utils + +import androidx.compose.ui.unit.dp +import to.bitkit.env.Env +import to.bitkit.ui.components.KEY_DELETE + +val NumberPadHeight = 350.dp + +fun handlePinKeyPress(currentPin: String, key: String): String = when { + key == KEY_DELETE -> currentPin.dropLast(1) + currentPin.length < Env.PIN_LENGTH -> currentPin + key + else -> currentPin +} From 214d778786860c85848a0a20f9e5200441905b63 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 25 Mar 2026 07:31:45 -0300 Subject: [PATCH 40/85] refactor: set modifier as parameter --- .../to/bitkit/ui/components/settings/SettingsIcon.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/components/settings/SettingsIcon.kt b/app/src/main/java/to/bitkit/ui/components/settings/SettingsIcon.kt index ebbbfd126..49dfd13de 100644 --- a/app/src/main/java/to/bitkit/ui/components/settings/SettingsIcon.kt +++ b/app/src/main/java/to/bitkit/ui/components/settings/SettingsIcon.kt @@ -11,14 +11,16 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp -import to.bitkit.ui.components.HorizontalSpacer import to.bitkit.ui.theme.Colors @Composable -fun SettingsIcon(@DrawableRes iconRes: Int) { +fun SettingsIcon( + @DrawableRes iconRes: Int, + modifier: Modifier = Modifier, +) { Box( contentAlignment = Alignment.Center, - modifier = Modifier + modifier = modifier .size(32.dp) .background(color = Colors.Black, shape = CircleShape), ) { @@ -29,5 +31,4 @@ fun SettingsIcon(@DrawableRes iconRes: Int) { modifier = Modifier.size(16.dp), ) } - HorizontalSpacer(8.dp) } From 825b8a36c329af9b7af2683830e5f0e73f2a302a Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 25 Mar 2026 07:32:06 -0300 Subject: [PATCH 41/85] refactor: remove unused parameter --- .../main/java/to/bitkit/ui/settings/support/SupportScreen.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt index 2c4a1b9a1..6c17c5f7e 100644 --- a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt @@ -44,6 +44,7 @@ import to.bitkit.ui.Routes import to.bitkit.ui.appViewModel import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.FillHeight +import to.bitkit.ui.components.HorizontalSpacer import to.bitkit.ui.components.VerticalSpacer import to.bitkit.ui.components.settings.Links import to.bitkit.ui.components.settings.SettingsButtonRow @@ -59,7 +60,6 @@ import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors private const val DEV_MODE_TAP_THRESHOLD = 5 -private val BrandColor = Colors.Brand @Composable fun SupportScreen( @@ -193,6 +193,7 @@ private fun Content( .testTag("DevOptions"), ) { SettingsIcon(R.drawable.ic_stack) + HorizontalSpacer(8.dp) BodyM( text = stringResource(R.string.settings__about__version), modifier = Modifier.weight(1f), @@ -226,7 +227,7 @@ private fun SupportFooter() { lineTo(size.width, size.height) close() } - drawPath(path, color = BrandColor) + drawPath(path, color = Colors.Brand) }, ) { Image( From e5497ac4f10aaf20f7e431e9ffad0abec6a07bda Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 25 Mar 2026 07:32:57 -0300 Subject: [PATCH 42/85] refactor: lift spacer --- .../to/bitkit/ui/components/settings/SettingsButtonRow.kt | 6 +++++- .../to/bitkit/ui/components/settings/SettingsSwitchRow.kt | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt b/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt index c8489e42d..7cf039172 100644 --- a/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt +++ b/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt @@ -30,6 +30,7 @@ import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.BodyMSB import to.bitkit.ui.components.BodyS import to.bitkit.ui.components.BodySSB +import to.bitkit.ui.components.HorizontalSpacer import to.bitkit.ui.shared.modifiers.clickableAlpha import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors @@ -93,7 +94,10 @@ fun SettingsButtonRow( SettingsButtonRowCore( title = title, hasIcon = true, - icon = { icon() }, + icon = { + icon() + HorizontalSpacer(8.dp) + }, subtitle = subtitle, value = value, description = description, diff --git a/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt b/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt index bb8a86b7e..b7bedc317 100644 --- a/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt +++ b/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.unit.dp import to.bitkit.R import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.BodyS +import to.bitkit.ui.components.HorizontalSpacer import to.bitkit.ui.shared.modifiers.clickableAlpha import to.bitkit.ui.theme.AppSwitchDefaults import to.bitkit.ui.theme.AppThemeSurface @@ -79,7 +80,10 @@ fun SettingsSwitchRow( onClick = onClick, subtitle = subtitle, colors = colors, - icon = { icon() }, + icon = { + icon() + HorizontalSpacer(8.dp) + }, modifier = modifier, ) } From 28966a03d633444658497d67095d604d87a728c0 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 25 Mar 2026 08:00:43 -0300 Subject: [PATCH 43/85] refactor: extract packages --- .../main/java/to/bitkit/ui/settings/SettingsScreen.kt | 9 +++++---- .../bitkit/ui/settings/general/WidgetsSettingsScreen.kt | 3 ++- app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt | 3 ++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index c0aff01ba..c2e66fd27 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -1,6 +1,7 @@ package to.bitkit.ui.settings import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.pager.HorizontalPager @@ -403,7 +404,7 @@ private fun GeneralTabContent( // Payments section SectionHeader( title = stringResource(R.string.settings__general__section_payments), - padding = androidx.compose.foundation.layout.PaddingValues(top = 16.dp), + padding = PaddingValues(top = 16.dp), ) SettingsButtonRow( @@ -497,7 +498,7 @@ private fun SecurityTabContent( // Safety section SectionHeader( title = stringResource(R.string.settings__security__section_safety), - padding = androidx.compose.foundation.layout.PaddingValues(top = 16.dp), + padding = PaddingValues(top = 16.dp), ) SettingsButtonRow( @@ -551,7 +552,7 @@ private fun SecurityTabContent( // Privacy section SectionHeader( title = stringResource(R.string.settings__security__section_privacy), - padding = androidx.compose.foundation.layout.PaddingValues(top = 16.dp), + padding = PaddingValues(top = 16.dp), ) SettingsSwitchRow( @@ -645,7 +646,7 @@ private fun AdvancedTabContent( // Networks section SectionHeader( title = stringResource(R.string.settings__adv__section_networks), - padding = androidx.compose.foundation.layout.PaddingValues(top = 16.dp), + padding = PaddingValues(top = 16.dp), ) SettingsButtonRow( diff --git a/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt index 585c7d220..b4f14453a 100644 --- a/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt @@ -1,6 +1,7 @@ package to.bitkit.ui.settings.general import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -89,7 +90,7 @@ private fun WidgetsSettingsContent( // Reset section SectionHeader( title = stringResource(R.string.settings__widgets__section_reset), - padding = androidx.compose.foundation.layout.PaddingValues(top = 16.dp), + padding = PaddingValues(top = 16.dp), ) SettingsButtonRow( diff --git a/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt b/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt index af11c9671..709fc5694 100644 --- a/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt +++ b/app/src/main/java/to/bitkit/ui/shared/util/Modifiers.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.drawscope.ContentDrawScope import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.input.pointer.pointerInput @@ -117,7 +118,7 @@ private class OuterGlowNode( var glowRadius: Dp, var cornerRadius: Dp, ) : DrawModifierNode, Modifier.Node() { - override fun androidx.compose.ui.graphics.drawscope.ContentDrawScope.draw() { + override fun ContentDrawScope.draw() { val glowRadiusPx = glowRadius.toPx() val cornerRadiusPx = cornerRadius.toPx() From 384462bf4bade5567ee172b058342ea8bceb8633 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 25 Mar 2026 08:01:35 -0300 Subject: [PATCH 44/85] refactor: add StringRes annotation --- app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index c2e66fd27..7fc403a4c 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -1,5 +1,6 @@ package to.bitkit.ui.settings +import androidx.annotation.StringRes import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize @@ -60,7 +61,7 @@ import to.bitkit.ui.theme.Colors import to.bitkit.ui.utils.rememberBiometricAuthSupported import to.bitkit.viewmodels.LanguageViewModel -private enum class SettingsTab(private val titleRes: Int) : TabItem { +private enum class SettingsTab(@StringRes private val titleRes: Int) : TabItem { General(R.string.settings__general_title), Security(R.string.settings__security_title), Advanced(R.string.settings__advanced_title); From da0301239e1c82500046c184c387514e7f169adf Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 25 Mar 2026 08:26:33 -0300 Subject: [PATCH 45/85] refactor: reduce parameters count by implementing uiState classes --- .../to/bitkit/ui/settings/SettingsScreen.kt | 266 ++++++++---------- 1 file changed, 124 insertions(+), 142 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index 7fc403a4c..97ea2d7c5 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -108,16 +109,34 @@ fun SettingsScreen( LaunchedEffect(Unit) { languageViewModel.fetchLanguageInfo() } SettingsContent( - // General - selectedCurrency = currencies.selectedCurrency, - currencySymbol = currencies.currencySymbol, - primaryDisplay = currencies.primaryDisplay, - defaultTransactionSpeed = defaultTransactionSpeed, - selectedLanguage = languageUiState.selectedLanguage.displayName, - showWidgets = showWidgets, - tagCount = lastUsedTags.size, - isQuickPayEnabled = isQuickPayEnabled, - notificationsGranted = notificationsGranted, + generalState = GeneralTabState( + selectedCurrency = currencies.selectedCurrency, + currencySymbol = currencies.currencySymbol, + primaryDisplay = currencies.primaryDisplay, + defaultTransactionSpeed = defaultTransactionSpeed, + selectedLanguage = languageUiState.selectedLanguage.displayName, + showWidgets = showWidgets, + tagCount = lastUsedTags.size, + isQuickPayEnabled = isQuickPayEnabled, + notificationsGranted = notificationsGranted, + ), + securityState = SecurityTabState( + isPinEnabled = isPinEnabled, + isBiometricEnabled = isBiometricEnabled, + isPinForPaymentsEnabled = isPinForPaymentsEnabled, + enableSwipeToHideBalance = enableSwipeToHideBalance, + hideBalanceOnOpen = hideBalanceOnOpen, + enableAutoReadClipboard = enableAutoReadClipboard, + enableSendAmountWarning = enableSendAmountWarning, + isBiometrySupported = rememberBiometricAuthSupported(), + ), + advancedState = AdvancedTabState( + isDevModeEnabled = isDevModeEnabled, + selectedAddressTypeName = selectedAddressTypeName, + openChannelCount = openChannelCount, + truncatedNodeId = truncatedNodeId, + isCustomElectrum = isCustomElectrum, + ), onLanguageClick = { navController.navigateToLanguageSettings() }, onLocalCurrencyClick = { navController.navigateToLocalCurrencySettings() }, onDefaultUnitClick = { navController.navigateToDefaultUnitSettings() }, @@ -132,15 +151,6 @@ fun SettingsScreen( navController.navigateTo(Routes.BackgroundPaymentsIntro) } }, - // Security - isPinEnabled = isPinEnabled, - isBiometricEnabled = isBiometricEnabled, - isPinForPaymentsEnabled = isPinForPaymentsEnabled, - enableSwipeToHideBalance = enableSwipeToHideBalance, - hideBalanceOnOpen = hideBalanceOnOpen, - enableAutoReadClipboard = enableAutoReadClipboard, - enableSendAmountWarning = enableSendAmountWarning, - isBiometrySupported = rememberBiometricAuthSupported(), onBackupWalletClick = { app.showSheet(Sheet.Backup()) }, onDataBackupsClick = { navController.navigateTo(Routes.BackupSettings) }, onResetWalletClick = { @@ -166,12 +176,6 @@ fun SettingsScreen( onHideBalanceOnOpenClick = { settings.setHideBalanceOnOpen(!hideBalanceOnOpen) }, onAutoReadClipboardClick = { settings.setEnableAutoReadClipboard(!enableAutoReadClipboard) }, onSendAmountWarningClick = { settings.setEnableSendAmountWarning(!enableSendAmountWarning) }, - // Advanced - isDevModeEnabled = isDevModeEnabled, - selectedAddressTypeName = selectedAddressTypeName, - openChannelCount = openChannelCount, - truncatedNodeId = truncatedNodeId, - isCustomElectrum = isCustomElectrum, onDevSettingsClick = { navController.navigateToDevSettings() }, onAddressTypeClick = { navController.navigateTo(Routes.AddressTypePreference) }, onCoinSelectionClick = { navController.navigateTo(Routes.CoinSelectPreference) }, @@ -180,24 +184,17 @@ fun SettingsScreen( onLightningNodeClick = { navController.navigateTo(Routes.NodeInfo) }, onElectrumServerClick = { navController.navigateTo(Routes.ElectrumConfig) }, onRgsServerClick = { navController.navigateTo(Routes.RgsServer) }, - // Navigation onBackClick = { navController.popBackStack() }, ) } -@Suppress("LongParameterList", "LongMethod") +@Suppress("LongParameterList") @Composable private fun SettingsContent( - // General - selectedCurrency: String = "USD", - currencySymbol: String = "$", - primaryDisplay: PrimaryDisplay = PrimaryDisplay.BITCOIN, - defaultTransactionSpeed: TransactionSpeed = TransactionSpeed.Medium, - selectedLanguage: String = "", - showWidgets: Boolean = true, - tagCount: Int = 0, - isQuickPayEnabled: Boolean = false, - notificationsGranted: Boolean = false, + generalState: GeneralTabState = GeneralTabState(), + securityState: SecurityTabState = SecurityTabState(), + advancedState: AdvancedTabState = AdvancedTabState(), + // General callbacks onLanguageClick: () -> Unit = {}, onLocalCurrencyClick: () -> Unit = {}, onDefaultUnitClick: () -> Unit = {}, @@ -206,15 +203,7 @@ private fun SettingsContent( onTransactionSpeedClick: () -> Unit = {}, onQuickPayClick: () -> Unit = {}, onBgPaymentsClick: () -> Unit = {}, - // Security - isPinEnabled: Boolean = false, - isBiometricEnabled: Boolean = false, - isPinForPaymentsEnabled: Boolean = false, - enableSwipeToHideBalance: Boolean = false, - hideBalanceOnOpen: Boolean = false, - enableAutoReadClipboard: Boolean = true, - enableSendAmountWarning: Boolean = true, - isBiometrySupported: Boolean = false, + // Security callbacks onBackupWalletClick: () -> Unit = {}, onDataBackupsClick: () -> Unit = {}, onResetWalletClick: () -> Unit = {}, @@ -225,12 +214,7 @@ private fun SettingsContent( onHideBalanceOnOpenClick: () -> Unit = {}, onAutoReadClipboardClick: () -> Unit = {}, onSendAmountWarningClick: () -> Unit = {}, - // Advanced - isDevModeEnabled: Boolean = false, - selectedAddressTypeName: String = "", - openChannelCount: Int = 0, - truncatedNodeId: String = "", - isCustomElectrum: Boolean = false, + // Advanced callbacks onDevSettingsClick: () -> Unit = {}, onAddressTypeClick: () -> Unit = {}, onCoinSelectionClick: () -> Unit = {}, @@ -268,15 +252,7 @@ private fun SettingsContent( HorizontalPager(state = pagerState) { page -> when (tabs[page]) { SettingsTab.General -> GeneralTabContent( - selectedCurrency = selectedCurrency, - currencySymbol = currencySymbol, - primaryDisplay = primaryDisplay, - defaultTransactionSpeed = defaultTransactionSpeed, - selectedLanguage = selectedLanguage, - showWidgets = showWidgets, - tagCount = tagCount, - isQuickPayEnabled = isQuickPayEnabled, - notificationsGranted = notificationsGranted, + state = generalState, onLanguageClick = onLanguageClick, onLocalCurrencyClick = onLocalCurrencyClick, onDefaultUnitClick = onDefaultUnitClick, @@ -288,14 +264,7 @@ private fun SettingsContent( ) SettingsTab.Security -> SecurityTabContent( - isPinEnabled = isPinEnabled, - isBiometricEnabled = isBiometricEnabled, - isPinForPaymentsEnabled = isPinForPaymentsEnabled, - enableSwipeToHideBalance = enableSwipeToHideBalance, - hideBalanceOnOpen = hideBalanceOnOpen, - enableAutoReadClipboard = enableAutoReadClipboard, - enableSendAmountWarning = enableSendAmountWarning, - isBiometrySupported = isBiometrySupported, + state = securityState, onBackupWalletClick = onBackupWalletClick, onDataBackupsClick = onDataBackupsClick, onResetWalletClick = onResetWalletClick, @@ -309,11 +278,7 @@ private fun SettingsContent( ) SettingsTab.Advanced -> AdvancedTabContent( - isDevModeEnabled = isDevModeEnabled, - selectedAddressTypeName = selectedAddressTypeName, - openChannelCount = openChannelCount, - truncatedNodeId = truncatedNodeId, - isCustomElectrum = isCustomElectrum, + state = advancedState, onDevSettingsClick = onDevSettingsClick, onAddressTypeClick = onAddressTypeClick, onCoinSelectionClick = onCoinSelectionClick, @@ -330,15 +295,7 @@ private fun SettingsContent( @Composable private fun GeneralTabContent( - selectedCurrency: String, - currencySymbol: String, - primaryDisplay: PrimaryDisplay, - defaultTransactionSpeed: TransactionSpeed, - selectedLanguage: String, - showWidgets: Boolean, - tagCount: Int, - isQuickPayEnabled: Boolean, - notificationsGranted: Boolean, + state: GeneralTabState, onLanguageClick: () -> Unit, onLocalCurrencyClick: () -> Unit, onDefaultUnitClick: () -> Unit, @@ -360,14 +317,14 @@ private fun GeneralTabContent( SettingsButtonRow( title = stringResource(R.string.settings__language_title), icon = { SettingsIcon(R.drawable.ic_translate) }, - value = SettingsButtonValue.StringValue(selectedLanguage), + value = SettingsButtonValue.StringValue(state.selectedLanguage), onClick = onLanguageClick, modifier = Modifier.testTag("LanguageSettings"), ) SettingsButtonRow( title = stringResource(R.string.settings__general__currency_local), icon = { SettingsIcon(R.drawable.ic_coins) }, - value = SettingsButtonValue.StringValue("$selectedCurrency ($currencySymbol)"), + value = SettingsButtonValue.StringValue("${state.selectedCurrency} (${state.currencySymbol})"), onClick = onLocalCurrencyClick, modifier = Modifier.testTag("CurrenciesSettings"), ) @@ -375,9 +332,9 @@ private fun GeneralTabContent( title = stringResource(R.string.settings__general__unit), icon = { SettingsIcon(R.drawable.ic_bitcoin_modern) }, value = SettingsButtonValue.StringValue( - when (primaryDisplay) { + when (state.primaryDisplay) { PrimaryDisplay.BITCOIN -> stringResource(R.string.settings__general__unit_bitcoin) - PrimaryDisplay.FIAT -> selectedCurrency + PrimaryDisplay.FIAT -> state.selectedCurrency } ), onClick = onDefaultUnitClick, @@ -387,16 +344,16 @@ private fun GeneralTabContent( title = stringResource(R.string.settings__widgets__nav_title), icon = { SettingsIcon(R.drawable.ic_stack) }, value = SettingsButtonValue.StringValue( - stringResource(if (showWidgets) R.string.settings__bg__on else R.string.settings__bg__off) + stringResource(if (state.showWidgets) R.string.settings__bg__on else R.string.settings__bg__off) ), onClick = onWidgetsClick, modifier = Modifier.testTag("WidgetsSettings"), ) - if (tagCount > 0) { + if (state.tagCount > 0) { SettingsButtonRow( title = stringResource(R.string.settings__general__tags), icon = { SettingsIcon(R.drawable.ic_tag) }, - value = SettingsButtonValue.StringValue(tagCount.toString()), + value = SettingsButtonValue.StringValue(state.tagCount.toString()), onClick = onTagsClick, modifier = Modifier.testTag("TagsSettings"), ) @@ -412,14 +369,14 @@ private fun GeneralTabContent( title = stringResource(R.string.settings__general__speed), icon = { SettingsIcon( - when (defaultTransactionSpeed) { + when (state.defaultTransactionSpeed) { is TransactionSpeed.Fast -> R.drawable.ic_speed_fast is TransactionSpeed.Slow -> R.drawable.ic_speed_slow else -> R.drawable.ic_speed_normal } ) }, - value = SettingsButtonValue.StringValue(defaultTransactionSpeed.transactionSpeedUiText()), + value = SettingsButtonValue.StringValue(state.defaultTransactionSpeed.transactionSpeedUiText()), onClick = onTransactionSpeedClick, modifier = Modifier.testTag("TransactionSpeedSettings"), ) @@ -427,7 +384,7 @@ private fun GeneralTabContent( title = stringResource(R.string.settings__quickpay__nav_title), icon = { SettingsIcon(R.drawable.ic_caret_double_right) }, value = SettingsButtonValue.StringValue( - stringResource(if (isQuickPayEnabled) R.string.settings__bg__on else R.string.settings__bg__off) + stringResource(if (state.isQuickPayEnabled) R.string.settings__bg__on else R.string.settings__bg__off) ), onClick = onQuickPayClick, modifier = Modifier.testTag("QuickpaySettings"), @@ -436,7 +393,9 @@ private fun GeneralTabContent( title = stringResource(R.string.settings__bg__title), icon = { SettingsIcon(R.drawable.ic_bell) }, value = SettingsButtonValue.StringValue( - stringResource(if (notificationsGranted) R.string.settings__bg__on else R.string.settings__bg__off) + stringResource( + if (state.notificationsGranted) R.string.settings__bg__on else R.string.settings__bg__off + ) ), onClick = onBgPaymentsClick, modifier = Modifier.testTag("BackgroundPaymentSettings"), @@ -446,17 +405,9 @@ private fun GeneralTabContent( } } -@Suppress("LongParameterList") @Composable private fun SecurityTabContent( - isPinEnabled: Boolean, - isBiometricEnabled: Boolean, - isPinForPaymentsEnabled: Boolean, - enableSwipeToHideBalance: Boolean, - hideBalanceOnOpen: Boolean, - enableAutoReadClipboard: Boolean, - enableSendAmountWarning: Boolean, - isBiometrySupported: Boolean, + state: SecurityTabState, onBackupWalletClick: () -> Unit, onDataBackupsClick: () -> Unit, onResetWalletClick: () -> Unit, @@ -507,7 +458,7 @@ private fun SecurityTabContent( icon = { SettingsIcon(R.drawable.ic_shield) }, value = SettingsButtonValue.StringValue( stringResource( - if (isPinEnabled) { + if (state.isPinEnabled) { R.string.settings__security__pin_enabled } else { R.string.settings__security__pin_disabled @@ -518,16 +469,16 @@ private fun SecurityTabContent( modifier = Modifier.testTag("PINCode"), ) - if (isPinEnabled) { + if (state.isPinEnabled) { SettingsSwitchRow( title = stringResource(R.string.settings__security__pin_payments), icon = { SettingsIcon(R.drawable.ic_coins) }, - isChecked = isPinForPaymentsEnabled, + isChecked = state.isPinForPaymentsEnabled, onClick = onPinForPaymentsClick, modifier = Modifier.testTag("EnablePinForPayments"), ) - if (isBiometrySupported) { + if (state.isBiometrySupported) { SettingsSwitchRow( title = run { val bioTypeName = stringResource(R.string.security__bio) @@ -535,7 +486,7 @@ private fun SecurityTabContent( .replace("{biometryTypeName}", bioTypeName) }, icon = { SettingsIcon(R.drawable.ic_smiley) }, - isChecked = isBiometricEnabled, + isChecked = state.isBiometricEnabled, onClick = onUseBiometricsClick, modifier = Modifier.testTag("UseBiometryInstead"), ) @@ -545,7 +496,7 @@ private fun SecurityTabContent( SettingsSwitchRow( title = stringResource(R.string.settings__security__warn_100), icon = { SettingsIcon(R.drawable.ic_warning) }, - isChecked = enableSendAmountWarning, + isChecked = state.enableSendAmountWarning, onClick = onSendAmountWarningClick, modifier = Modifier.testTag("SendAmountWarning"), ) @@ -559,21 +510,21 @@ private fun SecurityTabContent( SettingsSwitchRow( title = stringResource(R.string.settings__security__swipe_balance_to_hide), icon = { SettingsIcon(R.drawable.ic_hand_pointing) }, - isChecked = enableSwipeToHideBalance, + isChecked = state.enableSwipeToHideBalance, onClick = onSwipeToHideBalanceClick, modifier = Modifier.testTag("SwipeBalanceToHide"), ) SettingsSwitchRow( title = stringResource(R.string.settings__security__hide_balance_on_open), icon = { SettingsIcon(R.drawable.ic_eye_slash) }, - isChecked = hideBalanceOnOpen, + isChecked = state.hideBalanceOnOpen, onClick = onHideBalanceOnOpenClick, modifier = Modifier.testTag("HideBalanceOnOpen"), ) SettingsSwitchRow( title = stringResource(R.string.settings__security__clipboard), icon = { SettingsIcon(R.drawable.ic_clipboard_text) }, - isChecked = enableAutoReadClipboard, + isChecked = state.enableAutoReadClipboard, onClick = onAutoReadClipboardClick, modifier = Modifier.testTag("AutoReadClipboard"), ) @@ -584,11 +535,7 @@ private fun SecurityTabContent( @Composable private fun AdvancedTabContent( - isDevModeEnabled: Boolean, - selectedAddressTypeName: String, - openChannelCount: Int, - truncatedNodeId: String, - isCustomElectrum: Boolean, + state: AdvancedTabState, onDevSettingsClick: () -> Unit, onAddressTypeClick: () -> Unit, onCoinSelectionClick: () -> Unit, @@ -606,7 +553,7 @@ private fun AdvancedTabContent( .testTag("advanced_settings_screen") ) { // Debug section (only if dev mode enabled) - if (isDevModeEnabled) { + if (state.isDevModeEnabled) { SectionHeader(title = stringResource(R.string.settings__adv__section_debug)) SettingsButtonRow( @@ -623,8 +570,8 @@ private fun AdvancedTabContent( SettingsButtonRow( title = stringResource(R.string.settings__addr_type__title), icon = { SettingsIcon(R.drawable.ic_list_dashes) }, - value = if (selectedAddressTypeName.isNotEmpty()) { - SettingsButtonValue.StringValue(selectedAddressTypeName) + value = if (state.selectedAddressTypeName.isNotEmpty()) { + SettingsButtonValue.StringValue(state.selectedAddressTypeName) } else { SettingsButtonValue.None }, @@ -653,8 +600,8 @@ private fun AdvancedTabContent( SettingsButtonRow( title = stringResource(R.string.settings__adv__lightning_connections), icon = { SettingsIcon(R.drawable.ic_lightning) }, - value = if (openChannelCount > 0) { - SettingsButtonValue.StringValue(openChannelCount.toString()) + value = if (state.openChannelCount > 0) { + SettingsButtonValue.StringValue(state.openChannelCount.toString()) } else { SettingsButtonValue.None }, @@ -664,8 +611,8 @@ private fun AdvancedTabContent( SettingsButtonRow( title = stringResource(R.string.settings__adv__lightning_node), icon = { SettingsIcon(R.drawable.ic_git_branch) }, - value = if (truncatedNodeId.isNotEmpty()) { - SettingsButtonValue.StringValue("$truncatedNodeId...") + value = if (state.truncatedNodeId.isNotEmpty()) { + SettingsButtonValue.StringValue("${state.truncatedNodeId}...") } else { SettingsButtonValue.None }, @@ -677,7 +624,7 @@ private fun AdvancedTabContent( icon = { SettingsIcon(R.drawable.ic_hard_drives) }, value = SettingsButtonValue.StringValue( stringResource( - if (isCustomElectrum) { + if (state.isCustomElectrum) { R.string.settings__adv__electrum_custom } else { R.string.settings__adv__electrum_auto @@ -703,15 +650,12 @@ private fun AdvancedTabContent( private fun PreviewGeneral() { AppThemeSurface { SettingsContent( - selectedCurrency = "USD", - currencySymbol = "$", - primaryDisplay = PrimaryDisplay.BITCOIN, - defaultTransactionSpeed = TransactionSpeed.Medium, - selectedLanguage = "System Settings", - showWidgets = true, - tagCount = 8, - isQuickPayEnabled = true, - notificationsGranted = true, + generalState = GeneralTabState( + selectedLanguage = "System Settings", + tagCount = 8, + isQuickPayEnabled = true, + notificationsGranted = true, + ), ) } } @@ -721,10 +665,12 @@ private fun PreviewGeneral() { private fun PreviewSecurity() { AppThemeSurface { SettingsContent( - isPinEnabled = true, - isPinForPaymentsEnabled = true, - enableSwipeToHideBalance = true, - isBiometrySupported = true, + securityState = SecurityTabState( + isPinEnabled = true, + isPinForPaymentsEnabled = true, + enableSwipeToHideBalance = true, + isBiometrySupported = true, + ), initialTab = SettingsTab.Security, ) } @@ -735,11 +681,47 @@ private fun PreviewSecurity() { private fun PreviewAdvanced() { AppThemeSurface { SettingsContent( - isDevModeEnabled = true, - selectedAddressTypeName = "Taproot", - openChannelCount = 2, - truncatedNodeId = "34sdx", + advancedState = AdvancedTabState( + isDevModeEnabled = true, + selectedAddressTypeName = "Taproot", + openChannelCount = 2, + truncatedNodeId = "34sdx", + ), initialTab = SettingsTab.Advanced, ) } } + +@Immutable +data class GeneralTabState( + val selectedCurrency: String = "USD", + val currencySymbol: String = "$", + val primaryDisplay: PrimaryDisplay = PrimaryDisplay.BITCOIN, + val defaultTransactionSpeed: TransactionSpeed = TransactionSpeed.Medium, + val selectedLanguage: String = "", + val showWidgets: Boolean = true, + val tagCount: Int = 0, + val isQuickPayEnabled: Boolean = false, + val notificationsGranted: Boolean = false, +) + +@Immutable +data class SecurityTabState( + val isPinEnabled: Boolean = false, + val isBiometricEnabled: Boolean = false, + val isPinForPaymentsEnabled: Boolean = false, + val enableSwipeToHideBalance: Boolean = false, + val hideBalanceOnOpen: Boolean = false, + val enableAutoReadClipboard: Boolean = true, + val enableSendAmountWarning: Boolean = true, + val isBiometrySupported: Boolean = false, +) + +@Immutable +data class AdvancedTabState( + val isDevModeEnabled: Boolean = false, + val selectedAddressTypeName: String = "", + val openChannelCount: Int = 0, + val truncatedNodeId: String = "", + val isCustomElectrum: Boolean = false, +) From 04d15628e295366f44c0c16e023e9a99588d2ff1 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 25 Mar 2026 08:39:51 -0300 Subject: [PATCH 46/85] refactor: space modifiers --- .../general/DefaultUnitSettingsScreen.kt | 5 ++- .../ui/settings/pin/PinManagementScreen.kt | 5 ++- .../to/bitkit/ui/sheets/ChangePinSheet.kt | 33 ++++++++++--------- .../to/bitkit/ui/sheets/DisablePinSheet.kt | 13 ++++---- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/general/DefaultUnitSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/general/DefaultUnitSettingsScreen.kt index d67cbce69..716d4e09a 100644 --- a/app/src/main/java/to/bitkit/ui/settings/general/DefaultUnitSettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/general/DefaultUnitSettingsScreen.kt @@ -1,8 +1,6 @@ package to.bitkit.ui.settings.general import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -18,6 +16,7 @@ import to.bitkit.models.BitcoinDisplayUnit import to.bitkit.models.PrimaryDisplay import to.bitkit.repositories.CurrencyState import to.bitkit.ui.LocalCurrencies +import to.bitkit.ui.components.VerticalSpacer import to.bitkit.ui.components.settings.SectionFooter import to.bitkit.ui.components.settings.SectionHeader import to.bitkit.ui.components.settings.SettingsButtonRow @@ -107,7 +106,7 @@ fun DefaultUnitSettingsScreenContent( ) } - Spacer(modifier = Modifier.height(16.dp)) + VerticalSpacer(16.dp) } } } diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt index f288d16f3..b0aca2e7b 100644 --- a/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt @@ -5,9 +5,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable @@ -27,6 +25,7 @@ import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.PrimaryButton import to.bitkit.ui.components.SecondaryButton import to.bitkit.ui.components.Sheet +import to.bitkit.ui.components.VerticalSpacer import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.DrawerNavIcon import to.bitkit.ui.scaffold.ScreenColumn @@ -130,7 +129,7 @@ private fun Content( ) } - Spacer(modifier = Modifier.height(16.dp)) + VerticalSpacer(16.dp) } } } diff --git a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt index febde00cb..095ed4dc1 100644 --- a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt +++ b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding @@ -35,11 +34,13 @@ import to.bitkit.R import to.bitkit.env.Env import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.BodyS +import to.bitkit.ui.components.FillHeight import to.bitkit.ui.components.NumberPad import to.bitkit.ui.components.NumberPadType import to.bitkit.ui.components.PinDots import to.bitkit.ui.components.PrimaryButton import to.bitkit.ui.components.SheetSize +import to.bitkit.ui.components.VerticalSpacer import to.bitkit.ui.navigateTo import to.bitkit.ui.scaffold.SheetTopBar import to.bitkit.ui.shared.modifiers.clickableAlpha @@ -176,7 +177,7 @@ private fun ValidateContent( onBack = onBackClick, ) - Spacer(modifier = Modifier.height(16.dp)) + VerticalSpacer(16.dp) BodyM( text = stringResource(R.string.security__cp_text).withAccentBoldBright(), @@ -184,7 +185,7 @@ private fun ValidateContent( modifier = Modifier.padding(horizontal = 32.dp), ) - Spacer(modifier = Modifier.height(32.dp)) + VerticalSpacer(32.dp) AnimatedVisibility(visible = attemptsRemaining < Env.PIN_ATTEMPTS) { if (isLastAttempt) { @@ -208,14 +209,14 @@ private fun ValidateContent( .testTag("AttemptsRemaining"), ) } - Spacer(modifier = Modifier.height(16.dp)) + VerticalSpacer(16.dp) } - Spacer(modifier = Modifier.weight(1f)) + FillHeight() PinDots(pin = pin) - Spacer(modifier = Modifier.height(32.dp)) + VerticalSpacer(32.dp) NumberPad( onPress = onKeyPress, @@ -244,7 +245,7 @@ private fun NewPinContent( onBack = onBackClick, ) - Spacer(modifier = Modifier.height(16.dp)) + VerticalSpacer(16.dp) BodyM( text = stringResource(R.string.security__cp_setnew_text), @@ -252,12 +253,12 @@ private fun NewPinContent( modifier = Modifier.padding(horizontal = 32.dp), ) - Spacer(modifier = Modifier.height(32.dp)) - Spacer(modifier = Modifier.weight(1f)) + VerticalSpacer(32.dp) + FillHeight() PinDots(pin = pin) - Spacer(modifier = Modifier.height(32.dp)) + VerticalSpacer(32.dp) NumberPad( onPress = onKeyPress, @@ -288,7 +289,7 @@ private fun ConfirmContent( onBack = onBackClick, ) - Spacer(modifier = Modifier.height(16.dp)) + VerticalSpacer(16.dp) BodyM( text = stringResource(R.string.security__cp_retype_text), @@ -296,7 +297,7 @@ private fun ConfirmContent( modifier = Modifier.padding(horizontal = 32.dp), ) - Spacer(modifier = Modifier.height(32.dp)) + VerticalSpacer(32.dp) AnimatedVisibility(visible = showError) { BodyS( @@ -309,11 +310,11 @@ private fun ConfirmContent( ) } - Spacer(modifier = Modifier.weight(1f)) + FillHeight() PinDots(pin = pin) - Spacer(modifier = Modifier.height(32.dp)) + VerticalSpacer(32.dp) NumberPad( onPress = onKeyPress, @@ -340,7 +341,7 @@ private fun ResultContent( titleText = stringResource(R.string.security__cp_changed_title) ) - Spacer(modifier = Modifier.height(16.dp)) + VerticalSpacer(16.dp) BodyM( text = stringResource(R.string.security__cp_changed_text), @@ -369,7 +370,7 @@ private fun ResultContent( .testTag("OK"), ) - Spacer(modifier = Modifier.height(16.dp)) + VerticalSpacer(16.dp) } } diff --git a/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt index ea6913cb3..83a388c44 100644 --- a/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt +++ b/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt @@ -2,7 +2,6 @@ package to.bitkit.ui.sheets import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding @@ -24,10 +23,12 @@ import to.bitkit.R import to.bitkit.env.Env import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.BodyS +import to.bitkit.ui.components.FillHeight import to.bitkit.ui.components.NumberPad import to.bitkit.ui.components.NumberPadType import to.bitkit.ui.components.PinDots import to.bitkit.ui.components.SheetSize +import to.bitkit.ui.components.VerticalSpacer import to.bitkit.ui.scaffold.SheetTopBar import to.bitkit.ui.shared.modifiers.clickableAlpha import to.bitkit.ui.shared.modifiers.sheetHeight @@ -88,7 +89,7 @@ private fun DisablePinContent( onBack = onBackClick, ) - Spacer(modifier = Modifier.height(16.dp)) + VerticalSpacer(16.dp) BodyM( text = stringResource(R.string.security__pin_disable_text).withAccentBoldBright(), @@ -96,7 +97,7 @@ private fun DisablePinContent( modifier = Modifier.padding(horizontal = 32.dp), ) - Spacer(modifier = Modifier.height(32.dp)) + VerticalSpacer(32.dp) AnimatedVisibility(visible = attemptsRemaining < Env.PIN_ATTEMPTS) { if (isLastAttempt) { @@ -120,14 +121,14 @@ private fun DisablePinContent( .testTag("AttemptsRemaining"), ) } - Spacer(modifier = Modifier.height(16.dp)) + VerticalSpacer(16.dp) } - Spacer(modifier = Modifier.weight(1f)) + FillHeight() PinDots(pin = pin) - Spacer(modifier = Modifier.height(32.dp)) + VerticalSpacer(32.dp) NumberPad( onPress = onKeyPress, From 29c2f5be3917d12cb550e6fd99d059ce5f0cf77c Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 25 Mar 2026 08:54:10 -0300 Subject: [PATCH 47/85] refactor: reduce callback hell by using sealed interfaces and typealias --- .../to/bitkit/ui/settings/SettingsScreen.kt | 281 ++++++++---------- 1 file changed, 124 insertions(+), 157 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index 97ea2d7c5..88e2820ca 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -70,6 +70,7 @@ private enum class SettingsTab(@StringRes private val titleRes: Int) : TabItem { override val uiText @Composable get() = stringResource(titleRes) } +@Suppress("CyclomaticComplexMethod") @Composable fun SettingsScreen( navController: NavController, @@ -137,94 +138,67 @@ fun SettingsScreen( truncatedNodeId = truncatedNodeId, isCustomElectrum = isCustomElectrum, ), - onLanguageClick = { navController.navigateToLanguageSettings() }, - onLocalCurrencyClick = { navController.navigateToLocalCurrencySettings() }, - onDefaultUnitClick = { navController.navigateToDefaultUnitSettings() }, - onWidgetsClick = { navController.navigateToWidgetsSettings() }, - onTagsClick = { navController.navigateToTagsSettings() }, - onTransactionSpeedClick = { navController.navigateToTransactionSpeedSettings() }, - onQuickPayClick = { navController.navigateToQuickPaySettings(quickPayIntroSeen) }, - onBgPaymentsClick = { - if (bgPaymentsIntroSeen || notificationsGranted) { - navController.navigateTo(Routes.BackgroundPaymentsSettings) - } else { - navController.navigateTo(Routes.BackgroundPaymentsIntro) - } - }, - onBackupWalletClick = { app.showSheet(Sheet.Backup()) }, - onDataBackupsClick = { navController.navigateTo(Routes.BackupSettings) }, - onResetWalletClick = { - if (isPinEnabled) { - navController.navigateToAuthCheck(onSuccessActionId = AuthCheckAction.NAV_TO_RESET) - } else { - navController.navigateTo(Routes.ResetAndRestoreSettings) + onEvent = { event -> + when (event) { + SettingsEvent.LanguageClick -> navController.navigateToLanguageSettings() + SettingsEvent.LocalCurrencyClick -> navController.navigateToLocalCurrencySettings() + SettingsEvent.DefaultUnitClick -> navController.navigateToDefaultUnitSettings() + SettingsEvent.WidgetsClick -> navController.navigateToWidgetsSettings() + SettingsEvent.TagsClick -> navController.navigateToTagsSettings() + SettingsEvent.TransactionSpeedClick -> navController.navigateToTransactionSpeedSettings() + SettingsEvent.QuickPayClick -> navController.navigateToQuickPaySettings(quickPayIntroSeen) + SettingsEvent.BgPaymentsClick -> { + if (bgPaymentsIntroSeen || notificationsGranted) { + navController.navigateTo(Routes.BackgroundPaymentsSettings) + } else { + navController.navigateTo(Routes.BackgroundPaymentsIntro) + } + } + SettingsEvent.BackupWalletClick -> app.showSheet(Sheet.Backup()) + SettingsEvent.DataBackupsClick -> navController.navigateTo(Routes.BackupSettings) + SettingsEvent.ResetWalletClick -> { + if (isPinEnabled) { + navController.navigateToAuthCheck(onSuccessActionId = AuthCheckAction.NAV_TO_RESET) + } else { + navController.navigateTo(Routes.ResetAndRestoreSettings) + } + } + SettingsEvent.PinClick -> navController.navigateToPinManagement() + SettingsEvent.PinForPaymentsClick -> { + navController.navigateToAuthCheck( + onSuccessActionId = AuthCheckAction.TOGGLE_PIN_FOR_PAYMENTS, + ) + } + SettingsEvent.UseBiometricsClick -> { + navController.navigateToAuthCheck( + requireBiometrics = true, + onSuccessActionId = AuthCheckAction.TOGGLE_BIOMETRICS, + ) + } + SettingsEvent.SwipeToHideBalanceClick -> settings.setEnableSwipeToHideBalance(!enableSwipeToHideBalance) + SettingsEvent.HideBalanceOnOpenClick -> settings.setHideBalanceOnOpen(!hideBalanceOnOpen) + SettingsEvent.AutoReadClipboardClick -> settings.setEnableAutoReadClipboard(!enableAutoReadClipboard) + SettingsEvent.SendAmountWarningClick -> settings.setEnableSendAmountWarning(!enableSendAmountWarning) + SettingsEvent.DevSettingsClick -> navController.navigateToDevSettings() + SettingsEvent.AddressTypeClick -> navController.navigateTo(Routes.AddressTypePreference) + SettingsEvent.CoinSelectionClick -> navController.navigateTo(Routes.CoinSelectPreference) + SettingsEvent.AddressViewerClick -> navController.navigateTo(Routes.AddressViewer) + SettingsEvent.LightningConnectionsClick -> navController.navigateTo(Routes.LightningConnections) + SettingsEvent.LightningNodeClick -> navController.navigateTo(Routes.NodeInfo) + SettingsEvent.ElectrumServerClick -> navController.navigateTo(Routes.ElectrumConfig) + SettingsEvent.RgsServerClick -> navController.navigateTo(Routes.RgsServer) + SettingsEvent.BackClick -> navController.popBackStack() } }, - onPinClick = { navController.navigateToPinManagement() }, - onPinForPaymentsClick = { - navController.navigateToAuthCheck( - onSuccessActionId = AuthCheckAction.TOGGLE_PIN_FOR_PAYMENTS, - ) - }, - onUseBiometricsClick = { - navController.navigateToAuthCheck( - requireBiometrics = true, - onSuccessActionId = AuthCheckAction.TOGGLE_BIOMETRICS, - ) - }, - onSwipeToHideBalanceClick = { settings.setEnableSwipeToHideBalance(!enableSwipeToHideBalance) }, - onHideBalanceOnOpenClick = { settings.setHideBalanceOnOpen(!hideBalanceOnOpen) }, - onAutoReadClipboardClick = { settings.setEnableAutoReadClipboard(!enableAutoReadClipboard) }, - onSendAmountWarningClick = { settings.setEnableSendAmountWarning(!enableSendAmountWarning) }, - onDevSettingsClick = { navController.navigateToDevSettings() }, - onAddressTypeClick = { navController.navigateTo(Routes.AddressTypePreference) }, - onCoinSelectionClick = { navController.navigateTo(Routes.CoinSelectPreference) }, - onAddressViewerClick = { navController.navigateTo(Routes.AddressViewer) }, - onLightningConnectionsClick = { navController.navigateTo(Routes.LightningConnections) }, - onLightningNodeClick = { navController.navigateTo(Routes.NodeInfo) }, - onElectrumServerClick = { navController.navigateTo(Routes.ElectrumConfig) }, - onRgsServerClick = { navController.navigateTo(Routes.RgsServer) }, - onBackClick = { navController.popBackStack() }, ) } -@Suppress("LongParameterList") @Composable private fun SettingsContent( generalState: GeneralTabState = GeneralTabState(), securityState: SecurityTabState = SecurityTabState(), advancedState: AdvancedTabState = AdvancedTabState(), - // General callbacks - onLanguageClick: () -> Unit = {}, - onLocalCurrencyClick: () -> Unit = {}, - onDefaultUnitClick: () -> Unit = {}, - onWidgetsClick: () -> Unit = {}, - onTagsClick: () -> Unit = {}, - onTransactionSpeedClick: () -> Unit = {}, - onQuickPayClick: () -> Unit = {}, - onBgPaymentsClick: () -> Unit = {}, - // Security callbacks - onBackupWalletClick: () -> Unit = {}, - onDataBackupsClick: () -> Unit = {}, - onResetWalletClick: () -> Unit = {}, - onPinClick: () -> Unit = {}, - onPinForPaymentsClick: () -> Unit = {}, - onUseBiometricsClick: () -> Unit = {}, - onSwipeToHideBalanceClick: () -> Unit = {}, - onHideBalanceOnOpenClick: () -> Unit = {}, - onAutoReadClipboardClick: () -> Unit = {}, - onSendAmountWarningClick: () -> Unit = {}, - // Advanced callbacks - onDevSettingsClick: () -> Unit = {}, - onAddressTypeClick: () -> Unit = {}, - onCoinSelectionClick: () -> Unit = {}, - onAddressViewerClick: () -> Unit = {}, - onLightningConnectionsClick: () -> Unit = {}, - onLightningNodeClick: () -> Unit = {}, - onElectrumServerClick: () -> Unit = {}, - onRgsServerClick: () -> Unit = {}, - // Navigation - onBackClick: () -> Unit = {}, + onEvent: OnSettingsEvent = {}, initialTab: SettingsTab = SettingsTab.General, ) { val tabs = remember { SettingsTab.entries.toImmutableList() } @@ -237,7 +211,7 @@ private fun SettingsContent( ScreenColumn { AppTopBar( titleText = stringResource(R.string.settings__settings), - onBackClick = onBackClick, + onBackClick = { onEvent(SettingsEvent.BackClick) }, actions = { DrawerNavIcon() }, ) @@ -253,40 +227,17 @@ private fun SettingsContent( when (tabs[page]) { SettingsTab.General -> GeneralTabContent( state = generalState, - onLanguageClick = onLanguageClick, - onLocalCurrencyClick = onLocalCurrencyClick, - onDefaultUnitClick = onDefaultUnitClick, - onWidgetsClick = onWidgetsClick, - onTagsClick = onTagsClick, - onTransactionSpeedClick = onTransactionSpeedClick, - onQuickPayClick = onQuickPayClick, - onBgPaymentsClick = onBgPaymentsClick, + onEvent = onEvent, ) SettingsTab.Security -> SecurityTabContent( state = securityState, - onBackupWalletClick = onBackupWalletClick, - onDataBackupsClick = onDataBackupsClick, - onResetWalletClick = onResetWalletClick, - onPinClick = onPinClick, - onPinForPaymentsClick = onPinForPaymentsClick, - onUseBiometricsClick = onUseBiometricsClick, - onSwipeToHideBalanceClick = onSwipeToHideBalanceClick, - onHideBalanceOnOpenClick = onHideBalanceOnOpenClick, - onAutoReadClipboardClick = onAutoReadClipboardClick, - onSendAmountWarningClick = onSendAmountWarningClick, + onEvent = onEvent, ) SettingsTab.Advanced -> AdvancedTabContent( state = advancedState, - onDevSettingsClick = onDevSettingsClick, - onAddressTypeClick = onAddressTypeClick, - onCoinSelectionClick = onCoinSelectionClick, - onAddressViewerClick = onAddressViewerClick, - onLightningConnectionsClick = onLightningConnectionsClick, - onLightningNodeClick = onLightningNodeClick, - onElectrumServerClick = onElectrumServerClick, - onRgsServerClick = onRgsServerClick, + onEvent = onEvent, ) } } @@ -296,14 +247,7 @@ private fun SettingsContent( @Composable private fun GeneralTabContent( state: GeneralTabState, - onLanguageClick: () -> Unit, - onLocalCurrencyClick: () -> Unit, - onDefaultUnitClick: () -> Unit, - onWidgetsClick: () -> Unit, - onTagsClick: () -> Unit, - onTransactionSpeedClick: () -> Unit, - onQuickPayClick: () -> Unit, - onBgPaymentsClick: () -> Unit, + onEvent: OnSettingsEvent, ) { Column( modifier = Modifier @@ -318,14 +262,14 @@ private fun GeneralTabContent( title = stringResource(R.string.settings__language_title), icon = { SettingsIcon(R.drawable.ic_translate) }, value = SettingsButtonValue.StringValue(state.selectedLanguage), - onClick = onLanguageClick, + onClick = { onEvent(SettingsEvent.LanguageClick) }, modifier = Modifier.testTag("LanguageSettings"), ) SettingsButtonRow( title = stringResource(R.string.settings__general__currency_local), icon = { SettingsIcon(R.drawable.ic_coins) }, value = SettingsButtonValue.StringValue("${state.selectedCurrency} (${state.currencySymbol})"), - onClick = onLocalCurrencyClick, + onClick = { onEvent(SettingsEvent.LocalCurrencyClick) }, modifier = Modifier.testTag("CurrenciesSettings"), ) SettingsButtonRow( @@ -337,7 +281,7 @@ private fun GeneralTabContent( PrimaryDisplay.FIAT -> state.selectedCurrency } ), - onClick = onDefaultUnitClick, + onClick = { onEvent(SettingsEvent.DefaultUnitClick) }, modifier = Modifier.testTag("UnitSettings"), ) SettingsButtonRow( @@ -346,7 +290,7 @@ private fun GeneralTabContent( value = SettingsButtonValue.StringValue( stringResource(if (state.showWidgets) R.string.settings__bg__on else R.string.settings__bg__off) ), - onClick = onWidgetsClick, + onClick = { onEvent(SettingsEvent.WidgetsClick) }, modifier = Modifier.testTag("WidgetsSettings"), ) if (state.tagCount > 0) { @@ -354,7 +298,7 @@ private fun GeneralTabContent( title = stringResource(R.string.settings__general__tags), icon = { SettingsIcon(R.drawable.ic_tag) }, value = SettingsButtonValue.StringValue(state.tagCount.toString()), - onClick = onTagsClick, + onClick = { onEvent(SettingsEvent.TagsClick) }, modifier = Modifier.testTag("TagsSettings"), ) } @@ -377,7 +321,7 @@ private fun GeneralTabContent( ) }, value = SettingsButtonValue.StringValue(state.defaultTransactionSpeed.transactionSpeedUiText()), - onClick = onTransactionSpeedClick, + onClick = { onEvent(SettingsEvent.TransactionSpeedClick) }, modifier = Modifier.testTag("TransactionSpeedSettings"), ) SettingsButtonRow( @@ -386,7 +330,7 @@ private fun GeneralTabContent( value = SettingsButtonValue.StringValue( stringResource(if (state.isQuickPayEnabled) R.string.settings__bg__on else R.string.settings__bg__off) ), - onClick = onQuickPayClick, + onClick = { onEvent(SettingsEvent.QuickPayClick) }, modifier = Modifier.testTag("QuickpaySettings"), ) SettingsButtonRow( @@ -397,7 +341,7 @@ private fun GeneralTabContent( if (state.notificationsGranted) R.string.settings__bg__on else R.string.settings__bg__off ) ), - onClick = onBgPaymentsClick, + onClick = { onEvent(SettingsEvent.BgPaymentsClick) }, modifier = Modifier.testTag("BackgroundPaymentSettings"), ) @@ -408,16 +352,7 @@ private fun GeneralTabContent( @Composable private fun SecurityTabContent( state: SecurityTabState, - onBackupWalletClick: () -> Unit, - onDataBackupsClick: () -> Unit, - onResetWalletClick: () -> Unit, - onPinClick: () -> Unit, - onPinForPaymentsClick: () -> Unit, - onUseBiometricsClick: () -> Unit, - onSwipeToHideBalanceClick: () -> Unit, - onHideBalanceOnOpenClick: () -> Unit, - onAutoReadClipboardClick: () -> Unit, - onSendAmountWarningClick: () -> Unit, + onEvent: OnSettingsEvent, ) { Column( modifier = Modifier @@ -431,19 +366,19 @@ private fun SecurityTabContent( SettingsButtonRow( title = stringResource(R.string.settings__backup__wallet), icon = { SettingsIcon(R.drawable.ic_lock_key) }, - onClick = onBackupWalletClick, + onClick = { onEvent(SettingsEvent.BackupWalletClick) }, modifier = Modifier.testTag("BackupWallet"), ) SettingsButtonRow( title = stringResource(R.string.settings__backup__data), icon = { SettingsIcon(R.drawable.ic_database) }, - onClick = onDataBackupsClick, + onClick = { onEvent(SettingsEvent.DataBackupsClick) }, modifier = Modifier.testTag("BackupSettings"), ) SettingsButtonRow( title = stringResource(R.string.settings__backup__reset), icon = { SettingsIcon(R.drawable.ic_arrow_counter_clockwise) }, - onClick = onResetWalletClick, + onClick = { onEvent(SettingsEvent.ResetWalletClick) }, modifier = Modifier.testTag("ResetAndRestore"), ) @@ -465,7 +400,7 @@ private fun SecurityTabContent( } ) ), - onClick = onPinClick, + onClick = { onEvent(SettingsEvent.PinClick) }, modifier = Modifier.testTag("PINCode"), ) @@ -474,7 +409,7 @@ private fun SecurityTabContent( title = stringResource(R.string.settings__security__pin_payments), icon = { SettingsIcon(R.drawable.ic_coins) }, isChecked = state.isPinForPaymentsEnabled, - onClick = onPinForPaymentsClick, + onClick = { onEvent(SettingsEvent.PinForPaymentsClick) }, modifier = Modifier.testTag("EnablePinForPayments"), ) @@ -487,7 +422,7 @@ private fun SecurityTabContent( }, icon = { SettingsIcon(R.drawable.ic_smiley) }, isChecked = state.isBiometricEnabled, - onClick = onUseBiometricsClick, + onClick = { onEvent(SettingsEvent.UseBiometricsClick) }, modifier = Modifier.testTag("UseBiometryInstead"), ) } @@ -497,7 +432,7 @@ private fun SecurityTabContent( title = stringResource(R.string.settings__security__warn_100), icon = { SettingsIcon(R.drawable.ic_warning) }, isChecked = state.enableSendAmountWarning, - onClick = onSendAmountWarningClick, + onClick = { onEvent(SettingsEvent.SendAmountWarningClick) }, modifier = Modifier.testTag("SendAmountWarning"), ) @@ -511,21 +446,21 @@ private fun SecurityTabContent( title = stringResource(R.string.settings__security__swipe_balance_to_hide), icon = { SettingsIcon(R.drawable.ic_hand_pointing) }, isChecked = state.enableSwipeToHideBalance, - onClick = onSwipeToHideBalanceClick, + onClick = { onEvent(SettingsEvent.SwipeToHideBalanceClick) }, modifier = Modifier.testTag("SwipeBalanceToHide"), ) SettingsSwitchRow( title = stringResource(R.string.settings__security__hide_balance_on_open), icon = { SettingsIcon(R.drawable.ic_eye_slash) }, isChecked = state.hideBalanceOnOpen, - onClick = onHideBalanceOnOpenClick, + onClick = { onEvent(SettingsEvent.HideBalanceOnOpenClick) }, modifier = Modifier.testTag("HideBalanceOnOpen"), ) SettingsSwitchRow( title = stringResource(R.string.settings__security__clipboard), icon = { SettingsIcon(R.drawable.ic_clipboard_text) }, isChecked = state.enableAutoReadClipboard, - onClick = onAutoReadClipboardClick, + onClick = { onEvent(SettingsEvent.AutoReadClipboardClick) }, modifier = Modifier.testTag("AutoReadClipboard"), ) @@ -536,14 +471,7 @@ private fun SecurityTabContent( @Composable private fun AdvancedTabContent( state: AdvancedTabState, - onDevSettingsClick: () -> Unit, - onAddressTypeClick: () -> Unit, - onCoinSelectionClick: () -> Unit, - onAddressViewerClick: () -> Unit, - onLightningConnectionsClick: () -> Unit, - onLightningNodeClick: () -> Unit, - onElectrumServerClick: () -> Unit, - onRgsServerClick: () -> Unit, + onEvent: OnSettingsEvent, ) { Column( modifier = Modifier @@ -559,7 +487,7 @@ private fun AdvancedTabContent( SettingsButtonRow( title = stringResource(R.string.settings__dev_title), icon = { SettingsIcon(R.drawable.ic_settings_dev) }, - onClick = onDevSettingsClick, + onClick = { onEvent(SettingsEvent.DevSettingsClick) }, modifier = Modifier.testTag("DevSettings"), ) } @@ -575,19 +503,19 @@ private fun AdvancedTabContent( } else { SettingsButtonValue.None }, - onClick = onAddressTypeClick, + onClick = { onEvent(SettingsEvent.AddressTypeClick) }, modifier = Modifier.testTag("AddressTypePreference"), ) SettingsButtonRow( title = stringResource(R.string.settings__adv__coin_selection), icon = { SettingsIcon(R.drawable.ic_coins) }, - onClick = onCoinSelectionClick, + onClick = { onEvent(SettingsEvent.CoinSelectionClick) }, modifier = Modifier.testTag("CoinSelectPreference"), ) SettingsButtonRow( title = stringResource(R.string.settings__adv__address_viewer), icon = { SettingsIcon(R.drawable.ic_eye) }, - onClick = onAddressViewerClick, + onClick = { onEvent(SettingsEvent.AddressViewerClick) }, modifier = Modifier.testTag("AddressViewer"), ) @@ -605,7 +533,7 @@ private fun AdvancedTabContent( } else { SettingsButtonValue.None }, - onClick = onLightningConnectionsClick, + onClick = { onEvent(SettingsEvent.LightningConnectionsClick) }, modifier = Modifier.testTag("Channels"), ) SettingsButtonRow( @@ -616,7 +544,7 @@ private fun AdvancedTabContent( } else { SettingsButtonValue.None }, - onClick = onLightningNodeClick, + onClick = { onEvent(SettingsEvent.LightningNodeClick) }, modifier = Modifier.testTag("LightningNodeInfo"), ) SettingsButtonRow( @@ -631,13 +559,13 @@ private fun AdvancedTabContent( } ) ), - onClick = onElectrumServerClick, + onClick = { onEvent(SettingsEvent.ElectrumServerClick) }, modifier = Modifier.testTag("ElectrumConfig"), ) SettingsButtonRow( title = stringResource(R.string.settings__adv__rgs_server), icon = { SettingsIcon(R.drawable.ic_broadcast) }, - onClick = onRgsServerClick, + onClick = { onEvent(SettingsEvent.RgsServerClick) }, modifier = Modifier.testTag("RGSServer"), ) @@ -692,6 +620,45 @@ private fun PreviewAdvanced() { } } +sealed interface SettingsEvent { + // General + data object LanguageClick : SettingsEvent + data object LocalCurrencyClick : SettingsEvent + data object DefaultUnitClick : SettingsEvent + data object WidgetsClick : SettingsEvent + data object TagsClick : SettingsEvent + data object TransactionSpeedClick : SettingsEvent + data object QuickPayClick : SettingsEvent + data object BgPaymentsClick : SettingsEvent + + // Security + data object BackupWalletClick : SettingsEvent + data object DataBackupsClick : SettingsEvent + data object ResetWalletClick : SettingsEvent + data object PinClick : SettingsEvent + data object PinForPaymentsClick : SettingsEvent + data object UseBiometricsClick : SettingsEvent + data object SwipeToHideBalanceClick : SettingsEvent + data object HideBalanceOnOpenClick : SettingsEvent + data object AutoReadClipboardClick : SettingsEvent + data object SendAmountWarningClick : SettingsEvent + + // Advanced + data object DevSettingsClick : SettingsEvent + data object AddressTypeClick : SettingsEvent + data object CoinSelectionClick : SettingsEvent + data object AddressViewerClick : SettingsEvent + data object LightningConnectionsClick : SettingsEvent + data object LightningNodeClick : SettingsEvent + data object ElectrumServerClick : SettingsEvent + data object RgsServerClick : SettingsEvent + + // Navigation + data object BackClick : SettingsEvent +} + +typealias OnSettingsEvent = (SettingsEvent) -> Unit + @Immutable data class GeneralTabState( val selectedCurrency: String = "USD", From 2b6920c74d58b2b2926c6cdc7ee0ef32aa3e7727 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 25 Mar 2026 09:02:28 -0300 Subject: [PATCH 48/85] refactor: shadowing parameter --- app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt index 095ed4dc1..b57cbf91b 100644 --- a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt +++ b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt @@ -127,14 +127,13 @@ fun ChangePinSheet(app: AppViewModel) { ConfirmContent( pin = pin, showError = showError, - onKeyPress = { pin = handlePinKeyPress(pin, it) }, + onKeyPress = { key -> pin = handlePinKeyPress(pin, key) }, onBackClick = { navController.popBackStack() }, ) } composableWithDefaultTransitions { ResultContent( onOkClick = onDismiss, - onBackClick = onDismiss, ) } } @@ -329,7 +328,6 @@ private fun ConfirmContent( @Composable private fun ResultContent( onOkClick: () -> Unit, - onBackClick: () -> Unit, ) { Column( modifier = Modifier @@ -419,7 +417,6 @@ private fun PreviewResult() { AppThemeSurface { ResultContent( onOkClick = {}, - onBackClick = {}, ) } } From 35b9f49c6550db8a0b277ec36f1741f82425863a Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 25 Mar 2026 09:10:34 -0300 Subject: [PATCH 49/85] feat: remove black BG --- app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt index b57cbf91b..80c6d8c7b 100644 --- a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt +++ b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt @@ -264,7 +264,6 @@ private fun NewPinContent( type = NumberPadType.SIMPLE, modifier = Modifier .height(NumberPadHeight) - .background(Colors.Black), ) } } From 36d8c0f328d093f7a8219e4a2d334fdcdb410052 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 25 Mar 2026 09:26:08 -0300 Subject: [PATCH 50/85] feat: remove black BG from NumberPad --- .../to/bitkit/ui/screens/wallets/send/SendPinCheckScreen.kt | 2 -- .../main/java/to/bitkit/ui/settings/pin/PinChooseScreen.kt | 2 -- .../main/java/to/bitkit/ui/settings/pin/PinConfirmScreen.kt | 2 -- app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt | 4 +--- 4 files changed, 1 insertion(+), 9 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendPinCheckScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendPinCheckScreen.kt index 413573e47..02d34c3e3 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendPinCheckScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendPinCheckScreen.kt @@ -1,7 +1,6 @@ package to.bitkit.ui.screens.wallets.send import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height @@ -145,7 +144,6 @@ private fun PinCheckContent( type = NumberPadType.SIMPLE, modifier = Modifier .height(350.dp) - .background(Colors.Black) ) } } diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/PinChooseScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/PinChooseScreen.kt index c1584133b..a0b5f42f7 100644 --- a/app/src/main/java/to/bitkit/ui/settings/pin/PinChooseScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/pin/PinChooseScreen.kt @@ -1,6 +1,5 @@ package to.bitkit.ui.settings.pin -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -75,7 +74,6 @@ fun PinChooseScreen( type = NumberPadType.SIMPLE, modifier = Modifier .height(350.dp) - .background(Colors.Black) ) } } diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/PinConfirmScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/PinConfirmScreen.kt index 5b8a74e82..2e32ffea6 100644 --- a/app/src/main/java/to/bitkit/ui/settings/pin/PinConfirmScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/pin/PinConfirmScreen.kt @@ -1,7 +1,6 @@ package to.bitkit.ui.settings.pin import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -127,7 +126,6 @@ private fun ConfirmPinContent( type = NumberPadType.SIMPLE, modifier = Modifier .height(350.dp) - .background(Colors.Black) ) } } diff --git a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt index 80c6d8c7b..fffd33bf6 100644 --- a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt +++ b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt @@ -2,7 +2,6 @@ package to.bitkit.ui.sheets import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -318,8 +317,7 @@ private fun ConfirmContent( onPress = onKeyPress, type = NumberPadType.SIMPLE, modifier = Modifier - .height(NumberPadHeight) - .background(Colors.Black), + .height(NumberPadHeight), ) } } From 4f8d91e24710c39cf0201a16ebb0b7bf844e4ec3 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 25 Mar 2026 09:45:24 -0300 Subject: [PATCH 51/85] feat: coin selection label --- .../to/bitkit/ui/settings/AdvancedSettingsViewModel.kt | 4 ++++ app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsViewModel.kt b/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsViewModel.kt index f0d885525..df8577621 100644 --- a/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsViewModel.kt +++ b/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsViewModel.kt @@ -39,6 +39,10 @@ class AdvancedSettingsViewModel @Inject constructor( .map { it.electrumServer != Env.electrumServerUrl } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false) + val coinSelectAuto = settingsStore.data + .map { it.coinSelectAuto } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), true) + fun resetSuggestions() { viewModelScope.launch { settingsStore.update { it.copy(dismissedSuggestions = emptyList()) } diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index 88e2820ca..adf17cf68 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -106,6 +106,7 @@ fun SettingsScreen( val openChannelCount by advancedViewModel.openChannelCount.collectAsStateWithLifecycle() val truncatedNodeId by advancedViewModel.truncatedNodeId.collectAsStateWithLifecycle() val isCustomElectrum by advancedViewModel.isCustomElectrum.collectAsStateWithLifecycle() + val coinSelectAuto by advancedViewModel.coinSelectAuto.collectAsStateWithLifecycle() LaunchedEffect(Unit) { languageViewModel.fetchLanguageInfo() } @@ -134,6 +135,7 @@ fun SettingsScreen( advancedState = AdvancedTabState( isDevModeEnabled = isDevModeEnabled, selectedAddressTypeName = selectedAddressTypeName, + coinSelectAuto = coinSelectAuto, openChannelCount = openChannelCount, truncatedNodeId = truncatedNodeId, isCustomElectrum = isCustomElectrum, @@ -509,6 +511,11 @@ private fun AdvancedTabContent( SettingsButtonRow( title = stringResource(R.string.settings__adv__coin_selection), icon = { SettingsIcon(R.drawable.ic_coins) }, + value = SettingsButtonValue.StringValue( + stringResource( + if (state.coinSelectAuto) R.string.settings__adv__cs_auto else R.string.settings__adv__cs_manual + ) + ), onClick = { onEvent(SettingsEvent.CoinSelectionClick) }, modifier = Modifier.testTag("CoinSelectPreference"), ) @@ -688,6 +695,7 @@ data class SecurityTabState( data class AdvancedTabState( val isDevModeEnabled: Boolean = false, val selectedAddressTypeName: String = "", + val coinSelectAuto: Boolean = true, val openChannelCount: Int = 0, val truncatedNodeId: String = "", val isCustomElectrum: Boolean = false, From 41769600c3ba9f9d72f54a5a454c2777f365b50a Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Wed, 25 Mar 2026 10:00:50 -0300 Subject: [PATCH 52/85] chore: lint --- .../to/bitkit/ui/components/settings/SettingsSwitchRow.kt | 4 +--- app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt | 8 -------- app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt | 6 +++--- app/src/main/res/values/strings.xml | 2 +- 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt b/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt index b7bedc317..dd262955f 100644 --- a/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt +++ b/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt @@ -3,12 +3,10 @@ package to.bitkit.ui.components.settings import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.Switch @@ -55,7 +53,7 @@ fun SettingsSwitchRow( tint = iconTint, modifier = Modifier.size(32.dp), ) - Spacer(modifier = Modifier.width(10.dp)) + HorizontalSpacer(10.dp) } } else { null diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index adf17cf68..e8cd8b494 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -257,7 +257,6 @@ private fun GeneralTabContent( .padding(horizontal = 16.dp) .verticalScroll(rememberScrollState()) ) { - // Interface section SectionHeader(title = stringResource(R.string.settings__general__section_interface)) SettingsButtonRow( @@ -305,7 +304,6 @@ private fun GeneralTabContent( ) } - // Payments section SectionHeader( title = stringResource(R.string.settings__general__section_payments), padding = PaddingValues(top = 16.dp), @@ -362,7 +360,6 @@ private fun SecurityTabContent( .padding(horizontal = 16.dp) .verticalScroll(rememberScrollState()) ) { - // Back up or reset section SectionHeader(title = stringResource(R.string.settings__security__section_backup)) SettingsButtonRow( @@ -384,7 +381,6 @@ private fun SecurityTabContent( modifier = Modifier.testTag("ResetAndRestore"), ) - // Safety section SectionHeader( title = stringResource(R.string.settings__security__section_safety), padding = PaddingValues(top = 16.dp), @@ -438,7 +434,6 @@ private fun SecurityTabContent( modifier = Modifier.testTag("SendAmountWarning"), ) - // Privacy section SectionHeader( title = stringResource(R.string.settings__security__section_privacy), padding = PaddingValues(top = 16.dp), @@ -482,7 +477,6 @@ private fun AdvancedTabContent( .verticalScroll(rememberScrollState()) .testTag("advanced_settings_screen") ) { - // Debug section (only if dev mode enabled) if (state.isDevModeEnabled) { SectionHeader(title = stringResource(R.string.settings__adv__section_debug)) @@ -494,7 +488,6 @@ private fun AdvancedTabContent( ) } - // Payments section SectionHeader(title = stringResource(R.string.settings__adv__section_payments)) SettingsButtonRow( @@ -526,7 +519,6 @@ private fun AdvancedTabContent( modifier = Modifier.testTag("AddressViewer"), ) - // Networks section SectionHeader( title = stringResource(R.string.settings__adv__section_networks), padding = PaddingValues(top = 16.dp), diff --git a/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt index 83a388c44..f3b82bffb 100644 --- a/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt +++ b/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt @@ -57,7 +57,7 @@ fun DisablePinSheet(app: AppViewModel) { } } - DisablePinContent( + Content( pin = pin, attemptsRemaining = attemptsRemaining, onKeyPress = { pin = handlePinKeyPress(pin, it) }, @@ -67,7 +67,7 @@ fun DisablePinSheet(app: AppViewModel) { } @Composable -private fun DisablePinContent( +private fun Content( pin: String, attemptsRemaining: Int, onKeyPress: (String) -> Unit, @@ -143,7 +143,7 @@ private fun DisablePinContent( @Composable private fun Preview() { AppThemeSurface { - DisablePinContent( + Content( pin = "12", attemptsRemaining = 8, onKeyPress = {}, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e75a6cec2..df7cd85b1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -866,8 +866,8 @@ Profile Settings Shop - Support App Status + Support Wallet Widgets Invalid bitcoin send address From a1c2ced10bd904d58711531765797d194c630996 Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Wed, 25 Mar 2026 19:50:35 +0100 Subject: [PATCH 53/85] test tag for devMode toasts --- app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt index 6c17c5f7e..3c7cd6dc7 100644 --- a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt @@ -117,6 +117,7 @@ fun SupportScreen( R.string.settings__dev_disabled_message } ), + testTag = if (newValue) "DevModeEnabledToast" else "DevModeDisabledToast", ) devModeTapCount = 0 } From 6249b3f6d0255f1a2083011e2c45f95c85a58f29 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 26 Mar 2026 07:49:12 -0300 Subject: [PATCH 54/85] chore: remove trailing comma after modifier --- app/src/main/java/to/bitkit/ui/ContentView.kt | 4 +- .../to/bitkit/ui/components/DrawerMenu.kt | 8 +-- .../to/bitkit/ui/components/settings/Links.kt | 6 +- .../components/settings/SettingsButtonRow.kt | 12 ++-- .../ui/components/settings/SettingsIcon.kt | 6 +- .../components/settings/SettingsSwitchRow.kt | 18 +++--- .../java/to/bitkit/ui/scaffold/AppTopBar.kt | 10 ++-- .../wallets/send/SendPinCheckScreen.kt | 10 ++-- .../ui/settings/BackupSettingsScreen.kt | 4 +- .../to/bitkit/ui/settings/SettingsScreen.kt | 60 +++++++++---------- .../settings/general/WidgetsSettingsScreen.kt | 10 ++-- .../bitkit/ui/settings/pin/PinChooseScreen.kt | 2 +- .../ui/settings/pin/PinConfirmScreen.kt | 4 +- .../ui/settings/pin/PinManagementScreen.kt | 12 ++-- .../ui/settings/support/SupportScreen.kt | 18 +++--- .../to/bitkit/ui/sheets/ChangePinSheet.kt | 32 +++++----- .../to/bitkit/ui/sheets/DisablePinSheet.kt | 8 +-- 17 files changed, 112 insertions(+), 112 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt index bef184dd0..75b722a2e 100644 --- a/app/src/main/java/to/bitkit/ui/ContentView.kt +++ b/app/src/main/java/to/bitkit/ui/ContentView.kt @@ -199,7 +199,7 @@ fun ContentView( settingsViewModel: SettingsViewModel, backupsViewModel: BackupsViewModel, hazeState: HazeState, - modifier: Modifier = Modifier, + modifier: Modifier = Modifier ) { val navController = rememberNavController() @@ -487,7 +487,7 @@ fun ContentView( rootNavController = navController, hasSeenWidgetsIntro = hasSeenWidgetsIntro, hasSeenShopIntro = hasSeenShopIntro, - modifier = Modifier.align(Alignment.TopEnd), + modifier = Modifier.align(Alignment.TopEnd) ) } } diff --git a/app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt b/app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt index d24631209..d797b4bcf 100644 --- a/app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt +++ b/app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt @@ -68,7 +68,7 @@ fun DrawerMenu( rootNavController: NavController, hasSeenWidgetsIntro: Boolean, hasSeenShopIntro: Boolean, - modifier: Modifier = Modifier, + modifier: Modifier = Modifier ) { val scope = rememberCoroutineScope() @@ -246,7 +246,7 @@ private fun Menu( private fun Scrim( visible: Boolean, onClick: () -> Unit, - modifier: Modifier = Modifier, + modifier: Modifier = Modifier ) { AnimatedVisibility( visible = visible, @@ -266,7 +266,7 @@ private fun DrawerItem( label: String, @DrawableRes iconRes: Int, modifier: Modifier = Modifier, - onClick: (() -> Unit)? = null, + onClick: (() -> Unit)? = null ) { Column( modifier = modifier @@ -316,7 +316,7 @@ private fun Preview() { drawerState = rememberDrawerState(initialValue = DrawerValue.Open), hasSeenWidgetsIntro = false, hasSeenShopIntro = false, - modifier = Modifier.align(Alignment.TopEnd), + modifier = Modifier.align(Alignment.TopEnd) ) } } diff --git a/app/src/main/java/to/bitkit/ui/components/settings/Links.kt b/app/src/main/java/to/bitkit/ui/components/settings/Links.kt index 11a2e03b9..8c6083a38 100644 --- a/app/src/main/java/to/bitkit/ui/components/settings/Links.kt +++ b/app/src/main/java/to/bitkit/ui/components/settings/Links.kt @@ -33,19 +33,19 @@ fun Links(modifier: Modifier = Modifier) { Row( horizontalArrangement = Arrangement.SpaceBetween, - modifier = modifier, + modifier = modifier ) { socialLinks.forEach { link -> IconButton( onClick = { context.startActivity(Intent(Intent.ACTION_VIEW, link.url.toUri())) }, - modifier = Modifier.size(48.dp), + modifier = Modifier.size(48.dp) ) { Icon( painter = painterResource(link.iconRes), contentDescription = null, - modifier = Modifier.size(24.dp), + modifier = Modifier.size(24.dp) ) } } diff --git a/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt b/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt index 7cf039172..109919170 100644 --- a/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt +++ b/app/src/main/java/to/bitkit/ui/components/settings/SettingsButtonRow.kt @@ -74,7 +74,7 @@ fun SettingsButtonRow( } else { null }, - modifier = modifier, + modifier = modifier ) } @@ -105,7 +105,7 @@ fun SettingsButtonRow( enabled = enabled, loading = loading, onClick = onClick, - modifier = modifier, + modifier = modifier ) } @@ -178,7 +178,7 @@ private fun SettingsButtonRowCore( painter = painterResource(R.drawable.ic_checkmark), contentDescription = null, tint = Colors.Brand, - modifier = Modifier.size(32.dp), + modifier = Modifier.size(32.dp) ) else -> Unit @@ -193,7 +193,7 @@ private fun SettingsButtonRowCore( painter = painterResource(R.drawable.ic_chevron_right), contentDescription = null, tint = Colors.White64, - modifier = Modifier.size(24.dp), + modifier = Modifier.size(24.dp) ) } @@ -202,7 +202,7 @@ private fun SettingsButtonRowCore( painter = painterResource(R.drawable.ic_chevron_right), contentDescription = null, tint = Colors.White64, - modifier = Modifier.size(24.dp), + modifier = Modifier.size(24.dp) ) } } @@ -215,7 +215,7 @@ private fun SettingsButtonRowCore( color = Colors.White64, modifier = Modifier .padding(vertical = 4.dp) - .then(alphaModifier), + .then(alphaModifier) ) } } diff --git a/app/src/main/java/to/bitkit/ui/components/settings/SettingsIcon.kt b/app/src/main/java/to/bitkit/ui/components/settings/SettingsIcon.kt index 49dfd13de..88309b448 100644 --- a/app/src/main/java/to/bitkit/ui/components/settings/SettingsIcon.kt +++ b/app/src/main/java/to/bitkit/ui/components/settings/SettingsIcon.kt @@ -16,19 +16,19 @@ import to.bitkit.ui.theme.Colors @Composable fun SettingsIcon( @DrawableRes iconRes: Int, - modifier: Modifier = Modifier, + modifier: Modifier = Modifier ) { Box( contentAlignment = Alignment.Center, modifier = modifier .size(32.dp) - .background(color = Colors.Black, shape = CircleShape), + .background(color = Colors.Black, shape = CircleShape) ) { Icon( painter = painterResource(iconRes), contentDescription = null, tint = Colors.Brand, - modifier = Modifier.size(16.dp), + modifier = Modifier.size(16.dp) ) } } diff --git a/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt b/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt index dd262955f..7075046c0 100644 --- a/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt +++ b/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt @@ -37,7 +37,7 @@ fun SettingsSwitchRow( subtitle: String? = null, iconRes: Int? = null, iconTint: Color = Color.Unspecified, - colors: SwitchColors = AppSwitchDefaults.colors, + colors: SwitchColors = AppSwitchDefaults.colors ) { SettingsSwitchRowCore( title = title, @@ -51,14 +51,14 @@ fun SettingsSwitchRow( painter = painterResource(iconRes), contentDescription = null, tint = iconTint, - modifier = Modifier.size(32.dp), + modifier = Modifier.size(32.dp) ) HorizontalSpacer(10.dp) } } else { null }, - modifier = modifier, + modifier = modifier ) } @@ -70,7 +70,7 @@ fun SettingsSwitchRow( onClick: () -> Unit, modifier: Modifier = Modifier, subtitle: String? = null, - colors: SwitchColors = AppSwitchDefaults.colors, + colors: SwitchColors = AppSwitchDefaults.colors ) { SettingsSwitchRowCore( title = title, @@ -82,7 +82,7 @@ fun SettingsSwitchRow( icon() HorizontalSpacer(8.dp) }, - modifier = modifier, + modifier = modifier ) } @@ -94,10 +94,10 @@ private fun SettingsSwitchRowCore( modifier: Modifier = Modifier, subtitle: String? = null, icon: (@Composable () -> Unit)? = null, - colors: SwitchColors = AppSwitchDefaults.colors, + colors: SwitchColors = AppSwitchDefaults.colors ) { Column( - modifier = modifier.heightIn(min = 52.dp), + modifier = modifier.heightIn(min = 52.dp) ) { Row( horizontalArrangement = Arrangement.SpaceBetween, @@ -115,7 +115,7 @@ private fun SettingsSwitchRowCore( modifier = Modifier .weight(1f) .padding(end = 16.dp), - verticalArrangement = Arrangement.spacedBy(4.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) ) { BodyM(text = title, color = Colors.White, overflow = TextOverflow.Ellipsis) if (subtitle != null) { @@ -141,7 +141,7 @@ private fun Preview() { SettingsSwitchRow( title = "Setting 1", isChecked = true, - onClick = {}, + onClick = {} ) SettingsSwitchRow( title = "Setting 2", diff --git a/app/src/main/java/to/bitkit/ui/scaffold/AppTopBar.kt b/app/src/main/java/to/bitkit/ui/scaffold/AppTopBar.kt index 2dd1e8751..00c5565c0 100644 --- a/app/src/main/java/to/bitkit/ui/scaffold/AppTopBar.kt +++ b/app/src/main/java/to/bitkit/ui/scaffold/AppTopBar.kt @@ -39,7 +39,7 @@ fun AppTopBar( onBackClick: (() -> Unit)?, modifier: Modifier = Modifier, @DrawableRes icon: Int? = null, - actions: @Composable (RowScope.() -> Unit) = {}, + actions: @Composable (RowScope.() -> Unit) = {} ) { CenterAlignedTopAppBar( navigationIcon = { @@ -70,14 +70,14 @@ fun AppTopBar( containerColor = Color.Transparent, scrolledContainerColor = Color.Transparent, ), - modifier = modifier, + modifier = modifier ) } @Composable fun BackNavIcon( onClick: () -> Unit, - modifier: Modifier = Modifier, + modifier: Modifier = Modifier ) { IconButton( onClick = rememberDebouncedClick(onClick = onClick), @@ -93,7 +93,7 @@ fun BackNavIcon( @Composable fun DrawerNavIcon( - modifier: Modifier = Modifier, + modifier: Modifier = Modifier ) { val isPreview = LocalInspectionMode.current val drawerState = LocalDrawerState.current @@ -118,7 +118,7 @@ fun DrawerNavIcon( @Composable fun ScanNavIcon( onClick: () -> Unit, - modifier: Modifier = Modifier, + modifier: Modifier = Modifier ) { IconButton( onClick = rememberDebouncedClick(onClick = onClick), diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendPinCheckScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendPinCheckScreen.kt index 02d34c3e3..c572784e2 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendPinCheckScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendPinCheckScreen.kt @@ -81,7 +81,7 @@ private fun PinCheckContent( onKeyPress: (String) -> Unit, onBack: () -> Unit, onClickForgotPin: () -> Unit, - modifier: Modifier = Modifier, + modifier: Modifier = Modifier ) { val isLastAttempt = attemptsRemaining == 1 @@ -134,7 +134,7 @@ private fun PinCheckContent( PinDots( pin = pin, - modifier = Modifier.padding(vertical = 16.dp), + modifier = Modifier.padding(vertical = 16.dp) ) Spacer(modifier = Modifier.weight(1f)) @@ -160,7 +160,7 @@ private fun Preview() { onKeyPress = {}, onBack = {}, onClickForgotPin = {}, - modifier = Modifier.sheetHeight(), + modifier = Modifier.sheetHeight() ) } } @@ -177,7 +177,7 @@ private fun PreviewAttemptsLeft() { onKeyPress = {}, onBack = {}, onClickForgotPin = {}, - modifier = Modifier.sheetHeight(), + modifier = Modifier.sheetHeight() ) } } @@ -194,7 +194,7 @@ private fun PreviewAttemptsLast() { onKeyPress = {}, onBack = {}, onClickForgotPin = {}, - modifier = Modifier.sheetHeight(), + modifier = Modifier.sheetHeight() ) } } diff --git a/app/src/main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt index 31a4aca05..f90a4be10 100644 --- a/app/src/main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt @@ -122,7 +122,7 @@ private fun BackupSettingsScreenContent( private fun BackupStatusItem( uiState: BackupCategoryUiState, onRetryClick: (BackupCategory) -> Unit, - modifier: Modifier = Modifier, + modifier: Modifier = Modifier ) { val status = uiState.status @@ -168,7 +168,7 @@ private fun BackupStatusItem( private fun BackupStatusIcon( status: BackupItemStatus, @DrawableRes iconRes: Int, - modifier: Modifier = Modifier, + modifier: Modifier = Modifier ) { Box( contentAlignment = Alignment.Center, diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index e8cd8b494..a9fece8b2 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -222,7 +222,7 @@ private fun SettingsContent( currentTabIndex = pagerState.currentPage, selectedColor = Colors.White, onTabChange = { scope.launch { pagerState.animateScrollToPage(tabs.indexOf(it)) } }, - modifier = Modifier.padding(horizontal = 16.dp), + modifier = Modifier.padding(horizontal = 16.dp) ) HorizontalPager(state = pagerState) { page -> @@ -264,14 +264,14 @@ private fun GeneralTabContent( icon = { SettingsIcon(R.drawable.ic_translate) }, value = SettingsButtonValue.StringValue(state.selectedLanguage), onClick = { onEvent(SettingsEvent.LanguageClick) }, - modifier = Modifier.testTag("LanguageSettings"), + modifier = Modifier.testTag("LanguageSettings") ) SettingsButtonRow( title = stringResource(R.string.settings__general__currency_local), icon = { SettingsIcon(R.drawable.ic_coins) }, value = SettingsButtonValue.StringValue("${state.selectedCurrency} (${state.currencySymbol})"), onClick = { onEvent(SettingsEvent.LocalCurrencyClick) }, - modifier = Modifier.testTag("CurrenciesSettings"), + modifier = Modifier.testTag("CurrenciesSettings") ) SettingsButtonRow( title = stringResource(R.string.settings__general__unit), @@ -283,7 +283,7 @@ private fun GeneralTabContent( } ), onClick = { onEvent(SettingsEvent.DefaultUnitClick) }, - modifier = Modifier.testTag("UnitSettings"), + modifier = Modifier.testTag("UnitSettings") ) SettingsButtonRow( title = stringResource(R.string.settings__widgets__nav_title), @@ -292,7 +292,7 @@ private fun GeneralTabContent( stringResource(if (state.showWidgets) R.string.settings__bg__on else R.string.settings__bg__off) ), onClick = { onEvent(SettingsEvent.WidgetsClick) }, - modifier = Modifier.testTag("WidgetsSettings"), + modifier = Modifier.testTag("WidgetsSettings") ) if (state.tagCount > 0) { SettingsButtonRow( @@ -300,7 +300,7 @@ private fun GeneralTabContent( icon = { SettingsIcon(R.drawable.ic_tag) }, value = SettingsButtonValue.StringValue(state.tagCount.toString()), onClick = { onEvent(SettingsEvent.TagsClick) }, - modifier = Modifier.testTag("TagsSettings"), + modifier = Modifier.testTag("TagsSettings") ) } @@ -322,7 +322,7 @@ private fun GeneralTabContent( }, value = SettingsButtonValue.StringValue(state.defaultTransactionSpeed.transactionSpeedUiText()), onClick = { onEvent(SettingsEvent.TransactionSpeedClick) }, - modifier = Modifier.testTag("TransactionSpeedSettings"), + modifier = Modifier.testTag("TransactionSpeedSettings") ) SettingsButtonRow( title = stringResource(R.string.settings__quickpay__nav_title), @@ -331,7 +331,7 @@ private fun GeneralTabContent( stringResource(if (state.isQuickPayEnabled) R.string.settings__bg__on else R.string.settings__bg__off) ), onClick = { onEvent(SettingsEvent.QuickPayClick) }, - modifier = Modifier.testTag("QuickpaySettings"), + modifier = Modifier.testTag("QuickpaySettings") ) SettingsButtonRow( title = stringResource(R.string.settings__bg__title), @@ -342,7 +342,7 @@ private fun GeneralTabContent( ) ), onClick = { onEvent(SettingsEvent.BgPaymentsClick) }, - modifier = Modifier.testTag("BackgroundPaymentSettings"), + modifier = Modifier.testTag("BackgroundPaymentSettings") ) VerticalSpacer(32.dp) @@ -366,24 +366,24 @@ private fun SecurityTabContent( title = stringResource(R.string.settings__backup__wallet), icon = { SettingsIcon(R.drawable.ic_lock_key) }, onClick = { onEvent(SettingsEvent.BackupWalletClick) }, - modifier = Modifier.testTag("BackupWallet"), + modifier = Modifier.testTag("BackupWallet") ) SettingsButtonRow( title = stringResource(R.string.settings__backup__data), icon = { SettingsIcon(R.drawable.ic_database) }, onClick = { onEvent(SettingsEvent.DataBackupsClick) }, - modifier = Modifier.testTag("BackupSettings"), + modifier = Modifier.testTag("BackupSettings") ) SettingsButtonRow( title = stringResource(R.string.settings__backup__reset), icon = { SettingsIcon(R.drawable.ic_arrow_counter_clockwise) }, onClick = { onEvent(SettingsEvent.ResetWalletClick) }, - modifier = Modifier.testTag("ResetAndRestore"), + modifier = Modifier.testTag("ResetAndRestore") ) SectionHeader( title = stringResource(R.string.settings__security__section_safety), - padding = PaddingValues(top = 16.dp), + padding = PaddingValues(top = 16.dp) ) SettingsButtonRow( @@ -399,7 +399,7 @@ private fun SecurityTabContent( ) ), onClick = { onEvent(SettingsEvent.PinClick) }, - modifier = Modifier.testTag("PINCode"), + modifier = Modifier.testTag("PINCode") ) if (state.isPinEnabled) { @@ -408,7 +408,7 @@ private fun SecurityTabContent( icon = { SettingsIcon(R.drawable.ic_coins) }, isChecked = state.isPinForPaymentsEnabled, onClick = { onEvent(SettingsEvent.PinForPaymentsClick) }, - modifier = Modifier.testTag("EnablePinForPayments"), + modifier = Modifier.testTag("EnablePinForPayments") ) if (state.isBiometrySupported) { @@ -421,7 +421,7 @@ private fun SecurityTabContent( icon = { SettingsIcon(R.drawable.ic_smiley) }, isChecked = state.isBiometricEnabled, onClick = { onEvent(SettingsEvent.UseBiometricsClick) }, - modifier = Modifier.testTag("UseBiometryInstead"), + modifier = Modifier.testTag("UseBiometryInstead") ) } } @@ -431,12 +431,12 @@ private fun SecurityTabContent( icon = { SettingsIcon(R.drawable.ic_warning) }, isChecked = state.enableSendAmountWarning, onClick = { onEvent(SettingsEvent.SendAmountWarningClick) }, - modifier = Modifier.testTag("SendAmountWarning"), + modifier = Modifier.testTag("SendAmountWarning") ) SectionHeader( title = stringResource(R.string.settings__security__section_privacy), - padding = PaddingValues(top = 16.dp), + padding = PaddingValues(top = 16.dp) ) SettingsSwitchRow( @@ -444,21 +444,21 @@ private fun SecurityTabContent( icon = { SettingsIcon(R.drawable.ic_hand_pointing) }, isChecked = state.enableSwipeToHideBalance, onClick = { onEvent(SettingsEvent.SwipeToHideBalanceClick) }, - modifier = Modifier.testTag("SwipeBalanceToHide"), + modifier = Modifier.testTag("SwipeBalanceToHide") ) SettingsSwitchRow( title = stringResource(R.string.settings__security__hide_balance_on_open), icon = { SettingsIcon(R.drawable.ic_eye_slash) }, isChecked = state.hideBalanceOnOpen, onClick = { onEvent(SettingsEvent.HideBalanceOnOpenClick) }, - modifier = Modifier.testTag("HideBalanceOnOpen"), + modifier = Modifier.testTag("HideBalanceOnOpen") ) SettingsSwitchRow( title = stringResource(R.string.settings__security__clipboard), icon = { SettingsIcon(R.drawable.ic_clipboard_text) }, isChecked = state.enableAutoReadClipboard, onClick = { onEvent(SettingsEvent.AutoReadClipboardClick) }, - modifier = Modifier.testTag("AutoReadClipboard"), + modifier = Modifier.testTag("AutoReadClipboard") ) VerticalSpacer(32.dp) @@ -484,7 +484,7 @@ private fun AdvancedTabContent( title = stringResource(R.string.settings__dev_title), icon = { SettingsIcon(R.drawable.ic_settings_dev) }, onClick = { onEvent(SettingsEvent.DevSettingsClick) }, - modifier = Modifier.testTag("DevSettings"), + modifier = Modifier.testTag("DevSettings") ) } @@ -499,7 +499,7 @@ private fun AdvancedTabContent( SettingsButtonValue.None }, onClick = { onEvent(SettingsEvent.AddressTypeClick) }, - modifier = Modifier.testTag("AddressTypePreference"), + modifier = Modifier.testTag("AddressTypePreference") ) SettingsButtonRow( title = stringResource(R.string.settings__adv__coin_selection), @@ -510,18 +510,18 @@ private fun AdvancedTabContent( ) ), onClick = { onEvent(SettingsEvent.CoinSelectionClick) }, - modifier = Modifier.testTag("CoinSelectPreference"), + modifier = Modifier.testTag("CoinSelectPreference") ) SettingsButtonRow( title = stringResource(R.string.settings__adv__address_viewer), icon = { SettingsIcon(R.drawable.ic_eye) }, onClick = { onEvent(SettingsEvent.AddressViewerClick) }, - modifier = Modifier.testTag("AddressViewer"), + modifier = Modifier.testTag("AddressViewer") ) SectionHeader( title = stringResource(R.string.settings__adv__section_networks), - padding = PaddingValues(top = 16.dp), + padding = PaddingValues(top = 16.dp) ) SettingsButtonRow( @@ -533,7 +533,7 @@ private fun AdvancedTabContent( SettingsButtonValue.None }, onClick = { onEvent(SettingsEvent.LightningConnectionsClick) }, - modifier = Modifier.testTag("Channels"), + modifier = Modifier.testTag("Channels") ) SettingsButtonRow( title = stringResource(R.string.settings__adv__lightning_node), @@ -544,7 +544,7 @@ private fun AdvancedTabContent( SettingsButtonValue.None }, onClick = { onEvent(SettingsEvent.LightningNodeClick) }, - modifier = Modifier.testTag("LightningNodeInfo"), + modifier = Modifier.testTag("LightningNodeInfo") ) SettingsButtonRow( title = stringResource(R.string.settings__adv__electrum_server), @@ -559,13 +559,13 @@ private fun AdvancedTabContent( ) ), onClick = { onEvent(SettingsEvent.ElectrumServerClick) }, - modifier = Modifier.testTag("ElectrumConfig"), + modifier = Modifier.testTag("ElectrumConfig") ) SettingsButtonRow( title = stringResource(R.string.settings__adv__rgs_server), icon = { SettingsIcon(R.drawable.ic_broadcast) }, onClick = { onEvent(SettingsEvent.RgsServerClick) }, - modifier = Modifier.testTag("RGSServer"), + modifier = Modifier.testTag("RGSServer") ) VerticalSpacer(32.dp) diff --git a/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt index b4f14453a..59e340324 100644 --- a/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt @@ -71,7 +71,7 @@ private fun WidgetsSettingsContent( Column( modifier = Modifier .padding(horizontal = 16.dp) - .verticalScroll(rememberScrollState()), + .verticalScroll(rememberScrollState()) ) { // Display section SectionHeader(title = stringResource(R.string.settings__widgets__section_display)) @@ -97,13 +97,13 @@ private fun WidgetsSettingsContent( title = stringResource(R.string.settings__widgets__reset_widgets), icon = { SettingsIcon(R.drawable.ic_arrow_counter_clockwise) }, onClick = { showResetWidgetsDialog = true }, - modifier = Modifier.testTag("ResetWidgets"), + modifier = Modifier.testTag("ResetWidgets") ) SettingsButtonRow( title = stringResource(R.string.settings__widgets__reset_suggestions), icon = { SettingsIcon(R.drawable.ic_arrow_counter_clockwise) }, onClick = { showResetSuggestionsDialog = true }, - modifier = Modifier.testTag("ResetSuggestions"), + modifier = Modifier.testTag("ResetSuggestions") ) } @@ -117,7 +117,7 @@ private fun WidgetsSettingsContent( showResetWidgetsDialog = false }, onDismiss = { showResetWidgetsDialog = false }, - modifier = Modifier.testTag("reset_widgets_dialog"), + modifier = Modifier.testTag("reset_widgets_dialog") ) } @@ -131,7 +131,7 @@ private fun WidgetsSettingsContent( showResetSuggestionsDialog = false }, onDismiss = { showResetSuggestionsDialog = false }, - modifier = Modifier.testTag("reset_suggestions_dialog"), + modifier = Modifier.testTag("reset_suggestions_dialog") ) } } diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/PinChooseScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/PinChooseScreen.kt index a0b5f42f7..32c8d6ec2 100644 --- a/app/src/main/java/to/bitkit/ui/settings/pin/PinChooseScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/pin/PinChooseScreen.kt @@ -48,7 +48,7 @@ fun PinChooseScreen( BodyM( text = stringResource(R.string.security__pin_choose_text), color = Colors.White64, - modifier = Modifier.padding(horizontal = 32.dp), + modifier = Modifier.padding(horizontal = 32.dp) ) Spacer(modifier = Modifier.height(32.dp)) diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/PinConfirmScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/PinConfirmScreen.kt index 2e32ffea6..04bf921e4 100644 --- a/app/src/main/java/to/bitkit/ui/settings/pin/PinConfirmScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/pin/PinConfirmScreen.kt @@ -79,7 +79,7 @@ private fun ConfirmPinContent( showError: Boolean, onKeyPress: (String) -> Unit, onBack: () -> Unit, - modifier: Modifier = Modifier, + modifier: Modifier = Modifier ) { Column( modifier = modifier @@ -97,7 +97,7 @@ private fun ConfirmPinContent( BodyM( text = stringResource(R.string.security__pin_retype_text), color = Colors.White64, - modifier = Modifier.padding(horizontal = 32.dp), + modifier = Modifier.padding(horizontal = 32.dp) ) Spacer(modifier = Modifier.height(32.dp)) diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt index b0aca2e7b..a446f0ae1 100644 --- a/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt @@ -71,7 +71,7 @@ private fun Content( actions = { DrawerNavIcon() }, ) Column( - modifier = Modifier.padding(horizontal = 16.dp), + modifier = Modifier.padding(horizontal = 16.dp) ) { BodyM( text = stringResource( @@ -97,35 +97,35 @@ private fun Content( painterResource(R.drawable.shield) }, contentDescription = null, - modifier = Modifier.size(256.dp), + modifier = Modifier.size(256.dp) ) } if (isPinEnabled) { Row( horizontalArrangement = Arrangement.spacedBy(16.dp), - modifier = Modifier.fillMaxWidth(), + modifier = Modifier.fillMaxWidth() ) { SecondaryButton( text = stringResource(R.string.settings__security__pin_change), onClick = onChangePinClick, modifier = Modifier .weight(1f) - .testTag("ChangePIN"), + .testTag("ChangePIN") ) PrimaryButton( text = stringResource(R.string.security__pin_disable_button), onClick = onDisablePinClick, modifier = Modifier .weight(1f) - .testTag("DisablePin"), + .testTag("DisablePin") ) } } else { PrimaryButton( text = stringResource(R.string.security__pin_enable_button), onClick = onEnablePinClick, - modifier = Modifier.testTag("EnablePin"), + modifier = Modifier.testTag("EnablePin") ) } diff --git a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt index 3c7cd6dc7..09869f86b 100644 --- a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt @@ -171,12 +171,12 @@ private fun Content( title = stringResource(R.string.settings__support__status), icon = { SettingsIcon(R.drawable.ic_power) }, onClick = onClickAppStatus, - modifier = Modifier.testTag("AppStatus"), + modifier = Modifier.testTag("AppStatus") ) SettingsButtonRow( title = stringResource(R.string.settings__about__legal), icon = { SettingsIcon(R.drawable.ic_file_text) }, - onClick = onClickLegal, + onClick = onClickLegal ) SettingsButtonRow( title = stringResource(R.string.settings__about__share), @@ -191,13 +191,13 @@ private fun Content( .fillMaxWidth() .heightIn(min = 52.dp) .clickableAlpha { onClickVersion() } - .testTag("DevOptions"), + .testTag("DevOptions") ) { SettingsIcon(R.drawable.ic_stack) HorizontalSpacer(8.dp) BodyM( text = stringResource(R.string.settings__about__version), - modifier = Modifier.weight(1f), + modifier = Modifier.weight(1f) ) BodyM(text = appVersion, color = Colors.White64) } @@ -238,7 +238,7 @@ private fun SupportFooter() { .fillMaxWidth() .height(100.dp) .padding(horizontal = 16.dp) - .testTag("AboutLogo"), + .testTag("AboutLogo") ) } @@ -247,7 +247,7 @@ private fun SupportFooter() { modifier = Modifier .fillMaxWidth() .background(Colors.Brand) - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) ) { VerticalSpacer(16.dp) @@ -273,17 +273,17 @@ private fun BrandEndorsement() { Row( horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterHorizontally), verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth(), + modifier = Modifier.fillMaxWidth() ) { Image( painter = painterResource(R.drawable.ic_synonym_logo), contentDescription = null, - modifier = Modifier.height(24.dp), + modifier = Modifier.height(24.dp) ) Image( painter = painterResource(R.drawable.ic_tether_company), contentDescription = null, - modifier = Modifier.height(16.dp), + modifier = Modifier.height(16.dp) ) } } diff --git a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt index fffd33bf6..3cd4b1be1 100644 --- a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt +++ b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt @@ -168,7 +168,7 @@ private fun ValidateContent( .fillMaxWidth() .gradientBackground() .navigationBarsPadding() - .testTag("ChangePIN"), + .testTag("ChangePIN") ) { SheetTopBar( titleText = stringResource(R.string.security__cp_title), @@ -180,7 +180,7 @@ private fun ValidateContent( BodyM( text = stringResource(R.string.security__cp_text).withAccentBoldBright(), color = Colors.White64, - modifier = Modifier.padding(horizontal = 32.dp), + modifier = Modifier.padding(horizontal = 32.dp) ) VerticalSpacer(32.dp) @@ -193,7 +193,7 @@ private fun ValidateContent( textAlign = TextAlign.Center, modifier = Modifier .fillMaxWidth() - .testTag("LastAttempt"), + .testTag("LastAttempt") ) } else { BodyS( @@ -204,7 +204,7 @@ private fun ValidateContent( modifier = Modifier .fillMaxWidth() .clickableAlpha { onClickForgotPin() } - .testTag("AttemptsRemaining"), + .testTag("AttemptsRemaining") ) } VerticalSpacer(16.dp) @@ -220,7 +220,7 @@ private fun ValidateContent( onPress = onKeyPress, type = NumberPadType.SIMPLE, modifier = Modifier - .height(NumberPadHeight), + .height(NumberPadHeight) ) } } @@ -236,7 +236,7 @@ private fun NewPinContent( .fillMaxWidth() .gradientBackground() .navigationBarsPadding() - .testTag("ChangePIN2"), + .testTag("ChangePIN2") ) { SheetTopBar( titleText = stringResource(R.string.security__cp_setnew_title), @@ -248,7 +248,7 @@ private fun NewPinContent( BodyM( text = stringResource(R.string.security__cp_setnew_text), color = Colors.White64, - modifier = Modifier.padding(horizontal = 32.dp), + modifier = Modifier.padding(horizontal = 32.dp) ) VerticalSpacer(32.dp) @@ -279,7 +279,7 @@ private fun ConfirmContent( .fillMaxWidth() .gradientBackground() .navigationBarsPadding() - .testTag("ChangePIN2"), + .testTag("ChangePIN2") ) { SheetTopBar( titleText = stringResource(R.string.security__cp_retype_title), @@ -291,7 +291,7 @@ private fun ConfirmContent( BodyM( text = stringResource(R.string.security__cp_retype_text), color = Colors.White64, - modifier = Modifier.padding(horizontal = 32.dp), + modifier = Modifier.padding(horizontal = 32.dp) ) VerticalSpacer(32.dp) @@ -303,7 +303,7 @@ private fun ConfirmContent( color = Colors.Brand, modifier = Modifier .fillMaxWidth() - .testTag("WrongPIN"), + .testTag("WrongPIN") ) } @@ -317,7 +317,7 @@ private fun ConfirmContent( onPress = onKeyPress, type = NumberPadType.SIMPLE, modifier = Modifier - .height(NumberPadHeight), + .height(NumberPadHeight) ) } } @@ -330,7 +330,7 @@ private fun ResultContent( modifier = Modifier .fillMaxWidth() .gradientBackground() - .navigationBarsPadding(), + .navigationBarsPadding() ) { SheetTopBar( titleText = stringResource(R.string.security__cp_changed_title) @@ -341,19 +341,19 @@ private fun ResultContent( BodyM( text = stringResource(R.string.security__cp_changed_text), color = Colors.White64, - modifier = Modifier.padding(horizontal = 32.dp), + modifier = Modifier.padding(horizontal = 32.dp) ) Box( contentAlignment = Alignment.Center, modifier = Modifier .fillMaxWidth() - .weight(1f), + .weight(1f) ) { Image( painter = painterResource(R.drawable.check), contentDescription = null, - modifier = Modifier.size(256.dp), + modifier = Modifier.size(256.dp) ) } @@ -362,7 +362,7 @@ private fun ResultContent( onClick = onOkClick, modifier = Modifier .padding(horizontal = 32.dp) - .testTag("OK"), + .testTag("OK") ) VerticalSpacer(16.dp) diff --git a/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt index f3b82bffb..3e79ef981 100644 --- a/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt +++ b/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt @@ -82,7 +82,7 @@ private fun Content( .sheetHeight(SheetSize.MEDIUM) .gradientBackground() .navigationBarsPadding() - .testTag("DisablePIN"), + .testTag("DisablePIN") ) { SheetTopBar( titleText = stringResource(R.string.security__pin_disable_button), @@ -94,7 +94,7 @@ private fun Content( BodyM( text = stringResource(R.string.security__pin_disable_text).withAccentBoldBright(), color = Colors.White64, - modifier = Modifier.padding(horizontal = 32.dp), + modifier = Modifier.padding(horizontal = 32.dp) ) VerticalSpacer(32.dp) @@ -107,7 +107,7 @@ private fun Content( textAlign = TextAlign.Center, modifier = Modifier .fillMaxWidth() - .testTag("LastAttempt"), + .testTag("LastAttempt") ) } else { BodyS( @@ -118,7 +118,7 @@ private fun Content( modifier = Modifier .fillMaxWidth() .clickableAlpha { onClickForgotPin() } - .testTag("AttemptsRemaining"), + .testTag("AttemptsRemaining") ) } VerticalSpacer(16.dp) From 0cdef65d72b58f3f7242ab6ceb5cf9c78cb41092 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 26 Mar 2026 07:49:28 -0300 Subject: [PATCH 55/85] chore: update agent rule about trailing comma --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 46af00cd0..925963fe8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -192,7 +192,7 @@ suspend fun getData(): Result = withContext(Dispatchers.IO) { - NEVER hardcode strings and always preserve string resources - ALWAYS localize in ViewModels using injected `@ApplicationContext`, e.g. `context.getString()` - ALWAYS use `remember` for expensive Compose computations -- ALWAYS add modifiers to the last place in the argument list when calling composable functions +- ALWAYS add modifiers to the last place in the argument list when calling composable functions and NEVER add a trailing comma after the last `modifier` argument - NEVER add parameters with default values BEFORE the `modifier` parameter in composable functions - modifier must be the FIRST optional parameter - ALWAYS use `navController.navigateTo(route)` for simple navigation; NEVER use raw `navController.navigate(route)` — `navigateTo` prevents duplicate destinations - ALWAYS prefer `VerticalSpacer`, `HorizontalSpacer`, `FillHeight` and `FillWidth` over `Spacer` when applicable From 2fa1586551ddd29e9b9de9257f7d76f3ebe2802c Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 26 Mar 2026 08:15:07 -0300 Subject: [PATCH 56/85] chore: use `showSystemUi= true` instead and sheet preview setup --- .../to/bitkit/ui/settings/SettingsScreen.kt | 6 +- .../bitkit/ui/settings/pin/PinChooseScreen.kt | 2 +- .../ui/settings/pin/PinConfirmScreen.kt | 4 +- .../to/bitkit/ui/sheets/ChangePinSheet.kt | 59 +++++++++++-------- .../to/bitkit/ui/sheets/DisablePinSheet.kt | 19 +++--- 5 files changed, 51 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index a9fece8b2..142190047 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -572,7 +572,7 @@ private fun AdvancedTabContent( } } -@Preview(showBackground = true) +@Preview(showSystemUi = true) @Composable private fun PreviewGeneral() { AppThemeSurface { @@ -587,7 +587,7 @@ private fun PreviewGeneral() { } } -@Preview(showBackground = true) +@Preview(showSystemUi = true) @Composable private fun PreviewSecurity() { AppThemeSurface { @@ -603,7 +603,7 @@ private fun PreviewSecurity() { } } -@Preview(showBackground = true) +@Preview(showSystemUi = true) @Composable private fun PreviewAdvanced() { AppThemeSurface { diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/PinChooseScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/PinChooseScreen.kt index 32c8d6ec2..9c6592f4d 100644 --- a/app/src/main/java/to/bitkit/ui/settings/pin/PinChooseScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/pin/PinChooseScreen.kt @@ -78,7 +78,7 @@ fun PinChooseScreen( } } -@Preview(showBackground = true) +@Preview(showSystemUi = true) @Composable private fun Preview() { AppThemeSurface { diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/PinConfirmScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/PinConfirmScreen.kt index 04bf921e4..32252b1bb 100644 --- a/app/src/main/java/to/bitkit/ui/settings/pin/PinConfirmScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/pin/PinConfirmScreen.kt @@ -130,7 +130,7 @@ private fun ConfirmPinContent( } } -@Preview(showBackground = true) +@Preview(showSystemUi = true) @Composable private fun Preview() { AppThemeSurface { @@ -143,7 +143,7 @@ private fun Preview() { } } -@Preview(showBackground = true) +@Preview(showSystemUi = true) @Composable private fun PreviewRetry() { AppThemeSurface { diff --git a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt index 3cd4b1be1..abdc067fb 100644 --- a/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt +++ b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt @@ -33,6 +33,7 @@ import to.bitkit.R import to.bitkit.env.Env import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.BodyS +import to.bitkit.ui.components.BottomSheetPreview import to.bitkit.ui.components.FillHeight import to.bitkit.ui.components.NumberPad import to.bitkit.ui.components.NumberPadType @@ -369,51 +370,59 @@ private fun ResultContent( } } -@Preview(showBackground = true) +@Preview(showSystemUi = true) @Composable private fun PreviewValidate() { AppThemeSurface { - ValidateContent( - pin = "12", - attemptsRemaining = 8, - onKeyPress = {}, - onBackClick = {}, - onClickForgotPin = {}, - ) + BottomSheetPreview { + ValidateContent( + pin = "12", + attemptsRemaining = 8, + onKeyPress = {}, + onBackClick = {}, + onClickForgotPin = {}, + ) + } } } -@Preview(showBackground = true) +@Preview(showSystemUi = true) @Composable private fun PreviewNew() { AppThemeSurface { - NewPinContent( - pin = "12", - onKeyPress = {}, - onBackClick = {}, - ) + BottomSheetPreview { + NewPinContent( + pin = "12", + onKeyPress = {}, + onBackClick = {}, + ) + } } } -@Preview(showBackground = true) +@Preview(showSystemUi = true) @Composable private fun PreviewConfirm() { AppThemeSurface { - ConfirmContent( - pin = "12", - showError = false, - onKeyPress = {}, - onBackClick = {}, - ) + BottomSheetPreview { + ConfirmContent( + pin = "12", + showError = false, + onKeyPress = {}, + onBackClick = {}, + ) + } } } -@Preview(showBackground = true) +@Preview(showSystemUi = true) @Composable private fun PreviewResult() { AppThemeSurface { - ResultContent( - onOkClick = {}, - ) + BottomSheetPreview { + ResultContent( + onOkClick = {}, + ) + } } } diff --git a/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt index 3e79ef981..62b938f7c 100644 --- a/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt +++ b/app/src/main/java/to/bitkit/ui/sheets/DisablePinSheet.kt @@ -23,6 +23,7 @@ import to.bitkit.R import to.bitkit.env.Env import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.BodyS +import to.bitkit.ui.components.BottomSheetPreview import to.bitkit.ui.components.FillHeight import to.bitkit.ui.components.NumberPad import to.bitkit.ui.components.NumberPadType @@ -139,16 +140,18 @@ private fun Content( } } -@Preview(showBackground = true) +@Preview(showSystemUi = true) @Composable private fun Preview() { AppThemeSurface { - Content( - pin = "12", - attemptsRemaining = 8, - onKeyPress = {}, - onBackClick = {}, - onClickForgotPin = {}, - ) + BottomSheetPreview { + Content( + pin = "12", + attemptsRemaining = 8, + onKeyPress = {}, + onBackClick = {}, + onClickForgotPin = {}, + ) + } } } From 27801408bdc899efaeeac8a2028fe8d242f4561d Mon Sep 17 00:00:00 2001 From: Piotr Stachyra Date: Thu, 26 Mar 2026 12:30:13 +0100 Subject: [PATCH 57/85] add widget settings test tags --- .../java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt index 59e340324..72b4b405a 100644 --- a/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt @@ -80,11 +80,13 @@ private fun WidgetsSettingsContent( title = stringResource(R.string.settings__widgets__showWidgets), isChecked = showWidgets, onClick = onShowWidgetsClick, + modifier = Modifier.testTag("ShowWidgets"), ) SettingsSwitchRow( title = stringResource(R.string.settings__widgets__showWidgetTitles), isChecked = showWidgetTitles, onClick = onShowWidgetTitlesClick, + modifier = Modifier.testTag("ShowWidgetTitles"), ) // Reset section From e8cbcc4fa8281f47196e23c6263f10e7ab51863d Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 26 Mar 2026 08:35:00 -0300 Subject: [PATCH 58/85] chore: extract system settings string --- .../main/java/to/bitkit/models/Language.kt | 35 ++++++++++--------- .../ui/settings/LanguageSettingsScreen.kt | 9 +++-- .../to/bitkit/ui/settings/SettingsScreen.kt | 4 ++- .../to/bitkit/ui/utils/AppLocaleManager.kt | 2 +- app/src/main/res/values-ar/strings.xml | 1 + app/src/main/res/values-b+es+419/strings.xml | 1 + app/src/main/res/values-ca/strings.xml | 1 + app/src/main/res/values-cs/strings.xml | 1 + app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-el/strings.xml | 1 + app/src/main/res/values-es-rES/strings.xml | 1 + app/src/main/res/values-es/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values-it/strings.xml | 1 + app/src/main/res/values-nl/strings.xml | 1 + app/src/main/res/values-pl/strings.xml | 1 + app/src/main/res/values-pt-rBR/strings.xml | 1 + app/src/main/res/values-pt/strings.xml | 1 + app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 20 files changed, 46 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/to/bitkit/models/Language.kt b/app/src/main/java/to/bitkit/models/Language.kt index 1ae73724c..9749d4629 100644 --- a/app/src/main/java/to/bitkit/models/Language.kt +++ b/app/src/main/java/to/bitkit/models/Language.kt @@ -1,35 +1,38 @@ package to.bitkit.models +import androidx.annotation.StringRes import kotlinx.serialization.Serializable +import to.bitkit.R import java.util.Locale @Serializable enum class Language( - val displayName: String, + @StringRes val displayNameResId: Int? = null, + val nativeName: String? = null, val languageCode: String, val countryCode: String? = null, val isSystemDefault: Boolean = false, ) { SYSTEM_DEFAULT( - displayName = "System Settings", + displayNameResId = R.string.settings__language_system_default, languageCode = "system", countryCode = null, isSystemDefault = true ), - ARABIC("العربية", "ar"), - CATALAN("Català", "ca"), - CZECH("Čeština", "cs"), - DUTCH("Nederlands", "nl"), - ENGLISH("English", "en", "US"), - FRENCH("Français", "fr", "FR"), - GERMAN("Deutsch", "de"), - GREEK("Ελληνικά", "el"), - ITALIAN("Italiano", "it"), - POLISH("Polski", "pl"), - PORTUGUESE("Português", "pt", "BR"), - RUSSIAN("Русский", "ru"), - SPANISH("Español", "es", "ES"), - SPANISH_LATIN_AMERICA("Español (Latinoamérica)", "es", "419"); + ARABIC(nativeName = "العربية", languageCode = "ar"), + CATALAN(nativeName = "Català", languageCode = "ca"), + CZECH(nativeName = "Čeština", languageCode = "cs"), + DUTCH(nativeName = "Nederlands", languageCode = "nl"), + ENGLISH(nativeName = "English", languageCode = "en", countryCode = "US"), + FRENCH(nativeName = "Français", languageCode = "fr", countryCode = "FR"), + GERMAN(nativeName = "Deutsch", languageCode = "de"), + GREEK(nativeName = "Ελληνικά", languageCode = "el"), + ITALIAN(nativeName = "Italiano", languageCode = "it"), + POLISH(nativeName = "Polski", languageCode = "pl"), + PORTUGUESE(nativeName = "Português", languageCode = "pt", countryCode = "BR"), + RUSSIAN(nativeName = "Русский", languageCode = "ru"), + SPANISH(nativeName = "Español", languageCode = "es", countryCode = "ES"), + SPANISH_LATIN_AMERICA(nativeName = "Español (Latinoamérica)", languageCode = "es", countryCode = "419"); companion object { fun fromLanguageCode(languageCode: String, countryCode: String? = null): Language? { diff --git a/app/src/main/java/to/bitkit/ui/settings/LanguageSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/LanguageSettingsScreen.kt index c4fb3b320..776f4524b 100644 --- a/app/src/main/java/to/bitkit/ui/settings/LanguageSettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/LanguageSettingsScreen.kt @@ -68,9 +68,14 @@ private fun Content( Text13Up("Interface Language", color = Colors.White64, modifier = Modifier.padding(vertical = 16.dp)) LazyColumn { - items(uiState.languages, { item -> item.displayName }) { item -> + items(uiState.languages, { it.languageCode + it.countryCode }) { item -> + val title = if (item.displayNameResId != null) { + stringResource(item.displayNameResId) + } else { + item.nativeName.orEmpty() + } SettingsButtonRow( - title = item.displayName, + title = title, value = SettingsButtonValue.BooleanValue(item == uiState.selectedLanguage), onClick = { onClickLanguage(item) } ) diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index 142190047..c40998510 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -116,7 +116,9 @@ fun SettingsScreen( currencySymbol = currencies.currencySymbol, primaryDisplay = currencies.primaryDisplay, defaultTransactionSpeed = defaultTransactionSpeed, - selectedLanguage = languageUiState.selectedLanguage.displayName, + selectedLanguage = languageUiState.selectedLanguage.let { + if (it.displayNameResId != null) stringResource(it.displayNameResId) else it.nativeName.orEmpty() + }, showWidgets = showWidgets, tagCount = lastUsedTags.size, isQuickPayEnabled = isQuickPayEnabled, diff --git a/app/src/main/java/to/bitkit/ui/utils/AppLocaleManager.kt b/app/src/main/java/to/bitkit/ui/utils/AppLocaleManager.kt index 95a2b326c..e48ffd0db 100644 --- a/app/src/main/java/to/bitkit/ui/utils/AppLocaleManager.kt +++ b/app/src/main/java/to/bitkit/ui/utils/AppLocaleManager.kt @@ -52,7 +52,7 @@ class AppLocaleManager @Inject constructor( fun getSupportedLanguages(): List { return Language.entries.sortedWith( - compareBy { !it.isSystemDefault }.thenBy { it.displayName } + compareBy { !it.isSystemDefault }.thenBy { it.nativeName } ) } } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 510350936..3f0857036 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -649,6 +649,7 @@ نصيحة: بدّل بسرعة بين Bitcoin و {currency} بالنقر على رصيد محفظتك. الوحدة الافتراضية عام + إعدادات النظام اللغة يجعل Bitkit QuickPay الدفع أسرع بالدفع التلقائي لرموز QR عند مسحها. مدفوعات\n<accent>سلسة</accent> diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index 74e8531df..177c8c91c 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -649,6 +649,7 @@ Truco: Cambia rápidamente entre Bitcoin y {currency} tocando sobre el balance del monedero. Unidad por defecto Geral + Ajustes del sistema Idioma Bitkit QuickPay agiliza el proceso de pago mediante el pago automático de los códigos QR al ser escaneados. <accent>Pagos</accent>\nsin fricción diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 24b138c57..5682eb832 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -649,6 +649,7 @@ Consell: Canvia ràpidament entre Bitcoin i {currency} tocant el saldo de la teva cartera. Unitat per defecte General + Configuració del sistema Idioma Bitkit QuickPay fa que pagar sigui més ràpid pagant automàticament els codis QR quan s\'escanegen. <accent>Pagaments</accent>\nsense friccions diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 436f4b51a..3865fe5ef 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -649,6 +649,7 @@ Tip: Klepnutím na zůstatek v peněžence můžete rychle přepínat mezi bitcoiny a {currency}. Výchozí jednotka Obecné + Systémové nastavení Jazyk Služba Bitkit QuickPay urychluje odbavení tím, že po naskenování QR kódu automaticky zaplatí. <accent>Okamžité</accent>\nplatby diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 9bed949d5..18f8a91e2 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -523,6 +523,7 @@ Fortgeschritten Über Support + Systemeinstellungen Sprache Über Bitkit Danke, dass Sie ein verantwortungsvoller Bitcoiner sind.\nÄndern Sie Ihr Portemonnaie, ändern Sie die Welt.\n\nBitkit gibt Ihnen die Schlüssel zu Ihrem Geld, Profil, Kontakten und Webkonten.\n\nBitkit wurde von Synonym Software Ltd. entwickelt. diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 8e7052836..e306871cb 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -649,6 +649,7 @@ Συμβουλή: Εναλλαγή γρήγορα μεταξύ Bitcoin και {currency} πατώντας στο υπόλοιπο του πορτοφολιού σου. Προεπιλεγμένη μονάδα Γενικά + Ρυθμίσεις συστήματος Γλώσσα Το Bitkit QuickPay κάνει την ολοκλήρωση αγοράς πιο γρήγορη πληρώνοντας αυτόματα κωδικούς QR κατά τη σάρωση. <accent>Απρόσκοπτες</accent>\nπληρωμές diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 5fd669656..e21d72238 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -649,6 +649,7 @@ Truco: Cambia rápidamente entre Bitcoin y {currency} tocando sobre el balance del monedero. Unidad Predeterminada General + Ajustes del sistema Idioma Bitkit QuickPay hace que pagar sea más rápido pagando automáticamente los códigos QR cuando se escanean. Pagos\n<accent>sin fricción</accent> diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d2c3e8bea..f449d10b2 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -522,6 +522,7 @@ Avanzado Acerca de Copia de seguridad o Restaurar + Ajustes del sistema Idioma Soporte Legal diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 26d37ce68..9b2176875 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -518,6 +518,7 @@ Options Dev désactivées Les options pour les développeurs sont désormais désactivées dans l\'ensemble de l\'application. Général + Paramètres système Langue Sécurité et vie privée Sauvegarde ou restauration diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 35c7ac2b7..6391c2330 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -649,6 +649,7 @@ Suggerimento: alterna rapidamente tra Bitcoin e {currency} toccando il saldo del tuo portafoglio. Unità predefinita Generale + Impostazioni di sistema Lingua Bitkit QuickPay rende il checkout piu\' veloce pagando automaticamente i codici QR quando scansionati. <accent>Pagamenti</accent>\nsenza attriti diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index b609805bb..52a6fe126 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -649,6 +649,7 @@ Tip: Wissel snel tussen Bitcoin en {currency} door op je wallet-saldo te tikken. Standaardeenheid Algemeen + Systeeminstellingen Taal Bitkit QuickPay maakt afrekenen sneller door QR-codes automatisch te betalen wanneer ze worden gescand. <accent>Wrijvingsloze</accent>\nbetalingen diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index ec29c159e..387e0afee 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -607,6 +607,7 @@ Branch and Bound Losowy wybór dla prywatności Single Random Draw + Ustawienia systemowe Język Nie udało się otworzyć linków pomocy Wsparcie diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 92bcc5db5..5202a1421 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -649,6 +649,7 @@ Dica: Alterne rapidamente entre Bitcoin e {currency} tocando no saldo de sua carteira. Unidade padrão Geral + Configurações do sistema Idioma O Bitkit QuickPay agiliza o check-out pagando automaticamente os códigos QR quando escaneados. Pagamentos\n<accent>Sem atrito</accent> diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 572075998..6fac067a4 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -715,6 +715,7 @@ Branch and Bound Seleção aleatória para privacidade Single Random Draw + Configurações do sistema Idioma Tenha seu próprio\n<accent>perfil</accent> Configure seu perfil público e seus links, para que seus contatos possam pagá-lo a qualquer hora e em qualquer lugar. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c9222812c..2b5db1965 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -664,6 +664,7 @@ Совет: Быстро переключайтесь между биткойнами и {currency}, нажимая на баланс вашего кошелька. Единица Измерения по Умолчанию Основные + Системные настройки Язык Bitkit QuickPay ускоряет оплату, автоматически оплачивая QR-коды при сканировании. <accent>Платежи</accent> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index df7cd85b1..52192fcce 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -675,6 +675,7 @@ Tip: Quickly toggle between Bitcoin and {currency} by tapping on your wallet balance. Default Unit General + System Settings Language Bitkit QuickPay makes checking out faster by automatically paying QR codes when scanned. <accent>Frictionless</accent>\npayments From 582e306ee40ce121660565892e72129f0b400f92 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 26 Mar 2026 08:56:49 -0300 Subject: [PATCH 59/85] fix: english capitalization --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 52192fcce..a4bfb44bb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -652,7 +652,7 @@ Slow (cheaper) Slow Prices powered by Bitfinex & CoinGecko. - Local currency + Local Currency Local Currency Most Used Other Currencies From 3cd66615312635f898e13a38ffefda5ff46e336a Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 26 Mar 2026 09:16:53 -0300 Subject: [PATCH 60/85] fix: tab style --- .../components/CustomTabRowWithSpacing.kt | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/CustomTabRowWithSpacing.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/CustomTabRowWithSpacing.kt index 1c20627a0..4ca6985ef 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/CustomTabRowWithSpacing.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/CustomTabRowWithSpacing.kt @@ -2,7 +2,6 @@ package to.bitkit.ui.screens.wallets.activity.components import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.FastOutSlowInEasing -import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -62,19 +61,10 @@ fun CustomTabRowWithSpacing( ) } - val animatedAlpha by animateFloatAsState( - targetValue = if (isSelected) 1f else 0.2f, - animationSpec = tween( - durationMillis = 250, - easing = FastOutSlowInEasing - ), - label = "indicatorAlpha", - ) - val animatedColor by animateColorAsState( - targetValue = if (isSelected) selectedColor else Colors.White, + targetValue = if (isSelected) selectedColor else Colors.White50, animationSpec = tween( - durationMillis = 250, + durationMillis = 200, easing = FastOutSlowInEasing ), label = "indicatorColor", @@ -83,9 +73,8 @@ fun CustomTabRowWithSpacing( Box( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 4.dp) - .height(3.dp) - .background(animatedColor.copy(alpha = animatedAlpha)) + .height(2.dp) + .background(animatedColor) ) } From 46103e1400fa5563c9b191d75a22feb3bb59e25d Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 26 Mar 2026 09:39:53 -0300 Subject: [PATCH 61/85] fix: reimport icons --- .../drawable/ic_arrow_counter_clockwise.xml | 18 ++- app/src/main/res/drawable/ic_bell.xml | 26 ++-- .../main/res/drawable/ic_bitcoin_modern.xml | 10 +- app/src/main/res/drawable/ic_broadcast.xml | 35 +++--- .../res/drawable/ic_caret_double_right.xml | 21 ++-- .../main/res/drawable/ic_clipboard_text.xml | 35 +++--- app/src/main/res/drawable/ic_coins.xml | 111 ++++++++---------- app/src/main/res/drawable/ic_database.xml | 28 ++--- app/src/main/res/drawable/ic_eye.xml | 26 ++-- app/src/main/res/drawable/ic_eye_slash.xml | 42 ++++--- app/src/main/res/drawable/ic_git_branch.xml | 58 +++------ .../main/res/drawable/ic_hand_pointing.xml | 21 ++-- app/src/main/res/drawable/ic_hard_drives.xml | 36 +++--- app/src/main/res/drawable/ic_lightning.xml | 14 ++- app/src/main/res/drawable/ic_list_dashes.xml | 46 ++++---- app/src/main/res/drawable/ic_lock_key.xml | 35 +++--- app/src/main/res/drawable/ic_power.xml | 15 +-- app/src/main/res/drawable/ic_settings_dev.xml | 4 +- app/src/main/res/drawable/ic_share.xml | 23 ++-- app/src/main/res/drawable/ic_shield.xml | 21 ++-- app/src/main/res/drawable/ic_smiley.xml | 21 +--- app/src/main/res/drawable/ic_speed_normal.xml | 4 +- app/src/main/res/drawable/ic_stack.xml | 28 ++--- app/src/main/res/drawable/ic_stop_circle.xml | 15 +-- app/src/main/res/drawable/ic_tag.xml | 25 ++-- app/src/main/res/drawable/ic_translate.xml | 46 ++++---- app/src/main/res/drawable/ic_warning.xml | 26 ++-- 27 files changed, 346 insertions(+), 444 deletions(-) diff --git a/app/src/main/res/drawable/ic_arrow_counter_clockwise.xml b/app/src/main/res/drawable/ic_arrow_counter_clockwise.xml index 3e6f603a8..8219ece29 100644 --- a/app/src/main/res/drawable/ic_arrow_counter_clockwise.xml +++ b/app/src/main/res/drawable/ic_arrow_counter_clockwise.xml @@ -1,14 +1,12 @@ - - + android:viewportWidth="16" + android:viewportHeight="16"> + + diff --git a/app/src/main/res/drawable/ic_bell.xml b/app/src/main/res/drawable/ic_bell.xml index a7b0fb77b..635b69c4c 100644 --- a/app/src/main/res/drawable/ic_bell.xml +++ b/app/src/main/res/drawable/ic_bell.xml @@ -1,24 +1,18 @@ - - + android:pathData="M12.4601 8.21936C12.4901 10.3144 12.9401 11.5494 13.3351 12.2394C13.3801 12.3144 13.4051 12.3994 13.4101 12.4894C13.4101 12.5794 13.3901 12.6644 13.3451 12.7444C13.3001 12.8194 13.2401 12.8844 13.1601 12.9294C13.0851 12.9744 12.9951 12.9994 12.9101 12.9994H3.00508C2.62508 12.9994 2.37508 12.5894 2.56508 12.2644C2.98008 11.5594 3.46008 10.2544 3.46008 7.99936C3.46008 7.36436 3.59508 6.73436 3.85508 6.15436C4.11508 5.57436 4.49508 5.05936 4.97008 4.63436C5.44508 4.21436 6.00508 3.89436 6.61008 3.70436C7.21508 3.51436 7.85508 3.45436 8.48508 3.52436C10.8001 3.78436 12.4301 5.88436 12.4651 8.21436L12.4601 8.21936Z" + android:fillColor="#ffffff"/> + android:pathData="M9.45996 2H6.45996C6.18496 2 5.95996 1.775 5.95996 1.5C5.95996 1.225 6.18496 1 6.45996 1H9.45996C9.73496 1 9.95996 1.225 9.95996 1.5C9.95996 1.775 9.73496 2 9.45996 2Z" + android:fillColor="#ffffff"/> + android:pathData="M3.00479 13.5002C2.64479 13.5002 2.30979 13.3052 2.12979 12.9902C1.95479 12.6802 1.95479 12.3102 2.12979 12.0052C2.50479 11.3652 2.95479 10.1552 2.95479 7.99519C2.95479 7.28519 3.09979 6.59519 3.39479 5.95019C3.68479 5.30019 4.09979 4.73519 4.63479 4.26019C5.16479 3.79019 5.77978 3.44019 6.45478 3.22519C7.12978 3.01019 7.83478 2.94519 8.53978 3.02519C11.0198 3.30519 12.9248 5.53019 12.9598 8.20519C12.9898 10.2202 13.4148 11.3702 13.7698 11.9802C13.8548 12.1252 13.9048 12.2952 13.9098 12.4752C13.9098 12.6502 13.8698 12.8252 13.7848 12.9802C13.6948 13.1352 13.5698 13.2652 13.4198 13.3552C13.2648 13.4452 13.0948 13.4952 12.9148 13.4952H3.00479V13.5002ZM7.95478 4.00019C7.54978 4.00019 7.14479 4.06019 6.75479 4.18519C6.21479 4.35519 5.72479 4.63519 5.29979 5.01019C4.87479 5.39019 4.53979 5.84519 4.30979 6.36019C4.07479 6.88019 3.95979 7.43019 3.95979 8.00019C3.95979 10.3852 3.43479 11.7702 2.99979 12.5152L12.9148 12.5002C12.4948 11.7752 11.9948 10.4552 11.9648 8.22519C11.9348 6.05519 10.4148 4.25019 8.42978 4.02519C8.27478 4.00519 8.11478 4.00019 7.95978 4.00019H7.95478Z" + android:fillColor="#ffffff"/> - + android:pathData="M7.95996 15.5C7.29496 15.5 6.66496 15.24 6.18996 14.77C5.71996 14.3 5.45996 13.67 5.45996 13C5.45996 12.725 5.68496 12.5 5.95996 12.5C6.23496 12.5 6.45996 12.725 6.45996 13C6.45996 13.395 6.61996 13.78 6.89996 14.06C7.46496 14.62 8.45996 14.62 9.01996 14.06C9.29996 13.78 9.45996 13.395 9.45996 13C9.45996 12.725 9.68496 12.5 9.95996 12.5C10.235 12.5 10.46 12.725 10.46 13C10.46 13.665 10.2 14.295 9.72996 14.77C9.25996 15.245 8.62996 15.5 7.95996 15.5V15.5Z" + android:fillColor="#ffffff"/> diff --git a/app/src/main/res/drawable/ic_bitcoin_modern.xml b/app/src/main/res/drawable/ic_bitcoin_modern.xml index 67986df79..cca907ae4 100644 --- a/app/src/main/res/drawable/ic_bitcoin_modern.xml +++ b/app/src/main/res/drawable/ic_bitcoin_modern.xml @@ -1,9 +1,9 @@ - + diff --git a/app/src/main/res/drawable/ic_broadcast.xml b/app/src/main/res/drawable/ic_broadcast.xml index 7436da07e..b4d2ef9b7 100644 --- a/app/src/main/res/drawable/ic_broadcast.xml +++ b/app/src/main/res/drawable/ic_broadcast.xml @@ -1,19 +1,24 @@ - - - + + + + + + diff --git a/app/src/main/res/drawable/ic_caret_double_right.xml b/app/src/main/res/drawable/ic_caret_double_right.xml index b1021748f..21eb759c1 100644 --- a/app/src/main/res/drawable/ic_caret_double_right.xml +++ b/app/src/main/res/drawable/ic_caret_double_right.xml @@ -1,14 +1,15 @@ - - + android:viewportWidth="16" + android:viewportHeight="16"> + + + diff --git a/app/src/main/res/drawable/ic_clipboard_text.xml b/app/src/main/res/drawable/ic_clipboard_text.xml index 74aaf5fed..6c81572c3 100644 --- a/app/src/main/res/drawable/ic_clipboard_text.xml +++ b/app/src/main/res/drawable/ic_clipboard_text.xml @@ -1,22 +1,21 @@ - - - - + android:viewportWidth="16" + android:viewportHeight="16"> + + + + + diff --git a/app/src/main/res/drawable/ic_coins.xml b/app/src/main/res/drawable/ic_coins.xml index 881f73ce1..bcb77e70d 100644 --- a/app/src/main/res/drawable/ic_coins.xml +++ b/app/src/main/res/drawable/ic_coins.xml @@ -1,68 +1,51 @@ - - - - - - - - - - - - - - - + android:viewportWidth="16" + android:viewportHeight="16"> + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_database.xml b/app/src/main/res/drawable/ic_database.xml index 4ef8cc60d..6500680bf 100644 --- a/app/src/main/res/drawable/ic_database.xml +++ b/app/src/main/res/drawable/ic_database.xml @@ -1,18 +1,18 @@ - - - + android:viewportWidth="16" + android:viewportHeight="16"> + + + + diff --git a/app/src/main/res/drawable/ic_eye.xml b/app/src/main/res/drawable/ic_eye.xml index 8768f08f1..0f4383f5e 100644 --- a/app/src/main/res/drawable/ic_eye.xml +++ b/app/src/main/res/drawable/ic_eye.xml @@ -1,19 +1,15 @@ - - - + android:viewportWidth="16" + android:viewportHeight="16"> + + + diff --git a/app/src/main/res/drawable/ic_eye_slash.xml b/app/src/main/res/drawable/ic_eye_slash.xml index 36e0eeeda..77c1cb2e0 100644 --- a/app/src/main/res/drawable/ic_eye_slash.xml +++ b/app/src/main/res/drawable/ic_eye_slash.xml @@ -1,26 +1,24 @@ - - - - - + android:viewportWidth="16" + android:viewportHeight="16"> + + + + + + diff --git a/app/src/main/res/drawable/ic_git_branch.xml b/app/src/main/res/drawable/ic_git_branch.xml index 2ebce6167..4a4ce3e45 100644 --- a/app/src/main/res/drawable/ic_git_branch.xml +++ b/app/src/main/res/drawable/ic_git_branch.xml @@ -1,54 +1,24 @@ - - + android:pathData="M11.75 6C12.7165 6 13.5 5.2165 13.5 4.25C13.5 3.2835 12.7165 2.5 11.75 2.5C10.7835 2.5 10 3.2835 10 4.25C10 5.2165 10.7835 6 11.75 6Z" + android:fillColor="#ffffff"/> - + android:pathData="M11.749 5.5C12.0252 5.5 12.249 5.72386 12.249 6V6.50002C12.249 7.03044 12.0383 7.53913 11.6632 7.91419C11.2882 8.28925 10.7795 8.49995 10.2491 8.49995C10.2491 8.49995 10.2491 8.49995 10.2491 8.49995L5.749 8.50005C5.4838 8.50005 5.22944 8.6054 5.04191 8.79292C4.85439 8.98045 4.74903 9.23479 4.74902 9.49999C4.74902 9.49999 4.74902 9.49999 4.74902 9.49999V10C4.74902 10.2761 4.52517 10.5 4.24902 10.5C3.97288 10.5 3.74902 10.2761 3.74902 10V9.49999C3.74903 8.96957 3.95975 8.46087 4.33481 8.08581C4.70988 7.71075 5.21857 7.50005 5.74898 7.50005C5.74899 7.50005 5.74898 7.50005 5.74898 7.50005L10.249 7.49995C10.5143 7.49995 10.7686 7.3946 10.9561 7.20708C11.1437 7.01955 11.249 6.76521 11.249 6.50001V6C11.249 5.72386 11.4729 5.5 11.749 5.5Z" + android:fillColor="#ffffff"/> - + android:pathData="M4.24902 5.5C4.52517 5.5 4.74902 5.72386 4.74902 6V10C4.74902 10.2761 4.52517 10.5 4.24902 10.5C3.97288 10.5 3.74902 10.2761 3.74902 10V6C3.74902 5.72386 3.97288 5.5 4.24902 5.5Z" + android:fillColor="#ffffff"/> - + android:pathData="M4.25 10.5C3.55964 10.5 3 11.0596 3 11.75C3 12.4404 3.55964 13 4.25 13C4.94036 13 5.5 12.4404 5.5 11.75C5.5 11.0596 4.94036 10.5 4.25 10.5ZM2 11.75C2 10.5074 3.00736 9.5 4.25 9.5C5.49264 9.5 6.5 10.5074 6.5 11.75C6.5 12.9926 5.49264 14 4.25 14C3.00736 14 2 12.9926 2 11.75Z" + android:fillColor="#ffffff"/> - + android:pathData="M11.75 3C11.0596 3 10.5 3.55964 10.5 4.25C10.5 4.94036 11.0596 5.5 11.75 5.5C12.4404 5.5 13 4.94036 13 4.25C13 3.55964 12.4404 3 11.75 3ZM9.5 4.25C9.5 3.00736 10.5074 2 11.75 2C12.9926 2 14 3.00736 14 4.25C14 5.49264 12.9926 6.5 11.75 6.5C10.5074 6.5 9.5 5.49264 9.5 4.25Z" + android:fillColor="#ffffff"/> - + android:pathData="M4.25 3C3.55964 3 3 3.55964 3 4.25C3 4.94036 3.55964 5.5 4.25 5.5C4.94036 5.5 5.5 4.94036 5.5 4.25C5.5 3.55964 4.94036 3 4.25 3ZM2 4.25C2 3.00736 3.00736 2 4.25 2C5.49264 2 6.5 3.00736 6.5 4.25C6.5 5.49264 5.49264 6.5 4.25 6.5C3.00736 6.5 2 5.49264 2 4.25Z" + android:fillColor="#ffffff"/> diff --git a/app/src/main/res/drawable/ic_hand_pointing.xml b/app/src/main/res/drawable/ic_hand_pointing.xml index 71bc461b8..c1e93b340 100644 --- a/app/src/main/res/drawable/ic_hand_pointing.xml +++ b/app/src/main/res/drawable/ic_hand_pointing.xml @@ -1,14 +1,15 @@ - - + android:viewportWidth="16" + android:viewportHeight="16"> + + + diff --git a/app/src/main/res/drawable/ic_hard_drives.xml b/app/src/main/res/drawable/ic_hard_drives.xml index 7e98a27ea..deac2f86c 100644 --- a/app/src/main/res/drawable/ic_hard_drives.xml +++ b/app/src/main/res/drawable/ic_hard_drives.xml @@ -1,20 +1,24 @@ - - - - + android:viewportWidth="16" + android:viewportHeight="16"> + + + + + + diff --git a/app/src/main/res/drawable/ic_lightning.xml b/app/src/main/res/drawable/ic_lightning.xml index 2e265992f..b2aa45a49 100644 --- a/app/src/main/res/drawable/ic_lightning.xml +++ b/app/src/main/res/drawable/ic_lightning.xml @@ -1,10 +1,12 @@ - + android:viewportWidth="16" + android:viewportHeight="16"> + + diff --git a/app/src/main/res/drawable/ic_list_dashes.xml b/app/src/main/res/drawable/ic_list_dashes.xml index 92c0b1f78..5d8504eb7 100644 --- a/app/src/main/res/drawable/ic_list_dashes.xml +++ b/app/src/main/res/drawable/ic_list_dashes.xml @@ -1,30 +1,24 @@ - - - - - - + android:viewportWidth="16" + android:viewportHeight="16"> + + + + + + diff --git a/app/src/main/res/drawable/ic_lock_key.xml b/app/src/main/res/drawable/ic_lock_key.xml index b25415a7f..48ad2a921 100644 --- a/app/src/main/res/drawable/ic_lock_key.xml +++ b/app/src/main/res/drawable/ic_lock_key.xml @@ -1,22 +1,21 @@ - - - - + android:viewportWidth="16" + android:viewportHeight="16"> + + + + + diff --git a/app/src/main/res/drawable/ic_power.xml b/app/src/main/res/drawable/ic_power.xml index bdc66f033..044e96fd4 100644 --- a/app/src/main/res/drawable/ic_power.xml +++ b/app/src/main/res/drawable/ic_power.xml @@ -1,14 +1,9 @@ + android:viewportWidth="256" + android:viewportHeight="256"> - + android:pathData="M120,128V48a8,8,0,0,1,16,0v80a8,8,0,0,1-16,0Zm60.37-78.7a8,8,0,0,0-8.74,13.4C194.74,77.77,208,101.57,208,128a80,80,0,0,1-160,0c0-26.43,13.26-50.23,36.37-65.3a8,8,0,0,0-8.74-13.4C47.9,67.38,32,96.06,32,128a96,96,0,0,0,192,0C224,96.06,208.1,67.38,180.37,49.3Z" + android:fillColor="#ffffff"/> diff --git a/app/src/main/res/drawable/ic_settings_dev.xml b/app/src/main/res/drawable/ic_settings_dev.xml index 934c9b5d6..7877a8cf1 100644 --- a/app/src/main/res/drawable/ic_settings_dev.xml +++ b/app/src/main/res/drawable/ic_settings_dev.xml @@ -1,6 +1,6 @@ - - - + android:width="24dp" + android:height="24dp" + android:viewportWidth="256" + android:viewportHeight="256"> + diff --git a/app/src/main/res/drawable/ic_shield.xml b/app/src/main/res/drawable/ic_shield.xml index ab8dd87af..a0419ab43 100644 --- a/app/src/main/res/drawable/ic_shield.xml +++ b/app/src/main/res/drawable/ic_shield.xml @@ -1,14 +1,15 @@ - - + android:viewportWidth="16" + android:viewportHeight="16"> + + + diff --git a/app/src/main/res/drawable/ic_smiley.xml b/app/src/main/res/drawable/ic_smiley.xml index eb0ab1566..035038d81 100644 --- a/app/src/main/res/drawable/ic_smiley.xml +++ b/app/src/main/res/drawable/ic_smiley.xml @@ -1,20 +1,9 @@ - - - - + android:viewportWidth="256" + android:viewportHeight="256"> + diff --git a/app/src/main/res/drawable/ic_speed_normal.xml b/app/src/main/res/drawable/ic_speed_normal.xml index 0e74b1f63..82efd0b1b 100644 --- a/app/src/main/res/drawable/ic_speed_normal.xml +++ b/app/src/main/res/drawable/ic_speed_normal.xml @@ -1,6 +1,6 @@ - - - + android:viewportWidth="16" + android:viewportHeight="16"> + + + + diff --git a/app/src/main/res/drawable/ic_stop_circle.xml b/app/src/main/res/drawable/ic_stop_circle.xml index 4ff35298e..6ac0141ac 100644 --- a/app/src/main/res/drawable/ic_stop_circle.xml +++ b/app/src/main/res/drawable/ic_stop_circle.xml @@ -1,14 +1,9 @@ - - + android:viewportWidth="256" + android:viewportHeight="256"> + diff --git a/app/src/main/res/drawable/ic_tag.xml b/app/src/main/res/drawable/ic_tag.xml index 9bf21df5b..47e7eae9b 100644 --- a/app/src/main/res/drawable/ic_tag.xml +++ b/app/src/main/res/drawable/ic_tag.xml @@ -1,18 +1,15 @@ - - - + + + diff --git a/app/src/main/res/drawable/ic_translate.xml b/app/src/main/res/drawable/ic_translate.xml index 449df7825..0a724dd27 100644 --- a/app/src/main/res/drawable/ic_translate.xml +++ b/app/src/main/res/drawable/ic_translate.xml @@ -1,30 +1,24 @@ - - - - - - + android:viewportWidth="16" + android:viewportHeight="16"> + + + + + + diff --git a/app/src/main/res/drawable/ic_warning.xml b/app/src/main/res/drawable/ic_warning.xml index baaaea5e7..ef5593f34 100644 --- a/app/src/main/res/drawable/ic_warning.xml +++ b/app/src/main/res/drawable/ic_warning.xml @@ -1,22 +1,18 @@ + android:viewportWidth="16" + android:viewportHeight="16"> + android:pathData="M7.13451 2.49904L1.63599 11.9986C1.54801 12.1506 1.50161 12.3231 1.50147 12.4987C1.50132 12.6743 1.54743 12.8469 1.63516 12.999C1.7229 13.1512 1.84915 13.2776 2.00123 13.3654C2.1533 13.4533 2.32584 13.4995 2.50147 13.4995H13.4985C13.6741 13.4995 13.8467 13.4533 13.9987 13.3654C14.1508 13.2776 14.2771 13.1512 14.3648 12.999C14.4525 12.8469 14.4986 12.6743 14.4985 12.4987C14.4984 12.3231 14.452 12.1506 14.364 11.9986L8.86545 2.49904C8.77761 2.34728 8.65141 2.22129 8.4995 2.1337C8.3476 2.04611 8.17533 2 7.99998 2C7.82463 2 7.65237 2.04611 7.50046 2.1337C7.34855 2.22129 7.22235 2.34728 7.13451 2.49904V2.49904Z" + android:fillColor="#ffffff"/> + android:pathData="M8 5.5C8.27614 5.5 8.5 5.72386 8.5 6V9C8.5 9.27614 8.27614 9.5 8 9.5C7.72386 9.5 7.5 9.27614 7.5 9V6C7.5 5.72386 7.72386 5.5 8 5.5Z" + android:fillColor="#ffffff"/> + android:pathData="M8 12C8.41421 12 8.75 11.6642 8.75 11.25C8.75 10.8358 8.41421 10.5 8 10.5C7.58579 10.5 7.25 10.8358 7.25 11.25C7.25 11.6642 7.58579 12 8 12Z" + android:fillColor="#ffffff"/> + android:pathData="M7.2507 1.70055C7.47856 1.56916 7.73696 1.5 7.99998 1.5C8.26301 1.5 8.52141 1.56916 8.74927 1.70055C8.97712 1.83194 9.16643 2.02093 9.29819 2.24857L8.86918 2.49689L9.29819 2.24857L14.7967 11.7481C14.9287 11.9761 14.9983 12.2348 14.9985 12.4983C14.9987 12.7617 14.9296 13.0206 14.798 13.2488C14.6664 13.477 14.477 13.6666 14.2489 13.7984C14.0207 13.9301 13.7619 13.9995 13.4985 13.9995H2.50147C2.23802 13.9995 1.97922 13.9301 1.75111 13.7984C1.52299 13.6666 1.33361 13.477 1.20201 13.2488C1.07042 13.0206 1.00125 12.7617 1.00147 12.4983C1.00168 12.2348 1.07128 11.9761 1.20325 11.7481L6.70177 2.24857L7.14178 2.50325L6.70177 2.24857C6.83354 2.02093 7.02284 1.83193 7.2507 1.70055ZM7.99998 2.5C7.91231 2.5 7.82617 2.52305 7.75022 2.56685C7.67427 2.61064 7.61117 2.67364 7.56725 2.74952L2.06873 12.249L1.63599 11.9986L2.06873 12.249C2.02474 12.325 2.00154 12.4113 2.00147 12.4991C2.00139 12.5869 2.02445 12.6732 2.06831 12.7493C2.11218 12.8254 2.17531 12.8885 2.25135 12.9325C2.32739 12.9764 2.41365 12.9995 2.50147 12.9995H13.4985C13.5863 12.9995 13.6726 12.9764 13.7486 12.9325C13.8247 12.8885 13.8878 12.8254 13.9317 12.7493C13.9755 12.6732 13.9986 12.5869 13.9985 12.4991C13.9984 12.4113 13.9752 12.325 13.9312 12.249L14.364 11.9986L13.9312 12.249L8.43272 2.74952C8.3888 2.67364 8.3257 2.61064 8.24974 2.56685C8.17379 2.52305 8.08766 2.5 7.99998 2.5Z" + android:fillColor="#ffffff"/> From 6710e386ad11bb24eddfe59eac046a3805ec8d88 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 26 Mar 2026 09:39:53 -0300 Subject: [PATCH 62/85] fix: reimport icons --- .../drawable/ic_arrow_counter_clockwise.xml | 16 ++-- app/src/main/res/drawable/ic_broadcast.xml | 24 ++++-- .../res/drawable/ic_caret_double_right.xml | 21 ++++-- .../main/res/drawable/ic_clipboard_text.xml | 29 +++++--- app/src/main/res/drawable/ic_coins.xml | 68 ++++++++--------- app/src/main/res/drawable/ic_database.xml | 25 ++++--- app/src/main/res/drawable/ic_eye.xml | 20 ++--- app/src/main/res/drawable/ic_eye_slash.xml | 33 +++++---- app/src/main/res/drawable/ic_git_branch.xml | 73 +++++++------------ .../main/res/drawable/ic_hand_pointing.xml | 21 ++++-- app/src/main/res/drawable/ic_hard_drives.xml | 34 ++++++--- app/src/main/res/drawable/ic_lightning.xml | 17 +++-- app/src/main/res/drawable/ic_list_dashes.xml | 32 ++++---- app/src/main/res/drawable/ic_lock_key.xml | 29 +++++--- app/src/main/res/drawable/ic_power.xml | 15 ++-- app/src/main/res/drawable/ic_settings_dev.xml | 4 +- app/src/main/res/drawable/ic_share.xml | 23 ++---- app/src/main/res/drawable/ic_shield.xml | 21 ++++-- app/src/main/res/drawable/ic_smiley.xml | 29 +++++--- app/src/main/res/drawable/ic_speed_normal.xml | 4 +- app/src/main/res/drawable/ic_stack.xml | 25 ++++--- app/src/main/res/drawable/ic_stop_circle.xml | 15 ++-- app/src/main/res/drawable/ic_tag.xml | 12 +-- app/src/main/res/drawable/ic_translate.xml | 32 ++++---- app/src/main/res/drawable/ic_warning.xml | 40 +++++----- 25 files changed, 346 insertions(+), 316 deletions(-) diff --git a/app/src/main/res/drawable/ic_arrow_counter_clockwise.xml b/app/src/main/res/drawable/ic_arrow_counter_clockwise.xml index 3e6f603a8..545c176ab 100644 --- a/app/src/main/res/drawable/ic_arrow_counter_clockwise.xml +++ b/app/src/main/res/drawable/ic_arrow_counter_clockwise.xml @@ -1,14 +1,14 @@ + android:width="16dp" + android:height="16dp" + android:viewportWidth="16" + android:viewportHeight="16"> diff --git a/app/src/main/res/drawable/ic_broadcast.xml b/app/src/main/res/drawable/ic_broadcast.xml index 7436da07e..88d4e65bf 100644 --- a/app/src/main/res/drawable/ic_broadcast.xml +++ b/app/src/main/res/drawable/ic_broadcast.xml @@ -4,16 +4,28 @@ android:viewportWidth="16" android:viewportHeight="16"> + + + diff --git a/app/src/main/res/drawable/ic_caret_double_right.xml b/app/src/main/res/drawable/ic_caret_double_right.xml index b1021748f..1c9ccb556 100644 --- a/app/src/main/res/drawable/ic_caret_double_right.xml +++ b/app/src/main/res/drawable/ic_caret_double_right.xml @@ -1,14 +1,19 @@ + android:width="16dp" + android:height="16dp" + android:viewportWidth="16" + android:viewportHeight="16"> + diff --git a/app/src/main/res/drawable/ic_clipboard_text.xml b/app/src/main/res/drawable/ic_clipboard_text.xml index 74aaf5fed..6e1b7fee1 100644 --- a/app/src/main/res/drawable/ic_clipboard_text.xml +++ b/app/src/main/res/drawable/ic_clipboard_text.xml @@ -1,22 +1,27 @@ + android:width="16dp" + android:height="16dp" + android:viewportWidth="16" + android:viewportHeight="16"> + diff --git a/app/src/main/res/drawable/ic_coins.xml b/app/src/main/res/drawable/ic_coins.xml index 881f73ce1..a69ddf3e9 100644 --- a/app/src/main/res/drawable/ic_coins.xml +++ b/app/src/main/res/drawable/ic_coins.xml @@ -1,68 +1,68 @@ + android:width="16dp" + android:height="16dp" + android:viewportWidth="16" + android:viewportHeight="16"> diff --git a/app/src/main/res/drawable/ic_database.xml b/app/src/main/res/drawable/ic_database.xml index 4ef8cc60d..6f842cea0 100644 --- a/app/src/main/res/drawable/ic_database.xml +++ b/app/src/main/res/drawable/ic_database.xml @@ -1,18 +1,23 @@ + android:width="16dp" + android:height="16dp" + android:viewportWidth="16" + android:viewportHeight="16"> + diff --git a/app/src/main/res/drawable/ic_eye.xml b/app/src/main/res/drawable/ic_eye.xml index 8768f08f1..979ea8338 100644 --- a/app/src/main/res/drawable/ic_eye.xml +++ b/app/src/main/res/drawable/ic_eye.xml @@ -1,19 +1,19 @@ + android:width="16dp" + android:height="16dp" + android:viewportWidth="16" + android:viewportHeight="16"> diff --git a/app/src/main/res/drawable/ic_eye_slash.xml b/app/src/main/res/drawable/ic_eye_slash.xml index 36e0eeeda..fc60f3e0c 100644 --- a/app/src/main/res/drawable/ic_eye_slash.xml +++ b/app/src/main/res/drawable/ic_eye_slash.xml @@ -1,26 +1,31 @@ + android:width="16dp" + android:height="16dp" + android:viewportWidth="16" + android:viewportHeight="16"> + diff --git a/app/src/main/res/drawable/ic_git_branch.xml b/app/src/main/res/drawable/ic_git_branch.xml index 2ebce6167..eff49ad96 100644 --- a/app/src/main/res/drawable/ic_git_branch.xml +++ b/app/src/main/res/drawable/ic_git_branch.xml @@ -1,54 +1,31 @@ - - - - - - - - - - - - - + + + + + + diff --git a/app/src/main/res/drawable/ic_hand_pointing.xml b/app/src/main/res/drawable/ic_hand_pointing.xml index 71bc461b8..bd64b2101 100644 --- a/app/src/main/res/drawable/ic_hand_pointing.xml +++ b/app/src/main/res/drawable/ic_hand_pointing.xml @@ -1,14 +1,19 @@ + android:width="16dp" + android:height="16dp" + android:viewportWidth="16" + android:viewportHeight="16"> + diff --git a/app/src/main/res/drawable/ic_hard_drives.xml b/app/src/main/res/drawable/ic_hard_drives.xml index 7e98a27ea..e57a3c17f 100644 --- a/app/src/main/res/drawable/ic_hard_drives.xml +++ b/app/src/main/res/drawable/ic_hard_drives.xml @@ -1,20 +1,30 @@ + android:width="16dp" + android:height="16dp" + android:viewportWidth="16" + android:viewportHeight="16"> + + + android:pathData="M11.25,5.75C11.664,5.75 12,5.414 12,5C12,4.586 11.664,4.25 11.25,4.25C10.836,4.25 10.5,4.586 10.5,5C10.5,5.414 10.836,5.75 11.25,5.75Z" + android:fillColor="#FF4400"/> + android:pathData="M11.25,11.75C11.664,11.75 12,11.414 12,11C12,10.586 11.664,10.25 11.25,10.25C10.836,10.25 10.5,10.586 10.5,11C10.5,11.414 10.836,11.75 11.25,11.75Z" + android:fillColor="#FF4400"/> diff --git a/app/src/main/res/drawable/ic_lightning.xml b/app/src/main/res/drawable/ic_lightning.xml index 2e265992f..8c863840e 100644 --- a/app/src/main/res/drawable/ic_lightning.xml +++ b/app/src/main/res/drawable/ic_lightning.xml @@ -1,10 +1,15 @@ + android:width="16dp" + android:height="16dp" + android:viewportWidth="16" + android:viewportHeight="16"> + diff --git a/app/src/main/res/drawable/ic_list_dashes.xml b/app/src/main/res/drawable/ic_list_dashes.xml index 92c0b1f78..a5a08872b 100644 --- a/app/src/main/res/drawable/ic_list_dashes.xml +++ b/app/src/main/res/drawable/ic_list_dashes.xml @@ -1,30 +1,30 @@ + android:width="16dp" + android:height="16dp" + android:viewportWidth="16" + android:viewportHeight="16"> diff --git a/app/src/main/res/drawable/ic_lock_key.xml b/app/src/main/res/drawable/ic_lock_key.xml index b25415a7f..b6217c0bd 100644 --- a/app/src/main/res/drawable/ic_lock_key.xml +++ b/app/src/main/res/drawable/ic_lock_key.xml @@ -1,22 +1,27 @@ + android:width="16dp" + android:height="16dp" + android:viewportWidth="16" + android:viewportHeight="16"> + diff --git a/app/src/main/res/drawable/ic_power.xml b/app/src/main/res/drawable/ic_power.xml index bdc66f033..044e96fd4 100644 --- a/app/src/main/res/drawable/ic_power.xml +++ b/app/src/main/res/drawable/ic_power.xml @@ -1,14 +1,9 @@ + android:viewportWidth="256" + android:viewportHeight="256"> - + android:pathData="M120,128V48a8,8,0,0,1,16,0v80a8,8,0,0,1-16,0Zm60.37-78.7a8,8,0,0,0-8.74,13.4C194.74,77.77,208,101.57,208,128a80,80,0,0,1-160,0c0-26.43,13.26-50.23,36.37-65.3a8,8,0,0,0-8.74-13.4C47.9,67.38,32,96.06,32,128a96,96,0,0,0,192,0C224,96.06,208.1,67.38,180.37,49.3Z" + android:fillColor="#ffffff"/> diff --git a/app/src/main/res/drawable/ic_settings_dev.xml b/app/src/main/res/drawable/ic_settings_dev.xml index 934c9b5d6..7877a8cf1 100644 --- a/app/src/main/res/drawable/ic_settings_dev.xml +++ b/app/src/main/res/drawable/ic_settings_dev.xml @@ -1,6 +1,6 @@ - - - + android:width="24dp" + android:height="24dp" + android:viewportWidth="256" + android:viewportHeight="256"> + diff --git a/app/src/main/res/drawable/ic_shield.xml b/app/src/main/res/drawable/ic_shield.xml index ab8dd87af..3d763a92b 100644 --- a/app/src/main/res/drawable/ic_shield.xml +++ b/app/src/main/res/drawable/ic_shield.xml @@ -1,14 +1,19 @@ + android:width="16dp" + android:height="16dp" + android:viewportWidth="16" + android:viewportHeight="16"> + diff --git a/app/src/main/res/drawable/ic_smiley.xml b/app/src/main/res/drawable/ic_smiley.xml index eb0ab1566..51855c4cf 100644 --- a/app/src/main/res/drawable/ic_smiley.xml +++ b/app/src/main/res/drawable/ic_smiley.xml @@ -1,20 +1,25 @@ + android:width="16dp" + android:height="16dp" + android:viewportWidth="16" + android:viewportHeight="16"> + android:pathData="M8,14C11.314,14 14,11.314 14,8C14,4.686 11.314,2 8,2C4.686,2 2,4.686 2,8C2,11.314 4.686,14 8,14Z" + android:strokeAlpha="0.2" + android:fillColor="#FF4400" + android:fillAlpha="0.2"/> + android:pathData="M5.75,7.5C6.164,7.5 6.5,7.164 6.5,6.75C6.5,6.336 6.164,6 5.75,6C5.336,6 5,6.336 5,6.75C5,7.164 5.336,7.5 5.75,7.5Z" + android:fillColor="#FF4400"/> + diff --git a/app/src/main/res/drawable/ic_speed_normal.xml b/app/src/main/res/drawable/ic_speed_normal.xml index 0e74b1f63..82efd0b1b 100644 --- a/app/src/main/res/drawable/ic_speed_normal.xml +++ b/app/src/main/res/drawable/ic_speed_normal.xml @@ -1,6 +1,6 @@ + android:width="16dp" + android:height="16dp" + android:viewportWidth="16" + android:viewportHeight="16"> + diff --git a/app/src/main/res/drawable/ic_stop_circle.xml b/app/src/main/res/drawable/ic_stop_circle.xml index 4ff35298e..6ac0141ac 100644 --- a/app/src/main/res/drawable/ic_stop_circle.xml +++ b/app/src/main/res/drawable/ic_stop_circle.xml @@ -1,14 +1,9 @@ - - + android:viewportWidth="256" + android:viewportHeight="256"> + diff --git a/app/src/main/res/drawable/ic_tag.xml b/app/src/main/res/drawable/ic_tag.xml index 9bf21df5b..f722a69d4 100644 --- a/app/src/main/res/drawable/ic_tag.xml +++ b/app/src/main/res/drawable/ic_tag.xml @@ -4,15 +4,15 @@ android:viewportWidth="16" android:viewportHeight="16"> + android:pathData="M5.25,6C5.664,6 6,5.664 6,5.25C6,4.836 5.664,4.5 5.25,4.5C4.835,4.5 4.5,4.836 4.5,5.25C4.5,5.664 4.835,6 5.25,6Z" + android:fillColor="#FF4400"/> diff --git a/app/src/main/res/drawable/ic_translate.xml b/app/src/main/res/drawable/ic_translate.xml index 449df7825..def846a0b 100644 --- a/app/src/main/res/drawable/ic_translate.xml +++ b/app/src/main/res/drawable/ic_translate.xml @@ -1,30 +1,30 @@ + android:width="16dp" + android:height="16dp" + android:viewportWidth="16" + android:viewportHeight="16"> diff --git a/app/src/main/res/drawable/ic_warning.xml b/app/src/main/res/drawable/ic_warning.xml index baaaea5e7..35ad35f66 100644 --- a/app/src/main/res/drawable/ic_warning.xml +++ b/app/src/main/res/drawable/ic_warning.xml @@ -1,22 +1,22 @@ - - - - + android:width="16dp" + android:height="16dp" + android:viewportWidth="16" + android:viewportHeight="16"> + + + + From 0381a09329babfdacd18ed2f32a65d40c369a083 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 26 Mar 2026 10:21:44 -0300 Subject: [PATCH 63/85] fix: settings row height --- .../to/bitkit/ui/components/settings/SettingsSwitchRow.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt b/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt index 7075046c0..6371a5b8a 100644 --- a/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt +++ b/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt @@ -96,16 +96,14 @@ private fun SettingsSwitchRowCore( icon: (@Composable () -> Unit)? = null, colors: SwitchColors = AppSwitchDefaults.colors ) { - Column( - modifier = modifier.heightIn(min = 52.dp) - ) { + Column(modifier = modifier) { Row( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() + .heightIn(min = 52.dp) .clickableAlpha { onClick() } - .padding(vertical = 16.dp) ) { if (icon != null) { icon() From cb834044759ba24c75c3da00e461b02b79271843 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 26 Mar 2026 10:35:03 -0300 Subject: [PATCH 64/85] feat: host preview --- .../ui/settings/AdvancedSettingsViewModel.kt | 19 ++++++++++++++++--- .../to/bitkit/ui/settings/SettingsScreen.kt | 14 ++++---------- app/src/main/res/values/strings.xml | 1 - 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsViewModel.kt b/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsViewModel.kt index df8577621..5e633dbf5 100644 --- a/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsViewModel.kt +++ b/app/src/main/java/to/bitkit/ui/settings/AdvancedSettingsViewModel.kt @@ -10,12 +10,14 @@ import kotlinx.coroutines.launch import to.bitkit.data.SettingsStore import to.bitkit.env.Env import to.bitkit.ext.filterOpen +import to.bitkit.models.ElectrumServer import to.bitkit.models.addressTypeInfo import to.bitkit.models.toAddressType import to.bitkit.repositories.LightningRepo import javax.inject.Inject private const val NODE_ID_PREFIX_LENGTH = 5 +private const val ELECTRUM_HOST_PREFIX_LENGTH = 10 @HiltViewModel class AdvancedSettingsViewModel @Inject constructor( @@ -35,9 +37,20 @@ class AdvancedSettingsViewModel @Inject constructor( .map { it.nodeId.take(NODE_ID_PREFIX_LENGTH).ifEmpty { "" } } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), "") - val isCustomElectrum = settingsStore.data - .map { it.electrumServer != Env.electrumServerUrl } - .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false) + val electrumHost = settingsStore.data + .map { + if (it.electrumServer == Env.electrumServerUrl) { + "" + } else { + val host = ElectrumServer.parse(it.electrumServer).host + if (host.length > ELECTRUM_HOST_PREFIX_LENGTH) { + "${host.take(ELECTRUM_HOST_PREFIX_LENGTH)}..." + } else { + host + } + } + } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), "") val coinSelectAuto = settingsStore.data .map { it.coinSelectAuto } diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index c40998510..d62abffc7 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -105,7 +105,7 @@ fun SettingsScreen( val selectedAddressTypeName by advancedViewModel.selectedAddressTypeName.collectAsStateWithLifecycle() val openChannelCount by advancedViewModel.openChannelCount.collectAsStateWithLifecycle() val truncatedNodeId by advancedViewModel.truncatedNodeId.collectAsStateWithLifecycle() - val isCustomElectrum by advancedViewModel.isCustomElectrum.collectAsStateWithLifecycle() + val electrumHost by advancedViewModel.electrumHost.collectAsStateWithLifecycle() val coinSelectAuto by advancedViewModel.coinSelectAuto.collectAsStateWithLifecycle() LaunchedEffect(Unit) { languageViewModel.fetchLanguageInfo() } @@ -140,7 +140,7 @@ fun SettingsScreen( coinSelectAuto = coinSelectAuto, openChannelCount = openChannelCount, truncatedNodeId = truncatedNodeId, - isCustomElectrum = isCustomElectrum, + electrumHost = electrumHost, ), onEvent = { event -> when (event) { @@ -552,13 +552,7 @@ private fun AdvancedTabContent( title = stringResource(R.string.settings__adv__electrum_server), icon = { SettingsIcon(R.drawable.ic_hard_drives) }, value = SettingsButtonValue.StringValue( - stringResource( - if (state.isCustomElectrum) { - R.string.settings__adv__electrum_custom - } else { - R.string.settings__adv__electrum_auto - } - ) + state.electrumHost.ifEmpty { stringResource(R.string.settings__adv__electrum_auto) } ), onClick = { onEvent(SettingsEvent.ElectrumServerClick) }, modifier = Modifier.testTag("ElectrumConfig") @@ -692,5 +686,5 @@ data class AdvancedTabState( val coinSelectAuto: Boolean = true, val openChannelCount: Int = 0, val truncatedNodeId: String = "", - val isCustomElectrum: Boolean = false, + val electrumHost: String = "", ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a4bfb44bb..48ea2904c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -566,7 +566,6 @@ Largest First Sort by and use largest UTXO first. Potentially lower fee, but reveals your largest UTXOs. Auto - Custom Electrum Server Lightning Connections Lightning Node From 642012208365116e14cca25a936fee0ace2f2d68 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 26 Mar 2026 10:37:20 -0300 Subject: [PATCH 65/85] fix: reimport ic_bell --- app/src/main/res/drawable/ic_bell.xml | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/drawable/ic_bell.xml b/app/src/main/res/drawable/ic_bell.xml index 635b69c4c..014ea6050 100644 --- a/app/src/main/res/drawable/ic_bell.xml +++ b/app/src/main/res/drawable/ic_bell.xml @@ -1,18 +1,24 @@ + + + android:pathData="M12.46,8.219C12.49,10.314 12.94,11.549 13.335,12.239C13.38,12.314 13.405,12.399 13.41,12.489C13.41,12.579 13.39,12.664 13.345,12.744C13.3,12.819 13.24,12.884 13.16,12.929C13.085,12.974 12.995,12.999 12.91,12.999H3.005C2.625,12.999 2.375,12.589 2.565,12.264C2.98,11.559 3.46,10.254 3.46,7.999C3.46,7.364 3.595,6.734 3.855,6.154C4.115,5.574 4.495,5.059 4.97,4.634C5.445,4.214 6.005,3.894 6.61,3.704C7.215,3.514 7.855,3.454 8.485,3.524C10.8,3.784 12.43,5.884 12.465,8.214L12.46,8.219Z" + android:strokeAlpha="0.2" + android:fillColor="#ffffff" + android:fillAlpha="0.2"/> + From 8bd1fa195bd049ed6c3db3732c4ca6e9e34629f1 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 26 Mar 2026 11:12:25 -0300 Subject: [PATCH 66/85] chore: remove unused strings --- app/src/main/res/values-ar/strings.xml | 2 -- app/src/main/res/values-b+es+419/strings.xml | 2 -- app/src/main/res/values-ca/strings.xml | 2 -- app/src/main/res/values-cs/strings.xml | 2 -- app/src/main/res/values-de/strings.xml | 2 -- app/src/main/res/values-el/strings.xml | 2 -- app/src/main/res/values-es-rES/strings.xml | 2 -- app/src/main/res/values-es/strings.xml | 2 -- app/src/main/res/values-fr/strings.xml | 2 -- app/src/main/res/values-it/strings.xml | 2 -- app/src/main/res/values-nl/strings.xml | 2 -- app/src/main/res/values-pl/strings.xml | 2 -- app/src/main/res/values-pt-rBR/strings.xml | 2 -- app/src/main/res/values-pt/strings.xml | 2 -- app/src/main/res/values-ru/strings.xml | 2 -- app/src/main/res/values/strings.xml | 2 -- 16 files changed, 32 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 3f0857036..4acf7cb5b 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -573,9 +573,7 @@ فشل النسخ الاحتياطي: {time} قيد التشغيل آخر نسخة احتياطية: {time} - النسخ الاحتياطي أو الاستعادة نسخ محفظتك احتياطيًا - النسخ الاحتياطي أو الاستعادة تخصيص في إعدادات Bitkit على Android المدفوعات في الخلفية معطلة، لأنك رفضت الإشعارات. المدفوعات في الخلفية مفعلة. يمكنك استلام الأموال حتى عندما يكون التطبيق مغلقًا (إذا كان جهازك متصلاً بالإنترنت). diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index 177c8c91c..cdcfcb475 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -573,9 +573,7 @@ Copia de seguridad falló: {time} Ejecutándose Última Copia de Seguridad: {time} - Copia de seguridad o restauración Haga una copia de seguridad de su cartera - Copia de seguridad o restauración Personalizar en Ajustes de Bitkit en Android Los pagos en segundo plano están desactivados porque has denegado las notificaciones. Los pagos en segundo plano están activados. Puedes recibir fondos incluso cuando la app está cerrada (si tu dispositivo está conectado a internet). diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 5682eb832..fd656bd6b 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -573,9 +573,7 @@ Còpia de seguretat fallida: {time} Executant Última còpia de seguretat: {time} - Còpia de seguretat o restauració Fes còpia de seguretat de la cartera - Còpia de seguretat o restauració Personalitza a la configuració de Bitkit d\'Android Els pagaments en segon pla estan desactivats perquè has denegat les notificacions. Els pagaments en segon pla estan habilitats. Pots rebre fons fins i tot quan l\'aplicació està tancada (si el teu dispositiu està connectat a Internet). diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 3865fe5ef..1a4461290 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -573,9 +573,7 @@ Zálohování se nezdařilo: {time} Probíhá Poslední záloha: {time} - Záloha nebo obnovení Zálohujte svou peněženku - Záloha nebo obnovení Přizpůsobit v nastavení Android Bitkit Platby na pozadí jsou zakázány, protože jste odmítli oznámení. Platby na pozadí jsou povoleny. Prostředky můžete přijímat i při zavřené aplikaci (pokud je zařízení připojeno k internetu). diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 18f8a91e2..e2f64c3a7 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -519,7 +519,6 @@ Entwickler-Einstellungen sind jetzt in der gesamten App deaktiviert. Allgemein Sicherheit und Privatsphäre - Backup und Wiederherstellung Fortgeschritten Über Support @@ -591,7 +590,6 @@ Deaktiviert Nutze {biometryTypeName} Wenn eingeschaltet, kannst du {biometryTypeName} an Stelle von deinem PIN code verwenden, um dein Wallet zu entsperren oder Zahlungen zu senden. - Backup und Wiederherstellung Sichere dein Wallet Wallet zurücksetzen Backup fehlgeschlagen diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index e306871cb..1704db506 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -573,9 +573,7 @@ Αποτυχημένο αντίγραφο: {time} Εκτελείται Τελευταίο αντίγραφο: {time} - Αντίγραφο ασφαλείας ή επαναφορά Αντίγραφο ασφαλείας πορτοφολιού - Αντίγραφο ή επαναφορά Προσαρμογή στις ρυθμίσεις Android Bitkit Οι πληρωμές παρασκηνίου είναι απενεργοποιημένες επειδή αρνήθηκες τις ειδοποιήσεις. Οι πληρωμές παρασκηνίου είναι ενεργοποιημένες. Μπορείς να λαμβάνεις κεφάλαια ακόμα και όταν η εφαρμογή είναι κλειστή (αν η συσκευή σου είναι συνδεδεμένη στο διαδίκτυο). diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index e21d72238..3023595f1 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -573,9 +573,7 @@ Respaldo fallido: {time} En ejecución Último respaldo: {time} - Copia de seguridad o restaurar Respalda tu monedero - Copia de seguridad o restaurar Personalizar en ajustes de Bitkit en Android Los pagos en segundo plano están desactivados porque has denegado las notificaciones. Los pagos en segundo plano están activados. Puedes recibir fondos incluso cuando la aplicación está cerrada (si tu dispositivo está conectado a internet). diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index f449d10b2..ccb4b0b93 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -521,7 +521,6 @@ Seguridad y Privacidad Avanzado Acerca de - Copia de seguridad o Restaurar Ajustes del sistema Idioma Soporte @@ -584,7 +583,6 @@ Bitkit no pudo hacer una copia de seguridad de los datos del monedero. Reintentando en {interval, plural, one {# minuto} other {# minutos}}. Fallo de Copia de Seguridad de Datos últimas copias de seguridad - Copia de Seguridad o Restaurar Personalizar en los Ajustes de Bitkit Android Los pagos en segundo plano están desactivados porque has denegado las notificaciones. Los pagos en segundo plano están activados. Puedes recibir fondos incluso cuando la aplicación está cerrada (si tu dispositivo está conectado a internet). diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 9b2176875..9ed31a5a4 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -521,7 +521,6 @@ Paramètres système Langue Sécurité et vie privée - Sauvegarde ou restauration Avancé A propos Support @@ -574,7 +573,6 @@ Désactivé Utilisez plutôt {biometryTypeName} Lorsque cette option est activée, vous pouvez utiliser {biometryTypeName} au lieu de votre code PIN pour déverrouiller votre portefeuille ou envoyer des paiements. - Sauvegarder ou restauration Sauvegardez votre portefeuille Réinitialiser et restaurer le portefeuille Échec de la sauvegarde des données diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 6391c2330..5553aab8b 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -573,9 +573,7 @@ Backup fallito: {time} In esecuzione Ultimo backup: {time} - Backup o ripristino Esegui il backup del tuo wallet - Backup o Ripristino Personalizza nelle Impostazioni Android di Bitkit I pagamenti in background sono disabilitati perche\' hai negato le notifiche. I pagamenti in background sono abilitati. Puoi ricevere fondi anche quando l\'app e\' chiusa (se il dispositivo e\' connesso a internet). diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 52a6fe126..21642d264 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -573,9 +573,7 @@ Mislukte backup: {time} Bezig Laatste backup: {time} - Back-up of herstellen Back-up maken van je wallet - Back-up of herstellen Aanpassen in Android Bitkit-instellingen Achtergrondbetalingen zijn uitgeschakeld omdat je notificaties hebt geweigerd. Achtergrondbetalingen zijn ingeschakeld. Je kunt geld ontvangen zelfs als de app gesloten is (als je apparaat verbonden is met internet). diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 387e0afee..5cd52d1af 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -519,7 +519,6 @@ Opcje deweloperskie są teraz wyłączone w całej aplikacji. Ogólne Bezpieczeństwo i prywatność - Utwórz kopię zapasową lub przywróć Zaawansowane Informacje Wsparcie @@ -572,7 +571,6 @@ Wyłączone Użyj {biometryTypeName} wzamian Po włączeniu tej opcji możesz używać {biometryTypeName} zamiast kodu PIN do odblokowania portfela lub wysyłania płatności. - Utwórz kopię zapasową lub przywróć Utwórz kopię zapasową swojego portfela Resetowanie i przywracanie portfela Błąd tworzenia kopii zapasowej danych diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 5202a1421..98a2fd551 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -573,9 +573,7 @@ Falha no backup: {time} Executando Último backup: {time} - Fazer Backup ou Restaurar Faça backup da sua carteira - Fazer backup ou Restaurar Personalizar nas Configurações do Bitkit Android Pagamentos em segundo plano estão desativados porque você negou notificações. Pagamentos em segundo plano estão ativados. Você pode receber fundos mesmo quando o app está fechado (se seu dispositivo estiver conectado à internet). diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 6fac067a4..ca8815115 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -518,7 +518,6 @@ As opções de desenvolvedor estão agora desabilitadas em todo o aplicativo. Geral Segurança e Privacidade - Fazer backup ou Restaurar Avançado Sobre Suporte @@ -571,7 +570,6 @@ Desativado Usar {biometryTypeName} Quando ativado, você poderá usar {biometryTypeName} em vez do PIN para desbloquear ou pagar. - Fazer Backup ou Restaurar Faça backup da sua carteira Reiniciar e restaurar carteira Falha no Backup diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 2b5db1965..186d69412 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -587,9 +587,7 @@ Ошибка резервного копирования: {time} Выполняется Последнее Резервное Копирование: {time} - Резервное копирование Создать Резервную Копию Кошелька - Резервное копирование Настроить в настройках Android Bitkit Фоновые платежи отключены, потому что вы отклонили уведомления. Фоновые платежи включены. Вы можете получать средства, даже когда приложение закрыто (если ваше устройство подключено к интернету). diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 48ea2904c..ebb0b7b0e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -596,9 +596,7 @@ Failed Backup: {time} Running Latest Backup: {time} - Back Up Or Restore Back up your wallet - Back up or Restore Customize in Android Bitkit Settings Background payments are disabled, because you have denied notifications. Background payments are enabled. You can receive funds even when the app is closed (if your device is connected to the internet). From 8999476f63f4ff1f11635aee5c6c6444cd432297 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 26 Mar 2026 11:33:52 -0300 Subject: [PATCH 67/85] feat: update security__mnemonic_never_share --- .../to/bitkit/ui/settings/backups/ShowMnemonicScreen.kt | 8 ++++++-- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/backups/ShowMnemonicScreen.kt b/app/src/main/java/to/bitkit/ui/settings/backups/ShowMnemonicScreen.kt index 0505291d1..1b6705d13 100644 --- a/app/src/main/java/to/bitkit/ui/settings/backups/ShowMnemonicScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/backups/ShowMnemonicScreen.kt @@ -34,6 +34,8 @@ import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import kotlinx.collections.immutable.toImmutableList @@ -193,8 +195,10 @@ private fun ShowMnemonicContent( Spacer(modifier = Modifier.height(32.dp)) BodyS( - text = stringResource(R.string.security__mnemonic_never_share).withAccent(accentColor = Colors.Brand), - color = Colors.White64, + text = stringResource(R.string.security__mnemonic_never_share).withAccent( + defaultColor = Colors.Red, + accentStyle = SpanStyle(color = Colors.Red, fontWeight = FontWeight.Bold), + ), ) Spacer(modifier = Modifier.weight(1f)) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ebb0b7b0e..c2336cd78 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -465,7 +465,7 @@ Failed to load mnemonic Multiple Devices Don\'t use your Bitkit recovery phrase on multiple phones simultaneously, as this can corrupt your data. - <accent>Never share</accent> your recovery phrase with anyone as this may result in the loss of funds. + Make sure no one can see your screen. <accent>Never share your recovery phrase</accent> with anyone, as it may result in loss of funds. Mnemonic Phrase Successful Make sure you store your recovery phrase in a <accent>secure place</accent>, as this is the <accent>only way to recover</accent> your money! From 7183a957cc093a4e1cfe4fa288b1dc075dc8a5ea Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 26 Mar 2026 12:03:56 -0300 Subject: [PATCH 68/85] feat: add security__mnemonic_no_access --- .../ui/settings/backups/ShowMnemonicScreen.kt | 19 +++++++++++++++---- app/src/main/res/values-ar/strings.xml | 3 ++- app/src/main/res/values-b+es+419/strings.xml | 3 ++- app/src/main/res/values-ca/strings.xml | 3 ++- app/src/main/res/values-cs/strings.xml | 3 ++- app/src/main/res/values-de/strings.xml | 3 ++- app/src/main/res/values-el/strings.xml | 3 ++- app/src/main/res/values-es-rES/strings.xml | 3 ++- app/src/main/res/values-es/strings.xml | 3 ++- app/src/main/res/values-fr/strings.xml | 3 ++- app/src/main/res/values-it/strings.xml | 3 ++- app/src/main/res/values-nl/strings.xml | 3 ++- app/src/main/res/values-pl/strings.xml | 3 ++- app/src/main/res/values-pt-rBR/strings.xml | 3 ++- app/src/main/res/values-pt/strings.xml | 3 ++- app/src/main/res/values-ru/strings.xml | 3 ++- app/src/main/res/values/strings.xml | 1 + 17 files changed, 46 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/backups/ShowMnemonicScreen.kt b/app/src/main/java/to/bitkit/ui/settings/backups/ShowMnemonicScreen.kt index 1b6705d13..1eef45f44 100644 --- a/app/src/main/java/to/bitkit/ui/settings/backups/ShowMnemonicScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/backups/ShowMnemonicScreen.kt @@ -194,12 +194,23 @@ private fun ShowMnemonicContent( Spacer(modifier = Modifier.height(32.dp)) - BodyS( - text = stringResource(R.string.security__mnemonic_never_share).withAccent( + AnimatedContent( + targetState = showMnemonic, + transitionSpec = { fadeIn() togetherWith fadeOut() }, + label = "warningText" + ) { revealed -> + val warningText = stringResource( + if (revealed) { + R.string.security__mnemonic_no_access + } else { + R.string.security__mnemonic_never_share + } + ).withAccent( defaultColor = Colors.Red, accentStyle = SpanStyle(color = Colors.Red, fontWeight = FontWeight.Bold), - ), - ) + ) + BodyS(text = warningText) + } Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.height(24.dp)) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 4acf7cb5b..7dc395a3e 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -458,7 +458,8 @@ فشل تحميل عبارة الاسترداد أجهزة متعددة لا تستخدم عبارة الاسترداد الخاصة بـ Bitkit في هواتف متعددة في وقت واحد، لأن هذا قد يفسد بياناتك. - <accent>لا تشارك</accent> عبارة الاسترداد أبدًا مع أي شخص لأن ذلك قد يؤدي إلى خسارة الأموال. + تأكد من عدم رؤية أي شخص لشاشتك. <accent>لا تشارك عبارة الاسترداد أبدًا</accent> مع أي شخص، لأن ذلك قد يؤدي إلى خسارة الأموال. + <accent>لا يمكن لـ Bitkit الوصول إلى أموالك ولا يمكنه المساعدة في استردادها</accent> إذا فقدت عبارة الاسترداد. احتفظ بها في مكان آمن! عبارة الاسترداد نجاح تأكد من تخزين عبارة الاسترداد في <accent>مكان آمن</accent>، لأن هذه هي <accent>الطريقة الوحيدة لاسترداد</accent> أموالك! diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index cdcfcb475..c02b66848 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -458,7 +458,8 @@ Error al cargar frase mnemónica Múltiples Dispositivos No utilice su frase de recuperación Bitkit en varios teléfonos simultáneamente, ya que esto puede corromper sus datos. - <accent>Nunca comparta</accent> su frase de recuperación con nadie, ya que podría perder los fondos. + Asegúrese de que nadie pueda ver su pantalla. <accent>Nunca comparta su frase de recuperación</accent> con nadie, ya que podría resultar en la pérdida de fondos. + <accent>Bitkit no puede acceder a sus fondos ni ayudar a recuperarlos</accent> si pierde su frase de recuperación. ¡Guárdela en un lugar seguro! Frase semilla Éxito Asegúrese de guardar su frase de recuperación en un <accent>lugar seguro</accent>, ¡ya que es la <accent>única forma de recuperar</accent> su dinero! diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index fd656bd6b..a0e50132e 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -458,7 +458,8 @@ Error en carregar la frase mnemotècnica Múltiples dispositius No utilitzis la teva frase de recuperació de Bitkit en múltiples telèfons simultàniament, ja que això pot corrompre les teves dades. - <accent>Mai comparteixis</accent> la teva frase de recuperació amb ningú, ja que això pot resultar en la pèrdua de fons. + Assegura\'t que ningú pugui veure la teva pantalla. <accent>Mai comparteixis la teva frase de recuperació</accent> amb ningú, ja que pot resultar en la pèrdua de fons. + <accent>Bitkit no pot accedir als teus fons ni ajudar-te a recuperar-los</accent> si perds la teva frase de recuperació. Guarda-la en un lloc segur! Frase Mnemotècnica Exitós Assegura\'t de guardar la teva frase de recuperació en un <accent>lloc segur</accent>, ja que aquesta és l\'<accent>única manera de recuperar</accent> els teus diners! diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 1a4461290..1edf55f25 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -458,7 +458,8 @@ Nepodařilo se načíst mnemotechnickou frázi Více zařízení Nepoužívejte obnovovací frázi Bitkit na více telefonech současně, protože by to mohlo poškodit vaše data. - <accent>Nikdy nikomu nesdělujte</accent> svou frázi pro obnovení, protože to může vést ke ztrátě finančních prostředků. + Ujistěte se, že nikdo nevidí vaši obrazovku. <accent>Nikdy nikomu nesdělujte svou frázi pro obnovení</accent>, protože to může vést ke ztrátě prostředků. + <accent>Bitkit nemůže přistupovat k vašim prostředkům ani pomoci s jejich obnovením</accent>, pokud ztratíte svou frázi pro obnovení. Uchovejte ji v bezpečí! Mnemotechnická fráze Úspěšné Ujistěte se, že jste uložili frázi pro obnovení na <accent>bezpečném místě</accent>, protože to je <accent>jediný způsob, jak získat</accent> své peníze <accent>zpět</accent>! diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index e2f64c3a7..11c073566 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -440,7 +440,8 @@ Schreibe diese {length} Wörter in der richtigen Reihenfolge auf und bewahre sie an einem sicheren Ort auf. Verwende die folgenden 12 Wörter, um dein Geld zu einem späteren Zeitpunkt wiederherzustellen. Tippen zum Anzeigen - <accent>Teile</accent> deine Wiederherstellungsphrase <accent>niemals</accent> mit jemandem, da dies zum Verlust von Geldern führen kann. + Stelle sicher, dass niemand deinen Bildschirm sehen kann. <accent>Teile deine Wiederherstellungsphrase niemals</accent> mit jemandem, da dies zum Verlust von Geldern führen kann. + <accent>Bitkit kann nicht auf deine Gelder zugreifen und dir nicht bei der Wiederherstellung helfen</accent>, wenn du deine Wiederherstellungsphrase verlierst. Bewahre sie sicher auf! Wiederherstellungsphrase bestätigen Tippe die 12 Wörter in der richtigen Reihenfolge an. Erfolgreich diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 1704db506..87b958a40 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -458,7 +458,8 @@ Αποτυχία φόρτωσης mnemonic Πολλαπλές συσκευές Μη χρησιμοποιείς τη φράση ανάκτησης του Bitkit σε πολλά τηλέφωνα ταυτόχρονα, καθώς αυτό μπορεί να καταστρέψει τα δεδομένα σου. - <accent>Μην μοιράζεσαι ποτέ</accent> τη φράση ανάκτησής σου με κανέναν, καθώς αυτό μπορεί να οδηγήσει σε απώλεια κεφαλαίων. + Βεβαιώσου ότι κανείς δεν μπορεί να δει την οθόνη σου. <accent>Μην μοιράζεσαι ποτέ τη φράση ανάκτησής σου</accent> με κανέναν, καθώς μπορεί να οδηγήσει σε απώλεια κεφαλαίων. + <accent>Το Bitkit δεν μπορεί να αποκτήσει πρόσβαση στα κεφάλαιά σου ούτε να βοηθήσει στην ανάκτησή τους</accent> εάν χάσεις τη φράση ανάκτησής σου. Κράτησέ την ασφαλή! Φράση Mnemonic Επιτυχής Βεβαιώσου ότι αποθηκεύεις τη φράση ανάκτησής σου σε <accent>ασφαλές μέρος</accent>, καθώς αυτός είναι ο <accent>μόνος τρόπος να ανακτήσεις</accent> τα χρήματά σου! diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 3023595f1..b3649766b 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -458,7 +458,8 @@ Error al cargar el mnemónico Múltiples dispositivos No uses tu frase de recuperación de Bitkit en varios teléfonos simultáneamente, ya que esto puede corromper tus datos. - <accent>Nunca compartas</accent> tu frase de recuperación con nadie, ya que esto podría desembocar en la pérdida de fondos. + Asegúrate de que nadie pueda ver tu pantalla. <accent>Nunca compartas tu frase de recuperación</accent> con nadie, ya que podría resultar en la pérdida de fondos. + <accent>Bitkit no puede acceder a tus fondos ni ayudarte a recuperarlos</accent> si pierdes tu frase de recuperación. ¡Guárdala en un lugar seguro! Frase mnemotécnica Éxito Asegúrate de guardar tu frase de recuperación en un <accent>lugar seguro</accent>, ya que esta es la <accent>única forma de recuperar</accent> tu dinero. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index ccb4b0b93..0c0beaaf5 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -436,7 +436,8 @@ Su frase de recuperación Utilice las 12 palabras siguientes para recuperar su dinero más adelante. Toque para revelar - <accent>Nunca compartas</accent> tu frase de recuperación con nadie, ya que esto podría desembocar en la pérdida de fondos. + Asegúrate de que nadie pueda ver tu pantalla. <accent>Nunca compartas tu frase de recuperación</accent> con nadie, ya que podría resultar en la pérdida de fondos. + <accent>Bitkit no puede acceder a tus fondos ni ayudarte a recuperarlos</accent> si pierdes tu frase de recuperación. ¡Guárdala en un lugar seguro! Confirmar frase de recuperación Pulse las 12 palabras en el orden correcto. Frase mnemotécnica copiada al portapapeles diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 9ed31a5a4..4e8a384f8 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -440,7 +440,8 @@ Notez ces mots {length} dans le bon ordre et conservez-les dans un endroit sûr. Utilisez les 12 mots ci-dessous pour récupérer votre argent ultérieurement. Tapez pour révéler - <accent>Ne communiquez jamais</accent> votre phrase de recouvrement à qui que ce soit, car cela pourrait entraîner la perte des fonds. + Assurez-vous que personne ne puisse voir votre écran. <accent>Ne partagez jamais votre phrase de récupération</accent> avec qui que ce soit, car cela pourrait entraîner la perte de fonds. + <accent>Bitkit ne peut pas accéder à vos fonds ni vous aider à les récupérer</accent> si vous perdez votre phrase de récupération. Conservez-la en lieu sûr ! Confirmer la phrase de récupération Tapez les 12 mots dans le bon ordre. Mnémonique copié dans le presse-papiers diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 5553aab8b..af49bb475 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -458,7 +458,8 @@ Caricamento frase mnemonica non riuscito Dispositivi Multipli Non utilizzare la tua recovery phrase Bitkit su più telefoni contemporaneamente, in quanto potrebbe corrompere i tuoi dati. - <accent>Non condividere mai</accent> la tua frase di recupero con nessuno poiché ciò potrebbe comportare la perdita dei fondi. + Assicurati che nessuno possa vedere il tuo schermo. <accent>Non condividere mai la tua frase di recupero</accent> con nessuno, poiché potrebbe comportare la perdita dei fondi. + <accent>Bitkit non può accedere ai tuoi fondi né aiutarti a recuperarli</accent> se perdi la tua frase di recupero. Conservala al sicuro! Frase Mnemonica Successo Assicurati di conservare la frase di recupero in un <accent>luogo sicuro</accent>, poiché questo è <accent>l\'unico modo per recuperare</accent> i tuoi soldi! diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 21642d264..05b6e30a2 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -458,7 +458,8 @@ Herstelzin laden mislukt Meerdere apparaten Gebruik je Bitkit-herstelzin niet op meerdere telefoons tegelijk, dit kan je gegevens beschadigen. - <accent>Deel nooit</accent> je herstelzin met iemand, dit kan leiden tot verlies van geld. + Zorg ervoor dat niemand je scherm kan zien. <accent>Deel nooit je herstelzin</accent> met iemand, dit kan leiden tot verlies van geld. + <accent>Bitkit heeft geen toegang tot je geld en kan niet helpen bij het herstellen ervan</accent> als je je herstelzin verliest. Bewaar deze veilig! Herstelzin Succesvol Bewaar je herstelzin op een <accent>veilige plek</accent>, want dit is de <accent>enige manier om te herstellen</accent> je geld! diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 5cd52d1af..d3dc5f217 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -440,7 +440,8 @@ Zapisz poniższe {length} słów według kolejności i przechowuj je w bezpiecznym miejscu. Użyj 12 poniższych słów aby odzyskać swoje środki w przyszłości. Stuknij aby pokazać - <accent>Nigdy nie udostępniaj</accent> nikomu swojej frazy odzyskiwania, ponieważ może to spowodować utratę środków. + Upewnij się, że nikt nie widzi Twojego ekranu. <accent>Nigdy nie udostępniaj nikomu swojej frazy odzyskiwania</accent>, ponieważ może to spowodować utratę środków. + <accent>Bitkit nie ma dostępu do Twoich środków i nie może pomóc w ich odzyskaniu</accent>, jeśli utracisz frazę odzyskiwania. Przechowuj ją bezpiecznie! Potwierdź hasło Wpisz 12 słów w poprawnej kolejności. Sukces diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 98a2fd551..4e3f5cd16 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -458,7 +458,8 @@ Falha ao carregar frase mnemônica Vários Dispositivos Não use a frase de recuperação da Bitkit em vários telefones simultaneamente, pois isso pode corromper seus dados. - <accent>Nunca compartilhe</accent> sua frase de recuperação com ninguém, pois isso pode resultar na perda de fundos. + Certifique-se de que ninguém possa ver sua tela. <accent>Nunca compartilhe sua frase de recuperação</accent> com ninguém, pois isso pode resultar na perda de fundos. + <accent>O Bitkit não pode acessar seus fundos nem ajudar a recuperá-los</accent> se você perder sua frase de recuperação. Mantenha-a segura! Frase mnemônica Sucesso Armazene sua frase de recuperação em um <accent>local seguro</accent>, pois essa é a <accent>única maneira de recuperar</accent> seu dinheiro! diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index ca8815115..b53e0197d 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -441,7 +441,8 @@ Anote essas {length} palavras na ordem correta e guarde-as em um local seguro. Use as 12 palavras abaixo para recuperar seu dinheiro em uma data posterior. Toque para revelar - <accent>Nunca compartilhe</accent> sua frase de recuperação com ninguém, pois isso pode resultar na perda de fundos. + Certifique-se de que ninguém possa ver o seu ecrã. <accent>Nunca partilhe a sua frase de recuperação</accent> com ninguém, pois isso pode resultar na perda de fundos. + <accent>O Bitkit não pode aceder aos seus fundos nem ajudar a recuperá-los</accent> se perder a sua frase de recuperação. Mantenha-a segura! Confirmar frase de recuperação Insira as 12 palavras na ordem correta. Sucesso diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 186d69412..6c4c9188c 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -472,7 +472,8 @@ Не удалось загрузить мнемоническую фразу Несколько Устройств Не используйте фразу восстановления Bitkit на нескольких телефонах одновременно, так как это может привести к повреждению ваших данных. - <accent>Никогда не делитесь</accent> своей фразой восстановления с кем-либо, так как это может привести к потере средств. + Убедитесь, что никто не видит ваш экран. <accent>Никогда не делитесь своей фразой восстановления</accent> с кем-либо, так как это может привести к потере средств. + <accent>Bitkit не может получить доступ к вашим средствам и не может помочь их восстановить</accent>, если вы потеряете фразу восстановления. Храните её в безопасности! Фраза Восстановления Успешно Убедитесь, что храните свою фразу восстановления в <accent>безопасном месте</accent>, так как это <accent>единственный способ восстановить</accent> ваши средства! diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c2336cd78..755798ac3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -466,6 +466,7 @@ Multiple Devices Don\'t use your Bitkit recovery phrase on multiple phones simultaneously, as this can corrupt your data. Make sure no one can see your screen. <accent>Never share your recovery phrase</accent> with anyone, as it may result in loss of funds. + <accent>Bitkit cannot access your funds and cannot help recover them</accent> if you lose your recovery phrase. Keep it safe! Mnemonic Phrase Successful Make sure you store your recovery phrase in a <accent>secure place</accent>, as this is the <accent>only way to recover</accent> your money! From 73f3df4963c2e6452d5919aa439ebddfbb3b075e Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 26 Mar 2026 12:27:23 -0300 Subject: [PATCH 69/85] feat: update security__pass_never_share --- .../to/bitkit/ui/settings/backups/ShowPassphraseScreen.kt | 7 ++++++- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/backups/ShowPassphraseScreen.kt b/app/src/main/java/to/bitkit/ui/settings/backups/ShowPassphraseScreen.kt index eb9bba142..c93a86cd7 100644 --- a/app/src/main/java/to/bitkit/ui/settings/backups/ShowPassphraseScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/backups/ShowPassphraseScreen.kt @@ -15,6 +15,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import to.bitkit.R @@ -96,7 +98,10 @@ private fun ShowPassphraseContent( Spacer(modifier = Modifier.height(32.dp)) BodyS( - text = stringResource(R.string.security__pass_never_share).withAccent(accentColor = Colors.Brand), + text = stringResource(R.string.security__pass_never_share).withAccent( + defaultColor = Colors.Red, + accentStyle = SpanStyle(color = Colors.Brand, fontWeight = FontWeight.Bold), + ), color = Colors.White64, ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 755798ac3..c78143222 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -477,7 +477,7 @@ passphrase Confirm Passphrase Enter the passphrase you added while setting up and creating your wallet. - <accent>Never share</accent> your passphrase with anyone as this may result in the loss of funds. + <accent>Never share your passphrase</accent> with anyone, as it may result in loss of funds. Keep it secret! <accent>Passphrase:</accent> {passphrase} You added a passphrase to your recovery phrase during wallet setup. Your Passphrase From ab672847770d2e71893458b83a259ff14e14eeb6 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Thu, 26 Mar 2026 12:28:15 -0300 Subject: [PATCH 70/85] fix: use brando color instead of red --- .../java/to/bitkit/ui/settings/backups/ShowMnemonicScreen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/backups/ShowMnemonicScreen.kt b/app/src/main/java/to/bitkit/ui/settings/backups/ShowMnemonicScreen.kt index 1eef45f44..822e12487 100644 --- a/app/src/main/java/to/bitkit/ui/settings/backups/ShowMnemonicScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/backups/ShowMnemonicScreen.kt @@ -206,8 +206,8 @@ private fun ShowMnemonicContent( R.string.security__mnemonic_never_share } ).withAccent( - defaultColor = Colors.Red, - accentStyle = SpanStyle(color = Colors.Red, fontWeight = FontWeight.Bold), + defaultColor = Colors.Brand, + accentStyle = SpanStyle(color = Colors.Brand, fontWeight = FontWeight.Bold), ) BodyS(text = warningText) } From e910970b41e04b1b22550584f917d693cd8a5512 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 27 Mar 2026 07:49:05 -0300 Subject: [PATCH 71/85] feat: remove backup section header --- .../ui/settings/BackupSettingsScreen.kt | 30 +++++++------------ app/src/main/res/values-ar/strings.xml | 1 - app/src/main/res/values-b+es+419/strings.xml | 1 - app/src/main/res/values-ca/strings.xml | 1 - app/src/main/res/values-cs/strings.xml | 1 - app/src/main/res/values-de/strings.xml | 1 - app/src/main/res/values-el/strings.xml | 1 - app/src/main/res/values-es-rES/strings.xml | 1 - app/src/main/res/values-es/strings.xml | 1 - app/src/main/res/values-fr/strings.xml | 1 - app/src/main/res/values-it/strings.xml | 1 - app/src/main/res/values-nl/strings.xml | 1 - app/src/main/res/values-pl/strings.xml | 1 - app/src/main/res/values-pt-rBR/strings.xml | 1 - app/src/main/res/values-pt/strings.xml | 1 - app/src/main/res/values-ru/strings.xml | 1 - app/src/main/res/values/strings.xml | 1 - 17 files changed, 10 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt index f90a4be10..8a9b6dcc9 100644 --- a/app/src/main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt @@ -34,9 +34,7 @@ import to.bitkit.models.BackupCategory import to.bitkit.models.BackupItemStatus import to.bitkit.ui.backupsViewModel import to.bitkit.ui.components.BodyMSB -import to.bitkit.ui.components.Caption13Up import to.bitkit.ui.components.CaptionB -import to.bitkit.ui.components.FillWidth import to.bitkit.ui.components.VerticalSpacer import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.DrawerNavIcon @@ -85,26 +83,18 @@ private fun BackupSettingsScreenContent( ) { VerticalSpacer(16.dp) - Row(verticalAlignment = Alignment.CenterVertically) { - Caption13Up( - text = stringResource(R.string.settings__backup__latest), - color = Colors.White64, + @Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants") + if (Env.isE2eTest && allSynced) { + Icon( + painter = painterResource(R.drawable.ic_check_circle), + contentDescription = null, + tint = Colors.Green, + modifier = Modifier + .padding(end = 4.dp) + .size(16.dp) + .testTag("AllSynced") ) - FillWidth() - @Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants") - if (Env.isE2eTest && allSynced) { - Icon( - painter = painterResource(R.drawable.ic_check_circle), - contentDescription = null, - tint = Colors.Green, - modifier = Modifier - .padding(end = 4.dp) - .size(16.dp) - .testTag("AllSynced") - ) - } } - VerticalSpacer(12.dp) uiState.categories.map { categoryUiState -> BackupStatusItem( diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 7dc395a3e..0ddc82164 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -569,7 +569,6 @@ الأدوات فشل Bitkit في نسخ بيانات المحفظة احتياطيًا. إعادة المحاولة خلال {interval, plural, one {# دقيقة} other {# دقائق}}. فشل النسخ الاحتياطي للبيانات - أحدث النسخ الاحتياطية للبيانات إعادة تعيين واستعادة المحفظة فشل النسخ الاحتياطي: {time} قيد التشغيل diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index c02b66848..9e8026922 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -569,7 +569,6 @@ Widgets Bitkit no pudo respaldar los datos de la wallet. Reintentando en {interval, plural, one {# minuto} other {# minutos}}. Falla en la copia de seguridad de los datos - últimas copias de seguridad Restablecer y resetear billetera Copia de seguridad falló: {time} Ejecutándose diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index a0e50132e..8d0ef9331 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -569,7 +569,6 @@ Ginys Bitkit no ha pogut fer còpia de seguretat de les dades de la cartera. Reintentant en {interval, plural, one {# minut} other {# minuts}}. Error de còpia de seguretat de dades - últimes còpies de seguretat de dades Restablir i restaurar cartera Còpia de seguretat fallida: {time} Executant diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 1edf55f25..166ced78a 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -569,7 +569,6 @@ Widgety Bitkitu se nepodařilo zálohovat data peněženky. Opakovaný pokus za {interval, plural, one {# minute} dalších {# minutes}}. Zálohování dat selhalo - nejnovější zálohy dat Resetovat a obnovit peněženku Zálohování se nezdařilo: {time} Probíhá diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 11c073566..fa7adc20f 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -595,7 +595,6 @@ Wallet zurücksetzen Backup fehlgeschlagen Bitkit konnte die Wallet-Daten nicht sichern. Erneuter Versuch in {interval, plural, one {# Minute} other {# Minuten}}. - Letzte Backups Gescheitertes Backup: {time} Läuft Letztes Backup: {time} diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 87b958a40..d6f9c5e47 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -569,7 +569,6 @@ Widgets Το Bitkit απέτυχε να δημιουργήσει αντίγραφο ασφαλείας δεδομένων πορτοφολιού. Νέα προσπάθεια σε {interval, plural, one {# λεπτό} other {# λεπτά}}. Αποτυχία αντιγράφου ασφαλείας δεδομένων - τελευταία αντίγραφα ασφαλείας δεδομένων Επαναφορά και ανάκτηση πορτοφολιού Αποτυχημένο αντίγραφο: {time} Εκτελείται diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index b3649766b..f3e33ee95 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -569,7 +569,6 @@ Widgets Bitkit no pudo hacer copia de seguridad de los datos del monedero. Reintentando en {interval, plural, one {# minuto} other {# minutos}}. Error en la copia de datos - últimas copias de datos Borrar y restaurar monedero Respaldo fallido: {time} En ejecución diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 0c0beaaf5..19e37f62d 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -583,7 +583,6 @@ Contactos Bitkit no pudo hacer una copia de seguridad de los datos del monedero. Reintentando en {interval, plural, one {# minuto} other {# minutos}}. Fallo de Copia de Seguridad de Datos - últimas copias de seguridad Personalizar en los Ajustes de Bitkit Android Los pagos en segundo plano están desactivados porque has denegado las notificaciones. Los pagos en segundo plano están activados. Puedes recibir fondos incluso cuando la aplicación está cerrada (si tu dispositivo está conectado a internet). diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 4e8a384f8..a801688f2 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -578,7 +578,6 @@ Réinitialiser et restaurer le portefeuille Échec de la sauvegarde des données Bitkit n\'a pas réussi à sauvegarder les données du portefeuille. Réessayez dans {interval, plural, one {# minute} other {# minutes}}. - les dernières sauvegardes de données Échec de la sauvegarde : {time} Dernière sauvegarde : {time} En cours diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index af49bb475..4ecd33819 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -569,7 +569,6 @@ Widget Bitkit non è riuscito a eseguire il backup dei dati del portafoglio. Riprova tra {interval, plural, one {# minute} o {# minutes}}. Fallimento del backup dei dati - ultimi backup dei dati Resettare e ripristinare il wallet Backup fallito: {time} In esecuzione diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 05b6e30a2..25ab9c68d 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -569,7 +569,6 @@ Widgets Bitkit kon geen back-up maken van wallet-gegevens. Nieuwe poging over {interval, plural, one {# minuut} other {# minuten}}. Gegevensbackup mislukt - Laatste gegevensbackups Wallet resetten en herstellen Mislukte backup: {time} Bezig diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index d3dc5f217..a565e3802 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -576,7 +576,6 @@ Resetowanie i przywracanie portfela Błąd tworzenia kopii zapasowej danych Bitkit nie wykonał kopii zapasowej danych portfela. Ponowna próba za {interval, plural, one {# minute} other {# minutes}}. - ostatnie kopie zapasowe Nieudana kopia zapasowa: {time} Najnowsza kopia zapasowa: {time} Połączenia diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 4e3f5cd16..7eefbf17e 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -569,7 +569,6 @@ Widgets A Bitkit falhou ao fazer o backup dos dados da carteira. Tentando novamente em {interval, plural, one {# minute} other {# minutes}}. Falha no Backup - backups recentes Reiniciar e restaurar carteira Falha no backup: {time} Executando diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index b53e0197d..da76bf6cc 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -575,7 +575,6 @@ Reiniciar e restaurar carteira Falha no Backup A Bitkit falhou ao fazer o backup dos dados da carteira. Tentando novamente em {interval, plural, one {# minuto} other {# minutos}}. - backups recentes Em execução Falha no backup: {time} Último backup: {time} diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6c4c9188c..d6d2719f0 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -583,7 +583,6 @@ Виджеты Bitkit не удалось создать резервную копию данных кошелька. Повторная попытка через {interval, plural, one {# минуту} few {# минуты} many {# минут} other {# минут}}. Ошибка Резервного Копирования - Последние Резервные Копии Данных Сбросить и Восстановить Кошелёк Ошибка резервного копирования: {time} Выполняется diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c78143222..914a69d32 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -591,7 +591,6 @@ Widgets Bitkit failed to back up wallet data. Retrying in {interval, plural, one {# minute} other {# minutes}}. Data Backup Failure - latest data backups Data backups Reset wallet Failed Backup: {time} From adc150448e0409b2959b18b1fac492b4ac8f28cc Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 27 Mar 2026 08:20:20 -0300 Subject: [PATCH 72/85] fix: don't navigate to pin intro from settings --- app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index d62abffc7..34c455c21 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -167,7 +167,13 @@ fun SettingsScreen( navController.navigateTo(Routes.ResetAndRestoreSettings) } } - SettingsEvent.PinClick -> navController.navigateToPinManagement() + SettingsEvent.PinClick -> { + if (isPinEnabled) { + navController.navigateToPinManagement() + } else { + app.showSheet(Sheet.Pin()) + } + } SettingsEvent.PinForPaymentsClick -> { navController.navigateToAuthCheck( onSuccessActionId = AuthCheckAction.TOGGLE_PIN_FOR_PAYMENTS, From ca4eb44da98efb78a337d8a94382eff4a7c00ce6 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 27 Mar 2026 08:27:12 -0300 Subject: [PATCH 73/85] chore: update Modifier agent rules --- AGENTS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 925963fe8..03edec5bd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -192,8 +192,9 @@ suspend fun getData(): Result = withContext(Dispatchers.IO) { - NEVER hardcode strings and always preserve string resources - ALWAYS localize in ViewModels using injected `@ApplicationContext`, e.g. `context.getString()` - ALWAYS use `remember` for expensive Compose computations -- ALWAYS add modifiers to the last place in the argument list when calling composable functions and NEVER add a trailing comma after the last `modifier` argument -- NEVER add parameters with default values BEFORE the `modifier` parameter in composable functions - modifier must be the FIRST optional parameter +- ALWAYS declare `modifier: Modifier = Modifier,` as the FIRST optional parameter in composable declarations +- ALWAYS pass `modifier = ...` as the LAST argument in composable calls +- ALWAYS add trailing commas in multi-line declarations; NEVER add a trailing comma to `modifier = ...` at call sites - ALWAYS use `navController.navigateTo(route)` for simple navigation; NEVER use raw `navController.navigate(route)` — `navigateTo` prevents duplicate destinations - ALWAYS prefer `VerticalSpacer`, `HorizontalSpacer`, `FillHeight` and `FillWidth` over `Spacer` when applicable - PREFER declaring small dependant classes, constants, interfaces or top-level functions in the same file with the core class where these are used From 1c8ef3027913f26cd567d4a979a4d12811768cce Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 27 Mar 2026 08:29:04 -0300 Subject: [PATCH 74/85] fix: restore trailing comma at declaration time --- app/src/main/java/to/bitkit/ui/ContentView.kt | 2 +- app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt | 4 ++-- .../java/to/bitkit/ui/components/settings/SettingsIcon.kt | 2 +- app/src/main/java/to/bitkit/ui/scaffold/AppTopBar.kt | 6 +++--- .../to/bitkit/ui/screens/wallets/send/SendPinCheckScreen.kt | 2 +- .../main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt | 4 ++-- .../main/java/to/bitkit/ui/settings/pin/PinConfirmScreen.kt | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt index 75b722a2e..f4e30ee01 100644 --- a/app/src/main/java/to/bitkit/ui/ContentView.kt +++ b/app/src/main/java/to/bitkit/ui/ContentView.kt @@ -199,7 +199,7 @@ fun ContentView( settingsViewModel: SettingsViewModel, backupsViewModel: BackupsViewModel, hazeState: HazeState, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { val navController = rememberNavController() diff --git a/app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt b/app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt index d797b4bcf..859a21fa7 100644 --- a/app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt +++ b/app/src/main/java/to/bitkit/ui/components/DrawerMenu.kt @@ -68,7 +68,7 @@ fun DrawerMenu( rootNavController: NavController, hasSeenWidgetsIntro: Boolean, hasSeenShopIntro: Boolean, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { val scope = rememberCoroutineScope() @@ -246,7 +246,7 @@ private fun Menu( private fun Scrim( visible: Boolean, onClick: () -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { AnimatedVisibility( visible = visible, diff --git a/app/src/main/java/to/bitkit/ui/components/settings/SettingsIcon.kt b/app/src/main/java/to/bitkit/ui/components/settings/SettingsIcon.kt index 88309b448..a0178a78d 100644 --- a/app/src/main/java/to/bitkit/ui/components/settings/SettingsIcon.kt +++ b/app/src/main/java/to/bitkit/ui/components/settings/SettingsIcon.kt @@ -16,7 +16,7 @@ import to.bitkit.ui.theme.Colors @Composable fun SettingsIcon( @DrawableRes iconRes: Int, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Box( contentAlignment = Alignment.Center, diff --git a/app/src/main/java/to/bitkit/ui/scaffold/AppTopBar.kt b/app/src/main/java/to/bitkit/ui/scaffold/AppTopBar.kt index 00c5565c0..9f45e302f 100644 --- a/app/src/main/java/to/bitkit/ui/scaffold/AppTopBar.kt +++ b/app/src/main/java/to/bitkit/ui/scaffold/AppTopBar.kt @@ -77,7 +77,7 @@ fun AppTopBar( @Composable fun BackNavIcon( onClick: () -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { IconButton( onClick = rememberDebouncedClick(onClick = onClick), @@ -93,7 +93,7 @@ fun BackNavIcon( @Composable fun DrawerNavIcon( - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { val isPreview = LocalInspectionMode.current val drawerState = LocalDrawerState.current @@ -118,7 +118,7 @@ fun DrawerNavIcon( @Composable fun ScanNavIcon( onClick: () -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { IconButton( onClick = rememberDebouncedClick(onClick = onClick), diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendPinCheckScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendPinCheckScreen.kt index c572784e2..40303cef7 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendPinCheckScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendPinCheckScreen.kt @@ -81,7 +81,7 @@ private fun PinCheckContent( onKeyPress: (String) -> Unit, onBack: () -> Unit, onClickForgotPin: () -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { val isLastAttempt = attemptsRemaining == 1 diff --git a/app/src/main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt index 8a9b6dcc9..8ea3a7a22 100644 --- a/app/src/main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/BackupSettingsScreen.kt @@ -112,7 +112,7 @@ private fun BackupSettingsScreenContent( private fun BackupStatusItem( uiState: BackupCategoryUiState, onRetryClick: (BackupCategory) -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { val status = uiState.status @@ -158,7 +158,7 @@ private fun BackupStatusItem( private fun BackupStatusIcon( status: BackupItemStatus, @DrawableRes iconRes: Int, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Box( contentAlignment = Alignment.Center, diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/PinConfirmScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/PinConfirmScreen.kt index 32252b1bb..ef91a4652 100644 --- a/app/src/main/java/to/bitkit/ui/settings/pin/PinConfirmScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/pin/PinConfirmScreen.kt @@ -79,7 +79,7 @@ private fun ConfirmPinContent( showError: Boolean, onKeyPress: (String) -> Unit, onBack: () -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Column( modifier = modifier From a7e75f939fbd7805110a56cca50a7a673e405e5c Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 27 Mar 2026 08:45:49 -0300 Subject: [PATCH 75/85] fix: only display pin intro when navigating from home --- app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt | 8 +------- .../java/to/bitkit/ui/settings/pin/PinManagementScreen.kt | 3 ++- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index 34c455c21..d62abffc7 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -167,13 +167,7 @@ fun SettingsScreen( navController.navigateTo(Routes.ResetAndRestoreSettings) } } - SettingsEvent.PinClick -> { - if (isPinEnabled) { - navController.navigateToPinManagement() - } else { - app.showSheet(Sheet.Pin()) - } - } + SettingsEvent.PinClick -> navController.navigateToPinManagement() SettingsEvent.PinForPaymentsClick -> { navController.navigateToAuthCheck( onSuccessActionId = AuthCheckAction.TOGGLE_PIN_FOR_PAYMENTS, diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt index a446f0ae1..3c56109f7 100644 --- a/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt @@ -30,6 +30,7 @@ import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.DrawerNavIcon import to.bitkit.ui.scaffold.ScreenColumn import to.bitkit.ui.settingsViewModel +import to.bitkit.ui.sheets.PinRoute import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors @@ -43,7 +44,7 @@ fun PinManagementScreen( Content( isPinEnabled = isPinEnabled, - onEnablePinClick = { app.showSheet(Sheet.Pin()) }, + onEnablePinClick = { app.showSheet(Sheet.Pin(PinRoute.Choose)) }, onChangePinClick = { app.showSheet(Sheet.ChangePin) }, onDisablePinClick = { app.showSheet(Sheet.DisablePin) }, onBackClick = { navController.popBackStack() }, From 498919862ba1270b36a0f2850a0d4761a04c5e43 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 27 Mar 2026 08:56:34 -0300 Subject: [PATCH 76/85] feat: navigate home after reset widgets and suggestions --- .../ui/settings/general/WidgetsSettingsScreen.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt index 72b4b405a..325bdc031 100644 --- a/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/general/WidgetsSettingsScreen.kt @@ -22,6 +22,7 @@ import to.bitkit.ui.components.settings.SectionHeader import to.bitkit.ui.components.settings.SettingsButtonRow import to.bitkit.ui.components.settings.SettingsIcon import to.bitkit.ui.components.settings.SettingsSwitchRow +import to.bitkit.ui.navigateToHome import to.bitkit.ui.scaffold.AppAlertDialog import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.DrawerNavIcon @@ -44,8 +45,14 @@ fun WidgetsSettingsScreen( showWidgetTitles = showWidgetTitles, onShowWidgetsClick = { settings.setShowWidgets(!showWidgets) }, onShowWidgetTitlesClick = { settings.setShowWidgetTitles(!showWidgetTitles) }, - onResetWidgetsClick = { settings.resetWidgets() }, - onResetSuggestionsClick = { settings.resetDismissedSuggestions() }, + onResetWidgetsClick = { + settings.resetWidgets() + navController.navigateToHome() + }, + onResetSuggestionsClick = { + settings.resetDismissedSuggestions() + navController.navigateToHome() + }, ) } From dca950aa5d539f2a1981ce5f5d78da845a26bf4f Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 27 Mar 2026 09:09:08 -0300 Subject: [PATCH 77/85] fix: string case and translations --- app/src/main/res/values-ar/strings.xml | 1 + app/src/main/res/values-b+es+419/strings.xml | 1 + app/src/main/res/values-ca/strings.xml | 1 + app/src/main/res/values-cs/strings.xml | 1 + app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-el/strings.xml | 1 + app/src/main/res/values-es-rES/strings.xml | 1 + app/src/main/res/values-es/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values-it/strings.xml | 1 + app/src/main/res/values-nl/strings.xml | 1 + app/src/main/res/values-pl/strings.xml | 1 + app/src/main/res/values-pt-rBR/strings.xml | 1 + app/src/main/res/values-pt/strings.xml | 1 + app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values/strings.xml | 8 ++++---- 16 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 0ddc82164..2e0da908c 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -829,6 +829,7 @@ الإعدادات تسوّق حالة التطبيق + الدعم المحفظة الأدوات عنوان إرسال Bitcoin غير صالح diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index 9e8026922..f122a9683 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -829,6 +829,7 @@ Ajustes Tienda Estatus del App + Soporte Billetera Widgets Dirección de envío de bitcoin inválida diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 8d0ef9331..29f9c546e 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -829,6 +829,7 @@ Configuracions Botiga Estat de l\'aplicació + Suport Cartera Ginys Adreça d\'enviament de Bitcoin no vàlida diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 166ced78a..750d723d7 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -829,6 +829,7 @@ Nastavení Nakoupit Stav aplikace + Podpora Peněženka Widgety Neplatná adresa pro odeslání bitcoinu diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index fa7adc20f..f9b1073eb 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -760,6 +760,7 @@ Shop Einstellungen App Status + Support Senden Empfangen Bitcoin senden diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index d6f9c5e47..b1c525eac 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -829,6 +829,7 @@ Ρυθμίσεις Κατάστημα Κατάσταση εφαρμογής + Υποστήριξη Πορτοφόλι Widgets Μη έγκυρη διεύθυνση αποστολής bitcoin diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index f3e33ee95..d039ed8af 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -829,6 +829,7 @@ Ajustes Tienda Estado de la App + Soporte Monedero Widgets Dirección bitcoin de envío no válida diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 19e37f62d..2efcc0db3 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -759,6 +759,7 @@ Ajustes Tienda Estado de la App + Soporte Monedero Enviar Recibir diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index a801688f2..bfb570567 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -760,6 +760,7 @@ Boutique Config Statut de l\'application + Assistance Envoyer Recevoir Envoyer des bitcoins diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 4ecd33819..8f6bbd8c1 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -829,6 +829,7 @@ Impostazioni Negozio Stato dell\'app + Supporto Wallet Widget Indirizzo bitcoin di invio non valido diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 25ab9c68d..c7e47668b 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -829,6 +829,7 @@ Instellingen Winkel App-status + Ondersteuning Wallet Widgets Ongeldig bitcoin-verzendadres diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a565e3802..cce1909e9 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -760,6 +760,7 @@ Sklep Ustawienia Status aplikacji + Wsparcie Wyślij Otrzymaj Wyślij Bitcoin diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 7eefbf17e..16a03135d 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -829,6 +829,7 @@ Ajustes Comprar Status do Aplicativo + Suporte Carteira Widgets Endereço de envio bitcoin inválido diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index da76bf6cc..7b3b9ac9d 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -727,6 +727,7 @@ Comprar Ajustes Status do Aplicativo + Suporte Enviar Receber Enviar Bitcoin diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index d6d2719f0..a97bec793 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -846,6 +846,7 @@ Настройки Магазин Статус Приложения + Поддержка Кошелёк Виджеты Недействительный адрес отправки Bitcoin diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 914a69d32..865133a76 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -323,7 +323,7 @@ Swipe to hide your balance, enjoy more private payments, and protect your wallet by enabling security features. Your keys,\n<accent>your coins</accent> Let’s create your wallet. Please be aware that Bitkit is mobile software. <accent>Don’t store all your money in Bitkit.</accent> - Terms of use + Terms of Use By continuing you declare that you have read and accept the terms of use. Bitkit\n<accent>terms of use</accent> Use Bitkit to pay anyone, anywhere, any time, and spend your bitcoin on the things you value in life. @@ -548,7 +548,7 @@ Now using {type} addresses. Primary address type Settings updated - Address type + Address Type Currently selected Address Viewer Coin Selection @@ -591,7 +591,7 @@ Widgets Bitkit failed to back up wallet data. Retrying in {interval, plural, one {# minute} other {# minutes}}. Data Backup Failure - Data backups + Data Backups Reset wallet Failed Backup: {time} Running @@ -666,7 +666,7 @@ Transaction Speed Tags Previously used tags - Default unit + Default Unit Bitcoin Display amounts in Tip: Quickly toggle between Bitcoin and {currency} by tapping on your wallet balance. From 136f2361f71c0ba80e13f8724b388a1467d60f29 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 27 Mar 2026 09:18:16 -0300 Subject: [PATCH 78/85] fix: update text --- app/src/main/res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 865133a76..7d31935b6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -450,7 +450,7 @@ Set New PIN Enter your <accent>current</accent> PIN to change it. Change PIN - Try again, this is not the same PIN. + Not the same PIN. Try again. Show Seed Phrase Confirm Recovery Phrase Tap the 12 words in the correct order. @@ -494,7 +494,7 @@ Forgot your PIN? Reset and recover your Bitkit wallet with your recovery phrase. Set a new PIN after completing recovery. Forgot PIN? Last attempt. Entering the wrong PIN again will reset your wallet. - Try again, this is not the same PIN. + Not the same PIN. Try again. Retype 4-Digit PIN Please retype your 4-digit PIN to complete the setup process. Secure Wallet From cfde8453ee7df869d2a3c3dcdd681f29f121d275 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 27 Mar 2026 09:49:59 -0300 Subject: [PATCH 79/85] fix: guard backup under PIN and remove it from reset --- .../to/bitkit/ui/components/AuthCheckScreen.kt | 10 ++++------ .../java/to/bitkit/ui/settings/SettingsScreen.kt | 13 ++++++++----- .../ui/settings/backups/ResetAndRestoreScreen.kt | 16 +++++++++++++++- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/components/AuthCheckScreen.kt b/app/src/main/java/to/bitkit/ui/components/AuthCheckScreen.kt index 133fdc7d2..234af04a0 100644 --- a/app/src/main/java/to/bitkit/ui/components/AuthCheckScreen.kt +++ b/app/src/main/java/to/bitkit/ui/components/AuthCheckScreen.kt @@ -6,7 +6,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController import to.bitkit.ui.Routes import to.bitkit.ui.appViewModel -import to.bitkit.ui.navigateTo import to.bitkit.ui.settingsViewModel @Composable @@ -43,10 +42,9 @@ fun AuthCheckScreen( navController.popBackStack() } - AuthCheckAction.NAV_TO_RESET -> { - navController.navigateTo(Routes.ResetAndRestoreSettings) { - popUpTo(Routes.BackupSettings) - } + AuthCheckAction.SHOW_BACKUP_SHEET -> { + navController.popBackStack() + app.showSheet(Sheet.Backup()) } } }, @@ -58,5 +56,5 @@ object AuthCheckAction { const val TOGGLE_BIOMETRICS = "TOGGLE_BIOMETRICS" const val TOGGLE_PIN_FOR_PAYMENTS = "TOGGLE_PIN_FOR_PAYMENTS" const val DISABLE_PIN = "DISABLE_PIN" - const val NAV_TO_RESET = "NAV_TO_RESET" + const val SHOW_BACKUP_SHEET = "SHOW_BACKUP_SHEET" } diff --git a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt index d62abffc7..ba607ab64 100644 --- a/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt @@ -158,15 +158,18 @@ fun SettingsScreen( navController.navigateTo(Routes.BackgroundPaymentsIntro) } } - SettingsEvent.BackupWalletClick -> app.showSheet(Sheet.Backup()) - SettingsEvent.DataBackupsClick -> navController.navigateTo(Routes.BackupSettings) - SettingsEvent.ResetWalletClick -> { + SettingsEvent.BackupWalletClick -> { if (isPinEnabled) { - navController.navigateToAuthCheck(onSuccessActionId = AuthCheckAction.NAV_TO_RESET) + navController.navigateToAuthCheck( + onSuccessActionId = AuthCheckAction.SHOW_BACKUP_SHEET, + ) } else { - navController.navigateTo(Routes.ResetAndRestoreSettings) + app.showSheet(Sheet.Backup()) } } + SettingsEvent.DataBackupsClick -> navController.navigateTo(Routes.BackupSettings) + SettingsEvent.ResetWalletClick -> + navController.navigateTo(Routes.ResetAndRestoreSettings) SettingsEvent.PinClick -> navController.navigateToPinManagement() SettingsEvent.PinForPaymentsClick -> { navController.navigateToAuthCheck( diff --git a/app/src/main/java/to/bitkit/ui/settings/backups/ResetAndRestoreScreen.kt b/app/src/main/java/to/bitkit/ui/settings/backups/ResetAndRestoreScreen.kt index dedea0a92..229b44727 100644 --- a/app/src/main/java/to/bitkit/ui/settings/backups/ResetAndRestoreScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/backups/ResetAndRestoreScreen.kt @@ -23,17 +23,21 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController import to.bitkit.R import to.bitkit.ui.appViewModel +import to.bitkit.ui.components.AuthCheckAction import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.PrimaryButton import to.bitkit.ui.components.SecondaryButton import to.bitkit.ui.components.Sheet +import to.bitkit.ui.navigateToAuthCheck import to.bitkit.ui.scaffold.AppAlertDialog import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.DrawerNavIcon import to.bitkit.ui.scaffold.ScreenColumn +import to.bitkit.ui.settingsViewModel import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors import to.bitkit.ui.walletViewModel @@ -44,11 +48,21 @@ fun ResetAndRestoreScreen( ) { val app = appViewModel ?: return val wallet = walletViewModel ?: return + val settings = settingsViewModel ?: return + val isPinEnabled by settings.isPinEnabled.collectAsStateWithLifecycle() var showDialog by remember { mutableStateOf(false) } Content( showConfirmDialog = showDialog, - onClickBackup = { app.showSheet(Sheet.Backup()) }, + onClickBackup = { + if (isPinEnabled) { + navController.navigateToAuthCheck( + onSuccessActionId = AuthCheckAction.SHOW_BACKUP_SHEET, + ) + } else { + app.showSheet(Sheet.Backup()) + } + }, onClickReset = { showDialog = true }, onResetConfirm = { wallet.wipeWallet() }, onResetDismiss = { showDialog = false }, From 0748a668548200bffa63d2622bd621c3fe2c5c12 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 27 Mar 2026 10:30:10 -0300 Subject: [PATCH 80/85] fix: pin pop navigation --- .../bitkit/ui/settings/pin/PinManagementScreen.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt b/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt index 3c56109f7..0e86a1d2b 100644 --- a/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt @@ -9,7 +9,11 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect 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.platform.testTag @@ -41,6 +45,16 @@ fun PinManagementScreen( val app = appViewModel ?: return val settings = settingsViewModel ?: return val isPinEnabled by settings.isPinEnabled.collectAsStateWithLifecycle() + val currentSheet by app.currentSheet.collectAsStateWithLifecycle() + var pinSheetWasShown by remember { mutableStateOf(false) } + + LaunchedEffect(currentSheet) { + if (currentSheet is Sheet.Pin || currentSheet is Sheet.ChangePin || currentSheet is Sheet.DisablePin) { + pinSheetWasShown = true + } else if (pinSheetWasShown && currentSheet == null) { + navController.popBackStack() + } + } Content( isPinEnabled = isPinEnabled, From 0f9c3fd36db90b298ee71a354d3e7204000ff65b Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 27 Mar 2026 10:44:54 -0300 Subject: [PATCH 81/85] feat: lightning connections empty state --- .../lightning/LightningConnectionsScreen.kt | 194 ++++++++++++------ app/src/main/res/values/strings.xml | 3 + 2 files changed, 130 insertions(+), 67 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsScreen.kt index a2edbc291..dbbb430d2 100644 --- a/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsScreen.kt @@ -1,7 +1,9 @@ package to.bitkit.ui.settings.lightning import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize @@ -47,9 +49,11 @@ import to.bitkit.ext.amountOnClose import to.bitkit.ext.createChannelDetails import to.bitkit.models.formatToModernDisplay import to.bitkit.ui.Routes +import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.BodyMSB import to.bitkit.ui.components.Caption13Up import to.bitkit.ui.components.ChannelStatusUi +import to.bitkit.ui.components.Display import to.bitkit.ui.components.FillHeight import to.bitkit.ui.components.LightningChannel import to.bitkit.ui.components.PrimaryButton @@ -66,6 +70,7 @@ import to.bitkit.ui.shared.modifiers.clickableAlpha import to.bitkit.ui.shared.util.shareZipFile import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors +import to.bitkit.ui.utils.withAccent private const val CLOSED_CHANNEL_ALPHA = 0.64f @@ -73,8 +78,6 @@ object LightningConnectionsTestTags { const val SCREEN = "lightning_connections_screen" const val ADD_CONNECTION_BUTTON = "add_connection_button" const val EXPORT_LOGS_BUTTON = "export_logs_button" - const val SHOW_CLOSED_BUTTON = "show_closed_button" - const val CHANNEL_ITEM_PREFIX = "channel_item" } @Composable @@ -105,6 +108,7 @@ fun LightningConnectionsScreen( ) } +@Suppress("CyclomaticComplexMethod") @OptIn(ExperimentalMaterial3Api::class) @Composable private fun Content( @@ -146,103 +150,127 @@ private fun Content( onRefresh = onRefresh, modifier = Modifier.fillMaxSize() ) { - Column( - modifier = Modifier - .padding(horizontal = 16.dp) - .fillMaxSize() - .verticalScroll(rememberScrollState()) - ) { - VerticalSpacer(16.dp) - LightningBalancesSection(uiState.localBalance, uiState.remoteBalance) - HorizontalDivider(modifier = Modifier.padding(top = 16.dp)) + val isEmpty = uiState.openChannels.isEmpty() && + uiState.pendingConnections.isEmpty() && + uiState.closedChannels.isEmpty() - // Pending Channels Section - if (uiState.pendingConnections.isNotEmpty()) { + if (isEmpty) { + Column( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxSize() + ) { VerticalSpacer(16.dp) - Caption13Up(stringResource(R.string.lightning__conn_pending), color = Colors.White64) - ChannelList( - status = ChannelStatusUi.PENDING, - channels = uiState.pendingConnections, - onClickChannel = onClickChannel, - ) - } + LightningBalancesSection(uiState.localBalance, uiState.remoteBalance) + HorizontalDivider(modifier = Modifier.padding(top = 16.dp)) - // Open Channels Section - if (uiState.openChannels.isNotEmpty()) { - VerticalSpacer(16.dp) - Caption13Up(stringResource(R.string.lightning__conn_open), color = Colors.White64) - ChannelList( - status = ChannelStatusUi.OPEN, - channels = uiState.openChannels, - onClickChannel = onClickChannel, + EmptyStateContent(modifier = Modifier.weight(1f)) + + PrimaryButton( + text = stringResource(R.string.lightning__conn_onboarding_button), + onClick = onClickAddConnection, + modifier = Modifier + .fillMaxWidth() + .testTag(LightningConnectionsTestTags.ADD_CONNECTION_BUTTON) ) + VerticalSpacer(16.dp) } + } else { + Column( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxSize() + .verticalScroll(rememberScrollState()) + ) { + VerticalSpacer(16.dp) + LightningBalancesSection(uiState.localBalance, uiState.remoteBalance) + HorizontalDivider(modifier = Modifier.padding(top = 16.dp)) - // Closed & Failed Channels Section - AnimatedVisibility(visible = showClosed && uiState.failedOrders.isNotEmpty()) { - Column { + // Pending Channels Section + if (uiState.pendingConnections.isNotEmpty()) { VerticalSpacer(16.dp) - Caption13Up(stringResource(R.string.lightning__conn_failed), color = Colors.White64) + Caption13Up(stringResource(R.string.lightning__conn_pending), color = Colors.White64) ChannelList( - status = ChannelStatusUi.CLOSED, - channels = uiState.failedOrders, + status = ChannelStatusUi.PENDING, + channels = uiState.pendingConnections, onClickChannel = onClickChannel, ) } - } - // Closed Channels Section - AnimatedVisibility(visible = showClosed && uiState.closedChannels.isNotEmpty()) { - Column { + // Open Channels Section + if (uiState.openChannels.isNotEmpty()) { VerticalSpacer(16.dp) - Caption13Up(stringResource(R.string.lightning__conn_closed), color = Colors.White64) + Caption13Up(stringResource(R.string.lightning__conn_open), color = Colors.White64) ChannelList( - status = ChannelStatusUi.CLOSED, - channels = uiState.closedChannels, + status = ChannelStatusUi.OPEN, + channels = uiState.openChannels, onClickChannel = onClickChannel, ) } - } - // Show/Hide Closed Channels Button - if (uiState.failedOrders.isNotEmpty() || uiState.closedChannels.isNotEmpty()) { - VerticalSpacer(16.dp) - TertiaryButton( - text = stringResource( - when (showClosed) { - true -> R.string.lightning__conn_closed_hide - else -> R.string.lightning__conn_closed_show - } - ), - onClick = { showClosed = !showClosed }, - modifier = Modifier - .wrapContentWidth() - .testTag("ChannelsClosed") - ) - } + // Closed & Failed Channels Section + AnimatedVisibility(visible = showClosed && uiState.failedOrders.isNotEmpty()) { + Column { + VerticalSpacer(16.dp) + Caption13Up(stringResource(R.string.lightning__conn_failed), color = Colors.White64) + ChannelList( + status = ChannelStatusUi.CLOSED, + channels = uiState.failedOrders, + onClickChannel = onClickChannel, + ) + } + } - // Bottom Section - FillHeight() - VerticalSpacer(16.dp) - Row( - horizontalArrangement = Arrangement.spacedBy(16.dp), - ) { + // Closed Channels Section + AnimatedVisibility(visible = showClosed && uiState.closedChannels.isNotEmpty()) { + Column { + VerticalSpacer(16.dp) + Caption13Up(stringResource(R.string.lightning__conn_closed), color = Colors.White64) + ChannelList( + status = ChannelStatusUi.CLOSED, + channels = uiState.closedChannels, + onClickChannel = onClickChannel, + ) + } + } + + // Show/Hide Closed Channels Button + if (uiState.failedOrders.isNotEmpty() || uiState.closedChannels.isNotEmpty()) { + VerticalSpacer(16.dp) + TertiaryButton( + text = stringResource( + when (showClosed) { + true -> R.string.lightning__conn_closed_hide + else -> R.string.lightning__conn_closed_show + } + ), + onClick = { showClosed = !showClosed }, + modifier = Modifier + .wrapContentWidth() + .testTag("ChannelsClosed") + ) + } + + // Bottom Section + FillHeight() + VerticalSpacer(16.dp) SecondaryButton( text = stringResource(R.string.lightning__conn_button_export_logs), onClick = onClickExportLogs, modifier = Modifier - .weight(1f) + .fillMaxWidth() .testTag(LightningConnectionsTestTags.EXPORT_LOGS_BUTTON) ) + VerticalSpacer(16.dp) PrimaryButton( text = stringResource(R.string.lightning__conn_button_add), onClick = onClickAddConnection, modifier = Modifier - .weight(1f) + .fillMaxWidth() .testTag(LightningConnectionsTestTags.ADD_CONNECTION_BUTTON) ) + VerticalSpacer(16.dp) } - VerticalSpacer(16.dp) } } } @@ -354,6 +382,38 @@ private fun ChannelItem( } } +@Composable +private fun EmptyStateContent(modifier: Modifier = Modifier) { + Column(modifier = modifier) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) { + Image( + painter = painterResource(R.drawable.lightning), + contentDescription = null, + modifier = Modifier.size(256.dp) + ) + } + + Display( + text = stringResource(R.string.lightning__conn_onboarding_title) + .withAccent(accentColor = Colors.Purple), + ) + + VerticalSpacer(14.dp) + + BodyM( + text = stringResource(R.string.lightning__conn_onboarding_text), + color = Colors.White64, + ) + + VerticalSpacer(32.dp) + } +} + @Preview @Composable private fun Preview() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7d31935b6..6d409e9f2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -105,6 +105,9 @@ Closure reason Add Connection Export Logs + Get Started + Set up your spending balance to enjoy instant and cheap transactions with friends, family, and merchants. + Enable\nLightning\n<accent>payments</accent> Closed connections Hide Closed & Failed Show Closed & Failed From 119a9d4ad4833ffa83f88dd48b971a087c75ff2a Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 27 Mar 2026 11:07:40 -0300 Subject: [PATCH 82/85] fix: bg alignment --- .../java/to/bitkit/ui/settings/support/SupportScreen.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt index 09869f86b..8bb1a28f5 100644 --- a/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/support/SupportScreen.kt @@ -221,11 +221,13 @@ private fun SupportFooter() { .fillMaxWidth() .clipToBounds() .drawBehind { + val leftCutY = size.height + val rightCutY = 0f val path = Path().apply { - moveTo(size.width, size.height * 0.1f) - lineTo(0f, size.height * 0.65f) - lineTo(0f, size.height) + moveTo(0f, leftCutY) + lineTo(size.width, rightCutY) lineTo(size.width, size.height) + lineTo(0f, size.height) close() } drawPath(path, color = Colors.Brand) From 5c4a303cbc8b408f6fdd74df32daec3e8a988891 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 27 Mar 2026 11:28:02 -0300 Subject: [PATCH 83/85] fix: test tag --- .../java/to/bitkit/ui/settings/backups/ResetAndRestoreScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/backups/ResetAndRestoreScreen.kt b/app/src/main/java/to/bitkit/ui/settings/backups/ResetAndRestoreScreen.kt index 229b44727..9b2ff4f38 100644 --- a/app/src/main/java/to/bitkit/ui/settings/backups/ResetAndRestoreScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/backups/ResetAndRestoreScreen.kt @@ -148,7 +148,7 @@ object ResetAndRestoreTestTags { const val SCREEN = "restore_screen" const val BACKUP_BUTTON = "restore_backup_button" const val RESET_BUTTON = "restore_reset_button" - const val RESET_DIALOG = "restore_reset_button" + const val RESET_DIALOG = "restore_reset_dialog" } @Preview(showSystemUi = true) From 510e2051e7a369b801da77d3302ca52f8eebbca1 Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 27 Mar 2026 11:29:07 -0300 Subject: [PATCH 84/85] fix: replace color red with brand --- .../java/to/bitkit/ui/settings/backups/ShowPassphraseScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/ui/settings/backups/ShowPassphraseScreen.kt b/app/src/main/java/to/bitkit/ui/settings/backups/ShowPassphraseScreen.kt index c93a86cd7..cdc0f6c24 100644 --- a/app/src/main/java/to/bitkit/ui/settings/backups/ShowPassphraseScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/backups/ShowPassphraseScreen.kt @@ -99,7 +99,7 @@ private fun ShowPassphraseContent( Spacer(modifier = Modifier.height(32.dp)) BodyS( text = stringResource(R.string.security__pass_never_share).withAccent( - defaultColor = Colors.Red, + defaultColor = Colors.Brand, accentStyle = SpanStyle(color = Colors.Brand, fontWeight = FontWeight.Bold), ), color = Colors.White64, From 2dcb7ae06c4e79f3549baf53ba2bbb0235a5de5b Mon Sep 17 00:00:00 2001 From: jvsena42 Date: Fri, 27 Mar 2026 11:34:40 -0300 Subject: [PATCH 85/85] feat: translations --- app/src/main/res/values-ar/strings.xml | 19 +++++++++++++++++++ app/src/main/res/values-b+es+419/strings.xml | 19 +++++++++++++++++++ app/src/main/res/values-ca/strings.xml | 19 +++++++++++++++++++ app/src/main/res/values-cs/strings.xml | 19 +++++++++++++++++++ app/src/main/res/values-de/strings.xml | 19 +++++++++++++++++++ app/src/main/res/values-el/strings.xml | 19 +++++++++++++++++++ app/src/main/res/values-es-rES/strings.xml | 19 +++++++++++++++++++ app/src/main/res/values-es/strings.xml | 19 +++++++++++++++++++ app/src/main/res/values-fr/strings.xml | 19 +++++++++++++++++++ app/src/main/res/values-it/strings.xml | 19 +++++++++++++++++++ app/src/main/res/values-nl/strings.xml | 19 +++++++++++++++++++ app/src/main/res/values-pl/strings.xml | 19 +++++++++++++++++++ app/src/main/res/values-pt-rBR/strings.xml | 19 +++++++++++++++++++ app/src/main/res/values-pt/strings.xml | 19 +++++++++++++++++++ app/src/main/res/values-ru/strings.xml | 19 +++++++++++++++++++ 15 files changed, 285 insertions(+) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 2e0da908c..840561096 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -255,6 +255,9 @@ جارٍ الاتصال والمزامنة... يرجى الانتظار حتى يتصل Bitkit بشبكة الدفع (±10 ثوانٍ). أرصدة المحفظة + ابدأ الآن + قم بإعداد رصيد الإنفاق الخاص بك للاستمتاع بمعاملات فورية ورخيصة مع الأصدقاء والعائلة والتجار. + تفعيل\nLightning\n<accent>المدفوعات</accent> يرجى الانتظار أثناء ترحيل بيانات محفظتك القديمة إلى إصدار Bitkit الجديد. عادةً ما يستغرق هذا أقل من دقيقة. ترحيل المحفظة جارٍ ترحيل\n<accent>المحفظة</accent> @@ -513,6 +516,7 @@ مسح التطبيق تمت إعادة تعيين Bitkit وحذف جميع بيانات المحفظة. تم حذف بيانات المحفظة + تفعيل PIN قانوني مشاركة غيّر محفظتك، غيّر العالم. حمّل Bitkit لـ iPhone {appStoreUrl} أو Android {playStoreUrl} @@ -557,6 +561,8 @@ أخرى المدفوعات إعادة تعيين الاقتراحات + تلقائي + تصحيح متقدم إيصالات الاتصال الاتصالات @@ -574,6 +580,7 @@ قيد التشغيل آخر نسخة احتياطية: {time} نسخ محفظتك احتياطيًا + نسخ البيانات الاحتياطية تخصيص في إعدادات Bitkit على Android المدفوعات في الخلفية معطلة، لأنك رفضت الإشعارات. المدفوعات في الخلفية مفعلة. يمكنك استلام الأموال حتى عندما يكون التطبيق مغلقًا (إذا كان جهازك متصلاً بالإنترنت). @@ -646,6 +653,8 @@ عرض المبالغ بـ نصيحة: بدّل بسرعة بين Bitcoin و {currency} بالنقر على رصيد محفظتك. الوحدة الافتراضية + الواجهة + المدفوعات عام إعدادات النظام اللغة @@ -671,6 +680,9 @@ اسحب الرصيد للإخفاء استخدم {biometryTypeName} بدلاً تحذير عند الإرسال فوق 100$ + النسخ الاحتياطي أو إعادة التعيين + الخصوصية + الأمان الأمان والخصوصية الإعدادات فشل إكمال النسخة الاحتياطية الكاملة @@ -712,10 +724,17 @@ الدعم تم الإرسال بنجاح فشل الإرسال + Bitkit من تصميم Synonym Software, S.A. DE C.V. ©2025. جميع الحقوق محفوظة. الدعم الأدوات عرض عناوين الأدوات الأدوات + إعادة تعيين بطاقات الاقتراحات + إعادة تعيين الأدوات + هل أنت متأكد من رغبتك في إعادة تعيين الأدوات؟ سيتم عرض مجموعة الأدوات الافتراضية بالإعدادات الافتراضية. + إعادة تعيين الأدوات؟ + العرض + إعادة التعيين إلى الافتراضي امتلك\n<accent>ملفك الشخصي</accent> أعد ملفك الشخصي العام وروابطك، حتى تتمكن جهات اتصال Bitkit من الوصول إليك أو الدفع لك في أي وقت وأي مكان. الملف الشخصي diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index f122a9683..9dbd3625a 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -255,6 +255,9 @@ Conectado y sincronizando... Por favor, espere a que Bitkit se conecte a la red de pagos (±10 seg.). Saldos de billetera + Comenzar + Configure su saldo de gastos para disfrutar de transacciones instantáneas y económicas con amigos, familiares y comerciantes. + Habilitar\nLightning\n<accent>pagos</accent> Por favor espera mientras se migran los datos de tu billetera anterior a esta nueva versión de Bitkit. Esto normalmente tarda menos de un minuto. Migración de billetera MIGRANDO\n<accent>BILLETERA</accent> @@ -513,6 +516,7 @@ Eliminar datos de la app Bitkit ha sido reseteado y todos los datos han sido borrados. Datos de la billetera borrados + Habilitar PIN Legal Compartir Cambie su cartera, cambie el mundo. Descargar Bitkit para iPhone {appStoreUrl} o Android {playStoreUrl} @@ -557,6 +561,8 @@ Otros Pagos Resetear sugerencias + Auto + Debug Avanzado Recibos de conexión Conexiones @@ -574,6 +580,7 @@ Ejecutándose Última Copia de Seguridad: {time} Haga una copia de seguridad de su cartera + Copias de seguridad Personalizar en Ajustes de Bitkit en Android Los pagos en segundo plano están desactivados porque has denegado las notificaciones. Los pagos en segundo plano están activados. Puedes recibir fondos incluso cuando la app está cerrada (si tu dispositivo está conectado a internet). @@ -646,6 +653,8 @@ Mostrar importes en Truco: Cambia rápidamente entre Bitcoin y {currency} tocando sobre el balance del monedero. Unidad por defecto + Interfaz + Pagos Geral Ajustes del sistema Idioma @@ -671,6 +680,9 @@ Deslizar la balanza para ocultar Usar {biometryTypeName} en su lugar Advertir al enviar más de $100 usd + Respaldar o restablecer + Privacidad + Seguridad Seguridad y Privacidad Ajustes No se ha podido completar una copia de seguridad completa @@ -712,10 +724,17 @@ Soporte Enviado con éxito Fallo en el envío + Bitkit fue creado por Synonym Software, S.A. DE C.V. ©2025. Todos los derechos reservados. Soporte Widgets Mostrar títulos de widgets Widgets + Restablecer tarjetas de sugerencias + Restablecer widgets + ¿Está seguro de que desea restablecer los widgets? Se mostrará el conjunto de widgets predeterminado con las configuraciones predeterminadas. + ¿Restablecer widgets? + Pantalla + Restablecer valores predeterminados Sea dueño de su\n<accent>perfil</accent> Configura tu perfil público y tus enlaces para que tus contactos en Bitkit puedan encontrarte o pagarte en cualquier momento y lugar. Perfil diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 29f9c546e..c30193647 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -255,6 +255,9 @@ Connectant i sincronitzant... Si us plau, espereu que Bitkit es connecti a la xarxa de pagament (±10 segons). Saldos de la cartera + Comença + Configura el teu saldo de despeses per gaudir de transaccions instantànies i econòmiques amb amics, familiars i comerciants. + Habilitar\nLightning\n<accent>pagaments</accent> Si us plau, espera mentre les dades de la teva antiga cartera es migren a aquesta nova versió de Bitkit. Normalment triga menys d\'un minut. Migració de cartera MIGRANT\n<accent>CARTERA</accent> @@ -513,6 +516,7 @@ Esborra l\'aplicació Bitkit s\'ha restablert i totes les dades de la cartera s\'han eliminat. Dades de la cartera eliminades + Habilitar PIN Legal Compartir Canvia la teva cartera, canvia el món. Descarrega Bitkit per a iPhone {appStoreUrl} o Android {playStoreUrl} @@ -557,6 +561,8 @@ Altres Pagaments Restablir suggeriments + Auto + Debug Avançat Rebuts de connexió Connexions @@ -574,6 +580,7 @@ Executant Última còpia de seguretat: {time} Fes còpia de seguretat de la cartera + Còpies de seguretat Personalitza a la configuració de Bitkit d\'Android Els pagaments en segon pla estan desactivats perquè has denegat les notificacions. Els pagaments en segon pla estan habilitats. Pots rebre fons fins i tot quan l\'aplicació està tancada (si el teu dispositiu està connectat a Internet). @@ -646,6 +653,8 @@ Mostra les quantitats en Consell: Canvia ràpidament entre Bitcoin i {currency} tocant el saldo de la teva cartera. Unitat per defecte + Interfície + Pagaments General Configuració del sistema Idioma @@ -671,6 +680,9 @@ Lliscar saldo per amagar Utilitza {biometryTypeName} en lloc Avisar quan s\'enviïn més de 100 $ + Còpia de seguretat o restablir + Privacitat + Seguretat Seguretat i privacitat Configuracions No s\'ha pogut completar una còpia de seguretat completa @@ -712,10 +724,17 @@ Suport Enviat correctament Error en enviar + Bitkit ha estat creat per Synonym Software, S.A. DE C.V. ©2025. Tots els drets reservats. Suport Ginys Mostrar títols de ginys Ginys + Restablir targetes de suggeriments + Restablir widgets + Estàs segur que vols restablir els widgets? Es mostrarà el conjunt de widgets predeterminat amb les configuracions predeterminades. + Restablir widgets? + Pantalla + Restablir valors predeterminats Sigues propietari\n<accent>del teu perfil</accent> Configura el teu perfil públic i enllaços, perquè els teus contactes de Bitkit puguin contactar-te o pagar-te en qualsevol moment i lloc. Perfil diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 750d723d7..8f9d457f2 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -255,6 +255,9 @@ Připojování & synchronizace... Prosím vyčkejte, až se Bitkit připojí k platební síti (±10 sekund). Zůstatky peněženky + Začít + Nastavte si zůstatek pro výdaje a užívejte si okamžité a levné transakce s přáteli, rodinou a obchodníky. + Povolit\nLightning\n<accent>platby</accent> Počkejte prosím, než budou vaše stará data peněženky migrována do této nové verze Bitkit. Obvykle to trvá méně než minutu. Migrace peněženky MIGRACE\nPENĚŽENKY @@ -513,6 +516,7 @@ Vymazat aplikaci Bitkit byla resetována a všechna data peněženky byla vymazána. Odstranění dat peněženky + Povolit PIN Právní Sdílet Změňte svou peněženku, změňte svět. Stáhněte si Bitkit pro iPhone {appStoreUrl} nebo Android {playStoreUrl} @@ -557,6 +561,8 @@ Další Platby Resetovat návrhy + Auto + Debug Pokročilé Potvrzení o připojení Spojení @@ -574,6 +580,7 @@ Probíhá Poslední záloha: {time} Zálohujte svou peněženku + Zálohy dat Přizpůsobit v nastavení Android Bitkit Platby na pozadí jsou zakázány, protože jste odmítli oznámení. Platby na pozadí jsou povoleny. Prostředky můžete přijímat i při zavřené aplikaci (pokud je zařízení připojeno k internetu). @@ -646,6 +653,8 @@ Zobrazit částky v Tip: Klepnutím na zůstatek v peněžence můžete rychle přepínat mezi bitcoiny a {currency}. Výchozí jednotka + Rozhraní + Platby Obecné Systémové nastavení Jazyk @@ -671,6 +680,9 @@ Skrýt zůstatek přejetím Namísto toho použít {biometryTypeName} Upozornit při odesílání více než $100 + Zálohovat nebo resetovat + Soukromí + Bezpečnost Bezpečnost a soukromí Nastavení Selhalo dokončení úplné zálohy @@ -712,10 +724,17 @@ Podpora Úspěšně odesláno Odeslání se nezdařilo + Bitkit vytvořila společnost Synonym Software, S.A. DE C.V. ©2025. Všechna práva vyhrazena. Podpora Widgety Zobrazit názvy widgetů Widgety + Resetovat karty návrhů + Resetovat widgety + Opravdu chcete resetovat widgety? Zobrazí se výchozí sada widgetů s výchozím nastavením. + Resetovat widgety? + Zobrazení + Obnovit výchozí Vlastněte svůj\n<accent>profil</accent> Nastavte si veřejný profil a odkazy, aby vás kontakty ze služby Bitkit mohly kdykoli a kdekoli kontaktovat nebo vám zaplatit. Profil diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index f9b1073eb..b3e55d72f 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -255,6 +255,9 @@ Bitte warte bis sich Bitkit mit dem Zahlungsnetzwerk verbunden hat (±10 Sekunden). Verbinden & Synchronisieren… Wallet-Guthaben + Loslegen + Richte dein Ausgabenguthaben ein, um sofortige und günstige Transaktionen mit Freunden, Familie und Händlern zu genießen. + Lightning\n<accent>Zahlungen</accent>\naktivieren Bitte warte, während deine alten Wallet-Daten auf diese neue Bitkit-Version migriert werden. Das dauert normalerweise weniger als eine Minute. Wallet-Migration WALLET\n<accent>MIGRATION</accent> @@ -513,6 +516,7 @@ PIN-Code verwenden Wallet Bitkit wurde zurückgesetzt und alle Wallet-Daten wurden gelöscht. + PIN aktivieren Einstellungen Entwickler-Einstellungen aktiviert Entwickler-Einstellungen sind jetzt in der gesamten App aktiviert. @@ -553,9 +557,17 @@ ₿ {feeSats} für die durschnittliche Transaktion ({fiatSymbol}{fiatFormatted}) Tags Früher verwendete Tags + Oberfläche + Zahlungen Widgets Widgets Titel anzeigen + Vorschlagskarten zurücksetzen + Widgets zurücksetzen + Bist du sicher, dass du die Widgets zurücksetzen möchtest? Die Standard-Widgets mit Standardkonfigurationen werden angezeigt. + Widgets zurücksetzen? + Anzeige + Auf Standard zurücksetzen QuickPay <accent>Reibungslos</accent>\nZahlungen Bitkit QuickPay beschleunigt den Bezahlvorgang, indem es QR-Codes beim Scannen automatisch bezahlt. @@ -591,6 +603,9 @@ Deaktiviert Nutze {biometryTypeName} Wenn eingeschaltet, kannst du {biometryTypeName} an Stelle von deinem PIN code verwenden, um dein Wallet zu entsperren oder Zahlungen zu senden. + Sichern oder zurücksetzen + Datenschutz + Sicherheit Sichere dein Wallet Wallet zurücksetzen Backup fehlgeschlagen @@ -607,6 +622,7 @@ Tags Profil Kontakte + Datensicherungen Support Support-Links konnten nicht geöffnet werden Brauchst du Hilfe? Melde dein Problem von innerhalb Bitkit, besuche das Hilfezentrum, überprüfe den Status, oder kontaktiere uns auf den sozialen Medien. @@ -625,6 +641,7 @@ Senden fehlgeschlagen Es gab ein Problem beim Senden deiner Nachricht. Bitte versuche es erneut. Erneut versuchen + Bitkit wurde von Synonym Software, S.A. DE C.V. ©2025 entwickelt. Alle Rechte vorbehalten. App Status Internet Verbunden @@ -673,6 +690,8 @@ Lightning Node Electrum-Server Rapid-Gossip-Sync + Auto + Debug Schnell (teurer) Schnell ± 10-20 Minuten diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index b1c525eac..379545f73 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -255,6 +255,9 @@ Σύνδεση & Συγχρονισμός... Περίμενε το Bitkit να συνδεθεί στο δίκτυο πληρωμών (±10 δευτερόλεπτα). Υπόλοιπα πορτοφολιού + Ξεκινήστε + Ρυθμίστε το υπόλοιπο δαπανών σας για να απολαύσετε άμεσες και φθηνές συναλλαγές με φίλους, οικογένεια και εμπόρους. + Ενεργοποίηση\nLightning\n<accent>πληρωμών</accent> Περίμενε όσο τα παλιά δεδομένα του πορτοφολιού μεταφέρονται σε αυτή τη νέα έκδοση του Bitkit. Αυτό συνήθως διαρκεί λιγότερο από ένα λεπτό. Μετάβαση πορτοφολιού ΜΕΤΑΒΑΣΗ\n<accent>ΠΟΡΤΟΦΟΛΙΟΥ</accent> @@ -513,6 +516,7 @@ Διαγραφή εφαρμογής Το Bitkit επαναφέρθηκε και όλα τα δεδομένα πορτοφολιού διαγράφηκαν. Δεδομένα πορτοφολιού διαγράφηκαν + Ενεργοποίηση PIN Νομικά Διαμοιρασμός Άλλαξε το πορτοφόλι σου, άλλαξε τον κόσμο. Κατέβασε το Bitkit για iPhone {appStoreUrl} ή Android {playStoreUrl} @@ -557,6 +561,8 @@ Άλλα Πληρωμές Επαναφορά προτάσεων + Αυτόματο + Debug Για προχωρημένους Αποδείξεις συνδέσεων Συνδέσεις @@ -574,6 +580,7 @@ Εκτελείται Τελευταίο αντίγραφο: {time} Αντίγραφο ασφαλείας πορτοφολιού + Αντίγραφα δεδομένων Προσαρμογή στις ρυθμίσεις Android Bitkit Οι πληρωμές παρασκηνίου είναι απενεργοποιημένες επειδή αρνήθηκες τις ειδοποιήσεις. Οι πληρωμές παρασκηνίου είναι ενεργοποιημένες. Μπορείς να λαμβάνεις κεφάλαια ακόμα και όταν η εφαρμογή είναι κλειστή (αν η συσκευή σου είναι συνδεδεμένη στο διαδίκτυο). @@ -646,6 +653,8 @@ Εμφάνιση ποσών σε Συμβουλή: Εναλλαγή γρήγορα μεταξύ Bitcoin και {currency} πατώντας στο υπόλοιπο του πορτοφολιού σου. Προεπιλεγμένη μονάδα + Διεπαφή + Πληρωμές Γενικά Ρυθμίσεις συστήματος Γλώσσα @@ -671,6 +680,9 @@ Σύρε υπόλοιπο για απόκρυψη Χρήση {biometryTypeName} αντί Προειδοποίηση όταν στέλνεις πάνω από $100 + Δημιουργία αντιγράφου ή επαναφορά + Απόρρητο + Ασφάλεια Ασφάλεια και απόρρητο Ρυθμίσεις Αποτυχία ολοκλήρωσης πλήρους αντιγράφου ασφαλείας @@ -712,10 +724,17 @@ Υποστήριξη Στάλθηκε επιτυχώς Αποτυχία αποστολής + Το Bitkit δημιουργήθηκε από τη Synonym Software, S.A. DE C.V. ©2025. Με επιφύλαξη παντός δικαιώματος. Υποστήριξη Widgets Εμφάνιση τίτλων Widget Widgets + Επαναφορά καρτών προτάσεων + Επαναφορά widgets + Είσαι σίγουρος ότι θέλεις να επαναφέρεις τα widgets; Θα εμφανιστεί το προεπιλεγμένο σετ widgets με τις προεπιλεγμένες ρυθμίσεις. + Επαναφορά widgets; + Εμφάνιση + Επαναφορά προεπιλογών Κατέχεις το\n<accent>προφίλ σου</accent> Ρύθμισε το δημόσιο προφίλ και τους συνδέσμους σου, ώστε οι επαφές σου στο Bitkit να μπορούν να επικοινωνήσουν ή να σε πληρώσουν οποτεδήποτε, οπουδήποτε. Προφίλ diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index d039ed8af..252257879 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -255,6 +255,9 @@ Conectando y Sincronizando... Por favor, espere a que Bitkit se conecte a la red de pagos (±10 segundos). Saldos del monedero + Comenzar + Configura tu saldo de gastos para disfrutar de transacciones instantáneas y económicas con amigos, familiares y comerciantes. + Habilitar\nLightning\n<accent>pagos</accent> Por favor espera mientras se migran los datos de tu antiguo monedero a esta nueva versión de Bitkit. Esto suele tardar menos de un minuto. Migración del monedero MIGRANDO\n<accent>MONEDERO</accent> @@ -513,6 +516,7 @@ Borrar App Bitkit ha sido restaurado y todos los datos de monedero han sido borrados. Datos de Monedero Borrados + Habilitar PIN Legal Compartir Cambia tu monedero, cambia el mundo. Descarga Bitkit para iPhone {appStoreUrl} o Android {playStoreUrl} @@ -557,6 +561,8 @@ Otros Pagos Restablecer sugerencias + Auto + Debug Avanzado Registro de Conexiones Conexiones @@ -574,6 +580,7 @@ En ejecución Último respaldo: {time} Respalda tu monedero + Copias de seguridad Personalizar en ajustes de Bitkit en Android Los pagos en segundo plano están desactivados porque has denegado las notificaciones. Los pagos en segundo plano están activados. Puedes recibir fondos incluso cuando la aplicación está cerrada (si tu dispositivo está conectado a internet). @@ -646,6 +653,8 @@ Mostrar cantidades en Truco: Cambia rápidamente entre Bitcoin y {currency} tocando sobre el balance del monedero. Unidad Predeterminada + Interfaz + Pagos General Ajustes del sistema Idioma @@ -671,6 +680,9 @@ Desliza el saldo para ocultar Usar {biometryTypeName} en su lugar Advertir al enviar más de $100 + Respaldar o restablecer + Privacidad + Seguridad Seguridad y Privacidad Ajustes Falló al hacer una copia de seguridad completa @@ -712,10 +724,17 @@ Soporte Enviado Satisfactoriamente Error al enviar + Bitkit fue creado por Synonym Software, S.A. DE C.V. ©2025. Todos los derechos reservados. Soporte Widgets Mostrar Títulos de Widgets Widgets + Restablecer tarjetas de sugerencias + Restablecer widgets + ¿Estás seguro de que deseas restablecer los widgets? Se mostrará el conjunto de widgets predeterminado con las configuraciones predeterminadas. + ¿Restablecer widgets? + Pantalla + Restablecer valores predeterminados Sé dueño de\n<accent>tu perfil</accent> Configura tu perfil público y enlaces para que tus contactos Bitkit puedan localizarte o pagarte en cualquier momento y lugar. Perfil diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 2efcc0db3..8a8788259 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -255,6 +255,9 @@ Conexión abierta Por favor, espere a que Bitkit se conecte a la red de pagos (±10 segundos). Conectando y Sincronizando... + Comenzar + Configura tu saldo de gastos para disfrutar de transacciones instantáneas y económicas con amigos, familiares y comerciantes. + Habilitar\nLightning\n<accent>pagos</accent> Por favor espera mientras los datos de tu antiguo monedero migran a esta nueva versión de Bitkit. Esto normalmente tarda menos de un minuto. Migración del Monedero MIGRANDO\n<accent>MONEDERO</accent> @@ -513,6 +516,7 @@ Utilizar código PIN Datos de Monedero Borrados Bitkit ha sido restaurado y todos los datos de monedero han sido borrados. + Habilitar PIN Ajustes Opciones de desarrollo activadas Las opciones de desarrollo están ahora desactivadas en toda la aplicación. @@ -553,9 +557,17 @@ ₿ {feeSats} para la transacción típica ({fiatSymbol}{fiatFormatted}) Etiquetas Etiquetas utilizadas anteriormente + Interfaz + Pagos Widgets Widgets Mostrar Títulos de Widgets + Restablecer tarjetas de sugerencias + Restablecer widgets + ¿Estás seguro de que deseas restablecer los widgets? Se mostrará el conjunto de widgets predeterminado con las configuraciones predeterminadas. + ¿Restablecer widgets? + Pantalla + Restablecer valores predeterminados Desliza el saldo para ocultar Ocultar saldo al abrir Leer portapapeles para facilitar uso @@ -567,6 +579,9 @@ Desactivado Cuando está activado, puedes usar {biometryTypeName} en lugar de tu código PIN para desbloquear tu monedero o enviar pagos. Usar {biometryTypeName} en su lugar + Respaldar o restablecer + Privacidad + Seguridad Respalda tu monedero Borrar y restaurar monedero Respaldo fallido: {time} @@ -583,6 +598,7 @@ Contactos Bitkit no pudo hacer una copia de seguridad de los datos del monedero. Reintentando en {interval, plural, one {# minuto} other {# minutos}}. Fallo de Copia de Seguridad de Datos + Copias de seguridad Personalizar en los Ajustes de Bitkit Android Los pagos en segundo plano están desactivados porque has denegado las notificaciones. Los pagos en segundo plano están activados. Puedes recibir fondos incluso cuando la aplicación está cerrada (si tu dispositivo está conectado a internet). @@ -625,6 +641,7 @@ Error al abrir enlaces de soporte satoshi@satoshi.com Error al Enviar + Bitkit fue creado por Synonym Software, S.A. DE C.V. ©2025. Todos los derechos reservados. Estado de la App Internet Conectado @@ -673,6 +690,8 @@ Nodo Lightning Servidor Electrum Rapid-Gossip-Sync + Auto + Debug Rápido (más caro) Rápido ± 10-20 minutos diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index bfb570567..9b11dcd6a 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -255,6 +255,9 @@ Connexion inactive Veuillez attendre que Bitkit se connecte au réseau de paiement (±10 secondes). Connexion et synchronisation... + Commencer + Configurez votre solde de dépenses pour profiter de transactions instantanées et peu coûteuses avec vos amis, votre famille et les commerçants. + Activer les\n<accent>paiements</accent>\nLightning Veuillez patienter pendant la migration de vos anciennes données vers cette nouvelle version de Bitkit. Cela prend généralement moins d\'une minute. Migration du portefeuille MIGRATION\n<accent>DU PORTEFEUILLE</accent> @@ -513,6 +516,7 @@ Utiliser le code PIN Données du wallet supprimées Bitkit a été réinitialisé et toutes les données du walet ont été supprimées. + Activer le PIN Paramètres Options de développement activées Les options pour les développeurs sont désormais activées dans l\'ensemble de l\'application. @@ -553,9 +557,17 @@ ₿ {feeSats} pour une transaction moyenne ({fiatSymbol}{fiatFormatted} ) Tags Étiquettes précédemment utilisées + Interface + Paiements Widgets Widgets Afficher les titres des widgets + Réinitialiser les cartes de suggestions + Réinitialiser les widgets + Êtes-vous sûr de vouloir réinitialiser les widgets ? L\'ensemble de widgets par défaut avec les configurations par défaut sera affiché. + Réinitialiser les widgets ? + Affichage + Réinitialiser par défaut QuickPay <accent>Sans friction</accent>\npaiements Bitkit QuickPay accélère le passage à la caisse en payant automatiquement les QR codes lorsqu\'ils sont scannés. @@ -574,6 +586,9 @@ Désactivé Utilisez plutôt {biometryTypeName} Lorsque cette option est activée, vous pouvez utiliser {biometryTypeName} au lieu de votre code PIN pour déverrouiller votre portefeuille ou envoyer des paiements. + Sauvegarder ou réinitialiser + Confidentialité + Sécurité Sauvegardez votre portefeuille Réinitialiser et restaurer le portefeuille Échec de la sauvegarde des données @@ -590,6 +605,7 @@ Tags Profil Contacts + Sauvegardes des données Personnaliser dans les paramètres Android de Bitkit Les paiements en arrière-plan sont désactivés car vous avez refusé les notifications. Les paiements en arrière-plan sont activés. Vous pouvez recevoir des fonds même lorsque l\'application est fermée (si votre appareil est connecté à Internet). @@ -621,6 +637,7 @@ Un problème s\'est produit lors de l\'envoi de votre problème ou de votre question. Veuillez réessayer. Réessayer Échec de l\'ouverture des liens de support + Bitkit a été conçu par Synonym Software, S.A. DE C.V. ©2025. Tous droits réservés. Statut de l\'application Internet Connecté @@ -673,6 +690,8 @@ Nœud Lightning Serveur Electrum Rapid-Gossip-Sync + Auto + Debug Rapide (plus cher) Rapide ± 10-20 minutes diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 8f6bbd8c1..c54328363 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -255,6 +255,9 @@ Connessione e sincronizzazione... Attendi che Bitkit si connetta alla rete di pagamento (±10 secondi). Saldi Wallet + Inizia + Configura il tuo saldo di spesa per goderti transazioni istantanee e economiche con amici, familiari e commercianti. + Abilita\nLightning\n<accent>pagamenti</accent> Attendi mentre i dati del tuo vecchio wallet migrano a questa nuova versione di Bitkit. Questo di solito richiede meno di un minuto. Migrazione Wallet MIGRAZIONE\n<accent>WALLET</accent> @@ -513,6 +516,7 @@ Resetta App Bitkit è stato ripristinato e tutti i dati del portafoglio sono stati eliminati. Dati del portafoglio eliminati + Abilita PIN Termini di Utilizzo Condividi Cambia il tuo portafoglio, cambia il mondo. Scarica Bitkit per iPhone {appStoreUrl} o Android {playStoreUrl} @@ -557,6 +561,8 @@ Altro Pagamenti Reimposta suggerimenti + Auto + Debug Avanzate Ricevute di Connessione Connessioni @@ -574,6 +580,7 @@ In esecuzione Ultimo backup: {time} Esegui il backup del tuo wallet + Backup dei dati Personalizza nelle Impostazioni Android di Bitkit I pagamenti in background sono disabilitati perche\' hai negato le notifiche. I pagamenti in background sono abilitati. Puoi ricevere fondi anche quando l\'app e\' chiusa (se il dispositivo e\' connesso a internet). @@ -646,6 +653,8 @@ Visualizza gli importi in Suggerimento: alterna rapidamente tra Bitcoin e {currency} toccando il saldo del tuo portafoglio. Unità predefinita + Interfaccia + Pagamenti Generale Impostazioni di sistema Lingua @@ -671,6 +680,9 @@ Scorri per nascondere il saldo Usa {biometryTypeName} invece Avvisa quando invii più di $100 + Backup o ripristino + Privacy + Sicurezza Sicurezza e Privacy Impostazioni Impossibile completare un backup completo @@ -712,10 +724,17 @@ Supporto Inviato con successo Impossibile inviare + Bitkit è stato realizzato da Synonym Software, S.A. DE C.V. ©2025. Tutti i diritti riservati. Supporto Widget Mostra titoli dei widget Widget + Reimposta schede suggerimenti + Reimposta widget + Sei sicuro di voler reimpostare i widget? Verrà visualizzato il set di widget predefinito con le configurazioni predefinite. + Reimpostare i widget? + Visualizzazione + Ripristina impostazioni predefinite Possiedi il tuo\n<accent>profilo</accent> Configura il tuo profilo pubblico e i tuoi collegamenti, in modo che i tuoi contatti Bitkit possano raggiungerti o pagarti sempre e ovunque. Profilo diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index c7e47668b..141fd00b9 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -255,6 +255,9 @@ Verbinden & synchroniseren... Wacht tot Bitkit verbinding maakt met het betaalnetwerk (±10 seconden). Wallet-saldi + Aan de slag + Stel je uitgavenbalans in om te genieten van directe en goedkope transacties met vrienden, familie en winkels. + Lightning\n<accent>betalingen</accent>\ninschakelen Even geduld terwijl je oude wallet-gegevens migreren naar deze nieuwe Bitkit-versie. Dit duurt meestal minder dan een minuut. Wallet-migratie WALLET\n<accent>MIGREREN</accent> @@ -513,6 +516,7 @@ App wissen Bitkit is gereset en alle wallet-gegevens zijn verwijderd. Wallet-gegevens verwijderd + PIN inschakelen Juridisch Delen Verander je wallet, verander de wereld. Download Bitkit voor iPhone {appStoreUrl} of Android {playStoreUrl} @@ -557,6 +561,8 @@ Overig Betalingen Suggesties resetten + Auto + Debug Geavanceerd Verbindingsbewijzen Verbindingen @@ -574,6 +580,7 @@ Bezig Laatste backup: {time} Back-up maken van je wallet + Gegevensback-ups Aanpassen in Android Bitkit-instellingen Achtergrondbetalingen zijn uitgeschakeld omdat je notificaties hebt geweigerd. Achtergrondbetalingen zijn ingeschakeld. Je kunt geld ontvangen zelfs als de app gesloten is (als je apparaat verbonden is met internet). @@ -646,6 +653,8 @@ Bedragen weergeven in Tip: Wissel snel tussen Bitcoin en {currency} door op je wallet-saldo te tikken. Standaardeenheid + Interface + Betalingen Algemeen Systeeminstellingen Taal @@ -671,6 +680,9 @@ Veeg over saldo om te verbergen Gebruik {biometryTypeName} in plaats daarvan Waarschuwen bij verzenden boven $100 + Back-up of reset + Privacy + Veiligheid Beveiliging en privacy Instellingen Volledige backup niet voltooid @@ -712,10 +724,17 @@ Ondersteuning Succesvol verzonden Verzenden mislukt + Bitkit is gemaakt door Synonym Software, S.A. DE C.V. ©2025. Alle rechten voorbehouden. Ondersteuning Widgets Widgettitels tonen Widgets + Suggestiekaarten resetten + Widgets resetten + Weet je zeker dat je de widgets wilt resetten? De standaard widgetset met standaardconfiguraties wordt weergegeven. + Widgets resetten? + Weergave + Standaardinstellingen herstellen Beheer je\n<accent>profiel</accent> Stel je openbare profiel en links in, zodat je Bitkit-contacten je kunnen bereiken of betalen, altijd en overal. Profiel diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index cce1909e9..ad72a04c2 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -255,6 +255,9 @@ Saldo portfela Proszę poczekać, aż Bitkit połączy się z siecią płatności (±10 sekund). Podłączanie i synchronizacja... + Rozpocznij + Skonfiguruj saldo wydatków, aby cieszyć się natychmiastowymi i tanimi transakcjami ze znajomymi, rodziną i sprzedawcami. + Włącz\nLightning\n<accent>płatności</accent> Proszę czekać, trwa migracja danych starego portfela do nowej wersji Bitkit. Zwykle trwa to mniej niż minutę. Migracja portfela MIGRACJA\n<accent>PORTFELA</accent> @@ -513,6 +516,7 @@ Użyj kodu PIN Usunięto dane portfela Bitkit został zresetowany, a wszystkie dane portfela zostały usunięte. + Włącz PIN Ustawienia Opcje Dev włączone Opcje deweloperskie są teraz dostępne w całej aplikacji. @@ -551,9 +555,17 @@ ₿ {feeSats} dla przeciętnej transakcji ({fiatSymbol}{fiatFormatted} ) Tagi Poprzednio używane tagi + Interfejs + Płatności Widgety Widgety Pokazuj tytuły widgetów + Resetuj karty sugestii + Resetuj widgety + Czy na pewno chcesz zresetować widgety? Zostanie wyświetlony domyślny zestaw widgetów z domyślnymi konfiguracjami. + Resetować widgety? + Wyświetlanie + Przywróć domyślne QuickPay <accent>Bezproblemowe</accent>\npłatności Bitkit QuickPay przyspiesza płatności, automatycznie opłacając zeskanowane kody QR. @@ -572,6 +584,9 @@ Wyłączone Użyj {biometryTypeName} wzamian Po włączeniu tej opcji możesz używać {biometryTypeName} zamiast kodu PIN do odblokowania portfela lub wysyłania płatności. + Kopia zapasowa lub reset + Prywatność + Bezpieczeństwo Utwórz kopię zapasową swojego portfela Resetowanie i przywracanie portfela Błąd tworzenia kopii zapasowej danych @@ -588,6 +603,7 @@ Profil Kontakty W toku + Kopie zapasowe danych Dostosuj w ustawieniach Bitkit na Androidzie Płatności w tle są wyłączone, ponieważ odmówiono powiadomień. Płatności w tle są włączone. Możesz otrzymywać środki nawet gdy aplikacja jest zamknięta (jeśli Twoje urządzenie jest połączone z internetem). @@ -625,6 +641,7 @@ Nie udało się wysłać Coś poszło nie tak podczas próby wysłania Państwa pytania. Proszę spróbować ponownie. Spróbuj ponownie + Bitkit został stworzony przez Synonym Software, S.A. DE C.V. ©2025. Wszelkie prawa zastrzeżone. Status aplikacji Internet Połączono @@ -673,6 +690,8 @@ Węzeł Lightning Serwer Electrum Rapid-Gossip-Sync + Auto + Debug Szybko (drożej) Szybko ± 10-20 minut diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 16a03135d..8bb66e6d9 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -255,6 +255,9 @@ Conectando e Sincronizando... Por favor, espere que a Bitkit se conecte à rede de pagamento (±10 segundos). Saldos da Carteira + Começar + Configure seu saldo de gastos para aproveitar transações instantâneas e baratas com amigos, familiares e comerciantes. + Ativar\nLightning\n<accent>pagamentos</accent> Aguarde enquanto os dados da sua carteira antiga são migrados para esta nova versão da Bitkit. Isso geralmente leva menos de um minuto. Migração da Carteira MIGRANDO\n<accent>CARTEIRA</accent> @@ -513,6 +516,7 @@ Limpar Aplicativo O Bitkit foi redefinido e todos os dados da carteira foram excluídos. Dados da Carteira Deletados + Ativar PIN Legal Compartilhar Mude de carteira, mude o mundo. Baixe a Bitkit para iPhone {appStoreUrl} ou Android {playStoreUrl} @@ -557,6 +561,8 @@ Outros Pagamentos Resetar Sugestões + Auto + Debug Avançado Detalhes da Conexão Conexões @@ -574,6 +580,7 @@ Executando Último backup: {time} Faça backup da sua carteira + Backups de dados Personalizar nas Configurações do Bitkit Android Pagamentos em segundo plano estão desativados porque você negou notificações. Pagamentos em segundo plano estão ativados. Você pode receber fundos mesmo quando o app está fechado (se seu dispositivo estiver conectado à internet). @@ -646,6 +653,8 @@ Exibir valores em Dica: Alterne rapidamente entre Bitcoin e {currency} tocando no saldo de sua carteira. Unidade padrão + Interface + Pagamentos Geral Configurações do sistema Idioma @@ -671,6 +680,9 @@ Arraste o saldo para ocultar Usar {biometryTypeName} Avisar ao enviar mais de US$ 100 + Backup ou redefinir + Privacidade + Segurança Segurança e Privacidade Ajustes Falha ao concluir backup @@ -712,10 +724,17 @@ Suporte Enviado com Sucesso Falha no Envio + Bitkit foi criado pela Synonym Software, S.A. DE C.V. ©2025. Todos os direitos reservados. Suporte Widgets Mostrar Títulos dos Widgets Widgets + Redefinir cartões de sugestões + Redefinir widgets + Tem certeza de que deseja redefinir os widgets? O conjunto padrão de widgets com configurações padrão será exibido. + Redefinir widgets? + Exibição + Redefinir padrões Tenha seu próprio\n<accent>perfil</accent> Configure seu perfil público e seus links, para que seus contatos possam pagá-lo a qualquer hora e em qualquer lugar. Perfil diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 7b3b9ac9d..dcf964b10 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -255,6 +255,9 @@ Altura do Arquivo do Monitor de Canal Algumas conexões não puderam ser fechadas. TRANSFERÊNCIA EM ANDAMENTO + Começar + Configure o seu saldo de despesas para desfrutar de transações instantâneas e económicas com amigos, familiares e comerciantes. + Ativar\nLightning\n<accent>pagamentos</accent> Por favor aguarde enquanto seus dados da carteira antiga migram para esta nova versão da Bitkit. Isso geralmente leva menos de um minuto. Migração da Carteira MIGRANDO\n<accent>CARTEIRA</accent> @@ -512,6 +515,7 @@ Usar o código PIN Dados da Carteira Deletados O Bitkit foi redefinido e todos os dados da carteira foram excluídos. + Ativar PIN Ajustes Modo Desenvolvedor Ativado As opções de desenvolvedor estão agora habilitadas em todo o aplicativo. @@ -550,9 +554,17 @@ ₿ {feeSats} para uma transação média ({fiatSymbol}{fiatFormatted} ) Tags Tags usadas anteriormente + Interface + Pagamentos Widgets Widgets Mostrar Títulos dos Widgets + Repor cartões de sugestões + Repor widgets + Tem certeza de que deseja repor os widgets? O conjunto padrão de widgets com configurações padrão será exibido. + Repor widgets? + Apresentação + Repor predefinições QuickPay Pagamentos\n<accent>Sem atrito</accent> O Bitkit QuickPay agiliza o check-out pagando automaticamente os códigos QR quando escaneados. @@ -571,6 +583,9 @@ Desativado Usar {biometryTypeName} Quando ativado, você poderá usar {biometryTypeName} em vez do PIN para desbloquear ou pagar. + Cópia de segurança ou repor + Privacidade + Segurança Faça backup da sua carteira Reiniciar e restaurar carteira Falha no Backup @@ -587,6 +602,7 @@ Tags Perfil Contatos + Cópias de segurança Suporte Precisa de ajuda? Relate seu problema pela Bitkit, visite a central de ajuda, verifique o status ou entre em contato conosco em nossos canais sociais. Relatar problema @@ -605,6 +621,7 @@ Algo deu errado ao enviar seu problema ou pergunta. Por favor, tente novamente. Tente novamente Falha ao abrir links de suporte + Bitkit foi criado pela Synonym Software, S.A. DE C.V. ©2025. Todos os direitos reservados. Status do Aplicativo Internet Conectado @@ -653,6 +670,8 @@ Nó Lightning Servidor Electrum Servidor Rapid-Gossip-Sync + Auto + Debug Rápido (mais caro) Rápido ± 10-20 minutos diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a97bec793..56e7f7acf 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -267,6 +267,9 @@ Подключение и Синхронизация... Пожалуйста, подождите, пока Bitkit подключается к платёжной сети (±10 секунд). Балансы кошелька + Начать + Настройте баланс расходов, чтобы наслаждаться мгновенными и дешёвыми транзакциями с друзьями, семьёй и продавцами. + Включить\nLightning\n<accent>платежи</accent> Пожалуйста, подождите, пока данные вашего старого кошелька переносятся в эту новую версию Bitkit. Обычно это занимает меньше минуты. Миграция кошелька МИГРАЦИЯ @@ -527,6 +530,7 @@ Сбросить Приложение Bitkit был сброшен, и все данные кошелька были удалены. Данные Кошелька Удалены + Включить PIN Документы Поделиться Измените свой кошелёк, измените мир. Скачайте Bitkit для iPhone {appStoreUrl} или Android {playStoreUrl} @@ -571,6 +575,8 @@ Другое Платежи Сбросить Рекомендации + Авто + Отладка Дополнительно Квитанции соединений Соединения @@ -588,6 +594,7 @@ Выполняется Последнее Резервное Копирование: {time} Создать Резервную Копию Кошелька + Резервные копии данных Настроить в настройках Android Bitkit Фоновые платежи отключены, потому что вы отклонили уведомления. Фоновые платежи включены. Вы можете получать средства, даже когда приложение закрыто (если ваше устройство подключено к интернету). @@ -661,6 +668,8 @@ Отображать суммы в Совет: Быстро переключайтесь между биткойнами и {currency}, нажимая на баланс вашего кошелька. Единица Измерения по Умолчанию + Интерфейс + Платежи Основные Системные настройки Язык @@ -687,6 +696,9 @@ Смахнуть Баланс, чтобы Скрыть Использовать {biometryTypeName} вместо этого Предупреждать при Отправке более $100 + Резервное копирование или сброс + Конфиденциальность + Безопасность Безопасность и Конфиденциальность Настройки Не удалось выполнить полное резервное копирование @@ -728,10 +740,17 @@ Поддержка Успешно Отправлено Не Удалось Отправить + Bitkit создан компанией Synonym Software, S.A. DE C.V. ©2025. Все права защищены. Поддержка Виджеты Показывать Заголовки Виджетов Виджеты + Сбросить карточки предложений + Сбросить виджеты + Вы уверены, что хотите сбросить виджеты? Будет отображён набор виджетов по умолчанию с настройками по умолчанию. + Сбросить виджеты? + Отображение + Сбросить к настройкам по умолчанию Владейте своим\n<accent>профилем</accent> Настройте свой публичный профиль и ссылки, чтобы ваши контакты Bitkit могли связаться с вами или заплатить вам в любое время и в любом месте. Профиль