Skip to content

Commit 9568cb3

Browse files
committed
feat: add hardware wallets settings screen
1 parent b43897d commit 9568cb3

12 files changed

Lines changed: 487 additions & 76 deletions

File tree

app/src/main/java/to/bitkit/ui/ContentView.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ import to.bitkit.ui.settings.backgroundPayments.BackgroundPaymentsIntroScreen
162162
import to.bitkit.ui.settings.backgroundPayments.BackgroundPaymentsSettings
163163
import to.bitkit.ui.settings.backups.ResetAndRestoreScreen
164164
import to.bitkit.ui.settings.general.DefaultUnitSettingsScreen
165+
import to.bitkit.ui.settings.general.HardwareWalletsSettingsScreen
165166
import to.bitkit.ui.settings.general.LocalCurrencySettingsScreen
166167
import to.bitkit.ui.settings.general.TagsSettingsScreen
167168
import to.bitkit.ui.settings.general.WidgetsSettingsScreen
@@ -660,7 +661,7 @@ private fun RootNavHost(
660661
contacts(navController, settingsViewModel, appViewModel)
661662
profile(navController, settingsViewModel)
662663
shop(navController, settingsViewModel, appViewModel)
663-
generalSettingsSubScreens(navController, settingsViewModel)
664+
generalSettingsSubScreens(navController, appViewModel, settingsViewModel)
664665
advancedSettingsSubScreens(navController)
665666
transactionSpeedSettings(navController)
666667
pinManagement(navController)
@@ -1371,6 +1372,7 @@ private fun NavGraphBuilder.shop(
13711372

13721373
private fun NavGraphBuilder.generalSettingsSubScreens(
13731374
navController: NavHostController,
1375+
appViewModel: AppViewModel,
13741376
settingsViewModel: SettingsViewModel,
13751377
) {
13761378
composableWithDefaultTransitions<Routes.WidgetsSettings> {
@@ -1380,6 +1382,12 @@ private fun NavGraphBuilder.generalSettingsSubScreens(
13801382
composableWithDefaultTransitions<Routes.TagsSettings> {
13811383
TagsSettingsScreen(navController)
13821384
}
1385+
composableWithDefaultTransitions<Routes.HardwareWalletsSettings> {
1386+
HardwareWalletsSettingsScreen(
1387+
navController = navController,
1388+
onClickAdd = { appViewModel.showSheet(Sheet.Hardware()) },
1389+
)
1390+
}
13831391
composableWithDefaultTransitions<Routes.BackgroundPaymentsSettings> {
13841392
BackgroundPaymentsSettings(
13851393
onBack = { navController.popBackStack() },
@@ -1848,6 +1856,9 @@ sealed interface Routes {
18481856
@Serializable
18491857
data object TagsSettings : Routes
18501858

1859+
@Serializable
1860+
data object HardwareWalletsSettings : Routes
1861+
18511862
@Serializable
18521863
data object CoinSelectPreference : Routes
18531864

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package to.bitkit.ui.components
2+
3+
import androidx.compose.foundation.Image
4+
import androidx.compose.foundation.layout.BoxWithConstraints
5+
import androidx.compose.foundation.layout.BoxWithConstraintsScope
6+
import androidx.compose.foundation.layout.offset
7+
import androidx.compose.foundation.layout.size
8+
import androidx.compose.runtime.Composable
9+
import androidx.compose.ui.Alignment
10+
import androidx.compose.ui.Modifier
11+
import androidx.compose.ui.draw.BlurredEdgeTreatment
12+
import androidx.compose.ui.draw.blur
13+
import androidx.compose.ui.res.painterResource
14+
import androidx.compose.ui.unit.Dp
15+
import androidx.compose.ui.unit.dp
16+
import to.bitkit.R
17+
18+
// Device illustration proportions, taken from the Figma hardware wallet frames.
19+
private const val HW_DEVICE_IMAGE_SIZE_RATIO = 256f / 375f
20+
private const val HW_DEVICE_TREZOR_BLEED_RATIO = 84f / 375f
21+
private const val HW_DEVICE_LEDGER_BLEED_RATIO = 53f / 375f
22+
private const val HW_DEVICE_STAGGER_RATIO = 12f / 375f
23+
24+
@Composable
25+
fun HwDeviceIllustrations(modifier: Modifier = Modifier) {
26+
BoxWithConstraints(modifier) {
27+
val imageSize = maxWidth * HW_DEVICE_IMAGE_SIZE_RATIO
28+
val staggerY = maxWidth * HW_DEVICE_STAGGER_RATIO
29+
TrezorImage(imageSize = imageSize, staggerY = staggerY)
30+
LedgerImage(
31+
imageSize = imageSize,
32+
staggerY = staggerY,
33+
modifier = Modifier.blur(16.dp, BlurredEdgeTreatment.Unbounded)
34+
)
35+
}
36+
}
37+
38+
@Composable
39+
private fun BoxWithConstraintsScope.TrezorImage(
40+
imageSize: Dp,
41+
staggerY: Dp,
42+
modifier: Modifier = Modifier,
43+
) {
44+
Image(
45+
painter = painterResource(R.drawable.trezor),
46+
contentDescription = null,
47+
modifier = modifier
48+
.size(imageSize)
49+
.align(Alignment.CenterStart)
50+
.offset(x = -maxWidth * HW_DEVICE_TREZOR_BLEED_RATIO, y = staggerY)
51+
)
52+
}
53+
54+
@Composable
55+
private fun BoxWithConstraintsScope.LedgerImage(
56+
imageSize: Dp,
57+
staggerY: Dp,
58+
modifier: Modifier = Modifier,
59+
) {
60+
Image(
61+
painter = painterResource(R.drawable.ledger),
62+
contentDescription = null,
63+
modifier = modifier
64+
.size(imageSize)
65+
.align(Alignment.CenterEnd)
66+
.offset(x = maxWidth * HW_DEVICE_LEDGER_BLEED_RATIO, y = -staggerY)
67+
)
68+
}

app/src/main/java/to/bitkit/ui/screens/wallets/HardwareWalletScreen.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,13 @@ fun HardwareWalletScreen(
7676
if (wallet == null) onBackClick()
7777
}
7878

79-
wallet?.let {
79+
wallet?.let { device ->
8080
HardwareWalletContent(
81-
wallet = it,
82-
showRemoveDialog = uiState.showRemoveDialog,
81+
wallet = device,
82+
showRemoveDialog = uiState.isPendingRemoval != null,
8383
onActivityItemClick = onActivityItemClick,
8484
onTransferToSpendingClick = onTransferToSpendingClick,
85-
onRemoveClick = viewModel::onRemoveClick,
85+
onRemoveClick = { viewModel.onRemoveClick(device) },
8686
onConfirmRemove = { viewModel.removeDevice(deviceId) },
8787
onDismissRemoveDialog = viewModel::onDismissRemoveDialog,
8888
onBackClick = onBackClick,

app/src/main/java/to/bitkit/ui/screens/wallets/HwWalletViewModel.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ class HwWalletViewModel @Inject constructor(
3030
private val _uiState = MutableStateFlow(HwWalletDetailUiState())
3131
val uiState: StateFlow<HwWalletDetailUiState> = _uiState.asStateFlow()
3232

33-
fun onRemoveClick() = _uiState.update { it.copy(showRemoveDialog = true) }
33+
fun onRemoveClick(wallet: HwWallet) = _uiState.update { it.copy(isPendingRemoval = wallet) }
3434

35-
fun onDismissRemoveDialog() = _uiState.update { it.copy(showRemoveDialog = false) }
35+
fun onDismissRemoveDialog() = _uiState.update { it.copy(isPendingRemoval = null) }
3636

3737
fun removeDevice(deviceId: String) {
3838
viewModelScope.launch {
39-
_uiState.update { it.copy(showRemoveDialog = false) }
39+
_uiState.update { it.copy(isPendingRemoval = null) }
4040
hwWalletRepo.removeDevice(deviceId).onFailure {
4141
ToastEventBus.send(
4242
type = Toast.ToastType.ERROR,
@@ -50,5 +50,5 @@ class HwWalletViewModel @Inject constructor(
5050

5151
@Immutable
5252
data class HwWalletDetailUiState(
53-
val showRemoveDialog: Boolean = false,
53+
val isPendingRemoval: HwWallet? = null,
5454
)

app/src/main/java/to/bitkit/ui/settings/SettingsScreen.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import to.bitkit.ui.scaffold.AppTopBar
5757
import to.bitkit.ui.scaffold.DrawerNavIcon
5858
import to.bitkit.ui.scaffold.PinnedTabsScaffold
5959
import to.bitkit.ui.scaffold.ScreenColumn
60+
import to.bitkit.ui.screens.wallets.HwWalletViewModel
6061
import to.bitkit.ui.screens.wallets.activity.components.CustomTabRowWithSpacing
6162
import to.bitkit.ui.screens.wallets.activity.components.TabItem
6263
import to.bitkit.ui.settingsViewModel
@@ -79,6 +80,7 @@ fun SettingsScreen(
7980
navController: NavController,
8081
advancedViewModel: AdvancedSettingsViewModel = hiltViewModel(),
8182
languageViewModel: LanguageViewModel = hiltViewModel(),
83+
hwWalletViewModel: HwWalletViewModel = hiltViewModel(),
8284
) {
8385
val app = appViewModel ?: return
8486
val settings = settingsViewModel ?: return
@@ -94,6 +96,7 @@ fun SettingsScreen(
9496
val notificationsGranted by settings.notificationsGranted.collectAsStateWithLifecycle()
9597
val isPubkyAuthenticated by settings.isPubkyAuthenticated.collectAsStateWithLifecycle()
9698
val isPaykitEnabled by settings.isPaykitEnabled.collectAsStateWithLifecycle()
99+
val hardwareWallets by hwWalletViewModel.wallets.collectAsStateWithLifecycle()
97100
val languageUiState by languageViewModel.uiState.collectAsStateWithLifecycle()
98101

99102
// Security tab state
@@ -130,6 +133,7 @@ fun SettingsScreen(
130133
notificationsGranted = notificationsGranted,
131134
isPubkyAuthenticated = isPubkyAuthenticated,
132135
isPaykitEnabled = isPaykitEnabled,
136+
hardwareWalletCount = hardwareWallets.size,
133137
),
134138
securityState = SecurityTabState(
135139
isPinEnabled = isPinEnabled,
@@ -166,6 +170,7 @@ fun SettingsScreen(
166170
navController.navigateTo(Routes.BackgroundPaymentsIntro)
167171
}
168172
}
173+
SettingsEvent.HardwareWalletsClick -> navController.navigateTo(Routes.HardwareWalletsSettings)
169174
SettingsEvent.BackupWalletClick -> app.showSheet(Sheet.Backup())
170175
SettingsEvent.DataBackupsClick -> navController.navigateTo(Routes.BackupSettings)
171176
SettingsEvent.ResetWalletClick ->
@@ -365,6 +370,13 @@ private fun GeneralTabContent(
365370
onClick = { onEvent(SettingsEvent.BgPaymentsClick) },
366371
modifier = Modifier.testTag("BackgroundPaymentSettings")
367372
)
373+
SettingsButtonRow(
374+
title = stringResource(R.string.settings__hardware_wallets__nav_title),
375+
icon = { SettingsIcon(R.drawable.ic_device_mobile_speaker) },
376+
value = SettingsButtonValue.StringValue(state.hardwareWalletCount.toString()),
377+
onClick = { onEvent(SettingsEvent.HardwareWalletsClick) },
378+
modifier = Modifier.testTag("HardwareWalletsSettings")
379+
)
368380

369381
VerticalSpacer(32.dp)
370382
}
@@ -647,6 +659,7 @@ sealed interface SettingsEvent {
647659
data object PaymentPreferenceClick : SettingsEvent
648660
data object QuickPayClick : SettingsEvent
649661
data object BgPaymentsClick : SettingsEvent
662+
data object HardwareWalletsClick : SettingsEvent
650663

651664
// Security
652665
data object BackupWalletClick : SettingsEvent
@@ -689,6 +702,7 @@ data class GeneralTabState(
689702
val notificationsGranted: Boolean = false,
690703
val isPubkyAuthenticated: Boolean = false,
691704
val isPaykitEnabled: Boolean = false,
705+
val hardwareWalletCount: Int = 0,
692706
)
693707

694708
@Immutable

0 commit comments

Comments
 (0)