diff --git a/app/src/main/java/to/bitkit/data/dao/TagMetadataDao.kt b/app/src/main/java/to/bitkit/data/dao/TagMetadataDao.kt index b25bb1e56..8e03451f6 100644 --- a/app/src/main/java/to/bitkit/data/dao/TagMetadataDao.kt +++ b/app/src/main/java/to/bitkit/data/dao/TagMetadataDao.kt @@ -16,7 +16,10 @@ interface TagMetadataDao { suspend fun insert(tagMetadata: TagMetadataEntity) @Upsert - suspend fun upsert(tagMetadata: TagMetadataEntity) + suspend fun upsert(entity: TagMetadataEntity) + + @Upsert + suspend fun upsert(entities: List) @Query("SELECT * FROM tag_metadata") fun observeAll(): Flow> diff --git a/app/src/main/java/to/bitkit/data/dao/TransferDao.kt b/app/src/main/java/to/bitkit/data/dao/TransferDao.kt index b37b777c9..72f630625 100644 --- a/app/src/main/java/to/bitkit/data/dao/TransferDao.kt +++ b/app/src/main/java/to/bitkit/data/dao/TransferDao.kt @@ -17,6 +17,9 @@ interface TransferDao { @Upsert suspend fun upsert(transfer: TransferEntity) + @Upsert + suspend fun upsert(transfers: List) + @Update suspend fun update(transfer: TransferEntity) diff --git a/app/src/main/java/to/bitkit/models/BackupPayloads.kt b/app/src/main/java/to/bitkit/models/BackupPayloads.kt index 59b006015..16b48d9c4 100644 --- a/app/src/main/java/to/bitkit/models/BackupPayloads.kt +++ b/app/src/main/java/to/bitkit/models/BackupPayloads.kt @@ -1,6 +1,7 @@ package to.bitkit.models import com.synonym.bitkitcore.Activity +import com.synonym.bitkitcore.ClosedChannelDetails import com.synonym.bitkitcore.IBtInfo import com.synonym.bitkitcore.IBtOrder import com.synonym.bitkitcore.IcJitEntry @@ -38,4 +39,5 @@ data class ActivityBackupV1( val version: Int = 1, val createdAt: Long, val activities: List, + val closedChannels: List, ) diff --git a/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt b/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt index cbd35954b..0d476281c 100644 --- a/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/ActivityRepo.kt @@ -2,6 +2,7 @@ package to.bitkit.repositories import com.synonym.bitkitcore.Activity import com.synonym.bitkitcore.ActivityFilter +import com.synonym.bitkitcore.ClosedChannelDetails import com.synonym.bitkitcore.IcJitEntry import com.synonym.bitkitcore.LightningActivity import com.synonym.bitkitcore.PaymentState @@ -29,6 +30,7 @@ import to.bitkit.ext.amountOnClose import to.bitkit.ext.matchesPaymentId import to.bitkit.ext.nowTimestamp import to.bitkit.ext.rawId +import to.bitkit.models.ActivityBackupV1 import to.bitkit.services.CoreService import to.bitkit.utils.AddressChecker import to.bitkit.utils.Logger @@ -166,9 +168,6 @@ class ActivityRepo @Inject constructor( } } - /** - * Gets activities with specified filters - */ suspend fun getActivities( filter: ActivityFilter? = null, txType: PaymentType? = null, @@ -198,9 +197,6 @@ class ActivityRepo @Inject constructor( } } - /** - * Gets a specific activity by ID - */ suspend fun getActivity(id: String): Result = withContext(bgDispatcher) { return@withContext runCatching { coreService.activity.getActivity(id) @@ -209,6 +205,16 @@ class ActivityRepo @Inject constructor( } } + suspend fun getClosedChannels( + sortDirection: SortDirection = SortDirection.ASC, + ): Result> = withContext(bgDispatcher) { + runCatching { + coreService.activity.closedChannels(sortDirection) + }.onFailure { e -> + Logger.error("Error getting closed channels (sortDirection=$sortDirection)", e, context = TAG) + } + } + /** * Updates an activity * @param forceUpdate use it if you want update a deleted activity @@ -643,6 +649,13 @@ class ActivityRepo @Inject constructor( } } + suspend fun restoreFromBackup(backup: ActivityBackupV1): Result = withContext(bgDispatcher) { + return@withContext runCatching { + coreService.activity.upsertList(backup.activities) + coreService.activity.upsertClosedChannelList(backup.closedChannels) + } + } + // MARK: - Development/Testing Methods /** diff --git a/app/src/main/java/to/bitkit/repositories/BackupRepo.kt b/app/src/main/java/to/bitkit/repositories/BackupRepo.kt index 17ace5b98..00a980634 100644 --- a/app/src/main/java/to/bitkit/repositories/BackupRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/BackupRepo.kt @@ -371,10 +371,12 @@ class BackupRepo @Inject constructor( BackupCategory.ACTIVITY -> { val activities = activityRepo.getActivities().getOrDefault(emptyList()) + val closedChannels = activityRepo.getClosedChannels().getOrDefault(emptyList()) val payload = ActivityBackupV1( createdAt = currentTimeMillis(), activities = activities, + closedChannels = closedChannels, ) json.encodeToString(payload).toByteArray() @@ -399,47 +401,29 @@ class BackupRepo @Inject constructor( } performRestore(BackupCategory.WALLET) { dataBytes -> val parsed = json.decodeFromString(String(dataBytes)) - - parsed.transfers.forEach { transfer -> - db.transferDao().upsert(transfer) - } - + db.transferDao().upsert(parsed.transfers) Logger.debug("Restored ${parsed.transfers.size} transfers", context = TAG) } performRestore(BackupCategory.METADATA) { dataBytes -> val parsed = json.decodeFromString(String(dataBytes)) - - parsed.tagMetadata.forEach { entity -> - db.tagMetadataDao().upsert(entity) - } - + db.tagMetadataDao().upsert(parsed.tagMetadata) cacheStore.update { parsed.cache } - - Logger.debug("Restored caches and ${parsed.tagMetadata.size} tags metadata", context = TAG) + Logger.debug("Restored caches and ${parsed.tagMetadata.size} tags metadata records", TAG) } performRestore(BackupCategory.BLOCKTANK) { dataBytes -> val parsed = json.decodeFromString(String(dataBytes)) - - // TODO: Restore orders, CJIT entries, and info to bitkit-core using synonymdev/bitkit-core#46 - // For now, trigger a refresh from the server to sync the data - blocktankRepo.refreshInfo() - blocktankRepo.refreshOrders() - - Logger.debug( - "Triggered Blocktank refresh (${parsed.orders.size} orders," + - "${parsed.cjitEntries.size} CJIT entries," + - "info=${parsed.info != null} backed up)", - context = TAG, - ) + blocktankRepo.restoreFromBackup(parsed).onSuccess { + Logger.debug("Restored ${parsed.orders.size} orders, ${parsed.cjitEntries.size} CJITs", TAG) + } } performRestore(BackupCategory.ACTIVITY) { dataBytes -> val parsed = json.decodeFromString(String(dataBytes)) - - parsed.activities.forEach { activity -> - activityRepo.upsertActivity(activity) + activityRepo.restoreFromBackup(parsed).onSuccess { + Logger.debug( + "Restored ${parsed.activities.size} activities, ${parsed.closedChannels.size} closed channels", + context = TAG, + ) } - - Logger.debug("Restored ${parsed.activities.size} activities", context = TAG) } Logger.info("Full restore success", context = TAG) diff --git a/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt b/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt index 31a0002fe..2a1388aae 100644 --- a/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt @@ -31,6 +31,7 @@ import to.bitkit.data.CacheStore import to.bitkit.di.BgDispatcher import to.bitkit.env.Env import to.bitkit.ext.nowTimestamp +import to.bitkit.models.BlocktankBackupV1 import to.bitkit.models.EUR_CURRENCY import to.bitkit.services.CoreService import to.bitkit.services.LightningService @@ -374,6 +375,27 @@ class BlocktankRepo @Inject constructor( _blocktankState.update { BlocktankState() } } + suspend fun restoreFromBackup(backup: BlocktankBackupV1): Result = withContext(bgDispatcher) { + return@withContext runCatching { + coreService.blocktank.upsertOrderList(backup.orders) + coreService.blocktank.upsertCjitList(backup.cjitEntries) + backup.info?.let { info -> + coreService.blocktank.setInfo(info) + } + + // We don't refresh orders here because we rely on the polling mechanism. + // We also don't restore `paidOrders` the refresh interval uses restored paidOrderIds to rebuild the list. + + _blocktankState.update { + it.copy( + orders = backup.orders, + cjitEntries = backup.cjitEntries, + info = backup.info, + ) + } + } + } + companion object { private const val TAG = "BlocktankRepo" private const val DEFAULT_CHANNEL_EXPIRY_WEEKS = 6u diff --git a/app/src/main/java/to/bitkit/services/CoreService.kt b/app/src/main/java/to/bitkit/services/CoreService.kt index c875b3199..62c7c7153 100644 --- a/app/src/main/java/to/bitkit/services/CoreService.kt +++ b/app/src/main/java/to/bitkit/services/CoreService.kt @@ -4,6 +4,7 @@ import com.synonym.bitkitcore.Activity import com.synonym.bitkitcore.ActivityFilter import com.synonym.bitkitcore.BtOrderState2 import com.synonym.bitkitcore.CJitStateEnum +import com.synonym.bitkitcore.ClosedChannelDetails import com.synonym.bitkitcore.CreateCjitOptions import com.synonym.bitkitcore.CreateOrderOptions import com.synonym.bitkitcore.FeeRates @@ -26,6 +27,7 @@ import com.synonym.bitkitcore.deleteActivityById import com.synonym.bitkitcore.estimateOrderFeeFull import com.synonym.bitkitcore.getActivities import com.synonym.bitkitcore.getActivityById +import com.synonym.bitkitcore.getAllClosedChannels import com.synonym.bitkitcore.getAllUniqueTags import com.synonym.bitkitcore.getCjitEntries import com.synonym.bitkitcore.getInfo @@ -39,7 +41,13 @@ import com.synonym.bitkitcore.refreshActiveOrders import com.synonym.bitkitcore.removeTags import com.synonym.bitkitcore.updateActivity import com.synonym.bitkitcore.updateBlocktankUrl +import com.synonym.bitkitcore.upsertActivities import com.synonym.bitkitcore.upsertActivity +import com.synonym.bitkitcore.upsertCjitEntries +import com.synonym.bitkitcore.upsertClosedChannel +import com.synonym.bitkitcore.upsertClosedChannels +import com.synonym.bitkitcore.upsertInfo +import com.synonym.bitkitcore.upsertOrders import io.ktor.client.HttpClient import io.ktor.client.request.get import io.ktor.http.HttpStatusCode @@ -199,10 +207,20 @@ class ActivityService( } } - suspend fun upsert(activity: Activity) { - ServiceQueue.CORE.background { - upsertActivity(activity) - } + suspend fun upsert(activity: Activity) = ServiceQueue.CORE.background { + upsertActivity(activity) + } + + suspend fun upsertList(activities: List) = ServiceQueue.CORE.background { + upsertActivities(activities) + } + + suspend fun upsertClosedChannelItem(closedChannel: ClosedChannelDetails) = ServiceQueue.CORE.background { + upsertClosedChannel(closedChannel) + } + + suspend fun upsertClosedChannelList(closedChannels: List) = ServiceQueue.CORE.background { + upsertClosedChannels(closedChannels) } suspend fun getActivity(id: String): Activity? { @@ -267,6 +285,12 @@ class ActivityService( } } + suspend fun closedChannels( + sortDirection: SortDirection, + ): List = ServiceQueue.CORE.background { + getAllClosedChannels(sortDirection) + } + /** * Maps all `PaymentDetails` from LDK Node to bitkit-core [Activity] records. * @@ -325,7 +349,7 @@ class ActivityService( } } - private suspend fun processBolt11( + private fun processBolt11( kind: PaymentKind.Bolt11, payment: PaymentDetails, state: PaymentState, @@ -421,6 +445,7 @@ class ActivityService( confirmed = isConfirmed, timestamp = timestamp, isBoosted = false, + boostTxIds = emptyList(), isTransfer = false, doesExist = true, confirmTimestamp = confirmedTimestamp, @@ -471,11 +496,11 @@ class ActivityService( ) repeat(count) { i -> - val isLightning = Random.Default.nextBoolean() + val isLightning = Random.nextBoolean() val value = (1000..1_000_000).random().toULong() val txTimestamp = (timestamp.toLong() - (0..30L * 24 * 60 * 60).random()).toULong() // Random time in last 30 days - val txType = if (Random.Default.nextBoolean()) PaymentType.SENT else PaymentType.RECEIVED + val txType = if (Random.nextBoolean()) PaymentType.SENT else PaymentType.RECEIVED val status = when ((0..10).random()) { in 0..7 -> PaymentState.SUCCEEDED // 80% chance 8 -> PaymentState.PENDING // 10% chance @@ -497,7 +522,7 @@ class ActivityService( invoice = "lnbc$value", message = possibleMessages.random(), timestamp = txTimestamp, - preimage = if (Random.Default.nextBoolean()) "preimage$i" else null, + preimage = if (Random.nextBoolean()) "preimage$i" else null, createdAt = txTimestamp, updatedAt = txTimestamp ) @@ -513,16 +538,17 @@ class ActivityService( fee = (100..10_000).random().toULong(), feeRate = (1..100).random().toULong(), address = "bc1...$i", - confirmed = Random.Default.nextBoolean(), + confirmed = Random.nextBoolean(), timestamp = txTimestamp, - isBoosted = Random.Default.nextBoolean(), - isTransfer = Random.Default.nextBoolean(), + isBoosted = Random.nextBoolean(), + boostTxIds = emptyList(), + isTransfer = Random.nextBoolean(), doesExist = true, - confirmTimestamp = if (Random.Default.nextBoolean()) txTimestamp + 3600.toULong() else null, - channelId = if (Random.Default.nextBoolean()) "channel$i" else null, + confirmTimestamp = if (Random.nextBoolean()) txTimestamp + 3600.toULong() else null, + channelId = if (Random.nextBoolean()) "channel$i" else null, transferTxId = null, createdAt = txTimestamp, - updatedAt = txTimestamp + updatedAt = txTimestamp, ) ) } @@ -664,6 +690,18 @@ class BlocktankService( } } + suspend fun setInfo(info: IBtInfo) = ServiceQueue.CORE.background { + upsertInfo(info) + } + + suspend fun upsertOrderList(orders: List) = ServiceQueue.CORE.background { + upsertOrders(orders) + } + + suspend fun upsertCjitList(cjitEntries: List) = ServiceQueue.CORE.background { + upsertCjitEntries(cjitEntries) + } + // MARK: - Regtest methods suspend fun regtestMine(count: UInt = 1u) { com.synonym.bitkitcore.regtestMine(count = count) diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt index ed9eb60b6..318ea6f72 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityDetailScreen.kt @@ -693,6 +693,7 @@ private fun PreviewOnchain() { confirmed = true, timestamp = (System.currentTimeMillis() / 1000 - 3600).toULong(), isBoosted = false, + boostTxIds = emptyList(), isTransfer = false, doesExist = true, confirmTimestamp = (System.currentTimeMillis() / 1000).toULong(), diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt index 341021892..d6a611ea5 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt @@ -361,6 +361,7 @@ private fun PreviewOnchain() { confirmed = true, timestamp = (System.currentTimeMillis() / 1000 - 3600).toULong(), isBoosted = false, + boostTxIds = emptyList(), isTransfer = false, doesExist = true, confirmTimestamp = (System.currentTimeMillis() / 1000).toULong(), diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityIcon.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityIcon.kt index dc8fb6d48..540c02c2e 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityIcon.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/components/ActivityIcon.kt @@ -206,6 +206,7 @@ private fun Preview() { confirmed = true, timestamp = (System.currentTimeMillis() / 1000).toULong(), isBoosted = false, + boostTxIds = emptyList(), isTransfer = false, doesExist = true, confirmTimestamp = (System.currentTimeMillis() / 1000).toULong(), @@ -231,6 +232,7 @@ private fun Preview() { confirmed = false, timestamp = (System.currentTimeMillis() / 1000).toULong(), isBoosted = true, + boostTxIds = emptyList(), isTransfer = false, doesExist = true, confirmTimestamp = (System.currentTimeMillis() / 1000).toULong(), @@ -256,6 +258,7 @@ private fun Preview() { confirmed = false, timestamp = (System.currentTimeMillis() / 1000).toULong(), isBoosted = true, + boostTxIds = emptyList(), isTransfer = false, doesExist = true, confirmTimestamp = (System.currentTimeMillis() / 1000).toULong(), @@ -281,6 +284,7 @@ private fun Preview() { confirmed = true, timestamp = (System.currentTimeMillis() / 1000).toULong(), isBoosted = false, + boostTxIds = emptyList(), isTransfer = true, doesExist = true, confirmTimestamp = (System.currentTimeMillis() / 1000).toULong(), diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/utils/PreviewItems.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/utils/PreviewItems.kt index 68c4b1584..85067bb97 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/activity/utils/PreviewItems.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/activity/utils/PreviewItems.kt @@ -30,6 +30,7 @@ val previewActivityItems = buildList { confirmed = true, timestamp = today.epochSecond(), isBoosted = false, + boostTxIds = emptyList(), isTransfer = true, doesExist = true, confirmTimestamp = today.epochSecond(), @@ -93,6 +94,7 @@ val previewActivityItems = buildList { confirmed = false, timestamp = thisMonth.epochSecond(), isBoosted = false, + boostTxIds = emptyList(), isTransfer = true, doesExist = true, confirmTimestamp = today.epochSecond() + 3600u, diff --git a/app/src/main/java/to/bitkit/ui/settings/backups/BackupNavSheetViewModel.kt b/app/src/main/java/to/bitkit/ui/settings/backups/BackupNavSheetViewModel.kt index 02396f977..48a498c87 100644 --- a/app/src/main/java/to/bitkit/ui/settings/backups/BackupNavSheetViewModel.kt +++ b/app/src/main/java/to/bitkit/ui/settings/backups/BackupNavSheetViewModel.kt @@ -10,12 +10,17 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import to.bitkit.R +import to.bitkit.data.CacheStore import to.bitkit.data.SettingsStore import to.bitkit.data.keychain.Keychain +import to.bitkit.models.BackupCategory +import to.bitkit.models.HealthState import to.bitkit.models.Toast +import to.bitkit.repositories.HealthRepo import to.bitkit.ui.settings.backups.BackupContract.SideEffect import to.bitkit.ui.settings.backups.BackupContract.UiState import to.bitkit.ui.shared.toast.ToastEventBus @@ -27,6 +32,8 @@ class BackupNavSheetViewModel @Inject constructor( @ApplicationContext private val context: Context, private val settingsStore: SettingsStore, private val keychain: Keychain, + private val healthRepo: HealthRepo, + private val cacheStore: CacheStore, ) : ViewModel() { private val _uiState = MutableStateFlow(UiState()) @@ -37,6 +44,34 @@ class BackupNavSheetViewModel @Inject constructor( private fun setEffect(effect: SideEffect) = viewModelScope.launch { _effects.emit(effect) } + companion object { + private const val TAG = "BackupNavSheetViewModel" + } + + init { + collectState() + } + + private fun collectState() { + viewModelScope.launch { + combine(healthRepo.healthState, cacheStore.backupStatuses) { healthState, backupStatuses -> + when (healthState.backups) { + HealthState.ERROR -> null + else -> { + BackupCategory.entries + .filter { it != BackupCategory.LIGHTNING_CONNECTIONS } + .maxOfOrNull { category -> backupStatuses[category]?.synced ?: 0L } + .takeIf { it != 0L } + } + } + }.collect { lastBackupTimeMs -> + _uiState.update { + it.copy(lastBackupTimeMs = lastBackupTimeMs) + } + } + } + } + fun loadMnemonicData() { viewModelScope.launch { try { @@ -50,7 +85,7 @@ class BackupNavSheetViewModel @Inject constructor( ) } } catch (e: Throwable) { - Logger.error("Error loading mnemonic", e) + Logger.error("Error loading mnemonic", e, context = TAG) ToastEventBus.send( type = Toast.ToastType.WARNING, title = context.getString(R.string.security__mnemonic_error), @@ -109,11 +144,6 @@ class BackupNavSheetViewModel @Inject constructor( } fun onMultipleDevicesContinue() { - // TODO: get from actual repository state - val lastBackupTimeMs = System.currentTimeMillis() - _uiState.update { - it.copy(lastBackupTimeMs = lastBackupTimeMs) - } setEffect(SideEffect.NavigateToMetadata) } @@ -136,7 +166,7 @@ interface BackupContract { val bip39Passphrase: String = "", val showMnemonic: Boolean = false, val enteredPassphrase: String = "", - val lastBackupTimeMs: Long = System.currentTimeMillis(), + val lastBackupTimeMs: Long? = null, ) sealed interface SideEffect { diff --git a/app/src/main/java/to/bitkit/ui/settings/backups/MetadataScreen.kt b/app/src/main/java/to/bitkit/ui/settings/backups/MetadataScreen.kt index 75f56d78f..b7c54bb0c 100644 --- a/app/src/main/java/to/bitkit/ui/settings/backups/MetadataScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/backups/MetadataScreen.kt @@ -44,11 +44,19 @@ fun MetadataScreen( @Composable private fun MetadataContent( - lastBackupTimeMs: Long, + lastBackupTimeMs: Long?, onDismiss: () -> Unit, onBack: () -> Unit, ) { - val latestBackupTime = remember(lastBackupTimeMs) { lastBackupTimeMs.toLocalizedTimestamp() } + val backupTimeText = remember(lastBackupTimeMs) { + runCatching { lastBackupTimeMs?.toLocalizedTimestamp() }.getOrNull() + } + val lastBackupText = backupTimeText + ?.let { stringResource(R.string.security__mnemonic_latest_backup).replace("{time}", it) } + ?: run { + val err = stringResource(R.string.settings__status__backup__error) + stringResource(R.string.security__mnemonic_latest_backup).replace("{time}", err) + } Column( modifier = Modifier @@ -80,9 +88,7 @@ private fun MetadataContent( ) BodyS( - text = stringResource(R.string.security__mnemonic_latest_backup) - .replace("{time}", latestBackupTime) - .withBold(), + text = lastBackupText.withBold(), modifier = Modifier.testTag("backup_time_text") ) @@ -110,3 +116,15 @@ private fun Preview() { ) } } + +@Preview +@Composable +private fun PreviewFailed() { + AppThemeSurface { + MetadataContent( + lastBackupTimeMs = null, + onDismiss = {}, + onBack = {}, + ) + } +} diff --git a/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt index 6d88e87c4..ef6834b5f 100644 --- a/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt @@ -240,8 +240,9 @@ class TransferViewModel @Inject constructor( Logger.debug("Refreshing order '$orderId'") val order = blocktankRepo.getOrder(orderId, refresh = true).getOrNull() if (order == null) { - error = Exception("Order not found '$orderId'") - Logger.error("Order not found '$orderId'", context = TAG) + error = Exception("Order not found '$orderId'").also { + Logger.error(it.message, context = TAG) + } break } @@ -250,8 +251,9 @@ class TransferViewModel @Inject constructor( Logger.debug("LN setup step: $step") if (order.state2 == BtOrderState2.EXPIRED) { - error = Exception("Order expired '$orderId'") - Logger.error("Order expired '$orderId'", context = TAG) + error = Exception("Order expired '$orderId'").also { + Logger.error(it.message, context = TAG) + } break } if (step > 2) { diff --git a/app/src/test/java/to/bitkit/ui/sheets/BoostTransactionViewModelTest.kt b/app/src/test/java/to/bitkit/ui/sheets/BoostTransactionViewModelTest.kt index 85d6421fc..f25be6e2c 100644 --- a/app/src/test/java/to/bitkit/ui/sheets/BoostTransactionViewModelTest.kt +++ b/app/src/test/java/to/bitkit/ui/sheets/BoostTransactionViewModelTest.kt @@ -51,6 +51,7 @@ class BoostTransactionViewModelTest : BaseUnitTest() { confirmed = false, timestamp = 1234567890UL, isBoosted = false, + boostTxIds = emptyList(), isTransfer = false, doesExist = true, confirmTimestamp = null, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c2bfef852..2f9c7e81e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -44,7 +44,7 @@ activity-compose = { module = "androidx.activity:activity-compose", version.ref appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "barcodeScanning" } biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" } -bitkitcore = { module = "com.synonym:bitkit-core-android", version = "0.1.18" } +bitkitcore = { module = "com.synonym:bitkit-core-android", version = "0.1.22" } bouncycastle-provider-jdk = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "bouncyCastle" } camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camera" } camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camera" }