Skip to content

Commit 9c8b124

Browse files
committed
wip: reset activity state and wipe fixes
1 parent bfc497c commit 9c8b124

File tree

7 files changed

+187
-227
lines changed

7 files changed

+187
-227
lines changed

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

Lines changed: 48 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package to.bitkit.repositories
22

3+
import androidx.annotation.VisibleForTesting
34
import com.synonym.bitkitcore.Activity
45
import com.synonym.bitkitcore.ActivityFilter
56
import com.synonym.bitkitcore.ActivityTags
@@ -29,6 +30,7 @@ import to.bitkit.data.entities.TagMetadataEntity
2930
import to.bitkit.di.BgDispatcher
3031
import to.bitkit.ext.amountOnClose
3132
import to.bitkit.ext.matchesPaymentId
33+
import to.bitkit.ext.nowMillis
3234
import to.bitkit.ext.nowTimestamp
3335
import to.bitkit.ext.rawId
3436
import to.bitkit.models.ActivityBackupV1
@@ -54,53 +56,55 @@ class ActivityRepo @Inject constructor(
5456
) {
5557
val isSyncingLdkNodePayments = MutableStateFlow(false)
5658

59+
private val _state = MutableStateFlow(ActivityState())
60+
val state: StateFlow<ActivityState> = _state
61+
5762
private val _activitiesChanged = MutableStateFlow(0L)
5863
val activitiesChanged: StateFlow<Long> = _activitiesChanged
5964

60-
private fun notifyActivitiesChanged() = _activitiesChanged.update { clock.now().toEpochMilliseconds() }
65+
private fun notifyActivitiesChanged() = _activitiesChanged.update { nowMillis(clock) }
66+
67+
suspend fun resetState() = withContext(bgDispatcher) {
68+
_state.update { ActivityState() }
69+
isSyncingLdkNodePayments.update { false }
70+
notifyActivitiesChanged()
71+
Logger.debug("Activity state reset", context = TAG)
72+
}
6173

6274
suspend fun syncActivities(): Result<Unit> = withContext(bgDispatcher) {
6375
Logger.debug("syncActivities called", context = TAG)
6476

65-
return@withContext runCatching {
77+
val result = runCatching {
6678
withTimeout(SYNC_TIMEOUT_MS) {
6779
Logger.debug("isSyncingLdkNodePayments = ${isSyncingLdkNodePayments.value}", context = TAG)
6880
isSyncingLdkNodePayments.first { !it }
6981
}
7082

71-
isSyncingLdkNodePayments.value = true
83+
isSyncingLdkNodePayments.update { true }
7284

7385
deletePendingActivities()
74-
return@withContext lightningRepo.getPayments()
75-
.onSuccess { payments ->
76-
Logger.debug("Got payments with success, syncing activities", context = TAG)
77-
syncLdkNodePayments(payments = payments).onFailure { e ->
78-
return@withContext Result.failure(e)
79-
}
80-
updateActivitiesMetadata()
81-
syncTagsMetadata()
82-
boostPendingActivities()
83-
transferRepo.syncTransferStates()
84-
isSyncingLdkNodePayments.value = false
85-
return@withContext Result.success(Unit)
86-
}.onFailure { e ->
87-
Logger.error("Failed to sync ldk-node payments", e, context = TAG)
88-
isSyncingLdkNodePayments.value = false
89-
return@withContext Result.failure(e)
90-
}.map { Unit }
91-
}.onFailure { e ->
92-
when (e) {
93-
is TimeoutCancellationException -> {
94-
isSyncingLdkNodePayments.value = false
95-
Logger.warn("Timeout waiting for sync to complete, forcing reset", context = TAG)
96-
}
9786

98-
else -> {
99-
isSyncingLdkNodePayments.value = false
100-
Logger.error("syncActivities error", e, context = TAG)
101-
}
87+
lightningRepo.getPayments().mapCatching { payments ->
88+
Logger.debug("Got payments with success, syncing activities", context = TAG)
89+
syncLdkNodePayments(payments).getOrThrow()
90+
updateActivitiesMetadata()
91+
syncTagsMetadata()
92+
boostPendingActivities()
93+
transferRepo.syncTransferStates()
94+
getAllAvailableTags().map { }.getOrThrow()
95+
}.getOrThrow()
96+
}.onFailure { e ->
97+
if (e is TimeoutCancellationException) {
98+
Logger.warn("syncActivities timeout, forcing reset", context = TAG)
99+
} else {
100+
Logger.error("Failed to sync activities", e, context = TAG)
102101
}
103102
}
103+
104+
isSyncingLdkNodePayments.update { false }
105+
notifyActivitiesChanged()
106+
107+
return@withContext result
104108
}
105109

106110
/**
@@ -542,10 +546,11 @@ class ActivityRepo @Inject constructor(
542546
cacheStore.addActivityToPendingBoost(pendingBoostActivity)
543547
}
544548

545-
/**
546-
* Adds tags to an activity with business logic validation
547-
*/
548-
suspend fun addTagsToActivity(activityId: String, tags: List<String>): Result<Unit> = withContext(bgDispatcher) {
549+
@VisibleForTesting
550+
suspend fun addTagsToActivity(
551+
activityId: String,
552+
tags: List<String>,
553+
): Result<Unit> = withContext(bgDispatcher) {
549554
return@withContext runCatching {
550555
checkNotNull(coreService.activity.getActivity(activityId)) { "Activity with ID $activityId not found" }
551556

@@ -578,11 +583,9 @@ class ActivityRepo @Inject constructor(
578583
paymentHashOrTxId = paymentHashOrTxId,
579584
type = type,
580585
txType = txType
581-
).onSuccess { activity ->
582-
addTagsToActivity(activity.rawId(), tags = tags)
583-
}.onFailure { e ->
584-
return@withContext Result.failure(e)
585-
}.map { Unit }
586+
).mapCatching { activity ->
587+
addTagsToActivity(activity.rawId(), tags = tags).getOrThrow()
588+
}
586589
}
587590

588591
/**
@@ -612,12 +615,11 @@ class ActivityRepo @Inject constructor(
612615
}
613616
}
614617

615-
/**
616-
* Gets all possible tags across all activities
617-
*/
618618
suspend fun getAllAvailableTags(): Result<List<String>> = withContext(bgDispatcher) {
619619
return@withContext runCatching {
620620
coreService.activity.allPossibleTags()
621+
}.onSuccess { tags ->
622+
_state.update { it.copy(tags = tags) }
621623
}.onFailure { e ->
622624
Logger.error("getAllAvailableTags error", e, context = TAG)
623625
}
@@ -705,3 +707,7 @@ class ActivityRepo @Inject constructor(
705707
private const val TAG = "ActivityRepo"
706708
}
707709
}
710+
711+
data class ActivityState(
712+
val tags: List<String> = emptyList(),
713+
)

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

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,20 @@ class BackupRepo @Inject constructor(
6969
private val dataListenerJobs = mutableListOf<Job>()
7070
private var periodicCheckJob: Job? = null
7171
private var isObserving = false
72+
private var lastNotificationTime = 0L
73+
7274
private val _isRestoring = MutableStateFlow(false)
7375
val isRestoring: StateFlow<Boolean> = _isRestoring.asStateFlow()
7476

75-
private var lastNotificationTime = 0L
77+
private val _isWiping = MutableStateFlow(false)
7678

7779
fun reset() {
7880
stopObservingBackups()
7981
vssBackupClient.reset()
8082
}
8183

84+
fun setWiping(isWiping: Boolean) = _isWiping.update { isWiping }
85+
8286
fun startObservingBackups() {
8387
if (isObserving) return
8488

@@ -144,7 +148,7 @@ class BackupRepo @Inject constructor(
144148
.distinctUntilChanged()
145149
.drop(1)
146150
.collect {
147-
if (isRestoring.value) return@collect
151+
if (shouldSkipBackup()) return@collect
148152
markBackupRequired(BackupCategory.SETTINGS)
149153
}
150154
}
@@ -155,7 +159,7 @@ class BackupRepo @Inject constructor(
155159
.distinctUntilChanged()
156160
.drop(1)
157161
.collect {
158-
if (isRestoring.value) return@collect
162+
if (shouldSkipBackup()) return@collect
159163
markBackupRequired(BackupCategory.WIDGETS)
160164
}
161165
}
@@ -167,7 +171,7 @@ class BackupRepo @Inject constructor(
167171
.distinctUntilChanged()
168172
.drop(1)
169173
.collect {
170-
if (isRestoring.value) return@collect
174+
if (shouldSkipBackup()) return@collect
171175
markBackupRequired(BackupCategory.WALLET)
172176
}
173177
}
@@ -179,7 +183,7 @@ class BackupRepo @Inject constructor(
179183
.distinctUntilChanged()
180184
.drop(1)
181185
.collect {
182-
if (isRestoring.value) return@collect
186+
if (shouldSkipBackup()) return@collect
183187
markBackupRequired(BackupCategory.METADATA)
184188
}
185189
}
@@ -192,7 +196,7 @@ class BackupRepo @Inject constructor(
192196
.distinctUntilChanged()
193197
.drop(1)
194198
.collect {
195-
if (isRestoring.value) return@collect
199+
if (shouldSkipBackup()) return@collect
196200
markBackupRequired(BackupCategory.METADATA)
197201
}
198202
}
@@ -203,18 +207,18 @@ class BackupRepo @Inject constructor(
203207
blocktankRepo.blocktankState
204208
.drop(1)
205209
.collect {
206-
if (isRestoring.value) return@collect
210+
if (shouldSkipBackup()) return@collect
207211
markBackupRequired(BackupCategory.BLOCKTANK)
208212
}
209213
}
210214
dataListenerJobs.add(blocktankJob)
211215

212-
// ACTIVITY - Observe all activity changes notified by ActivityRepo on any mutation to core's activity store
216+
// ACTIVITY - Observe activity changes
213217
val activityChangesJob = scope.launch {
214218
activityRepo.activitiesChanged
215219
.drop(1)
216220
.collect {
217-
if (isRestoring.value) return@collect
221+
if (shouldSkipBackup()) return@collect
218222
markBackupRequired(BackupCategory.ACTIVITY)
219223
}
220224
}
@@ -227,7 +231,7 @@ class BackupRepo @Inject constructor(
227231
val lastSync = lightningService.status?.latestLightningWalletSyncTimestamp?.toLong()
228232
?.let { it * 1000 } // Convert seconds to millis
229233
?: return@collect
230-
if (isRestoring.value) return@collect
234+
if (shouldSkipBackup()) return@collect
231235
cacheStore.updateBackupStatus(BackupCategory.LIGHTNING_CONNECTIONS) {
232236
it.copy(required = lastSync, synced = lastSync, running = false)
233237
}
@@ -505,7 +509,9 @@ class BackupRepo @Inject constructor(
505509

506510
private fun currentTimeMillis(): Long = nowMillis(clock)
507511

508-
private fun BackupItemStatus.shouldBackup() = this.isRequired && !this.running && !isRestoring.value
512+
private fun shouldSkipBackup(): Boolean = _isRestoring.value || _isWiping.value
513+
514+
private fun BackupItemStatus.shouldBackup() = this.isRequired && !this.running && !shouldSkipBackup()
509515

510516
companion object {
511517
private const val TAG = "BackupRepo"

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

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class WalletRepo @Inject constructor(
5252
private val deriveBalanceStateUseCase: DeriveBalanceStateUseCase,
5353
private val backupRepo: BackupRepo,
5454
private val blocktankRepo: BlocktankRepo,
55+
private val activityRepo: ActivityRepo,
5556
) {
5657
private val repoScope = CoroutineScope(bgDispatcher + SupervisorJob())
5758

@@ -237,29 +238,44 @@ class WalletRepo @Inject constructor(
237238
}
238239

239240
suspend fun wipeWallet(walletIndex: Int = 0): Result<Unit> = withContext(bgDispatcher) {
241+
// !order is critical
240242
try {
243+
backupRepo.setWiping(true)
241244
backupRepo.reset()
242-
blocktankRepo.resetState()
243-
244-
_walletState.update { WalletState() }
245-
_balanceState.update { BalanceState() }
246245

247246
keychain.wipe()
247+
248+
// clear stored state
249+
coreService.wipeData()
248250
db.clearAllTables()
249251
settingsStore.reset()
250252
cacheStore.reset()
251-
// TODO CLEAN ACTIVITY'S AND UPDATE STATE. CHECK ActivityListViewModel.removeAllActivities
252-
coreService.wipeData()
253-
Logger.reset()
254-
setWalletExistsState()
255253

256-
return@withContext lightningRepo.wipeStorage(walletIndex = walletIndex)
254+
// clear cached state
255+
blocktankRepo.resetState()
256+
activityRepo.resetState()
257+
resetState()
258+
259+
// stop and wipe node caches
260+
return@withContext lightningRepo.wipeStorage(walletIndex)
261+
.onSuccess {
262+
// trigger nav to onboarding
263+
setWalletExistsState()
264+
Logger.reset()
265+
}
257266
} catch (e: Throwable) {
258267
Logger.error("Wipe wallet error", e)
259268
Result.failure(e)
269+
} finally {
270+
backupRepo.setWiping(false)
260271
}
261272
}
262273

274+
private fun resetState() {
275+
_walletState.update { WalletState() }
276+
_balanceState.update { BalanceState() }
277+
}
278+
263279
// Blockchain address management
264280
fun getOnchainAddress(): String = _walletState.value.onchainAddress
265281

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ fun ContentView(
311311

312312
LaunchedEffect(balance) {
313313
// Anytime we receive a balance update, we should sync the payments to activity list
314-
activityListViewModel.syncLdkNodePayments()
314+
activityListViewModel.resync()
315315
}
316316

317317
// Keep backups in sync

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,9 @@ fun HomeScreen(
161161
drawerState = drawerState,
162162
latestActivities = latestActivities,
163163
onRefresh = {
164-
activityListViewModel.fetchLatestActivities()
164+
activityListViewModel.resync()
165165
walletViewModel.onPullToRefresh()
166166
homeViewModel.refreshWidgets()
167-
activityListViewModel.syncLdkNodePayments()
168167
},
169168
onClickProfile = {
170169
if (!hasSeenProfileIntro) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ fun ActivityDetailScreen(
155155
title = context.getString(R.string.wallet__boost_success_title),
156156
description = context.getString(R.string.wallet__boost_success_msg)
157157
)
158-
listViewModel.fetchLatestActivities()
158+
listViewModel.resync()
159159
onCloseClick()
160160
},
161161
onFailure = {

0 commit comments

Comments
 (0)