diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt index b504d0fa6..5b4d30333 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 @@ -152,11 +148,7 @@ 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.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 @@ -167,7 +159,9 @@ 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.DisablePinSheet import to.bitkit.ui.sheets.ForceTransferSheet import to.bitkit.ui.sheets.GiftSheet import to.bitkit.ui.sheets.HighBalanceWarningSheet @@ -398,6 +392,8 @@ fun ContentView( is Sheet.ActivityDateRangeSelector -> DateRangeSelectorSheet() 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) @@ -527,16 +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) - changePin(navController) - changePinNew(navController) - changePinConfirm(navController) - changePinResult(navController) + pinManagement(navController) defaultUnitSettings(currencyViewModel, navController) localCurrencySettings(currencyViewModel, navController) backupSettings(navController) @@ -972,11 +962,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 +986,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 +1007,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,43 +1016,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.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.pinManagement(navController: NavHostController) { + composableWithDefaultTransitions { + PinManagementScreen(navController) } } @@ -1517,21 +1456,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.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.navigateToPinManagement() = navigateTo(Routes.PinManagement) fun NavController.navigateToAuthCheck( showLogoOnPin: Boolean = false, @@ -1592,9 +1517,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 +1536,6 @@ sealed interface Routes { @Serializable data object NodeInfo : Routes - @Serializable - data object GeneralSettings : Routes - @Serializable data object TransactionSpeedSettings : Routes @@ -1626,9 +1545,6 @@ sealed interface Routes { @Serializable data object TagsSettings : Routes - @Serializable - data object AdvancedSettings : Routes - @Serializable data object CoinSelectPreference : Routes @@ -1644,29 +1560,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 - - @Serializable - data object ChangePin : Routes - - @Serializable - data object ChangePinNew : Routes - - @Serializable - data class ChangePinConfirm(val newPin: String) : Routes - - @Serializable - data object ChangePinResult : Routes + data object PinManagement : Routes @Serializable data class AuthCheck( 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..d24631209 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_chats_circle, + 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/SheetHost.kt b/app/src/main/java/to/bitkit/ui/components/SheetHost.kt index eabfcca0b..e1e7e4396 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,8 @@ 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 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/components/settings/Links.kt b/app/src/main/java/to/bitkit/ui/components/settings/Links.kt index 94728fc37..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 @@ -1,12 +1,12 @@ 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.platform.LocalContext @@ -15,99 +15,39 @@ 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) + +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 = Colors.White16, - 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 = Colors.White16, - 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 = Colors.White16, - 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 = Colors.White16, - 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 = Colors.White16, - 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 = Colors.White16, - 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), + ) + } } } } 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..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 @@ -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, @@ -119,7 +183,7 @@ fun SettingsButtonRow( } 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), 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/components/settings/SettingsSwitchRow.kt b/app/src/main/java/to/bitkit/ui/components/settings/SettingsSwitchRow.kt index 2b512440f..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 @@ -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,62 @@ fun SettingsSwitchRow( onClick: () -> Unit, modifier: Modifier = Modifier, subtitle: String? = null, + 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( @@ -42,6 +105,10 @@ fun SettingsSwitchRow( .clickableAlpha { onClick() } .padding(vertical = 16.dp) ) { + if (icon != null) { + icon() + } + Column( modifier = Modifier .weight(1f) @@ -79,6 +146,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/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) ) } 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..8f668384c 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,692 @@ 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.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.mutableIntStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.runtime.rememberCoroutineScope 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 kotlinx.coroutines.launch 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.SettingsIcon +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.theme.Colors +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 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() + 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, + showWidgets = showWidgets, + tagCount = lastUsedTags.size, + isQuickPayEnabled = isQuickPayEnabled, + 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 = "", + showWidgets: Boolean = true, + tagCount: Int = 0, + isQuickPayEnabled: 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 = {}, + initialTab: SettingsTab = SettingsTab.General, ) { + val tabs = remember { SettingsTab.entries } + val pagerState = rememberPagerState( + initialPage = tabs.indexOf(initialTab), + pageCount = { tabs.size }, + ) + val scope = rememberCoroutineScope() + 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") - ) - SettingsButtonRow( - title = stringResource(R.string.settings__backup_title), - iconRes = R.drawable.ic_settings_backup, - onClick = onBackupClick, - modifier = Modifier.testTag("BackupSettings") - ) - SettingsButtonRow( - title = stringResource(R.string.settings__advanced_title), - iconRes = R.drawable.ic_settings_advanced, - onClick = onAdvancedClick, - modifier = Modifier.testTag("AdvancedSettings") - ) + + CustomTabRowWithSpacing( + tabs = tabs, + currentTabIndex = pagerState.currentPage, + selectedColor = Colors.White, + onTabChange = { scope.launch { pagerState.animateScrollToPage(tabs.indexOf(it)) } }, + modifier = Modifier.padding(horizontal = 16.dp), + ) + + HorizontalPager(state = pagerState) { page -> + when (tabs[page]) { + SettingsTab.General -> GeneralTabContent( + selectedCurrency = selectedCurrency, + primaryDisplay = primaryDisplay, + defaultTransactionSpeed = defaultTransactionSpeed, + selectedLanguage = selectedLanguage, + showWidgets = showWidgets, + tagCount = tagCount, + isQuickPayEnabled = isQuickPayEnabled, + 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.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, + showWidgets: Boolean, + tagCount: Int, + isQuickPayEnabled: 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), + 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), + icon = { SettingsIcon(R.drawable.ic_coins) }, + value = SettingsButtonValue.StringValue(selectedCurrency), + onClick = onLocalCurrencyClick, + modifier = Modifier.testTag("CurrenciesSettings"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__general__unit), + icon = { SettingsIcon(R.drawable.ic_bitcoin_modern) }, + 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), + 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 (tagCount > 0) { 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), + icon = { SettingsIcon(R.drawable.ic_tag) }, + value = SettingsButtonValue.StringValue(tagCount.toString()), + 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), + 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"), + ) + 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"), + ) + SettingsButtonRow( + 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) + ), + 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), + icon = { SettingsIcon(R.drawable.ic_lock_key) }, + onClick = onBackupWalletClick, + modifier = Modifier.testTag("BackupWallet"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__backup__data), + icon = { SettingsIcon(R.drawable.ic_database) }, + onClick = onDataBackupsClick, + modifier = Modifier.testTag("BackupSettings"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__backup__reset), + icon = { SettingsIcon(R.drawable.ic_arrow_counter_clockwise) }, + 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), + icon = { SettingsIcon(R.drawable.ic_shield) }, + 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), + icon = { SettingsIcon(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) + }, + icon = { SettingsIcon(R.drawable.ic_smiley) }, + 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), + icon = { SettingsIcon(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), + 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), + icon = { SettingsIcon(R.drawable.ic_eye_slash) }, + isChecked = hideBalanceOnOpen, + onClick = onHideBalanceOnOpenClick, + modifier = Modifier.testTag("HideBalanceOnOpen"), + ) + SettingsSwitchRow( + title = stringResource(R.string.settings__security__clipboard), + icon = { SettingsIcon(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), + icon = { SettingsIcon(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), + icon = { SettingsIcon(R.drawable.ic_list_dashes) }, + 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), + icon = { SettingsIcon(R.drawable.ic_coins) }, + onClick = onCoinSelectionClick, + modifier = Modifier.testTag("CoinSelectPreference"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__adv__address_viewer), + icon = { SettingsIcon(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), + icon = { SettingsIcon(R.drawable.ic_lightning) }, + onClick = onLightningConnectionsClick, + modifier = Modifier.testTag("Channels"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__adv__lightning_node), + icon = { SettingsIcon(R.drawable.ic_git_branch) }, + onClick = onLightningNodeClick, + modifier = Modifier.testTag("LightningNodeInfo"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__adv__electrum_server), + icon = { SettingsIcon(R.drawable.ic_hard_drives) }, + onClick = onElectrumServerClick, + modifier = Modifier.testTag("ElectrumConfig"), + ) + SettingsButtonRow( + title = stringResource(R.string.settings__adv__rgs_server), + icon = { SettingsIcon(R.drawable.ic_broadcast) }, + onClick = onRgsServerClick, + modifier = Modifier.testTag("RGSServer"), + ) + + VerticalSpacer(32.dp) + } +} + +@Preview(showBackground = true) +@Composable +private fun PreviewGeneral() { + AppThemeSurface { + SettingsContent(tagCount = 3) + } +} + +@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 Preview() { +private fun PreviewAdvanced() { AppThemeSurface { - SettingsScreenContent( + SettingsContent( isDevModeEnabled = true, - onGeneralClick = {}, - onSecurityClick = {}, - onBackupClick = {}, - onAdvancedClick = {}, - onSupportClick = {}, - onAboutClick = {}, - onDevClick = {}, - onCogTap = {}, - onBackClick = {}, + selectedAddressTypeName = "Taproot", + initialTab = SettingsTab.Advanced, ) } } 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/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 8913265f2..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/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..f288d16f3 --- /dev/null +++ b/app/src/main/java/to/bitkit/ui/settings/pin/PinManagementScreen.kt @@ -0,0 +1,152 @@ +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.appViewModel +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.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 = { app.showSheet(Sheet.ChangePin) }, + onDisablePinClick = { app.showSheet(Sheet.DisablePin) }, + 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_enabled_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_enabled_text + } else { + R.string.security__pin_security_text + } + ), + color = Colors.White64, + ) + + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) { + Image( + painter = if (isPinEnabled) { + painterResource(R.drawable.shield_check) + } else { + 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..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 @@ -2,39 +2,75 @@ 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.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 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 +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.FillHeight +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.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( 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 +80,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 +130,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,37 +144,148 @@ private fun Content( ) Column( - modifier = Modifier.padding(horizontal = 16.dp) + modifier = Modifier + .weight(1f) + .verticalScroll(rememberScrollState()) ) { - Spacer(modifier = Modifier.height(32.dp)) + // Padded content — setting rows + Column(modifier = Modifier.padding(horizontal = 16.dp)) { + VerticalSpacer(16.dp) - BodyM(text = stringResource(R.string.settings__support__text), color = Colors.White64) + 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__status), - onClick = onClickAppStatus, - modifier = Modifier.testTag("AppStatus") - ) + 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, + ) - Image( - painter = painterResource(R.drawable.question_mark), - contentDescription = null, - modifier = Modifier - .fillMaxWidth() - .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) - Links(modifier = Modifier.fillMaxWidth()) + FillHeight() - Spacer(modifier = Modifier.height(16.dp)) + SupportFooter() } } } +@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"), + ) + } + + // 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(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() { @@ -100,3 +293,11 @@ private fun Preview() { Content() } } + +@Preview(showSystemUi = true, device = PIXEL_TABLET) +@Composable +private fun PreviewTablet() { + AppThemeSurface { + Content() + } +} 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..dc007bda6 --- /dev/null +++ b/app/src/main/java/to/bitkit/ui/sheets/ChangePinSheet.kt @@ -0,0 +1,443 @@ +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 +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.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.composableWithDefaultTransitions +import to.bitkit.ui.utils.withAccentBoldBright +import to.bitkit.viewmodels.AppViewModel + +private val NumberPadHeight = 350.dp + +@Suppress("CyclomaticComplexMethod") +@Composable +fun ChangePinSheet(app: AppViewModel) { + val navController = rememberNavController() + val onDismiss = app::hideSheet + + Column( + modifier = Modifier + .fillMaxWidth() + .sheetHeight(SheetSize.MEDIUM) + ) { + 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 + + Column( + modifier = Modifier + .fillMaxWidth() + .gradientBackground() + .navigationBarsPadding() + .testTag("ChangePIN"), + ) { + SheetTopBar( + titleText = stringResource(R.string.security__cp_title), + onBack = onBackClick, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + BodyM( + text = stringResource(R.string.security__cp_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), + ) + } +} + +@Composable +private fun NewPinContent( + pin: String, + onKeyPress: (String) -> Unit, + onBackClick: () -> Unit, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .gradientBackground() + .navigationBarsPadding() + .testTag("ChangePIN2"), + ) { + SheetTopBar( + titleText = stringResource(R.string.security__cp_setnew_title), + onBack = onBackClick, + ) + + 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.weight(1f)) + + PinDots(pin = pin) + + Spacer(modifier = Modifier.height(32.dp)) + + NumberPad( + onPress = onKeyPress, + type = NumberPadType.SIMPLE, + modifier = Modifier + .height(NumberPadHeight) + .background(Colors.Black), + ) + } +} + +@Composable +private fun ConfirmContent( + pin: String, + showError: Boolean, + onKeyPress: (String) -> Unit, + onBackClick: () -> Unit, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .gradientBackground() + .navigationBarsPadding() + .testTag("ChangePIN2"), + ) { + SheetTopBar( + titleText = stringResource(R.string.security__cp_retype_title), + onBack = onBackClick, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + BodyM( + text = stringResource(R.string.security__cp_retype_text), + color = Colors.White64, + modifier = Modifier.padding(horizontal = 32.dp), + ) + + 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"), + ) + } + + Spacer(modifier = Modifier.weight(1f)) + + PinDots(pin = pin) + + Spacer(modifier = Modifier.height(32.dp)) + + NumberPad( + onPress = onKeyPress, + type = NumberPadType.SIMPLE, + modifier = Modifier + .height(NumberPadHeight) + .background(Colors.Black), + ) + } +} + +@Composable +private fun ResultContent( + onOkClick: () -> Unit, + onBackClick: () -> Unit, +) { + Column( + modifier = Modifier + .fillMaxWidth() + .gradientBackground() + .navigationBarsPadding(), + ) { + SheetTopBar( + titleText = stringResource(R.string.security__cp_changed_title) + ) + + Spacer(modifier = Modifier.height(16.dp)) + + BodyM( + text = stringResource(R.string.security__cp_changed_text), + color = Colors.White64, + modifier = Modifier.padding(horizontal = 32.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)) + } +} + +@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 = {}, + ) + } +} 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/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/drawable-hdpi/shield_check.webp b/app/src/main/res/drawable-hdpi/shield_check.webp new file mode 100644 index 000000000..a5f515529 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/shield_check.webp differ diff --git a/app/src/main/res/drawable-mdpi/shield_check.webp b/app/src/main/res/drawable-mdpi/shield_check.webp new file mode 100644 index 000000000..09fdccb82 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/shield_check.webp differ diff --git a/app/src/main/res/drawable-xhdpi/shield_check.webp b/app/src/main/res/drawable-xhdpi/shield_check.webp new file mode 100644 index 000000000..5a978350f Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/shield_check.webp differ diff --git a/app/src/main/res/drawable-xxhdpi/shield_check.webp b/app/src/main/res/drawable-xxhdpi/shield_check.webp new file mode 100644 index 000000000..1d9baab4d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/shield_check.webp differ 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 000000000..706c8979d Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/shield_check.webp differ 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_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_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_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 @@ - - - - - - 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_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 @@ + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index c422599da..231032049 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -437,11 +437,11 @@ تواصل مع الدعم لقد غيّرت رمز PIN بنجاح إلى تركيبة جديدة من 4 أرقام. تم تغيير PIN - يرجى إعادة كتابة رمز PIN المكون من 4 أرقام لإكمال عملية الإعداد. + أعد كتابة رقم PIN المكون من 4 أرقام لإكمال الإعداد وحفظ رقم PIN الجديد. أعد كتابة PIN الجديد يرجى استخدام رمز PIN ستتذكره. إذا نسيت رمز PIN، يمكنك إعادة تعيينه، لكن ذلك سيتطلب استعادة محفظتك. تعيين PIN جديد - يمكنك تغيير رمز PIN إلى تركيبة\nجديدة من 4 أرقام. يرجى إدخال رمز PIN الحالي أولاً. + أدخل رقم PIN <accent>الحالي</accent> لتغييره. تغيير PIN حاول مرة أخرى، هذا ليس نفس رمز PIN. عرض عبارة الاسترداد @@ -477,8 +477,9 @@ اختر رمز PIN من 4 أرقام يرجى استخدام رمز PIN ستتذكره. إذا نسيت رمز PIN، يمكنك إعادة تعيينه، لكن ذلك سيتطلب استعادة محفظتك. تعطيل PIN - رمز PIN مفعل حاليًا. إذا أردت تعطيل رمز PIN، تحتاج إلى إدخال رمز PIN الحالي أولاً. - تعطيل PIN + أدخل رقم <accent>PIN</accent> لتعطيله. + رمز PIN مفعل حالياً. إذا كنت تريد تعطيله أو تغييره، يجب إدخال رمز PIN الحالي أولاً. + PIN مفعل يرجى إدخال رمز PIN إعادة التعيين (يتطلب عبارة الاسترداد) نسيت رمز PIN؟ أعد التعيين واستعد محفظة Bitkit باستخدام عبارة الاسترداد. عيّن رمز PIN جديد بعد إكمال الاستعادة. @@ -664,7 +665,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..84aec5456 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -437,11 +437,11 @@ 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 - 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 @@ -477,8 +477,9 @@ 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. - Desactivar PIN + 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) ¿Olvidaste tu PIN? Restablece y recupera tu billetera de Bitkit con tu frase de recuperación. Establece un nuevo PIN al finalizar. @@ -664,7 +665,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..ed1cc092d 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -437,11 +437,11 @@ 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 - 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ó @@ -477,8 +477,9 @@ 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. - Desactiva el PIN + 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ó) 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ó. @@ -664,7 +665,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..66d5b2c99 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -437,11 +437,11 @@ 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 - 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 @@ -477,8 +477,9 @@ 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. - Zakázat PIN + 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) 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. @@ -664,7 +665,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..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. - PIN deaktivieren - Der PIN-Code ist derzeit aktiviert. Wenn du deinen PIN deaktivieren möchtest, musst du zuerst deinen aktuellen PIN-Code eingeben. + Der PIN-Code ist derzeit aktiviert. Wenn du ihn deaktivieren oder ändern möchtest, musst du zuerst deinen aktuellen PIN-Code eingeben. + PIN aktiviert + 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. @@ -500,9 +501,9 @@ 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. + 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. @@ -583,7 +584,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..66ec26b43 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -437,11 +437,11 @@ Επικοινωνία με υποστήριξη Άλλαξες επιτυχώς το PIN σου σε νέο συνδυασμό 4 ψηφίων. Το PIN άλλαξε - Πληκτρολόγησε ξανά το 4ψήφιο PIN για να ολοκληρώσεις τη διαδικασία ρύθμισης. + Πληκτρολόγησε ξανά το 4ψήφιο PIN για να ολοκληρώσεις τη ρύθμιση και να αποθηκεύσεις το νέο PIN. Επανάληψη νέου PIN Χρησιμοποίησε ένα PIN που θα θυμάσαι. Αν ξεχάσεις το PIN σου μπορείς να το επαναφέρεις, αλλά αυτό θα απαιτήσει επαναφορά του πορτοφολιού σου. Ορισμός νέου PIN - Μπορείς να αλλάξεις τον κωδικό PIN σου σε νέο\nσυνδυασμό 4 ψηφίων. Εισάγαγε πρώτα τον τρέχοντα κωδικό PIN. + Εισαγάγετε το <accent>τρέχον</accent> PIN σας για να το αλλάξετε. Αλλαγή PIN Δοκίμασε ξανά, δεν είναι το ίδιο PIN. Εμφάνιση φράσης seed @@ -477,8 +477,9 @@ Επίλεξε 4ψήφιο PIN Χρησιμοποίησε ένα PIN που θα θυμάσαι. Αν ξεχάσεις το PIN σου μπορείς να το επαναφέρεις, αλλά αυτό θα απαιτήσει επαναφορά του πορτοφολιού σου. Απενεργοποίηση PIN - Ο κωδικός PIN είναι ενεργοποιημένος. Αν θέλεις να απενεργοποιήσεις το PIN, πρέπει να εισάγεις πρώτα τον τρέχοντα κωδικό PIN. - Απενεργοποίηση PIN + Εισαγάγετε το <accent>PIN</accent> σας για να το απενεργοποιήσετε. + Ο κωδικός PIN είναι ενεργοποιημένος. Αν θέλετε να τον απενεργοποιήσετε ή να τον αλλάξετε, πρέπει πρώτα να εισαγάγετε τον τρέχοντα κωδικό PIN. + PIN ενεργοποιημένο Εισάγαγε τον κωδικό PIN Επαναφορά (Απαιτείται φράση ανάκτησης) Ξέχασες το PIN; Επανάφερε και ανάκτησε το πορτοφόλι Bitkit με τη φράση ανάκτησής σου. Όρισε νέο PIN μετά την ολοκλήρωση της ανάκτησης. @@ -664,7 +665,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..cf3cc0658 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -437,11 +437,11 @@ 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 - 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 @@ -477,8 +477,9 @@ 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. - Desactivar PIN + 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) ¿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. @@ -664,7 +665,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..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. - Desactivar PIN - El código PIN está actualmente activado. Si desea desactivar su PIN, deberá introducir primero su código PIN actual. + El código PIN está actualmente activado. Si desea desactivarlo o cambiarlo, primero debe introducir su código PIN actual. + PIN activado + 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. @@ -500,9 +501,9 @@ 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. + 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. @@ -559,7 +560,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..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. - Désactiver le code PIN - Le code PIN est actuellement activé. Si vous souhaitez désactiver votre code PIN, vous devez d\'abord saisir votre code PIN actuel. + 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é + 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é. @@ -499,10 +500,10 @@ Afficher la phrase de récupération. Contacter le support Effacer l\'application - Modifier le code PIN - Vous pouvez remplacer votre code PIN par une nouvelle\ncombinaison de 4 chiffres. Veuillez d\'abord introduire votre code PIN actuel. + 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. @@ -566,7 +567,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..b7af8bc88 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -437,11 +437,11 @@ 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 - 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 @@ -477,8 +477,9 @@ 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. - Disabilita il PIN + 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) Ha dimenticato il PIN? Resetta e recupera il tuo wallet Bitkit con la recovery phrase. Imposta un nuovo PIN dopo aver completato il recupero. @@ -664,7 +665,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..4c7a0f8c5 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -437,12 +437,12 @@ 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 - Je kunt je pincode wijzigen naar een nieuwe\n4-cijferige combinatie. Voer eerst je huidige pincode in. - Pincode wijzigen + Voer je <accent>huidige</accent> PIN in om deze te wijzigen. + PIN wijzigen Probeer opnieuw, dit is niet dezelfde pincode. Herstelzin tonen Herstelzin bevestigen @@ -477,8 +477,9 @@ 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. - Pincode uitschakelen + 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) Pincode vergeten? Reset en herstel je Bitkit-wallet met je herstelzin. Stel een nieuwe pincode in na het voltooien van het herstel. @@ -664,7 +665,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..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ą. - Wyłącz kod PIN - Kod PIN jest obecnie aktywny. Jeśli chcesz go wyłączyć pierw musisz podać aktualny kod PIN. + Kod PIN jest obecnie włączony. Jeśli chcesz go wyłączyć lub zmienić, najpierw wprowadź aktualny kod PIN. + PIN włączony + 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. @@ -499,10 +500,10 @@ Pokaż frazę odzyskiwania Skontaktuj się z pomocą techniczną Wyczyść aplikację - Zmień kod PIN - Możesz zmienić swój kod PIN na nową\n4-cyfrową kombinację. Najpierw wprowadź aktualny kod PIN. + 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ą. @@ -565,7 +566,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..47f9434f7 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -437,11 +437,11 @@ 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 - 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 @@ -477,8 +477,9 @@ 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. - Desativar PIN + 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) 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. @@ -664,7 +665,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..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. - Desativar PIN - O código PIN está ativado no momento. Se quiser desativar o PIN, você precisará, primeiro, digitar o código PIN atual. + O código PIN está ativado. Se deseja desativá-lo ou alterá-lo, primeiro digite seu código PIN atual. + PIN ativado + 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. @@ -499,9 +500,9 @@ 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. + 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. @@ -564,7 +565,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..c9222812c 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -451,11 +451,11 @@ Связаться с Поддержкой Вы успешно изменили свой PIN-код на новую комбинацию из 4 цифр. PIN-код Изменён - Повторно введите 4-значный PIN-код, чтобы завершить процесс установки. + Повторно введите 4-значный PIN, чтобы завершить настройку и сохранить новый PIN. Повторите Новый PIN-код Пожалуйста, используйте PIN-код, который вы запомните. Если вы забудете свой PIN-код, вы можете сбросить его, но для этого потребуется восстановить кошелёк. Новый PIN-код - Вы можете изменить свой PIN-код на новую\nчетырёхзначную комбинацию. Сначала введите текущий PIN-код. + Введите <accent>текущий</accent> PIN, чтобы изменить его. Изменить PIN-код Попробуйте ещё раз, это не тот PIN-код. Показать Фразу Восстановления @@ -491,8 +491,9 @@ Выберите 4-значный PIN-код Пожалуйста, используйте PIN-код, который вы запомните. Если вы забудете свой PIN-код, вы можете сбросить его, но для этого потребуется восстановить кошелёк. Отключить PIN-код - PIN-код в настоящее время включен. Если вы хотите отключить свой PIN-код, вам необходимо сначала ввести текущий PIN-код. - Отключить PIN-код + Введите <accent>PIN</accent>, чтобы отключить его. + PIN-код в настоящее время включён. Если вы хотите отключить или изменить PIN, сначала введите текущий PIN-код. + PIN включён Пожалуйста, введите PIN-код Сбросить (требуется Фраза Восстановления) Забыли свой PIN-код? Сбросьте и восстановите свой кошелёк Bitkit с помощью фразы восстановления. Установите новый PIN-код после завершения восстановления. @@ -680,7 +681,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 5e85b445b..692c88afc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -444,11 +444,11 @@ 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 - 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 @@ -484,8 +484,10 @@ 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 - PIN code is currently enabled. If you want to disable your PIN, you need to enter your current PIN code first. - Disable PIN + Enable PIN + 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) Forgot your PIN? Reset and recover your Bitkit wallet with your recovery phrase. Set a new PIN after completing recovery. @@ -570,6 +572,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 +590,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 +657,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,17 +686,20 @@ 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 - Change PIN Code + Change PIN Disabled 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 +731,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 +744,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 +864,7 @@ Profile Settings Shop + Support App Status Wallet Widgets