Skip to content

Commit e429197

Browse files
committed
refactor: hide trezor setup gate
1 parent 7d57e61 commit e429197

6 files changed

Lines changed: 36 additions & 69 deletions

File tree

app/src/main/java/to/bitkit/repositories/TrezorRepo.kt

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import to.bitkit.data.HwWalletStore
5252
import to.bitkit.di.IoDispatcher
5353
import to.bitkit.env.Env
5454
import to.bitkit.ext.nowMs
55+
import to.bitkit.ext.runSuspendCatching
5556
import to.bitkit.models.ALL_ADDRESS_TYPES
5657
import to.bitkit.models.TransportType
5758
import to.bitkit.models.toAccountDerivationPath
@@ -190,7 +191,6 @@ class TrezorRepo @Inject constructor(
190191
hwWalletStore.reset()
191192
_state.update {
192193
it.copy(
193-
isInitialized = false,
194194
isScanning = false,
195195
isConnecting = false,
196196
isAutoReconnecting = false,
@@ -242,16 +242,16 @@ class TrezorRepo @Inject constructor(
242242
isSetup = CompletableDeferred()
243243
}
244244
if (isSetup.isCompleted) {
245-
if (runCatching { isSetup.await() }.isSuccess) return@withLock Result.success(Unit)
245+
if (runSuspendCatching { isSetup.await() }.isSuccess) return@withLock Result.success(Unit)
246246
isSetup = CompletableDeferred()
247247
}
248248

249-
runCatching {
249+
runSuspendCatching {
250250
val credentialPath = "${Env.bitkitCoreStoragePath(walletIndex)}/trezor-credentials.json"
251251
Logger.debug("Initializing Trezor with credential path: '$credentialPath'", context = TAG)
252252
trezorService.initialize(credentialPath)
253253
val known = loadKnownDevices()
254-
_state.update { it.copy(isInitialized = true, knownDevices = known.toImmutableList(), error = null) }
254+
_state.update { it.copy(knownDevices = known.toImmutableList(), error = null) }
255255
isSetup.complete(Unit)
256256
Unit
257257
}.onFailure { e ->
@@ -264,7 +264,7 @@ class TrezorRepo @Inject constructor(
264264

265265
suspend fun scan(includeBluetooth: Boolean = true): Result<List<TrezorDeviceInfo>> = withContext(ioDispatcher) {
266266
runCatching {
267-
ensureInitialized()
267+
awaitSetup()
268268
_state.update { it.copy(isScanning = true, error = null) }
269269
val devices = trezorService.scan(includeBluetooth = includeBluetooth)
270270
val knownIds = _state.value.knownDevices.map { it.id }.toSet()
@@ -279,7 +279,7 @@ class TrezorRepo @Inject constructor(
279279

280280
suspend fun listDevices(): Result<List<TrezorDeviceInfo>> = withContext(ioDispatcher) {
281281
runCatching {
282-
ensureInitialized()
282+
awaitSetup()
283283
val devices = trezorService.listDevices()
284284
val knownIds = _state.value.knownDevices.map { it.id }.toSet()
285285
val nearby = devices.filter { it.id !in knownIds }
@@ -296,7 +296,7 @@ class TrezorRepo @Inject constructor(
296296
requestUsbPermission: Boolean = true,
297297
): Result<TrezorFeatures> = withContext(ioDispatcher) {
298298
runCatching {
299-
ensureInitialized()
299+
awaitSetup()
300300
_state.update { it.copy(isConnecting = true, error = null) }
301301
TrezorDebugLog.log("CONNECT", "connect() called for deviceId=$deviceId")
302302
val features = connectWithThpRetry(
@@ -382,6 +382,7 @@ class TrezorRepo @Inject constructor(
382382
scriptType: AccountType? = null,
383383
): Result<TransactionHistoryResult> = withContext(ioDispatcher) {
384384
runCatching {
385+
awaitSetup()
385386
trezorService.getTransactionHistory(
386387
extendedKey = extendedKey,
387388
electrumUrl = electrumUrlForNetwork(network),
@@ -400,6 +401,7 @@ class TrezorRepo @Inject constructor(
400401
scriptType: AccountType? = null,
401402
): Result<AccountInfoResult> = withContext(ioDispatcher) {
402403
runCatching {
404+
awaitSetup()
403405
trezorService.getAccountInfo(
404406
extendedKey = extendedKey,
405407
electrumUrl = electrumUrlForNetwork(network),
@@ -417,6 +419,7 @@ class TrezorRepo @Inject constructor(
417419
network: BitkitCoreNetwork = Env.network.toCoreNetwork(),
418420
): Result<SingleAddressInfoResult> = withContext(ioDispatcher) {
419421
runCatching {
422+
awaitSetup()
420423
trezorService.getAddressInfo(
421424
address = address,
422425
electrumUrl = electrumUrlForNetwork(network),
@@ -438,6 +441,7 @@ class TrezorRepo @Inject constructor(
438441
coinSelection: CoinSelection,
439442
): Result<List<ComposeResult>> = withContext(ioDispatcher) {
440443
runCatching {
444+
awaitSetup()
441445
val fingerprint = trezorService.getDeviceFingerprint()
442446
val params = ComposeParams(
443447
wallet = WalletParams(
@@ -478,6 +482,7 @@ class TrezorRepo @Inject constructor(
478482
network: BitkitCoreNetwork,
479483
): Result<String> = withContext(ioDispatcher) {
480484
runCatching {
485+
awaitSetup()
481486
trezorService.broadcastRawTx(
482487
serializedTx = serializedTx,
483488
electrumUrl = electrumUrlForNetwork(network),
@@ -577,7 +582,7 @@ class TrezorRepo @Inject constructor(
577582

578583
_state.update { it.copy(isAutoReconnecting = true, error = null) }
579584
runCatching {
580-
ensureInitialized(walletIndex)
585+
awaitSetup(walletIndex)
581586
val cachedFeatures = if (trezorService.isConnected()) _state.value.connectedDevice else null
582587
if (cachedFeatures != null) {
583588
cachedFeatures
@@ -629,11 +634,9 @@ class TrezorRepo @Inject constructor(
629634
_state.update { it.copy(isConnecting = true, error = null) }
630635
TrezorDebugLog.log("RECONNECT", "=== connectKnownDevice START ===")
631636
TrezorDebugLog.log("RECONNECT", "deviceId=$deviceId")
632-
TrezorDebugLog.log("RECONNECT", "isInitialized=${_state.value.isInitialized}")
633-
val needsInitialization = !_state.value.isInitialized
634-
if (needsInitialization) TrezorDebugLog.log("RECONNECT", "Initializing...")
635-
ensureInitialized()
636-
if (needsInitialization) TrezorDebugLog.log("RECONNECT", "Initialized OK")
637+
TrezorDebugLog.log("RECONNECT", "Awaiting setup...")
638+
awaitSetup()
639+
TrezorDebugLog.log("RECONNECT", "Setup OK")
637640
TrezorDebugLog.log("RECONNECT", "Scanning for devices...")
638641
val scannedDevices = trezorService.scan()
639642
TrezorDebugLog.log(
@@ -704,6 +707,7 @@ class TrezorRepo @Inject constructor(
704707
electrumUrl: String = electrumUrlForNetwork(network),
705708
): Result<Unit> = withContext(ioDispatcher) {
706709
runCatching {
710+
awaitSetup()
707711
val params = WatcherParams(
708712
watcherId = watcherId,
709713
extendedKey = extendedKey,
@@ -723,6 +727,7 @@ class TrezorRepo @Inject constructor(
723727

724728
suspend fun stopWatcher(watcherId: String): Result<Unit> = withContext(ioDispatcher) {
725729
runCatching {
730+
awaitSetup()
726731
trezorService.stopWatcher(watcherId)
727732
TrezorDebugLog.log(WATCHER_TAG, "Stopped watcher '$watcherId'")
728733
Logger.info("Stopped watcher '$watcherId'", context = TAG)
@@ -738,6 +743,7 @@ class TrezorRepo @Inject constructor(
738743

739744
suspend fun stopAllWatchers(): Result<Unit> = withContext(ioDispatcher) {
740745
runCatching {
746+
awaitSetup()
741747
trezorService.stopAllWatchers()
742748
TrezorDebugLog.log(WATCHER_TAG, "Stopped all watchers")
743749
}.onFailure {
@@ -888,18 +894,23 @@ class TrezorRepo @Inject constructor(
888894
val deviceId = _state.value.connectedDeviceId
889895
?: _state.value.knownDevices.firstOrNull()?.id
890896
?: throw AppError("No device to reconnect")
891-
ensureInitialized()
897+
awaitSetup()
892898
val devices = trezorService.scan()
893899
val device = devices.find { it.id == deviceId }
894900
?: throw AppError("Device not found during reconnect")
895901
val features = connectWithThpRetry(device.id, trezorUiHandler.currentSelection())
896902
_state.update { it.copy(connected = ConnectedTrezorDevice(id = deviceId, features = features)) }
897903
}
898904

899-
private suspend fun ensureInitialized(walletIndex: Int = 0) {
900-
if (!_state.value.isInitialized) {
905+
private suspend fun awaitSetup(walletIndex: Int = 0) {
906+
if (isSetup.isCancelled || !isSetup.isCompleted) {
901907
initialize(walletIndex).getOrThrow()
902908
}
909+
val setup = runSuspendCatching { isSetup.await() }
910+
if (setup.isSuccess) return
911+
912+
initialize(walletIndex).getOrThrow()
913+
isSetup.await()
903914
}
904915

905916
private suspend fun resetSetup() {
@@ -980,7 +991,6 @@ class TrezorRepo @Inject constructor(
980991

981992
@Stable
982993
data class TrezorState(
983-
val isInitialized: Boolean = false,
984994
val isScanning: Boolean = false,
985995
val isConnecting: Boolean = false,
986996
val isAutoReconnecting: Boolean = false,

app/src/main/java/to/bitkit/ui/screens/trezor/TrezorPreviewData.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,15 +187,13 @@ internal object TrezorPreviewData {
187187
)
188188

189189
val connectedState = TrezorState(
190-
isInitialized = true,
191190
connected = ConnectedTrezorDevice(
192191
id = "trezor-abc123",
193192
features = sampleFeatures,
194193
),
195194
)
196195

197196
val connectedStateWithResults = TrezorState(
198-
isInitialized = true,
199197
connected = ConnectedTrezorDevice(
200198
id = "trezor-abc123",
201199
features = sampleFeatures,

app/src/main/java/to/bitkit/ui/screens/trezor/TrezorScreen.kt

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,6 @@ private fun TrezorScreenContent(
102102

103103
val permissionsState = rememberMultiplePermissionsState(bluetoothPermissions)
104104

105-
LaunchedEffect(Unit) {
106-
viewModel.initialize()
107-
}
108-
109105
val onScanWithPermissions: () -> Unit = {
110106
if (permissionsState.allPermissionsGranted) {
111107
viewModel.scan()
@@ -130,7 +126,6 @@ private fun TrezorScreenContent(
130126
Content(
131127
trezorState = trezorState,
132128
uiState = uiState,
133-
onInitialize = viewModel::initialize,
134129
onScan = onScanWithPermissions,
135130
onConnectNearby = viewModel::connect,
136131
onConnectKnown = viewModel::connectKnownDevice,
@@ -178,7 +173,6 @@ private fun TrezorScreenContent(
178173
private fun Content(
179174
trezorState: TrezorState,
180175
uiState: TrezorUiState,
181-
onInitialize: () -> Unit = {},
182176
onScan: () -> Unit = {},
183177
onConnectNearby: (String) -> Unit = {},
184178
onConnectKnown: (String) -> Unit = {},
@@ -249,7 +243,6 @@ private fun Content(
249243

250244
ActionButtonsRow(
251245
trezorState = trezorState,
252-
onInitialize = onInitialize,
253246
onScan = onScan,
254247
onDisconnect = onDisconnect,
255248
permissionsGranted = permissionsGranted,
@@ -679,12 +672,8 @@ private fun StatusRow(trezorState: TrezorState) {
679672
StatusBadge(text = "Connected", color = Colors.Green)
680673
}
681674

682-
trezorState.isInitialized -> {
683-
StatusBadge(text = "Ready", color = Colors.Brand)
684-
}
685-
686675
else -> {
687-
StatusBadge(text = "Not initialized", color = Colors.White32)
676+
StatusBadge(text = "Ready", color = Colors.Brand)
688677
}
689678
}
690679
}
@@ -706,7 +695,6 @@ internal fun StatusBadge(text: String, color: Color) {
706695
@Composable
707696
private fun ActionButtonsRow(
708697
trezorState: TrezorState,
709-
onInitialize: () -> Unit,
710698
onScan: () -> Unit,
711699
onDisconnect: () -> Unit,
712700
permissionsGranted: Boolean = true,
@@ -716,14 +704,7 @@ private fun ActionButtonsRow(
716704
modifier = Modifier.fillMaxWidth()
717705
) {
718706
if (trezorState.isAutoReconnecting) return@Row
719-
if (!trezorState.isInitialized) {
720-
PrimaryButton(
721-
text = "Initialize",
722-
onClick = onInitialize,
723-
size = ButtonSize.Small,
724-
modifier = Modifier.weight(1f)
725-
)
726-
} else if (trezorState.connectedDevice != null) {
707+
if (trezorState.connectedDevice != null) {
727708
SecondaryButton(
728709
text = "Disconnect",
729710
onClick = onDisconnect,
@@ -751,7 +732,7 @@ private fun ActionButtonsRow(
751732

752733
@Preview
753734
@Composable
754-
private fun PreviewNotInitialized() {
735+
private fun PreviewReady() {
755736
AppThemeSurface {
756737
Content(
757738
trezorState = TrezorState(),
@@ -762,10 +743,10 @@ private fun PreviewNotInitialized() {
762743

763744
@Preview
764745
@Composable
765-
private fun PreviewInitialized() {
746+
private fun PreviewScanning() {
766747
AppThemeSurface {
767748
Content(
768-
trezorState = TrezorState(isInitialized = true),
749+
trezorState = TrezorState(isScanning = true),
769750
uiState = TrezorUiState(),
770751
)
771752
}
@@ -777,7 +758,6 @@ private fun PreviewWithDevices() {
777758
AppThemeSurface {
778759
Content(
779760
trezorState = TrezorState(
780-
isInitialized = true,
781761
knownDevices = listOf(TrezorPreviewData.sampleKnownDevice).toImmutableList(),
782762
nearbyDevices = listOf(TrezorPreviewData.sampleNearbyDevice).toImmutableList(),
783763
connected = ConnectedTrezorDevice(

app/src/main/java/to/bitkit/ui/screens/trezor/TrezorViewModel.kt

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -143,16 +143,6 @@ class TrezorViewModel @Inject constructor(
143143
}
144144
}
145145

146-
fun initialize() {
147-
viewModelScope.launch(bgDispatcher) {
148-
trezorRepo.initialize()
149-
.onSuccess {
150-
ToastEventBus.send(type = Toast.ToastType.INFO, title = "Trezor initialized")
151-
}
152-
.onFailure { ToastEventBus.send(it) }
153-
}
154-
}
155-
156146
fun scan() {
157147
viewModelScope.launch(bgDispatcher) {
158148
trezorRepo.scan()

app/src/test/java/to/bitkit/repositories/TrezorRepoTest.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,15 @@ class TrezorRepoTest : BaseUnitTest() {
162162
// region initialize
163163

164164
@Test
165-
fun `initialize should update state to initialized on success`() = test {
165+
fun `initialize should load known devices on success`() = test {
166+
val knownDevice = mockKnownDevice()
167+
whenever(hwWalletStore.loadKnownDevices()).thenReturn(listOf(knownDevice))
166168
sut = createSut()
167169

168170
val result = sut.initialize()
169171

170172
assertTrue(result.isSuccess)
171-
assertTrue(sut.state.value.isInitialized)
173+
assertEquals(listOf(knownDevice), sut.state.value.knownDevices)
172174
assertNull(sut.state.value.error)
173175
}
174176

@@ -192,7 +194,6 @@ class TrezorRepoTest : BaseUnitTest() {
192194
val result = sut.initialize()
193195

194196
assertTrue(result.isFailure)
195-
assertFalse(sut.state.value.isInitialized)
196197
assertEquals("init failed", sut.state.value.error)
197198
}
198199

@@ -223,7 +224,6 @@ class TrezorRepoTest : BaseUnitTest() {
223224
val result = sut.scan()
224225

225226
assertTrue(result.isSuccess)
226-
assertTrue(sut.state.value.isInitialized)
227227
verify(trezorService).initialize(anyOrNull())
228228
verify(trezorService).scan()
229229
}
@@ -1095,7 +1095,6 @@ class TrezorRepoTest : BaseUnitTest() {
10951095
sut = createSut()
10961096

10971097
val state = sut.state.value
1098-
assertFalse(state.isInitialized)
10991098
assertFalse(state.isScanning)
11001099
assertFalse(state.isConnecting)
11011100
assertFalse(state.isAutoReconnecting)

app/src/test/java/to/bitkit/ui/screens/trezor/TrezorViewModelTest.kt

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -167,16 +167,6 @@ class TrezorViewModelTest : BaseUnitTest() {
167167

168168
// region Async methods
169169

170-
@Test
171-
fun `initialize should call trezorRepo initialize`() = test {
172-
whenever(trezorRepo.initialize()).thenReturn(Result.success(Unit))
173-
174-
sut.initialize()
175-
advanceUntilIdle()
176-
177-
verify(trezorRepo).initialize()
178-
}
179-
180170
@Test
181171
fun `scan should call trezorRepo scan`() = test {
182172
whenever(trezorRepo.scan()).thenReturn(Result.success(emptyList()))

0 commit comments

Comments
 (0)