From 52a21b67a1dd6e624748a490e430522f991650e9 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 22 Oct 2026 02:32:28 +0200 Subject: [PATCH 1/8] feat: stop offering new lightning channels --- .../receive/ReceiveAutoTabSelectionTest.kt | 2 - .../receive/ReceiveHardwareFlowTest.kt | 1 - .../to/bitkit/models/ActivityBannerType.kt | 15 +- .../java/to/bitkit/models/ChannelsClosing.kt | 12 ++ .../bitkit/models/ReceiveLiquidityDecision.kt | 68 -------- .../main/java/to/bitkit/models/Suggestion.kt | 6 - .../to/bitkit/repositories/BlocktankRepo.kt | 155 ------------------ .../to/bitkit/repositories/LightningRepo.kt | 4 + .../to/bitkit/repositories/SuggestionsRepo.kt | 18 +- .../to/bitkit/services/MigrationService.kt | 1 - app/src/main/java/to/bitkit/ui/ContentView.kt | 33 +--- .../java/to/bitkit/ui/components/SheetHost.kt | 1 - .../screens/wallets/HardwareWalletScreen.kt | 29 ---- .../bitkit/ui/screens/wallets/HomeScreen.kt | 58 +++---- .../ui/screens/wallets/HomeViewModel.kt | 17 +- .../ui/screens/wallets/SavingsWalletScreen.kt | 55 ------- .../screens/wallets/SpendingWalletScreen.kt | 58 ++----- .../wallets/receive/EditInvoiceScreen.kt | 84 +--------- .../screens/wallets/receive/EditInvoiceVM.kt | 87 ---------- .../wallets/receive/ReceiveQrScreen.kt | 92 +---------- .../screens/wallets/receive/ReceiveSheet.kt | 74 +-------- .../ui/screens/widgets/AddWidgetsScreen.kt | 2 +- .../suggestions/SuggestionsPreviewScreen.kt | 2 +- .../lightning/LightningConnectionsScreen.kt | 3 +- .../to/bitkit/ui/sheets/GiftErrorSheet.kt | 88 ---------- .../java/to/bitkit/ui/sheets/GiftLoading.kt | 149 ----------------- .../java/to/bitkit/ui/sheets/GiftRoute.kt | 23 --- .../java/to/bitkit/ui/sheets/GiftSheet.kt | 97 ----------- .../java/to/bitkit/ui/sheets/GiftViewModel.kt | 155 ------------------ app/src/main/java/to/bitkit/utils/Errors.kt | 1 - .../java/to/bitkit/viewmodels/AppViewModel.kt | 9 +- .../to/bitkit/viewmodels/SettingsViewModel.kt | 3 - .../to/bitkit/viewmodels/WalletViewModel.kt | 1 + app/src/main/res/drawable/arrow.xml | 9 - app/src/main/res/values-ar/strings.xml | 16 +- app/src/main/res/values-b+es+419/strings.xml | 16 +- app/src/main/res/values-ca/strings.xml | 16 +- app/src/main/res/values-cs/strings.xml | 16 +- app/src/main/res/values-de/strings.xml | 16 +- app/src/main/res/values-el/strings.xml | 16 +- app/src/main/res/values-es-rES/strings.xml | 16 +- app/src/main/res/values-es/strings.xml | 16 +- app/src/main/res/values-fr/strings.xml | 16 +- app/src/main/res/values-it/strings.xml | 16 +- app/src/main/res/values-nl/strings.xml | 16 +- app/src/main/res/values-pl/strings.xml | 16 +- app/src/main/res/values-pt-rBR/strings.xml | 16 +- app/src/main/res/values-pt/strings.xml | 16 +- app/src/main/res/values-ru/strings.xml | 18 +- app/src/main/res/values/strings.xml | 16 +- .../models/ReceiveLiquidityDecisionTest.kt | 124 -------------- .../repositories/SuggestionsRepoTest.kt | 3 - .../test/java/to/bitkit/ui/ContentViewTest.kt | 8 - .../ui/screens/wallets/HomeViewModelTest.kt | 28 ++++ .../wallets/receive/EditInvoiceVMTest.kt | 108 ------------ changelog.d/next/1669.removed.md | 1 + 56 files changed, 161 insertions(+), 1781 deletions(-) create mode 100644 app/src/main/java/to/bitkit/models/ChannelsClosing.kt delete mode 100644 app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceVM.kt delete mode 100644 app/src/main/java/to/bitkit/ui/sheets/GiftErrorSheet.kt delete mode 100644 app/src/main/java/to/bitkit/ui/sheets/GiftLoading.kt delete mode 100644 app/src/main/java/to/bitkit/ui/sheets/GiftRoute.kt delete mode 100644 app/src/main/java/to/bitkit/ui/sheets/GiftSheet.kt delete mode 100644 app/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt delete mode 100644 app/src/main/res/drawable/arrow.xml delete mode 100644 app/src/test/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceVMTest.kt create mode 100644 changelog.d/next/1669.removed.md diff --git a/app/src/androidTest/java/to/bitkit/ui/screens/wallets/receive/ReceiveAutoTabSelectionTest.kt b/app/src/androidTest/java/to/bitkit/ui/screens/wallets/receive/ReceiveAutoTabSelectionTest.kt index 954883f335..61796cca25 100644 --- a/app/src/androidTest/java/to/bitkit/ui/screens/wallets/receive/ReceiveAutoTabSelectionTest.kt +++ b/app/src/androidTest/java/to/bitkit/ui/screens/wallets/receive/ReceiveAutoTabSelectionTest.kt @@ -32,7 +32,6 @@ class ReceiveAutoTabSelectionTest { fun keepsTabPickedByTapWhenAutoBecomesAvailable() { setContent() - composeTestRule.onNodeWithTag("Tab-spending").performClick() composeTestRule.onNodeWithTag("Tab-savings").performClick() composeTestRule.waitForIdle() @@ -59,7 +58,6 @@ class ReceiveAutoTabSelectionTest { walletState = WALLET_STATE, lightningState = lightningState, onClickEditInvoice = { editedTabs += it }, - onClickReceiveCjit = {}, ) } } diff --git a/app/src/androidTest/java/to/bitkit/ui/screens/wallets/receive/ReceiveHardwareFlowTest.kt b/app/src/androidTest/java/to/bitkit/ui/screens/wallets/receive/ReceiveHardwareFlowTest.kt index 4a46dabf50..f94a87b257 100644 --- a/app/src/androidTest/java/to/bitkit/ui/screens/wallets/receive/ReceiveHardwareFlowTest.kt +++ b/app/src/androidTest/java/to/bitkit/ui/screens/wallets/receive/ReceiveHardwareFlowTest.kt @@ -56,7 +56,6 @@ class ReceiveHardwareFlowTest { walletState = WALLET_STATE, lightningState = LightningState(), onClickEditInvoice = {}, - onClickReceiveCjit = {}, onClickHardwareEditInvoice = { editState.beginHardwareEdit() isEditing = true diff --git a/app/src/main/java/to/bitkit/models/ActivityBannerType.kt b/app/src/main/java/to/bitkit/models/ActivityBannerType.kt index 7a6cf8a6c8..94d5d161e8 100644 --- a/app/src/main/java/to/bitkit/models/ActivityBannerType.kt +++ b/app/src/main/java/to/bitkit/models/ActivityBannerType.kt @@ -10,17 +10,26 @@ enum class ActivityBannerType( @DrawableRes val icon: Int, @StringRes val title: Int, val color: Color, + val testTag: String, ) { SPENDING( color = Colors.Purple, icon = R.drawable.ic_transfer, - title = R.string.lightning__transfer_in_progress + title = R.string.lightning__transfer_in_progress, + testTag = "TransferToSpendingBanner", ), SAVINGS( color = Colors.Brand, icon = R.drawable.ic_transfer, - title = R.string.lightning__transfer_in_progress - ) + title = R.string.lightning__transfer_in_progress, + testTag = "TransferToSavingsBanner", + ), + CHANNELS_CLOSING( + color = Colors.Yellow, + icon = R.drawable.ic_warning, + title = R.string.lightning__channels_closing_banner, + testTag = "ChannelsClosingBanner", + ), } data class BannerItem( diff --git a/app/src/main/java/to/bitkit/models/ChannelsClosing.kt b/app/src/main/java/to/bitkit/models/ChannelsClosing.kt new file mode 100644 index 0000000000..bdb089aac7 --- /dev/null +++ b/app/src/main/java/to/bitkit/models/ChannelsClosing.kt @@ -0,0 +1,12 @@ +package to.bitkit.models + +import java.time.LocalDate +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle +import java.util.Locale + +/** Date on which Blocktank closes the remaining Lightning channels; set before release. */ +val CHANNELS_CLOSE_DATE: LocalDate = LocalDate.of(2026, 12, 15) + +fun formatChannelsCloseDate(locale: Locale = Locale.getDefault()): String = + CHANNELS_CLOSE_DATE.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(locale)) diff --git a/app/src/main/java/to/bitkit/models/ReceiveLiquidityDecision.kt b/app/src/main/java/to/bitkit/models/ReceiveLiquidityDecision.kt index 43edaf2b09..ac5f8d1f3a 100644 --- a/app/src/main/java/to/bitkit/models/ReceiveLiquidityDecision.kt +++ b/app/src/main/java/to/bitkit/models/ReceiveLiquidityDecision.kt @@ -1,27 +1,5 @@ package to.bitkit.models -enum class ReceiveLiquiditySource { - SAVINGS, - AUTO, - SPENDING, -} - -sealed interface ReceiveAdditionalLiquidityAction { - data object None : ReceiveAdditionalLiquidityAction - data object ChooseAmount : ReceiveAdditionalLiquidityAction - data class CreateCjit(val amountSats: ULong) : ReceiveAdditionalLiquidityAction - data object GeoBlocked : ReceiveAdditionalLiquidityAction -} - -data class ReceiveAdditionalLiquidityParams( - val source: ReceiveLiquiditySource, - val invoiceAmountSats: ULong, - val inboundCapacitySats: ULong?, - val minCjitSats: ULong?, - val maxCjitAmountSats: ULong?, - val isGeoBlocked: Boolean, -) - object ReceiveLiquidityDecision { fun canCreateLightningInvoice( hasReadyChannels: Boolean, @@ -36,50 +14,4 @@ object ReceiveLiquidityDecision { return invoiceAmountSats <= inboundCapacitySats } - - fun additionalLiquidityAction(params: ReceiveAdditionalLiquidityParams): ReceiveAdditionalLiquidityAction { - return when { - params.source != ReceiveLiquiditySource.SPENDING -> ReceiveAdditionalLiquidityAction.None - !needsInboundLiquidity(params.invoiceAmountSats, params.inboundCapacitySats) -> - ReceiveAdditionalLiquidityAction.None - (params.inboundCapacitySats ?: 0uL) == 0uL -> ReceiveAdditionalLiquidityAction.None - params.isGeoBlocked -> ReceiveAdditionalLiquidityAction.GeoBlocked - shouldChooseAmount(params) -> ReceiveAdditionalLiquidityAction.ChooseAmount - else -> ReceiveAdditionalLiquidityAction.CreateCjit(params.invoiceAmountSats) - } - } - - fun needsCjitLimitsForAdditionalLiquidity( - source: ReceiveLiquiditySource, - invoiceAmountSats: ULong, - inboundCapacitySats: ULong?, - isGeoBlocked: Boolean, - ): Boolean { - if (source != ReceiveLiquiditySource.SPENDING) return false - if (!needsInboundLiquidity(invoiceAmountSats, inboundCapacitySats)) return false - if ((inboundCapacitySats ?: 0uL) == 0uL) return false - - return !isGeoBlocked - } - - fun needsInboundLiquidity( - invoiceAmountSats: ULong, - inboundCapacitySats: ULong?, - ): Boolean { - val inbound = inboundCapacitySats ?: 0uL - - if (invoiceAmountSats == 0uL) { - return inbound == 0uL - } - - return invoiceAmountSats > inbound - } - - private fun shouldChooseAmount(params: ReceiveAdditionalLiquidityParams): Boolean { - val min = params.minCjitSats ?: 0uL - val max = params.maxCjitAmountSats?.takeIf { it > 0uL } ?: return true - if (params.invoiceAmountSats == 0uL || min == 0uL) return true - - return params.invoiceAmountSats < min || params.invoiceAmountSats > max - } } diff --git a/app/src/main/java/to/bitkit/models/Suggestion.kt b/app/src/main/java/to/bitkit/models/Suggestion.kt index c66ee2ffb5..942d8f0b1c 100644 --- a/app/src/main/java/to/bitkit/models/Suggestion.kt +++ b/app/src/main/java/to/bitkit/models/Suggestion.kt @@ -25,12 +25,6 @@ enum class Suggestion( color = Colors.Blue24, icon = R.drawable.trezor, ), - LIGHTNING( - title = R.string.cards__lightning__title, - description = R.string.cards__lightning__description, - color = Colors.Purple24, - icon = R.drawable.lightning, - ), BACK_UP( title = R.string.cards__backupSeedPhrase__title, description = R.string.cards__backupSeedPhrase__description, diff --git a/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt b/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt index 26b920b112..f2f4d0ae32 100644 --- a/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt @@ -15,17 +15,12 @@ import com.synonym.bitkitcore.IBtOrder import com.synonym.bitkitcore.IcJitEntry import com.synonym.bitkitcore.calculateChannelLiquidityOptions import com.synonym.bitkitcore.getDefaultLspBalance -import com.synonym.bitkitcore.giftOrder -import com.synonym.bitkitcore.giftPay import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.async -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.delay import kotlinx.coroutines.ensureActive @@ -33,7 +28,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn @@ -47,23 +41,17 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout -import kotlinx.coroutines.withTimeoutOrNull -import org.lightningdevkit.ldknode.Bolt11Invoice import org.lightningdevkit.ldknode.ChannelDetails -import org.lightningdevkit.ldknode.Event -import to.bitkit.async.ServiceQueue import to.bitkit.async.appScope import to.bitkit.data.BlocktankRefundAddress import to.bitkit.data.CacheStore import to.bitkit.di.BgDispatcher import to.bitkit.env.Env -import to.bitkit.ext.calculateRemoteBalance import to.bitkit.ext.nowTimestamp import to.bitkit.ext.runSuspendCatching import to.bitkit.models.BlocktankBackupV1 import to.bitkit.models.CjitQuoteValidator import to.bitkit.models.EUR -import to.bitkit.models.msatCeilOf import to.bitkit.models.safe import to.bitkit.services.CoreService import to.bitkit.services.LightningService @@ -75,8 +63,6 @@ import javax.inject.Inject import javax.inject.Named import javax.inject.Singleton import kotlin.math.ceil -import kotlin.time.Duration -import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.seconds @Singleton @@ -617,131 +603,11 @@ class BlocktankRepo @Inject constructor( } } - suspend fun claimGiftCode( - code: String, - amount: ULong, - waitTimeout: Duration = TIMEOUT_GIFT_CODE, - ): Result = withContext(bgDispatcher) { - runCatching { - require(code.isNotBlank()) { "Gift code cannot be blank" } - require(amount > 0u) { "Gift amount must be positive" } - - Logger.debug("Starting gift code claim: amount=$amount, timeout=$waitTimeout", context = TAG) - - lightningRepo.executeWhenNodeRunning( - operationName = "claimGiftCode", - waitTimeout = waitTimeout, - ) { - delay(PEER_CONNECTION_DELAY_MS) - - val channels = lightningRepo.getChannelsAsync().getOrThrow() - val maxInboundCapacity = channels.calculateRemoteBalance() - - Logger.debug( - "Liquidity check: maxInbound=$maxInboundCapacity, required=$amount", - context = TAG - ) - - if (maxInboundCapacity >= amount) { - Logger.debug("Sufficient liquidity available, claiming with existing channel", context = TAG) - Result.success(claimGiftCodeWithLiquidity(code, amount)) - } else { - Logger.debug("Insufficient liquidity, opening new channel", context = TAG) - Result.success(claimGiftCodeWithoutLiquidity(code, amount)) - } - }.getOrThrow() - }.onFailure { - Logger.error("Failed to claim gift code", it, context = TAG) - } - } - - private suspend fun claimGiftCodeWithLiquidity(code: String, amount: ULong): GiftClaimResult = coroutineScope { - val invoice = lightningRepo.createInvoice( - amountSats = null, - description = "blocktank-gift-code:$code", - expirySeconds = 1.hours.inWholeSeconds.toUInt(), - ).getOrThrow() - - val expectedPaymentHash = Bolt11Invoice.fromStr(invoice).paymentHash() - - Logger.debug("Created invoice for gift code, requesting payment from LSP", context = TAG) - - val paymentReceivedDeferred = async(start = CoroutineStart.UNDISPATCHED) { - lightningRepo.nodeEvents - .filterIsInstance() - .first { it.paymentHash == expectedPaymentHash } - } - - val giftResponse = ServiceQueue.CORE.background { - giftPay(invoice = invoice) - } - - Logger.debug( - "Gift payment request completed: id='${giftResponse.id}', awaiting LDK PaymentReceived", - context = TAG, - ) - - val paymentReceived = withTimeoutOrNull(GIFT_PAYMENT_RECEIVE_TIMEOUT) { - paymentReceivedDeferred.await() - } - - if (paymentReceived == null) { - paymentReceivedDeferred.cancel() - throw ServiceError.GiftClaimPaymentNotReceived() - } - - Logger.debug( - "Gift payment confirmed by LDK: hash='${paymentReceived.paymentHash}', " + - "amountMsat='${paymentReceived.amountMsat}'", - context = TAG, - ) - - val receivedSats = msatCeilOf(paymentReceived.amountMsat).toLong() - - GiftClaimResult.SuccessWithLiquidity( - paymentHashOrTxId = paymentReceived.paymentHash, - sats = receivedSats.takeIf { it > 0 } ?: amount.toLong(), - invoice = invoice, - code = code, - ) - } - - private suspend fun claimGiftCodeWithoutLiquidity(code: String, amount: ULong): GiftClaimResult { - val nodeId = lightningService.nodeId ?: throw ServiceError.NodeNotStarted() - - Logger.debug("Creating gift order for code (insufficient liquidity)", context = TAG) - - val order = ServiceQueue.CORE.background { - giftOrder(clientNodeId = nodeId, code = "blocktank-gift-code:$code") - } - - val orderId = checkNotNull(order.orderId) { "Order ID is null after gift order creation" } - Logger.debug("Gift order created: $orderId", context = TAG) - - val openedOrder = openChannel(orderId).getOrThrow() - Logger.debug("Channel opened for gift order: ${openedOrder.id}", context = TAG) - - val fundingTxId = openedOrder.channel?.fundingTx?.id - if (fundingTxId == null) { - Logger.warn("Channel opened but funding transaction ID is null", context = TAG) - } - - return GiftClaimResult.SuccessWithoutLiquidity( - paymentHashOrTxId = fundingTxId ?: orderId, - sats = amount.toLong(), - invoice = openedOrder.payment?.bolt11Invoice?.request ?: "", - code = code, - ) - } - companion object { private const val TAG = "BlocktankRepo" private const val REFUND_ADDRESS_ALLOCATION_LIMIT = 20 private const val DEFAULT_CHANNEL_EXPIRY_WEEKS = 6u private const val DEFAULT_SOURCE = "bitkit-android" - private const val PEER_CONNECTION_DELAY_MS = 2_000L - private val TIMEOUT_GIFT_CODE = 30.seconds - private val GIFT_PAYMENT_RECEIVE_TIMEOUT = 45.seconds private const val CJIT_REFRESH_ATTEMPTS = 3 private val CJIT_REFRESH_TIMEOUT = 5.seconds private val CJIT_REFRESH_RETRY_DELAY = 1.seconds @@ -785,24 +651,3 @@ data class BlocktankState( val info: IBtInfo? = null, val minCjitSats: Int? = null, ) - -sealed class GiftClaimResult { - abstract val paymentHashOrTxId: String - abstract val sats: Long - abstract val invoice: String - abstract val code: String - - data class SuccessWithLiquidity( - override val paymentHashOrTxId: String, - override val sats: Long, - override val invoice: String, - override val code: String, - ) : GiftClaimResult() - - data class SuccessWithoutLiquidity( - override val paymentHashOrTxId: String, - override val sats: Long, - override val invoice: String, - override val code: String, - ) : GiftClaimResult() -} diff --git a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt index d1752edce9..c51a06a148 100644 --- a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt @@ -138,6 +138,10 @@ class LightningRepo @Inject constructor( private val _lightningState = MutableStateFlow(LightningState()) val lightningState = _lightningState.asStateFlow() + val hasTrustedChannels = lightningState + .map { separateTrustedChannels(it.channels).first.isNotEmpty() } + .distinctUntilChanged() + private val _nodeEventUpdates = MutableSharedFlow(extraBufferCapacity = 64) val nodeEventUpdates = _nodeEventUpdates.asSharedFlow() val nodeEvents = nodeEventUpdates.map { it.event } diff --git a/app/src/main/java/to/bitkit/repositories/SuggestionsRepo.kt b/app/src/main/java/to/bitkit/repositories/SuggestionsRepo.kt index 6d88fe02f9..ff44e53e00 100644 --- a/app/src/main/java/to/bitkit/repositories/SuggestionsRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/SuggestionsRepo.kt @@ -7,10 +7,8 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import to.bitkit.data.SettingsData import to.bitkit.data.SettingsStore -import to.bitkit.data.entities.TransferEntity import to.bitkit.di.BgDispatcher import to.bitkit.models.Suggestion -import to.bitkit.models.TransferType import to.bitkit.models.toSuggestionOrNull import javax.inject.Inject import javax.inject.Singleton @@ -20,7 +18,6 @@ class SuggestionsRepo @Inject constructor( @BgDispatcher private val bgDispatcher: CoroutineDispatcher, private val walletRepo: WalletRepo, private val settingsStore: SettingsStore, - private val transferRepo: TransferRepo, private val pubkyRepo: PubkyRepo, private val hwWalletRepo: HwWalletRepo, ) { @@ -31,17 +28,16 @@ class SuggestionsRepo @Inject constructor( val suggestionsFlow: Flow> = combine( walletRepo.balanceState, settingsStore.data, - transferRepo.activeTransfers, pubkyRepo.isAuthenticated, hwWalletRepo.wallets, - ) { balanceState, settings, transfers, profileAuthenticated, hardwareWallets -> + ) { balanceState, settings, profileAuthenticated, hardwareWallets -> val hasHardwareWallet = hardwareWallets.isNotEmpty() val baseSuggestions = when { balanceState.totalLightningSats > 0uL -> spendingSuggestions(settings, profileAuthenticated, hasHardwareWallet) balanceState.totalOnchainSats > 0uL -> - savingsOnlySuggestions(settings, transfers, profileAuthenticated, hasHardwareWallet) - else -> emptyWalletSuggestions(settings, transfers, profileAuthenticated, hasHardwareWallet) + savingsOnlySuggestions(settings, profileAuthenticated, hasHardwareWallet) + else -> emptyWalletSuggestions(settings, profileAuthenticated, hasHardwareWallet) } val dismissedList = settings.dismissedSuggestions.mapNotNull { it.toSuggestionOrNull() } baseSuggestions @@ -72,15 +68,11 @@ class SuggestionsRepo @Inject constructor( private fun savingsOnlySuggestions( settings: SettingsData, - transfers: List, profileAuthenticated: Boolean, hasHardwareWallet: Boolean, ) = listOfNotNull( Suggestion.BACK_UP.takeIf { !settings.backupVerified }, Suggestion.SECURE.takeIf { !settings.isPinEnabled }, - Suggestion.LIGHTNING.takeIf { - transfers.all { it.type != TransferType.TO_SPENDING } - }, Suggestion.HARDWARE.takeIf { !hasHardwareWallet }, Suggestion.SUPPORT, Suggestion.PROFILE.takeIf { !profileAuthenticated }, @@ -90,14 +82,10 @@ class SuggestionsRepo @Inject constructor( private fun emptyWalletSuggestions( settings: SettingsData, - transfers: List, profileAuthenticated: Boolean, hasHardwareWallet: Boolean, ) = listOfNotNull( Suggestion.BUY, - Suggestion.LIGHTNING.takeIf { - transfers.all { it.type != TransferType.TO_SPENDING } - }, Suggestion.HARDWARE.takeIf { !hasHardwareWallet }, Suggestion.SUPPORT, Suggestion.BACK_UP.takeIf { !settings.backupVerified }, diff --git a/app/src/main/java/to/bitkit/services/MigrationService.kt b/app/src/main/java/to/bitkit/services/MigrationService.kt index eaa3af3c4f..3e9e6610cc 100644 --- a/app/src/main/java/to/bitkit/services/MigrationService.kt +++ b/app/src/main/java/to/bitkit/services/MigrationService.kt @@ -1079,7 +1079,6 @@ class MigrationService internal constructor( val mapping = mapOf( "backupSeedPhrase" to Suggestion.BACK_UP, "buyBitcoin" to Suggestion.BUY, - "lightning" to Suggestion.LIGHTNING, "quickpay" to Suggestion.QUICK_PAY, "shop" to Suggestion.SHOP, "slashtagsProfile" to Suggestion.PROFILE, diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt index fff4a35de1..959cc54849 100644 --- a/app/src/main/java/to/bitkit/ui/ContentView.kt +++ b/app/src/main/java/to/bitkit/ui/ContentView.kt @@ -219,7 +219,6 @@ 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 import to.bitkit.ui.sheets.LnurlAuthSheet import to.bitkit.ui.sheets.PinSheet @@ -658,7 +657,6 @@ fun ContentView( ) is Sheet.BTCPayConnection -> BTCPayConnectionSheet(sheet, appViewModel) - is Sheet.Gift -> GiftSheet(sheet, appViewModel) is Sheet.QrScanner -> QrScanningSheet(sheet, appViewModel) is Sheet.PubkyAuth -> PubkyAuthApprovalSheet( authUrl = sheet.authUrl, @@ -1292,28 +1290,22 @@ private fun NavGraphBuilder.home( ) } deepLinkableComposable { - val hasSeenSpendingIntro by settingsViewModel.hasSeenSpendingIntro.collectAsStateWithLifecycle() - val isGeoBlocked by appViewModel.isGeoBlocked.collectAsStateWithLifecycle() val onchainActivities by activityListViewModel.onchainActivities.collectAsStateWithLifecycle() val forceCloseRemainingDuration by appViewModel.forceCloseRemainingDuration.collectAsStateWithLifecycle() SavingsWalletScreen( - isGeoBlocked = isGeoBlocked, onchainActivities = onchainActivities ?: persistentListOf(), onAllActivityButtonClick = { navController.navigateToAllActivity(activityListViewModel::clearFilters) }, onActivityItemClick = { navController.navToActivityDetail(it) }, onEmptyActivityRowClick = { appViewModel.showSheet(Sheet.Receive()) }, - onTransferToSpendingClick = { - navController.navigateToTransferSpendingStart(hasSeenSpendingIntro) - }, onBackClick = { navController.popBackStack() }, forceCloseRemainingDuration = forceCloseRemainingDuration, ) } deepLinkableComposable { val hasSeenSavingsIntro by settingsViewModel.hasSeenSavingsIntro.collectAsStateWithLifecycle() - val hasSeenSpendingIntro by settingsViewModel.hasSeenSpendingIntro.collectAsStateWithLifecycle() val lightningState by walletViewModel.lightningState.collectAsStateWithLifecycle() + val hasTrustedChannels by walletViewModel.hasTrustedChannels.collectAsStateWithLifecycle(initialValue = false) val lightningActivities by activityListViewModel.lightningActivities.collectAsStateWithLifecycle() SpendingWalletScreen( @@ -1329,21 +1321,15 @@ private fun NavGraphBuilder.home( navController.navigateToTransferSavingsAvailability() } }, - onTransferFromSavingsClick = { - navController.navigateToTransferSpendingStart(hasSeenSpendingIntro) - }, onBackClick = { navController.popBackStack() }, + showChannelsClosingNotice = hasTrustedChannels, ) } deepLinkableComposable { val walletId = it.toRoute().walletId - val hasSeenSpendingIntro by settingsViewModel.hasSeenSpendingIntro.collectAsStateWithLifecycle() HardwareWalletScreen( walletId = walletId, onActivityItemClick = { navController.navToActivityDetail(it) }, - onTransferToSpendingClick = { selectedDeviceId -> - navController.navigateToTransferSpendingStart(hasSeenSpendingIntro, selectedDeviceId) - }, onBackClick = { navController.popBackStack() }, ) } @@ -2209,11 +2195,6 @@ fun NavController.navigateOnSavingsTransferExit() { fun NavController.navigateToTransferSpendingStart(hasSeenSpendingIntro: Boolean) = navigateTo(transferSpendingStartRoute(hasSeenSpendingIntro)) -fun NavController.navigateToTransferSpendingStart( - hasSeenSpendingIntro: Boolean, - walletId: String, -) = navigateTo(transferSpendingStartRoute(hasSeenSpendingIntro, walletId)) - internal fun shouldDismissSheetForScreenLink(handled: Boolean, currentSheet: Sheet?): Boolean = handled && currentSheet != null @@ -2239,16 +2220,6 @@ internal fun transferSpendingStartRoute(hasSeenSpendingIntro: Boolean): Routes = else -> Routes.SpendingIntro } -internal fun transferSpendingStartRoute( - hasSeenSpendingIntro: Boolean, - walletId: String, -): Routes = when { - hasSeenSpendingIntro -> Routes.SpendingAmountHw(walletId) - else -> Routes.SpendingIntroHw(walletId) -} - -fun NavController.navigateToTransferIntro() = navigateTo(Routes.TransferIntro) - fun NavController.navigateToTransferFunding() = navigateTo(Routes.Funding) fun NavController.navToActivityDetail(activity: Activity) = navigateTo( 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 ff1b102332..b16ba57cae 100644 --- a/app/src/main/java/to/bitkit/ui/components/SheetHost.kt +++ b/app/src/main/java/to/bitkit/ui/components/SheetHost.kt @@ -96,7 +96,6 @@ sealed interface Sheet { data object ActivityTagSelector : Sheet data class LnurlAuth(val domain: String, val lnurl: String, val k1: String) : Sheet data object ForceTransfer : Sheet - data class Gift(val code: String, val amount: ULong) : Sheet data object ConnectionClosed : Sheet data class BTCPayConnection( val setup: SamRockSetupRequest, diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/HardwareWalletScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/HardwareWalletScreen.kt index 050ee0eaa0..990f167d13 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/HardwareWalletScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/HardwareWalletScreen.kt @@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -45,7 +44,6 @@ import to.bitkit.models.HwWallet import to.bitkit.models.HwWalletVendor import to.bitkit.models.TransportType import to.bitkit.ui.components.BalanceHeaderView -import to.bitkit.ui.components.SecondaryButton import to.bitkit.ui.components.StatusBarSpacer import to.bitkit.ui.components.TabBar import to.bitkit.ui.components.TertiaryButton @@ -65,7 +63,6 @@ import to.bitkit.ui.theme.TopBarGradient fun HardwareWalletScreen( walletId: String, onActivityItemClick: (Activity) -> Unit, - onTransferToSpendingClick: (String) -> Unit, onBackClick: () -> Unit, viewModel: HwWalletViewModel = hiltViewModel(), ) { @@ -84,7 +81,6 @@ fun HardwareWalletScreen( wallet = device, showRemoveDialog = uiState.isPendingRemoval != null, onActivityItemClick = onActivityItemClick, - onTransferToSpendingClick = onTransferToSpendingClick, onRemoveClick = { viewModel.onRemoveClick(device) }, onConfirmRemove = { viewModel.removeDevice(walletId) }, onDismissRemoveDialog = viewModel::onDismissRemoveDialog, @@ -100,7 +96,6 @@ private fun HardwareWalletContent( wallet: HwWallet, showRemoveDialog: Boolean, onActivityItemClick: (Activity) -> Unit, - onTransferToSpendingClick: (String) -> Unit, onRemoveClick: () -> Unit, onConfirmRemove: () -> Unit, onDismissRemoveDialog: () -> Unit, @@ -109,7 +104,6 @@ private fun HardwareWalletContent( onKeepBackupDataChange: (Boolean) -> Unit = {}, ) { val hasFunds = wallet.balanceSats > 0uL - val hasFundingFunds = wallet.fundingBalanceSats > 0uL val hasActivity = wallet.activities.isNotEmpty() val showEmptyState = !hasFunds && !hasActivity @@ -176,24 +170,6 @@ private fun HardwareWalletContent( if (!showEmptyState) { item { VerticalSpacer(32.dp) } - if (hasFundingFunds) { - item { - SecondaryButton( - onClick = { onTransferToSpendingClick(wallet.id) }, - text = stringResource(R.string.wallet__transfer_to_spending), - icon = { - Icon( - painter = painterResource(R.drawable.ic_transfer), - contentDescription = null, - modifier = Modifier.size(16.dp) - ) - }, - hazeState = hazeState, - modifier = Modifier.testTag("HardwareTransferToSpending") - ) - } - } - activityListGroupedItems( items = wallet.activities, onActivityItemClick = onActivityItemClick, @@ -277,7 +253,6 @@ private fun Preview() { wallet = previewWallet(), showRemoveDialog = false, onActivityItemClick = {}, - onTransferToSpendingClick = { _ -> }, onRemoveClick = {}, onConfirmRemove = {}, onDismissRemoveDialog = {}, @@ -297,7 +272,6 @@ private fun PreviewJade() { wallet = previewWallet(vendor = HwWalletVendor.BLOCKSTREAM), showRemoveDialog = false, onActivityItemClick = {}, - onTransferToSpendingClick = { _ -> }, onRemoveClick = {}, onConfirmRemove = {}, onDismissRemoveDialog = {}, @@ -317,7 +291,6 @@ private fun PreviewNoActivity() { wallet = previewWallet(activities = persistentListOf()), showRemoveDialog = false, onActivityItemClick = {}, - onTransferToSpendingClick = { _ -> }, onRemoveClick = {}, onConfirmRemove = {}, onDismissRemoveDialog = {}, @@ -337,7 +310,6 @@ private fun PreviewEmpty() { wallet = previewWallet(balanceSats = 0uL, activities = persistentListOf()), showRemoveDialog = false, onActivityItemClick = {}, - onTransferToSpendingClick = { _ -> }, onRemoveClick = {}, onConfirmRemove = {}, onDismissRemoveDialog = {}, @@ -357,7 +329,6 @@ private fun PreviewRemoveDialog() { wallet = previewWallet(), showRemoveDialog = true, onActivityItemClick = {}, - onTransferToSpendingClick = { _ -> }, onRemoveClick = {}, onConfirmRemove = {}, onDismissRemoveDialog = {}, diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt index 5c75d59a64..df71bd0860 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt @@ -169,8 +169,6 @@ import to.bitkit.ui.navToActivityDetail import to.bitkit.ui.navigateTo import to.bitkit.ui.navigateToAllActivity import to.bitkit.ui.navigateToProfile -import to.bitkit.ui.navigateToTransferFunding -import to.bitkit.ui.navigateToTransferIntro import to.bitkit.ui.scaffold.AppAlertDialog import to.bitkit.ui.screens.wallets.activity.components.ActivityListSimple import to.bitkit.ui.screens.wallets.activity.utils.previewActivityItems @@ -242,7 +240,6 @@ fun HomeScreen( ) { val context = LocalContext.current val resources = LocalResources.current - val hasSeenTransferIntro by settingsViewModel.hasSeenTransferIntro.collectAsStateWithLifecycle() val hasSeenShopIntro by settingsViewModel.hasSeenShopIntro.collectAsStateWithLifecycle() val hasSeenProfileIntro by settingsViewModel.hasSeenProfileIntro.collectAsStateWithLifecycle() val profileNavigationScope = rememberCoroutineScope() @@ -318,14 +315,6 @@ fun HomeScreen( appViewModel.showSheet(Sheet.Hardware()) } - Suggestion.LIGHTNING -> { - if (!hasSeenTransferIntro) { - rootNavController.navigateToTransferIntro() - } else { - rootNavController.navigateToTransferFunding() - } - } - Suggestion.BACK_UP -> { appViewModel.showSheet(Sheet.Backup(BackupRoute.Intro)) } @@ -704,31 +693,34 @@ private fun WalletPage( VerticalSpacer(32.dp) if (!homeUiState.showEmptyState) { - if (hasActivity) { - AnimatedVisibility(homeUiState.banners.isNotEmpty()) { - Column( - verticalArrangement = Arrangement.spacedBy(16.dp), - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 16.dp) - ) { - homeUiState.banners.forEach { banner -> - ActivityBanner( - gradientColor = banner.type.color, - title = banner.title, - icon = banner.type.icon, - onClick = { - when (banner.type) { - ActivityBannerType.SPENDING -> onNavigateToSettingUp() - ActivityBannerType.SAVINGS -> Unit - } - }, - modifier = Modifier.fillMaxWidth() - ) - } + AnimatedVisibility(homeUiState.banners.isNotEmpty()) { + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp) + ) { + homeUiState.banners.forEach { banner -> + ActivityBanner( + gradientColor = banner.type.color, + title = banner.title, + icon = banner.type.icon, + onClick = { + when (banner.type) { + ActivityBannerType.SPENDING -> onNavigateToSettingUp() + ActivityBannerType.SAVINGS -> Unit + ActivityBannerType.CHANNELS_CLOSING -> onNavigateToSpending() + } + }, + modifier = Modifier + .fillMaxWidth() + .testTag(banner.type.testTag) + ) } } + } + if (hasActivity) { ActivityListSimple( items = latestActivities, maxItems = (slotCapacity - countNonItemSlots(homeUiState)).coerceAtLeast(0), diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeViewModel.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeViewModel.kt index 57a6b36f7e..408e778da2 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeViewModel.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeViewModel.kt @@ -25,12 +25,14 @@ import to.bitkit.models.ActivityBannerType import to.bitkit.models.BannerItem import to.bitkit.models.Suggestion import to.bitkit.models.WidgetType +import to.bitkit.models.formatChannelsCloseDate import to.bitkit.models.widget.ArticleModel import to.bitkit.models.widget.toArticleModel import to.bitkit.models.widget.toBlockModel import to.bitkit.repositories.ActivityRepo import to.bitkit.repositories.CurrencyRepo import to.bitkit.repositories.HwWalletRepo +import to.bitkit.repositories.LightningRepo import to.bitkit.repositories.PubkyRepo import to.bitkit.repositories.SuggestionsRepo import to.bitkit.repositories.TransferRepo @@ -50,6 +52,7 @@ class HomeViewModel @Inject constructor( private val currencyRepo: CurrencyRepo, private val settingsStore: SettingsStore, private val transferRepo: TransferRepo, + private val lightningRepo: LightningRepo, private val pubkyRepo: PubkyRepo, private val activityRepo: ActivityRepo, private val hwWalletRepo: HwWalletRepo, @@ -306,7 +309,8 @@ class HomeViewModel @Inject constructor( combine( walletRepo.balanceState, transferRepo.forceCloseRemainingDuration, - ) { balanceState, remainingDuration -> + lightningRepo.hasTrustedChannels, + ) { balanceState, remainingDuration, hasTrustedChannels -> val defaultTitle = context.getString(R.string.lightning__transfer_in_progress) val savingsTitle = remainingDuration?.let { context.getString(R.string.lightning__transfer_ready_in, it) @@ -321,6 +325,17 @@ class HomeViewModel @Inject constructor( type = ActivityBannerType.SAVINGS, title = savingsTitle, ).takeIf { balanceState.balanceInTransferToSavings > 0uL }, + if (hasTrustedChannels) { + BannerItem( + type = ActivityBannerType.CHANNELS_CLOSING, + title = context.getString( + R.string.lightning__channels_closing_banner, + formatChannelsCloseDate().uppercase(), + ), + ) + } else { + null + }, ) }.collect { banners -> _uiState.update { it.copy(banners = banners.toImmutableList()) } diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt index f9aa4c5da8..061344eb50 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt @@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -41,7 +40,6 @@ import to.bitkit.ui.LocalBalances import to.bitkit.ui.components.BalanceHeaderView import to.bitkit.ui.components.EmptyStateView import to.bitkit.ui.components.IncomingTransfer -import to.bitkit.ui.components.SecondaryButton import to.bitkit.ui.components.StatusBarSpacer import to.bitkit.ui.components.TabBar import to.bitkit.ui.components.TopBarSpacer @@ -58,12 +56,10 @@ import to.bitkit.ui.utils.withAccent @Composable fun SavingsWalletScreen( - isGeoBlocked: Boolean, onchainActivities: ImmutableList, onAllActivityButtonClick: () -> Unit, onEmptyActivityRowClick: () -> Unit, onActivityItemClick: (Activity) -> Unit, - onTransferToSpendingClick: () -> Unit, onBackClick: () -> Unit, forceCloseRemainingDuration: String? = null, balances: BalanceState = LocalBalances.current, @@ -73,10 +69,6 @@ fun SavingsWalletScreen( val hasActivity = onchainActivities.isNotEmpty() mutableStateOf(!hasFunds && !hasActivity) } - val canTransfer by remember(balances.totalOnchainSats) { - val hasFunds = balances.totalOnchainSats > 0uL - mutableStateOf(hasFunds && !isGeoBlocked) - } val hazeState = rememberHazeState() @@ -152,25 +144,6 @@ fun SavingsWalletScreen( if (!showEmptyState) { item { VerticalSpacer(32.dp) } - if (canTransfer) { - item { - SecondaryButton( - onClick = onTransferToSpendingClick, - text = stringResource(R.string.wallet__transfer_to_spending), - icon = { - Icon( - painter = painterResource(R.drawable.ic_transfer), - contentDescription = null, - modifier = Modifier.size(16.dp) - ) - }, - hazeState = hazeState, - modifier = Modifier.testTag("TransferToSpending") - ) - } - item { VerticalSpacer(32.dp) } - } - activityListGroupedItems( items = onchainActivities, onActivityItemClick = onActivityItemClick, @@ -198,12 +171,10 @@ private fun Preview() { AppThemeSurface { Box { SavingsWalletScreen( - isGeoBlocked = false, onchainActivities = previewOnchainActivityItems(), onAllActivityButtonClick = {}, onActivityItemClick = {}, onEmptyActivityRowClick = {}, - onTransferToSpendingClick = {}, onBackClick = {}, balances = BalanceState(totalOnchainSats = 50_000u), ) @@ -218,12 +189,10 @@ private fun PreviewTransfer() { AppThemeSurface { Box { SavingsWalletScreen( - isGeoBlocked = false, onchainActivities = previewOnchainActivityItems(), onAllActivityButtonClick = {}, onActivityItemClick = {}, onEmptyActivityRowClick = {}, - onTransferToSpendingClick = {}, onBackClick = {}, balances = BalanceState( totalOnchainSats = 50_000u, @@ -241,32 +210,10 @@ private fun PreviewNoActivity() { AppThemeSurface { Box { SavingsWalletScreen( - isGeoBlocked = false, onchainActivities = persistentListOf(), onAllActivityButtonClick = {}, onActivityItemClick = {}, onEmptyActivityRowClick = {}, - onTransferToSpendingClick = {}, - onBackClick = {}, - balances = BalanceState(totalOnchainSats = 50_000u), - ) - TabBar() - } - } -} - -@Preview(showSystemUi = true) -@Composable -private fun PreviewGeoBlocked() { - AppThemeSurface { - Box { - SavingsWalletScreen( - isGeoBlocked = true, - onchainActivities = previewOnchainActivityItems(), - onAllActivityButtonClick = {}, - onActivityItemClick = {}, - onEmptyActivityRowClick = {}, - onTransferToSpendingClick = {}, onBackClick = {}, balances = BalanceState(totalOnchainSats = 50_000u), ) @@ -281,12 +228,10 @@ private fun PreviewEmpty() { AppThemeSurface { Box { SavingsWalletScreen( - isGeoBlocked = false, onchainActivities = persistentListOf(), onAllActivityButtonClick = {}, onActivityItemClick = {}, onEmptyActivityRowClick = {}, - onTransferToSpendingClick = {}, onBackClick = {}, ) TabBar() diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt index 2f2a40b2ad..180ff9d3f9 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt @@ -23,6 +23,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -39,8 +40,10 @@ import org.lightningdevkit.ldknode.ChannelDetails import to.bitkit.R import to.bitkit.ext.createChannelDetails import to.bitkit.models.BalanceState +import to.bitkit.models.formatChannelsCloseDate import to.bitkit.ui.LocalBalances import to.bitkit.ui.components.BalanceHeaderView +import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.EmptyStateView import to.bitkit.ui.components.IncomingTransfer import to.bitkit.ui.components.SecondaryButton @@ -66,8 +69,8 @@ fun SpendingWalletScreen( onActivityItemClick: (Activity) -> Unit, onEmptyActivityRowClick: () -> Unit, onTransferToSavingsClick: () -> Unit, - onTransferFromSavingsClick: () -> Unit, onBackClick: () -> Unit, + showChannelsClosingNotice: Boolean = false, balances: BalanceState = LocalBalances.current, ) { val showEmptyState by remember(balances.totalLightningSats, lightningActivities.size) { @@ -80,9 +83,8 @@ fun SpendingWalletScreen( val hasChannels = channels.isNotEmpty() mutableStateOf(hasLnBalance && hasChannels) } - val canTransferFromSavings by remember(showEmptyState, balances.totalOnchainSats) { - mutableStateOf(showEmptyState && balances.totalOnchainSats > 0uL) - } + val locale = LocalConfiguration.current.locales[0] + val channelsCloseDate = remember(locale) { formatChannelsCloseDate(locale) } val hazeState = rememberHazeState() Box( @@ -152,22 +154,14 @@ fun SpendingWalletScreen( } } - if (canTransferFromSavings) { - item { VerticalSpacer(32.dp) } - + if (showChannelsClosingNotice) { item { - SecondaryButton( - onClick = onTransferFromSavingsClick, - text = stringResource(R.string.lightning__funding__button1), - icon = { - Icon( - painter = painterResource(R.drawable.ic_transfer), - contentDescription = null, - modifier = Modifier.size(16.dp) - ) - }, - hazeState = hazeState, - modifier = Modifier.testTag("TransferFromSavings") + BodyM( + text = stringResource(R.string.lightning__channels_closing_notice, channelsCloseDate), + color = Colors.White64, + modifier = Modifier + .padding(top = 16.dp) + .testTag("ChannelsClosingNotice") ) } } @@ -227,8 +221,8 @@ private fun Preview() { onActivityItemClick = {}, onEmptyActivityRowClick = {}, onTransferToSavingsClick = {}, - onTransferFromSavingsClick = {}, onBackClick = {}, + showChannelsClosingNotice = true, balances = BalanceState(totalLightningSats = 50_000u), ) TabBar() @@ -248,7 +242,6 @@ private fun PreviewTransfer() { onActivityItemClick = {}, onEmptyActivityRowClick = {}, onTransferToSavingsClick = {}, - onTransferFromSavingsClick = {}, onBackClick = {}, balances = BalanceState( totalLightningSats = 50_000u, @@ -272,7 +265,6 @@ private fun PreviewNoActivity() { onActivityItemClick = {}, onEmptyActivityRowClick = {}, onTransferToSavingsClick = {}, - onTransferFromSavingsClick = {}, onBackClick = {}, balances = BalanceState(totalLightningSats = 50_000u), ) @@ -293,29 +285,7 @@ private fun PreviewEmpty() { onActivityItemClick = {}, onEmptyActivityRowClick = {}, onTransferToSavingsClick = {}, - onTransferFromSavingsClick = {}, - onBackClick = {}, - ) - TabBar() - } - } -} - -@Preview(showSystemUi = true) -@Composable -private fun PreviewEmptyWithSavings() { - AppThemeSurface { - Box { - SpendingWalletScreen( - channels = persistentListOf(), - lightningActivities = persistentListOf(), - onAllActivityButtonClick = {}, - onActivityItemClick = {}, - onEmptyActivityRowClick = {}, - onTransferToSavingsClick = {}, - onTransferFromSavingsClick = {}, onBackClick = {}, - balances = BalanceState(totalOnchainSats = 100_000u), ) TabBar() } diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceScreen.kt index 295d21da45..d39a62a6df 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceScreen.kt @@ -21,38 +21,25 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.TextField 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.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.tooling.preview.Devices.NEXUS_5 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 kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import to.bitkit.R -import to.bitkit.ext.runSuspendCatching -import to.bitkit.models.ReceiveAdditionalLiquidityAction -import to.bitkit.models.ReceiveLiquiditySource -import to.bitkit.models.ReceiveLiquiditySource.AUTO -import to.bitkit.models.ReceiveLiquiditySource.SAVINGS -import to.bitkit.models.ReceiveLiquiditySource.SPENDING import to.bitkit.repositories.CurrencyState -import to.bitkit.repositories.LightningState import to.bitkit.repositories.WalletState import to.bitkit.ui.LocalCurrencies -import to.bitkit.ui.appViewModel -import to.bitkit.ui.blocktankViewModel import to.bitkit.ui.components.AddTagButton import to.bitkit.ui.components.BodySSB import to.bitkit.ui.components.BottomSheetPreview @@ -72,8 +59,6 @@ import to.bitkit.ui.theme.AppTextFieldDefaults import to.bitkit.ui.theme.AppTextStyles import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors -import to.bitkit.utils.Logger -import to.bitkit.utils.ServiceError import to.bitkit.viewmodels.AmountInputViewModel import to.bitkit.viewmodels.previewAmountInputViewModel @@ -82,8 +67,6 @@ import to.bitkit.viewmodels.previewAmountInputViewModel fun EditInvoiceScreen( amountInputViewModel: AmountInputViewModel, walletUiState: WalletState, - lightningState: LightningState, - sourceTab: ReceiveTab, updateInvoice: (ULong?) -> Unit, onClickAddTag: () -> Unit, onClickTag: (String) -> Unit, @@ -91,62 +74,12 @@ fun EditInvoiceScreen( showPaymentRequestButton: Boolean, onClickPaymentRequest: (amountSats: ULong, note: String) -> Unit, onBack: () -> Unit, - navigateReceiveConfirm: (CjitEntryDetails) -> Unit, onchainOnly: Boolean = false, updateOnchainInvoice: (ULong?) -> Unit = {}, - navigateCjitAmount: () -> Unit, - navigateGeoBlock: () -> Unit, currencies: CurrencyState = LocalCurrencies.current, - editInvoiceVM: EditInvoiceVM = hiltViewModel(), ) { - val app = appViewModel ?: return - val context = LocalContext.current - val blocktankVM = blocktankViewModel ?: return var keyboardVisible by remember { mutableStateOf(false) } - var isCreatingCjit by remember { mutableStateOf(false) } val amountInputUiState by amountInputViewModel.uiState.collectAsStateWithLifecycle() - val currentReceiveSats by rememberUpdatedState(amountInputUiState.sats.toULong()) - val isLoading by editInvoiceVM.isLoading.collectAsStateWithLifecycle() - - LaunchedEffect(onchainOnly) { - if (onchainOnly) return@LaunchedEffect - editInvoiceVM.editInvoiceEffect.collect { effect -> - val receiveSats = currentReceiveSats - when (effect) { - is EditInvoiceVM.EditInvoiceScreenEffects.ApplyReceiveLiquidityAction -> { - when (val action = effect.action) { - ReceiveAdditionalLiquidityAction.None -> { - updateInvoice(receiveSats) - onBack() - } - ReceiveAdditionalLiquidityAction.ChooseAmount -> { - updateInvoice(receiveSats) - navigateCjitAmount() - } - is ReceiveAdditionalLiquidityAction.CreateCjit -> { - isCreatingCjit = true - runSuspendCatching { - val entry = blocktankVM.createCjit(action.amountSats) - CjitEntryDetails.from(entry, action.amountSats).getOrThrow() - }.onSuccess { - navigateReceiveConfirm(it) - }.onFailure { - Logger.error("Failed to create CJIT invoice", it, context = "EditInvoiceScreen") - if (!app.toastReceiveCjitError(context, it) && - it !is ServiceError.ChannelSizeExceedsMaximum - ) { - app.toast(it) - } - navigateCjitAmount() - } - isCreatingCjit = false - } - ReceiveAdditionalLiquidityAction.GeoBlocked -> navigateGeoBlock() - } - } - } - } - } EditInvoiceContent( amountInputViewModel = amountInputViewModel, @@ -165,17 +98,13 @@ fun EditInvoiceScreen( }, onContinueKeyboard = { keyboardVisible = false }, onContinueGeneral = { - editInvoiceVM.onClickContinue( - source = sourceTab.toReceiveLiquiditySource(), - amountSats = amountInputUiState.sats.toULong(), - isGeoBlocked = lightningState.isGeoBlocked, - ) + updateInvoice(amountInputUiState.sats.toULong()) + onBack() }, onContinueOnchain = { amountSats -> updateOnchainInvoice(amountSats) onBack() }, - isLoading = isLoading || isCreatingCjit, onClickAddTag = onClickAddTag, onClickTag = onClickTag, showPaymentRequestButton = showPaymentRequestButton, @@ -186,15 +115,6 @@ fun EditInvoiceScreen( ) } -private fun ReceiveTab.toReceiveLiquiditySource(): ReceiveLiquiditySource { - return when (this) { - ReceiveTab.SAVINGS -> SAVINGS - ReceiveTab.AUTO -> AUTO - ReceiveTab.SPENDING -> SPENDING - ReceiveTab.HARDWARE, ReceiveTab.USDT -> SAVINGS - } -} - @Suppress("ViewModelForwarding") @Composable fun EditInvoiceContent( diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceVM.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceVM.kt deleted file mode 100644 index 654a0dcf43..0000000000 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceVM.kt +++ /dev/null @@ -1,87 +0,0 @@ -package to.bitkit.ui.screens.wallets.receive - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asSharedFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import to.bitkit.models.ReceiveAdditionalLiquidityAction -import to.bitkit.models.ReceiveAdditionalLiquidityParams -import to.bitkit.models.ReceiveLiquidityDecision -import to.bitkit.models.ReceiveLiquiditySource -import to.bitkit.repositories.BlocktankRepo -import to.bitkit.repositories.WalletRepo -import javax.inject.Inject - -@HiltViewModel -class EditInvoiceVM @Inject constructor( - private val walletRepo: WalletRepo, - private val blocktankRepo: BlocktankRepo, -) : ViewModel() { - - private val _editInvoiceEffect = MutableSharedFlow(extraBufferCapacity = 1) - val editInvoiceEffect = _editInvoiceEffect.asSharedFlow() - - private val _isLoading = MutableStateFlow(false) - val isLoading = _isLoading.asStateFlow() - - private fun editInvoiceEffect(effect: EditInvoiceScreenEffects) = viewModelScope.launch { - _editInvoiceEffect.emit( - effect - ) - } - - fun onClickContinue( - source: ReceiveLiquiditySource, - amountSats: ULong, - isGeoBlocked: Boolean, - ) { - viewModelScope.launch { - _isLoading.update { true } - val inboundCapacitySats = walletRepo.inboundLiquiditySats() - val maxCjitAmountSats = maxCjitAmountSats(source, amountSats, inboundCapacitySats, isGeoBlocked) - val action = ReceiveLiquidityDecision.additionalLiquidityAction( - ReceiveAdditionalLiquidityParams( - source = source, - invoiceAmountSats = amountSats, - inboundCapacitySats = inboundCapacitySats, - minCjitSats = blocktankRepo.blocktankState.value.minCjitSats?.toULong(), - maxCjitAmountSats = maxCjitAmountSats, - isGeoBlocked = isGeoBlocked, - ) - ) - editInvoiceEffect(EditInvoiceScreenEffects.ApplyReceiveLiquidityAction(action)) - _isLoading.update { false } - } - } - - private suspend fun maxCjitAmountSats( - source: ReceiveLiquiditySource, - amountSats: ULong, - inboundCapacitySats: ULong, - isGeoBlocked: Boolean, - ): ULong? { - if (!ReceiveLiquidityDecision.needsCjitLimitsForAdditionalLiquidity( - source = source, - invoiceAmountSats = amountSats, - inboundCapacitySats = inboundCapacitySats, - isGeoBlocked = isGeoBlocked, - ) - ) { - return null - } - - blocktankRepo.refreshMinCjitSats() - return blocktankRepo.maxCjitAmountSats().getOrNull() - } - - sealed interface EditInvoiceScreenEffects { - data class ApplyReceiveLiquidityAction( - val action: ReceiveAdditionalLiquidityAction, - ) : EditInvoiceScreenEffects - } -} diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt index b845927b0b..b909b64f55 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveQrScreen.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBarsPadding @@ -39,7 +38,6 @@ import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.withFrameNanos import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.hapticfeedback.HapticFeedbackType @@ -76,7 +74,6 @@ import to.bitkit.ui.components.BodyS import to.bitkit.ui.components.BottomSheetPreview import to.bitkit.ui.components.ButtonSize import to.bitkit.ui.components.Caption13Up -import to.bitkit.ui.components.Display import to.bitkit.ui.components.GradientCircularProgressIndicator import to.bitkit.ui.components.PrimaryButton import to.bitkit.ui.components.QrCodeImage @@ -96,7 +93,6 @@ import to.bitkit.ui.shared.util.shareText import to.bitkit.ui.theme.AppShapes import to.bitkit.ui.theme.AppThemeSurface import to.bitkit.ui.theme.Colors -import to.bitkit.ui.utils.withAccent @OptIn(FlowPreview::class) @Composable @@ -106,7 +102,6 @@ fun ReceiveQrScreen( walletState: WalletState, lightningState: LightningState, onClickEditInvoice: (ReceiveTab) -> Unit, - onClickReceiveCjit: () -> Unit, modifier: Modifier = Modifier, onClickHardwareEditInvoice: () -> Unit = { onClickEditInvoice(ReceiveTab.HARDWARE) }, initialTab: ReceiveTab? = null, @@ -156,7 +151,9 @@ fun ReceiveQrScreen( if (canCreateLightningInvoice && cjitInvoice.isNullOrEmpty()) { add(ReceiveTab.AUTO) } - add(ReceiveTab.SPENDING) + if (canCreateLightningInvoice || !cjitInvoice.isNullOrEmpty()) { + add(ReceiveTab.SPENDING) + } if (usdtEnabled && cjitInvoice == null) add(ReceiveTab.USDT) }.toImmutableList() } @@ -308,12 +305,6 @@ fun ReceiveQrScreen( } } - val showingCjitOnboarding = remember(lightningState, cjitInvoice, canCreateLightningInvoice) { - !canCreateLightningInvoice && - lightningState.nodeLifecycleState.isRunning() && - cjitInvoice.isNullOrEmpty() - } - fun selectReceiveTab(tab: ReceiveTab) { val wrapsToFirst = selectedTab == visibleTabs.last() && tab == visibleTabs.first() selectedTab = tab @@ -434,12 +425,6 @@ fun ReceiveQrScreen( .fillParentMaxHeight() ) { when { - showingCjitOnboarding && tab == ReceiveTab.SPENDING -> { - CjitOnBoardingView( - modifier = Modifier.weight(1f) - ) - } - tab == ReceiveTab.HARDWARE && hardwareReceiveState.address == null -> { HardwareAddressLoadingView( isLoading = hardwareReceiveState.isLoadingAddress, @@ -507,9 +492,7 @@ fun ReceiveQrScreen( VerticalSpacer(24.dp) - val showCjitButton = showingCjitOnboarding && selectedTab == ReceiveTab.SPENDING val buttonVariant = when { - showCjitButton -> BottomButtonVariant.CJIT showDetails -> BottomButtonVariant.SHOW_QR else -> BottomButtonVariant.SHOW_DETAILS } @@ -518,25 +501,6 @@ fun ReceiveQrScreen( label = "ReceiveBottomButtonCrossfade", ) { variant -> when (variant) { - BottomButtonVariant.CJIT -> PrimaryButton( - text = stringResource(R.string.wallet__receive__cjit), - icon = { - Icon( - painter = painterResource(R.drawable.ic_lightning_alt), - tint = Colors.Purple, - contentDescription = null, - ) - }, - onClick = { - onClickReceiveCjit() - showDetails = false - }, - fullWidth = true, - modifier = Modifier - .padding(horizontal = 16.dp) - .testTag("ShowDetails") - ) - BottomButtonVariant.SHOW_QR -> Column( verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.padding(horizontal = 16.dp) @@ -715,49 +679,6 @@ fun ReceiveQrView( } } -@Composable -fun CjitOnBoardingView(modifier: Modifier = Modifier) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier - .clip(AppShapes.small) - .background(color = Colors.Black) - .padding(32.dp) - ) { - Display(stringResource(R.string.wallet__receive_onboarding_title).withAccent(accentColor = Colors.Purple)) - VerticalSpacer(8.dp) - BodyM( - stringResource(R.string.wallet__receive_onboarding_description), - color = Colors.White64, - modifier = Modifier.fillMaxWidth() - ) - VerticalSpacer(32.dp) - Box( - modifier = Modifier - .fillMaxWidth() - .weight(1f) - ) { - Icon( - painter = painterResource(R.drawable.ic_lightning_alt), - tint = Colors.Purple, - contentDescription = null, - modifier = Modifier - .size(64.dp) - .align(Alignment.TopCenter) - ) - Icon( - painter = painterResource(R.drawable.arrow), - tint = Colors.Purple, - contentDescription = null, - modifier = Modifier - .align(Alignment.CenterStart) - .padding(start = 32.dp) - .fillMaxHeight() - ) - } - } -} - @Composable private fun ReceiveDetailsView( tab: ReceiveTab, @@ -919,7 +840,7 @@ private fun ReceiveInvoiceErrorView( } } -private enum class BottomButtonVariant { CJIT, SHOW_QR, SHOW_DETAILS } +private enum class BottomButtonVariant { SHOW_QR, SHOW_DETAILS } enum class CopyAddressType { ONCHAIN, LIGHTNING } @@ -1048,7 +969,6 @@ private fun PreviewSavingsMode() { onClickEditInvoice = {}, modifier = Modifier.sheetHeight(), initialTab = ReceiveTab.SAVINGS, - onClickReceiveCjit = {}, ) } } @@ -1120,7 +1040,6 @@ private fun PreviewAutoMode() { onClickEditInvoice = {}, modifier = Modifier.sheetHeight(), initialTab = ReceiveTab.AUTO, - onClickReceiveCjit = {}, ) } } @@ -1187,7 +1106,6 @@ private fun PreviewSpendingMode() { onClickEditInvoice = {}, modifier = Modifier.sheetHeight(), initialTab = ReceiveTab.SPENDING, - onClickReceiveCjit = {}, ) } } @@ -1204,7 +1122,6 @@ private fun PreviewNodeNotReady() { lightningState = LightningState( nodeLifecycleState = NodeLifecycleState.Starting, ), - onClickReceiveCjit = {}, onClickEditInvoice = {}, modifier = Modifier.sheetHeight(), ) @@ -1225,7 +1142,6 @@ private fun PreviewSmall() { ), onClickEditInvoice = {}, modifier = Modifier.sheetHeight(), - onClickReceiveCjit = {}, ) } } diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveSheet.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveSheet.kt index b6b56c32fc..9c268a9b89 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveSheet.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/receive/ReceiveSheet.kt @@ -89,8 +89,6 @@ fun ReceiveSheet( cjitSessionState.clear() navController.navigateToReceiveStart(startRoute) } - var editInvoiceSourceTab by remember { mutableStateOf(ReceiveTab.SAVINGS) } - var isAdditionalLiquidityAmountEntry by remember { mutableStateOf(false) } val lightningState: LightningState by wallet.lightningState.collectAsStateWithLifecycle() val paymentRequestTargets by appViewModel.eligiblePaymentRequestTargets.collectAsStateWithLifecycle() val paymentRequestContacts by appViewModel.pubkyContacts.collectAsStateWithLifecycle() @@ -157,22 +155,12 @@ fun ReceiveSheet( cjitInvoice = cjitSessionState.cjitInvoice, walletState = walletState, lightningState = lightningState, - onClickReceiveCjit = { - if (lightningState.isGeoBlocked) { - navController.navigateTo(ReceiveRoute.GeoBlock) - } else { - isAdditionalLiquidityAmountEntry = lightningState.channels.isNotEmpty() - navController.navigateTo(ReceiveRoute.Amount) - } - }, onClickEditInvoice = { - editInvoiceSourceTab = it invoiceEditState.beginSoftwareEdit(it) resetEditInvoiceAmount() navController.navigateTo(ReceiveRoute.EditInvoice) }, onClickHardwareEditInvoice = { - editInvoiceSourceTab = ReceiveTab.HARDWARE invoiceEditState.beginHardwareEdit() resetEditInvoiceAmount() navController.navigateTo(ReceiveRoute.EditInvoice) @@ -287,13 +275,7 @@ fun ReceiveSheet( ReceiveAmountScreen( onCjitCreated = { entry -> cjitSessionState.onCjitCreated(entry) - navController.navigateTo( - if (isAdditionalLiquidityAmountEntry) { - ReceiveRoute.ConfirmIncreaseInbound - } else { - ReceiveRoute.Confirm - } - ) + navController.navigateTo(ReceiveRoute.Confirm) }, onBack = { navController.popBackStack() }, ) @@ -320,23 +302,6 @@ fun ReceiveSheet( ) } } - composableWithDefaultTransitions { - cjitSessionState.entryDetails?.let { entryDetails -> - ReceiveConfirmScreen( - entry = entryDetails, - onLearnMore = { navController.navigateTo(ReceiveRoute.LiquidityAdditional) }, - onContinue = { invoice -> - wallet.updateOnchainBip21Amount(entryDetails.receiveAmountSats.toULong()) - cjitSessionState.onCjitConfirmed(invoice) - navController.navigateTo( - ReceiveRoute.QR - ) { popUpTo(ReceiveRoute.QR) { inclusive = true } } - }, - isAdditional = true, - onBack = { navController.popBackStack() }, - ) - } - } composableWithDefaultTransitions { cjitSessionState.entryDetails?.let { entryDetails -> val context = LocalContext.current @@ -356,34 +321,12 @@ fun ReceiveSheet( ) } } - composableWithDefaultTransitions { - cjitSessionState.entryDetails?.let { entryDetails -> - val context = LocalContext.current - val notificationsGranted by settingsViewModel.notificationsGranted.collectAsStateWithLifecycle() - val onNotificationSwitchClick = rememberNotificationToggleClick( - isGranted = notificationsGranted, - onPermissionResult = { granted -> settingsViewModel.setNotificationPreference(granted) }, - onOpenSystemSettings = { context.openNotificationSettings() }, - ) - - ReceiveLiquidityScreen( - entry = entryDetails, - onContinue = { navController.popBackStack() }, - isAdditional = true, - onBack = { navController.popBackStack() }, - hasNotificationPermission = notificationsGranted, - onSwitchClick = onNotificationSwitchClick, - ) - } - } composableWithDefaultTransitions { val walletUiState by wallet.walletState.collectAsStateWithLifecycle() @Suppress("ViewModelForwarding") EditInvoiceScreen( amountInputViewModel = editInvoiceAmountViewModel, walletUiState = walletUiState, - lightningState = lightningState, - sourceTab = editInvoiceSourceTab, onBack = { navController.popBackStack() }, updateInvoice = { if (!cjitSessionState.hasConfirmedInvoiceForAmount(it)) { @@ -408,17 +351,8 @@ fun ReceiveSheet( isEditingPaymentRequestAmount = false navController.navigateTo(ReceiveRoute.PaymentRequestRecipient) }, - navigateReceiveConfirm = { entry -> - cjitSessionState.onCjitCreated(entry) - navController.navigateTo(ReceiveRoute.ConfirmIncreaseInbound) - }, onchainOnly = invoiceEditState.isHardwareInvoice, updateOnchainInvoice = wallet::setBip21AmountSats, - navigateCjitAmount = { - isAdditionalLiquidityAmountEntry = true - navController.navigateTo(ReceiveRoute.Amount) - }, - navigateGeoBlock = { navController.navigateTo(ReceiveRoute.GeoBlock) }, ) } composableWithDefaultTransitions { @@ -531,15 +465,9 @@ sealed interface ReceiveRoute { @Serializable data object Confirm : InternalOnly - @Serializable - data object ConfirmIncreaseInbound : InternalOnly - @Serializable data object Liquidity : InternalOnly - @Serializable - data object LiquidityAdditional : InternalOnly - @Serializable data object EditInvoice : DeepLinkStart diff --git a/app/src/main/java/to/bitkit/ui/screens/widgets/AddWidgetsScreen.kt b/app/src/main/java/to/bitkit/ui/screens/widgets/AddWidgetsScreen.kt index 267e1de3c2..d48d980917 100644 --- a/app/src/main/java/to/bitkit/ui/screens/widgets/AddWidgetsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/widgets/AddWidgetsScreen.kt @@ -498,7 +498,7 @@ private val PreviewArticle = ArticleModel( private val PREVIEW_SUGGESTIONS = persistentListOf( Suggestion.BACK_UP, Suggestion.SECURE, - Suggestion.LIGHTNING, + Suggestion.BUY, Suggestion.SUPPORT, ) private const val DISABLED_CARD_ALPHA = 0.42f diff --git a/app/src/main/java/to/bitkit/ui/screens/widgets/suggestions/SuggestionsPreviewScreen.kt b/app/src/main/java/to/bitkit/ui/screens/widgets/suggestions/SuggestionsPreviewScreen.kt index f1a430cc23..1321af439d 100644 --- a/app/src/main/java/to/bitkit/ui/screens/widgets/suggestions/SuggestionsPreviewScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/widgets/suggestions/SuggestionsPreviewScreen.kt @@ -47,7 +47,7 @@ import to.bitkit.ui.theme.Insets private val previewSuggestions = persistentListOf( Suggestion.BACK_UP, Suggestion.SECURE, - Suggestion.LIGHTNING, + Suggestion.BUY, Suggestion.SUPPORT, ) diff --git a/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsScreen.kt index 84ec1944a8..b532cfaf3c 100644 --- a/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsScreen.kt @@ -62,7 +62,6 @@ import to.bitkit.ui.components.TertiaryButton import to.bitkit.ui.components.Title import to.bitkit.ui.components.VerticalSpacer import to.bitkit.ui.navigateTo -import to.bitkit.ui.navigateToTransferFunding import to.bitkit.ui.scaffold.AppTopBar import to.bitkit.ui.scaffold.ScreenColumn import to.bitkit.ui.shared.modifiers.clickableAlpha @@ -94,7 +93,7 @@ fun LightningConnectionsScreen( Content( uiState = uiState, onBack = { navController.popBackStack() }, - onClickAddConnection = { navController.navigateToTransferFunding() }, + onClickAddConnection = { navController.navigateTo(Routes.FundingAdvanced) }, onClickExportLogs = { viewModel.zipLogsForSharing { uri -> context.shareZipFile(uri) } }, diff --git a/app/src/main/java/to/bitkit/ui/sheets/GiftErrorSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/GiftErrorSheet.kt deleted file mode 100644 index daa85e6dce..0000000000 --- a/app/src/main/java/to/bitkit/ui/sheets/GiftErrorSheet.kt +++ /dev/null @@ -1,88 +0,0 @@ -package to.bitkit.ui.sheets - -import androidx.annotation.StringRes -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding -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.unit.dp -import to.bitkit.R -import to.bitkit.ui.components.BodyM -import to.bitkit.ui.components.FillHeight -import to.bitkit.ui.components.PrimaryButton -import to.bitkit.ui.components.SheetSize -import to.bitkit.ui.components.VerticalSpacer -import to.bitkit.ui.scaffold.SheetTopBar -import to.bitkit.ui.shared.modifiers.sheetHeight -import to.bitkit.ui.shared.util.gradientBackground -import to.bitkit.ui.theme.Colors - -@Composable -fun GiftErrorSheet( - @StringRes titleRes: Int, - @StringRes textRes: Int, - testTag: String, - onDismiss: () -> Unit, -) { - Content( - titleRes = titleRes, - textRes = textRes, - testTag = testTag, - onDismiss = onDismiss, - ) -} - -@Composable -private fun Content( - @StringRes titleRes: Int, - @StringRes textRes: Int, - testTag: String, - modifier: Modifier = Modifier, - onDismiss: () -> Unit = {}, -) { - Column( - modifier = modifier - .sheetHeight(SheetSize.LARGE) - .gradientBackground() - .navigationBarsPadding() - .padding(horizontal = 16.dp) - ) { - SheetTopBar(titleText = stringResource(titleRes)) - VerticalSpacer(16.dp) - - BodyM( - text = stringResource(textRes), - color = Colors.White64, - ) - - FillHeight() - - Image( - painter = painterResource(R.drawable.exclamation_mark), - contentDescription = null, - modifier = Modifier - .fillMaxWidth(IMAGE_WIDTH_FRACTION) - .aspectRatio(1.0f) - .align(Alignment.CenterHorizontally) - ) - - FillHeight() - - PrimaryButton( - text = stringResource(R.string.common__ok), - onClick = onDismiss, - modifier = Modifier - .fillMaxWidth() - .testTag(testTag), - ) - VerticalSpacer(16.dp) - } -} diff --git a/app/src/main/java/to/bitkit/ui/sheets/GiftLoading.kt b/app/src/main/java/to/bitkit/ui/sheets/GiftLoading.kt deleted file mode 100644 index eb27d3bb45..0000000000 --- a/app/src/main/java/to/bitkit/ui/sheets/GiftLoading.kt +++ /dev/null @@ -1,149 +0,0 @@ -package to.bitkit.ui.sheets - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -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.font.FontWeight -import androidx.compose.ui.unit.dp -import to.bitkit.R -import to.bitkit.models.BITCOIN_SYMBOL -import to.bitkit.models.PrimaryDisplay -import to.bitkit.models.formatToModernDisplay -import to.bitkit.ui.LocalCurrencies -import to.bitkit.ui.components.BodyM -import to.bitkit.ui.components.Display -import to.bitkit.ui.components.FillHeight -import to.bitkit.ui.components.MoneySSB -import to.bitkit.ui.components.SheetSize -import to.bitkit.ui.components.VerticalSpacer -import to.bitkit.ui.currencyViewModel -import to.bitkit.ui.scaffold.SheetTopBar -import to.bitkit.ui.shared.modifiers.sheetHeight -import to.bitkit.ui.shared.util.gradientBackground -import to.bitkit.ui.theme.Colors - -@Composable -fun GiftLoading(amount: ULong) { - Content(amount = amount) -} - -@Composable -private fun Content( - amount: ULong, - modifier: Modifier = Modifier, -) { - Column( - modifier = modifier - .sheetHeight(SheetSize.LARGE) - .gradientBackground() - .navigationBarsPadding() - .padding(horizontal = 16.dp) - ) { - SheetTopBar(titleText = stringResource(R.string.other__gift__claiming__title)) - VerticalSpacer(16.dp) - - val currencies = LocalCurrencies.current - val currency = currencyViewModel - val primaryDisplay = currencies.primaryDisplay - - if (primaryDisplay == PrimaryDisplay.BITCOIN) { - MoneySSB( - sats = amount.toLong(), - unit = PrimaryDisplay.FIAT, - color = Colors.White64, - showSymbol = true, - modifier = Modifier.align(Alignment.Start), - ) - VerticalSpacer(16.dp) - val bitcoinAmount = remember(amount, currency, currencies) { - currency?.convert(amount.toLong())?.bitcoinDisplay(currencies.displayUnit)?.value - ?: amount.formatToModernDisplay() - } - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.align(Alignment.Start), - ) { - Display( - text = BITCOIN_SYMBOL, - fontWeight = FontWeight.ExtraBold, - color = Colors.White, - modifier = Modifier.padding(end = 6.dp), - ) - Display( - text = bitcoinAmount, - color = Colors.White, - ) - } - } else { - MoneySSB( - sats = amount.toLong(), - unit = PrimaryDisplay.BITCOIN, - color = Colors.White64, - showSymbol = true, - modifier = Modifier.align(Alignment.Start), - ) - VerticalSpacer(16.dp) - val fiatAmount = remember(amount, currency) { - currency?.convert(amount.toLong())?.formatted ?: "" - } - val fiatSymbol = remember(amount, currency) { - currency?.convert(amount.toLong())?.symbol ?: "" - } - if (fiatAmount.isNotEmpty()) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.align(Alignment.Start), - ) { - Display( - text = fiatSymbol, - fontWeight = FontWeight.ExtraBold, - color = Colors.White, - modifier = Modifier.padding(end = 6.dp), - ) - Display( - text = fiatAmount, - color = Colors.White, - ) - } - } - } - VerticalSpacer(32.dp) - - BodyM( - text = stringResource(R.string.other__gift__claiming__text), - color = Colors.White64, - ) - - FillHeight() - - Image( - painter = painterResource(R.drawable.gift), - contentDescription = null, - modifier = Modifier - .fillMaxWidth(IMAGE_WIDTH_FRACTION) - .aspectRatio(1.0f) - .align(Alignment.CenterHorizontally) - ) - - VerticalSpacer(32.dp) - - CircularProgressIndicator( - modifier = Modifier - .align(Alignment.CenterHorizontally) - .padding(bottom = 32.dp) - .testTag("GiftLoading") - ) - } -} diff --git a/app/src/main/java/to/bitkit/ui/sheets/GiftRoute.kt b/app/src/main/java/to/bitkit/ui/sheets/GiftRoute.kt deleted file mode 100644 index 060bb8fbb4..0000000000 --- a/app/src/main/java/to/bitkit/ui/sheets/GiftRoute.kt +++ /dev/null @@ -1,23 +0,0 @@ -package to.bitkit.ui.sheets - -import kotlinx.serialization.Serializable - -internal const val IMAGE_WIDTH_FRACTION = 0.8f - -@Serializable -sealed interface GiftRoute { - @Serializable - data object Loading : GiftRoute - - @Serializable - data object Used : GiftRoute - - @Serializable - data object UsedUp : GiftRoute - - @Serializable - data object Error : GiftRoute - - @Serializable - data object Success : GiftRoute -} diff --git a/app/src/main/java/to/bitkit/ui/sheets/GiftSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/GiftSheet.kt deleted file mode 100644 index dd3bc8d99b..0000000000 --- a/app/src/main/java/to/bitkit/ui/sheets/GiftSheet.kt +++ /dev/null @@ -1,97 +0,0 @@ -package to.bitkit.ui.sheets - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.imePadding -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.rememberNavController -import to.bitkit.R -import to.bitkit.models.NewTransactionSheetDetails -import to.bitkit.ui.components.Sheet -import to.bitkit.ui.navigateTo -import to.bitkit.ui.shared.modifiers.sheetHeight -import to.bitkit.ui.utils.composableWithDefaultTransitions -import to.bitkit.viewmodels.AppViewModel - -@Composable -fun GiftSheet( - sheet: Sheet.Gift, - appViewModel: AppViewModel, - modifier: Modifier = Modifier, - viewModel: GiftViewModel = hiltViewModel(), -) { - val navController = rememberNavController() - - LaunchedEffect(sheet.code, sheet.amount) { - viewModel.initialize(sheet.code, sheet.amount) - } - - val onSuccessState = rememberUpdatedState { details: NewTransactionSheetDetails -> - appViewModel.hideSheet() - appViewModel.showTransactionSheet(details) - } - - LaunchedEffect(Unit) { - viewModel.successEvent.collect { details -> - onSuccessState.value.invoke(details) - } - } - - LaunchedEffect(Unit) { - viewModel.navigationEvent.collect { route -> - when (route) { - is GiftRoute.Success -> appViewModel.hideSheet() - else -> navController.navigateTo(route) { - popUpTo(GiftRoute.Loading) { inclusive = false } - } - } - } - } - - Column( - modifier = modifier - .fillMaxWidth() - .sheetHeight() - .imePadding() - .testTag("GiftSheet") - ) { - NavHost( - navController = navController, - startDestination = GiftRoute.Loading, - ) { - composableWithDefaultTransitions { - GiftLoading(amount = sheet.amount) - } - composableWithDefaultTransitions { - GiftErrorSheet( - titleRes = R.string.other__gift__used__title, - textRes = R.string.other__gift__used__text, - testTag = "GiftUsed", - onDismiss = { appViewModel.hideSheet() }, - ) - } - composableWithDefaultTransitions { - GiftErrorSheet( - titleRes = R.string.other__gift__used_up__title, - textRes = R.string.other__gift__used_up__text, - testTag = "GiftUsedUp", - onDismiss = { appViewModel.hideSheet() }, - ) - } - composableWithDefaultTransitions { - GiftErrorSheet( - titleRes = R.string.other__gift__error__title, - textRes = R.string.other__gift__error__text, - testTag = "GiftError", - onDismiss = { appViewModel.hideSheet() }, - ) - } - } - } -} diff --git a/app/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt b/app/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt deleted file mode 100644 index b6e8b75f7b..0000000000 --- a/app/src/main/java/to/bitkit/ui/sheets/GiftViewModel.kt +++ /dev/null @@ -1,155 +0,0 @@ -package to.bitkit.ui.sheets - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.synonym.bitkitcore.Activity -import com.synonym.bitkitcore.LightningActivity -import com.synonym.bitkitcore.PaymentState -import com.synonym.bitkitcore.PaymentType -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.asSharedFlow -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import to.bitkit.di.BgDispatcher -import to.bitkit.ext.create -import to.bitkit.ext.nowTimestamp -import to.bitkit.models.NewTransactionSheetDetails -import to.bitkit.models.NewTransactionSheetDirection -import to.bitkit.models.NewTransactionSheetType -import to.bitkit.repositories.ActivityRepo -import to.bitkit.repositories.BlocktankRepo -import to.bitkit.repositories.GiftClaimResult -import to.bitkit.utils.Logger -import javax.inject.Inject -import kotlin.time.Duration.Companion.milliseconds - -@HiltViewModel -class GiftViewModel @Inject constructor( - @BgDispatcher private val bgDispatcher: CoroutineDispatcher, - private val blocktankRepo: BlocktankRepo, - private val activityRepo: ActivityRepo, -) : ViewModel() { - - private val _navigationEvent = MutableSharedFlow(extraBufferCapacity = 1) - val navigationEvent = _navigationEvent.asSharedFlow() - - private val _successEvent = MutableSharedFlow(extraBufferCapacity = 1) - val successEvent = _successEvent.asSharedFlow() - - private var code: String = "" - var amount: ULong = 0uL - private set - - @Volatile - private var isClaiming: Boolean = false - - fun initialize(code: String, amount: ULong) { - if (!isClaiming) { - viewModelScope.launch { - _navigationEvent.emit(GiftRoute.Loading) - } - } - this.code = code - this.amount = amount - viewModelScope.launch(bgDispatcher) { - claimGift() - } - } - - private suspend fun claimGift() = withContext(bgDispatcher) { - if (isClaiming) { - Logger.debug("Gift claim already in progress, skipping", context = TAG) - return@withContext - } - isClaiming = true - - try { - Logger.debug("Claiming gift: code=$code, amount=$amount", context = TAG) - blocktankRepo.claimGiftCode( - code = code, - amount = amount, - waitTimeout = NODE_STARTUP_TIMEOUT_MS.milliseconds, - ).fold( - onSuccess = { - Logger.debug("Gift claim successful: $it", context = TAG) - if (it is GiftClaimResult.SuccessWithoutLiquidity) { - insertGiftActivity(it.paymentHashOrTxId, it.sats, it.invoice, it.code) - } - _successEvent.emit( - NewTransactionSheetDetails( - type = NewTransactionSheetType.LIGHTNING, - direction = NewTransactionSheetDirection.RECEIVED, - paymentHashOrTxId = it.paymentHashOrTxId, - sats = it.sats, - ) - ) - _navigationEvent.emit(GiftRoute.Success) - }, - onFailure = { handleGiftClaimError(it) }, - ) - } finally { - isClaiming = false - } - } - - private suspend fun insertGiftActivity( - paymentHashOrTxId: String, - sats: Long, - invoice: String, - code: String, - ) { - val nowTimestamp = nowTimestamp().epochSecond.toULong() - - val lightningActivity = LightningActivity.create( - id = paymentHashOrTxId, - txType = PaymentType.RECEIVED, - status = PaymentState.SUCCEEDED, - value = sats.toULong(), - invoice = invoice, - timestamp = nowTimestamp, - message = code, - ) - - activityRepo.insertActivity(Activity.Lightning(lightningActivity)).getOrThrow() - } - - private suspend fun handleGiftClaimError(error: Throwable) { - Logger.error("Gift claim failed", error, context = TAG) - - val route = when { - errorContains(error, "GIFT_CODE_ALREADY_USED") -> { - Logger.info("Gift code was already used", context = TAG) - GiftRoute.Used - } - errorContains(error, "GIFT_CODE_USED_UP") -> { - Logger.info("Gift code promotion depleted", context = TAG) - GiftRoute.UsedUp - } - else -> { - Logger.error("Unhandled gift claim error type", error, context = TAG) - GiftRoute.Error - } - } - - _navigationEvent.emit(route) - } - - private fun errorContains(error: Throwable, text: String): Boolean { - var currentError: Throwable? = error - while (currentError != null) { - val errorText = currentError.toString() + (currentError.message ?: "") - if (errorText.contains(text, ignoreCase = true)) { - return true - } - currentError = currentError.cause - } - return false - } - - companion object { - private const val TAG = "GiftViewModel" - private const val NODE_STARTUP_TIMEOUT_MS = 30_000L - } -} diff --git a/app/src/main/java/to/bitkit/utils/Errors.kt b/app/src/main/java/to/bitkit/utils/Errors.kt index 9aa0542d2d..820c1d3e33 100644 --- a/app/src/main/java/to/bitkit/utils/Errors.kt +++ b/app/src/main/java/to/bitkit/utils/Errors.kt @@ -26,7 +26,6 @@ sealed class ServiceError(message: String) : AppError(message) { class ChannelSizeExceedsMaximum : ServiceError("Channel size exceeds maximum") class CjitQuoteInvalid : ServiceError("CJIT quote is invalid") class GeoBlocked : ServiceError("Geo blocked user") - class GiftClaimPaymentNotReceived : ServiceError("Gift claim payment not received") class NodeCapacityUnavailable : ServiceError("Additional spending capacity is unavailable") } diff --git a/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt index ef470a7fd4..885ce4e07b 100644 --- a/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt @@ -4594,7 +4594,6 @@ class AppViewModel @Inject constructor( is Scanner.LnurlAuth -> handleNonPaymentScan { onScanLnurlAuth(scan.data, fromMainScanner) } is Scanner.LnurlChannel -> handleNonPaymentScan { onScanLnurlChannel(scan.data) } is Scanner.NodeId -> handleNonPaymentScan { onScanNodeId(scan) } - is Scanner.Gift -> handleNonPaymentScan { onScanGift(scan.code, scan.amount) } else -> { val hasIncomingPaymentRequest = clearIncomingPaymentRequestTarget() val logMessage = if (hasIncomingPaymentRequest) { @@ -5445,11 +5444,6 @@ class AppViewModel @Inject constructor( mainScreenEffect(MainScreenEffect.Navigate(nextRoute)) } - private fun onScanGift(code: String, amount: ULong) { - hideSheet() // hide scan sheet if opened - showSheet(Sheet.Gift(code = code, amount = amount)) - } - private suspend fun handleQuickPayIfApplicable( amountSats: ULong, fromMainScanner: Boolean, @@ -8023,8 +8017,7 @@ class AppViewModel @Inject constructor( showPairingCodeSheet(requestId) } - private fun isHighPrioritySheet(sheet: Sheet?) = sheet is Sheet.Gift || - sheet is Sheet.Send || + private fun isHighPrioritySheet(sheet: Sheet?) = sheet is Sheet.Send || sheet is Sheet.BTCPayConnection || sheet is Sheet.LnurlAuth || sheet is Sheet.Pin || diff --git a/app/src/main/java/to/bitkit/viewmodels/SettingsViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/SettingsViewModel.kt index 5b3bdf5964..10cdae7f9b 100644 --- a/app/src/main/java/to/bitkit/viewmodels/SettingsViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/SettingsViewModel.kt @@ -142,9 +142,6 @@ class SettingsViewModel @Inject constructor( } } - val hasSeenTransferIntro = settingsStore.data.map { it.hasSeenTransferIntro } - .asStateFlow(initialValue = false) - fun setHasSeenTransferIntro(value: Boolean) { viewModelScope.launch { settingsStore.update { it.copy(hasSeenTransferIntro = value) } diff --git a/app/src/main/java/to/bitkit/viewmodels/WalletViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/WalletViewModel.kt index 05ed1b131e..76022a5899 100644 --- a/app/src/main/java/to/bitkit/viewmodels/WalletViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/WalletViewModel.kt @@ -89,6 +89,7 @@ class WalletViewModel @Inject constructor( } val lightningState = lightningRepo.lightningState + val hasTrustedChannels = lightningRepo.hasTrustedChannels val walletState = walletRepo.walletState val balanceState = walletRepo.balanceState diff --git a/app/src/main/res/drawable/arrow.xml b/app/src/main/res/drawable/arrow.xml deleted file mode 100644 index 9d3ab6b589..0000000000 --- a/app/src/main/res/drawable/arrow.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 0894e7c3af..2e56ecf5aa 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -7,8 +7,6 @@ شراء شارك Bitkit دعوة - مدفوعات فورية - إنفاق عندما يكون Bitkit مغلقًا استلم أموالك إعداد رمز PIN @@ -106,6 +104,8 @@ يمكنك الآن الدفع لأي شخص، في أي مكان، فورًا. رصيد الإنفاق جاهز نقطة القناة + تُغلق القنوات في %1$s + ستُغلق قنوات Lightning الخاصة بك في %1$s. انقل رصيد الإنفاق إلى المدخرات قبل ذلك. إغلاق إغلاق الاتصال فشل التحويل @@ -348,14 +348,6 @@ تمت استعادة اتصال الإنترنت مشاكل في الاتصال بالإنترنت يبدو أنك غير متصل، جارٍ محاولة إعادة الاتصال... - جارٍ المطالبة برمز هدية Bitkit... - جارٍ المطالبة بالهدية - تعذر على Bitkit المطالبة بالأموال. يرجى المحاولة لاحقًا أو التواصل معنا. - خطأ في رمز الهدية - تم استخدام رمز هدية Bitkit هذا بالفعل، وتم صرف الأموال. - رمز مستخدم - عذرًا، لقد تأخرت! تمت المطالبة بجميع الهدايا لهذا الرمز. - نفدت الهدايا اعرف المزيد مفهوم رصيد مرتفع @@ -970,7 +962,6 @@ تم استلام Bitcoin انقطع اتصال النظير. استلام - استلام أموال Lightning استلام Bitcoin فاتورة Bitcoin لاستلام المزيد من Bitcoin الفوري، يحتاج Bitkit لزيادة السيولة. سيتم خصم رسوم شبكة <accent>{networkFee}</accent> ورسوم مزود الخدمة <accent>{serviceFee}</accent> من المبلغ المحدد. @@ -986,8 +977,6 @@ يستخدم رصيد الإنفاق شبكة Lightning لجعل مدفوعاتك أرخص وأسرع وأكثر خصوصية.\n\nهذا يعمل مثل الوصول إلى الإنترنت، لكنك تدفع مقابل السيولة والتوجيه بدلاً من عرض النطاق الترددي.\n\nيتضمن هذا الإعداد بعض التكاليف لمرة واحدة. يستخدم رصيد الإنفاق شبكة Lightning لجعل مدفوعاتك أرخص وأسرع وأكثر خصوصية.\n\nهذا يعمل مثل الوصول إلى الإنترنت، لكنك تدفع مقابل السيولة والتوجيه بدلاً من عرض النطاق الترددي.\n\nيحتاج Bitkit لزيادة سعة الاستقبال لرصيد الإنفاق لمعالجة هذا الدفع. ملاحظة اختيارية للدافع - استمتع بمعاملات فورية ورخيصة\nمع الأصدقاء والعائلة\nوالتجار. - استلم على <accent>رصيد الإنفاق</accent> عرض التفاصيل عرض رمز QR تعديل الفاتورة @@ -1078,7 +1067,6 @@ أصبحت المعاملة غير مؤكدة بسبب إعادة تنظيم السلسلة المعاملة غير مؤكدة تحويل إلى المدخرات - تحويل إلى الإنفاق لم ينجح سحبك. يرجى مسح رمز QR مرة أخرى أو التواصل مع الدعم. إضافة أداة افحص إحصائيات متنوعة عن كتل Bitcoin المُعدّنة حديثًا. 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 b051b9e1e7..9a12d18766 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -7,8 +7,6 @@ Comprar Compatir BitKit Invitar - Pagos instantáneos - Gastar Sin Bitkit abierto Cobrar Configurar un código PIN @@ -111,6 +109,8 @@ Ahora puede pagar a cualquier persona, en cualquier lugar y al instante. Saldo de gastos listo Punto del canal + LOS CANALES CIERRAN EL %1$s + Tus canales Lightning se cerrarán el %1$s. Transfiere tu saldo de gasto a ahorros antes de esa fecha. Cerrar Terminar conexión Transferencia fallida @@ -356,14 +356,6 @@ Conexión a Internet restablecida Problemas de conectividad a Internet Parece que está desconectado, intentando reconectarse... - Canjeando su código de regalo Bitkit... - Canjeando regalo - Bitkit no pudo recibir los fondos. Por favor, intente de nuevo más tarde o póngase en contacto con nosotros. - Error de código de regalo - Este código de regalo de Bitkit ya ha sido utilizado y los fondos han sido pagados. - Código usado - Lo sentimos, ¡llegaste tarde! Todos los regalos para este código ya han sido canjeados. - Sin regalos Más información Entendido Saldo Elevado @@ -1001,7 +993,6 @@ Bitcoin recibido Par desconectado. Recibir - Recibir fondos Lightning Recibir Bitcoin Factura Bitcoin Para recibir más Bitcoin al instante, Bitkit necesita aumentar tu liquidez. Se descontará una comisión de red de <accent>{networkFee}</accent> y una comisión del proveedor de servicio de <accent>{serviceFee}</accent> del monto que especificaste. @@ -1017,8 +1008,6 @@ Tu saldo para gastar usa la red Lightning para que tus pagos sean más baratos, rápidos y privados.\n⠀\nFunciona como el acceso a internet, pero pagas por liquidez y ruteo en lugar de por ancho de banda.\n⠀\nEsta configuración incluye algunos costos únicos. Su saldo de gastos utiliza la Lightning Network para que sus pagos sean más baratos, más rápidos y más privados.\n\nFunciona como el acceso a Internet, pero usted paga por la liquidez y el enrutamiento en lugar del ancho de banda.\n\nBitkit necesita aumentar la capacidad de recepción de su saldo de gastos para procesar este pago. Nota opcional para el pagador - Disfruta transacciones instantáneas\ny baratas con amigos, familia\ny comercios. - Recibe en <accent>saldo de gastos</accent> Ver detalles Mostrar código QR Editar factura @@ -1115,7 +1104,6 @@ La transacción quedó sin confirmar debido a una reorganización del blockchain Transacción no confirmada Transferir a ahorros - Transferir a gastos Tu retiro no fue exitoso. Por favor escanea el código QR nuevamente o contacta soporte. Añadir Widget Examine diversas estadísticas sobre los nuevos bloques de Bitcoin minados. diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index f83790081a..3350711636 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -7,8 +7,6 @@ Comprar Comparteix Bitkit Convidar - Pagaments instantanis - Gastar Quan Bitkit està tancat Rep pagaments Configurar un codi PIN @@ -109,6 +107,8 @@ Ara pots pagar a qualsevol persona, en qualsevol lloc, instantàniament. Saldo de despesa llest Punt del canal + ELS CANALS ES TANQUEN EL %1$s + Els teus canals Lightning es tancaran el %1$s. Transfereix el teu saldo de despesa a estalvis abans d\'aquesta data. Tancar Tanca la connexió Transferència fallida @@ -352,14 +352,6 @@ Connexió a Internet restaurada Problemes de connexió a Internet Sembla que estàs desconnectat, intentant tornar a connectar-te... - Reclamant el teu codi de regal de Bitkit... - Reclamant regal - Bitkit no ha pogut reclamar els fons. Si us plau, torna-ho a provar més tard o contacta\'ns. - Error de codi de regal - Aquest codi de regal de Bitkit ja ha estat utilitzat i els fons han estat pagats. - Codi utilitzat - Ho sentim, arribes tard! Tots els regals per a aquest codi ja han estat reclamats. - Sense regals Aprèn més Entès Saldo alt @@ -994,7 +986,6 @@ Rebut Bitcoin Parell desconnectat. Rebre - Rebre fons Lightning Rebre Bitcoin Factura de Bitcoin Per rebre més Bitcoin instantani, Bitkit ha d\'augmentar la teva liquiditat. Es descomptarà una tarifa de xarxa de <accent>{networkFee}</accent> i una tarifa de proveïdor de servei de <accent>{serviceFee}</accent> de l\'import especificat. @@ -1010,8 +1001,6 @@ El teu saldo de despesa utilitza Lightning Network per fer els teus pagaments més barats, ràpids i privats.\n\nAixò funciona com l\'accés a Internet, però pagues per liquiditat i encaminament en lloc d\'amplada de banda.\n\nAquesta configuració inclou alguns costos únics. El teu saldo de despesa utilitza Lightning Network per fer els teus pagaments més barats, ràpids i privats.\n\nAixò funciona com l\'accés a Internet, però pagues per liquiditat i encaminament en lloc d\'amplada de banda.\n\nBitkit necessita augmentar la capacitat de recepció del teu saldo de despesa per processar aquest pagament. Nota opcional per al pagador - Gaudeix de transaccions instantànies i barates\namb amics, família\ni comerciants. - Rebre al <accent>saldo de despesa</accent> Mostrar detalls Mostra el codi QR Editar factura @@ -1106,7 +1095,6 @@ La transacció s\'ha tornat no confirmada a causa d\'una reorganització de blockchain Transacció no confirmada Transferir a estalvis - Transferir a despesa La teva retirada no ha tingut èxit. Si us plau, escaneja el codi QR de nou o contacta amb el suport. Afegir giny Examina diverses estadístiques dels blocs de Bitcoin recentment minats. diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index bb7a644e4b..926c9533b5 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -7,8 +7,6 @@ Koupit Sdílet Bitkit Pozvat - Okamžité platby - Utratit Když je Bitkit zavřený Přijímat platby Nastavte PIN kód @@ -111,6 +109,8 @@ Nyní můžete platit komukoli, kdekoli a okamžitě. Disponibilní zůstatek připraven Bod kanálu + KANÁLY SE ZAVŘOU %1$s + Vaše Lightning kanály budou uzavřeny %1$s. Do té doby převeďte zůstatek útrat do úspor. Zavřít Uzavřít spojení Přenos se nezdařil @@ -355,14 +355,6 @@ Obnoveno internetové připojení Problémy s připojením k internetu Zdá se, že jste odpojen, snažíte se znovu připojit... - Uplatnění dárkového kódu Bitkit... - Uplatnění daru - Bitkit nemohl nárokovat prostředky. Zkuste to prosím znovu později nebo nás kontaktujte. - Chyba dárkového kódu - Tento dárkový kód BitKitu již byl použit a prostředky byly vyplaceny. - Použitý kód - Omlouváme se, přicházíte pozdě! Všechny dary pro tento kód již byly vyzvednuty. - Dary vyčerpány Další informace Rozumím Vysoký zůstatek @@ -998,7 +990,6 @@ bitcoin přijatý Peer odpojen. Přijmout - Přijímat Lightning prostředky Přijmout bitcoin bitcoinová faktura Chcete-li získat více okamžitých bitcoinů, musí Bitkit navýšit vaši likviditu. Od vámi zadané částky bude odečten síťový poplatek <accent>{networkFee}</accent> a <accent>{serviceFee}</accent> poplatek poskytovatele služeb . @@ -1014,8 +1005,6 @@ Váš zůstatek na účtu využívá síť lightning network, aby byly vaše platby levnější, rychlejší a soukromější.\n\nFunguje to jako přístup k internetu, ale místo za šířku pásma platíte za likviditu a směrování.\n\nToto nastavení zahrnuje některé jednorázové náklady. Váš zůstatek na účtu využívá síť lightning network, aby byly vaše platby levnější, rychlejší a soukromější.\n\nFunguje to jako přístup k internetu, ale místo za šířku pásma platíte za likviditu a směrování.\n\nBitkit potřebuje zvýšit přijímací kapacitu vašeho výdajového zůstatku, aby mohl tuto platbu zpracovat. Nepovinná poznámka pro plátce - Užívejte si okamžité a levné\ntransakce s přáteli, rodinou\na obchodníky. - Přijímat na disponibilní zůstatek Zobrazit podrobnosti Zobrazit QR kód Upravit fakturu @@ -1112,7 +1101,6 @@ Transakce se stala nepotvrzenou kvůli reorganizaci blockchainu Transakce nepotvrzena Převést do úspor - Převést do útrat Váš výběr byl neúspěšný. Naskenujte prosím QR kód znovu nebo kontaktujte podporu. Přidat widget Prozkoumejte různé statistiky nově vytěžených bloků bitcoinu. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index efc30cca07..b49770ea1c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -7,8 +7,6 @@ Scanne und zahle Einladen Teile Bitkit - Ausgeben - Sofortüberweisung Sichern Lege eine PIN fest Shop @@ -101,6 +99,8 @@ Abhängig von der Gebühr Abhängig von der Gebühr Abhängig von der Gebühr + KANÄLE SCHLIESSEN AM %1$s + Deine Lightning-Kanäle werden am %1$s geschlossen. Übertrage dein Guthaben vorher auf das Sparkonto. Spending\n<accent>Balance</accent> Fülle dein Guthaben auf, um sofortige und günstige Transaktionen mit Freunden, Familie und Händlern zu genießen. Los geht\'s @@ -354,14 +354,6 @@ Sieht so aus, als wärst du nicht verbunden, wiederverbinden... Internetverbindung wiederhergestellt Bitkit wurde erfolgreich mit dem Internet verbunden. - Dein Bitkit-Geschenkcode wird eingelöst... - Geschenk wird eingelöst - Bitkit konnte die Gelder nicht einlösen. Bitte versuche es später erneut oder kontaktiere uns. - Geschenkcode-Fehler - Dieser Bitkit-Geschenkcode wurde bereits verwendet und die Gelder wurden ausgezahlt. - Code verwendet - Entschuldigung, du bist zu spät! Alle Geschenke für diesen Code wurden bereits eingelöst. - Keine Geschenke mehr Hoher Kontostand Hoher\n<accent>Kontostand</accent> <accent>Dein Wallet-Kontostand übersteigt $500.</accent>\nZu deiner Sicherheit solltest du einen Teil deiner Ersparnisse in ein Offline-Wallet verschieben. @@ -987,11 +979,8 @@ Lightning-Rechnung Optionale Notiz an den Zahler QR-Code anzeigen - Lightning-Gelder empfangen Um dein Ausgabenguthaben einzurichten, wird eine <accent>{networkFee}</accent> Netzwerkgebühr und <accent>{serviceFee}</accent> Service-Provider-Gebühr abgezogen. Um mehr sofortiges Bitcoin zu empfangen, muss Bitkit deine Liquidität erhöhen. Eine <accent>{networkFee}</accent> Netzwerkgebühr und <accent>{serviceFee}</accent> Service-Provider-Gebühr werden von dem von dir angegebenen Betrag abgezogen. - Empfangen auf <accent>Ausgabenguthaben</accent> - Genieße sofortige und günstige\nTransaktionen mit Freunden, Familie\nund Händlern. Details anzeigen Auto Sparkonto @@ -1110,7 +1099,6 @@ Lightning-Startfehler Peer getrennt. Übertragen zum Sparkonto - Übertragen zum Ausgabenkonto Deine Abhebung war nicht erfolgreich. Bitte scanne den QR-Code erneut oder kontaktiere den Support. Widgets Hallo,\n<accent>Widgets</accent> diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 3800135d9c..c4e8b0a778 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -7,8 +7,6 @@ Αγορά Μοιράσου το Bitkit Πρόσκληση - Άμεσες πληρωμές - Δαπάνη Όταν το Bitkit είναι κλειστό Λήψη πληρωμών Καταχωρήστε κωδικό PIN @@ -106,6 +104,8 @@ Μπορείς τώρα να πληρώσεις οποιονδήποτε, οπουδήποτε, άμεσα. Υπόλοιπο δαπανών έτοιμο Σημείο καναλιού + ΤΑ ΚΑΝΑΛΙΑ ΚΛΕΙΝΟΥΝ ΣΤΙΣ %1$s + Τα κανάλια Lightning σας θα κλείσουν στις %1$s. Μεταφέρετε το υπόλοιπο δαπανών σε αποταμιεύσεις πριν από τότε. Κλείσιμο Κλείσιμο σύνδεσης Αποτυχία μεταφοράς @@ -349,14 +349,6 @@ Η σύνδεση στο διαδίκτυο αποκαταστάθηκε Προβλήματα σύνδεσης στο διαδίκτυο Φαίνεται ότι είσαι αποσυνδεδεμένος, προσπάθεια επανασύνδεσης... - Εξαργύρωση του κωδικού δώρου Bitkit... - Εξαργύρωση δώρου - Το Bitkit δεν μπόρεσε να εξαργυρώσει τα κεφάλαια. Δοκίμασε ξανά αργότερα ή επικοινώνησε μαζί μας. - Σφάλμα κωδικού δώρου - Αυτός ο κωδικός δώρου Bitkit έχει ήδη χρησιμοποιηθεί και τα κεφάλαια έχουν εκταμιευθεί. - Χρησιμοποιημένος κωδικός - Λυπούμαστε, άργησες! Όλα τα δώρα για αυτόν τον κωδικό έχουν ήδη εξαργυρωθεί. - Εξαντλήθηκαν τα δώρα Μάθε περισσότερα Κατανοητό Υψηλό υπόλοιπο @@ -976,7 +968,6 @@ Ελήφθησαν Bitcoin Ο ομότιμος αποσυνδέθηκε. Λήψη - Λήψη κεφαλαίων Lightning Λήψη Bitcoin Τιμολόγιο Bitcoin Για να λάβεις περισσότερα άμεσα Bitcoin, το Bitkit πρέπει να αυξήσει τη ρευστότητά σου. Τέλος δικτύου <accent>{networkFee}</accent> και τέλος παρόχου υπηρεσιών <accent>{serviceFee}</accent> θα αφαιρεθούν από το ποσό που καθόρισες. @@ -992,8 +983,6 @@ Το υπόλοιπο δαπανών χρησιμοποιεί το Lightning Network για να κάνει τις πληρωμές σου φθηνότερες, ταχύτερες και πιο ιδιωτικές.\n\nΛειτουργεί όπως η πρόσβαση στο διαδίκτυο, αλλά πληρώνεις για ρευστότητα & δρομολόγηση αντί για εύρος ζώνης.\n\nΑυτή η ρύθμιση περιλαμβάνει κάποια εφάπαξ κόστη. Το υπόλοιπο δαπανών χρησιμοποιεί το Lightning Network για να κάνει τις πληρωμές σου φθηνότερες, ταχύτερες και πιο ιδιωτικές.\n\nΛειτουργεί όπως η πρόσβαση στο διαδίκτυο, αλλά πληρώνεις για ρευστότητα & δρομολόγηση αντί για εύρος ζώνης.\n\nΤο Bitkit πρέπει να αυξήσει τη χωρητικότητα λήψης του υπολοίπου δαπανών σου για να επεξεργαστεί αυτή την πληρωμή. Προαιρετική σημείωση προς τον πληρωτή - Απόλαυσε άμεσες και φθηνές\nσυναλλαγές με φίλους, οικογένεια\nκαι εμπόρους. - Λήψη σε <accent>υπόλοιπο δαπανών</accent> Εμφάνιση λεπτομερειών Εμφάνιση κωδικού QR Επεξεργασία τιμολογίου @@ -1085,7 +1074,6 @@ Η συναλλαγή έγινε μη επιβεβαιωμένη λόγω αναδιοργάνωσης blockchain Μη επιβεβαιωμένη συναλλαγή Μεταφορά σε αποταμιεύσεις - Μεταφορά σε δαπάνες Η ανάληψή σου δεν ήταν επιτυχής. Σάρωσε ξανά τον κωδικό QR ή επικοινώνησε με την υποστήριξη. Προσθήκη Widget Εξέτασε διάφορα στατιστικά για τα νεοεξορυχθέντα Bitcoin Blocks. diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index fc09657b40..3e99e09edf 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -7,8 +7,6 @@ Comprar Comparta Bitkit Invitar - Pagos instantáneos - Gastar Cuando Bitkit está cerrado Recibe pagos Establezca un código PIN @@ -111,6 +109,8 @@ Ahora puede pagar a cualquier persona, en cualquier lugar y al instante. Saldo de gasto listo Punto del canal + LOS CANALES CIERRAN EL %1$s + Tus canales Lightning se cerrarán el %1$s. Transfiere tu saldo de gasto a ahorros antes de esa fecha. Cerrar Terminar conexión Transferencia fallida @@ -355,14 +355,6 @@ Conexión a Internet Restaurada Problemas de Conectividad de Internet Parece que estás desconectado, intentando reconectar... - Reclamando tu código de regalo Bitkit... - Reclamando regalo - Bitkit no pudo reclamar los fondos. Por favor, inténtalo más tarde o contáctanos. - Error del código de regalo - Este código de regalo Bitkit ya ha sido usado y los fondos han sido pagados. - Código usado - ¡Lo siento, llegaste tarde! Todos los regalos de este código ya han sido reclamados. - Sin regalos Aprender Más Entendido Saldo alto @@ -996,7 +988,6 @@ Bitcoin recibido Par desconectado. Recibir - Recibir fondos Lightning Recibir Bitcoin Factura bitcoin Para recibir más Bitcoin instantáneo, Bitkit tiene que aumentar tu liquidez. Se deducirán una comisión de red de <accent>{networkFee}</accent> y una comisión del proveedor de servicios de <accent>{serviceFee}</accent> de la cantidad especificada. @@ -1012,8 +1003,6 @@ Tu saldo de gasto utiliza Lightning Network para hacer tus pagos más baratos, rápidos y privados.\n\nFunciona como el acceso a internet, pero pagas por liquidez y enrutamiento en lugar de ancho de banda.\n\nEsta configuración incluye algunos costes únicos. Tu saldo de gasto utiliza Lightning Network para hacer tus pagos más baratos, rápidos y privados.\n\nFunciona como el acceso a internet, pero pagas por liquidez y enrutamiento en lugar de ancho de banda.\n\nBitkit necesita aumentar la capacidad de recepción de tu saldo de gasto para procesar este pago. Nota opcional para el pagador - Disfruta de transacciones instantáneas y baratas\ncon amigos, familia\ny comerciantes. - Recibe en el <accent>saldo de gasto</accent> Mostrar detalles Mostrar código QR Editar Factura @@ -1107,7 +1096,6 @@ La transacción quedó sin confirmar debido a una reorganización de la blockchain Transacción sin confirmar Transferir a ahorros - Transferir a gasto Tu retiro no tuvo éxito. Por favor, escanea el código QR de nuevo o contacta con soporte. Añadir Widget Examina varias estadísticas de los bloques de Bitcoin recién minados. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 5072785f9b..09d8696e74 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -6,8 +6,6 @@ Comprar Comparta Bitkit Invitar - Pagos instantáneos - Gastar Cuando Bitkit está cerrado Recibir pagos Establezca un código PIN @@ -108,6 +106,8 @@ ID del par Saldo de Gastos Listo Punto de canal + LOS CANALES CIERRAN EL %1$s + Tus canales Lightning se cerrarán el %1$s. Transfiere tu saldo de gasto a ahorros antes de esa fecha. Transferencia Fallida No se pueden transferir los fondos de vuelta a ahorros. Por favor, inténtalo de nuevo. Tus fondos se están transfiriendo de vuelta a tus ahorros. @@ -379,14 +379,6 @@ Permite el acceso a la cámara para escanear facturas bitcoin y pagar más rápido. ESCANEAR\n<accent>CÓDIGO QR</accent> Escanear QR - Reclamando tu código de regalo Bitkit... - Reclamando Regalo - Bitkit no pudo reclamar los fondos. Por favor, inténtalo más tarde o contáctanos. - Error del Código de Regalo - Este código de regalo de Bitkit ya ha sido usado y los fondos han sido pagados. - Código Usado - ¡Lo siento, llegas tarde! Todos los regalos de este código ya han sido reclamados. - Sin Regalos Saldo Alto Saldo\n<accent>Alto</accent> Inicio de Sesión Fallido (LNURL) @@ -975,8 +967,6 @@ Factura bitcoin Factura Lightning Nota opcional para el pagador - Disfruta de transacciones\ninstantáneas y baratas con\namigos, familia y comerciantes. - Recibir en <accent>saldo de gastos</accent> Mostrar Detalles Mostrar código QR Activa la configuración en segundo plano para salir de Bitkit de forma segura mientras se configura tu saldo. @@ -992,7 +982,6 @@ Auto Ahorros Gastos - Recibir fondos de Lightning Recibirás MÍNIMO Actividad @@ -1096,7 +1085,6 @@ Los fondos que retires se depositarán en tu saldo de gastos de Bitkit. Retirar Bitcoin Transferir a Ahorros - Transferir a Gastos Tu retiro no tuvo éxito. Por favor, escanea el código QR de nuevo o contacta con soporte. Dirección de envío de bitcoin inválida Error al actualizar factura diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 6948ef3d1e..95b8b1a85e 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -7,8 +7,6 @@ Scannez et payez Inviter Partager Bitkit - Dépenser - Paiements instantanés Sécurité Définir un code PIN Boutique @@ -100,6 +98,8 @@ Dépend des frais Dépend des frais Dépend des frais + LES CANAUX FERMENT LE %1$s + Vos canaux Lightning seront fermés le %1$s. Transférez votre solde de dépenses vers l\'Épargne avant cette date. Dépenses \n<accent>Solde</accent> Alimentez votre solde de dépenses pour profiter de transactions instantanées et bon marché avec vos amis, votre famille et les commerçants. Commencer @@ -437,14 +437,6 @@ Mise à jour Bitkit ne peut pas lire les données fournies. Cette facture Lightning a expiré. - Réclamer un cadeau - Réclamer votre code cadeau Bitkit... - Erreur de code cadeau - Bitkit n\'a pas pu réclamer les fonds. Veuillez réessayer plus tard ou nous contacter. - Code utilisé - Ce code cadeau Bitkit a déjà été utilisé et les fonds ont été versés. - Désolé, vous arrivez trop tard ! Tous les cadeaux pour ce code ont déjà été réclamés. - Plus de cadeaux Boutique Passez à la norme Bitcoin. Dépensez vos bitcoins en cartes-cadeaux numériques, eSIM, recharges téléphoniques, et plus encore. Boutique @@ -973,7 +965,6 @@ La transaction n\'est plus confirmée suite à une réorganisation de la blockchain Transaction non confirmée Transférer vers l\'Épargne - Transférer vers les Dépenses Votre retrait a échoué. Veuillez scanner le QR code à nouveau ou contacter le support. Sélection des UTXOs Total requis @@ -1101,11 +1092,8 @@ Le montant minimum pour cette facture est de ₿ {amount}. Pair déconnecté. Erreur de démarrage de Lightning - Recevoir des fonds Lightning Activez la configuration en arrière-plan pour quitter Bitkit en toute sécurité pendant la configuration de votre solde. Configurer en arrière-plan - Profitez de transactions instantanées\net peu coûteuses avec vos amis, votre\nfamille et les commerçants. - Recevoir sur le <accent>solde dépenses</accent> Afficher les détails Auto Épargne diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 1689758d4e..b613f512bc 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -7,8 +7,6 @@ Compra Condividi Bitkit Invita - Pagamenti istantanei - Spendi Quando Bitkit e\' chiuso Ricevi pagamenti Imposta un codice PIN @@ -110,6 +108,8 @@ Ora puoi pagare chiunque, ovunque, istantaneamente. Conto di spesa Pronto Punto del canale + I CANALI CHIUDONO IL %1$s + I tuoi canali Lightning verranno chiusi il %1$s. Trasferisci il saldo di spesa ai risparmi prima di allora. Chiudi Chiusura Connessione Trasferimento non riuscito @@ -354,14 +354,6 @@ Connessione Internet ripristinata Problemi di connettività Internet Sembra che tu sia disconnesso, tentativo di riconnessione... - Riscatto del tuo codice regalo Bitkit... - Riscatto Regalo - Bitkit non e\' riuscito a riscuotere i fondi. Riprova piu\' tardi o contattaci. - Errore Codice Regalo - Questo codice regalo Bitkit e\' gia\' stato usato e i fondi sono stati pagati. - Codice Usato - Spiacenti, sei in ritardo! Tutti i regali per questo codice sono gia\' stati riscossi. - Regali Esauriti Scopri di Più Ho Capito Saldo Elevato @@ -997,7 +989,6 @@ Bitcoin ricevuti Peer disconnesso. Ricevi - Ricevi fondi Lightning Ricevi Bitcoin Bitcoin invoice Per ricevere piu\' Bitcoin istantanei, Bitkit deve aumentare la tua liquidita\'. Una commissione di rete di <accent>{networkFee}</accent> e una commissione del fornitore di servizio di <accent>{serviceFee}</accent> verranno detratte dall\'importo specificato. @@ -1013,8 +1004,6 @@ Il tuo Conto di Spesa utilizza Lightning Network per rendere i tuoi pagamenti piu\' economici, veloci e privati.\n\nFunziona come l\'accesso a Internet, ma paghi per liquidita\' e routing invece che per la banda.\n\nQuesta configurazione include alcuni costi una tantum. Il tuo Conto di Spesa utilizza Lightning Network per rendere i tuoi pagamenti piu\' economici, veloci e privati.\n\nFunziona come l\'accesso a Internet, ma paghi per liquidita\' e routing invece che per la banda.\n\nBitkit deve aumentare la capacita\' di ricezione del tuo conto di spesa per elaborare questo pagamento. Nota opzionale per chi paga - Goditi transazioni istantanee\ned economiche con amici, familiari\ne commercianti. - Ricevi sul <accent>conto di spesa</accent> Mostra Dettagli Visualizza QR Code Modifica Fattura @@ -1110,7 +1099,6 @@ La transazione non è più confermata a causa di una riorganizzazione della blockchain Transazione Non Confermata Trasferisci ai Risparmi - Trasferisci alle Spese Il tuo prelievo non e\' riuscito. Scansiona nuovamente il codice QR o contatta l\'assistenza. Aggiungi Widget Esamina varie statistiche sui blocchi Bitcoin appena estratti. diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 2cb52c4df6..33b1492a76 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -7,8 +7,6 @@ Koop Deel Bitkit Uitnodiging - Directe betalingen - Besteed Als Bitkit gesloten is Ontvang betaling Pincode instellen @@ -110,6 +108,8 @@ Je kunt nu overal, direct, iedereen betalen. Bestedingssaldo gereed Kanaalpunt + KANALEN SLUITEN OP %1$s + Je Lightning-kanalen worden op %1$s gesloten. Boek je bestedingssaldo vóór die datum over naar spaargeld. Sluiten Verbinding sluiten Overboeking mislukt @@ -353,14 +353,6 @@ Internetverbinding hersteld Internetverbindingsproblemen Je lijkt niet verbonden te zijn, proberen opnieuw te verbinden... - Je Bitkit-cadeaucode wordt geclaimd... - Cadeau claimen - Bitkit kon het geld niet claimen. Probeer het later opnieuw of neem contact met ons op. - Cadeaucode-fout - Deze Bitkit-cadeaucode is al gebruikt en het geld is uitbetaald. - Gebruikte code - Sorry, je bent te laat! Alle cadeaus voor deze code zijn al geclaimd. - Cadeaus op Meer informatie Begrepen Hoog saldo @@ -992,7 +984,6 @@ Bitcoin ontvangen Peer niet verbonden. Ontvangen - Lightning-geld ontvangen Bitcoin ontvangen Bitcoin-factuur Om meer directe Bitcoin te ontvangen, moet Bitkit je liquiditeit verhogen. Een netwerkvergoeding van <accent>{networkFee}</accent> en serviceprovidervergoeding van <accent>{serviceFee}</accent> worden afgetrokken van het door jou opgegeven bedrag. @@ -1008,8 +999,6 @@ Je bestedingssaldo gebruikt het Lightning Network om je betalingen goedkoper, sneller en privater te maken.\n\nDit werkt als internettoegang, maar je betaalt voor liquiditeit & routering in plaats van bandbreedte.\n\nDeze configuratie bevat eenmalige kosten. Je bestedingssaldo gebruikt het Lightning Network om je betalingen goedkoper, sneller en privater te maken.\n\nDit werkt als internettoegang, maar je betaalt voor liquiditeit & routering in plaats van bandbreedte.\n\nBitkit moet de ontvangstcapaciteit van je bestedingssaldo verhogen om deze betaling te verwerken. Optionele notitie voor betaler - Geniet van directe en goedkope\ntransacties met vrienden, familie\nen winkeliers. - Ontvangen op <accent>bestedingssaldo</accent> Details tonen QR-code tonen Factuur bewerken @@ -1105,7 +1094,6 @@ Transactie is niet meer bevestigd vanwege blockchain-reorganisatie Transactie niet bevestigd Overboeken naar spaargeld - Overboeken naar bestedingssaldo Je opname is niet gelukt. Scan de QR-code opnieuw of neem contact op met support. Widget toevoegen Bekijk verschillende statistieken over nieuw gedolven Bitcoin-blokken. diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 6b90799070..9f7d3b04a4 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -7,8 +7,6 @@ Skanuj i zapłać Zaproś Udostępnij Bitkit - Wydawaj - Płatności natychmiastowe Zabezpiecz Ustaw kod PIN Sklep @@ -102,6 +100,8 @@ Zależy od opłaty Zależy od opłaty Zależy od opłaty + KANAŁY ZOSTANĄ ZAMKNIĘTE %1$s + Twoje kanały Lightning zostaną zamknięte %1$s. Przed tym terminem prześlij saldo wydatków na oszczędności. Saldo \n<accent>Wydatków</accent> Zasil swoje saldo wydatków, aby cieszyć się natychmiastowymi i tanimi transakcjami z przyjaciółmi, rodziną i sprzedawcami. Rozpocznij @@ -440,14 +440,6 @@ Ta faktura Lightning wygasła. Udostępnij kod QR przez Podróże - Odbiór prezentu - Odbiór kodu podarunkowego Bitkit... - Błąd kodu podarunkowego - Bitkit nie mógł odebrać środków. Proszę spróbować ponownie później lub skontaktować się z nami. - Użyty kod - Ten kod podarunkowy Bitkit został już wykorzystany, a środki zostały wypłacone. - Przepraszamy, jesteś za późno! Wszystkie prezenty dla tego kodu zostały już odebrane. - Brak prezentów Sklep Przestaw się na standard Bitcoina. Wydawaj swoje Bitcoiny na karty podarunkowe, eSIM-y, doładowania telefonu i inne usługi. Sklep @@ -978,7 +970,6 @@ Transakcja stała się niepotwierdzona z powodu reorganizacji łańcucha bloków Transakcja niepotwierdzona Prześlij na oszczędności - Prześlij na saldo wydatków Twoja wypłata nie powiodła się. Proszę ponownie zeskanować kod QR lub skontaktować się z pomocą techniczną. Wybór monet /Coin Selection/ Wymagana kwota @@ -1107,11 +1098,8 @@ Minimalna kwota dla tej faktury wynosi ₿ {amount}. Węzeł rozłączony. Błąd uruchamiania Lightning - Odbierz środki Lightning Włącz konfigurację w tle, aby bezpiecznie opuścić Bitkit podczas konfigurowania salda. Konfiguracja w tle - Ciesz się natychmiastowymi i tanimi\ntransakcjami z przyjaciółmi, rodziną\ni sprzedawcami. - Odbierz na <accent>saldo wydatków</accent> Pokaż szczegóły Auto Oszczędności diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 53396ccd4e..df3111a8f0 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -7,8 +7,6 @@ Comprar Compartilhe a Bitkit Convide - Pagamentos instantâneos - Pagar Quando a Bitkit está fechada Receba pagamentos Configure um PIN @@ -112,6 +110,8 @@ Agora é possível pagar qualquer pessoa, em qualquer lugar, instantaneamente. Saldo de Gastos Pronto Ponto do canal + CANAIS FECHAM EM %1$s + Seus canais Lightning serão fechados em %1$s. Transfira seu saldo de gastos para a poupança antes dessa data. Fechar Fechar Conexão A Transferência Falhou @@ -357,14 +357,6 @@ Conexão com a Internet Restaurada. Problemas de Conectividade com a Internet Parece que você está desconectado, tentando reconectar... - Reivindicando seu gitf code Bitkit... - Reivindicando presente - A Bitkit não pôde reivindicar os fundos. Tente novamente mais tarde ou entre em contato conosco. - Erro no código de presente - Este gift code da Bitkit já foi usado e os fundos já foram pagos. - Código usado - Desculpe, você chegou tarde! Todos os presentes deste código já foram reivindicados. - Presentes Esgotados Saiba Mais Entendido Saldo Alto @@ -1001,7 +993,6 @@ Bitcoin Recebido Peer desconectado. Receber - Receber fundos Lightning Receber Bitcoin Invoice Bitcoin Para receber mais Bitcoin instantâneo, a Bitkit precisa aumentar sua liquidez. A taxa de rede de <accent>{networkFee}</accent> e <accent>{serviceFee}</accent> de taxa do provedor de serviços serão deduzidas do valor que você especificou. @@ -1017,8 +1008,6 @@ Seu Saldo de Gastos usa a Lightning Network para tornar seus pagamentos mais baratos, mais rápidos e mais privados.\n\nIsso funciona como o acesso à Internet, mas você paga por liquidez e roteamento em vez de largura de banda.\n\nEssa configuração inclui alguns custos únicos. Seu Saldo de Gastos usa a Lightning Network para tornar seus pagamentos mais baratos, mais rápidos e mais privados.\n\nIsso funciona como o acesso à Internet, mas você paga por liquidez e roteamento em vez de largura de banda.\n\nA Bitkit precisa aumentar a capacidade de recebimento de seu saldo de gastos para processar esse pagamento. Comentário opcional para o pagador - Aproveite transações instantâneas\ne baratas com amigos, família\ne comerciantes. - Receba no <accent>saldo de gastos</accent> Mostrar Detalhes Mostrar código QR Editar Invoice @@ -1115,7 +1104,6 @@ Transação ficou não confirmada devido a reorganização da blockchain Transação Não Confirmada Transferir Para Poupança - Transferir Para Gastos Seu saque não foi bem-sucedido. Por favor, escaneie o QR code novamente ou entre em contato com o suporte. Adicionar Widget Examinar várias estatísticas sobre blocos de Bitcoin recém-minerados. diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 33a4fed7a7..82fd6ae033 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -7,8 +7,6 @@ Escanear e pagar Convide Compartilhe a Bitkit - Pagar - Pagamentos instantâneos Segurança Configure um PIN Comprar @@ -97,6 +95,8 @@ Depende da taxa Depende da taxa Depende da taxa + CANAIS FECHAM A %1$s + Os seus canais Lightning serão fechados a %1$s. Transfira o seu saldo de gastos para a poupança antes dessa data. Saldo de\n<accent>Gastos</accent> Use seu saldo de gastos para desfrutar de transações instantâneas e baratas com amigos, familiares e comerciantes. Começar @@ -406,14 +406,6 @@ Atualizar Bitkit não consegue ler os dados fornecidos. Este invoice lightning expirou. - Reivindicando presente - Reivindicando seu gitf code Bitkit... - Erro no código de presente - A Bitkit não pôde reivindicar os fundos. Tente novamente mais tarde ou entre em contato conosco. - Código usado - Este gift code da Bitkit já foi usado e os fundos já foram pagos. - Desculpe, você chegou tarde demais! Todos os presentes para este código já foram resgatados. - Presentes Esgotados Entrar Entrar em {domain}? Entrar @@ -932,7 +924,6 @@ Reduzir taxa Não é possível aumentar a taxa mais. Caso contrário, excederá metade do saldo de entrada atual. Transferir Para Poupança - Transferir Para Gastos Bitcoin Recebido Bitcoin Recebido Instantaneamente Selecionar intervalo @@ -952,13 +943,10 @@ Valor muito baixo O valor mínimo para este invoice é de ₿ {amount}. Erro de inicialização da Lightning - Receber fundos Lightning Auto Poupança Gastos Mostrar Detalhes - Receba no <accent>saldo de gastos</accent> - Aproveite transações instantâneas e baratas\ncom amigos, familiares\ne comerciantes. Ative a configuração em segundo plano para sair da Bitkit com segurança enquanto seu saldo está sendo configurado. Configurar em segundo plano Transação Substituída diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 1b60527452..ea8699fd88 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -7,8 +7,6 @@ Купить Поделитесь Bitkit Пригласить - Мгновенные платежи - Тратить Когда Bitkit закрыт Получать платежи Установите PIN-код @@ -120,6 +118,8 @@ Теперь вы можете платить кому угодно, где угодно, мгновенно. Баланс Расходов Готов Точка Канала + КАНАЛЫ ЗАКРОЮТСЯ %1$s + Ваши каналы Lightning будут закрыты %1$s. До этой даты переведите баланс расходов в сбережения. Закрыть Закрыть Соединение Перевод Не Удался @@ -371,14 +371,6 @@ Интернет-Соединение Восстановлено Проблемы с Подключением к Интернету Похоже, вы отключены, пытаемся переподключиться... - Получение вашего подарочного кода Bitkit... - Получение подарка - Bitkit не смог получить средства. Пожалуйста, попробуйте позже или свяжитесь с нами. - Ошибка подарочного кода - Этот подарочный код Bitkit уже был использован, и средства были выплачены. - Использованный код - К сожалению, вы опоздали! Все подарки по этому коду уже получены. - Подарки закончились Узнать Больше Понятно Высокий Баланс @@ -1019,7 +1011,6 @@ Получен Биткоин Пир отключён. Получить - Получить Lightning средства Получить Биткоин Биткоин инвойс Чтобы получить больше мгновенных биткоинов, Bitkit должен увеличить вашу ликвидность. Сетевая комиссия <accent>{networkFee}</accent> и комиссия провайдера услуг <accent>{serviceFee}</accent> будут вычтены из указанной суммы. @@ -1043,10 +1034,6 @@ Bitkit должен увеличить приёмную ёмкость вашего баланса расходов для обработки этого платежа. Опциональная заметка для плательщика - Наслаждайтесь мгновенными и недорогими -транзакциями с друзьями, семьёй -и продавцами. - Получить на <accent>баланс расходов</accent> Показать детали Показать QR-код Редактировать Инвойс @@ -1144,7 +1131,6 @@ Bitkit должен увеличить приёмную ёмкость ваше Транзакция стала неподтверждённой из-за реорганизации блокчейна Транзакция не подтверждена Перевести в сбережения - Перевести на расходы Вывод не удался. Пожалуйста, отсканируйте QR-код снова или обратитесь в поддержку. Добавить Виджет Изучайте различную статистику по недавно добытым блокам Bitcoin. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f3e6434bbe..aed1b6bae0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -36,8 +36,6 @@ Hardware Share Bitkit Invite - Instant payments - Spend When Bitkit is closed Get paid Set up a PIN code @@ -254,6 +252,8 @@ You can now pay anyone, anywhere, instantly. Spending Balance Ready Channel point + CHANNELS CLOSE ON %1$s + Your Lightning channels will be closed on %1$s. Move your spending balance to savings before then. Close Close Connection Transfer Failed @@ -527,14 +527,6 @@ It appears you’re disconnected, trying to reconnect... It appears you\’re disconnected. Please check your connection. Bitkit will try to reconnect every few seconds. Connection\nIssues]]> - Claiming your Bitkit gift code... - Claiming Gift - Bitkit couldn\'t claim the funds. Please try again later or contact us. - Gift Code Error - This Bitkit gift code has already been used, and the funds have been paid out. - Used Code - Sorry, you\'re too late! All gifts for this code have already been claimed. - Out of Gifts Learn More Understood High Balance @@ -1538,7 +1530,6 @@ This Lightning invoice uses features Bitkit does not support yet. Peer disconnected. Receive - Receive Lightning funds Receive Bitcoin Bitcoin invoice The liquidity quote is no longer valid. Try again to get a fresh quote. @@ -1560,8 +1551,6 @@ Your Spending Balance uses the Lightning Network to make your payments cheaper, faster, and more private.\n\nThis works like internet access, but you pay for liquidity & routing instead of bandwidth.\n\nThis setup includes some one-time costs. Your Spending Balance uses the Lightning Network to make your payments cheaper, faster, and more private.\n\nThis works like internet access, but you pay for liquidity & routing instead of bandwidth.\n\nBitkit needs to increase the receiving capacity of your spending balance to process this payment. Optional note to payer - Enjoy instant and cheap bitcoin payments on the Lightning Network. - Receive on <accent>spending balance</accent> Show Details QR Code Edit Invoice @@ -1667,7 +1656,6 @@ Transaction became unconfirmed due to blockchain reorganization Transaction Unconfirmed Transfer To Savings - Transfer To Spending Your withdrawal was unsuccessful. Please scan the QR code again or contact support. Active accounts Copy xpub diff --git a/app/src/test/java/to/bitkit/models/ReceiveLiquidityDecisionTest.kt b/app/src/test/java/to/bitkit/models/ReceiveLiquidityDecisionTest.kt index 1275ce35b4..e4f1899d67 100644 --- a/app/src/test/java/to/bitkit/models/ReceiveLiquidityDecisionTest.kt +++ b/app/src/test/java/to/bitkit/models/ReceiveLiquidityDecisionTest.kt @@ -1,21 +1,11 @@ package to.bitkit.models import org.junit.Test -import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue class ReceiveLiquidityDecisionTest { - private val defaultAdditionalLiquidityParams = ReceiveAdditionalLiquidityParams( - source = ReceiveLiquiditySource.SPENDING, - invoiceAmountSats = 10_000u, - inboundCapacitySats = 1_000u, - minCjitSats = 5_000u, - maxCjitAmountSats = 100_000u, - isGeoBlocked = false, - ) - @Test fun `lightning invoice requires ready channel`() { assertFalse( @@ -64,118 +54,4 @@ class ReceiveLiquidityDecisionTest { ) ) } - - @Test - fun `zero inbound does not route to additional CJIT`() { - assertEquals( - ReceiveAdditionalLiquidityAction.None, - additionalLiquidityAction( - defaultAdditionalLiquidityParams.copy(inboundCapacitySats = 0u) - ) - ) - } - - @Test - fun `savings and auto edits do not route to CJIT`() { - listOf(ReceiveLiquiditySource.SAVINGS, ReceiveLiquiditySource.AUTO).forEach { - assertEquals( - ReceiveAdditionalLiquidityAction.None, - additionalLiquidityAction( - defaultAdditionalLiquidityParams.copy(source = it) - ) - ) - } - } - - @Test - fun `below CJIT minimum routes to amount picker`() { - assertEquals( - ReceiveAdditionalLiquidityAction.ChooseAmount, - additionalLiquidityAction( - defaultAdditionalLiquidityParams.copy(invoiceAmountSats = 4_000u) - ) - ) - } - - @Test - fun `at CJIT minimum creates CJIT`() { - assertEquals( - ReceiveAdditionalLiquidityAction.CreateCjit(5_000u), - additionalLiquidityAction( - defaultAdditionalLiquidityParams.copy(invoiceAmountSats = 5_000u) - ) - ) - } - - @Test - fun `over max CJIT amount routes to amount picker`() { - assertEquals( - ReceiveAdditionalLiquidityAction.ChooseAmount, - additionalLiquidityAction( - defaultAdditionalLiquidityParams.copy(invoiceAmountSats = 100_001u) - ) - ) - } - - @Test - fun `unknown max CJIT amount routes to amount picker`() { - assertEquals( - ReceiveAdditionalLiquidityAction.ChooseAmount, - additionalLiquidityAction( - defaultAdditionalLiquidityParams.copy(maxCjitAmountSats = null) - ) - ) - } - - @Test - fun `geo-blocked routes to geo-block screen`() { - assertEquals( - ReceiveAdditionalLiquidityAction.GeoBlocked, - additionalLiquidityAction( - defaultAdditionalLiquidityParams.copy(isGeoBlocked = true) - ) - ) - } - - @Test - fun `CJIT limits are fetched only when additional liquidity can use them`() { - assertFalse( - ReceiveLiquidityDecision.needsCjitLimitsForAdditionalLiquidity( - source = ReceiveLiquiditySource.AUTO, - invoiceAmountSats = 10_000u, - inboundCapacitySats = 1_000u, - isGeoBlocked = false, - ) - ) - - assertFalse( - ReceiveLiquidityDecision.needsCjitLimitsForAdditionalLiquidity( - source = ReceiveLiquiditySource.SPENDING, - invoiceAmountSats = 10_000u, - inboundCapacitySats = 0u, - isGeoBlocked = false, - ) - ) - - assertFalse( - ReceiveLiquidityDecision.needsCjitLimitsForAdditionalLiquidity( - source = ReceiveLiquiditySource.SPENDING, - invoiceAmountSats = 10_000u, - inboundCapacitySats = 1_000u, - isGeoBlocked = true, - ) - ) - - assertTrue( - ReceiveLiquidityDecision.needsCjitLimitsForAdditionalLiquidity( - source = ReceiveLiquiditySource.SPENDING, - invoiceAmountSats = 10_000u, - inboundCapacitySats = 1_000u, - isGeoBlocked = false, - ) - ) - } - - private fun additionalLiquidityAction(params: ReceiveAdditionalLiquidityParams) = - ReceiveLiquidityDecision.additionalLiquidityAction(params) } diff --git a/app/src/test/java/to/bitkit/repositories/SuggestionsRepoTest.kt b/app/src/test/java/to/bitkit/repositories/SuggestionsRepoTest.kt index 16e1a01074..e929179396 100644 --- a/app/src/test/java/to/bitkit/repositories/SuggestionsRepoTest.kt +++ b/app/src/test/java/to/bitkit/repositories/SuggestionsRepoTest.kt @@ -28,7 +28,6 @@ class SuggestionsRepoTest : BaseUnitTest() { private val walletRepo = mock() private val settingsStore = mock() - private val transferRepo = mock() private val pubkyRepo = mock() private val hwWalletRepo = mock() @@ -42,7 +41,6 @@ class SuggestionsRepoTest : BaseUnitTest() { whenever(walletRepo.balanceState).thenReturn(MutableStateFlow(BalanceState())) whenever(settingsStore.data).thenReturn(flowOf(settings)) - whenever(transferRepo.activeTransfers).thenReturn(flowOf(emptyList())) whenever(pubkyRepo.isAuthenticated).thenReturn(MutableStateFlow(false)) whenever(hwWalletRepo.wallets).thenReturn(hardwareWallets) @@ -50,7 +48,6 @@ class SuggestionsRepoTest : BaseUnitTest() { bgDispatcher = testDispatcher, walletRepo = walletRepo, settingsStore = settingsStore, - transferRepo = transferRepo, pubkyRepo = pubkyRepo, hwWalletRepo = hwWalletRepo, ) diff --git a/app/src/test/java/to/bitkit/ui/ContentViewTest.kt b/app/src/test/java/to/bitkit/ui/ContentViewTest.kt index cc75279ca8..71b7841dd7 100644 --- a/app/src/test/java/to/bitkit/ui/ContentViewTest.kt +++ b/app/src/test/java/to/bitkit/ui/ContentViewTest.kt @@ -180,14 +180,6 @@ class ContentViewTest { assertEquals(Routes.SpendingAmount, transferSpendingStartRoute(hasSeenSpendingIntro = true)) } - @Test - fun `hardware spending start route keeps device id after intro`() { - val deviceId = "trezor-1" - - assertEquals(Routes.SpendingIntroHw(deviceId), transferSpendingStartRoute(false, deviceId)) - assertEquals(Routes.SpendingAmountHw(deviceId), transferSpendingStartRoute(true, deviceId)) - } - @Test fun `transfer effect destinations cover funding paid and hw signed`() { assertEquals(Routes.SettingUp, transferEffectDestination(TransferEffect.OnSpendingFundingPaid)) diff --git a/app/src/test/java/to/bitkit/ui/screens/wallets/HomeViewModelTest.kt b/app/src/test/java/to/bitkit/ui/screens/wallets/HomeViewModelTest.kt index 1c0cb0bb4a..cc434db044 100644 --- a/app/src/test/java/to/bitkit/ui/screens/wallets/HomeViewModelTest.kt +++ b/app/src/test/java/to/bitkit/ui/screens/wallets/HomeViewModelTest.kt @@ -9,7 +9,9 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.advanceUntilIdle import org.junit.Before import org.junit.Test +import org.mockito.kotlin.any import org.mockito.kotlin.doSuspendableAnswer +import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -18,6 +20,7 @@ import to.bitkit.data.SettingsData import to.bitkit.data.SettingsStore import to.bitkit.data.WidgetsData import to.bitkit.env.Env +import to.bitkit.models.ActivityBannerType import to.bitkit.models.BalanceState import to.bitkit.models.HwWallet import to.bitkit.models.Suggestion @@ -26,6 +29,7 @@ import to.bitkit.repositories.ActivityRepo import to.bitkit.repositories.CurrencyRepo import to.bitkit.repositories.CurrencyState import to.bitkit.repositories.HwWalletRepo +import to.bitkit.repositories.LightningRepo import to.bitkit.repositories.PubkyRepo import to.bitkit.repositories.SuggestionsRepo import to.bitkit.repositories.TransferRepo @@ -47,6 +51,8 @@ class HomeViewModelTest : BaseUnitTest() { private val currencyRepo = mock() private val settingsStore = mock() private val transferRepo = mock() + private val lightningRepo = mock() + private val hasTrustedChannels = MutableStateFlow(false) private val pubkyRepo = mock() private val activityRepo = mock() private val hwWalletRepo = mock() @@ -63,6 +69,7 @@ class HomeViewModelTest : BaseUnitTest() { suggestions = MutableStateFlow(emptyList()) whenever(context.getString(R.string.lightning__transfer_in_progress)).thenReturn("Transfer in progress") + whenever(context.getString(eq(R.string.lightning__channels_closing_banner), any())).thenReturn("Channels close") whenever(walletRepo.balanceState).thenReturn(MutableStateFlow(BalanceState())) whenever(widgetsRepo.widgetsDataFlow).thenReturn(MutableStateFlow(WidgetsData())) whenever(widgetsRepo.articlesFlow).thenReturn(MutableStateFlow(emptyList())) @@ -71,6 +78,7 @@ class HomeViewModelTest : BaseUnitTest() { whenever(settingsStore.data).thenReturn(MutableStateFlow(SettingsData())) whenever(transferRepo.activeTransfers).thenReturn(MutableStateFlow(emptyList())) whenever(transferRepo.forceCloseRemainingDuration).thenReturn(MutableStateFlow(null)) + whenever(lightningRepo.hasTrustedChannels).thenReturn(hasTrustedChannels) whenever(pubkyRepo.isAuthenticated).thenReturn(MutableStateFlow(false)) whenever(pubkyRepo.displayName).thenReturn(MutableStateFlow(null)) whenever(pubkyRepo.displayImageUri).thenReturn(MutableStateFlow(null)) @@ -93,6 +101,25 @@ class HomeViewModelTest : BaseUnitTest() { assertTrue(Suggestion.HARDWARE in sut.uiState.value.suggestions) } + @Test + fun `shows channels closing banner when wallet has blocktank channels`() = test { + hasTrustedChannels.value = true + val sut = createViewModel() + + advanceUntilIdle() + + assertTrue(sut.uiState.value.banners.any { it.type == ActivityBannerType.CHANNELS_CLOSING }) + } + + @Test + fun `hides channels closing banner without blocktank channels`() = test { + val sut = createViewModel() + + advanceUntilIdle() + + assertFalse(sut.uiState.value.banners.any { it.type == ActivityBannerType.CHANNELS_CLOSING }) + } + @Test fun `updates hardware wallets from repo`() = test { val hardwareWallet = hardwareWallet() @@ -160,6 +187,7 @@ class HomeViewModelTest : BaseUnitTest() { currencyRepo = currencyRepo, settingsStore = settingsStore, transferRepo = transferRepo, + lightningRepo = lightningRepo, pubkyRepo = pubkyRepo, activityRepo = activityRepo, hwWalletRepo = hwWalletRepo, diff --git a/app/src/test/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceVMTest.kt b/app/src/test/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceVMTest.kt deleted file mode 100644 index 9bacae9389..0000000000 --- a/app/src/test/java/to/bitkit/ui/screens/wallets/receive/EditInvoiceVMTest.kt +++ /dev/null @@ -1,108 +0,0 @@ -package to.bitkit.ui.screens.wallets.receive - -import app.cash.turbine.test -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.runBlocking -import org.junit.Before -import org.junit.Test -import org.mockito.kotlin.mock -import org.mockito.kotlin.times -import org.mockito.kotlin.verify -import org.mockito.kotlin.whenever -import to.bitkit.models.ReceiveAdditionalLiquidityAction -import to.bitkit.models.ReceiveLiquiditySource -import to.bitkit.repositories.BlocktankRepo -import to.bitkit.repositories.BlocktankState -import to.bitkit.repositories.WalletRepo -import to.bitkit.test.BaseUnitTest -import to.bitkit.ui.screens.wallets.receive.EditInvoiceVM.EditInvoiceScreenEffects -import kotlin.test.assertEquals - -class EditInvoiceVMTest : BaseUnitTest() { - - private lateinit var sut: EditInvoiceVM - private val walletRepo: WalletRepo = mock() - private val blocktankRepo: BlocktankRepo = mock() - - @Before - fun setUp() = runBlocking { - whenever(blocktankRepo.blocktankState).thenReturn(MutableStateFlow(BlocktankState(minCjitSats = 5_000))) - whenever(walletRepo.inboundLiquiditySats()).thenReturn(1_000u) - sut = EditInvoiceVM(walletRepo, blocktankRepo) - } - - @Test - fun `onClickContinue should emit none for auto when amount exceeds inbound`() = test { - sut.editInvoiceEffect.test { - sut.onClickContinue( - source = ReceiveLiquiditySource.AUTO, - amountSats = 10_000u, - isGeoBlocked = false, - ) - - assertEquals( - EditInvoiceScreenEffects.ApplyReceiveLiquidityAction(ReceiveAdditionalLiquidityAction.None), - awaitItem(), - ) - cancelAndIgnoreRemainingEvents() - } - verify(walletRepo, times(1)).inboundLiquiditySats() - } - - @Test - fun `onClickContinue should emit choose amount for spending below CJIT minimum`() = test { - whenever(blocktankRepo.maxCjitAmountSats()).thenReturn(Result.success(100_000u)) - - sut.editInvoiceEffect.test { - sut.onClickContinue( - source = ReceiveLiquiditySource.SPENDING, - amountSats = 4_000u, - isGeoBlocked = false, - ) - - assertEquals( - EditInvoiceScreenEffects.ApplyReceiveLiquidityAction(ReceiveAdditionalLiquidityAction.ChooseAmount), - awaitItem(), - ) - cancelAndIgnoreRemainingEvents() - } - } - - @Test - fun `onClickContinue should emit create CJIT for spending amount within limits`() = test { - whenever(blocktankRepo.maxCjitAmountSats()).thenReturn(Result.success(100_000u)) - - sut.editInvoiceEffect.test { - sut.onClickContinue( - source = ReceiveLiquiditySource.SPENDING, - amountSats = 10_000u, - isGeoBlocked = false, - ) - - assertEquals( - EditInvoiceScreenEffects.ApplyReceiveLiquidityAction( - ReceiveAdditionalLiquidityAction.CreateCjit(10_000u) - ), - awaitItem(), - ) - cancelAndIgnoreRemainingEvents() - } - } - - @Test - fun `onClickContinue should emit geo blocked without fetching CJIT limits`() = test { - sut.editInvoiceEffect.test { - sut.onClickContinue( - source = ReceiveLiquiditySource.SPENDING, - amountSats = 10_000u, - isGeoBlocked = true, - ) - - assertEquals( - EditInvoiceScreenEffects.ApplyReceiveLiquidityAction(ReceiveAdditionalLiquidityAction.GeoBlocked), - awaitItem(), - ) - cancelAndIgnoreRemainingEvents() - } - } -} diff --git a/changelog.d/next/1669.removed.md b/changelog.d/next/1669.removed.md new file mode 100644 index 0000000000..24fc843c91 --- /dev/null +++ b/changelog.d/next/1669.removed.md @@ -0,0 +1 @@ +Bitkit no longer opens new Lightning channels or instant receive channels, and wallets with Lightning channels show the date the channels close so funds can be moved to savings first. From b8fed099cc83ae3dc6483c00383e3e7f0d3a743d Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 22 Oct 2026 02:32:28 +0200 Subject: [PATCH 2/8] test: update journeys for removed channel purchases --- journeys/README.md | 49 +++++++++--- journeys/amount-limits/README.md | 27 ++++--- .../external-amount-over-max.xml | 2 +- .../transfer-spending-advanced-over-max.xml | 26 ------- .../transfer-spending-over-max.xml | 24 ------ .../transfer-spending-preset-delete.xml | 47 ------------ journeys/backup-restore/README.md | 11 +-- ...restore-keeps-tags-and-closed-channels.xml | 13 ++-- journeys/channels-closing/README.md | 41 ++++++++++ .../channels-closing-notice.xml | 31 ++++++++ journeys/cjit-notifications/README.md | 65 ++++++---------- .../cjit-foreground-service-notification.xml | 25 ------- .../cjit-push-single-notification.xml | 24 ------ ...n-cjit-channel-no-payment-notification.xml | 13 ++-- journeys/hardware-wallet/README.md | 32 ++++---- .../hardware-wallet/activity-blue-icons.xml | 5 +- journeys/hardware-wallet/detail-overview.xml | 6 +- .../passphrase-transfer-to-spending.xml | 74 ------------------- .../transfer-to-spending-max-lsp-cap.xml | 44 ----------- .../transfer-to-spending-node-warmup.xml | 37 ---------- .../hardware-wallet/transfer-to-spending.xml | 59 --------------- .../node-lifecycle/cancelled-node-restart.xml | 5 +- journeys/notification-permission/README.md | 72 ++++-------------- ...ceive-cjit-confirm-notification-toggle.xml | 23 ------ ...ive-cjit-liquidity-notification-toggle.xml | 25 ------- .../toggle-off-opens-system-settings.xml | 24 ------ ...r-spending-confirm-notification-toggle.xml | 25 ------- .../receive/receive-auto-tab-selection.xml | 6 +- .../receive-onchain-only-without-channel.xml | 27 +++++++ journeys/receive/receive-tabs-wraparound.xml | 20 ++--- journeys/receive/usdt-receive-swipe-tabs.xml | 17 +++-- .../spending-confirm-amount-change.xml | 37 ---------- .../transfer-to-savings-returns-home.xml | 7 +- .../closed-channel-transfer-settles.xml | 58 --------------- journeys/usdt-wallet-navigation.xml | 2 +- 35 files changed, 266 insertions(+), 737 deletions(-) delete mode 100644 journeys/amount-limits/transfer-spending-advanced-over-max.xml delete mode 100644 journeys/amount-limits/transfer-spending-over-max.xml delete mode 100644 journeys/amount-limits/transfer-spending-preset-delete.xml create mode 100644 journeys/channels-closing/README.md create mode 100644 journeys/channels-closing/channels-closing-notice.xml delete mode 100644 journeys/cjit-notifications/cjit-foreground-service-notification.xml delete mode 100644 journeys/cjit-notifications/cjit-push-single-notification.xml delete mode 100644 journeys/hardware-wallet/passphrase-transfer-to-spending.xml delete mode 100644 journeys/hardware-wallet/transfer-to-spending-max-lsp-cap.xml delete mode 100644 journeys/hardware-wallet/transfer-to-spending-node-warmup.xml delete mode 100644 journeys/hardware-wallet/transfer-to-spending.xml delete mode 100644 journeys/notification-permission/receive-cjit-confirm-notification-toggle.xml delete mode 100644 journeys/notification-permission/receive-cjit-liquidity-notification-toggle.xml delete mode 100644 journeys/notification-permission/toggle-off-opens-system-settings.xml delete mode 100644 journeys/notification-permission/transfer-spending-confirm-notification-toggle.xml create mode 100644 journeys/receive/receive-onchain-only-without-channel.xml delete mode 100644 journeys/transfer/spending-confirm-amount-change.xml delete mode 100644 journeys/transfers/closed-channel-transfer-settles.xml diff --git a/journeys/README.md b/journeys/README.md index 5647c3bef5..94e7cc5773 100644 --- a/journeys/README.md +++ b/journeys/README.md @@ -26,7 +26,7 @@ escalating. What this proves, and the preconditions needed to prove it. Tap the Spending balance card on the home screen - Verify the spending amount screen (tag "SpendingAmount") is visible + Verify the spending wallet screen (tag "TotalBalance") is visible ``` @@ -107,6 +107,22 @@ through the `lsp` helper at the repo root, which talks to the same staging LSP: `deposit` prints the funding txid; **`mine` prints nothing on success** and signals only through its exit status, so an empty response there is not a failure. Give the wallet ~20s to sync. +### Getting a Lightning channel + +Blocktank is winding down, and this build no longer buys channels from it (synonymdev/bitkit-android#1669): +Transfer To Spending, the Receive sheet's CJIT button and gift codes are gone. A journey that needs a +Lightning channel as its precondition says "a wallet with an open Lightning channel"; get one in either way: + +- Install a build from before that change (master before synonymdev/bitkit-android#1669), open a channel with + Transfer To Spending, then install this build over it. The wallet, its channel and its balances carry over. +- Or open a manual channel to the staging LSP node on this build: Settings › Advanced › Lightning + Connections › add connection (opens Funding Advanced) › Manual Setup (testTag `FundManual`), then enter the + node id, host and port from `./lsp GET /info` (id + `028a8910b0048630d4eb17af25668cdd7ea6f2d8ae20956e7a06e2ae46ebcb69fc`, host `34.65.86.104`, port `9400`), + pick an amount, confirm, and mine blocks with `./lsp POST /regtest/chain/mine '{"count":3}'`. The Spending + balance is available once the channel is ready. A channel this wallet opens holds outbound liquidity + only, so a journey that receives over Lightning needs the first route, which buys inbound capacity. + Fund a wallet before any amount journey — with a zero balance the caps fall back to the global maximum and the journeys pass for the wrong reason. Per-suite preconditions (Trezor emulator, Pubky fixtures, push notifications) live in each suite's README. @@ -127,16 +143,31 @@ journey PR, which is what made this file conflict on every merge. | Controlled USDT quotes, provider history, funded submission and recovery | an explicitly configured local USDT gateway/RPC/bundler fixture with owned test accounts and funds; absent from the standard journey environment. Report these steps as blocked unless that setup and transfer authorization are supplied — [USDT QA](../docs/usdt-qa.md) | | On-chain funds and blocks on regtest | `./lsp` deposit and mine against the staging LSP — [Backend preconditions](#backend-preconditions) | | Several separate on-chain UTXOs to choose between | three or more `./lsp` deposits, each mined, so manual coin selection has inputs to list — [Backend preconditions](#backend-preconditions) | -| Lightning channels, CJIT orders and quoted maxima | the same staging LSP the dev flavor targets, plus its node as an external LN peer — [Backend preconditions](#backend-preconditions), [amount-limits](amount-limits/README.md) | +| Lightning channels and quoted maxima | the same staging LSP the dev flavor targets, plus its node as an external LN peer; this build opens channels only through Lightning Connections › add connection — [Getting a Lightning channel](#getting-a-lightning-channel), [amount-limits](amount-limits/README.md) | | A hardware wallet to pair, watch and sign with | the deterministic Trezor emulator from `bitkit-docker` over the Bridge transport, with the USB attach intent injected by `adb`; USB enumeration, permission grants, the OS picker and BLE are not simulated — [hardware-wallet](hardware-wallet/README.md) | -| Push notifications to a backgrounded or killed app | an FCM push from a CJIT order paid through `./lsp`, read back with `adb shell dumpsys notification` — [cjit-notifications](cjit-notifications/README.md) | -| The OS notification-permission dialog | an API 33+ target, reset with `adb shell pm revoke to.bitkit.dev android.permission.POST_NOTIFICATIONS` — [notification-permission](notification-permission/README.md) | +| Notifications read back from the shade | `adb shell dumpsys notification` — [cjit-notifications](cjit-notifications/README.md). Push to a killed app via a CJIT order is retired with CJIT | +| The OS notification-permission dialog | an API 33+ target, reset with `adb shell pm revoke to.bitkit.dev android.permission.POST_NOTIFICATIONS`; no journey uses it since the suite was retired — [notification-permission](notification-permission/README.md) | | An incoming Payment Request from a linked issuer | the fixture issuer, saved as a contact and linked as identities — [payment-requests](payment-requests/README.md) | | Two linked Bitkit wallets for a subscription lifecycle | a second Bitkit instance linked to the first, so a proposal can be reviewed and accepted — [subscriptions](subscriptions) | | A Pubky identity and a two-wallet marketplace purchase | the integration fixture runtime: Pubky testnet, Paykit Server, regtest bitcoind and Fulcrum — [pubky-marketplace](pubky-marketplace/README.md) | | LNURL pay, withdraw, channel and auth, and Lightning Addresses | the `bitkit-docker` `lnurl-server` on local regtest, with the app started by `just run docker`, which builds with `E2E=true` and forwards its ports over `adb reverse`; it issues memo invoices, so a check that needs a description-hash invoice needs another endpoint — [lnurl](lnurl) | | Deep links, addresses and invoices handed to the app | `adb shell am start -a android.intent.action.VIEW -d ""`; `bitkit://` screen and sheet routes sit behind the dev-mode gate — [Running a journey](#running-a-journey), [deeplinks](deeplinks) | +## Retired journeys + +Blocktank channel purchases were removed in synonymdev/bitkit-android#1669, so every journey whose route +buys a channel went with them. They are listed here rather than left as stale files; the iOS twin +(synonymdev/bitkit-ios#1097) retires the same names. + +| Retired journey | Route it covered | +| --- | --- | +| `amount-limits/transfer-spending-over-max.xml`, `transfer-spending-advanced-over-max.xml`, `transfer-spending-preset-delete.xml` | Transfer To Spending amount and Receiving Capacity number pads | +| `cjit-notifications/cjit-foreground-service-notification.xml`, `cjit-push-single-notification.xml` | CJIT channel-ready notifications from the foreground service and from an FCM push | +| `hardware-wallet/transfer-to-spending.xml`, `transfer-to-spending-max-lsp-cap.xml`, `transfer-to-spending-node-warmup.xml`, `passphrase-transfer-to-spending.xml` | Hardware wallet Transfer To Spending (the `HardwareTransferToSpending` button is gone) | +| `notification-permission/receive-cjit-confirm-notification-toggle.xml`, `receive-cjit-liquidity-notification-toggle.xml`, `transfer-spending-confirm-notification-toggle.xml`, `toggle-off-opens-system-settings.xml` | The "Set up in background" toggle on the CJIT and Spending Confirm screens, which nothing navigates to now | +| `transfer/spending-confirm-amount-change.xml` | Spending Confirm amount rebuilt at swipe | +| `transfers/closed-channel-transfer-settles.xml` | Pending Transfer To Spending banner after the LSP closes the channel | + ## Cross-platform These journeys are also carried by [`bitkit-ios/journeys`](https://github.com/synonymdev/bitkit-ios/tree/main/journeys), @@ -150,8 +181,6 @@ Known differences in the corpus, as of the iOS port (synonymdev/bitkit-ios#691): | Here | On iOS | | --- | --- | -| `cjit-notifications/cjit-foreground-service-notification.xml` | `cjit-background-notification.xml`, with the thousands-separator assertions dropped — the notification extension never formats an amount | -| `notification-permission/toggle-off-opens-system-settings.xml` | `toggle-off-and-system-settings-route.xml` — iOS does not deep link into system settings, so it asserts the real route in through Settings ▸ Notifications | | `hardware-wallet/usb-reconnect.xml` | `reconnect.xml` — over Bridge, since iOS cannot do WebUSB | | `hardware-wallet/receive-onchain.xml`, `hardware-wallet/send-onchain.xml` | not ported | | `activity/date-range-rapid-month-taps.xml` | not ported — iOS has no activity journey suite, and the rapid month tap behaviour was not checked there | @@ -170,7 +199,6 @@ Known differences in the corpus, as of the iOS port (synonymdev/bitkit-ios#691): | `send/retry-original-dialog-style.xml` | not ported — iOS shows the two Retry original payment steps as native alerts | | `send/prepared-fee-recovery.md` | Same four manual fault cases and file on both platforms; the recipe records original-send versus replacement fee retention and iOS Max/identity-flow adaptations. These faults are not journey capabilities. | | `settings/electrum-server-error-toasts.xml` | not ported — iOS still shows one generic message for every manual Electrum connect failure | -| `transfers/closed-channel-transfer-settles.xml` | not ported — the closed-channel and order-closure settle rules are an iOS follow-up | | `onchain-receive/*` | port pending in synonymdev/bitkit-ios#588, which wires `onchainTransactionConfirmed` into the same received-sheet flow but carries no `journeys/` files. Two adaptations when it lands: iOS suppresses replayed historical receives with a `pendingRestoreActivitySeen` flag cleared by the first post-restore on-chain sync, not the one-hour block-timestamp guard used here, so a stale-confirmation step has to drive a restore instead of a clock; and iOS has no foreground-service path, so `confirmed-only-background-notification.xml` has no counterpart | | `backup/show-mnemonic-long-words.xml` | not ported — the long-word fit is an Android-only change (synonymdev/bitkit-android#633); whether iOS wraps long words at larger text sizes is unchecked | | `security/forgot-pin-nav-bar-inset.xml` | not ported — Android-only fix (synonymdev/bitkit-android#1572): the Android sheet host adds no navigation-bar inset, so each sheet screen pads itself. iOS sheets keep their content inside the bottom safe area | @@ -185,7 +213,6 @@ Known differences in the corpus, as of the iOS port (synonymdev/bitkit-ios#691): | `deeplinks/screen-deeplink.xml`, `deeplinks/sheet-deeplink.xml` | not ported — iOS registers the `bitkit` scheme but has no screen or sheet router | | `backup-restore/restore-keeps-tags-and-closed-channels.xml` | not ported yet — iOS already gates uploads across the whole restore (`AppScene.restoreFromMostRecentBackup` sets `BackupService.setRestoring(true)` before the timestamp probe), but still applies the three activity slices in one block (`BackupService.performFullRestoreFromLatestBackup`), which is the half this journey pins; port it with the iOS slice fix | | `shop/gift-card-category-titles.xml` | not ported — iOS still hardcodes the category names, and its route in has no screen deeplink | -| `amount-limits/transfer-spending-preset-delete.xml` | not ported yet — the same fix shipped in synonymdev/bitkit-ios#289, so this one should port | | `app-update/critical-update-onboarding.xml` | not ported yet — iOS already blocks at the top level in `AppScene`, so the journey applies there once written | | `home/pull-to-refresh-rates.xml` | not ported — iOS does not refresh exchange rates on pull to refresh | | `home/timed-reminders-do-not-interrupt.xml` | paired draft in synonymdev/bitkit-ios#1013; the journey name and actions match, but iOS device verification is incomplete. Android resumes Home after permission prompts and background/foreground; iOS uses primary-screen lifecycle callbacks and explicit rechecks. | @@ -193,13 +220,13 @@ Known differences in the corpus, as of the iOS port (synonymdev/bitkit-ios#691): | `receive/receive-auto-tab-selection.xml` | not ported — the Auto tab override fix is Android-only so far; iOS parity not checked | | `security/pin-lock-on-resume.xml` | not ported yet — iOS already clears the PIN verification on entering the background, so the journey applies; it lands with the iOS side of synonymdev/bitkit-android#1298 | | `security/pin-result-long-label.xml` | not ported — the toggle exists on the iOS security success screen, but the overlap check is a follow-up | +| `channels-closing/channels-closing-notice.xml` | port pending in synonymdev/bitkit-ios#1097 — same file, journey name and steps; the iOS views take the `ChannelsClosingBanner` and `ChannelsClosingNotice` identifiers, and the date is a placeholder constant until the shutdown date is final | +| `receive/receive-onchain-only-without-channel.xml` | port pending in synonymdev/bitkit-ios#1097 — same file, journey name and steps; Android asserts the absence of `Tab-spending` and of the Spend suggestion card (`Suggestion-lightning`) by identifier, iOS cards expose no identifier, so read them from a screenshot | | `tags/activity-tag-length-cap.xml` | not ported — iOS has no 20-character cap on tag input | | `transfer/transfer-to-savings-returns-home.xml` | not ported — iOS already resets navigation to home on the same OK, so the journey has no iOS counterpart yet | | `lnurl/lnurl-pay-comment-note.xml` | not ported — bitkit-ios has not been checked for keeping the LNURL-pay comment on the activity | | `backup/confirm-mnemonic-clear-wrong-word.xml` | not ported — `BackupConfirmMnemonic.swift` clears only the last word by its chip, with no red-word tap | | `coin-selection/manual-coin-selection-load.xml` | not ported — iOS has `SendUtxoSelectionView` but no load error, retry or identifiers to assert on | -| `transfer/spending-confirm-amount-change.xml` | same file — both platforms show the same "Fees changed" info toast; iOS has no app-log step, and closing the received sheet re-sizes its Spending Confirm, so the lower amounts can show before the swipe | -| — | `hardware-wallet/transfer-to-spending-over-max.xml` exists only on iOS | ### Running one on iOS @@ -258,6 +285,8 @@ and Settings (`Tab-general`, `Tab-security`, `Tab-advanced`, `NavigationBack`, ` | Confirm mnemonic selected slot | `SelectedWord-` | — *(no identifier on `ConfirmWordView` rows)* | | Widgets intro screen container | — | `WidgetsOnboarding` | | Home suggestion cards | `Suggestion-` | — *(cards expose no identifier)* | +| Channels closing banner on Home | `ChannelsClosingBanner` | `ChannelsClosingBanner` *(to match when synonymdev/bitkit-ios#1097 lands)* | +| Channels closing notice on the Spending wallet | `ChannelsClosingNotice` | `ChannelsClosingNotice` *(to match when synonymdev/bitkit-ios#1097 lands)* | | Receive QR copy button | `ReceiveCopyQR` | `ReceiveCopyQR` *(absent from `snapshot-ui` targets; see below)* | | Payment Request row | `PaymentRequestRow-` | `PaymentRequestRow--` *(`-one-time` for a one-off)* | | Payment Request detail screen | `PaymentRequestDetailsScreen` | `PaymentRequestDetailScreen` | diff --git a/journeys/amount-limits/README.md b/journeys/amount-limits/README.md index e89c47fd6b..9da1006301 100644 --- a/journeys/amount-limits/README.md +++ b/journeys/amount-limits/README.md @@ -2,8 +2,10 @@ These journeys exercise the "block numberpad input exceeding the max/available amount" behaviour added on `fix/block-input-over-max`. The same `AmountInputViewModel.setMaxAmount` + -`MaxExceeded` effect path backs all four amount-entry screens (Send, Transfer→Spending, -Receiving capacity, External node). +`MaxExceeded` effect path backs the amount-entry screens. Two journeys remain: Send and External +node. The Transfer→Spending and Receiving-capacity journeys are retired because Blocktank channel +purchases were removed (synonymdev/bitkit-android#1669); their screens still back the same view model but +no button navigates to them. ## What the feature does - Typing a digit that would push the amount **over the cap is rejected** — the display stays at @@ -20,9 +22,8 @@ Receiving capacity, External node). - Fund + mine via the `lsp` skill: `./lsp POST /regtest/chain/deposit '{"address":"","amountSat":100000}'` then `./lsp POST /regtest/chain/mine '{"count":3}'` and wait for the balance to sync. -2. **Transfer/Spending/Receiving-capacity flows need the node connected to the LSP** so a real - max can be quoted. On the Spending amount screen the max starts at `0` behind a spinner — - **wait for it to populate** before typing. +2. **The External-node flow needs the node running** so the on-chain maximum can be computed; + wait for the available amount to populate before typing. 3. **External-node flow needs a reachable LN peer.** The staging LSP node works as the peer: id `028a8910b0048630d4eb17af25668cdd7ea6f2d8ae20956e7a06e2ae46ebcb69fc`, host `34.65.86.104`, port `9400` (from `./lsp GET /info`). @@ -44,12 +45,14 @@ Receiving capacity, External node). - Send: screen `send_amount_screen`, field `SendNumberField`, available `available_balance`, max `SendAmountMax`, continue `ContinueAmount`; recipient `RecipientManual` / `RecipientInput` / `AddressContinue`; home Send button `Send`. -- Transfer→Spending: screen `SpendingAmount`, field `SpendingAmountNumberField`, - available `SpendingAmountAvailable`, 25% `SpendingAmountQuarter`, max `SpendingAmountMax`, - continue `SpendingAmountContinue`. -- Receiving capacity: screen `SpendingAdvanced`, field `SpendingAdvancedNumberField`, - min/default/max `SpendingAdvancedMin`/`SpendingAdvancedDefault`/`SpendingAdvancedMax`, - continue `SpendingAdvancedContinue`. -- External: funding `FundManual`; connection `NodeIdInput`/`HostInput`/`PortInput`/`ExternalContinue`; +- External: Lightning Connections add button `add_connection_button` (opens Funding Advanced), `FundManual`; connection `NodeIdInput`/`HostInput`/`PortInput`/`ExternalContinue`; amount screen `ExternalAmount`, field `ExternalAmountNumberField`, 25% `ExternalAmountQuarter`, max `ExternalAmountMax`, continue `ExternalAmountContinue`. + +## Retired journeys + +`transfer-spending-over-max.xml`, `transfer-spending-advanced-over-max.xml` and +`transfer-spending-preset-delete.xml` were retired because Blocktank channel purchases were removed +(synonymdev/bitkit-android#1669). Their route started at the Savings wallet's "Transfer To Spending" +button, which is gone. Their tags (`TransferToSpending`, `SpendingAmount*`, `SpendingAdvanced*`) are no +longer reachable from the UI. diff --git a/journeys/amount-limits/external-amount-over-max.xml b/journeys/amount-limits/external-amount-over-max.xml index b633dbdced..4a70615a15 100644 --- a/journeys/amount-limits/external-amount-over-max.xml +++ b/journeys/amount-limits/external-amount-over-max.xml @@ -13,7 +13,7 @@ Open Settings, then the "Advanced" tab, then "Lightning Connections" - Tap "Get Started" to open the funding options screen + Tap "Get Started" (testTag "add_connection_button"; "Add Connection" when a connection already exists) to open the Funding Advanced screen, which offers LNURL Channel QR and Manual Setup Tap "Manual Setup" (testTag "FundManual") Type the node id into the node id field (testTag "NodeIdInput") Type the host into the host field (testTag "HostInput") diff --git a/journeys/amount-limits/transfer-spending-advanced-over-max.xml b/journeys/amount-limits/transfer-spending-advanced-over-max.xml deleted file mode 100644 index e0b1557fbe..0000000000 --- a/journeys/amount-limits/transfer-spending-advanced-over-max.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - Verifies the receiving capacity (advanced) number pad blocks input exceeding the maximum LSP - balance, shows the "Receiving Capacity Maximum" warning toast, and still allows deleting digits - while at the cap. - - Precondition: onboarded dev wallet with a POSITIVE on-chain Savings balance and a running node - connected to the LSP. Start on the wallet home screen. The advanced screen is reached by first - setting a valid spending amount and continuing to the confirm screen. - - - Tap the Spending balance card on the home screen - Tap "Transfer from Savings" - If a transfer intro screen appears, tap "Get Started" - Wait until the spending amount screen (testTag "SpendingAmount") has loaded a positive maximum (testTag "SpendingAmountAvailable") - Tap "25%" (testTag "SpendingAmountQuarter") to set a valid amount within range - Tap Continue (testTag "SpendingAmountContinue") - On the confirm screen, tap "Advanced" - Verify the receiving capacity screen (testTag "SpendingAdvanced") is visible - Tap the "9" key (testTag "N9") nine times to enter a capacity far larger than the maximum allowed - Verify a "Receiving Capacity Maximum" warning toast appears - Verify the amount in the input field (testTag "SpendingAdvancedNumberField") does not exceed the maximum receiving capacity - Tap the delete key (testTag "NRemove") once - Verify the amount in the input field (testTag "SpendingAdvancedNumberField") decreased after the delete - - diff --git a/journeys/amount-limits/transfer-spending-over-max.xml b/journeys/amount-limits/transfer-spending-over-max.xml deleted file mode 100644 index 3e1a3a9544..0000000000 --- a/journeys/amount-limits/transfer-spending-over-max.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - Verifies the "Transfer to Spending" amount number pad blocks input exceeding the maximum allowed - transfer amount, shows the "Spending Balance Maximum" warning toast, and still allows deleting - digits while at the cap. - - Precondition: onboarded dev wallet with a POSITIVE on-chain Savings balance and a running node - connected to the LSP (so a real max can be quoted). The max starts at 0 behind a spinner — wait - for it to populate. Note the cap may be lower than the visible "Available" due to fees. Start on - the wallet home screen. - - - Tap the Spending balance card on the home screen - Tap "Transfer from Savings" - If a transfer intro screen appears, tap "Get Started" - Verify the spending amount screen (testTag "SpendingAmount") is visible - Wait until the available/maximum amount (testTag "SpendingAmountAvailable") finishes loading and shows a positive value - Tap the "9" key (testTag "N9") nine times to enter an amount far larger than the maximum allowed - Verify a "Spending Balance Maximum" warning toast appears - Verify the amount in the input field (testTag "SpendingAmountNumberField") does not exceed the stated maximum - Tap the delete key (testTag "NRemove") once - Verify the amount in the input field (testTag "SpendingAmountNumberField") decreased after the delete - - diff --git a/journeys/amount-limits/transfer-spending-preset-delete.xml b/journeys/amount-limits/transfer-spending-preset-delete.xml deleted file mode 100644 index cda6165264..0000000000 --- a/journeys/amount-limits/transfer-spending-preset-delete.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - Verifies that after the "Transfer to Spending" amount is filled by a preset (25% or MAX) or by - switching the number pad unit from fiat, every delete press removes exactly one digit and - appended digits are accepted. The displayed amount keeps its sats grouping spaces, but the - number pad input underneath does not, so no press is spent on a space (synonymdev/bitkit-android#558). - - Precondition: onboarded dev wallet with the display unit set to modern Bitcoin (sats), primary - display Bitcoin, a POSITIVE on-chain Savings balance of at least 250 000 sats, LSP limits above - that, and a running node connected to the LSP. Start on the wallet home screen. No funds move: - the journey never taps Continue. - - Why 250 000: the number pad rejects any sats input above its max - (AmountInputViewModel.handleNumberPadInput, the guard comparing the new amount to maxAmount), - and that max is `maxAllowedToSend` — the Savings balance less the quoted LSP order fee, also - capped by the LSP's own max client balance (TransferViewModel, SpendingAmountScreen's - `setMaxAmount`). It is the value shown by "SpendingAmountUnit". Entering 10 fiat units and - switching back to sats gives about 10 000 sats at a BTC price near 100 000 fiat units, and the - append in the step below makes that about 100 001, so a smaller balance has the digit dropped - and the max toast shown instead, which false-fails the step. The guard in the fiat-entry step - below keeps that true if the price moves far enough for 10 fiat units to be worth more than a - tenth of the max. - - iOS counterpart: the same fix shipped in synonymdev/bitkit-ios#289, but the journey is not - ported to the iOS corpus yet. - - - Tap the Savings balance card (testTag "ActivitySavings") on the home screen - Tap "Transfer To Spending" (testTag "TransferToSpending") - If the spending intro screen appears, tap "Get Started" (testTag "SpendingIntro-button") - Verify the spending amount screen (testTag "SpendingAmount") is visible - Wait until the available amount (testTag "SpendingAmountUnit") finishes loading and shows a positive value - Tap the 25% button (testTag "SpendingAmountQuarter") - Verify the amount in the input field (testTag "SpendingAmountNumberField") shows a grouped value with a space, e.g. "78 777" - Tap the delete key (testTag "NRemove") repeatedly until the amount is 0, reading the amount after each press - Verify every delete press changed the amount, e.g. "78 777" → "7 877" → "787" → "78" → "7" → "0", with no press leaving the amount unchanged - Tap the number pad unit toggle (testTag "SpendingNumberPadUnit") so it shows the fiat currency - Tap "1" (testTag "N1") and "0" (testTag "N0") to enter 10 fiat units; if 10 fiat units are worth more than a tenth of the amount shown in "SpendingAmountUnit", tap only "1" (testTag "N1") instead, so the digit appended below still fits under that amount - Tap the number pad unit toggle (testTag "SpendingNumberPadUnit") so it shows "BITCOIN" - Verify the amount in the input field is a grouped sats value of at least 4 digits - Tap "1" (testTag "N1") - Verify the amount gained exactly one trailing digit "1", is not over the amount shown in "SpendingAmountUnit", and that no "Spending Balance Maximum" warning toast appeared - Tap the delete key (testTag "NRemove") repeatedly until the amount is 0, reading the amount after each press - Verify every delete press changed the amount - Tap the back button (testTag "NavigationBack") to leave without transferring - - diff --git a/journeys/backup-restore/README.md b/journeys/backup-restore/README.md index 9f4460b390..6258e1e8d4 100644 --- a/journeys/backup-restore/README.md +++ b/journeys/backup-restore/README.md @@ -26,8 +26,10 @@ recovery phrase, and what the app is allowed to upload while that restore is sti 3. **Fund through the staging LSP**, as the top-level README describes: `./lsp POST /regtest/chain/deposit '{"address":"","amountSat":500000}'` then `./lsp POST /regtest/chain/mine '{"count":3}'`. Give the wallet ~20s to sync. -4. **The channel needs the node connected to the LSP.** On the spending amount screen the max starts - at `0` behind a spinner — wait for it to populate before entering an amount. +4. **The channel is opened by hand.** This build cannot buy a channel from Blocktank + (synonymdev/bitkit-android#1669), so the journey opens a manual channel to the staging LSP node from + Lightning Connections. On the external amount screen the max starts at `0` behind a spinner — wait for + it to populate before entering an amount. ## Gotchas @@ -52,7 +54,6 @@ recovery phrase, and what the app is allowed to upload while that restore is sti - Restore: `RestoreWallet`, `Word-`, `RestoreButton`, `GetStartedButton`. - Receive: `Receive`, `ShowDetails`, `ReceiveCopyQR`. - Activity: `ActivityShort-`, `ActivityTag`, `ActivityTags`, `AddTagInput`, `AddTagSave`. -- Transfer: `ActivitySavings`, `TransferToSpending`, `TransferIntro-button`, `FundTransfer`, - `SpendingIntro-button`, `SpendingAmount`, `SpendingAmountContinue`, `GRAB`, - `TransferSuccess-button`. +- Manual channel: `add_connection_button`, `FundManual`, `NodeIdInput`, `HostInput`, `PortInput`, + `ExternalContinue`, `ExternalAmount`, `ExternalAmountContinue`, `GRAB`, `ExternalSuccess`. - Lightning connections: `Channel`, `CloseConnection`, `CloseConnectionButton`, `ChannelsClosed`. diff --git a/journeys/backup-restore/restore-keeps-tags-and-closed-channels.xml b/journeys/backup-restore/restore-keeps-tags-and-closed-channels.xml index 518cc48928..83c9a20693 100644 --- a/journeys/backup-restore/restore-keeps-tags-and-closed-channels.xml +++ b/journeys/backup-restore/restore-keeps-tags-and-closed-channels.xml @@ -11,7 +11,9 @@ because the second restore is where the ungated upload used to win the race. Precondition: a dev (regtest) build, onboarded with a NEW wallet, and the staging LSP reachable - through the `lsp` helper at the repo root for funding and mining (see ../README.md). The journey + through the `lsp` helper at the repo root for funding and mining (see ../README.md). The channel + is opened by hand to the staging LSP node, because this build cannot buy one from Blocktank + (synonymdev/bitkit-android#1669); see ../README.md, Getting a Lightning channel. The journey wipes the wallet half way through, so do not run it against a wallet whose funds matter. @@ -24,12 +26,11 @@ Tap the received activity row (testTag "ActivityShort-0") and verify its detail screen opens Tap "Tag" (testTag "ActivityTag"), enter the tag restoretag in the tag input (testTag "AddTagInput") and save it (testTag "AddTagSave") Verify the tag restoretag is listed on the activity detail screen (testTag "ActivityTags"), then go back to Home - Tap the SAVINGS balance tile (testTag "ActivitySavings"), then "Transfer To Spending" (testTag "TransferToSpending") - Pass any first-run intros on the way: transfer intro (testTag "TransferIntro-button"), the transfer funding option (testTag "FundTransfer") and the spending intro (testTag "SpendingIntro-button") - On the spending amount screen (testTag "SpendingAmount") enter 100 000 on the number pad ("N1" then "N0" five times) and tap Continue (testTag "SpendingAmountContinue") - Wait for the Blocktank order to be quoted, then confirm the transfer by swiping the confirm handle (testTag "GRAB") - Wait for the channel to open and leave the flow through its final button (testTag "TransferSuccess-button") Open Settings, then the "Advanced" tab (testTag "Tab-advanced"), then "Lightning Connections" (testTag "Channels") + Tap "Get Started" (testTag "add_connection_button") to open the Funding Advanced screen, then tap "Manual Setup" (testTag "FundManual") + Enter the staging LSP node id (testTag "NodeIdInput"), host (testTag "HostInput") and port (testTag "PortInput") from `./lsp GET /info`, and tap Continue (testTag "ExternalContinue") + On the amount screen (testTag "ExternalAmount") enter 100 000 on the number pad ("N1" then "N0" five times) and tap Continue (testTag "ExternalAmountContinue"), then confirm by swiping the confirm handle (testTag "GRAB") + Leave the success screen (testTag "ExternalSuccess") through its button, run `./lsp POST /regtest/chain/mine '{"count":3}'`, and wait for the connection to show as open on Lightning Connections (testTag "Channels") Tap the open connection (testTag "Channel"), tap "Close Connection" (testTag "CloseConnection") and confirm (testTag "CloseConnectionButton") Verify the connection has left the open list, and that it is listed under "Show Closed & Failed" (testTag "ChannelsClosed") Open Settings, then the "Security" tab, then "Data Backups" (testTag "BackupSettings"), and wait on the list (testTag "BackupScrollView") until the "Transaction Log" row reports a recent backup — that row carries no testTag, so read its text diff --git a/journeys/channels-closing/README.md b/journeys/channels-closing/README.md new file mode 100644 index 0000000000..6429c2e763 --- /dev/null +++ b/journeys/channels-closing/README.md @@ -0,0 +1,41 @@ +# Channels closing journeys + +Synonym is winding down Blocktank, its Lightning service provider. synonymdev/bitkit-android#1669 removed +every path that buys a new Lightning channel from it and added a notice for users who still have one. +The iOS twin is synonymdev/bitkit-ios#1097. + +## What the notice does + +- **Home banner** `ChannelsClosingBanner`, "CHANNELS CLOSE ON DEC 15, 2026", built from the same + component as the "TRANSFER IN PROGRESS" banner. It shows for a wallet with a Lightning channel, open + or pending, to a Blocktank (trusted LSP) node, whether or not the wallet has activity; channels to other + nodes (LNURL channel, manual setup to a third-party node) do not show it. Tapping it opens the Spending + wallet. +- **Spending wallet notice** `ChannelsClosingNotice` under the balance header, with the same date and the + same Blocktank-channel condition. +- The date is a placeholder constant (Dec 15, 2026) until the shutdown date is final. Journeys assert the + banner and the notice agree and read the date from the screen instead of hardcoding it. +- **Removed:** Savings "Transfer To Spending" (`TransferToSpending`), Spending "Transfer from Savings" + (`TransferFromSavings`), hardware wallet "Transfer To Spending" (`HardwareTransferToSpending`), the + Home "Spend" suggestion card (`Suggestion-lightning`), the Receive sheet's CJIT button and gift codes. + Transfer To Savings, send, receive over an existing channel, LNURL channel and manual external node + stay. + +## Mandatory setup + +1. **A wallet with an open Lightning channel.** See [Getting a Lightning channel](../README.md#getting-a-lightning-channel): + install a build from before synonymdev/bitkit-android#1669, open a channel with Transfer To Spending, + then install this build over it; or open a manual channel to the staging LSP node from Settings › + Advanced › Lightning Connections › add connection › Manual Setup. + +## Journeys + +| Journey | Covers | +| --- | --- | +| `channels-closing-notice.xml` | Banner on Home → tap → Spending wallet notice and Transfer To Savings; Savings wallet has no Transfer To Spending | +| [`../receive/receive-onchain-only-without-channel.xml`](../receive/receive-onchain-only-without-channel.xml) | The opposite precondition: a funded wallet with no channels shows no banner, no Spend card and a Savings-only Receive sheet | + +## Test tags + +`ChannelsClosingBanner`, `ChannelsClosingNotice`, `ActivitySavings`, `ActivitySpending`, `TotalBalance`, +`TransferToSavings`, `TransferToSpending` (asserted absent). diff --git a/journeys/channels-closing/channels-closing-notice.xml b/journeys/channels-closing/channels-closing-notice.xml new file mode 100644 index 0000000000..1dab6a07ac --- /dev/null +++ b/journeys/channels-closing/channels-closing-notice.xml @@ -0,0 +1,31 @@ + + + Verifies the notice that Synonym is winding down Blocktank (its Lightning service provider) for a + user with a Lightning channel (synonymdev/bitkit-android#1669). Home shows a banner, + "CHANNELS CLOSE ON DEC 15, 2026" (testTag "ChannelsClosingBanner"), tapping it opens the Spending + wallet, which shows "Your Lightning channels will be closed on Dec 15, 2026. Move your spending + balance to savings before then." (testTag "ChannelsClosingNotice") under the balance header, and + Transfer To Savings (testTag "TransferToSavings") is still offered. The Savings wallet has no + Transfer To Spending button (testTag "TransferToSpending"), since new channels can no longer be + bought. The date is a placeholder constant until the shutdown date is final; assert whatever + date the build shows, and keep the banner and the notice in agreement. + + Precondition: onboarded dev wallet with a Lightning channel to the staging LSP (Blocktank) node, open + or pending, and a POSITIVE Spending balance; channels to other nodes do not show the notice. This build cannot buy a channel from Blocktank, so get + one by installing a build from before the change (master before synonymdev/bitkit-android#1669), + opening a channel with Transfer To Spending and installing this build over it, or by opening a + manual channel to the staging LSP node (see ../README.md, Getting a Lightning channel). Start on + the wallet home screen. + + + Verify the Spending balance card (testTag "ActivitySpending") shows a positive amount + Verify the banner (testTag "ChannelsClosingBanner") is visible on the home screen with the text "CHANNELS CLOSE ON DEC 15, 2026" and the same style as the "TRANSFER IN PROGRESS" banner; note the date it shows + Tap the banner (testTag "ChannelsClosingBanner") + Verify the Spending wallet screen is open, showing the balance (testTag "TotalBalance") + Verify the notice (testTag "ChannelsClosingNotice") reads "Your Lightning channels will be closed on Dec 15, 2026. Move your spending balance to savings before then." with the same date the banner showed + Verify the "Transfer To Savings" button (testTag "TransferToSavings") is visible + Press the system back button, then tap the Savings balance card (testTag "ActivitySavings") + Verify the Savings wallet screen is open and no "Transfer To Spending" button (testTag "TransferToSpending") is shown + Press the system back button, tap the Spending balance card (testTag "ActivitySpending") and verify the notice (testTag "ChannelsClosingNotice") is still shown + + diff --git a/journeys/cjit-notifications/README.md b/journeys/cjit-notifications/README.md index fb3be3e23f..8e641b0cc7 100644 --- a/journeys/cjit-notifications/README.md +++ b/journeys/cjit-notifications/README.md @@ -1,55 +1,38 @@ -# CJIT channel-ready notification journeys +# Channel-ready notification journeys -These journeys reproduce the three notification issues raised in PR #787 (`fix/channelready-cjit`) -and verify the fixes. They exercise the `ChannelReady` event path across the two notification -producers: +One journey remains from the PR #787 (`fix/channelready-cjit`) notification work: +`non-cjit-channel-no-payment-notification.xml`. Opening a regular (non-CJIT) Lightning channel must +not be reported as a received payment, whether it is a "Payment Received" notification or a stack of +duplicates. When the app is in the foreground the only feedback is the in-app "Spending balance ready" +toast. -- **`WakeNodeWorker`** — wakes a killed/background node from an FCM push, opens the channel, and is - the *only* producer that should show a user-facing notification when nothing in-process is running. -- **`LightningNodeService`** (foreground service) + **`AppViewModel`** — handle the same - `ChannelReady` event when the app/service is alive, via `NotifyChannelReadyHandler`. +The folder keeps its name so the file path stays stable for the iOS twin. -## The bugs being reproduced +## Retired journeys -1. **🟢 Missing thousands separators.** `WakeNodeWorker` built the amount as `"₿ $sats"` (raw), - so a CJIT receive showed `₿ 48064` instead of `₿ 48 064`. -2. **🟠 Double notification.** When the foreground service was running, a CJIT push produced - *two* notifications — one from `LightningNodeService` (`Received ₿ 48 064 ($30.79)`) and one - from `WakeNodeWorker` (`₿ 48064 / Via new channel`). -3. **🔴 Non-CJIT channel shows a payment notification (and multiplies).** Opening a regular - (non-CJIT) channel produced one-or-more `₿ 45000 / Via new channel` notifications even though no - payment was received. +`cjit-foreground-service-notification.xml` and `cjit-push-single-notification.xml` were retired because +Blocktank channel purchases, CJIT included, were removed (synonymdev/bitkit-android#1669). They paid a +CJIT order through `./lsp POST /cjit` and asserted the single, space-grouped "Received ₿ …" notification +from the foreground service and from an FCM push. Nothing in the app creates a CJIT order now, so there +is no way to drive the push. -## The fix (what the journeys assert) +## What the remaining journey asserts -- All received-payment notifications (foreground service, in-app, and the `WakeNodeWorker` push - path, for both regular payments and CJIT receives) are built by one shared - `ReceivedNotificationContent`, so they are identical: title "Payment Received", body - "Received ₿ <amount> (<fiat>)" — space-grouped BTC plus fiat, ordered by the primary - display setting (or hidden when notification details are off). The legacy bare "₿ <sats>" / - "Via new channel" push format is gone. [#1] -- `WakeNodeWorker` only emits a CJIT notification when the channel actually has a Blocktank CJIT - entry and the receive was not already recorded; a regular channel opening shows no payment - notification. [#3] -- `WakeNodeWorker` skips its own user-facing notification when an in-process handler covers the - event — i.e. when the app is in the foreground OR `LightningNodeService.isRunning` — leaving a - single notification. It still wakes the node and records the activity. [#2] +- `WakeNodeWorker` only emits a notification when the channel actually has a Blocktank CJIT entry and the + receive was not already recorded; a regular channel opening shows no payment notification. +- Received-payment notifications are built by one shared `ReceivedNotificationContent`: title "Payment + Received", body "Received ₿ ()". ## Mandatory setup -1. **Onboarded dev (regtest) wallet** with the node connected to the staging/regtest LSP. -2. **Create a CJIT entry and fund it** with the `blocktank-api` (`lsp`) skill so a real JIT channel - opens on payment: - - `./lsp POST /cjit '{"channelSizeSat":100000,"invoiceSat":50000,"invoiceDescription":"cjit","nodeId":"","channelExpiryWeeks":1}'` - - Pay the returned bolt11 invoice: `./lsp POST /regtest/lightning/pay '{"invoice":""}'` - - `./lsp POST /regtest/mine '{"count":3}'` if confirmation is needed. -3. **Background notifications** toggle (foreground service) is in Settings → Notifications; enabling - it keeps `LightningNodeService` alive in the background (`LightningNodeService.isRunning == true`). +1. **Onboarded dev (regtest) wallet** with the node connected to the staging/regtest LSP and a positive + on-chain Savings balance. +2. **Open a channel without CJIT.** This build has no Transfer To Spending, so open a manual channel to + the staging LSP node: Settings › Advanced › Lightning Connections › add connection › Manual Setup. See + [Getting a Lightning channel](../README.md#getting-a-lightning-channel). ## Inspecting notifications (adb) -Use the notification shade dump to assert what was posted: - ```sh adb shell dumpsys notification --noredact | grep -A3 -i "to.bitkit.dev" ``` @@ -62,5 +45,3 @@ notifications Bitkit posted for a single channel opening. - Foreground "Spending balance ready" toast: testTag `SpendingBalanceReadyToast`. - Notification strings: `notification__received__title` ("Payment Received") and `notification__received__body_amount` ("Received %s"). -- Amount grouping separator: `SATS_GROUPING_SEPARATOR` (a space) via `formatToModernDisplay`, - applied inside `ReceivedNotificationContent`. diff --git a/journeys/cjit-notifications/cjit-foreground-service-notification.xml b/journeys/cjit-notifications/cjit-foreground-service-notification.xml deleted file mode 100644 index 3d04d920af..0000000000 --- a/journeys/cjit-notifications/cjit-foreground-service-notification.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - Reproduces PR #787 issues #1 (missing thousands separators) and #2 (double notification). - - With background notifications (the LightningNodeService foreground service) enabled and the app - in the background, a CJIT payment that opens a JIT channel must produce EXACTLY ONE "Payment - Received" notification, with a space-grouped amount (e.g. "Received ₿ 48 064 (...)"), NOT a raw - "₿ 48064" and NOT a second "Via new channel" notification from WakeNodeWorker. - - Precondition: onboarded regtest wallet connected to the LSP. A funded CJIT entry is ready to pay - (see README.md). Background notifications enabled in Settings → Notifications. No Lightning - channel open yet (this is the first, channel-opening payment). - - - Confirm background notifications are enabled (Settings → Notifications → background/foreground service toggle ON), then go back to the wallet home screen - Send the device to the background (press Home) so no Bitkit activity is in the foreground but the foreground service keeps running - Pay the CJIT invoice via the lsp skill: ./lsp POST /regtest/lightning/pay '{"invoice":"<bolt11>"}' - Wait ~5s for the ChannelReady event and notification delivery - Dump notifications: adb shell dumpsys notification --noredact | grep -A3 -i "to.bitkit" - Verify EXACTLY ONE Bitkit payment notification is posted for this channel opening (not two) - Verify its title is "Payment Received" and body is "Received ₿ <amount> (<fiat>)" with the amount space-grouped (e.g. "Received ₿ 48 064 ($30.79)") — NOT a bare "₿ 48064" - Verify there is NO separate "Via new channel" notification from WakeNodeWorker alongside it (the push path is deduped while the foreground service is running) - Tap the notification, bring the app to the foreground, and verify the received-transaction sheet shows the correct amount - - diff --git a/journeys/cjit-notifications/cjit-push-single-notification.xml b/journeys/cjit-notifications/cjit-push-single-notification.xml deleted file mode 100644 index 6039e99397..0000000000 --- a/journeys/cjit-notifications/cjit-push-single-notification.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - Reproduces PR #787 issue #2 on the pure push path. With the app fully killed (no foreground - activity and no foreground service), a CJIT payment must still surface a single payment - notification delivered by WakeNodeWorker — this is the wake-the-node-and-open-the-channel path, - so it must keep working. The notification must use the same rich content as the in-app one: - title "Payment Received", body "Received ₿ <amount> (<fiat>)" — NOT the old bare - "₿ <sats>" / "Via new channel". - - Precondition: onboarded regtest wallet connected to the LSP. A funded CJIT entry is ready to pay - (see README.md). Background notifications DISABLED (so no foreground service survives the kill), - and the app force-stopped so only the FCM push + WakeNodeWorker run. - - - Ensure background notifications are DISABLED in Settings → Notifications - Force-stop the app: adb shell am force-stop to.bitkit.dev - Pay the CJIT invoice via the lsp skill: ./lsp POST /regtest/lightning/pay '{"invoice":"<bolt11>"}' - Wait up to ~30s for the FCM push to wake the node and deliver the notification - Dump notifications: adb shell dumpsys notification --noredact | grep -A3 -i "to.bitkit" - Verify EXACTLY ONE Bitkit notification is posted for this CJIT payment - Verify the title is "Payment Received" and the body is "Received ₿ <amount> (<fiat>)" with the amount space-grouped (e.g. "Received ₿ 48 064 ($30.79)") — NOT a bare "₿ 48064" and NOT "Via new channel" - Tap the notification to open the app and verify the received amount in the activity list matches - - diff --git a/journeys/cjit-notifications/non-cjit-channel-no-payment-notification.xml b/journeys/cjit-notifications/non-cjit-channel-no-payment-notification.xml index 9e6777d95f..1bc02826ef 100644 --- a/journeys/cjit-notifications/non-cjit-channel-no-payment-notification.xml +++ b/journeys/cjit-notifications/non-cjit-channel-no-payment-notification.xml @@ -5,13 +5,16 @@ When the app is in the foreground the only feedback is the in-app "Spending balance ready" toast. Precondition: onboarded regtest wallet connected to the LSP, with a positive on-chain balance to - fund the transfer. No CJIT entry is involved here — this is a manual/Transfer channel opening - (orderPaymentConfirmed), not a CJIT receive. + fund the channel. No CJIT entry is involved here — this is a manual channel opening, not a CJIT + receive. This build cannot buy a channel from Blocktank (synonymdev/bitkit-android#1669), so the + channel is opened to the staging LSP node by hand (see ../README.md, Getting a Lightning channel). - Keep the app in the foreground on the wallet home screen - Open a regular spending channel via Transfer → Spending (fund some sats and confirm), OR open one through the lsp skill order flow - Mine confirmations if required: ./lsp POST /regtest/mine '{"count":3}' + Start on the wallet home screen + Open Settings, then the "Advanced" tab (testTag "Tab-advanced"), then "Lightning Connections" (testTag "Channels"), tap "Get Started" or "Add Connection" (testTag "add_connection_button") and tap "Manual Setup" (testTag "FundManual") + Enter the staging LSP node id (testTag "NodeIdInput"), host (testTag "HostInput") and port (testTag "PortInput") from ./lsp GET /info, tap Continue (testTag "ExternalContinue"), enter an amount within the available maximum, then continue and confirm to open the channel + Mine confirmations: ./lsp POST /regtest/chain/mine '{"count":3}' + Return to the wallet home screen and keep the app in the foreground Wait for the ChannelReady event (~5-10s) Verify an in-app "Spending balance ready" toast appears (testTag "SpendingBalanceReadyToast") Dump notifications: adb shell dumpsys notification --noredact | grep -A3 -i "to.bitkit" diff --git a/journeys/hardware-wallet/README.md b/journeys/hardware-wallet/README.md index 2d4cfa3d8f..87ff4f46d9 100644 --- a/journeys/hardware-wallet/README.md +++ b/journeys/hardware-wallet/README.md @@ -64,7 +64,7 @@ each end by re-pairing after a forget, and `detail-overview.xml` runs last becau Remove step forgets the device. Run the send and receive journeys while the paired native-segwit account is funded. The `passphrase-*` journeys run as a block after `connect-home-tile.xml`, in the order listed: `passphrase-pairing.xml` pairs the hidden wallet -the other three rely on, and `passphrase-settings-remove.xml` removes it again. +the other two rely on, and `passphrase-settings-remove.xml` removes it again. | Journey | Covers | | - | - | @@ -75,16 +75,12 @@ the other three rely on, and `passphrase-settings-remove.xml` removes it again. | `suggestion-intro-sheet.xml` | Forget device, Hardware suggestion card, full connect flow (Intro → Searching → Found → Paired → Finish) re-pairs | | `connect-flow.xml` | Settings Add button → connect flow with an edited Label Funds → paired device count + name | | `settings-hardware-wallets.xml` | Payments count row, Hardware Wallets screen list, rename sheet, Add button sheet/back dismiss, per-row delete confirm + re-pair | -| `detail-overview.xml` | Detail screen overview, Transfer placeholder when funded, activity, Remove confirm + forget | -| `transfer-to-spending.xml` | Happy-path transfer plus one scoped hardware Transfer activity | -| `transfer-to-spending-max-lsp-cap.xml` | MAX when Trezor balance is higher than remaining LSP headroom; verifies MAX uses AVAILABLE and reaches sign without insufficient funds | -| `transfer-to-spending-node-warmup.xml` | Transfer started during app/node warm-up; verifies loading recovers into the sign screen | +| `detail-overview.xml` | Detail screen overview, no Transfer To Spending button, activity, Remove confirm + forget | | `send-onchain.xml` | Normal Send flow funded by Trezor: source selection, guarded preparation, device signing, broadcast, success, and activity | | `receive-onchain.xml` | Trezor Receive tab: current address and QR display plus exact on-device address verification | | `passphrase-pairing.xml` | Passphrase button on Paired → Enter Passphrase → Passphrase Funds Found; second home tile, own label, no passphrase in logs | | `passphrase-duplicate.xml` | Re-entering a watched passphrase reports "already added" and adds no tile | | `passphrase-settings-remove.xml` | Per-identity settings row, rename and delete; removing the hidden wallet keeps the device paired | -| `passphrase-transfer-to-spending.xml` | Signs with the live session, re-prompts after the session is dropped, refuses a wrong passphrase | Connect-flow testTags: `HardwareWalletSheet`, `HardwareWalletIntroScreen`, `HardwareWalletIntroCancel`, `HardwareWalletIntroContinue`, @@ -100,9 +96,7 @@ and `RenameHardwareWalletSave`. Passphrase testTags: `HardwareWalletPairedPassphrase`, `HardwareWalletPassphraseScreen`, `HardwareWalletPassphraseInput`, `HardwareWalletPassphraseBack`, -`HardwareWalletPassphraseContinue`, `HardwareWalletPassphrasePairedScreen`, and on the transfer -sign screen `HwTransferPassphraseSheet`, `HwTransferPassphraseInput`, -`HwTransferPassphraseCancel`, `HwTransferPassphraseContinue`. +`HardwareWalletPassphraseContinue`, `HardwareWalletPassphrasePairedScreen`. Send and receive testTags: `Send`, `RecipientManual`, `RecipientInput`, `AddressContinue`, `send_amount_screen`, `AssetButton-switch`, `ContinueAmount`, `SendConfirmAssetButton`, @@ -120,9 +114,8 @@ path is better validated on a physical device because the Bridge path can still without BLE. A physical device holds one hidden wallet open at a time, and Bitkit never stores the -passphrase, so the `passphrase-*` journeys assert both halves of that: the tile, label, -settings row, activity scope and removal are per identity, while signing reuses the live -session and asks again once it is gone. They also grep the app log and datastore to prove the +passphrase, so the `passphrase-*` journeys assert that: the tile, label, settings row, +activity scope and removal are per identity. They also grep the app log and datastore to prove the passphrase is never written anywhere — note `BlockScreenshots` is a no-op in debug builds, so the passphrase steps remain screenshottable while journeys run. @@ -134,12 +127,17 @@ with `./bitcoin-cli`. The activity journeys use a mixed fixture with two confirmed hardware receives carrying distinct amounts and one hardware Transfer To Spending transaction. This proves ordering, tab filtering, tags, inputs/outputs, transfer metadata, and row uniqueness together. +The Transfer row cannot be created on this build; take it from a build from before +synonymdev/bitkit-android#1669. -For transfer-to-spending QA, explicitly cover the LSP cap boundary: the hardware wallet -balance can be much larger than the displayed AVAILABLE amount because MAX is capped by -Blocktank channel headroom. After signing, decode the funding transaction and compare the -activity DB row: the on-chain activity fee should be the composed mining fee, while the -funding output should equal the final Blocktank `order.feeSat`. +## Retired journeys + +`transfer-to-spending.xml`, `transfer-to-spending-max-lsp-cap.xml`, +`transfer-to-spending-node-warmup.xml` and `passphrase-transfer-to-spending.xml` were retired because +Blocktank channel purchases were removed (synonymdev/bitkit-android#1669): the hardware wallet detail +screen no longer has a Transfer To Spending button (`HardwareTransferToSpending`). The +`HardwareTransfer*` and `HwTransferPassphrase*` screens and tags are still in the nav graph but nothing +in the UI reaches them. ## Blockstream Jade diff --git a/journeys/hardware-wallet/activity-blue-icons.xml b/journeys/hardware-wallet/activity-blue-icons.xml index 67a80ee73e..3a1099dcfe 100644 --- a/journeys/hardware-wallet/activity-blue-icons.xml +++ b/journeys/hardware-wallet/activity-blue-icons.xml @@ -2,7 +2,10 @@ Verifies hardware wallet on-chain activity in the unified home and All Activity lists, with wallet-scoped blue icons and tab filters. Requires a paired Bridge emulator with at - least two confirmed receives with distinct amounts and one Transfer To Spending activity. + least two confirmed receives with distinct amounts and one Transfer To Spending activity. This build + cannot create that activity (the button was removed in synonymdev/bitkit-android#1669), so the + fixture comes from a build from before that change (master before it) that signed a hardware + Transfer To Spending; the activity stays in the wallet after installing this build over it. diff --git a/journeys/hardware-wallet/detail-overview.xml b/journeys/hardware-wallet/detail-overview.xml index 8d13bda3c6..e4425d8428 100644 --- a/journeys/hardware-wallet/detail-overview.xml +++ b/journeys/hardware-wallet/detail-overview.xml @@ -1,8 +1,8 @@ Opens the hardware wallet detail screen from the home tile and verifies its overview: - the top bar, balance header, the Transfer To Spending entry when funds are present, - the grouped activity list, and the Remove device confirm dialog. The final + the top bar, balance header, the absence of a Transfer To Spending entry (removed with + Blocktank channel purchases, synonymdev/bitkit-android#1669), the grouped activity list, and the Remove device confirm dialog. The final Remove step forgets the device, so run this last (re-run connect-home-tile.xml to pair again). Requires a paired Bridge emulator (run connect-home-tile.xml first). @@ -17,7 +17,7 @@ Verify the hardware wallet detail screen opens (testTag "HardwareWalletScreen"), showing the device name with a blue bitcoin icon in the top bar and a balance header - If a "Transfer To Spending" button is shown (testTag "HardwareTransferToSpending"), tap it, verify the transfer amount screen opens (testTag "HardwareTransferAmount") titled "TRANSFER TO SPENDING", then navigate back to the hardware wallet detail screen; otherwise skip this step + Verify no "Transfer To Spending" button (testTag "HardwareTransferToSpending") is shown, whether or not the wallet holds funds If the activity list shows transactions, verify their circular icons are blue, then tap the first one, verify an activity detail screen opens, and navigate back diff --git a/journeys/hardware-wallet/passphrase-transfer-to-spending.xml b/journeys/hardware-wallet/passphrase-transfer-to-spending.xml deleted file mode 100644 index b3333bdc0a..0000000000 --- a/journeys/hardware-wallet/passphrase-transfer-to-spending.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - Drives Transfer To Spending from a passphrase (hidden) wallet. While the Trezor session that - holds the passphrase is still open the transfer signs straight away; after the session is - dropped Bitkit asks for the passphrase again, and a wrong one is refused instead of signing - from whichever wallet the device happens to have open. Requires the emulator started with - passphrase protection enabled, the hidden wallet from passphrase-pairing.xml paired, and its - native-segwit account holding spendable regtest funds. - - - - Launch the Bitkit app and go to the wallet home screen - - - Tap the hidden wallet tile ("Hidden Trezor") and verify its detail screen opens (testTag "HardwareWalletScreen") - - - Tap "Transfer To Spending" (testTag "HardwareTransferToSpending"), and if the first-run intro is shown tap "Get Started" - - - Tap the "25%" quick button (testTag "HardwareTransferAmountQuarter"), then "Continue" (testTag "HardwareTransferAmountContinue") and wait for the Blocktank order - - - Verify the sign screen opens (testTag "HardwareTransferSign") titled "SIGN WITH YOUR DEVICE" - - - Tap "Open Trezor Connect" (testTag "HardwareTransferOpenTrezorConnect") and verify no passphrase sheet appears, because the session opened at pairing still holds the hidden wallet - - - Approve the Recipient, Amount, Locktime, and Summary prompts on the Bridge emulator in order, and verify the transaction signed screen appears (testTag "HardwareTransferSigned") - - - Wait for the Processing Payment screen, tap "Continue Using Bitkit", and verify the app returns to the wallet home screen - - - adb: adb shell am force-stop to.bitkit.dev - - - adb: adb shell monkey -p to.bitkit.dev -c android.intent.category.LAUNCHER 1 - - - Once the app is back on the home screen, open the hidden wallet tile and start Transfer To Spending again, setting an amount with the "25%" quick button and continuing to the sign screen - - - Tap "Open Trezor Connect" (testTag "HardwareTransferOpenTrezorConnect") and verify the passphrase sheet opens (testTag "HwTransferPassphraseSheet"), because the session that held the passphrase is gone - - - Type a wrong passphrase "not-the-one" into the input (testTag "HwTransferPassphraseInput") and tap "Continue" (testTag "HwTransferPassphraseContinue") - - - Confirm on the emulated device for every account the passphrase session reads: the device - prompts once per address type and the call blocks until it is acknowledged. Either tap the - emulator UI or run: ../bitkit-docker/scripts/trezor-emulator send-json '{"type":"emulator-press-yes","id":1}' - - - Verify an error toast says the passphrase opens a different wallet, that no signing prompt was shown on the emulator, and that no new transfer appears in the activity list - - - Navigate to the wallet home screen and verify the hardware tile count is unchanged: the wallet the wrong passphrase opened must not be added - - - Reopen the sign screen, tap "Open Trezor Connect", type the correct passphrase "bitkit-hidden" and tap "Continue" - - - Approve the Recipient, Amount, Locktime, and Summary prompts on the Bridge emulator, and verify the transaction signed screen appears (testTag "HardwareTransferSigned") - - - adb: adb shell "run-as to.bitkit.dev sh -c 'grep -rl -e bitkit-hidden -e not-the-one files/logs/ files/datastore/ || echo NO_PASSPHRASE_LEAK'" - - - Verify the previous command printed NO_PASSPHRASE_LEAK: neither the correct nor the rejected passphrase may be written to the logs or the datastore - - - diff --git a/journeys/hardware-wallet/transfer-to-spending-max-lsp-cap.xml b/journeys/hardware-wallet/transfer-to-spending-max-lsp-cap.xml deleted file mode 100644 index a36e8485d6..0000000000 --- a/journeys/hardware-wallet/transfer-to-spending-max-lsp-cap.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - Verifies that a funded hardware wallet whose balance is larger than the current - Blocktank/LSP headroom is capped by the transferable spending limit, not by the - device balance. Requires a paired Bridge emulator or physical Trezor with a hardware - balance larger than the available transfer limit, and at least one existing channel or - pending order consuming most of the regtest LSP cap. - - - - Launch the Bitkit app and go to the wallet home screen - - - Verify the hardware wallet tile shows a balance larger than the AVAILABLE amount expected in the transfer flow - - - Tap the hardware wallet tile beneath the SAVINGS and SPENDING tiles, and verify the hardware wallet detail screen opens (testTag "HardwareWalletScreen") - - - Tap the "Transfer To Spending" button (testTag "HardwareTransferToSpending") - - - Verify the transfer amount screen opens (testTag "HardwareTransferAmount"), titled "TRANSFER TO SPENDING", and the AVAILABLE amount is lower than the hardware wallet balance because it is capped by LSP headroom - - - Tap the "MAX" quick button (testTag "HardwareTransferAmountMax") - - - Verify the amount field matches the AVAILABLE amount exactly and does not use the full hardware wallet balance - - - Tap "Continue" (testTag "HardwareTransferAmountContinue") and wait for the Blocktank order to be created - - - Verify the sign screen opens (testTag "HardwareTransferSign"), showing NETWORK FEES, SERVICE FEES, TO SPENDING and TOTAL, without an insufficient-funds toast - - - Tap "Open Trezor Connect" (testTag "HardwareTransferOpenTrezorConnect") and approve every hardware-wallet prompt - - - Verify the transaction signed screen appears (testTag "HardwareTransferSigned") and then advances to Processing Payment / setting-up progress - - - diff --git a/journeys/hardware-wallet/transfer-to-spending-node-warmup.xml b/journeys/hardware-wallet/transfer-to-spending-node-warmup.xml deleted file mode 100644 index 518131da38..0000000000 --- a/journeys/hardware-wallet/transfer-to-spending-node-warmup.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - Verifies that starting a hardware-wallet transfer while the Lightning node is still - warming up does not fail or strand the CTA in loading. The flow should show the normal - loading/progress UI, continue once the node reaches the needed state, and land on the - sign screen. Requires a paired and funded hardware wallet. - - - - adb: adb shell am force-stop to.bitkit.dev - - - adb: adb shell monkey -p to.bitkit.dev -c android.intent.category.LAUNCHER 1 - - - As soon as the wallet home screen is visible, tap the hardware wallet tile beneath the SAVINGS and SPENDING tiles - - - Tap the "Transfer To Spending" button (testTag "HardwareTransferToSpending") - - - Verify the transfer amount screen or loading/progress UI appears, and no reconnect, node-not-ready, or generic failure toast is shown while the node warms up - - - Tap the "25%" quick button (testTag "HardwareTransferAmountQuarter") if the amount screen is shown - - - Tap "Continue" (testTag "HardwareTransferAmountContinue") if the amount screen is shown, then wait for order creation and node warm-up to finish - - - Verify the sign screen opens (testTag "HardwareTransferSign"), titled "SIGN WITH YOUR DEVICE", and the Continue/Open Trezor Connect CTA is no longer loading - - - Navigate back without signing so this journey only covers node warm-up behavior - - - diff --git a/journeys/hardware-wallet/transfer-to-spending.xml b/journeys/hardware-wallet/transfer-to-spending.xml deleted file mode 100644 index a93a397e40..0000000000 --- a/journeys/hardware-wallet/transfer-to-spending.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - Drives the watch-only Transfer To Spending flow for a paired Trezor: Amount -> Sign With - Your Device -> Transaction Signed -> Processing Payment, then verifies the new transfer is - represented once in the hardware wallet's Core activity scope. Requires a paired Bridge - emulator whose native-segwit account holds spendable regtest funds and at least one older - hardware receive row with a distinct amount. - - - - Launch the Bitkit app and go to the wallet home screen - - - Verify the recent activity list contains an older blue hardware received row with a distinct amount - - - Tap the hardware wallet tile beneath the SAVINGS and SPENDING tiles, and verify the hardware wallet detail screen opens (testTag "HardwareWalletScreen") - - - Tap the "Transfer To Spending" button (testTag "HardwareTransferToSpending") - - - If the first-run Transfer To Spending intro is shown, tap "Get Started" - - - Verify the transfer amount screen opens (testTag "HardwareTransferAmount"), titled "TRANSFER TO SPENDING", showing an AVAILABLE row, the 25% and MAX quick buttons, and a number pad - - - Tap the "25%" quick button (testTag "HardwareTransferAmountQuarter") to set a valid amount below the available limit - - - Tap "Continue" (testTag "HardwareTransferAmountContinue") and wait for the fee estimate - - - Verify the sign screen opens (testTag "HardwareTransferSign"), titled "SIGN WITH YOUR DEVICE", showing the NETWORK FEES, SERVICE FEES, TO SPENDING and TOTAL cells, the Learn More and Advanced buttons, and the Trezor illustration - - - Tap "Open Trezor Connect" (testTag "HardwareTransferOpenTrezorConnect") to create the Blocktank order. If its fee is higher than the estimate, verify the fee cells refresh and tap "Open Trezor Connect" again; otherwise signing starts immediately - - - Approve the Recipient, Amount, Locktime, and Summary prompts on the Bridge emulator in order - - - Verify the transaction signed screen appears (testTag "HardwareTransferSigned"), titled "TRANSACTION SIGNED", showing the same fee cells and the checkmark illustration - - - Wait for the screen to auto-forward and verify the Processing Payment / setting-up progress screen appears - - - Tap "Continue Using Bitkit" and verify the app returns to the wallet home screen - - - Verify the recent activity list contains exactly one new blue hardware Transfer row with subtitle "From Savings", above the older blue received row - - - Tap the new blue Transfer row and verify its detail screen shows "TO SPENDING" - - - diff --git a/journeys/node-lifecycle/cancelled-node-restart.xml b/journeys/node-lifecycle/cancelled-node-restart.xml index ab91c0face..09f26caa73 100644 --- a/journeys/node-lifecycle/cancelled-node-restart.xml +++ b/journeys/node-lifecycle/cancelled-node-restart.xml @@ -15,7 +15,10 @@ rebuild the node with `restartWithRgsServer`, which takes the identical cancel-and-recover path. A cancelled change is never persisted, so the run leaves the emulator on the default URL. Precondition: onboarded dev debug build (`to.bitkit.dev`) with dev mode on, a usable Lightning - channel with inbound capacity, the configured Electrum server reachable, the default RGS + channel with inbound capacity (this build cannot buy one from Blocktank, so install a build from + before synonymdev/bitkit-android#1669, open a channel with Transfer To Spending, then install this + build over it; a manual channel has no inbound capacity, see ../README.md, Getting a Lightning + channel), the configured Electrum server reachable, the default RGS snapshot server reachable, and the regtest LSP reachable. Building the node takes several seconds on the emulator, which is the window the back press must land in. The lifecycle lines are only in the app log, not in `android layout`; read `files/logs/` through `run-as`, and list diff --git a/journeys/notification-permission/README.md b/journeys/notification-permission/README.md index 4b2faa4135..fd5cdb1e8d 100644 --- a/journeys/notification-permission/README.md +++ b/journeys/notification-permission/README.md @@ -1,62 +1,20 @@ # Notification-permission journeys -These journeys exercise the notification-permission request triggered by the -"Set up in background" toggle that backs Bitkit's background-payments setup. +This suite is retired. Every journey in it reached the "Set up in background" toggle through a +Blocktank channel purchase, and those were removed in synonymdev/bitkit-android#1669: -The behaviour was changed on `fix/limit-system-notification-permission`: tapping the -toggle now goes through the shared `rememberRequestNotificationPermission` helper -(`ui/utils/RequestNotificationPermissions.kt`) instead of jumping straight to the -system notification settings. +- `receive-cjit-confirm-notification-toggle.xml` and `toggle-off-opens-system-settings.xml` used the + Receive → CJIT confirm screen (`ReceiveConfirmScreen`), reached through "Receive Lightning funds". +- `receive-cjit-liquidity-notification-toggle.xml` used the Receive → CJIT liquidity screen + (`ReceiveLiquidityScreen`). +- `transfer-spending-confirm-notification-toggle.xml` used the Transfer → Spending confirm screen + (`SpendingConfirmScreen`), reached through "Transfer To Spending". -## What the fix does -- **Toggle OFF (permission already granted)**: re-requesting a granted permission is a - no-op, so tapping opens the **system notification settings** (`openNotificationSettings`) - where the user can actually turn notifications off — the behaviour these toggles had - before the refactor. -- **Toggle ON, Android 13+ (API 33, TIRAMISU)**: tapping launches the OS - `POST_NOTIFICATIONS` runtime permission dialog. Granting it flips the toggle to - checked; the result is persisted via `SettingsViewModel.setNotificationPreference`. -- **Toggle ON, pre-13 (API < 33)**: there is no runtime dialog, so it falls back to the - system notification settings. +No button in the UI navigates to those screens any more, so the toggle's permission behaviour +(the shared `rememberRequestNotificationPermission` helper: OFF with permission granted opens the system +notification settings, ON on API 33+ shows the `POST_NOTIFICATIONS` dialog, ON before API 33 opens the +system settings) is no longer reachable on a device through these routes. Settings › Background payments +(testTag `BackgroundPaymentSettings`) is a separate entry point that no journey covers yet. -The same helper backs four entry points; these journeys cover the three the user can -reach directly: -- **Transfer → Spending confirm** (`SpendingConfirmScreen`) -- **Receive → CJIT confirm** (`ReceiveConfirmScreen`) -- **Receive → CJIT liquidity** (`ReceiveLiquidityScreen`, via "Learn more") - -## Mandatory setup -1. **Use an API 33+ device** to verify the runtime-dialog path. On API < 33 the dialog - never appears — only the system-settings fallback is exercised. -2. **Start from a fresh notification-permission state.** The OS only shows the - `POST_NOTIFICATIONS` dialog while the permission is in the "ask" state. Once granted - or denied it will not show again, and the journey will silently pass for the wrong - reason. Reset before each run: - `adb shell pm revoke to.bitkit.dev android.permission.POST_NOTIFICATIONS` - (or reinstall / clear app data). -3. **Node must be connected to the LSP (Blocktank).** Both the Transfer→Spending and - Receive→CJIT confirm screens need a real order quoted by Blocktank before the toggle - screen renders. With the hosted staging backend this is `api.stag0.blocktank.to`. -4. **Transfer→Spending also needs a positive on-chain Savings balance** so a real max can - be quoted. Fund + mine via the `blocktank-api:lsp` skill, then wait for the balance to - sync. - -## Gotchas -- **The permission dialog is one-shot** — see setup #2. Always revoke/reset first. -- **Blocktank must be reachable.** If `api.stag0.blocktank.to:443` is down, CJIT/order - creation hangs on a spinner at "Continue" and the confirm screen never appears, so the - toggle is unreachable. Verify the host first: - `curl -s -m 8 -o /dev/null -w '%{http_code}\n' https://api.stag0.blocktank.to/blocktank/api/v2/info` - (`000` = down). This is infra, not the toggle. -- The system permission dialog is **OS UI**, not Compose — locate its buttons with - `android screen --annotate` (text "Allow" / "Don't allow"), not `android layout` tags. -- On grant, the toggle reflects `notificationsGranted`; it only flips to checked once the - `ON_RESUME` re-check or the launcher callback fires. - -## Test tags -- Transfer→Spending toggle switch: `SpendingConfirmNotificationSwitch` -- Receive→CJIT confirm toggle switch: `ReceiveConfirmNotificationSwitch` -- Receive→CJIT liquidity toggle switch: `ReceiveLiquidityNotificationSwitch` -- Spending amount screen: `SpendingAmount`, continue `SpendingAmountContinue`, - available/max `SpendingAmountAvailable` / `SpendingAmountMax`. -- The toggle label on all screens is "Set up in background". +The reset command for a fresh permission state, should a journey for another entry point be written: +`adb shell pm revoke to.bitkit.dev android.permission.POST_NOTIFICATIONS`. diff --git a/journeys/notification-permission/receive-cjit-confirm-notification-toggle.xml b/journeys/notification-permission/receive-cjit-confirm-notification-toggle.xml deleted file mode 100644 index 46144c1745..0000000000 --- a/journeys/notification-permission/receive-cjit-confirm-notification-toggle.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - Verifies that the "Set up in background" toggle on the Receive → CJIT confirm screen - (ReceiveConfirmScreen) launches the Android POST_NOTIFICATIONS runtime permission - dialog on API 33+, and that granting it checks the toggle. - - Precondition: API 33+ onboarded dev wallet with the node connected to the LSP so a - CJIT order can be quoted, and POST_NOTIFICATIONS in the "ask" state (revoke or - reinstall first — the dialog is one-shot). Start on the wallet home screen. - - - Tap the "Receive" button on the home screen - Tap the "Spending" tab in the Receive sheet - Tap "Receive Lightning funds" - On the amount screen, enter an amount above the CJIT minimum (e.g. 100 000 sats) using the number pad - Tap "Continue" and wait for the CJIT order to be created and the confirm screen ("To set up your spending balance...") to appear - Verify the "Set up in background" toggle (testTag "ReceiveConfirmNotificationSwitch") is visible and unchecked - Tap the "Set up in background" toggle - Verify the Android system notification permission dialog appears (text like "Allow Bitkit to send you notifications?") - Tap "Allow" - Verify the "Set up in background" toggle is now checked - - diff --git a/journeys/notification-permission/receive-cjit-liquidity-notification-toggle.xml b/journeys/notification-permission/receive-cjit-liquidity-notification-toggle.xml deleted file mode 100644 index 1486c08a08..0000000000 --- a/journeys/notification-permission/receive-cjit-liquidity-notification-toggle.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - Verifies that the "Set up in background" toggle on the Receive → CJIT liquidity screen - (ReceiveLiquidityScreen, reached via "Learn more" from the confirm screen) launches the - Android POST_NOTIFICATIONS runtime permission dialog on API 33+, and that granting it - checks the toggle. - - Precondition: API 33+ onboarded dev wallet with the node connected to the LSP so a CJIT - order can be quoted, and POST_NOTIFICATIONS in the "ask" state (revoke or reinstall - first — the dialog is one-shot). Start on the wallet home screen. - - - Tap the "Receive" button on the home screen - Tap the "Spending" tab in the Receive sheet - Tap "Receive Lightning funds" - On the amount screen, enter an amount above the CJIT minimum (e.g. 100 000 sats) using the number pad - Tap "Continue" and wait for the confirm screen to appear - Tap "Learn more" to open the liquidity screen - Verify the liquidity screen with the lightning channel and the "Set up in background" toggle (testTag "ReceiveLiquidityNotificationSwitch") is visible and unchecked - Tap the "Set up in background" toggle - Verify the Android system notification permission dialog appears (text like "Allow Bitkit to send you notifications?") - Tap "Allow" - Verify the "Set up in background" toggle is now checked - - diff --git a/journeys/notification-permission/toggle-off-opens-system-settings.xml b/journeys/notification-permission/toggle-off-opens-system-settings.xml deleted file mode 100644 index c90ca54c4f..0000000000 --- a/journeys/notification-permission/toggle-off-opens-system-settings.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - Verifies that when the "Set up in background" toggle is already ON (POST_NOTIFICATIONS - granted), tapping it opens the system notification settings instead of re-requesting the - permission (which would be a no-op). Uses the Receive → CJIT confirm screen, but the - behaviour is shared by the transfer and liquidity toggles. - - Precondition: API 33+ onboarded dev wallet with the node connected to the LSP so a CJIT - order can be quoted, and POST_NOTIFICATIONS ALREADY GRANTED so the toggle starts checked - (grant via: adb shell pm grant to.bitkit.dev android.permission.POST_NOTIFICATIONS). - Start on the wallet home screen. - - - Tap the "Receive" button on the home screen - Tap the "Spending" tab in the Receive sheet - Tap "Receive Lightning funds" - On the amount screen, enter an amount above the CJIT minimum (e.g. 100 000 sats) using the number pad - Tap "Continue" and wait for the confirm screen to appear - Verify the "Set up in background" toggle (testTag "ReceiveConfirmNotificationSwitch") is visible and checked - Tap the "Set up in background" toggle - Verify the Android system notification settings screen for Bitkit opens (no in-app navigation, no permission dialog) - Press back to return to Bitkit and verify the confirm screen is still shown - - diff --git a/journeys/notification-permission/transfer-spending-confirm-notification-toggle.xml b/journeys/notification-permission/transfer-spending-confirm-notification-toggle.xml deleted file mode 100644 index 71fde7c486..0000000000 --- a/journeys/notification-permission/transfer-spending-confirm-notification-toggle.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - Verifies that the "Set up in background" toggle on the Transfer → Spending confirm - screen (SpendingConfirmScreen) launches the Android POST_NOTIFICATIONS runtime - permission dialog on API 33+, and that granting it checks the toggle. - - Precondition: API 33+ onboarded dev wallet with a POSITIVE on-chain Savings balance and - the node connected to the LSP (so a real max can be quoted), and POST_NOTIFICATIONS in - the "ask" state (revoke or reinstall first — the dialog is one-shot). The spending max - starts at 0 behind a spinner — wait for it to populate. Start on the wallet home screen. - - - Tap the Spending balance card on the home screen - Tap "Transfer from Savings" - If a transfer intro screen appears, tap "Get Started" - On the spending amount screen (testTag "SpendingAmount"), wait until the available/maximum amount (testTag "SpendingAmountAvailable") finishes loading and shows a positive value - Enter an amount within the maximum using the number pad - Tap continue (testTag "SpendingAmountContinue") and wait for the spending confirm screen to appear - Verify the "Set up in background" toggle (testTag "SpendingConfirmNotificationSwitch") is visible and unchecked - Tap the "Set up in background" toggle - Verify the Android system notification permission dialog appears (text like "Allow Bitkit to send you notifications?") - Tap "Allow" - Verify the "Set up in background" toggle is now checked - - diff --git a/journeys/receive/receive-auto-tab-selection.xml b/journeys/receive/receive-auto-tab-selection.xml index 499e4a98ed..0f802ebf99 100644 --- a/journeys/receive/receive-auto-tab-selection.xml +++ b/journeys/receive/receive-auto-tab-selection.xml @@ -2,8 +2,10 @@ Verifies that the Receive sheet opens on the Auto tab without sliding to it, that a tab the user picks is not overridden by the Auto tab, and that a leftover invoice amount from an earlier Edit - Invoice session does not open the sheet on Savings. Requires a wallet with a usable spending - channel so the Auto tab is available once the Lightning node is running. The selected tab is + Invoice session does not open the sheet on Savings. Requires a wallet with an open, usable + Lightning channel with inbound capacity so the Auto tab is available once the Lightning node is + running; this build cannot buy one from Blocktank (synonymdev/bitkit-android#1669), see ../README.md, + Getting a Lightning channel. Without a channel the sheet shows Savings only. The selected tab is shown only by its underline, which `android layout` does not expose, so tab assertions need a screenshot; the Auto tab's presence can be read from the testTag "Tab-auto". The underline itself crossfades over 200 ms even when the switch is instant, so the two steps that check for an diff --git a/journeys/receive/receive-onchain-only-without-channel.xml b/journeys/receive/receive-onchain-only-without-channel.xml new file mode 100644 index 0000000000..6cbff0036a --- /dev/null +++ b/journeys/receive/receive-onchain-only-without-channel.xml @@ -0,0 +1,27 @@ + + + Verifies that a funded wallet with no Lightning channel can only receive on-chain: the Receive sheet + offers the Savings tab and nothing that buys Lightning liquidity, and Home shows neither the Spend + suggestion card nor the channels-closing banner. Blocktank channel purchases were removed in + synonymdev/bitkit-android#1669: there is no "Receive Lightning funds" (CJIT) button or onboarding, + and the Spending and Auto tabs appear only when the wallet can create a Lightning invoice over a + ready channel with inbound liquidity. + + Precondition: a freshly onboarded dev wallet on the staging regtest backend with NO Lightning + channel (open, pending or closed), USDT disabled and no hardware wallet paired, funded with an + on-chain deposit: ./lsp POST /regtest/chain/deposit with the Savings address from Receive, Show + Details, then ./lsp POST /regtest/chain/mine '{"count":3}', and wait about 20 seconds for the + balance and the activity row to appear. Start on the wallet home screen. + + + Verify the Savings balance card (testTag "ActivitySavings") shows the deposit and the Spending balance card (testTag "ActivitySpending") shows 0 + Verify the home screen has no Spend suggestion card (testTag "Suggestion-lightning") and no channels-closing banner (testTag "ChannelsClosingBanner"), scrolling the suggestions row if needed + Tap the Receive button (testTag "Receive") + Verify the Savings tab (testTag "Tab-savings") is selected and the QR code (testTag "QRCode") is visible + Verify there is no Spending tab (testTag "Tab-spending") and no Auto tab (testTag "Tab-auto") + Verify there is no "Receive Lightning funds" button anywhere on the sheet + Tap "Edit" (testTag "SpecifyInvoiceButton"), tap the amount field (testTag "ReceiveNumberPadTextField"), enter a small amount (for example "N1" then "N0" twice), tap Continue (testTag "ReceiveNumberPadSubmit") and then "QR Code" (testTag "ShowQrReceive") + Verify the sheet returns to the QR screen with no liquidity or channel confirm screen in between, the Savings tab is still selected and there is still no Spending tab (testTag "Tab-spending") + Close the sheet, open the Savings wallet (testTag "ActivitySavings") and verify no "Transfer To Spending" button (testTag "TransferToSpending") is shown, then open the Spending wallet (testTag "ActivitySpending") and verify no "Transfer from Savings" button (testTag "TransferFromSavings") and no notice (testTag "ChannelsClosingNotice") is shown + + diff --git a/journeys/receive/receive-tabs-wraparound.xml b/journeys/receive/receive-tabs-wraparound.xml index bd0a672eb5..1125fa7f55 100644 --- a/journeys/receive/receive-tabs-wraparound.xml +++ b/journeys/receive/receive-tabs-wraparound.xml @@ -6,15 +6,16 @@ the Receive sheet, also when it is released above the sheet, and a tap on a QR still copies it. Precondition: onboarded dev wallet with USDT enabled (introductory sheet or Settings, General, - Payments), no open Spending channel, no CJIT invoice pending and no hardware wallet paired, so the - tabs are Savings, Spending and USDT; a usable channel adds the Auto tab. Start on the wallet home + Payments), no Lightning channel and no hardware wallet paired, so the tabs are Savings and USDT. + Without a channel with inbound liquidity the Receive sheet has no Spending or Auto tab, since + Blocktank channel purchases were removed (synonymdev/bitkit-android#1669). Start on the wallet home screen. The USDT address loads without funding; wait for the QR before swiping. Swipe with `adb shell input swipe` across the middle of the QR, about 60% of its width, in 300 ms (`adb shell input swipe 220 1176 860 1176 300` to the right on a 1080 px wide screen). A swipe that starts at the screen edge is the system back gesture, which closes the sheet on every - tab; start the swipes at least 100 px from the edge. The pager behind Savings, Auto and Spending - snaps back from a short synthetic swipe on the emulator, so swipe across it in 1200 ms + tab; start the swipes at least 100 px from the edge. The pager behind Savings snaps back from a + short synthetic swipe on the emulator, so swipe across it in 1200 ms (`adb shell input swipe 150 1176 900 1176 1200`); the USDT QR takes the 300 ms swipe. @@ -23,16 +24,11 @@ Swipe left across the USDT QR; verify the Savings tab is selected Start a screen recording, tap the USDT tab (testTag "Tab-usdt") and wait until the USDT QR is visible, then swipe left across the USDT QR; verify the Savings tab is selected and the recording goes straight from the USDT QR to the Savings QR, with no Auto or Spending QR and no second USDT screen in between Start a screen recording, tap the USDT tab and wait until the USDT QR is visible, then tap the Savings tab; verify the Savings tab is selected and the recording shows the same direct change from USDT to Savings - Swipe left across the Savings QR in 1200 ms (`adb shell input swipe 900 1176 150 1176 1200`); verify the Spending tab (testTag "Tab-spending") is selected - Swipe left across the Spending content in 1200 ms; verify the USDT tab is selected and wait until its QR is visible + Verify there is no Spending tab (testTag "Tab-spending") and no Auto tab (testTag "Tab-auto") in the tab row + Swipe left across the Savings QR in 1200 ms (`adb shell input swipe 900 1176 150 1176 1200`); verify the USDT tab is selected and wait until its QR is visible Swipe left from the top right of the USDT QR to above the sheet (`adb shell input swipe 950 720 100 200 300`); verify the Receive sheet is still open and the Savings tab is selected Swipe right from the top left of the Savings QR to above the sheet (`adb shell input swipe 100 720 950 200 1200`); verify the Receive sheet is still open and the USDT tab is selected Swipe left across the USDT QR, then tap the Savings QR once; verify the "copied" tooltip appears - Close Receive, turn USDT off (Settings, General, Payments), return to the wallet home screen and tap Receive; verify the Savings tab is selected and only Savings and Spending show - Swipe right across the Savings QR; verify the Spending tab is selected - Swipe right across the Spending content; verify the Savings tab is selected - Swipe left across the Savings QR; verify the Spending tab is selected - Swipe left across the Spending content; verify the Savings tab is selected - Swipe left across the Savings QR; verify the Spending tab is selected + Close Receive, turn USDT off (Settings, General, Payments), return to the wallet home screen and tap Receive; verify the Savings tab is selected and there is no Spending tab (testTag "Tab-spending"), no Auto tab and no USDT tab (testTag "Tab-usdt") diff --git a/journeys/receive/usdt-receive-swipe-tabs.xml b/journeys/receive/usdt-receive-swipe-tabs.xml index fea00c104a..22177674cd 100644 --- a/journeys/receive/usdt-receive-swipe-tabs.xml +++ b/journeys/receive/usdt-receive-swipe-tabs.xml @@ -1,27 +1,28 @@ On the Receive sheet, swiping sideways over the QR changes tabs on every tab, USDT included. - With USDT selected, a swipe to the right over its QR selects the tab before it (Spending) and - does not copy the address; a swipe to the left wraps around to the first tab (Savings), since - USDT is the last tab. A single tap on the USDT QR still copies the address and shows the "copied" + With USDT selected, a swipe to the right over its QR selects the tab before it (Savings, since a + wallet without a Lightning channel has no Spending tab) and does not copy the address; a swipe to + the left wraps around to the first tab (Savings), since USDT is the last tab. A single tap on the USDT QR still copies the address and shows the "copied" tooltip. Precondition: onboarded dev wallet with USDT enabled (introductory sheet or Settings, General, - Payments), no CJIT invoice pending, start on the wallet home screen. The USDT address loads + Payments) and no Lightning channel, so the tabs are Savings and USDT (Blocktank channel purchases + were removed in synonymdev/bitkit-android#1669, and the Spending tab appears only over a channel + with inbound liquidity). Start on the wallet home screen. The USDT address loads without funding; wait for the QR before swiping. The copy shows as a tooltip over the QR for about two seconds, so a check for "no copy" reads a screenshot taken right after the swipe. Swipe with `adb shell input swipe` across the middle of - the QR, about 60% of its width, in 300 ms. Back from Spending to USDT goes through the tab + the QR, about 60% of its width, in 300 ms. Back from Savings to USDT goes through the tab pager, which snaps back from a 300 ms synthetic swipe on the emulator; a 600 ms swipe moves it. - On a wallet without a Lightning channel, Spending shows its setup view instead of a QR. Tap Receive (testTag "Receive") Tap the USDT tab (testTag "Tab-usdt") and wait until the USDT QR is visible Swipe right across the QR and take a screenshot right away - Verify the Spending tab (testTag "Tab-spending") is selected, the Lightning receive content or the Spending setup is shown, and the screenshot shows no "copied" tooltip - Swipe left across the middle of the Spending content, whether it shows a Lightning QR or the Spending setup, in 600 ms (`adb shell input swipe 900 1200 150 1200 600`); verify the USDT tab is selected and wait until its QR is visible + Verify the Savings tab (testTag "Tab-savings") is selected, there is no Spending tab (testTag "Tab-spending"), and the screenshot shows no "copied" tooltip + Swipe left across the middle of the Savings QR in 600 ms (`adb shell input swipe 900 1200 150 1200 600`); verify the USDT tab is selected and wait until its QR is visible Tap the USDT QR once and verify the "copied" tooltip appears Wait until the tooltip is gone, swipe left across the USDT QR and take a screenshot right away; verify the Savings tab (testTag "Tab-savings") is selected and the screenshot shows no "copied" tooltip diff --git a/journeys/transfer/spending-confirm-amount-change.xml b/journeys/transfer/spending-confirm-amount-change.xml deleted file mode 100644 index 068923d04f..0000000000 --- a/journeys/transfer/spending-confirm-amount-change.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - Verifies that a transfer to spending never pays more than the total Spending Confirm shows - (synonymdev/bitkit-android#1361). The funding plan is rebuilt when the order is created at swipe. - A plan that costs the same or less pays on that swipe, at the rebuilt amount. A plan whose total is - higher pays nothing: the screen shows the new amounts and a "Fees changed" toast, and the next - swipe pays them. The higher-total case needs a spend of the wallet's own UTXO outside the app, which - the Capabilities table does not list, so unit tests and a manual test cover it. - - The amount changes through coin selection: Spending Confirm sizes the network fee for two inputs, - then a larger UTXO confirms before the swipe, so the plan rebuilt at swipe spends one input and - pays a lower network fee, at once. - - iOS differs in one step: it has no app-log step. - - Precondition: a freshly onboarded dev wallet on the staging regtest backend with no channel and - exactly two confirmed on-chain UTXOs of 150 000 sats each (two ./lsp deposits to the wallet's - savings address, each followed by a mine). Start on the wallet home screen. - - - Tap Receive (testTag "Receive"), tap the "Savings" receive tab (the tab row carries no test tag), tap "Show Details" (testTag "ShowDetails"), read the address from testTag "ReceiveOnchainAddress", then press back to the home screen - Tap the Savings balance card (testTag "ActivitySavings"), then "Transfer To Spending" (testTag "TransferToSpending") - If the spending intro appears, tap "Get Started" (testTag "SpendingIntro-button") - Enter 200 000 on the number pad (testTags "N2", "N0", "N0", "N0", "N0", "N0") and tap "Continue" (testTag "SpendingAmountContinue") - Verify the confirm screen is visible (testTag "SpendingConfirmMore") and wait until its network fee is shown - Note the amounts under "Network Fees" and "Total" - Run: adb logcat -c - Run in one command: ./lsp POST /regtest/chain/deposit '{"address":"<savings addr>","amountSat":500000}' && ./lsp POST /regtest/chain/mine '{"count":1}' - Wait 30s for the wallet to sync the new UTXO - If the received sheet (testTag "ReceivedTransaction") is visible, tap its button (testTag "ReceivedTransactionButton") - Verify the confirm screen still shows the amounts noted before the deposit - Swipe the "Swipe To Transfer" handle (testTag "GRAB") fully to the right - Wait up to 90s for the setting up screen (testTag "LightningSettingUp") - Run: adb logcat -d -s APP:V | grep "BT confirm" - Verify the last "BT confirm" line shows a lower miningFee than the "Network Fees" amount noted, not the amount the screen showed - - diff --git a/journeys/transfer/transfer-to-savings-returns-home.xml b/journeys/transfer/transfer-to-savings-returns-home.xml index bca5412dd2..c00ea26dfd 100644 --- a/journeys/transfer/transfer-to-savings-returns-home.xml +++ b/journeys/transfer/transfer-to-savings-returns-home.xml @@ -6,8 +6,11 @@ (synonymdev/bitkit-android#809). iOS resets navigation to home on the same OK. Precondition: onboarded dev wallet with an open, usable channel to the LSP and a POSITIVE - Spending balance. The transfer closes that channel, so reopen one (Blocktank order through the - lsp helper, then mine blocks) afterwards if later work needs a Spending balance. Start on the + Spending balance. This build cannot buy a channel from Blocktank (synonymdev/bitkit-android#1669), so + get one by installing a build from before that change, opening a channel with Transfer To Spending + and installing this build over it, or by opening a manual channel to the staging LSP node (see + ../README.md, Getting a Lightning channel). The transfer closes that channel, and a replacement + needs the same steps again if later work needs a Spending balance. Start on the wallet home screen. diff --git a/journeys/transfers/closed-channel-transfer-settles.xml b/journeys/transfers/closed-channel-transfer-settles.xml deleted file mode 100644 index 7b0daa4ee3..0000000000 --- a/journeys/transfers/closed-channel-transfer-settles.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - Covers issue #614. A pending Transfer to Spending keeps its "TRANSFER IN PROGRESS" banner across - an app restart while the node starts, and a transfer whose channel the LSP later closes does not - leave a banner behind or inflate the Spending balance. - - Precondition: onboarded dev wallet (staging regtest) with a positive on-chain Savings balance and - a running node connected to the LSP. Start on the wallet home screen. The LSP only opens the - channel after the order payment has one confirmation, so the transfer stays pending until a block - is mined. The dev build mines that block itself: the SettingUp screen auto-mines one block 5 s - after it appears on regtest, and leaving the screen cancels it. Tap "Continue Using Bitkit" within - those 5 s so the transfer is still pending for the restart steps; the block is then mined by hand - later in the journey. - - Transfer orders are zero-conf, so on an unmodified build the transfer settles as soon as the - channel is ready and the closed-channel settle path is not reached on its own. This journey then - checks that nothing is left behind after the close. Reaching the closed-channel path on a device - needs a temporary local change that skips the channel-ready settle; it was verified that way for - issue #614 and is covered by TransferRepoTest.kt. - - On-screen, this journey is a no-regression check: the banner, balance and close-sheet steps pass - on master too, because both versions settle the transfer when the zero-conf channel becomes ready. - The one thing that tells the two builds apart on a device is the app log. Only the fixed build - fetches LSP orders for still-pending transfers, so only it writes "orders for active transfers" - while the transfer is pending; master never calls the LSP there. The fixed build may also write - "Skipped syncing transfers because channels are unavailable" while the node is starting after the - restart, which master does not write either, but that line is not guaranteed on every run. - - The close moves the transfer amount back to Savings minus fees, so the wallet ends close to its - starting balances. - - - Note the Spending balance shown on the home screen (testTag "ActivitySpending") - Tap the Savings balance card (testTag "ActivitySavings") - Tap "Transfer To Spending" (testTag "TransferToSpending") - If the spending intro screen appears, tap its button (testTag "SpendingIntro-button") - Verify the spending amount screen (testTag "SpendingAmount") is visible - Wait until the available amount (testTag "SpendingAmountUnit") shows a positive value - Tap the "5" key (testTag "N5") enough times to enter an amount the LSP accepts (50000 sats is a safe value), then tap Continue (testTag "SpendingAmountContinue") - Swipe "Swipe To Transfer" to confirm - As soon as the setting up screen (testTag "LightningSettingUp") appears, tap "Continue Using Bitkit" (testTag "TransferSuccess-button") within 5 s, before the screen auto-mines a block; do not dump the layout first - Verify the purple "TRANSFER IN PROGRESS" banner is visible on the home screen (screenshot; the banner carries no testTag). If the banner is already gone, the auto-mine won the race: report the run as inconclusive and start over - Run adb shell am force-stop to.bitkit.dev, then launch the app again - Wait for the home screen and for the node to start (app log line "Node started") - Verify the purple "TRANSFER IN PROGRESS" banner is still visible (screenshot) - Read the order id from the app log: the active TO_SPENDING transfer's "lspOrderId" in the latest "Active transfers at block height" line - Verify the app log has a line ending in "orders for active transfers" written after the transfer was created; master never writes it, so its absence means the build under test does not carry the fix - Run ./lsp POST /regtest/chain/mine '{"count":1}' - Poll ./lsp GET /channels/<lspOrderId> until channel.state is "open", and note channel.fundingTx.id and channel.fundingTx.vout - Tap the OK button (testTag "TransferSuccess-button") if the Transfer Successful screen is shown; its label is randomized ("Great!", "Nice!", …) - Run ./lsp POST /regtest/channel/close '{"fundingTxId":"<fundingTx.id>","vout":<fundingTx.vout>,"forceCloseAfterSec":0}' - Run ./lsp POST /regtest/chain/mine '{"count":3}' - Wait for the "Connection Closed" sheet and tap OK (testTag "ConnectionClosedButton") - Verify no "TRANSFER IN PROGRESS" banner is visible on the home screen (screenshot) - Verify the Spending balance (testTag "ActivitySpending") matches the value noted in the first step - Verify the app log has a "ready, settled transfer:" line for the transfer (the build writes "Channel <channelId> ready, settled transfer: <id>", with no quotes around the ids), and that the latest "Active transfers at block height" line lists no TO_SPENDING transfer. The closed-channel settle lines ("Settled transfer '<id>' for closed channel …") only appear on a build that skips the channel-ready settle, so do not expect them here - - diff --git a/journeys/usdt-wallet-navigation.xml b/journeys/usdt-wallet-navigation.xml index 1332329977..eb1c30345f 100644 --- a/journeys/usdt-wallet-navigation.xml +++ b/journeys/usdt-wallet-navigation.xml @@ -8,7 +8,7 @@ Open Receive. Verify the network is Arbitrum One. Open details, verify the address starts with 0x, and verify Details Copy contains only the address. Return to the QR and verify Copy and tapping the QR both copy only the same address, while the QR still contains the full USDT payment URI including the network and token. On Android, system Back from Details returns to the USDT QR; Back from the QR closes Receive without switching to Savings. Close Receive, open Send and manual entry. Enter an invalid Bitcoin address and verify the address error prevents continuing. Replace it with an owned valid EVM address. Continue to Amount, enter six decimal places and try a seventh; verify the amount remains limited to six. Close before requesting a quote. - Set an amount and note in Bitcoin Receive, then close it. Open a fresh USDT Receive sheet and switch to Savings and Spending. Verify Savings shows a fresh Bitcoin QR with no previous amount or note. Spending shows a fresh Lightning QR when receive liquidity is available; an unfunded wallet shows the Spending receive setup instead. Verify hardware-wallet receive remains available when paired. + Set an amount and note in Bitcoin Receive, then close it. Open a fresh USDT Receive sheet and switch to Savings and, when the wallet has a Lightning channel with inbound liquidity, Spending. Verify Savings shows a fresh Bitcoin QR with no previous amount or note. When the Spending tab (testTag "Tab-spending") is present it shows a fresh Lightning QR; a wallet without such a channel has no Spending tab. Verify hardware-wallet receive remains available when paired. Return Home and open Bitcoin Receive. Verify the Bitcoin address, network and amount controls remain separate from USDT. With saved USDT activity, open All Activity. Search for USDT and usdt and verify both show USDT rows. Search for u, us or t and verify a USDT row appears only if its recipient or transaction ID contains the query. Verify a matching recipient or transaction-ID fragment still finds the row, and clearing search restores the list. From 563bb05af720116c9c69d07b515a226d5c8ba3f3 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 22 Oct 2026 12:55:59 +0200 Subject: [PATCH 3/8] feat: explain unavailable channels on transfer to spending --- app/src/main/java/to/bitkit/ui/ContentView.kt | 14 ++ .../java/to/bitkit/ui/components/SheetHost.kt | 1 + .../screens/wallets/HardwareWalletScreen.kt | 29 ++++ .../ui/screens/wallets/SavingsWalletScreen.kt | 55 ++++++++ .../screens/wallets/SpendingWalletScreen.kt | 49 +++++++ .../ui/sheets/ChannelsUnavailableSheet.kt | 127 ++++++++++++++++++ app/src/main/res/values-ar/strings.xml | 3 + app/src/main/res/values-b+es+419/strings.xml | 3 + app/src/main/res/values-ca/strings.xml | 3 + app/src/main/res/values-cs/strings.xml | 3 + app/src/main/res/values-de/strings.xml | 3 + app/src/main/res/values-el/strings.xml | 3 + app/src/main/res/values-es-rES/strings.xml | 3 + app/src/main/res/values-es/strings.xml | 3 + app/src/main/res/values-fr/strings.xml | 3 + app/src/main/res/values-it/strings.xml | 3 + app/src/main/res/values-nl/strings.xml | 3 + app/src/main/res/values-pl/strings.xml | 3 + app/src/main/res/values-pt-rBR/strings.xml | 3 + app/src/main/res/values-pt/strings.xml | 3 + app/src/main/res/values-ru/strings.xml | 3 + app/src/main/res/values/strings.xml | 3 + journeys/README.md | 7 +- journeys/amount-limits/README.md | 2 +- journeys/channels-closing/README.md | 15 ++- .../channels-closing-notice.xml | 11 +- journeys/cjit-notifications/README.md | 2 +- journeys/hardware-wallet/README.md | 5 +- journeys/hardware-wallet/detail-overview.xml | 6 +- .../receive-onchain-only-without-channel.xml | 7 +- 30 files changed, 359 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/to/bitkit/ui/sheets/ChannelsUnavailableSheet.kt diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt index 959cc54849..6bd7fdf8a0 100644 --- a/app/src/main/java/to/bitkit/ui/ContentView.kt +++ b/app/src/main/java/to/bitkit/ui/ContentView.kt @@ -216,6 +216,7 @@ 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.ChannelsUnavailableSheet import to.bitkit.ui.sheets.ConnectionClosedSheet import to.bitkit.ui.sheets.DisablePinSheet import to.bitkit.ui.sheets.ForceTransferSheet @@ -655,6 +656,14 @@ fun ContentView( Sheet.ConnectionClosed -> ConnectionClosedSheet( onDismiss = { appViewModel.hideSheet() }, ) + Sheet.ChannelsUnavailable -> { + val hasTrustedChannels by walletViewModel.hasTrustedChannels + .collectAsStateWithLifecycle(initialValue = false) + ChannelsUnavailableSheet( + showCloseDate = hasTrustedChannels, + onDismiss = { appViewModel.hideSheet() }, + ) + } is Sheet.BTCPayConnection -> BTCPayConnectionSheet(sheet, appViewModel) is Sheet.QrScanner -> QrScanningSheet(sheet, appViewModel) @@ -1290,14 +1299,17 @@ private fun NavGraphBuilder.home( ) } deepLinkableComposable { + val isGeoBlocked by appViewModel.isGeoBlocked.collectAsStateWithLifecycle() val onchainActivities by activityListViewModel.onchainActivities.collectAsStateWithLifecycle() val forceCloseRemainingDuration by appViewModel.forceCloseRemainingDuration.collectAsStateWithLifecycle() SavingsWalletScreen( + isGeoBlocked = isGeoBlocked, onchainActivities = onchainActivities ?: persistentListOf(), onAllActivityButtonClick = { navController.navigateToAllActivity(activityListViewModel::clearFilters) }, onActivityItemClick = { navController.navToActivityDetail(it) }, onEmptyActivityRowClick = { appViewModel.showSheet(Sheet.Receive()) }, + onTransferToSpendingClick = { appViewModel.showSheet(Sheet.ChannelsUnavailable) }, onBackClick = { navController.popBackStack() }, forceCloseRemainingDuration = forceCloseRemainingDuration, ) @@ -1321,6 +1333,7 @@ private fun NavGraphBuilder.home( navController.navigateToTransferSavingsAvailability() } }, + onTransferFromSavingsClick = { appViewModel.showSheet(Sheet.ChannelsUnavailable) }, onBackClick = { navController.popBackStack() }, showChannelsClosingNotice = hasTrustedChannels, ) @@ -1330,6 +1343,7 @@ private fun NavGraphBuilder.home( HardwareWalletScreen( walletId = walletId, onActivityItemClick = { navController.navToActivityDetail(it) }, + onTransferToSpendingClick = { appViewModel.showSheet(Sheet.ChannelsUnavailable) }, onBackClick = { navController.popBackStack() }, ) } 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 b16ba57cae..85dcaf0d17 100644 --- a/app/src/main/java/to/bitkit/ui/components/SheetHost.kt +++ b/app/src/main/java/to/bitkit/ui/components/SheetHost.kt @@ -97,6 +97,7 @@ sealed interface Sheet { data class LnurlAuth(val domain: String, val lnurl: String, val k1: String) : Sheet data object ForceTransfer : Sheet data object ConnectionClosed : Sheet + data object ChannelsUnavailable : Sheet data class BTCPayConnection( val setup: SamRockSetupRequest, val isConnecting: Boolean = false, diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/HardwareWalletScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/HardwareWalletScreen.kt index 990f167d13..050ee0eaa0 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/HardwareWalletScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/HardwareWalletScreen.kt @@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -44,6 +45,7 @@ import to.bitkit.models.HwWallet import to.bitkit.models.HwWalletVendor import to.bitkit.models.TransportType import to.bitkit.ui.components.BalanceHeaderView +import to.bitkit.ui.components.SecondaryButton import to.bitkit.ui.components.StatusBarSpacer import to.bitkit.ui.components.TabBar import to.bitkit.ui.components.TertiaryButton @@ -63,6 +65,7 @@ import to.bitkit.ui.theme.TopBarGradient fun HardwareWalletScreen( walletId: String, onActivityItemClick: (Activity) -> Unit, + onTransferToSpendingClick: (String) -> Unit, onBackClick: () -> Unit, viewModel: HwWalletViewModel = hiltViewModel(), ) { @@ -81,6 +84,7 @@ fun HardwareWalletScreen( wallet = device, showRemoveDialog = uiState.isPendingRemoval != null, onActivityItemClick = onActivityItemClick, + onTransferToSpendingClick = onTransferToSpendingClick, onRemoveClick = { viewModel.onRemoveClick(device) }, onConfirmRemove = { viewModel.removeDevice(walletId) }, onDismissRemoveDialog = viewModel::onDismissRemoveDialog, @@ -96,6 +100,7 @@ private fun HardwareWalletContent( wallet: HwWallet, showRemoveDialog: Boolean, onActivityItemClick: (Activity) -> Unit, + onTransferToSpendingClick: (String) -> Unit, onRemoveClick: () -> Unit, onConfirmRemove: () -> Unit, onDismissRemoveDialog: () -> Unit, @@ -104,6 +109,7 @@ private fun HardwareWalletContent( onKeepBackupDataChange: (Boolean) -> Unit = {}, ) { val hasFunds = wallet.balanceSats > 0uL + val hasFundingFunds = wallet.fundingBalanceSats > 0uL val hasActivity = wallet.activities.isNotEmpty() val showEmptyState = !hasFunds && !hasActivity @@ -170,6 +176,24 @@ private fun HardwareWalletContent( if (!showEmptyState) { item { VerticalSpacer(32.dp) } + if (hasFundingFunds) { + item { + SecondaryButton( + onClick = { onTransferToSpendingClick(wallet.id) }, + text = stringResource(R.string.wallet__transfer_to_spending), + icon = { + Icon( + painter = painterResource(R.drawable.ic_transfer), + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + }, + hazeState = hazeState, + modifier = Modifier.testTag("HardwareTransferToSpending") + ) + } + } + activityListGroupedItems( items = wallet.activities, onActivityItemClick = onActivityItemClick, @@ -253,6 +277,7 @@ private fun Preview() { wallet = previewWallet(), showRemoveDialog = false, onActivityItemClick = {}, + onTransferToSpendingClick = { _ -> }, onRemoveClick = {}, onConfirmRemove = {}, onDismissRemoveDialog = {}, @@ -272,6 +297,7 @@ private fun PreviewJade() { wallet = previewWallet(vendor = HwWalletVendor.BLOCKSTREAM), showRemoveDialog = false, onActivityItemClick = {}, + onTransferToSpendingClick = { _ -> }, onRemoveClick = {}, onConfirmRemove = {}, onDismissRemoveDialog = {}, @@ -291,6 +317,7 @@ private fun PreviewNoActivity() { wallet = previewWallet(activities = persistentListOf()), showRemoveDialog = false, onActivityItemClick = {}, + onTransferToSpendingClick = { _ -> }, onRemoveClick = {}, onConfirmRemove = {}, onDismissRemoveDialog = {}, @@ -310,6 +337,7 @@ private fun PreviewEmpty() { wallet = previewWallet(balanceSats = 0uL, activities = persistentListOf()), showRemoveDialog = false, onActivityItemClick = {}, + onTransferToSpendingClick = { _ -> }, onRemoveClick = {}, onConfirmRemove = {}, onDismissRemoveDialog = {}, @@ -329,6 +357,7 @@ private fun PreviewRemoveDialog() { wallet = previewWallet(), showRemoveDialog = true, onActivityItemClick = {}, + onTransferToSpendingClick = { _ -> }, onRemoveClick = {}, onConfirmRemove = {}, onDismissRemoveDialog = {}, diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt index 061344eb50..f9aa4c5da8 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt @@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -40,6 +41,7 @@ import to.bitkit.ui.LocalBalances import to.bitkit.ui.components.BalanceHeaderView import to.bitkit.ui.components.EmptyStateView import to.bitkit.ui.components.IncomingTransfer +import to.bitkit.ui.components.SecondaryButton import to.bitkit.ui.components.StatusBarSpacer import to.bitkit.ui.components.TabBar import to.bitkit.ui.components.TopBarSpacer @@ -56,10 +58,12 @@ import to.bitkit.ui.utils.withAccent @Composable fun SavingsWalletScreen( + isGeoBlocked: Boolean, onchainActivities: ImmutableList, onAllActivityButtonClick: () -> Unit, onEmptyActivityRowClick: () -> Unit, onActivityItemClick: (Activity) -> Unit, + onTransferToSpendingClick: () -> Unit, onBackClick: () -> Unit, forceCloseRemainingDuration: String? = null, balances: BalanceState = LocalBalances.current, @@ -69,6 +73,10 @@ fun SavingsWalletScreen( val hasActivity = onchainActivities.isNotEmpty() mutableStateOf(!hasFunds && !hasActivity) } + val canTransfer by remember(balances.totalOnchainSats) { + val hasFunds = balances.totalOnchainSats > 0uL + mutableStateOf(hasFunds && !isGeoBlocked) + } val hazeState = rememberHazeState() @@ -144,6 +152,25 @@ fun SavingsWalletScreen( if (!showEmptyState) { item { VerticalSpacer(32.dp) } + if (canTransfer) { + item { + SecondaryButton( + onClick = onTransferToSpendingClick, + text = stringResource(R.string.wallet__transfer_to_spending), + icon = { + Icon( + painter = painterResource(R.drawable.ic_transfer), + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + }, + hazeState = hazeState, + modifier = Modifier.testTag("TransferToSpending") + ) + } + item { VerticalSpacer(32.dp) } + } + activityListGroupedItems( items = onchainActivities, onActivityItemClick = onActivityItemClick, @@ -171,10 +198,12 @@ private fun Preview() { AppThemeSurface { Box { SavingsWalletScreen( + isGeoBlocked = false, onchainActivities = previewOnchainActivityItems(), onAllActivityButtonClick = {}, onActivityItemClick = {}, onEmptyActivityRowClick = {}, + onTransferToSpendingClick = {}, onBackClick = {}, balances = BalanceState(totalOnchainSats = 50_000u), ) @@ -189,10 +218,12 @@ private fun PreviewTransfer() { AppThemeSurface { Box { SavingsWalletScreen( + isGeoBlocked = false, onchainActivities = previewOnchainActivityItems(), onAllActivityButtonClick = {}, onActivityItemClick = {}, onEmptyActivityRowClick = {}, + onTransferToSpendingClick = {}, onBackClick = {}, balances = BalanceState( totalOnchainSats = 50_000u, @@ -210,10 +241,32 @@ private fun PreviewNoActivity() { AppThemeSurface { Box { SavingsWalletScreen( + isGeoBlocked = false, onchainActivities = persistentListOf(), onAllActivityButtonClick = {}, onActivityItemClick = {}, onEmptyActivityRowClick = {}, + onTransferToSpendingClick = {}, + onBackClick = {}, + balances = BalanceState(totalOnchainSats = 50_000u), + ) + TabBar() + } + } +} + +@Preview(showSystemUi = true) +@Composable +private fun PreviewGeoBlocked() { + AppThemeSurface { + Box { + SavingsWalletScreen( + isGeoBlocked = true, + onchainActivities = previewOnchainActivityItems(), + onAllActivityButtonClick = {}, + onActivityItemClick = {}, + onEmptyActivityRowClick = {}, + onTransferToSpendingClick = {}, onBackClick = {}, balances = BalanceState(totalOnchainSats = 50_000u), ) @@ -228,10 +281,12 @@ private fun PreviewEmpty() { AppThemeSurface { Box { SavingsWalletScreen( + isGeoBlocked = false, onchainActivities = persistentListOf(), onAllActivityButtonClick = {}, onActivityItemClick = {}, onEmptyActivityRowClick = {}, + onTransferToSpendingClick = {}, onBackClick = {}, ) TabBar() diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt index 180ff9d3f9..dda6d4f202 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/SpendingWalletScreen.kt @@ -69,6 +69,7 @@ fun SpendingWalletScreen( onActivityItemClick: (Activity) -> Unit, onEmptyActivityRowClick: () -> Unit, onTransferToSavingsClick: () -> Unit, + onTransferFromSavingsClick: () -> Unit, onBackClick: () -> Unit, showChannelsClosingNotice: Boolean = false, balances: BalanceState = LocalBalances.current, @@ -83,6 +84,9 @@ fun SpendingWalletScreen( val hasChannels = channels.isNotEmpty() mutableStateOf(hasLnBalance && hasChannels) } + val canTransferFromSavings by remember(showEmptyState, balances.totalOnchainSats) { + mutableStateOf(showEmptyState && balances.totalOnchainSats > 0uL) + } val locale = LocalConfiguration.current.locales[0] val channelsCloseDate = remember(locale) { formatChannelsCloseDate(locale) } val hazeState = rememberHazeState() @@ -166,6 +170,26 @@ fun SpendingWalletScreen( } } + if (canTransferFromSavings) { + item { VerticalSpacer(32.dp) } + + item { + SecondaryButton( + onClick = onTransferFromSavingsClick, + text = stringResource(R.string.lightning__funding__button1), + icon = { + Icon( + painter = painterResource(R.drawable.ic_transfer), + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + }, + hazeState = hazeState, + modifier = Modifier.testTag("TransferFromSavings") + ) + } + } + if (!showEmptyState) { item { VerticalSpacer(32.dp) } @@ -221,6 +245,7 @@ private fun Preview() { onActivityItemClick = {}, onEmptyActivityRowClick = {}, onTransferToSavingsClick = {}, + onTransferFromSavingsClick = {}, onBackClick = {}, showChannelsClosingNotice = true, balances = BalanceState(totalLightningSats = 50_000u), @@ -242,6 +267,7 @@ private fun PreviewTransfer() { onActivityItemClick = {}, onEmptyActivityRowClick = {}, onTransferToSavingsClick = {}, + onTransferFromSavingsClick = {}, onBackClick = {}, balances = BalanceState( totalLightningSats = 50_000u, @@ -265,6 +291,7 @@ private fun PreviewNoActivity() { onActivityItemClick = {}, onEmptyActivityRowClick = {}, onTransferToSavingsClick = {}, + onTransferFromSavingsClick = {}, onBackClick = {}, balances = BalanceState(totalLightningSats = 50_000u), ) @@ -285,7 +312,29 @@ private fun PreviewEmpty() { onActivityItemClick = {}, onEmptyActivityRowClick = {}, onTransferToSavingsClick = {}, + onTransferFromSavingsClick = {}, + onBackClick = {}, + ) + TabBar() + } + } +} + +@Preview(showSystemUi = true) +@Composable +private fun PreviewEmptyWithSavings() { + AppThemeSurface { + Box { + SpendingWalletScreen( + channels = persistentListOf(), + lightningActivities = persistentListOf(), + onAllActivityButtonClick = {}, + onActivityItemClick = {}, + onEmptyActivityRowClick = {}, + onTransferToSavingsClick = {}, + onTransferFromSavingsClick = {}, onBackClick = {}, + balances = BalanceState(totalOnchainSats = 100_000u), ) TabBar() } diff --git a/app/src/main/java/to/bitkit/ui/sheets/ChannelsUnavailableSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/ChannelsUnavailableSheet.kt new file mode 100644 index 0000000000..5be1b8cf57 --- /dev/null +++ b/app/src/main/java/to/bitkit/ui/sheets/ChannelsUnavailableSheet.kt @@ -0,0 +1,127 @@ +package to.bitkit.ui.sheets + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +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.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalConfiguration +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 to.bitkit.R +import to.bitkit.models.formatChannelsCloseDate +import to.bitkit.ui.components.BodyM +import to.bitkit.ui.components.BottomSheetPreview +import to.bitkit.ui.components.PrimaryButton +import to.bitkit.ui.components.SheetSize +import to.bitkit.ui.components.VerticalSpacer +import to.bitkit.ui.scaffold.SheetTopBar +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 + +@Composable +fun ChannelsUnavailableSheet( + showCloseDate: Boolean, + onDismiss: () -> Unit, +) { + Content( + showCloseDate = showCloseDate, + onDismiss = onDismiss, + ) +} + +@Composable +private fun Content( + showCloseDate: Boolean, + modifier: Modifier = Modifier, + onDismiss: () -> Unit = {}, +) { + val locale = LocalConfiguration.current.locales[0] + val closeDate = remember(locale) { formatChannelsCloseDate(locale) } + + Column( + modifier = modifier + .sheetHeight( + size = SheetSize.MEDIUM + ) + .gradientBackground() + .navigationBarsPadding() + .padding(horizontal = 16.dp) + .testTag("ChannelsUnavailableSheet") + ) { + SheetTopBar(titleText = stringResource(R.string.lightning__channels_unavailable__title)) + + BodyM( + text = stringResource(R.string.lightning__channels_unavailable__text), + color = Colors.White64, + modifier = Modifier.fillMaxWidth() + ) + + if (showCloseDate) { + VerticalSpacer(16.dp) + BodyM( + text = stringResource(R.string.lightning__channels_closing_notice, closeDate), + color = Colors.White64, + modifier = Modifier + .fillMaxWidth() + .testTag("ChannelsUnavailableCloseDate") + ) + } + + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) { + Image( + painter = painterResource(R.drawable.exclamation_mark), + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier.size(256.dp) + ) + } + + PrimaryButton( + text = stringResource(R.string.common__ok), + onClick = onDismiss, + modifier = Modifier + .fillMaxWidth() + .testTag("ChannelsUnavailableButton") + ) + + VerticalSpacer(16.dp) + } +} + +@Preview(showSystemUi = true) +@Composable +private fun Preview() { + AppThemeSurface { + BottomSheetPreview { + Content(showCloseDate = true) + } + } +} + +@Preview(showSystemUi = true) +@Composable +private fun PreviewWithoutChannels() { + AppThemeSurface { + BottomSheetPreview { + Content(showCloseDate = false) + } + } +} diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 2e56ecf5aa..4177e905fb 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -106,6 +106,8 @@ نقطة القناة تُغلق القنوات في %1$s ستُغلق قنوات Lightning الخاصة بك في %1$s. انقل رصيد الإنفاق إلى المدخرات قبل ذلك. + لم يعد Bitkit يوفّر قنوات Lightning جديدة، لذا لا يمكنك نقل الأموال من المدخرات إلى الإنفاق. + القنوات الجديدة غير متاحة إغلاق إغلاق الاتصال فشل التحويل @@ -1067,6 +1069,7 @@ أصبحت المعاملة غير مؤكدة بسبب إعادة تنظيم السلسلة المعاملة غير مؤكدة تحويل إلى المدخرات + تحويل إلى الإنفاق لم ينجح سحبك. يرجى مسح رمز QR مرة أخرى أو التواصل مع الدعم. إضافة أداة افحص إحصائيات متنوعة عن كتل Bitcoin المُعدّنة حديثًا. 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 9a12d18766..3e7b4b9209 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -111,6 +111,8 @@ Punto del canal LOS CANALES CIERRAN EL %1$s Tus canales Lightning se cerrarán el %1$s. Transfiere tu saldo de gasto a ahorros antes de esa fecha. + Bitkit ya no ofrece canales Lightning nuevos, así que no puedes transferir fondos de ahorros a gasto. + Canales nuevos no disponibles Cerrar Terminar conexión Transferencia fallida @@ -1104,6 +1106,7 @@ La transacción quedó sin confirmar debido a una reorganización del blockchain Transacción no confirmada Transferir a ahorros + Transferir a gastos Tu retiro no fue exitoso. Por favor escanea el código QR nuevamente o contacta soporte. Añadir Widget Examine diversas estadísticas sobre los nuevos bloques de Bitcoin minados. diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 3350711636..5bfd763030 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -109,6 +109,8 @@ Punt del canal ELS CANALS ES TANQUEN EL %1$s Els teus canals Lightning es tancaran el %1$s. Transfereix el teu saldo de despesa a estalvis abans d\'aquesta data. + Bitkit ja no ofereix canals Lightning nous, així que no pots transferir fons d\'estalvis a despesa. + Canals nous no disponibles Tancar Tanca la connexió Transferència fallida @@ -1095,6 +1097,7 @@ La transacció s\'ha tornat no confirmada a causa d\'una reorganització de blockchain Transacció no confirmada Transferir a estalvis + Transferir a despesa La teva retirada no ha tingut èxit. Si us plau, escaneja el codi QR de nou o contacta amb el suport. Afegir giny Examina diverses estadístiques dels blocs de Bitcoin recentment minats. diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 926c9533b5..986f4cf374 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -111,6 +111,8 @@ Bod kanálu KANÁLY SE ZAVŘOU %1$s Vaše Lightning kanály budou uzavřeny %1$s. Do té doby převeďte zůstatek útrat do úspor. + Bitkit už nenabízí nové Lightning kanály, takže nemůžete převádět prostředky z úspor do útrat. + Nové kanály nejsou dostupné Zavřít Uzavřít spojení Přenos se nezdařil @@ -1101,6 +1103,7 @@ Transakce se stala nepotvrzenou kvůli reorganizaci blockchainu Transakce nepotvrzena Převést do úspor + Převést do útrat Váš výběr byl neúspěšný. Naskenujte prosím QR kód znovu nebo kontaktujte podporu. Přidat widget Prozkoumejte různé statistiky nově vytěžených bloků bitcoinu. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index b49770ea1c..8edc5d20ec 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -101,6 +101,8 @@ Abhängig von der Gebühr KANÄLE SCHLIESSEN AM %1$s Deine Lightning-Kanäle werden am %1$s geschlossen. Übertrage dein Guthaben vorher auf das Sparkonto. + Bitkit bietet keine neuen Lightning-Kanäle mehr an, daher kannst du kein Guthaben vom Sparkonto auf das Ausgabenkonto übertragen. + Neue Kanäle nicht verfügbar Spending\n<accent>Balance</accent> Fülle dein Guthaben auf, um sofortige und günstige Transaktionen mit Freunden, Familie und Händlern zu genießen. Los geht\'s @@ -1099,6 +1101,7 @@ Lightning-Startfehler Peer getrennt. Übertragen zum Sparkonto + Übertragen zum Ausgabenkonto Deine Abhebung war nicht erfolgreich. Bitte scanne den QR-Code erneut oder kontaktiere den Support. Widgets Hallo,\n<accent>Widgets</accent> diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index c4e8b0a778..619e16690f 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -106,6 +106,8 @@ Σημείο καναλιού ΤΑ ΚΑΝΑΛΙΑ ΚΛΕΙΝΟΥΝ ΣΤΙΣ %1$s Τα κανάλια Lightning σας θα κλείσουν στις %1$s. Μεταφέρετε το υπόλοιπο δαπανών σε αποταμιεύσεις πριν από τότε. + Το Bitkit δεν προσφέρει πλέον νέα κανάλια Lightning, οπότε δεν μπορείτε να μεταφέρετε κεφάλαια από τις αποταμιεύσεις στις δαπάνες. + Νέα κανάλια μη διαθέσιμα Κλείσιμο Κλείσιμο σύνδεσης Αποτυχία μεταφοράς @@ -1074,6 +1076,7 @@ Η συναλλαγή έγινε μη επιβεβαιωμένη λόγω αναδιοργάνωσης blockchain Μη επιβεβαιωμένη συναλλαγή Μεταφορά σε αποταμιεύσεις + Μεταφορά σε δαπάνες Η ανάληψή σου δεν ήταν επιτυχής. Σάρωσε ξανά τον κωδικό QR ή επικοινώνησε με την υποστήριξη. Προσθήκη Widget Εξέτασε διάφορα στατιστικά για τα νεοεξορυχθέντα Bitcoin Blocks. diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 3e99e09edf..71e1480f15 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -111,6 +111,8 @@ Punto del canal LOS CANALES CIERRAN EL %1$s Tus canales Lightning se cerrarán el %1$s. Transfiere tu saldo de gasto a ahorros antes de esa fecha. + Bitkit ya no ofrece canales Lightning nuevos, así que no puedes transferir fondos de ahorro a gasto. + Canales nuevos no disponibles Cerrar Terminar conexión Transferencia fallida @@ -1096,6 +1098,7 @@ La transacción quedó sin confirmar debido a una reorganización de la blockchain Transacción sin confirmar Transferir a ahorros + Transferir a gasto Tu retiro no tuvo éxito. Por favor, escanea el código QR de nuevo o contacta con soporte. Añadir Widget Examina varias estadísticas de los bloques de Bitcoin recién minados. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 09d8696e74..3ca3d64b16 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -108,6 +108,8 @@ Punto de canal LOS CANALES CIERRAN EL %1$s Tus canales Lightning se cerrarán el %1$s. Transfiere tu saldo de gasto a ahorros antes de esa fecha. + Bitkit ya no ofrece canales Lightning nuevos, así que no puedes transferir fondos de ahorro a gasto. + Canales nuevos no disponibles Transferencia Fallida No se pueden transferir los fondos de vuelta a ahorros. Por favor, inténtalo de nuevo. Tus fondos se están transfiriendo de vuelta a tus ahorros. @@ -1085,6 +1087,7 @@ Los fondos que retires se depositarán en tu saldo de gastos de Bitkit. Retirar Bitcoin Transferir a Ahorros + Transferir a Gastos Tu retiro no tuvo éxito. Por favor, escanea el código QR de nuevo o contacta con soporte. Dirección de envío de bitcoin inválida Error al actualizar factura diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 95b8b1a85e..3800a9bba8 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -100,6 +100,8 @@ Dépend des frais LES CANAUX FERMENT LE %1$s Vos canaux Lightning seront fermés le %1$s. Transférez votre solde de dépenses vers l\'Épargne avant cette date. + Bitkit ne propose plus de nouveaux canaux Lightning, vous ne pouvez donc pas transférer de fonds de l\'Épargne vers les Dépenses. + Nouveaux canaux indisponibles Dépenses \n<accent>Solde</accent> Alimentez votre solde de dépenses pour profiter de transactions instantanées et bon marché avec vos amis, votre famille et les commerçants. Commencer @@ -965,6 +967,7 @@ La transaction n\'est plus confirmée suite à une réorganisation de la blockchain Transaction non confirmée Transférer vers l\'Épargne + Transférer vers les Dépenses Votre retrait a échoué. Veuillez scanner le QR code à nouveau ou contacter le support. Sélection des UTXOs Total requis diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index b613f512bc..053bade96d 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -110,6 +110,8 @@ Punto del canale I CANALI CHIUDONO IL %1$s I tuoi canali Lightning verranno chiusi il %1$s. Trasferisci il saldo di spesa ai risparmi prima di allora. + Bitkit non offre più nuovi canali Lightning, quindi non puoi trasferire fondi dai risparmi alla spesa. + Nuovi canali non disponibili Chiudi Chiusura Connessione Trasferimento non riuscito @@ -1099,6 +1101,7 @@ La transazione non è più confermata a causa di una riorganizzazione della blockchain Transazione Non Confermata Trasferisci ai Risparmi + Trasferisci alle Spese Il tuo prelievo non e\' riuscito. Scansiona nuovamente il codice QR o contatta l\'assistenza. Aggiungi Widget Esamina varie statistiche sui blocchi Bitcoin appena estratti. diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 33b1492a76..e8aeaf4e26 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -110,6 +110,8 @@ Kanaalpunt KANALEN SLUITEN OP %1$s Je Lightning-kanalen worden op %1$s gesloten. Boek je bestedingssaldo vóór die datum over naar spaargeld. + Bitkit biedt geen nieuwe Lightning-kanalen meer aan, dus je kunt geen geld van spaargeld naar je bestedingssaldo overboeken. + Nieuwe kanalen niet beschikbaar Sluiten Verbinding sluiten Overboeking mislukt @@ -1094,6 +1096,7 @@ Transactie is niet meer bevestigd vanwege blockchain-reorganisatie Transactie niet bevestigd Overboeken naar spaargeld + Overboeken naar bestedingssaldo Je opname is niet gelukt. Scan de QR-code opnieuw of neem contact op met support. Widget toevoegen Bekijk verschillende statistieken over nieuw gedolven Bitcoin-blokken. diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 9f7d3b04a4..b5b54c20d9 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -102,6 +102,8 @@ Zależy od opłaty KANAŁY ZOSTANĄ ZAMKNIĘTE %1$s Twoje kanały Lightning zostaną zamknięte %1$s. Przed tym terminem prześlij saldo wydatków na oszczędności. + Bitkit nie oferuje już nowych kanałów Lightning, więc nie możesz przesyłać środków z oszczędności na wydatki. + Nowe kanały niedostępne Saldo \n<accent>Wydatków</accent> Zasil swoje saldo wydatków, aby cieszyć się natychmiastowymi i tanimi transakcjami z przyjaciółmi, rodziną i sprzedawcami. Rozpocznij @@ -970,6 +972,7 @@ Transakcja stała się niepotwierdzona z powodu reorganizacji łańcucha bloków Transakcja niepotwierdzona Prześlij na oszczędności + Prześlij na saldo wydatków Twoja wypłata nie powiodła się. Proszę ponownie zeskanować kod QR lub skontaktować się z pomocą techniczną. Wybór monet /Coin Selection/ Wymagana kwota diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index df3111a8f0..45ff6d2c2e 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -112,6 +112,8 @@ Ponto do canal CANAIS FECHAM EM %1$s Seus canais Lightning serão fechados em %1$s. Transfira seu saldo de gastos para a poupança antes dessa data. + O Bitkit não oferece mais novos canais Lightning, então você não pode transferir fundos da poupança para gastos. + Novos canais indisponíveis Fechar Fechar Conexão A Transferência Falhou @@ -1104,6 +1106,7 @@ Transação ficou não confirmada devido a reorganização da blockchain Transação Não Confirmada Transferir Para Poupança + Transferir Para Gastos Seu saque não foi bem-sucedido. Por favor, escaneie o QR code novamente ou entre em contato com o suporte. Adicionar Widget Examinar várias estatísticas sobre blocos de Bitcoin recém-minerados. diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 82fd6ae033..7ca00b8a72 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -97,6 +97,8 @@ Depende da taxa CANAIS FECHAM A %1$s Os seus canais Lightning serão fechados a %1$s. Transfira o seu saldo de gastos para a poupança antes dessa data. + O Bitkit já não oferece novos canais Lightning, por isso não pode transferir fundos da poupança para gastos. + Novos canais indisponíveis Saldo de\n<accent>Gastos</accent> Use seu saldo de gastos para desfrutar de transações instantâneas e baratas com amigos, familiares e comerciantes. Começar @@ -938,6 +940,7 @@ Erro ao atualizar invoice Erro ao buscar invoice lnurl Par desconectado. + Transferir Para Gastos Seu saque não foi bem-sucedido. Por favor, escaneie o código QR novamente ou entre em contato com o suporte. Pagar com Bitcoin Valor muito baixo diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index ea8699fd88..45e4f8e8ca 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -120,6 +120,8 @@ Точка Канала КАНАЛЫ ЗАКРОЮТСЯ %1$s Ваши каналы Lightning будут закрыты %1$s. До этой даты переведите баланс расходов в сбережения. + Bitkit больше не открывает новые каналы Lightning, поэтому перевести средства из сбережений в расходы нельзя. + Новые каналы недоступны Закрыть Закрыть Соединение Перевод Не Удался @@ -1131,6 +1133,7 @@ Bitkit должен увеличить приёмную ёмкость ваше Транзакция стала неподтверждённой из-за реорганизации блокчейна Транзакция не подтверждена Перевести в сбережения + Перевести на расходы Вывод не удался. Пожалуйста, отсканируйте QR-код снова или обратитесь в поддержку. Добавить Виджет Изучайте различную статистику по недавно добытым блокам Bitcoin. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index aed1b6bae0..6ce6b493ba 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -254,6 +254,8 @@ Channel point CHANNELS CLOSE ON %1$s Your Lightning channels will be closed on %1$s. Move your spending balance to savings before then. + Bitkit no longer offers new Lightning channels, so you can\'t move funds from savings to spending. + New Channels Unavailable Close Close Connection Transfer Failed @@ -1656,6 +1658,7 @@ Transaction became unconfirmed due to blockchain reorganization Transaction Unconfirmed Transfer To Savings + Transfer To Spending Your withdrawal was unsuccessful. Please scan the QR code again or contact support. Active accounts Copy xpub diff --git a/journeys/README.md b/journeys/README.md index 94e7cc5773..82c86efce2 100644 --- a/journeys/README.md +++ b/journeys/README.md @@ -110,7 +110,7 @@ exit status, so an empty response there is not a failure. Give the wallet ~20s t ### Getting a Lightning channel Blocktank is winding down, and this build no longer buys channels from it (synonymdev/bitkit-android#1669): -Transfer To Spending, the Receive sheet's CJIT button and gift codes are gone. A journey that needs a +the Transfer To Spending buttons now open a "New Channels Unavailable" sheet instead of the transfer flow, and the Receive sheet's CJIT button and gift codes are gone. A journey that needs a Lightning channel as its precondition says "a wallet with an open Lightning channel"; get one in either way: - Install a build from before that change (master before synonymdev/bitkit-android#1669), open a channel with @@ -163,7 +163,7 @@ buys a channel went with them. They are listed here rather than left as stale fi | --- | --- | | `amount-limits/transfer-spending-over-max.xml`, `transfer-spending-advanced-over-max.xml`, `transfer-spending-preset-delete.xml` | Transfer To Spending amount and Receiving Capacity number pads | | `cjit-notifications/cjit-foreground-service-notification.xml`, `cjit-push-single-notification.xml` | CJIT channel-ready notifications from the foreground service and from an FCM push | -| `hardware-wallet/transfer-to-spending.xml`, `transfer-to-spending-max-lsp-cap.xml`, `transfer-to-spending-node-warmup.xml`, `passphrase-transfer-to-spending.xml` | Hardware wallet Transfer To Spending (the `HardwareTransferToSpending` button is gone) | +| `hardware-wallet/transfer-to-spending.xml`, `transfer-to-spending-max-lsp-cap.xml`, `transfer-to-spending-node-warmup.xml`, `passphrase-transfer-to-spending.xml` | Hardware wallet Transfer To Spending (the `HardwareTransferToSpending` button now opens the New Channels Unavailable sheet instead) | | `notification-permission/receive-cjit-confirm-notification-toggle.xml`, `receive-cjit-liquidity-notification-toggle.xml`, `transfer-spending-confirm-notification-toggle.xml`, `toggle-off-opens-system-settings.xml` | The "Set up in background" toggle on the CJIT and Spending Confirm screens, which nothing navigates to now | | `transfer/spending-confirm-amount-change.xml` | Spending Confirm amount rebuilt at swipe | | `transfers/closed-channel-transfer-settles.xml` | Pending Transfer To Spending banner after the LSP closes the channel | @@ -287,6 +287,9 @@ and Settings (`Tab-general`, `Tab-security`, `Tab-advanced`, `NavigationBack`, ` | Home suggestion cards | `Suggestion-` | — *(cards expose no identifier)* | | Channels closing banner on Home | `ChannelsClosingBanner` | `ChannelsClosingBanner` *(to match when synonymdev/bitkit-ios#1097 lands)* | | Channels closing notice on the Spending wallet | `ChannelsClosingNotice` | `ChannelsClosingNotice` *(to match when synonymdev/bitkit-ios#1097 lands)* | +| New Channels Unavailable sheet | `ChannelsUnavailableSheet` | `ChannelsUnavailableSheet` *(to match when synonymdev/bitkit-ios#1097 lands)* | +| Channel close date line in that sheet | `ChannelsUnavailableCloseDate` | `ChannelsUnavailableCloseDate` *(to match when synonymdev/bitkit-ios#1097 lands)* | +| OK button in that sheet | `ChannelsUnavailableButton` | `ChannelsUnavailableButton` *(to match when synonymdev/bitkit-ios#1097 lands)* | | Receive QR copy button | `ReceiveCopyQR` | `ReceiveCopyQR` *(absent from `snapshot-ui` targets; see below)* | | Payment Request row | `PaymentRequestRow-` | `PaymentRequestRow--` *(`-one-time` for a one-off)* | | Payment Request detail screen | `PaymentRequestDetailsScreen` | `PaymentRequestDetailScreen` | diff --git a/journeys/amount-limits/README.md b/journeys/amount-limits/README.md index 9da1006301..2e6d925a4d 100644 --- a/journeys/amount-limits/README.md +++ b/journeys/amount-limits/README.md @@ -54,5 +54,5 @@ no button navigates to them. `transfer-spending-over-max.xml`, `transfer-spending-advanced-over-max.xml` and `transfer-spending-preset-delete.xml` were retired because Blocktank channel purchases were removed (synonymdev/bitkit-android#1669). Their route started at the Savings wallet's "Transfer To Spending" -button, which is gone. Their tags (`TransferToSpending`, `SpendingAmount*`, `SpendingAdvanced*`) are no +button, which now opens the New Channels Unavailable sheet instead of the transfer flow. Their tags (`TransferToSpending`, `SpendingAmount*`, `SpendingAdvanced*`) are no longer reachable from the UI. diff --git a/journeys/channels-closing/README.md b/journeys/channels-closing/README.md index 6429c2e763..988af82943 100644 --- a/journeys/channels-closing/README.md +++ b/journeys/channels-closing/README.md @@ -15,9 +15,13 @@ The iOS twin is synonymdev/bitkit-ios#1097. same Blocktank-channel condition. - The date is a placeholder constant (Dec 15, 2026) until the shutdown date is final. Journeys assert the banner and the notice agree and read the date from the screen instead of hardcoding it. -- **Removed:** Savings "Transfer To Spending" (`TransferToSpending`), Spending "Transfer from Savings" - (`TransferFromSavings`), hardware wallet "Transfer To Spending" (`HardwareTransferToSpending`), the - Home "Spend" suggestion card (`Suggestion-lightning`), the Receive sheet's CJIT button and gift codes. +- **Unavailable sheet:** Savings "Transfer To Spending" (`TransferToSpending`), Spending "Transfer from + Savings" (`TransferFromSavings`) and hardware wallet "Transfer To Spending" (`HardwareTransferToSpending`) + stay, but open the "New Channels Unavailable" sheet (`ChannelsUnavailableSheet`) instead of the transfer + flow. For a wallet with a Blocktank channel it also shows the close date (`ChannelsUnavailableCloseDate`); + the OK button (`ChannelsUnavailableButton`) closes it and leaves the user on the same screen. +- **Removed:** the Home "Spend" suggestion card (`Suggestion-lightning`), the Receive sheet's CJIT button + and gift codes. Transfer To Savings, send, receive over an existing channel, LNURL channel and manual external node stay. @@ -32,10 +36,11 @@ The iOS twin is synonymdev/bitkit-ios#1097. | Journey | Covers | | --- | --- | -| `channels-closing-notice.xml` | Banner on Home → tap → Spending wallet notice and Transfer To Savings; Savings wallet has no Transfer To Spending | +| `channels-closing-notice.xml` | Banner on Home → tap → Spending wallet notice and Transfer To Savings; Savings Transfer To Spending opens the New Channels Unavailable sheet with the close date | | [`../receive/receive-onchain-only-without-channel.xml`](../receive/receive-onchain-only-without-channel.xml) | The opposite precondition: a funded wallet with no channels shows no banner, no Spend card and a Savings-only Receive sheet | ## Test tags `ChannelsClosingBanner`, `ChannelsClosingNotice`, `ActivitySavings`, `ActivitySpending`, `TotalBalance`, -`TransferToSavings`, `TransferToSpending` (asserted absent). +`TransferToSavings`, `TransferToSpending`, `ChannelsUnavailableSheet`, `ChannelsUnavailableCloseDate`, +`ChannelsUnavailableButton`. diff --git a/journeys/channels-closing/channels-closing-notice.xml b/journeys/channels-closing/channels-closing-notice.xml index 1dab6a07ac..38b9b9f35e 100644 --- a/journeys/channels-closing/channels-closing-notice.xml +++ b/journeys/channels-closing/channels-closing-notice.xml @@ -5,9 +5,10 @@ "CHANNELS CLOSE ON DEC 15, 2026" (testTag "ChannelsClosingBanner"), tapping it opens the Spending wallet, which shows "Your Lightning channels will be closed on Dec 15, 2026. Move your spending balance to savings before then." (testTag "ChannelsClosingNotice") under the balance header, and - Transfer To Savings (testTag "TransferToSavings") is still offered. The Savings wallet has no - Transfer To Spending button (testTag "TransferToSpending"), since new channels can no longer be - bought. The date is a placeholder constant until the shutdown date is final; assert whatever + Transfer To Savings (testTag "TransferToSavings") is still offered. The Savings wallet's + Transfer To Spending button (testTag "TransferToSpending") opens the "New Channels Unavailable" + sheet (testTag "ChannelsUnavailableSheet") instead of the transfer flow, since new channels can no + longer be bought; the sheet shows the same close date as the banner. The date is a placeholder constant until the shutdown date is final; assert whatever date the build shows, and keep the banner and the notice in agreement. Precondition: onboarded dev wallet with a Lightning channel to the staging LSP (Blocktank) node, open @@ -25,7 +26,9 @@ Verify the notice (testTag "ChannelsClosingNotice") reads "Your Lightning channels will be closed on Dec 15, 2026. Move your spending balance to savings before then." with the same date the banner showed Verify the "Transfer To Savings" button (testTag "TransferToSavings") is visible Press the system back button, then tap the Savings balance card (testTag "ActivitySavings") - Verify the Savings wallet screen is open and no "Transfer To Spending" button (testTag "TransferToSpending") is shown + Verify the Savings wallet screen is open and tap the "Transfer To Spending" button (testTag "TransferToSpending") + Verify the sheet (testTag "ChannelsUnavailableSheet") opens with the title "New Channels Unavailable" and the text "Bitkit no longer offers new Lightning channels, so you can't move funds from savings to spending.", and that the line (testTag "ChannelsUnavailableCloseDate") reads "Your Lightning channels will be closed on Dec 15, 2026. Move your spending balance to savings before then." with the same date the banner showed + Tap "OK" (testTag "ChannelsUnavailableButton") and verify the sheet closes and the Savings wallet screen is still shown Press the system back button, tap the Spending balance card (testTag "ActivitySpending") and verify the notice (testTag "ChannelsClosingNotice") is still shown diff --git a/journeys/cjit-notifications/README.md b/journeys/cjit-notifications/README.md index 8e641b0cc7..4ad793dd41 100644 --- a/journeys/cjit-notifications/README.md +++ b/journeys/cjit-notifications/README.md @@ -27,7 +27,7 @@ is no way to drive the push. 1. **Onboarded dev (regtest) wallet** with the node connected to the staging/regtest LSP and a positive on-chain Savings balance. -2. **Open a channel without CJIT.** This build has no Transfer To Spending, so open a manual channel to +2. **Open a channel without CJIT.** Transfer To Spending on this build only opens a New Channels Unavailable sheet, so open a manual channel to the staging LSP node: Settings › Advanced › Lightning Connections › add connection › Manual Setup. See [Getting a Lightning channel](../README.md#getting-a-lightning-channel). diff --git a/journeys/hardware-wallet/README.md b/journeys/hardware-wallet/README.md index 87ff4f46d9..c47643fc98 100644 --- a/journeys/hardware-wallet/README.md +++ b/journeys/hardware-wallet/README.md @@ -75,7 +75,7 @@ the other two rely on, and `passphrase-settings-remove.xml` removes it again. | `suggestion-intro-sheet.xml` | Forget device, Hardware suggestion card, full connect flow (Intro → Searching → Found → Paired → Finish) re-pairs | | `connect-flow.xml` | Settings Add button → connect flow with an edited Label Funds → paired device count + name | | `settings-hardware-wallets.xml` | Payments count row, Hardware Wallets screen list, rename sheet, Add button sheet/back dismiss, per-row delete confirm + re-pair | -| `detail-overview.xml` | Detail screen overview, no Transfer To Spending button, activity, Remove confirm + forget | +| `detail-overview.xml` | Detail screen overview, Transfer To Spending opens the New Channels Unavailable sheet, activity, Remove confirm + forget | | `send-onchain.xml` | Normal Send flow funded by Trezor: source selection, guarded preparation, device signing, broadcast, success, and activity | | `receive-onchain.xml` | Trezor Receive tab: current address and QR display plus exact on-device address verification | | `passphrase-pairing.xml` | Passphrase button on Paired → Enter Passphrase → Passphrase Funds Found; second home tile, own label, no passphrase in logs | @@ -135,7 +135,8 @@ synonymdev/bitkit-android#1669. `transfer-to-spending.xml`, `transfer-to-spending-max-lsp-cap.xml`, `transfer-to-spending-node-warmup.xml` and `passphrase-transfer-to-spending.xml` were retired because Blocktank channel purchases were removed (synonymdev/bitkit-android#1669): the hardware wallet detail -screen no longer has a Transfer To Spending button (`HardwareTransferToSpending`). The +screen's Transfer To Spending button (`HardwareTransferToSpending`) now opens the New Channels Unavailable +sheet instead of the transfer flow. The `HardwareTransfer*` and `HwTransferPassphrase*` screens and tags are still in the nav graph but nothing in the UI reaches them. diff --git a/journeys/hardware-wallet/detail-overview.xml b/journeys/hardware-wallet/detail-overview.xml index e4425d8428..75471fc6c2 100644 --- a/journeys/hardware-wallet/detail-overview.xml +++ b/journeys/hardware-wallet/detail-overview.xml @@ -1,8 +1,8 @@ Opens the hardware wallet detail screen from the home tile and verifies its overview: - the top bar, balance header, the absence of a Transfer To Spending entry (removed with - Blocktank channel purchases, synonymdev/bitkit-android#1669), the grouped activity list, and the Remove device confirm dialog. The final + the top bar, balance header, the Transfer To Spending entry (which opens the "New Channels Unavailable" + sheet since Blocktank channel purchases were removed, synonymdev/bitkit-android#1669), the grouped activity list, and the Remove device confirm dialog. The final Remove step forgets the device, so run this last (re-run connect-home-tile.xml to pair again). Requires a paired Bridge emulator (run connect-home-tile.xml first). @@ -17,7 +17,7 @@ Verify the hardware wallet detail screen opens (testTag "HardwareWalletScreen"), showing the device name with a blue bitcoin icon in the top bar and a balance header - Verify no "Transfer To Spending" button (testTag "HardwareTransferToSpending") is shown, whether or not the wallet holds funds + If the wallet holds funds, verify the "Transfer To Spending" button (testTag "HardwareTransferToSpending") is shown, tap it, verify the sheet (testTag "ChannelsUnavailableSheet") opens with the title "New Channels Unavailable" and the text "Bitkit no longer offers new Lightning channels, so you can't move funds from savings to spending.", then tap "OK" (testTag "ChannelsUnavailableButton") and verify the hardware wallet detail screen is still shown If the activity list shows transactions, verify their circular icons are blue, then tap the first one, verify an activity detail screen opens, and navigate back diff --git a/journeys/receive/receive-onchain-only-without-channel.xml b/journeys/receive/receive-onchain-only-without-channel.xml index 6cbff0036a..66285e7801 100644 --- a/journeys/receive/receive-onchain-only-without-channel.xml +++ b/journeys/receive/receive-onchain-only-without-channel.xml @@ -4,6 +4,8 @@ offers the Savings tab and nothing that buys Lightning liquidity, and Home shows neither the Spend suggestion card nor the channels-closing banner. Blocktank channel purchases were removed in synonymdev/bitkit-android#1669: there is no "Receive Lightning funds" (CJIT) button or onboarding, + and the Transfer To Spending and Transfer from Savings buttons open the "New Channels Unavailable" + sheet, and the Spending and Auto tabs appear only when the wallet can create a Lightning invoice over a ready channel with inbound liquidity. @@ -22,6 +24,9 @@ Verify there is no "Receive Lightning funds" button anywhere on the sheet Tap "Edit" (testTag "SpecifyInvoiceButton"), tap the amount field (testTag "ReceiveNumberPadTextField"), enter a small amount (for example "N1" then "N0" twice), tap Continue (testTag "ReceiveNumberPadSubmit") and then "QR Code" (testTag "ShowQrReceive") Verify the sheet returns to the QR screen with no liquidity or channel confirm screen in between, the Savings tab is still selected and there is still no Spending tab (testTag "Tab-spending") - Close the sheet, open the Savings wallet (testTag "ActivitySavings") and verify no "Transfer To Spending" button (testTag "TransferToSpending") is shown, then open the Spending wallet (testTag "ActivitySpending") and verify no "Transfer from Savings" button (testTag "TransferFromSavings") and no notice (testTag "ChannelsClosingNotice") is shown + Close the sheet, open the Savings wallet (testTag "ActivitySavings") and tap the "Transfer To Spending" button (testTag "TransferToSpending") + Verify the sheet (testTag "ChannelsUnavailableSheet") shows the title "New Channels Unavailable" and the text "Bitkit no longer offers new Lightning channels, so you can't move funds from savings to spending.", and no close date line (testTag "ChannelsUnavailableCloseDate"); tap "OK" (testTag "ChannelsUnavailableButton") and verify the Savings wallet screen is still shown + Go back, open the Spending wallet (testTag "ActivitySpending"), verify no notice (testTag "ChannelsClosingNotice") is shown and tap the "Transfer from Savings" button (testTag "TransferFromSavings") + Verify the same sheet (testTag "ChannelsUnavailableSheet") opens with no close date line (testTag "ChannelsUnavailableCloseDate"), tap "OK" (testTag "ChannelsUnavailableButton") and verify the Spending wallet screen is still shown From 7d11c536446cb4242f0aa2d54608e924433067dc Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 22 Oct 2026 13:29:22 +0200 Subject: [PATCH 4/8] feat: offer manual channel and third-party lsps on transfer --- .../java/to/bitkit/models/ChannelsClosing.kt | 13 ++ app/src/main/java/to/bitkit/ui/ContentView.kt | 15 ++ .../ui/screens/transfer/ChooseLspScreen.kt | 134 ++++++++++++++++++ .../ui/sheets/ChannelsUnavailableSheet.kt | 32 ++++- app/src/main/res/values-ar/strings.xml | 8 +- app/src/main/res/values-b+es+419/strings.xml | 8 +- app/src/main/res/values-ca/strings.xml | 8 +- app/src/main/res/values-cs/strings.xml | 8 +- app/src/main/res/values-de/strings.xml | 8 +- app/src/main/res/values-el/strings.xml | 8 +- app/src/main/res/values-es-rES/strings.xml | 8 +- app/src/main/res/values-es/strings.xml | 8 +- app/src/main/res/values-fr/strings.xml | 8 +- app/src/main/res/values-it/strings.xml | 8 +- app/src/main/res/values-nl/strings.xml | 8 +- app/src/main/res/values-pl/strings.xml | 8 +- app/src/main/res/values-pt-rBR/strings.xml | 8 +- app/src/main/res/values-pt/strings.xml | 8 +- app/src/main/res/values-ru/strings.xml | 8 +- app/src/main/res/values/strings.xml | 8 +- docs/screens-map.md | 1 + journeys/README.md | 13 +- journeys/amount-limits/README.md | 2 +- journeys/channels-closing/README.md | 10 +- .../channels-closing-notice.xml | 6 +- .../choose-lsp-from-transfer.xml | 25 ++++ journeys/cjit-notifications/README.md | 2 +- journeys/hardware-wallet/README.md | 4 +- journeys/hardware-wallet/detail-overview.xml | 4 +- .../receive-onchain-only-without-channel.xml | 6 +- 30 files changed, 336 insertions(+), 59 deletions(-) create mode 100644 app/src/main/java/to/bitkit/ui/screens/transfer/ChooseLspScreen.kt create mode 100644 journeys/channels-closing/choose-lsp-from-transfer.xml diff --git a/app/src/main/java/to/bitkit/models/ChannelsClosing.kt b/app/src/main/java/to/bitkit/models/ChannelsClosing.kt index bdb089aac7..c155507960 100644 --- a/app/src/main/java/to/bitkit/models/ChannelsClosing.kt +++ b/app/src/main/java/to/bitkit/models/ChannelsClosing.kt @@ -8,5 +8,18 @@ import java.util.Locale /** Date on which Blocktank closes the remaining Lightning channels; set before release. */ val CHANNELS_CLOSE_DATE: LocalDate = LocalDate.of(2026, 12, 15) +/** Third-party Lightning service providers whose sites sell a channel the user claims by scanning a QR. */ +val THIRD_PARTY_LSPS: List = listOf( + ThirdPartyLsp(name = "LNBIG", url = "https://lnbig.com/#/open-channel"), + ThirdPartyLsp(name = "ZEUS Olympus", url = "https://channels.zeuslsp.com/"), + ThirdPartyLsp(name = "Megalithic", url = "https://megalithic.me/get_a_channel"), + ThirdPartyLsp(name = "Amboss Magma", url = "https://amboss.space/magma"), +) + +data class ThirdPartyLsp( + val name: String, + val url: String, +) + fun formatChannelsCloseDate(locale: Locale = Locale.getDefault()): String = CHANNELS_CLOSE_DATE.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(locale)) diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt index 6bd7fdf8a0..d3801949de 100644 --- a/app/src/main/java/to/bitkit/ui/ContentView.kt +++ b/app/src/main/java/to/bitkit/ui/ContentView.kt @@ -132,6 +132,7 @@ import to.bitkit.ui.screens.subscriptions.CreateSubscriptionSheet import to.bitkit.ui.screens.subscriptions.SubscriptionDetailScreen import to.bitkit.ui.screens.subscriptions.SubscriptionSheet import to.bitkit.ui.screens.subscriptions.SubscriptionsScreen +import to.bitkit.ui.screens.transfer.ChooseLspScreen import to.bitkit.ui.screens.transfer.FundingAdvancedScreen import to.bitkit.ui.screens.transfer.FundingScreen import to.bitkit.ui.screens.transfer.LiquidityScreen @@ -661,6 +662,10 @@ fun ContentView( .collectAsStateWithLifecycle(initialValue = false) ChannelsUnavailableSheet( showCloseDate = hasTrustedChannels, + onOpenChannel = { + appViewModel.hideSheet() + navController.navigateTo(Routes.ChooseLsp) + }, onDismiss = { appViewModel.hideSheet() }, ) } @@ -1162,6 +1167,13 @@ private fun RootNavHost( isGeoBlocked = isGeoBlocked, ) } + deepLinkableComposable { + ChooseLspScreen( + onManualClick = { navController.navigateTo(Routes.ExternalNav) }, + onScanClick = { appViewModel.showScannerSheet() }, + onBackClick = { navController.popBackStack() }, + ) + } deepLinkableComposable { FundingAdvancedScreen( onLnurl = { appViewModel.showScannerSheet() }, @@ -2478,6 +2490,9 @@ sealed interface Routes { @Serializable data object FundingAdvanced : Routes.DeepLinkable + @Serializable + data object ChooseLsp : Routes.DeepLinkable + @Serializable data object ExternalNav : Routes.DeepLinkable diff --git a/app/src/main/java/to/bitkit/ui/screens/transfer/ChooseLspScreen.kt b/app/src/main/java/to/bitkit/ui/screens/transfer/ChooseLspScreen.kt new file mode 100644 index 0000000000..8e7b6634b4 --- /dev/null +++ b/app/src/main/java/to/bitkit/ui/screens/transfer/ChooseLspScreen.kt @@ -0,0 +1,134 @@ +package to.bitkit.ui.screens.transfer + +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.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalUriHandler +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 kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import to.bitkit.R +import to.bitkit.models.THIRD_PARTY_LSPS +import to.bitkit.models.ThirdPartyLsp +import to.bitkit.ui.components.BodyM +import to.bitkit.ui.components.BodyS +import to.bitkit.ui.components.Display +import to.bitkit.ui.components.PrimaryButton +import to.bitkit.ui.components.SecondaryButton +import to.bitkit.ui.components.VerticalSpacer +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.theme.AppThemeSurface +import to.bitkit.ui.theme.Colors +import to.bitkit.ui.utils.withAccent + +@Composable +fun ChooseLspScreen( + onManualClick: () -> Unit, + onScanClick: () -> Unit, + onBackClick: () -> Unit, +) { + val uriHandler = LocalUriHandler.current + Content( + lsps = THIRD_PARTY_LSPS.toImmutableList(), + onLspClick = { uriHandler.openUri(it.url) }, + onManualClick = onManualClick, + onScanClick = onScanClick, + onBackClick = onBackClick, + ) +} + +@Composable +private fun Content( + lsps: ImmutableList, + modifier: Modifier = Modifier, + onLspClick: (ThirdPartyLsp) -> Unit = {}, + onManualClick: () -> Unit = {}, + onScanClick: () -> Unit = {}, + onBackClick: () -> Unit = {}, +) { + ScreenColumn(modifier = modifier) { + AppTopBar( + titleText = stringResource(R.string.lightning__funding_advanced__nav_title), + onBackClick = onBackClick, + actions = { DrawerNavIcon() }, + ) + Column( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .verticalScroll(rememberScrollState()) + .padding(horizontal = 16.dp) + ) { + VerticalSpacer(16.dp) + Display( + text = stringResource(R.string.lightning__choose_lsp__title).withAccent(accentColor = Colors.Purple), + ) + VerticalSpacer(8.dp) + BodyM(text = stringResource(R.string.lightning__choose_lsp__text), color = Colors.White64) + VerticalSpacer(67.dp) + lsps.forEach { lsp -> + SettingsButtonRow( + title = lsp.name, + onClick = { onLspClick(lsp) }, + modifier = Modifier.testTag("ChooseLspProvider") + ) + } + VerticalSpacer(67.dp) + BodyS(text = stringResource(R.string.lightning__choose_lsp__explanation), color = Colors.White64) + VerticalSpacer(16.dp) + } + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + ) { + SecondaryButton( + text = stringResource(R.string.lightning__funding_advanced__button2), + onClick = onManualClick, + modifier = Modifier + .weight(1f) + .testTag("ChooseLspManual") + ) + PrimaryButton( + text = stringResource(R.string.wallet__receive_show_qr), + icon = { + Icon( + painter = painterResource(R.drawable.ic_scan), + contentDescription = null, + tint = Colors.White, + modifier = Modifier.size(16.dp) + ) + }, + onClick = onScanClick, + modifier = Modifier + .weight(1f) + .testTag("ChooseLspScan") + ) + } + VerticalSpacer(16.dp) + } +} + +@Preview(showSystemUi = true) +@Composable +private fun Preview() { + AppThemeSurface { + Content(lsps = THIRD_PARTY_LSPS.toImmutableList()) + } +} diff --git a/app/src/main/java/to/bitkit/ui/sheets/ChannelsUnavailableSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/ChannelsUnavailableSheet.kt index 5be1b8cf57..b16ffc470d 100644 --- a/app/src/main/java/to/bitkit/ui/sheets/ChannelsUnavailableSheet.kt +++ b/app/src/main/java/to/bitkit/ui/sheets/ChannelsUnavailableSheet.kt @@ -1,8 +1,10 @@ package to.bitkit.ui.sheets 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.fillMaxWidth import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding @@ -23,6 +25,7 @@ import to.bitkit.models.formatChannelsCloseDate import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.BottomSheetPreview import to.bitkit.ui.components.PrimaryButton +import to.bitkit.ui.components.SecondaryButton import to.bitkit.ui.components.SheetSize import to.bitkit.ui.components.VerticalSpacer import to.bitkit.ui.scaffold.SheetTopBar @@ -34,10 +37,12 @@ import to.bitkit.ui.theme.Colors @Composable fun ChannelsUnavailableSheet( showCloseDate: Boolean, + onOpenChannel: () -> Unit, onDismiss: () -> Unit, ) { Content( showCloseDate = showCloseDate, + onOpenChannel = onOpenChannel, onDismiss = onDismiss, ) } @@ -46,6 +51,7 @@ fun ChannelsUnavailableSheet( private fun Content( showCloseDate: Boolean, modifier: Modifier = Modifier, + onOpenChannel: () -> Unit = {}, onDismiss: () -> Unit = {}, ) { val locale = LocalConfiguration.current.locales[0] @@ -94,13 +100,25 @@ private fun Content( ) } - PrimaryButton( - text = stringResource(R.string.common__ok), - onClick = onDismiss, - modifier = Modifier - .fillMaxWidth() - .testTag("ChannelsUnavailableButton") - ) + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier.fillMaxWidth() + ) { + SecondaryButton( + text = stringResource(R.string.common__cancel), + onClick = onDismiss, + modifier = Modifier + .weight(1f) + .testTag("ChannelsUnavailableButton") + ) + PrimaryButton( + text = stringResource(R.string.lightning__channels_unavailable__button), + onClick = onOpenChannel, + modifier = Modifier + .weight(1f) + .testTag("ChannelsUnavailableOpenChannel") + ) + } VerticalSpacer(16.dp) } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 4177e905fb..73d388d43f 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -106,8 +106,12 @@ نقطة القناة تُغلق القنوات في %1$s ستُغلق قنوات Lightning الخاصة بك في %1$s. انقل رصيد الإنفاق إلى المدخرات قبل ذلك. - لم يعد Bitkit يوفّر قنوات Lightning جديدة، لذا لا يمكنك نقل الأموال من المدخرات إلى الإنفاق. - القنوات الجديدة غير متاحة + فتح قناة + للأسف، لم تعد خدمة التحويل التلقائي للأموال إلى رصيد الإنفاق متاحة. لا يزال بإمكانك فتح قناة Lightning يدويًا لتعبئة رصيد الإنفاق. + لا يوجد تحويل تلقائي + عندما يقدّمون لك رمز QR للاتصال بقناة Lightning أو المطالبة بها، اضغط أدناه لمسح رمز QR أو لصقه. + اطلب قناة من مزوّد خدمة Lightning ‏(LSP). فيما يلي بعض الخيارات. لا يدير Bitkit هذه الخدمات ولا يؤيدها ولا يدعمها. + اختر\n<accent>مزوّد Lightning</accent> إغلاق إغلاق الاتصال فشل التحويل 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 3e7b4b9209..bb8c5180ad 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -111,8 +111,12 @@ Punto del canal LOS CANALES CIERRAN EL %1$s Tus canales Lightning se cerrarán el %1$s. Transfiere tu saldo de gasto a ahorros antes de esa fecha. - Bitkit ya no ofrece canales Lightning nuevos, así que no puedes transferir fondos de ahorros a gasto. - Canales nuevos no disponibles + Abrir canal + Lamentablemente, el servicio para transferir fondos automáticamente a tu saldo de gasto ya no está disponible. Aún puedes abrir manualmente un canal Lightning para recargar tu saldo de gasto. + Sin transferencia automática + Cuando te den un QR para conectar o reclamar el canal Lightning, toca abajo para escanear o pegar el QR. + Pide un canal a un proveedor de servicios Lightning (LSP). Abajo hay algunas opciones. Bitkit no las opera, respalda ni brinda soporte. + Elige un\n<accent>proveedor Lightning</accent> Cerrar Terminar conexión Transferencia fallida diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 5bfd763030..6dcce10df4 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -109,8 +109,12 @@ Punt del canal ELS CANALS ES TANQUEN EL %1$s Els teus canals Lightning es tancaran el %1$s. Transfereix el teu saldo de despesa a estalvis abans d\'aquesta data. - Bitkit ja no ofereix canals Lightning nous, així que no pots transferir fons d\'estalvis a despesa. - Canals nous no disponibles + Obrir canal + Malauradament, el servei per transferir fons automàticament al teu saldo de despesa ja no està disponible. Encara pots obrir manualment un canal Lightning per recarregar el teu saldo de despesa. + Sense transferència automàtica + Quan et donin un QR per connectar o reclamar el canal Lightning, toca a sota per escanejar o enganxar el QR. + Demana un canal a un proveïdor de serveis Lightning (LSP). A sota hi ha algunes opcions. Bitkit no les gestiona, avala ni hi dona suport. + Tria un\n<accent>proveïdor Lightning</accent> Tancar Tanca la connexió Transferència fallida diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 986f4cf374..abf672e34c 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -111,8 +111,12 @@ Bod kanálu KANÁLY SE ZAVŘOU %1$s Vaše Lightning kanály budou uzavřeny %1$s. Do té doby převeďte zůstatek útrat do úspor. - Bitkit už nenabízí nové Lightning kanály, takže nemůžete převádět prostředky z úspor do útrat. - Nové kanály nejsou dostupné + Otevřít kanál + Služba pro automatický převod prostředků na zůstatek útrat bohužel již není k dispozici. Stále můžete ručně otevřít Lightning kanál a doplnit si zůstatek útrat. + Žádný automatický převod + Až vám dají QR kód pro připojení nebo převzetí Lightning kanálu, klepněte níže a QR kód naskenujte nebo vložte. + Objednejte si kanál u poskytovatele služeb Lightning (LSP). Níže je několik možností. Bitkit je neprovozuje, nedoporučuje ani nepodporuje. + Vyberte\n<accent>poskytovatele Lightning</accent> Zavřít Uzavřít spojení Přenos se nezdařil diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 8edc5d20ec..a8747e03da 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -101,8 +101,12 @@ Abhängig von der Gebühr KANÄLE SCHLIESSEN AM %1$s Deine Lightning-Kanäle werden am %1$s geschlossen. Übertrage dein Guthaben vorher auf das Sparkonto. - Bitkit bietet keine neuen Lightning-Kanäle mehr an, daher kannst du kein Guthaben vom Sparkonto auf das Ausgabenkonto übertragen. - Neue Kanäle nicht verfügbar + Kanal öffnen + Leider ist der Dienst, der Guthaben automatisch auf dein Ausgabenkonto überträgt, nicht mehr verfügbar. Du kannst weiterhin manuell einen Lightning-Kanal öffnen, um dein Ausgabenkonto aufzuladen. + Keine automatische Übertragung + Wenn du einen QR-Code zum Verbinden oder Einlösen des Lightning-Kanals erhältst, tippe unten, um ihn zu scannen oder einzufügen. + Bestelle einen Kanal bei einem Lightning Service Provider (LSP). Unten findest du einige Optionen. Bitkit betreibt, empfiehlt oder unterstützt diese nicht. + Wähle einen\n<accent>Lightning-Anbieter</accent> Spending\n<accent>Balance</accent> Fülle dein Guthaben auf, um sofortige und günstige Transaktionen mit Freunden, Familie und Händlern zu genießen. Los geht\'s diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 619e16690f..e473df00f9 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -106,8 +106,12 @@ Σημείο καναλιού ΤΑ ΚΑΝΑΛΙΑ ΚΛΕΙΝΟΥΝ ΣΤΙΣ %1$s Τα κανάλια Lightning σας θα κλείσουν στις %1$s. Μεταφέρετε το υπόλοιπο δαπανών σε αποταμιεύσεις πριν από τότε. - Το Bitkit δεν προσφέρει πλέον νέα κανάλια Lightning, οπότε δεν μπορείτε να μεταφέρετε κεφάλαια από τις αποταμιεύσεις στις δαπάνες. - Νέα κανάλια μη διαθέσιμα + Άνοιγμα καναλιού + Δυστυχώς, η υπηρεσία αυτόματης μεταφοράς κεφαλαίων στο υπόλοιπο δαπανών δεν είναι πλέον διαθέσιμη. Μπορείτε ακόμα να ανοίξετε χειροκίνητα ένα κανάλι Lightning για να αναπληρώσετε το υπόλοιπο δαπανών. + Χωρίς αυτόματη μεταφορά + Όταν σας δώσουν ένα QR για σύνδεση ή διεκδίκηση του καναλιού Lightning, πατήστε παρακάτω για σάρωση ή επικόλληση του QR. + Παραγγείλετε ένα κανάλι από έναν πάροχο υπηρεσιών Lightning (LSP). Παρακάτω υπάρχουν μερικές επιλογές. Το Bitkit δεν τις λειτουργεί, δεν τις προτείνει και δεν τις υποστηρίζει. + Επιλέξτε\n<accent>πάροχο Lightning</accent> Κλείσιμο Κλείσιμο σύνδεσης Αποτυχία μεταφοράς diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 71e1480f15..f264accead 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -111,8 +111,12 @@ Punto del canal LOS CANALES CIERRAN EL %1$s Tus canales Lightning se cerrarán el %1$s. Transfiere tu saldo de gasto a ahorros antes de esa fecha. - Bitkit ya no ofrece canales Lightning nuevos, así que no puedes transferir fondos de ahorro a gasto. - Canales nuevos no disponibles + Abrir canal + Lamentablemente, el servicio para transferir fondos automáticamente a tu saldo de gasto ya no está disponible. Aún puedes abrir manualmente un canal Lightning para recargar tu saldo de gasto. + Sin transferencia automática + Cuando te den un QR para conectar o reclamar el canal Lightning, toca abajo para escanear o pegar el QR. + Pide un canal a un proveedor de servicios Lightning (LSP). Abajo tienes algunas opciones. Bitkit no las gestiona, respalda ni da soporte. + Elige un\n<accent>proveedor Lightning</accent> Cerrar Terminar conexión Transferencia fallida diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 3ca3d64b16..0501552408 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -108,8 +108,12 @@ Punto de canal LOS CANALES CIERRAN EL %1$s Tus canales Lightning se cerrarán el %1$s. Transfiere tu saldo de gasto a ahorros antes de esa fecha. - Bitkit ya no ofrece canales Lightning nuevos, así que no puedes transferir fondos de ahorro a gasto. - Canales nuevos no disponibles + Abrir canal + Lamentablemente, el servicio para transferir fondos automáticamente a tu saldo de gasto ya no está disponible. Aún puedes abrir manualmente un canal Lightning para recargar tu saldo de gasto. + Sin transferencia automática + Cuando te den un QR para conectar o reclamar el canal Lightning, toca abajo para escanear o pegar el QR. + Pide un canal a un proveedor de servicios Lightning (LSP). Abajo tienes algunas opciones. Bitkit no las gestiona, respalda ni da soporte. + Elige un\n<accent>proveedor Lightning</accent> Transferencia Fallida No se pueden transferir los fondos de vuelta a ahorros. Por favor, inténtalo de nuevo. Tus fondos se están transfiriendo de vuelta a tus ahorros. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 3800a9bba8..8d33ed4c48 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -100,8 +100,12 @@ Dépend des frais LES CANAUX FERMENT LE %1$s Vos canaux Lightning seront fermés le %1$s. Transférez votre solde de dépenses vers l\'Épargne avant cette date. - Bitkit ne propose plus de nouveaux canaux Lightning, vous ne pouvez donc pas transférer de fonds de l\'Épargne vers les Dépenses. - Nouveaux canaux indisponibles + Ouvrir un canal + Malheureusement, le service de transfert automatique de fonds vers votre solde de dépenses n\'est plus disponible. Vous pouvez toujours ouvrir manuellement un canal Lightning pour recharger votre solde de dépenses. + Pas de transfert automatique + Quand ils vous donnent un QR pour connecter ou réclamer le canal Lightning, touchez ci-dessous pour scanner ou coller le QR. + Commandez un canal auprès d\'un fournisseur de services Lightning (LSP). Voici quelques options. Bitkit ne les exploite pas, ne les cautionne pas et ne les prend pas en charge. + Choisissez un\n<accent>fournisseur Lightning</accent> Dépenses \n<accent>Solde</accent> Alimentez votre solde de dépenses pour profiter de transactions instantanées et bon marché avec vos amis, votre famille et les commerçants. Commencer diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 053bade96d..85bed5f486 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -110,8 +110,12 @@ Punto del canale I CANALI CHIUDONO IL %1$s I tuoi canali Lightning verranno chiusi il %1$s. Trasferisci il saldo di spesa ai risparmi prima di allora. - Bitkit non offre più nuovi canali Lightning, quindi non puoi trasferire fondi dai risparmi alla spesa. - Nuovi canali non disponibili + Apri canale + Purtroppo il servizio per trasferire automaticamente fondi al saldo di spesa non è più disponibile. Puoi ancora aprire manualmente un canale Lightning per ricaricare il saldo di spesa. + Nessun trasferimento automatico + Quando ti danno un QR per collegare o riscattare il canale Lightning, tocca qui sotto per scansionare o incollare il QR. + Ordina un canale da un fornitore di servizi Lightning (LSP). Qui sotto trovi alcune opzioni. Bitkit non le gestisce, non le approva e non le supporta. + Scegli un\n<accent>fornitore Lightning</accent> Chiudi Chiusura Connessione Trasferimento non riuscito diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index e8aeaf4e26..51a4fa104f 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -110,8 +110,12 @@ Kanaalpunt KANALEN SLUITEN OP %1$s Je Lightning-kanalen worden op %1$s gesloten. Boek je bestedingssaldo vóór die datum over naar spaargeld. - Bitkit biedt geen nieuwe Lightning-kanalen meer aan, dus je kunt geen geld van spaargeld naar je bestedingssaldo overboeken. - Nieuwe kanalen niet beschikbaar + Kanaal openen + Helaas is de dienst om automatisch geld naar je bestedingssaldo over te boeken niet meer beschikbaar. Je kunt nog steeds handmatig een Lightning-kanaal openen om je bestedingssaldo aan te vullen. + Geen automatische overboeking + Als je een QR krijgt om het Lightning-kanaal te verbinden of te claimen, tik dan hieronder om de QR te scannen of te plakken. + Bestel een kanaal bij een Lightning Service Provider (LSP). Hieronder staan enkele opties. Bitkit beheert, onderschrijft of ondersteunt deze niet. + Kies een\n<accent>Lightning-aanbieder</accent> Sluiten Verbinding sluiten Overboeking mislukt diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index b5b54c20d9..0bce1958e3 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -102,8 +102,12 @@ Zależy od opłaty KANAŁY ZOSTANĄ ZAMKNIĘTE %1$s Twoje kanały Lightning zostaną zamknięte %1$s. Przed tym terminem prześlij saldo wydatków na oszczędności. - Bitkit nie oferuje już nowych kanałów Lightning, więc nie możesz przesyłać środków z oszczędności na wydatki. - Nowe kanały niedostępne + Otwórz kanał + Niestety usługa automatycznego przesyłania środków na saldo wydatków nie jest już dostępna. Nadal możesz ręcznie otworzyć kanał Lightning, aby doładować saldo wydatków. + Brak automatycznego przesłania + Gdy otrzymasz kod QR do połączenia lub odebrania kanału Lightning, dotknij poniżej, aby zeskanować lub wkleić kod QR. + Zamów kanał u dostawcy usług Lightning (LSP). Poniżej kilka opcji. Bitkit ich nie prowadzi, nie poleca ani nie wspiera. + Wybierz\n<accent>dostawcę Lightning</accent> Saldo \n<accent>Wydatków</accent> Zasil swoje saldo wydatków, aby cieszyć się natychmiastowymi i tanimi transakcjami z przyjaciółmi, rodziną i sprzedawcami. Rozpocznij diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 45ff6d2c2e..af1831c159 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -112,8 +112,12 @@ Ponto do canal CANAIS FECHAM EM %1$s Seus canais Lightning serão fechados em %1$s. Transfira seu saldo de gastos para a poupança antes dessa data. - O Bitkit não oferece mais novos canais Lightning, então você não pode transferir fundos da poupança para gastos. - Novos canais indisponíveis + Abrir canal + Infelizmente, o serviço de transferência automática de fundos para o seu saldo de gastos não está mais disponível. Você ainda pode abrir manualmente um canal Lightning para recarregar seu saldo de gastos. + Sem transferência automática + Quando derem um QR para conectar ou resgatar o canal Lightning, toque abaixo para escanear ou colar o QR. + Peça um canal a um provedor de serviços Lightning (LSP). Abaixo estão algumas opções. O Bitkit não opera, endossa nem oferece suporte a elas. + Escolha um\n<accent>provedor Lightning</accent> Fechar Fechar Conexão A Transferência Falhou diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 7ca00b8a72..7052626e35 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -97,8 +97,12 @@ Depende da taxa CANAIS FECHAM A %1$s Os seus canais Lightning serão fechados a %1$s. Transfira o seu saldo de gastos para a poupança antes dessa data. - O Bitkit já não oferece novos canais Lightning, por isso não pode transferir fundos da poupança para gastos. - Novos canais indisponíveis + Abrir canal + Infelizmente, o serviço de transferência automática de fundos para o seu saldo de gastos já não está disponível. Ainda pode abrir manualmente um canal Lightning para carregar o seu saldo de gastos. + Sem transferência automática + Quando lhe derem um QR para ligar ou reclamar o canal Lightning, toque abaixo para digitalizar ou colar o QR. + Peça um canal a um fornecedor de serviços Lightning (LSP). Abaixo estão algumas opções. O Bitkit não as gere, recomenda nem suporta. + Escolha um\n<accent>fornecedor Lightning</accent> Saldo de\n<accent>Gastos</accent> Use seu saldo de gastos para desfrutar de transações instantâneas e baratas com amigos, familiares e comerciantes. Começar diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 45e4f8e8ca..3ee6ca9bb6 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -120,8 +120,12 @@ Точка Канала КАНАЛЫ ЗАКРОЮТСЯ %1$s Ваши каналы Lightning будут закрыты %1$s. До этой даты переведите баланс расходов в сбережения. - Bitkit больше не открывает новые каналы Lightning, поэтому перевести средства из сбережений в расходы нельзя. - Новые каналы недоступны + Открыть канал + К сожалению, сервис автоматического перевода средств на баланс расходов больше недоступен. Вы по-прежнему можете вручную открыть канал Lightning, чтобы пополнить баланс расходов. + Нет автоматического перевода + Когда вам дадут QR-код для подключения или получения канала Lightning, нажмите ниже, чтобы отсканировать или вставить QR-код. + Закажите канал у провайдера услуг Lightning (LSP). Ниже несколько вариантов. Bitkit не управляет ими, не рекомендует и не поддерживает их. + Выберите\n<accent>провайдера Lightning</accent> Закрыть Закрыть Соединение Перевод Не Удался diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6ce6b493ba..7f09c53880 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -254,8 +254,12 @@ Channel point CHANNELS CLOSE ON %1$s Your Lightning channels will be closed on %1$s. Move your spending balance to savings before then. - Bitkit no longer offers new Lightning channels, so you can\'t move funds from savings to spending. - New Channels Unavailable + Open Channel + Unfortunately, the service to automatically transfer funds to your spending balance is no longer available. You can still manually open a Lightning channel to top up your spending balance. + No automatic transfer + When they give you a QR to connect or claim the Lightning channel, tap below to scan or paste the QR. + Order a channel from a Lightning Service Provider (LSP). Below are some options. Bitkit does not run, endorse, or support these. + Choose a\n<accent>Lightning provider</accent> Close Close Connection Transfer Failed diff --git a/docs/screens-map.md b/docs/screens-map.md index bfde34c946..b2150d9691 100644 --- a/docs/screens-map.md +++ b/docs/screens-map.md @@ -129,6 +129,7 @@ Frame names are stable across handoff iterations; node ids are not, so the map l | Android | Figma | | - | - | +| ChooseLspScreen.kt | `todo` | | FundingAdvancedScreen.kt | Manual Setup › Lightning Manual Setup | | FundingScreen.kt | Lightning Onboarding From Suggestion Card › Lightning onboarding Fund | | LiquidityScreen.kt | Transfer to Spending Balance From Savings (Channel Opening) › Transfer to Spending Receiving Capacity | diff --git a/journeys/README.md b/journeys/README.md index 82c86efce2..85ff823b01 100644 --- a/journeys/README.md +++ b/journeys/README.md @@ -110,7 +110,7 @@ exit status, so an empty response there is not a failure. Give the wallet ~20s t ### Getting a Lightning channel Blocktank is winding down, and this build no longer buys channels from it (synonymdev/bitkit-android#1669): -the Transfer To Spending buttons now open a "New Channels Unavailable" sheet instead of the transfer flow, and the Receive sheet's CJIT button and gift codes are gone. A journey that needs a +the Transfer To Spending buttons now open a "No automatic transfer" sheet instead of the transfer flow, and the Receive sheet's CJIT button and gift codes are gone. A journey that needs a Lightning channel as its precondition says "a wallet with an open Lightning channel"; get one in either way: - Install a build from before that change (master before synonymdev/bitkit-android#1669), open a channel with @@ -163,7 +163,7 @@ buys a channel went with them. They are listed here rather than left as stale fi | --- | --- | | `amount-limits/transfer-spending-over-max.xml`, `transfer-spending-advanced-over-max.xml`, `transfer-spending-preset-delete.xml` | Transfer To Spending amount and Receiving Capacity number pads | | `cjit-notifications/cjit-foreground-service-notification.xml`, `cjit-push-single-notification.xml` | CJIT channel-ready notifications from the foreground service and from an FCM push | -| `hardware-wallet/transfer-to-spending.xml`, `transfer-to-spending-max-lsp-cap.xml`, `transfer-to-spending-node-warmup.xml`, `passphrase-transfer-to-spending.xml` | Hardware wallet Transfer To Spending (the `HardwareTransferToSpending` button now opens the New Channels Unavailable sheet instead) | +| `hardware-wallet/transfer-to-spending.xml`, `transfer-to-spending-max-lsp-cap.xml`, `transfer-to-spending-node-warmup.xml`, `passphrase-transfer-to-spending.xml` | Hardware wallet Transfer To Spending (the `HardwareTransferToSpending` button now opens the No automatic transfer sheet instead) | | `notification-permission/receive-cjit-confirm-notification-toggle.xml`, `receive-cjit-liquidity-notification-toggle.xml`, `transfer-spending-confirm-notification-toggle.xml`, `toggle-off-opens-system-settings.xml` | The "Set up in background" toggle on the CJIT and Spending Confirm screens, which nothing navigates to now | | `transfer/spending-confirm-amount-change.xml` | Spending Confirm amount rebuilt at swipe | | `transfers/closed-channel-transfer-settles.xml` | Pending Transfer To Spending banner after the LSP closes the channel | @@ -221,6 +221,7 @@ Known differences in the corpus, as of the iOS port (synonymdev/bitkit-ios#691): | `security/pin-lock-on-resume.xml` | not ported yet — iOS already clears the PIN verification on entering the background, so the journey applies; it lands with the iOS side of synonymdev/bitkit-android#1298 | | `security/pin-result-long-label.xml` | not ported — the toggle exists on the iOS security success screen, but the overlap check is a follow-up | | `channels-closing/channels-closing-notice.xml` | port pending in synonymdev/bitkit-ios#1097 — same file, journey name and steps; the iOS views take the `ChannelsClosingBanner` and `ChannelsClosingNotice` identifiers, and the date is a placeholder constant until the shutdown date is final | +| `channels-closing/choose-lsp-from-transfer.xml` | port pending in synonymdev/bitkit-ios#1097 — same file, journey name and steps; the provider list and its URLs must match the Android `THIRD_PARTY_LSPS` | | `receive/receive-onchain-only-without-channel.xml` | port pending in synonymdev/bitkit-ios#1097 — same file, journey name and steps; Android asserts the absence of `Tab-spending` and of the Spend suggestion card (`Suggestion-lightning`) by identifier, iOS cards expose no identifier, so read them from a screenshot | | `tags/activity-tag-length-cap.xml` | not ported — iOS has no 20-character cap on tag input | | `transfer/transfer-to-savings-returns-home.xml` | not ported — iOS already resets navigation to home on the same OK, so the journey has no iOS counterpart yet | @@ -287,9 +288,13 @@ and Settings (`Tab-general`, `Tab-security`, `Tab-advanced`, `NavigationBack`, ` | Home suggestion cards | `Suggestion-` | — *(cards expose no identifier)* | | Channels closing banner on Home | `ChannelsClosingBanner` | `ChannelsClosingBanner` *(to match when synonymdev/bitkit-ios#1097 lands)* | | Channels closing notice on the Spending wallet | `ChannelsClosingNotice` | `ChannelsClosingNotice` *(to match when synonymdev/bitkit-ios#1097 lands)* | -| New Channels Unavailable sheet | `ChannelsUnavailableSheet` | `ChannelsUnavailableSheet` *(to match when synonymdev/bitkit-ios#1097 lands)* | +| No automatic transfer sheet | `ChannelsUnavailableSheet` | `ChannelsUnavailableSheet` *(to match when synonymdev/bitkit-ios#1097 lands)* | | Channel close date line in that sheet | `ChannelsUnavailableCloseDate` | `ChannelsUnavailableCloseDate` *(to match when synonymdev/bitkit-ios#1097 lands)* | -| OK button in that sheet | `ChannelsUnavailableButton` | `ChannelsUnavailableButton` *(to match when synonymdev/bitkit-ios#1097 lands)* | +| Cancel button in that sheet | `ChannelsUnavailableButton` | `ChannelsUnavailableButton` *(to match when synonymdev/bitkit-ios#1097 lands)* | +| Open Channel button in that sheet | `ChannelsUnavailableOpenChannel` | `ChannelsUnavailableOpenChannel` *(to match when synonymdev/bitkit-ios#1097 lands)* | +| Provider row on Choose a Lightning provider | `ChooseLspProvider` | `ChooseLspProvider` *(to match when synonymdev/bitkit-ios#1097 lands)* | +| Manual Setup on Choose a Lightning provider | `ChooseLspManual` | `ChooseLspManual` *(to match when synonymdev/bitkit-ios#1097 lands)* | +| QR Code on Choose a Lightning provider | `ChooseLspScan` | `ChooseLspScan` *(to match when synonymdev/bitkit-ios#1097 lands)* | | Receive QR copy button | `ReceiveCopyQR` | `ReceiveCopyQR` *(absent from `snapshot-ui` targets; see below)* | | Payment Request row | `PaymentRequestRow-` | `PaymentRequestRow--` *(`-one-time` for a one-off)* | | Payment Request detail screen | `PaymentRequestDetailsScreen` | `PaymentRequestDetailScreen` | diff --git a/journeys/amount-limits/README.md b/journeys/amount-limits/README.md index 2e6d925a4d..6f33f4f85f 100644 --- a/journeys/amount-limits/README.md +++ b/journeys/amount-limits/README.md @@ -54,5 +54,5 @@ no button navigates to them. `transfer-spending-over-max.xml`, `transfer-spending-advanced-over-max.xml` and `transfer-spending-preset-delete.xml` were retired because Blocktank channel purchases were removed (synonymdev/bitkit-android#1669). Their route started at the Savings wallet's "Transfer To Spending" -button, which now opens the New Channels Unavailable sheet instead of the transfer flow. Their tags (`TransferToSpending`, `SpendingAmount*`, `SpendingAdvanced*`) are no +button, which now opens the No automatic transfer sheet instead of the transfer flow. Their tags (`TransferToSpending`, `SpendingAmount*`, `SpendingAdvanced*`) are no longer reachable from the UI. diff --git a/journeys/channels-closing/README.md b/journeys/channels-closing/README.md index 988af82943..3000719085 100644 --- a/journeys/channels-closing/README.md +++ b/journeys/channels-closing/README.md @@ -17,9 +17,9 @@ The iOS twin is synonymdev/bitkit-ios#1097. banner and the notice agree and read the date from the screen instead of hardcoding it. - **Unavailable sheet:** Savings "Transfer To Spending" (`TransferToSpending`), Spending "Transfer from Savings" (`TransferFromSavings`) and hardware wallet "Transfer To Spending" (`HardwareTransferToSpending`) - stay, but open the "New Channels Unavailable" sheet (`ChannelsUnavailableSheet`) instead of the transfer + stay, but open the "No automatic transfer" sheet (`ChannelsUnavailableSheet`) instead of the transfer flow. For a wallet with a Blocktank channel it also shows the close date (`ChannelsUnavailableCloseDate`); - the OK button (`ChannelsUnavailableButton`) closes it and leaves the user on the same screen. + Cancel (`ChannelsUnavailableButton`) closes it, and Open Channel (`ChannelsUnavailableOpenChannel`) opens the Choose a Lightning provider screen and leaves the user on the same screen. - **Removed:** the Home "Spend" suggestion card (`Suggestion-lightning`), the Receive sheet's CJIT button and gift codes. Transfer To Savings, send, receive over an existing channel, LNURL channel and manual external node @@ -36,11 +36,13 @@ The iOS twin is synonymdev/bitkit-ios#1097. | Journey | Covers | | --- | --- | -| `channels-closing-notice.xml` | Banner on Home → tap → Spending wallet notice and Transfer To Savings; Savings Transfer To Spending opens the New Channels Unavailable sheet with the close date | +| `channels-closing-notice.xml` | Banner on Home → tap → Spending wallet notice and Transfer To Savings; Savings Transfer To Spending opens the No automatic transfer sheet with the close date | +| `choose-lsp-from-transfer.xml` | Transfer To Spending → sheet → Open Channel → Choose a Lightning provider: four provider rows open their sites in the browser; Manual Setup and QR Code open the existing manual setup and scanner | | [`../receive/receive-onchain-only-without-channel.xml`](../receive/receive-onchain-only-without-channel.xml) | The opposite precondition: a funded wallet with no channels shows no banner, no Spend card and a Savings-only Receive sheet | ## Test tags `ChannelsClosingBanner`, `ChannelsClosingNotice`, `ActivitySavings`, `ActivitySpending`, `TotalBalance`, `TransferToSavings`, `TransferToSpending`, `ChannelsUnavailableSheet`, `ChannelsUnavailableCloseDate`, -`ChannelsUnavailableButton`. +`ChannelsUnavailableButton`, `ChannelsUnavailableOpenChannel`, `ChooseLspProvider`, `ChooseLspManual`, +`ChooseLspScan`. diff --git a/journeys/channels-closing/channels-closing-notice.xml b/journeys/channels-closing/channels-closing-notice.xml index 38b9b9f35e..c5a7fefc44 100644 --- a/journeys/channels-closing/channels-closing-notice.xml +++ b/journeys/channels-closing/channels-closing-notice.xml @@ -6,7 +6,7 @@ wallet, which shows "Your Lightning channels will be closed on Dec 15, 2026. Move your spending balance to savings before then." (testTag "ChannelsClosingNotice") under the balance header, and Transfer To Savings (testTag "TransferToSavings") is still offered. The Savings wallet's - Transfer To Spending button (testTag "TransferToSpending") opens the "New Channels Unavailable" + Transfer To Spending button (testTag "TransferToSpending") opens the "No automatic transfer" sheet (testTag "ChannelsUnavailableSheet") instead of the transfer flow, since new channels can no longer be bought; the sheet shows the same close date as the banner. The date is a placeholder constant until the shutdown date is final; assert whatever date the build shows, and keep the banner and the notice in agreement. @@ -27,8 +27,8 @@ Verify the "Transfer To Savings" button (testTag "TransferToSavings") is visible Press the system back button, then tap the Savings balance card (testTag "ActivitySavings") Verify the Savings wallet screen is open and tap the "Transfer To Spending" button (testTag "TransferToSpending") - Verify the sheet (testTag "ChannelsUnavailableSheet") opens with the title "New Channels Unavailable" and the text "Bitkit no longer offers new Lightning channels, so you can't move funds from savings to spending.", and that the line (testTag "ChannelsUnavailableCloseDate") reads "Your Lightning channels will be closed on Dec 15, 2026. Move your spending balance to savings before then." with the same date the banner showed - Tap "OK" (testTag "ChannelsUnavailableButton") and verify the sheet closes and the Savings wallet screen is still shown + Verify the sheet (testTag "ChannelsUnavailableSheet") opens with the title "No automatic transfer" and the text "Unfortunately, the service to automatically transfer funds to your spending balance is no longer available. You can still manually open a Lightning channel to top up your spending balance.", and that the line (testTag "ChannelsUnavailableCloseDate") reads "Your Lightning channels will be closed on Dec 15, 2026. Move your spending balance to savings before then." with the same date the banner showed + Tap "Cancel" (testTag "ChannelsUnavailableButton") and verify the sheet closes and the Savings wallet screen is still shown Press the system back button, tap the Spending balance card (testTag "ActivitySpending") and verify the notice (testTag "ChannelsClosingNotice") is still shown diff --git a/journeys/channels-closing/choose-lsp-from-transfer.xml b/journeys/channels-closing/choose-lsp-from-transfer.xml new file mode 100644 index 0000000000..ee6b7769c8 --- /dev/null +++ b/journeys/channels-closing/choose-lsp-from-transfer.xml @@ -0,0 +1,25 @@ + + + Verifies the way to a channel that is left after Blocktank channel purchases were removed + (synonymdev/bitkit-android#1669). Transfer To Spending opens the "No automatic transfer" sheet, whose + "Open Channel" button (testTag "ChannelsUnavailableOpenChannel") opens the "Choose a Lightning provider" + screen. That screen lists third-party providers (testTag "ChooseLspProvider"), and each row opens the + provider's own channel order page in the browser. Its footer has "Manual Setup" (testTag + "ChooseLspManual"), which opens the manual channel setup with an empty node, and "QR Code" (testTag + "ChooseLspScan"), which opens the scanner for the provider's LNURL channel or node QR. No step creates a + Blocktank order. The design (Figma, Bitkit - Final Review v63 › Third-party LSP › Choose LSP) shows the + provider page embedded in the app; this build opens it in the browser instead. + + Precondition: onboarded dev wallet with a positive on-chain Savings balance, so the Savings wallet shows + "Transfer To Spending". A channel is not needed. Start on the wallet home screen. + + + Tap the Savings balance card (testTag "ActivitySavings"), then "Transfer To Spending" (testTag "TransferToSpending") + Verify the sheet (testTag "ChannelsUnavailableSheet") shows the title "No automatic transfer", then tap "Open Channel" (testTag "ChannelsUnavailableOpenChannel") + Verify the screen titled "Spending Balance" shows "CHOOSE A LIGHTNING PROVIDER", the text "Order a channel from a Lightning Service Provider (LSP). Below are some options. Bitkit does not run, endorse, or support these." and four provider rows (testTag "ChooseLspProvider"): "LNBIG", "ZEUS Olympus", "Megalithic" and "Amboss Magma" + Verify the explanation "When they give you a QR to connect or claim the Lightning channel, tap below to scan or paste the QR." and the buttons "Manual Setup" (testTag "ChooseLspManual") and "QR Code" (testTag "ChooseLspScan") are visible + Tap "Megalithic" and verify the browser opens https://megalithic.me/get_a_channel, then return to Bitkit and verify the provider screen is still shown + Tap "QR Code" (testTag "ChooseLspScan") and verify the scanner sheet opens, then close it + Tap "Manual Setup" (testTag "ChooseLspManual") and verify the manual connection screen shows empty node id (testTag "NodeIdInput"), host (testTag "HostInput") and port (testTag "PortInput") fields + + diff --git a/journeys/cjit-notifications/README.md b/journeys/cjit-notifications/README.md index 4ad793dd41..7464a437b4 100644 --- a/journeys/cjit-notifications/README.md +++ b/journeys/cjit-notifications/README.md @@ -27,7 +27,7 @@ is no way to drive the push. 1. **Onboarded dev (regtest) wallet** with the node connected to the staging/regtest LSP and a positive on-chain Savings balance. -2. **Open a channel without CJIT.** Transfer To Spending on this build only opens a New Channels Unavailable sheet, so open a manual channel to +2. **Open a channel without CJIT.** Transfer To Spending on this build only opens a No automatic transfer sheet, so open a manual channel to the staging LSP node: Settings › Advanced › Lightning Connections › add connection › Manual Setup. See [Getting a Lightning channel](../README.md#getting-a-lightning-channel). diff --git a/journeys/hardware-wallet/README.md b/journeys/hardware-wallet/README.md index c47643fc98..ad30d84efc 100644 --- a/journeys/hardware-wallet/README.md +++ b/journeys/hardware-wallet/README.md @@ -75,7 +75,7 @@ the other two rely on, and `passphrase-settings-remove.xml` removes it again. | `suggestion-intro-sheet.xml` | Forget device, Hardware suggestion card, full connect flow (Intro → Searching → Found → Paired → Finish) re-pairs | | `connect-flow.xml` | Settings Add button → connect flow with an edited Label Funds → paired device count + name | | `settings-hardware-wallets.xml` | Payments count row, Hardware Wallets screen list, rename sheet, Add button sheet/back dismiss, per-row delete confirm + re-pair | -| `detail-overview.xml` | Detail screen overview, Transfer To Spending opens the New Channels Unavailable sheet, activity, Remove confirm + forget | +| `detail-overview.xml` | Detail screen overview, Transfer To Spending opens the No automatic transfer sheet, activity, Remove confirm + forget | | `send-onchain.xml` | Normal Send flow funded by Trezor: source selection, guarded preparation, device signing, broadcast, success, and activity | | `receive-onchain.xml` | Trezor Receive tab: current address and QR display plus exact on-device address verification | | `passphrase-pairing.xml` | Passphrase button on Paired → Enter Passphrase → Passphrase Funds Found; second home tile, own label, no passphrase in logs | @@ -135,7 +135,7 @@ synonymdev/bitkit-android#1669. `transfer-to-spending.xml`, `transfer-to-spending-max-lsp-cap.xml`, `transfer-to-spending-node-warmup.xml` and `passphrase-transfer-to-spending.xml` were retired because Blocktank channel purchases were removed (synonymdev/bitkit-android#1669): the hardware wallet detail -screen's Transfer To Spending button (`HardwareTransferToSpending`) now opens the New Channels Unavailable +screen's Transfer To Spending button (`HardwareTransferToSpending`) now opens the No automatic transfer sheet instead of the transfer flow. The `HardwareTransfer*` and `HwTransferPassphrase*` screens and tags are still in the nav graph but nothing in the UI reaches them. diff --git a/journeys/hardware-wallet/detail-overview.xml b/journeys/hardware-wallet/detail-overview.xml index 75471fc6c2..4e0d0437b9 100644 --- a/journeys/hardware-wallet/detail-overview.xml +++ b/journeys/hardware-wallet/detail-overview.xml @@ -1,7 +1,7 @@ Opens the hardware wallet detail screen from the home tile and verifies its overview: - the top bar, balance header, the Transfer To Spending entry (which opens the "New Channels Unavailable" + the top bar, balance header, the Transfer To Spending entry (which opens the "No automatic transfer" sheet since Blocktank channel purchases were removed, synonymdev/bitkit-android#1669), the grouped activity list, and the Remove device confirm dialog. The final Remove step forgets the device, so run this last (re-run connect-home-tile.xml to pair again). Requires a paired Bridge emulator (run connect-home-tile.xml first). @@ -17,7 +17,7 @@ Verify the hardware wallet detail screen opens (testTag "HardwareWalletScreen"), showing the device name with a blue bitcoin icon in the top bar and a balance header - If the wallet holds funds, verify the "Transfer To Spending" button (testTag "HardwareTransferToSpending") is shown, tap it, verify the sheet (testTag "ChannelsUnavailableSheet") opens with the title "New Channels Unavailable" and the text "Bitkit no longer offers new Lightning channels, so you can't move funds from savings to spending.", then tap "OK" (testTag "ChannelsUnavailableButton") and verify the hardware wallet detail screen is still shown + If the wallet holds funds, verify the "Transfer To Spending" button (testTag "HardwareTransferToSpending") is shown, tap it, verify the sheet (testTag "ChannelsUnavailableSheet") opens with the title "No automatic transfer" and the text "Unfortunately, the service to automatically transfer funds to your spending balance is no longer available. You can still manually open a Lightning channel to top up your spending balance.", then tap "Cancel" (testTag "ChannelsUnavailableButton") and verify the hardware wallet detail screen is still shown If the activity list shows transactions, verify their circular icons are blue, then tap the first one, verify an activity detail screen opens, and navigate back diff --git a/journeys/receive/receive-onchain-only-without-channel.xml b/journeys/receive/receive-onchain-only-without-channel.xml index 66285e7801..4bb61bfa60 100644 --- a/journeys/receive/receive-onchain-only-without-channel.xml +++ b/journeys/receive/receive-onchain-only-without-channel.xml @@ -4,7 +4,7 @@ offers the Savings tab and nothing that buys Lightning liquidity, and Home shows neither the Spend suggestion card nor the channels-closing banner. Blocktank channel purchases were removed in synonymdev/bitkit-android#1669: there is no "Receive Lightning funds" (CJIT) button or onboarding, - and the Transfer To Spending and Transfer from Savings buttons open the "New Channels Unavailable" + and the Transfer To Spending and Transfer from Savings buttons open the "No automatic transfer" sheet, and the Spending and Auto tabs appear only when the wallet can create a Lightning invoice over a ready channel with inbound liquidity. @@ -25,8 +25,8 @@ Tap "Edit" (testTag "SpecifyInvoiceButton"), tap the amount field (testTag "ReceiveNumberPadTextField"), enter a small amount (for example "N1" then "N0" twice), tap Continue (testTag "ReceiveNumberPadSubmit") and then "QR Code" (testTag "ShowQrReceive") Verify the sheet returns to the QR screen with no liquidity or channel confirm screen in between, the Savings tab is still selected and there is still no Spending tab (testTag "Tab-spending") Close the sheet, open the Savings wallet (testTag "ActivitySavings") and tap the "Transfer To Spending" button (testTag "TransferToSpending") - Verify the sheet (testTag "ChannelsUnavailableSheet") shows the title "New Channels Unavailable" and the text "Bitkit no longer offers new Lightning channels, so you can't move funds from savings to spending.", and no close date line (testTag "ChannelsUnavailableCloseDate"); tap "OK" (testTag "ChannelsUnavailableButton") and verify the Savings wallet screen is still shown + Verify the sheet (testTag "ChannelsUnavailableSheet") shows the title "No automatic transfer" and the text "Unfortunately, the service to automatically transfer funds to your spending balance is no longer available. You can still manually open a Lightning channel to top up your spending balance.", and no close date line (testTag "ChannelsUnavailableCloseDate"); tap "Cancel" (testTag "ChannelsUnavailableButton") and verify the Savings wallet screen is still shown Go back, open the Spending wallet (testTag "ActivitySpending"), verify no notice (testTag "ChannelsClosingNotice") is shown and tap the "Transfer from Savings" button (testTag "TransferFromSavings") - Verify the same sheet (testTag "ChannelsUnavailableSheet") opens with no close date line (testTag "ChannelsUnavailableCloseDate"), tap "OK" (testTag "ChannelsUnavailableButton") and verify the Spending wallet screen is still shown + Verify the same sheet (testTag "ChannelsUnavailableSheet") opens with no close date line (testTag "ChannelsUnavailableCloseDate"), tap "Cancel" (testTag "ChannelsUnavailableButton") and verify the Spending wallet screen is still shown From 63357e92565bcc1ff34d585f550fa48131adc696 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 22 Oct 2026 13:41:54 +0200 Subject: [PATCH 5/8] feat: match transfer unavailable sheet to design --- app/src/main/java/to/bitkit/ui/ContentView.kt | 19 +-- .../ui/sheets/ChannelsUnavailableSheet.kt | 140 +++--------------- app/src/main/res/values-ar/strings.xml | 6 +- app/src/main/res/values-b+es+419/strings.xml | 6 +- app/src/main/res/values-ca/strings.xml | 6 +- app/src/main/res/values-cs/strings.xml | 6 +- app/src/main/res/values-de/strings.xml | 6 +- app/src/main/res/values-el/strings.xml | 6 +- app/src/main/res/values-es-rES/strings.xml | 6 +- app/src/main/res/values-es/strings.xml | 6 +- app/src/main/res/values-fr/strings.xml | 6 +- app/src/main/res/values-it/strings.xml | 6 +- app/src/main/res/values-nl/strings.xml | 6 +- app/src/main/res/values-pl/strings.xml | 6 +- app/src/main/res/values-pt-rBR/strings.xml | 6 +- app/src/main/res/values-pt/strings.xml | 6 +- app/src/main/res/values-ru/strings.xml | 6 +- app/src/main/res/values/strings.xml | 6 +- journeys/README.md | 14 +- journeys/amount-limits/README.md | 2 +- journeys/channels-closing/README.md | 16 +- .../channels-closing-notice.xml | 10 +- .../choose-lsp-from-transfer.xml | 6 +- journeys/cjit-notifications/README.md | 2 +- journeys/hardware-wallet/README.md | 4 +- journeys/hardware-wallet/detail-overview.xml | 4 +- .../receive-onchain-only-without-channel.xml | 9 +- 27 files changed, 111 insertions(+), 211 deletions(-) diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt index d3801949de..db6fde381d 100644 --- a/app/src/main/java/to/bitkit/ui/ContentView.kt +++ b/app/src/main/java/to/bitkit/ui/ContentView.kt @@ -657,18 +657,13 @@ fun ContentView( Sheet.ConnectionClosed -> ConnectionClosedSheet( onDismiss = { appViewModel.hideSheet() }, ) - Sheet.ChannelsUnavailable -> { - val hasTrustedChannels by walletViewModel.hasTrustedChannels - .collectAsStateWithLifecycle(initialValue = false) - ChannelsUnavailableSheet( - showCloseDate = hasTrustedChannels, - onOpenChannel = { - appViewModel.hideSheet() - navController.navigateTo(Routes.ChooseLsp) - }, - onDismiss = { appViewModel.hideSheet() }, - ) - } + Sheet.ChannelsUnavailable -> ChannelsUnavailableSheet( + onContinue = { + appViewModel.hideSheet() + navController.navigateTo(Routes.ChooseLsp) + }, + onCancel = { appViewModel.hideSheet() }, + ) is Sheet.BTCPayConnection -> BTCPayConnectionSheet(sheet, appViewModel) is Sheet.QrScanner -> QrScanningSheet(sheet, appViewModel) diff --git a/app/src/main/java/to/bitkit/ui/sheets/ChannelsUnavailableSheet.kt b/app/src/main/java/to/bitkit/ui/sheets/ChannelsUnavailableSheet.kt index b16ffc470d..b886ca3625 100644 --- a/app/src/main/java/to/bitkit/ui/sheets/ChannelsUnavailableSheet.kt +++ b/app/src/main/java/to/bitkit/ui/sheets/ChannelsUnavailableSheet.kt @@ -1,127 +1,36 @@ package to.bitkit.ui.sheets -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.fillMaxWidth -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.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import to.bitkit.R -import to.bitkit.models.formatChannelsCloseDate -import to.bitkit.ui.components.BodyM import to.bitkit.ui.components.BottomSheetPreview -import to.bitkit.ui.components.PrimaryButton -import to.bitkit.ui.components.SecondaryButton -import to.bitkit.ui.components.SheetSize -import to.bitkit.ui.components.VerticalSpacer -import to.bitkit.ui.scaffold.SheetTopBar +import to.bitkit.ui.components.SheetIntro 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.withAccent @Composable fun ChannelsUnavailableSheet( - showCloseDate: Boolean, - onOpenChannel: () -> Unit, - onDismiss: () -> Unit, -) { - Content( - showCloseDate = showCloseDate, - onOpenChannel = onOpenChannel, - onDismiss = onDismiss, - ) -} - -@Composable -private fun Content( - showCloseDate: Boolean, + onContinue: () -> Unit, + onCancel: () -> Unit, modifier: Modifier = Modifier, - onOpenChannel: () -> Unit = {}, - onDismiss: () -> Unit = {}, ) { - val locale = LocalConfiguration.current.locales[0] - val closeDate = remember(locale) { formatChannelsCloseDate(locale) } - - Column( - modifier = modifier - .sheetHeight( - size = SheetSize.MEDIUM - ) - .gradientBackground() - .navigationBarsPadding() - .padding(horizontal = 16.dp) - .testTag("ChannelsUnavailableSheet") - ) { - SheetTopBar(titleText = stringResource(R.string.lightning__channels_unavailable__title)) - - BodyM( - text = stringResource(R.string.lightning__channels_unavailable__text), - color = Colors.White64, - modifier = Modifier.fillMaxWidth() - ) - - if (showCloseDate) { - VerticalSpacer(16.dp) - BodyM( - text = stringResource(R.string.lightning__channels_closing_notice, closeDate), - color = Colors.White64, - modifier = Modifier - .fillMaxWidth() - .testTag("ChannelsUnavailableCloseDate") - ) - } - - Box( - contentAlignment = Alignment.Center, - modifier = Modifier - .fillMaxWidth() - .weight(1f) - ) { - Image( - painter = painterResource(R.drawable.exclamation_mark), - contentDescription = null, - contentScale = ContentScale.Fit, - modifier = Modifier.size(256.dp) - ) - } - - Row( - horizontalArrangement = Arrangement.spacedBy(16.dp), - modifier = Modifier.fillMaxWidth() - ) { - SecondaryButton( - text = stringResource(R.string.common__cancel), - onClick = onDismiss, - modifier = Modifier - .weight(1f) - .testTag("ChannelsUnavailableButton") - ) - PrimaryButton( - text = stringResource(R.string.lightning__channels_unavailable__button), - onClick = onOpenChannel, - modifier = Modifier - .weight(1f) - .testTag("ChannelsUnavailableOpenChannel") - ) - } - - VerticalSpacer(16.dp) - } + SheetIntro( + navTitle = stringResource(R.string.lightning__channels_unavailable__nav_title), + title = stringResource(R.string.lightning__channels_unavailable__title).withAccent(accentColor = Colors.Purple), + description = AnnotatedString(stringResource(R.string.lightning__channels_unavailable__text)), + image = R.drawable.lightning, + continueText = stringResource(R.string.common__continue), + onContinue = onContinue, + cancelText = stringResource(R.string.common__cancel), + onCancel = onCancel, + testTag = "ChannelsUnavailableSheet", + modifier = modifier.sheetHeight(isModal = true) + ) } @Preview(showSystemUi = true) @@ -129,17 +38,10 @@ private fun Content( private fun Preview() { AppThemeSurface { BottomSheetPreview { - Content(showCloseDate = true) - } - } -} - -@Preview(showSystemUi = true) -@Composable -private fun PreviewWithoutChannels() { - AppThemeSurface { - BottomSheetPreview { - Content(showCloseDate = false) + ChannelsUnavailableSheet( + onContinue = {}, + onCancel = {}, + ) } } } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 73d388d43f..2b29f6e8e8 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -106,9 +106,9 @@ نقطة القناة تُغلق القنوات في %1$s ستُغلق قنوات Lightning الخاصة بك في %1$s. انقل رصيد الإنفاق إلى المدخرات قبل ذلك. - فتح قناة - للأسف، لم تعد خدمة التحويل التلقائي للأموال إلى رصيد الإنفاق متاحة. لا يزال بإمكانك فتح قناة Lightning يدويًا لتعبئة رصيد الإنفاق. - لا يوجد تحويل تلقائي + التحويل غير متاح + لم تعد التحويلات التلقائية إلى رصيد الإنفاق متاحة. استخدم عقدتك الخاصة، أو أنشئ اتصالًا مع مزوّد آخر. + <accent>الإعداد اليدوي</accent>\nمطلوب عندما يقدّمون لك رمز QR للاتصال بقناة Lightning أو المطالبة بها، اضغط أدناه لمسح رمز QR أو لصقه. اطلب قناة من مزوّد خدمة Lightning ‏(LSP). فيما يلي بعض الخيارات. لا يدير Bitkit هذه الخدمات ولا يؤيدها ولا يدعمها. اختر\n<accent>مزوّد Lightning</accent> 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 bb8c5180ad..5843d0adc9 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -111,9 +111,9 @@ Punto del canal LOS CANALES CIERRAN EL %1$s Tus canales Lightning se cerrarán el %1$s. Transfiere tu saldo de gasto a ahorros antes de esa fecha. - Abrir canal - Lamentablemente, el servicio para transferir fondos automáticamente a tu saldo de gasto ya no está disponible. Aún puedes abrir manualmente un canal Lightning para recargar tu saldo de gasto. - Sin transferencia automática + Transferencia no disponible + Las transferencias automáticas a tu saldo de gasto ya no están disponibles. Usa tu propio nodo o configura una conexión con otro proveedor. + <accent>Configuración manual</accent>\nnecesaria Cuando te den un QR para conectar o reclamar el canal Lightning, toca abajo para escanear o pegar el QR. Pide un canal a un proveedor de servicios Lightning (LSP). Abajo hay algunas opciones. Bitkit no las opera, respalda ni brinda soporte. Elige un\n<accent>proveedor Lightning</accent> diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 6dcce10df4..178924e27f 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -109,9 +109,9 @@ Punt del canal ELS CANALS ES TANQUEN EL %1$s Els teus canals Lightning es tancaran el %1$s. Transfereix el teu saldo de despesa a estalvis abans d\'aquesta data. - Obrir canal - Malauradament, el servei per transferir fons automàticament al teu saldo de despesa ja no està disponible. Encara pots obrir manualment un canal Lightning per recarregar el teu saldo de despesa. - Sense transferència automàtica + Transferència no disponible + Les transferències automàtiques al teu saldo de despesa ja no estan disponibles. Fes servir el teu propi node o configura una connexió amb un altre proveïdor. + <accent>Configuració manual</accent>\nnecessària Quan et donin un QR per connectar o reclamar el canal Lightning, toca a sota per escanejar o enganxar el QR. Demana un canal a un proveïdor de serveis Lightning (LSP). A sota hi ha algunes opcions. Bitkit no les gestiona, avala ni hi dona suport. Tria un\n<accent>proveïdor Lightning</accent> diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index abf672e34c..fc10ab24b2 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -111,9 +111,9 @@ Bod kanálu KANÁLY SE ZAVŘOU %1$s Vaše Lightning kanály budou uzavřeny %1$s. Do té doby převeďte zůstatek útrat do úspor. - Otevřít kanál - Služba pro automatický převod prostředků na zůstatek útrat bohužel již není k dispozici. Stále můžete ručně otevřít Lightning kanál a doplnit si zůstatek útrat. - Žádný automatický převod + Převod není dostupný + Automatické převody na zůstatek útrat již nejsou k dispozici. Použijte vlastní uzel nebo si nastavte připojení u jiného poskytovatele. + <accent>Vyžadováno ruční</accent>\nnastavení Až vám dají QR kód pro připojení nebo převzetí Lightning kanálu, klepněte níže a QR kód naskenujte nebo vložte. Objednejte si kanál u poskytovatele služeb Lightning (LSP). Níže je několik možností. Bitkit je neprovozuje, nedoporučuje ani nepodporuje. Vyberte\n<accent>poskytovatele Lightning</accent> diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index a8747e03da..0a1b15ecde 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -101,9 +101,9 @@ Abhängig von der Gebühr KANÄLE SCHLIESSEN AM %1$s Deine Lightning-Kanäle werden am %1$s geschlossen. Übertrage dein Guthaben vorher auf das Sparkonto. - Kanal öffnen - Leider ist der Dienst, der Guthaben automatisch auf dein Ausgabenkonto überträgt, nicht mehr verfügbar. Du kannst weiterhin manuell einen Lightning-Kanal öffnen, um dein Ausgabenkonto aufzuladen. - Keine automatische Übertragung + Übertragung nicht verfügbar + Automatische Übertragungen auf dein Ausgabenkonto sind nicht mehr verfügbar. Nutze deinen eigenen Node oder richte eine Verbindung mit einem anderen Anbieter ein. + <accent>Manuelle Einrichtung</accent>\nerforderlich Wenn du einen QR-Code zum Verbinden oder Einlösen des Lightning-Kanals erhältst, tippe unten, um ihn zu scannen oder einzufügen. Bestelle einen Kanal bei einem Lightning Service Provider (LSP). Unten findest du einige Optionen. Bitkit betreibt, empfiehlt oder unterstützt diese nicht. Wähle einen\n<accent>Lightning-Anbieter</accent> diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index e473df00f9..11a0241a62 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -106,9 +106,9 @@ Σημείο καναλιού ΤΑ ΚΑΝΑΛΙΑ ΚΛΕΙΝΟΥΝ ΣΤΙΣ %1$s Τα κανάλια Lightning σας θα κλείσουν στις %1$s. Μεταφέρετε το υπόλοιπο δαπανών σε αποταμιεύσεις πριν από τότε. - Άνοιγμα καναλιού - Δυστυχώς, η υπηρεσία αυτόματης μεταφοράς κεφαλαίων στο υπόλοιπο δαπανών δεν είναι πλέον διαθέσιμη. Μπορείτε ακόμα να ανοίξετε χειροκίνητα ένα κανάλι Lightning για να αναπληρώσετε το υπόλοιπο δαπανών. - Χωρίς αυτόματη μεταφορά + Μεταφορά μη διαθέσιμη + Οι αυτόματες μεταφορές στο υπόλοιπο δαπανών δεν είναι πλέον διαθέσιμες. Χρησιμοποιήστε τον δικό σας κόμβο ή ρυθμίστε σύνδεση με άλλον πάροχο. + <accent>Απαιτείται χειροκίνητη</accent>\nρύθμιση Όταν σας δώσουν ένα QR για σύνδεση ή διεκδίκηση του καναλιού Lightning, πατήστε παρακάτω για σάρωση ή επικόλληση του QR. Παραγγείλετε ένα κανάλι από έναν πάροχο υπηρεσιών Lightning (LSP). Παρακάτω υπάρχουν μερικές επιλογές. Το Bitkit δεν τις λειτουργεί, δεν τις προτείνει και δεν τις υποστηρίζει. Επιλέξτε\n<accent>πάροχο Lightning</accent> diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index f264accead..2f44322514 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -111,9 +111,9 @@ Punto del canal LOS CANALES CIERRAN EL %1$s Tus canales Lightning se cerrarán el %1$s. Transfiere tu saldo de gasto a ahorros antes de esa fecha. - Abrir canal - Lamentablemente, el servicio para transferir fondos automáticamente a tu saldo de gasto ya no está disponible. Aún puedes abrir manualmente un canal Lightning para recargar tu saldo de gasto. - Sin transferencia automática + Transferencia no disponible + Las transferencias automáticas a tu saldo de gasto ya no están disponibles. Usa tu propio nodo o configura una conexión con otro proveedor. + <accent>Configuración manual</accent>\nnecesaria Cuando te den un QR para conectar o reclamar el canal Lightning, toca abajo para escanear o pegar el QR. Pide un canal a un proveedor de servicios Lightning (LSP). Abajo tienes algunas opciones. Bitkit no las gestiona, respalda ni da soporte. Elige un\n<accent>proveedor Lightning</accent> diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 0501552408..df3af1acab 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -108,9 +108,9 @@ Punto de canal LOS CANALES CIERRAN EL %1$s Tus canales Lightning se cerrarán el %1$s. Transfiere tu saldo de gasto a ahorros antes de esa fecha. - Abrir canal - Lamentablemente, el servicio para transferir fondos automáticamente a tu saldo de gasto ya no está disponible. Aún puedes abrir manualmente un canal Lightning para recargar tu saldo de gasto. - Sin transferencia automática + Transferencia no disponible + Las transferencias automáticas a tu saldo de gasto ya no están disponibles. Usa tu propio nodo o configura una conexión con otro proveedor. + <accent>Configuración manual</accent>\nnecesaria Cuando te den un QR para conectar o reclamar el canal Lightning, toca abajo para escanear o pegar el QR. Pide un canal a un proveedor de servicios Lightning (LSP). Abajo tienes algunas opciones. Bitkit no las gestiona, respalda ni da soporte. Elige un\n<accent>proveedor Lightning</accent> diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 8d33ed4c48..89d839761c 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -100,9 +100,9 @@ Dépend des frais LES CANAUX FERMENT LE %1$s Vos canaux Lightning seront fermés le %1$s. Transférez votre solde de dépenses vers l\'Épargne avant cette date. - Ouvrir un canal - Malheureusement, le service de transfert automatique de fonds vers votre solde de dépenses n\'est plus disponible. Vous pouvez toujours ouvrir manuellement un canal Lightning pour recharger votre solde de dépenses. - Pas de transfert automatique + Transfert indisponible + Les transferts automatiques vers votre solde de dépenses ne sont plus disponibles. Utilisez votre propre nœud ou configurez une connexion avec un autre fournisseur. + <accent>Configuration manuelle</accent>\nrequise Quand ils vous donnent un QR pour connecter ou réclamer le canal Lightning, touchez ci-dessous pour scanner ou coller le QR. Commandez un canal auprès d\'un fournisseur de services Lightning (LSP). Voici quelques options. Bitkit ne les exploite pas, ne les cautionne pas et ne les prend pas en charge. Choisissez un\n<accent>fournisseur Lightning</accent> diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 85bed5f486..70e5a9e2b4 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -110,9 +110,9 @@ Punto del canale I CANALI CHIUDONO IL %1$s I tuoi canali Lightning verranno chiusi il %1$s. Trasferisci il saldo di spesa ai risparmi prima di allora. - Apri canale - Purtroppo il servizio per trasferire automaticamente fondi al saldo di spesa non è più disponibile. Puoi ancora aprire manualmente un canale Lightning per ricaricare il saldo di spesa. - Nessun trasferimento automatico + Trasferimento non disponibile + I trasferimenti automatici al saldo di spesa non sono più disponibili. Usa il tuo nodo o configura una connessione con un altro fornitore. + <accent>Configurazione manuale</accent>\nrichiesta Quando ti danno un QR per collegare o riscattare il canale Lightning, tocca qui sotto per scansionare o incollare il QR. Ordina un canale da un fornitore di servizi Lightning (LSP). Qui sotto trovi alcune opzioni. Bitkit non le gestisce, non le approva e non le supporta. Scegli un\n<accent>fornitore Lightning</accent> diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 51a4fa104f..a7b989bd38 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -110,9 +110,9 @@ Kanaalpunt KANALEN SLUITEN OP %1$s Je Lightning-kanalen worden op %1$s gesloten. Boek je bestedingssaldo vóór die datum over naar spaargeld. - Kanaal openen - Helaas is de dienst om automatisch geld naar je bestedingssaldo over te boeken niet meer beschikbaar. Je kunt nog steeds handmatig een Lightning-kanaal openen om je bestedingssaldo aan te vullen. - Geen automatische overboeking + Overboeking niet beschikbaar + Automatische overboekingen naar je bestedingssaldo zijn niet meer beschikbaar. Gebruik je eigen node of stel een verbinding in met een andere aanbieder. + <accent>Handmatige instelling</accent>\nvereist Als je een QR krijgt om het Lightning-kanaal te verbinden of te claimen, tik dan hieronder om de QR te scannen of te plakken. Bestel een kanaal bij een Lightning Service Provider (LSP). Hieronder staan enkele opties. Bitkit beheert, onderschrijft of ondersteunt deze niet. Kies een\n<accent>Lightning-aanbieder</accent> diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 0bce1958e3..f87d1bb890 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -102,9 +102,9 @@ Zależy od opłaty KANAŁY ZOSTANĄ ZAMKNIĘTE %1$s Twoje kanały Lightning zostaną zamknięte %1$s. Przed tym terminem prześlij saldo wydatków na oszczędności. - Otwórz kanał - Niestety usługa automatycznego przesyłania środków na saldo wydatków nie jest już dostępna. Nadal możesz ręcznie otworzyć kanał Lightning, aby doładować saldo wydatków. - Brak automatycznego przesłania + Przesłanie niedostępne + Automatyczne przesyłanie środków na saldo wydatków nie jest już dostępne. Użyj własnego węzła lub skonfiguruj połączenie z innym dostawcą. + <accent>Wymagana ręczna</accent>\nkonfiguracja Gdy otrzymasz kod QR do połączenia lub odebrania kanału Lightning, dotknij poniżej, aby zeskanować lub wkleić kod QR. Zamów kanał u dostawcy usług Lightning (LSP). Poniżej kilka opcji. Bitkit ich nie prowadzi, nie poleca ani nie wspiera. Wybierz\n<accent>dostawcę Lightning</accent> diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index af1831c159..cc12b84350 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -112,9 +112,9 @@ Ponto do canal CANAIS FECHAM EM %1$s Seus canais Lightning serão fechados em %1$s. Transfira seu saldo de gastos para a poupança antes dessa data. - Abrir canal - Infelizmente, o serviço de transferência automática de fundos para o seu saldo de gastos não está mais disponível. Você ainda pode abrir manualmente um canal Lightning para recarregar seu saldo de gastos. - Sem transferência automática + Transferência indisponível + As transferências automáticas para seu saldo de gastos não estão mais disponíveis. Use seu próprio nó ou configure uma conexão com outro provedor. + <accent>Configuração manual</accent>\nnecessária Quando derem um QR para conectar ou resgatar o canal Lightning, toque abaixo para escanear ou colar o QR. Peça um canal a um provedor de serviços Lightning (LSP). Abaixo estão algumas opções. O Bitkit não opera, endossa nem oferece suporte a elas. Escolha um\n<accent>provedor Lightning</accent> diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 7052626e35..4b48f030ad 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -97,9 +97,9 @@ Depende da taxa CANAIS FECHAM A %1$s Os seus canais Lightning serão fechados a %1$s. Transfira o seu saldo de gastos para a poupança antes dessa data. - Abrir canal - Infelizmente, o serviço de transferência automática de fundos para o seu saldo de gastos já não está disponível. Ainda pode abrir manualmente um canal Lightning para carregar o seu saldo de gastos. - Sem transferência automática + Transferência indisponível + As transferências automáticas para o seu saldo de gastos já não estão disponíveis. Use o seu próprio nó ou configure uma ligação com outro fornecedor. + <accent>Configuração manual</accent>\nnecessária Quando lhe derem um QR para ligar ou reclamar o canal Lightning, toque abaixo para digitalizar ou colar o QR. Peça um canal a um fornecedor de serviços Lightning (LSP). Abaixo estão algumas opções. O Bitkit não as gere, recomenda nem suporta. Escolha um\n<accent>fornecedor Lightning</accent> diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 3ee6ca9bb6..2ca482b3ea 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -120,9 +120,9 @@ Точка Канала КАНАЛЫ ЗАКРОЮТСЯ %1$s Ваши каналы Lightning будут закрыты %1$s. До этой даты переведите баланс расходов в сбережения. - Открыть канал - К сожалению, сервис автоматического перевода средств на баланс расходов больше недоступен. Вы по-прежнему можете вручную открыть канал Lightning, чтобы пополнить баланс расходов. - Нет автоматического перевода + Перевод недоступен + Автоматические переводы на баланс расходов больше недоступны. Используйте собственный узел или настройте подключение к другому провайдеру. + <accent>Требуется ручная</accent>\nнастройка Когда вам дадут QR-код для подключения или получения канала Lightning, нажмите ниже, чтобы отсканировать или вставить QR-код. Закажите канал у провайдера услуг Lightning (LSP). Ниже несколько вариантов. Bitkit не управляет ими, не рекомендует и не поддерживает их. Выберите\n<accent>провайдера Lightning</accent> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7f09c53880..4eb2da1cf0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -254,9 +254,9 @@ Channel point CHANNELS CLOSE ON %1$s Your Lightning channels will be closed on %1$s. Move your spending balance to savings before then. - Open Channel - Unfortunately, the service to automatically transfer funds to your spending balance is no longer available. You can still manually open a Lightning channel to top up your spending balance. - No automatic transfer + Transfer Unavailable + Automatic transfers to your spending balance are no longer available. Use your own node, or set up a connection with another provider. + <accent>Manual setup</accent>\nrequired When they give you a QR to connect or claim the Lightning channel, tap below to scan or paste the QR. Order a channel from a Lightning Service Provider (LSP). Below are some options. Bitkit does not run, endorse, or support these. Choose a\n<accent>Lightning provider</accent> diff --git a/journeys/README.md b/journeys/README.md index 85ff823b01..394666343c 100644 --- a/journeys/README.md +++ b/journeys/README.md @@ -110,7 +110,7 @@ exit status, so an empty response there is not a failure. Give the wallet ~20s t ### Getting a Lightning channel Blocktank is winding down, and this build no longer buys channels from it (synonymdev/bitkit-android#1669): -the Transfer To Spending buttons now open a "No automatic transfer" sheet instead of the transfer flow, and the Receive sheet's CJIT button and gift codes are gone. A journey that needs a +the Transfer To Spending buttons now open a "Manual setup required" sheet instead of the transfer flow, and the Receive sheet's CJIT button and gift codes are gone. A journey that needs a Lightning channel as its precondition says "a wallet with an open Lightning channel"; get one in either way: - Install a build from before that change (master before synonymdev/bitkit-android#1669), open a channel with @@ -163,7 +163,7 @@ buys a channel went with them. They are listed here rather than left as stale fi | --- | --- | | `amount-limits/transfer-spending-over-max.xml`, `transfer-spending-advanced-over-max.xml`, `transfer-spending-preset-delete.xml` | Transfer To Spending amount and Receiving Capacity number pads | | `cjit-notifications/cjit-foreground-service-notification.xml`, `cjit-push-single-notification.xml` | CJIT channel-ready notifications from the foreground service and from an FCM push | -| `hardware-wallet/transfer-to-spending.xml`, `transfer-to-spending-max-lsp-cap.xml`, `transfer-to-spending-node-warmup.xml`, `passphrase-transfer-to-spending.xml` | Hardware wallet Transfer To Spending (the `HardwareTransferToSpending` button now opens the No automatic transfer sheet instead) | +| `hardware-wallet/transfer-to-spending.xml`, `transfer-to-spending-max-lsp-cap.xml`, `transfer-to-spending-node-warmup.xml`, `passphrase-transfer-to-spending.xml` | Hardware wallet Transfer To Spending (the `HardwareTransferToSpending` button now opens the Manual setup required sheet instead) | | `notification-permission/receive-cjit-confirm-notification-toggle.xml`, `receive-cjit-liquidity-notification-toggle.xml`, `transfer-spending-confirm-notification-toggle.xml`, `toggle-off-opens-system-settings.xml` | The "Set up in background" toggle on the CJIT and Spending Confirm screens, which nothing navigates to now | | `transfer/spending-confirm-amount-change.xml` | Spending Confirm amount rebuilt at swipe | | `transfers/closed-channel-transfer-settles.xml` | Pending Transfer To Spending banner after the LSP closes the channel | @@ -288,10 +288,12 @@ and Settings (`Tab-general`, `Tab-security`, `Tab-advanced`, `NavigationBack`, ` | Home suggestion cards | `Suggestion-` | — *(cards expose no identifier)* | | Channels closing banner on Home | `ChannelsClosingBanner` | `ChannelsClosingBanner` *(to match when synonymdev/bitkit-ios#1097 lands)* | | Channels closing notice on the Spending wallet | `ChannelsClosingNotice` | `ChannelsClosingNotice` *(to match when synonymdev/bitkit-ios#1097 lands)* | -| No automatic transfer sheet | `ChannelsUnavailableSheet` | `ChannelsUnavailableSheet` *(to match when synonymdev/bitkit-ios#1097 lands)* | -| Channel close date line in that sheet | `ChannelsUnavailableCloseDate` | `ChannelsUnavailableCloseDate` *(to match when synonymdev/bitkit-ios#1097 lands)* | -| Cancel button in that sheet | `ChannelsUnavailableButton` | `ChannelsUnavailableButton` *(to match when synonymdev/bitkit-ios#1097 lands)* | -| Open Channel button in that sheet | `ChannelsUnavailableOpenChannel` | `ChannelsUnavailableOpenChannel` *(to match when synonymdev/bitkit-ios#1097 lands)* | +| Manual setup required sheet (Transfer Unavailable) | `ChannelsUnavailableSheet` | `ChannelsUnavailableSheet` *(to match when synonymdev/bitkit-ios#1097 lands)* | +| Lightning-bolt image in that sheet | `ChannelsUnavailableSheetImage` | `ChannelsUnavailableSheetImage` *(to match when synonymdev/bitkit-ios#1097 lands)* | +| Title in that sheet | `ChannelsUnavailableSheetTitle` | `ChannelsUnavailableSheetTitle` *(to match when synonymdev/bitkit-ios#1097 lands)* | +| Description in that sheet | `ChannelsUnavailableSheetDescription` | `ChannelsUnavailableSheetDescription` *(to match when synonymdev/bitkit-ios#1097 lands)* | +| Cancel button in that sheet | `ChannelsUnavailableSheetCancel` | `ChannelsUnavailableSheetCancel` *(to match when synonymdev/bitkit-ios#1097 lands)* | +| Continue button in that sheet | `ChannelsUnavailableSheetContinue` | `ChannelsUnavailableSheetContinue` *(to match when synonymdev/bitkit-ios#1097 lands)* | | Provider row on Choose a Lightning provider | `ChooseLspProvider` | `ChooseLspProvider` *(to match when synonymdev/bitkit-ios#1097 lands)* | | Manual Setup on Choose a Lightning provider | `ChooseLspManual` | `ChooseLspManual` *(to match when synonymdev/bitkit-ios#1097 lands)* | | QR Code on Choose a Lightning provider | `ChooseLspScan` | `ChooseLspScan` *(to match when synonymdev/bitkit-ios#1097 lands)* | diff --git a/journeys/amount-limits/README.md b/journeys/amount-limits/README.md index 6f33f4f85f..f25a233eac 100644 --- a/journeys/amount-limits/README.md +++ b/journeys/amount-limits/README.md @@ -54,5 +54,5 @@ no button navigates to them. `transfer-spending-over-max.xml`, `transfer-spending-advanced-over-max.xml` and `transfer-spending-preset-delete.xml` were retired because Blocktank channel purchases were removed (synonymdev/bitkit-android#1669). Their route started at the Savings wallet's "Transfer To Spending" -button, which now opens the No automatic transfer sheet instead of the transfer flow. Their tags (`TransferToSpending`, `SpendingAmount*`, `SpendingAdvanced*`) are no +button, which now opens the Manual setup required sheet instead of the transfer flow. Their tags (`TransferToSpending`, `SpendingAmount*`, `SpendingAdvanced*`) are no longer reachable from the UI. diff --git a/journeys/channels-closing/README.md b/journeys/channels-closing/README.md index 3000719085..f8804865dc 100644 --- a/journeys/channels-closing/README.md +++ b/journeys/channels-closing/README.md @@ -17,9 +17,10 @@ The iOS twin is synonymdev/bitkit-ios#1097. banner and the notice agree and read the date from the screen instead of hardcoding it. - **Unavailable sheet:** Savings "Transfer To Spending" (`TransferToSpending`), Spending "Transfer from Savings" (`TransferFromSavings`) and hardware wallet "Transfer To Spending" (`HardwareTransferToSpending`) - stay, but open the "No automatic transfer" sheet (`ChannelsUnavailableSheet`) instead of the transfer - flow. For a wallet with a Blocktank channel it also shows the close date (`ChannelsUnavailableCloseDate`); - Cancel (`ChannelsUnavailableButton`) closes it, and Open Channel (`ChannelsUnavailableOpenChannel`) opens the Choose a Lightning provider screen and leaves the user on the same screen. + stay, but open the "Transfer Unavailable" sheet (`ChannelsUnavailableSheet`: image, title "MANUAL SETUP REQUIRED", + description) instead of the transfer flow. It has no close date line; the date stays on the Home banner and + the Spending notice. Cancel (`ChannelsUnavailableSheetCancel`) closes it and leaves the user where they + were, and Continue (`ChannelsUnavailableSheetContinue`) closes it and opens the Choose a Lightning provider screen. - **Removed:** the Home "Spend" suggestion card (`Suggestion-lightning`), the Receive sheet's CJIT button and gift codes. Transfer To Savings, send, receive over an existing channel, LNURL channel and manual external node @@ -36,13 +37,14 @@ The iOS twin is synonymdev/bitkit-ios#1097. | Journey | Covers | | --- | --- | -| `channels-closing-notice.xml` | Banner on Home → tap → Spending wallet notice and Transfer To Savings; Savings Transfer To Spending opens the No automatic transfer sheet with the close date | -| `choose-lsp-from-transfer.xml` | Transfer To Spending → sheet → Open Channel → Choose a Lightning provider: four provider rows open their sites in the browser; Manual Setup and QR Code open the existing manual setup and scanner | +| `channels-closing-notice.xml` | Banner on Home → tap → Spending wallet notice and Transfer To Savings; Savings Transfer To Spending opens the Manual setup required sheet (no close date) and Cancel returns to Savings | +| `choose-lsp-from-transfer.xml` | Transfer To Spending → sheet → Continue → Choose a Lightning provider: four provider rows open their sites in the browser; Manual Setup and QR Code open the existing manual setup and scanner | | [`../receive/receive-onchain-only-without-channel.xml`](../receive/receive-onchain-only-without-channel.xml) | The opposite precondition: a funded wallet with no channels shows no banner, no Spend card and a Savings-only Receive sheet | ## Test tags `ChannelsClosingBanner`, `ChannelsClosingNotice`, `ActivitySavings`, `ActivitySpending`, `TotalBalance`, -`TransferToSavings`, `TransferToSpending`, `ChannelsUnavailableSheet`, `ChannelsUnavailableCloseDate`, -`ChannelsUnavailableButton`, `ChannelsUnavailableOpenChannel`, `ChooseLspProvider`, `ChooseLspManual`, +`TransferToSavings`, `TransferToSpending`, `ChannelsUnavailableSheet`, `ChannelsUnavailableSheetImage`, +`ChannelsUnavailableSheetTitle`, `ChannelsUnavailableSheetDescription`, `ChannelsUnavailableSheetCancel`, +`ChannelsUnavailableSheetContinue`, `ChooseLspProvider`, `ChooseLspManual`, `ChooseLspScan`. diff --git a/journeys/channels-closing/channels-closing-notice.xml b/journeys/channels-closing/channels-closing-notice.xml index c5a7fefc44..63dd1a9c03 100644 --- a/journeys/channels-closing/channels-closing-notice.xml +++ b/journeys/channels-closing/channels-closing-notice.xml @@ -6,10 +6,10 @@ wallet, which shows "Your Lightning channels will be closed on Dec 15, 2026. Move your spending balance to savings before then." (testTag "ChannelsClosingNotice") under the balance header, and Transfer To Savings (testTag "TransferToSavings") is still offered. The Savings wallet's - Transfer To Spending button (testTag "TransferToSpending") opens the "No automatic transfer" + Transfer To Spending button (testTag "TransferToSpending") opens the "Manual setup required" sheet (testTag "ChannelsUnavailableSheet") instead of the transfer flow, since new channels can no - longer be bought; the sheet shows the same close date as the banner. The date is a placeholder constant until the shutdown date is final; assert whatever - date the build shows, and keep the banner and the notice in agreement. + longer be bought; the sheet has no close date line. The date is a placeholder constant until the + shutdown date is final; assert whatever date the build shows, and keep the banner and the notice in agreement. Precondition: onboarded dev wallet with a Lightning channel to the staging LSP (Blocktank) node, open or pending, and a POSITIVE Spending balance; channels to other nodes do not show the notice. This build cannot buy a channel from Blocktank, so get @@ -27,8 +27,8 @@ Verify the "Transfer To Savings" button (testTag "TransferToSavings") is visible Press the system back button, then tap the Savings balance card (testTag "ActivitySavings") Verify the Savings wallet screen is open and tap the "Transfer To Spending" button (testTag "TransferToSpending") - Verify the sheet (testTag "ChannelsUnavailableSheet") opens with the title "No automatic transfer" and the text "Unfortunately, the service to automatically transfer funds to your spending balance is no longer available. You can still manually open a Lightning channel to top up your spending balance.", and that the line (testTag "ChannelsUnavailableCloseDate") reads "Your Lightning channels will be closed on Dec 15, 2026. Move your spending balance to savings before then." with the same date the banner showed - Tap "Cancel" (testTag "ChannelsUnavailableButton") and verify the sheet closes and the Savings wallet screen is still shown + Verify the sheet (testTag "ChannelsUnavailableSheet") opens with the nav title "Transfer Unavailable", the lightning-bolt image (testTag "ChannelsUnavailableSheetImage"), the title "MANUAL SETUP REQUIRED" with "MANUAL SETUP" in purple (testTag "ChannelsUnavailableSheetTitle") and the text "Automatic transfers to your spending balance are no longer available. Use your own node, or set up a connection with another provider." (testTag "ChannelsUnavailableSheetDescription"), with "Cancel" (testTag "ChannelsUnavailableSheetCancel") and "Continue" (testTag "ChannelsUnavailableSheetContinue") buttons, and no close date line + Tap "Cancel" (testTag "ChannelsUnavailableSheetCancel") and verify the sheet closes and the Savings wallet screen is still shown Press the system back button, tap the Spending balance card (testTag "ActivitySpending") and verify the notice (testTag "ChannelsClosingNotice") is still shown diff --git a/journeys/channels-closing/choose-lsp-from-transfer.xml b/journeys/channels-closing/choose-lsp-from-transfer.xml index ee6b7769c8..2379c64e99 100644 --- a/journeys/channels-closing/choose-lsp-from-transfer.xml +++ b/journeys/channels-closing/choose-lsp-from-transfer.xml @@ -1,8 +1,8 @@ Verifies the way to a channel that is left after Blocktank channel purchases were removed - (synonymdev/bitkit-android#1669). Transfer To Spending opens the "No automatic transfer" sheet, whose - "Open Channel" button (testTag "ChannelsUnavailableOpenChannel") opens the "Choose a Lightning provider" + (synonymdev/bitkit-android#1669). Transfer To Spending opens the "Manual setup required" sheet, whose + "Continue" button (testTag "ChannelsUnavailableSheetContinue") opens the "Choose a Lightning provider" screen. That screen lists third-party providers (testTag "ChooseLspProvider"), and each row opens the provider's own channel order page in the browser. Its footer has "Manual Setup" (testTag "ChooseLspManual"), which opens the manual channel setup with an empty node, and "QR Code" (testTag @@ -15,7 +15,7 @@ Tap the Savings balance card (testTag "ActivitySavings"), then "Transfer To Spending" (testTag "TransferToSpending") - Verify the sheet (testTag "ChannelsUnavailableSheet") shows the title "No automatic transfer", then tap "Open Channel" (testTag "ChannelsUnavailableOpenChannel") + Verify the sheet (testTag "ChannelsUnavailableSheet") shows the title "MANUAL SETUP REQUIRED" (testTag "ChannelsUnavailableSheetTitle"), then tap "Continue" (testTag "ChannelsUnavailableSheetContinue") and verify the sheet closes Verify the screen titled "Spending Balance" shows "CHOOSE A LIGHTNING PROVIDER", the text "Order a channel from a Lightning Service Provider (LSP). Below are some options. Bitkit does not run, endorse, or support these." and four provider rows (testTag "ChooseLspProvider"): "LNBIG", "ZEUS Olympus", "Megalithic" and "Amboss Magma" Verify the explanation "When they give you a QR to connect or claim the Lightning channel, tap below to scan or paste the QR." and the buttons "Manual Setup" (testTag "ChooseLspManual") and "QR Code" (testTag "ChooseLspScan") are visible Tap "Megalithic" and verify the browser opens https://megalithic.me/get_a_channel, then return to Bitkit and verify the provider screen is still shown diff --git a/journeys/cjit-notifications/README.md b/journeys/cjit-notifications/README.md index 7464a437b4..c882b963a6 100644 --- a/journeys/cjit-notifications/README.md +++ b/journeys/cjit-notifications/README.md @@ -27,7 +27,7 @@ is no way to drive the push. 1. **Onboarded dev (regtest) wallet** with the node connected to the staging/regtest LSP and a positive on-chain Savings balance. -2. **Open a channel without CJIT.** Transfer To Spending on this build only opens a No automatic transfer sheet, so open a manual channel to +2. **Open a channel without CJIT.** Transfer To Spending on this build only opens a Manual setup required sheet, so open a manual channel to the staging LSP node: Settings › Advanced › Lightning Connections › add connection › Manual Setup. See [Getting a Lightning channel](../README.md#getting-a-lightning-channel). diff --git a/journeys/hardware-wallet/README.md b/journeys/hardware-wallet/README.md index ad30d84efc..1e82932f2e 100644 --- a/journeys/hardware-wallet/README.md +++ b/journeys/hardware-wallet/README.md @@ -75,7 +75,7 @@ the other two rely on, and `passphrase-settings-remove.xml` removes it again. | `suggestion-intro-sheet.xml` | Forget device, Hardware suggestion card, full connect flow (Intro → Searching → Found → Paired → Finish) re-pairs | | `connect-flow.xml` | Settings Add button → connect flow with an edited Label Funds → paired device count + name | | `settings-hardware-wallets.xml` | Payments count row, Hardware Wallets screen list, rename sheet, Add button sheet/back dismiss, per-row delete confirm + re-pair | -| `detail-overview.xml` | Detail screen overview, Transfer To Spending opens the No automatic transfer sheet, activity, Remove confirm + forget | +| `detail-overview.xml` | Detail screen overview, Transfer To Spending opens the Manual setup required sheet, activity, Remove confirm + forget | | `send-onchain.xml` | Normal Send flow funded by Trezor: source selection, guarded preparation, device signing, broadcast, success, and activity | | `receive-onchain.xml` | Trezor Receive tab: current address and QR display plus exact on-device address verification | | `passphrase-pairing.xml` | Passphrase button on Paired → Enter Passphrase → Passphrase Funds Found; second home tile, own label, no passphrase in logs | @@ -135,7 +135,7 @@ synonymdev/bitkit-android#1669. `transfer-to-spending.xml`, `transfer-to-spending-max-lsp-cap.xml`, `transfer-to-spending-node-warmup.xml` and `passphrase-transfer-to-spending.xml` were retired because Blocktank channel purchases were removed (synonymdev/bitkit-android#1669): the hardware wallet detail -screen's Transfer To Spending button (`HardwareTransferToSpending`) now opens the No automatic transfer +screen's Transfer To Spending button (`HardwareTransferToSpending`) now opens the Manual setup required sheet instead of the transfer flow. The `HardwareTransfer*` and `HwTransferPassphrase*` screens and tags are still in the nav graph but nothing in the UI reaches them. diff --git a/journeys/hardware-wallet/detail-overview.xml b/journeys/hardware-wallet/detail-overview.xml index 4e0d0437b9..9390af08bf 100644 --- a/journeys/hardware-wallet/detail-overview.xml +++ b/journeys/hardware-wallet/detail-overview.xml @@ -1,7 +1,7 @@ Opens the hardware wallet detail screen from the home tile and verifies its overview: - the top bar, balance header, the Transfer To Spending entry (which opens the "No automatic transfer" + the top bar, balance header, the Transfer To Spending entry (which opens the "Manual setup required" sheet since Blocktank channel purchases were removed, synonymdev/bitkit-android#1669), the grouped activity list, and the Remove device confirm dialog. The final Remove step forgets the device, so run this last (re-run connect-home-tile.xml to pair again). Requires a paired Bridge emulator (run connect-home-tile.xml first). @@ -17,7 +17,7 @@ Verify the hardware wallet detail screen opens (testTag "HardwareWalletScreen"), showing the device name with a blue bitcoin icon in the top bar and a balance header - If the wallet holds funds, verify the "Transfer To Spending" button (testTag "HardwareTransferToSpending") is shown, tap it, verify the sheet (testTag "ChannelsUnavailableSheet") opens with the title "No automatic transfer" and the text "Unfortunately, the service to automatically transfer funds to your spending balance is no longer available. You can still manually open a Lightning channel to top up your spending balance.", then tap "Cancel" (testTag "ChannelsUnavailableButton") and verify the hardware wallet detail screen is still shown + If the wallet holds funds, verify the "Transfer To Spending" button (testTag "HardwareTransferToSpending") is shown, tap it, verify the sheet (testTag "ChannelsUnavailableSheet") opens with the nav title "Transfer Unavailable", the lightning-bolt image (testTag "ChannelsUnavailableSheetImage"), the title "MANUAL SETUP REQUIRED" with "MANUAL SETUP" in purple (testTag "ChannelsUnavailableSheetTitle") and the text "Automatic transfers to your spending balance are no longer available. Use your own node, or set up a connection with another provider." (testTag "ChannelsUnavailableSheetDescription"), with "Cancel" (testTag "ChannelsUnavailableSheetCancel") and "Continue" (testTag "ChannelsUnavailableSheetContinue") buttons, then tap "Cancel" (testTag "ChannelsUnavailableSheetCancel") and verify the hardware wallet detail screen is still shown If the activity list shows transactions, verify their circular icons are blue, then tap the first one, verify an activity detail screen opens, and navigate back diff --git a/journeys/receive/receive-onchain-only-without-channel.xml b/journeys/receive/receive-onchain-only-without-channel.xml index 4bb61bfa60..27c51f5401 100644 --- a/journeys/receive/receive-onchain-only-without-channel.xml +++ b/journeys/receive/receive-onchain-only-without-channel.xml @@ -4,9 +4,8 @@ offers the Savings tab and nothing that buys Lightning liquidity, and Home shows neither the Spend suggestion card nor the channels-closing banner. Blocktank channel purchases were removed in synonymdev/bitkit-android#1669: there is no "Receive Lightning funds" (CJIT) button or onboarding, - and the Transfer To Spending and Transfer from Savings buttons open the "No automatic transfer" - sheet, - and the Spending and Auto tabs appear only when the wallet can create a Lightning invoice over a + and the Transfer To Spending and Transfer from Savings buttons open the "Manual setup required" + sheet, and the Spending and Auto tabs appear only when the wallet can create a Lightning invoice over a ready channel with inbound liquidity. Precondition: a freshly onboarded dev wallet on the staging regtest backend with NO Lightning @@ -25,8 +24,8 @@ Tap "Edit" (testTag "SpecifyInvoiceButton"), tap the amount field (testTag "ReceiveNumberPadTextField"), enter a small amount (for example "N1" then "N0" twice), tap Continue (testTag "ReceiveNumberPadSubmit") and then "QR Code" (testTag "ShowQrReceive") Verify the sheet returns to the QR screen with no liquidity or channel confirm screen in between, the Savings tab is still selected and there is still no Spending tab (testTag "Tab-spending") Close the sheet, open the Savings wallet (testTag "ActivitySavings") and tap the "Transfer To Spending" button (testTag "TransferToSpending") - Verify the sheet (testTag "ChannelsUnavailableSheet") shows the title "No automatic transfer" and the text "Unfortunately, the service to automatically transfer funds to your spending balance is no longer available. You can still manually open a Lightning channel to top up your spending balance.", and no close date line (testTag "ChannelsUnavailableCloseDate"); tap "Cancel" (testTag "ChannelsUnavailableButton") and verify the Savings wallet screen is still shown + Verify the sheet (testTag "ChannelsUnavailableSheet") opens with the nav title "Transfer Unavailable", the lightning-bolt image (testTag "ChannelsUnavailableSheetImage"), the title "MANUAL SETUP REQUIRED" with "MANUAL SETUP" in purple (testTag "ChannelsUnavailableSheetTitle") and the text "Automatic transfers to your spending balance are no longer available. Use your own node, or set up a connection with another provider." (testTag "ChannelsUnavailableSheetDescription"), with "Cancel" (testTag "ChannelsUnavailableSheetCancel") and "Continue" (testTag "ChannelsUnavailableSheetContinue") buttons; tap "Cancel" (testTag "ChannelsUnavailableSheetCancel") and verify the Savings wallet screen is still shown Go back, open the Spending wallet (testTag "ActivitySpending"), verify no notice (testTag "ChannelsClosingNotice") is shown and tap the "Transfer from Savings" button (testTag "TransferFromSavings") - Verify the same sheet (testTag "ChannelsUnavailableSheet") opens with no close date line (testTag "ChannelsUnavailableCloseDate"), tap "Cancel" (testTag "ChannelsUnavailableButton") and verify the Spending wallet screen is still shown + Verify the same sheet (testTag "ChannelsUnavailableSheet") opens, tap "Cancel" (testTag "ChannelsUnavailableSheetCancel") and verify the Spending wallet screen is still shown From 9d388fc7d8ff14be28d9d43c6020fdf56fb98a26 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 22 Oct 2026 14:31:36 +0200 Subject: [PATCH 6/8] fix: fit channels closing banner on one line --- .../java/to/bitkit/ui/components/ActivityBanner.kt | 12 ++++++++++++ app/src/main/java/to/bitkit/ui/components/Text.kt | 5 +++++ .../java/to/bitkit/ui/screens/wallets/HomeScreen.kt | 1 + 3 files changed, 18 insertions(+) diff --git a/app/src/main/java/to/bitkit/ui/components/ActivityBanner.kt b/app/src/main/java/to/bitkit/ui/components/ActivityBanner.kt index 43e3aa849b..bd45d81e2c 100644 --- a/app/src/main/java/to/bitkit/ui/components/ActivityBanner.kt +++ b/app/src/main/java/to/bitkit/ui/components/ActivityBanner.kt @@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.requiredHeight import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.text.TextAutoSize import androidx.compose.material3.Icon import androidx.compose.material3.ShapeDefaults import androidx.compose.runtime.Composable @@ -34,6 +35,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import to.bitkit.R import to.bitkit.models.ActivityBannerType import to.bitkit.ui.shared.modifiers.clickableAlpha @@ -43,12 +45,16 @@ import to.bitkit.ui.theme.Colors private const val GLOW_ANIMATION_MILLIS = 1200 +/** Smallest size an auto-sized banner title shrinks to so it fits on one line. */ +private val TITLE_MIN_FONT_SIZE = 12.sp + @Composable fun ActivityBanner( gradientColor: Color, title: String, @DrawableRes icon: Int, modifier: Modifier = Modifier, + autoSizeTitle: Boolean = false, onClick: (() -> Unit)? = null, ) { val infiniteTransition = rememberInfiniteTransition(label = "glow") @@ -169,6 +175,12 @@ fun ActivityBanner( Headline20( text = AnnotatedString(title), color = Colors.White, + maxLines = if (autoSizeTitle) 1 else Int.MAX_VALUE, + autoSize = if (autoSizeTitle) { + TextAutoSize.StepBased(minFontSize = TITLE_MIN_FONT_SIZE, maxFontSize = 20.sp) + } else { + null + }, ) } } diff --git a/app/src/main/java/to/bitkit/ui/components/Text.kt b/app/src/main/java/to/bitkit/ui/components/Text.kt index 51db982c7b..a8925afb2b 100644 --- a/app/src/main/java/to/bitkit/ui/components/Text.kt +++ b/app/src/main/java/to/bitkit/ui/components/Text.kt @@ -1,6 +1,7 @@ package to.bitkit.ui.components +import androidx.compose.foundation.text.TextAutoSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -103,6 +104,8 @@ fun Headline20( text: AnnotatedString, modifier: Modifier = Modifier, color: Color = MaterialTheme.colorScheme.primary, + maxLines: Int = Int.MAX_VALUE, + autoSize: TextAutoSize? = null, ) { Text( text = text, @@ -112,6 +115,8 @@ fun Headline20( letterSpacing = (-.5).sp, color = color, ), + maxLines = maxLines, + autoSize = autoSize, modifier = modifier ) } diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt index df71bd0860..fa1fe66848 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/HomeScreen.kt @@ -705,6 +705,7 @@ private fun WalletPage( gradientColor = banner.type.color, title = banner.title, icon = banner.type.icon, + autoSizeTitle = banner.type == ActivityBannerType.CHANNELS_CLOSING, onClick = { when (banner.type) { ActivityBannerType.SPENDING -> onNavigateToSettingUp() From 6f86a1bff6ae0bfee0358c99ec8cc8d9b0ac5844 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Thu, 22 Oct 2026 16:08:15 +0200 Subject: [PATCH 7/8] feat: remove blocktank order, cjit and geocheck calls --- .../ui/screens/transfer/FundingScreenTest.kt | 3 +- app/src/main/java/to/bitkit/env/Env.kt | 3 - .../main/java/to/bitkit/fcm/WakeNodeWorker.kt | 21 - .../to/bitkit/repositories/BlocktankRepo.kt | 173 +------ .../to/bitkit/repositories/LightningRepo.kt | 23 - .../java/to/bitkit/repositories/LogsRepo.kt | 2 - .../to/bitkit/repositories/TransferRepo.kt | 34 -- .../java/to/bitkit/repositories/WalletRepo.kt | 2 - .../java/to/bitkit/services/CoreService.kt | 42 -- .../to/bitkit/services/LightningService.kt | 8 +- app/src/main/java/to/bitkit/ui/ContentView.kt | 5 - .../main/java/to/bitkit/ui/MainActivity.kt | 2 - .../ui/onboarding/OnboardingSlidesScreen.kt | 7 +- .../ui/screens/transfer/FundingScreen.kt | 13 +- .../ui/screens/wallets/SavingsWalletScreen.kt | 28 +- .../lightning/ChannelDetailViewModel.kt | 1 - .../LightningConnectionsViewModel.kt | 1 - app/src/main/java/to/bitkit/utils/Errors.kt | 1 - .../java/to/bitkit/viewmodels/AppViewModel.kt | 6 - .../bitkit/viewmodels/BlocktankViewModel.kt | 2 +- app/src/main/res/values-ar/strings.xml | 3 - app/src/main/res/values-b+es+419/strings.xml | 3 - app/src/main/res/values-ca/strings.xml | 3 - app/src/main/res/values-cs/strings.xml | 3 - app/src/main/res/values-de/strings.xml | 3 - app/src/main/res/values-el/strings.xml | 3 - app/src/main/res/values-es-rES/strings.xml | 3 - app/src/main/res/values-es/strings.xml | 3 - app/src/main/res/values-fr/strings.xml | 3 - app/src/main/res/values-it/strings.xml | 3 - app/src/main/res/values-nl/strings.xml | 3 - app/src/main/res/values-pl/strings.xml | 3 - app/src/main/res/values-pt-rBR/strings.xml | 3 - app/src/main/res/values-pt/strings.xml | 3 - app/src/main/res/values-ru/strings.xml | 3 - app/src/main/res/values/strings.xml | 3 - .../bitkit/repositories/BlocktankRepoTest.kt | 447 +----------------- .../bitkit/repositories/LightningRepoTest.kt | 122 +---- .../bitkit/repositories/TransferRepoTest.kt | 201 +------- .../to/bitkit/repositories/WalletRepoTest.kt | 25 +- .../viewmodels/AppViewModelSendFlowTest.kt | 1 - 41 files changed, 84 insertions(+), 1137 deletions(-) diff --git a/app/src/androidTest/java/to/bitkit/ui/screens/transfer/FundingScreenTest.kt b/app/src/androidTest/java/to/bitkit/ui/screens/transfer/FundingScreenTest.kt index a062322016..0e180c24f1 100644 --- a/app/src/androidTest/java/to/bitkit/ui/screens/transfer/FundingScreenTest.kt +++ b/app/src/androidTest/java/to/bitkit/ui/screens/transfer/FundingScreenTest.kt @@ -52,14 +52,13 @@ class FundingScreenTest { manualClicks = 0 } - private fun setContent(fundableBalance: ULong = 100_000uL, isGeoBlocked: Boolean = false) { + private fun setContent(fundableBalance: ULong = 100_000uL) { composeTestRule.setContent { CompositionLocalProvider( LocalBalances provides BalanceState(channelFundableBalance = fundableBalance) ) { AppThemeSurface { FundingScreen( - isGeoBlocked = isGeoBlocked, onTransfer = { transferClicks++ }, onFund = { fundClicks++ }, onManual = { manualClicks++ }, diff --git a/app/src/main/java/to/bitkit/env/Env.kt b/app/src/main/java/to/bitkit/env/Env.kt index 09797720d0..3d04477257 100644 --- a/app/src/main/java/to/bitkit/env/Env.kt +++ b/app/src/main/java/to/bitkit/env/Env.kt @@ -37,7 +37,6 @@ internal object Env { val isDebug = BuildConfig.DEBUG const val isE2eTest = BuildConfig.E2E - const val isGeoblockingEnabled = BuildConfig.GEO val e2eBackend = BuildConfig.E2E_BACKEND.lowercase() val isLocalE2eBackend = isE2eTest && e2eBackend == "local" const val e2eLocalHost = BuildConfig.E2E_LOCAL_HOST @@ -69,7 +68,6 @@ internal object Env { const val fxRateRefreshInterval = 2 * 60 * 1000L // 2 minutes in millis const val fxRateStaleThreshold = 10 * 60 * 1000L // 10 minutes in millis - const val lspOrdersRefreshInterval = 2 * 60 * 1000L // 2 minutes in millis val pushNotificationFeatures = BlocktankNotificationType.entries const val derivationName = "bitkit-notifications" @@ -152,7 +150,6 @@ internal object Env { else -> "https://bitkit.stag0.blocktank.to/fx/rates/btc" } - const val geoCheckUrl = "https://api1.blocktank.to/api/geocheck" const val chatwootUrl = "https://synonym.to/api/chatwoot" const val newsBaseUrl = "https://feeds.synonym.to/news-feed/api" const val mempoolBaseUrl = "https://mempool.space/api" diff --git a/app/src/main/java/to/bitkit/fcm/WakeNodeWorker.kt b/app/src/main/java/to/bitkit/fcm/WakeNodeWorker.kt index 8edf0af18e..17fabb0061 100644 --- a/app/src/main/java/to/bitkit/fcm/WakeNodeWorker.kt +++ b/app/src/main/java/to/bitkit/fcm/WakeNodeWorker.kt @@ -12,8 +12,6 @@ import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.delay import kotlinx.coroutines.withTimeout import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.contentOrNull import kotlinx.serialization.json.jsonObject import org.lightningdevkit.ldknode.Event import to.bitkit.App @@ -86,25 +84,6 @@ class WakeNodeWorker @AssistedInject constructor( timeout = timeout, eventHandler = { event -> handleLdkEvent(event) } ) - - // Once node is started, handle the manual channel opening if needed - if (notificationType == orderPaymentConfirmed) { - val orderId = (notificationPayload?.get("orderId") as? JsonPrimitive)?.contentOrNull - - if (orderId == null) { - Logger.error("Missing orderId", context = TAG) - } else { - Logger.info("Open channel request for order $orderId", context = TAG) - blocktankRepo.openChannel(orderId).onFailure { - Logger.error("Failed to open channel", it, context = TAG) - bestAttemptContent = NotificationDetails( - title = appContext.getString(R.string.notification__channel_open_failed_title), - body = it.message ?: appContext.getString(R.string.common__error_body), - ) - deliver() - } - } - } } // Stops node on timeout & avoids notification replay by OS withTimeout(timeout) { deliverSignal.await() } diff --git a/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt b/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt index f2f4d0ae32..9ccb00d381 100644 --- a/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/BlocktankRepo.kt @@ -1,9 +1,6 @@ package to.bitkit.repositories import androidx.compose.runtime.Stable -import com.synonym.bitkitcore.AddressType -import com.synonym.bitkitcore.BtOrderState2 -import com.synonym.bitkitcore.CJitStateEnum import com.synonym.bitkitcore.ChannelLiquidityOptions import com.synonym.bitkitcore.ChannelLiquidityParams import com.synonym.bitkitcore.CreateCjitOptions @@ -18,35 +15,22 @@ import com.synonym.bitkitcore.getDefaultLspBalance import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.currentCoroutineContext -import kotlinx.coroutines.delay import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update -import kotlinx.coroutines.isActive import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeout import org.lightningdevkit.ldknode.ChannelDetails import to.bitkit.async.appScope -import to.bitkit.data.BlocktankRefundAddress import to.bitkit.data.CacheStore import to.bitkit.di.BgDispatcher -import to.bitkit.env.Env import to.bitkit.ext.nowTimestamp import to.bitkit.ext.runSuspendCatching import to.bitkit.models.BlocktankBackupV1 @@ -55,15 +39,12 @@ import to.bitkit.models.EUR import to.bitkit.models.safe import to.bitkit.services.CoreService import to.bitkit.services.LightningService -import to.bitkit.utils.AppError import to.bitkit.utils.Logger import to.bitkit.utils.ServiceError import java.math.BigDecimal import javax.inject.Inject -import javax.inject.Named import javax.inject.Singleton import kotlin.math.ceil -import kotlin.time.Duration.Companion.seconds @Singleton class BlocktankRepo @Inject constructor( @@ -72,11 +53,9 @@ class BlocktankRepo @Inject constructor( private val lightningService: LightningService, private val currencyRepo: CurrencyRepo, private val cacheStore: CacheStore, - @Named("enablePolling") private val enablePolling: Boolean, private val lightningRepo: LightningRepo, ) { private val repoScope = appScope(bgDispatcher, TAG) - private val refundAddressMutex = Mutex() private val _blocktankState = MutableStateFlow(BlocktankState()) val blocktankState: StateFlow = _blocktankState.asStateFlow() @@ -85,27 +64,14 @@ class BlocktankRepo @Inject constructor( private var isRefreshing = false init { - startPolling() observePaidOrders() repoScope.launch { refreshInfo() - refreshOrders() + loadCachedOrders() } } - private fun startPolling() { - if (!enablePolling) return - flow { - while (currentCoroutineContext().isActive) { - emit(Unit) - delay(Env.lspOrdersRefreshInterval) - } - }.flowOn(bgDispatcher) - .onEach { refreshOrders() } - .launchIn(repoScope) - } - private fun observePaidOrders() { repoScope.launch { cacheStore.data @@ -130,41 +96,7 @@ class BlocktankRepo @Inject constructor( fundingTx.id == fundingTxo.txid && fundingTx.vout == fundingTxo.vout.toULong() } - val cached = _blocktankState.value.cjitEntries - cached.matching()?.let { return@withContext it } - - // A ChannelReady can only be a CJIT if a live cached entry is still awaiting its channel; otherwise skip - // the server round-trip so a non-CJIT transfer confirmation isn't delayed by a slow Blocktank API. - val hasPendingCjit = cached.any { - it.channel == null && it.state != CJitStateEnum.EXPIRED && it.state != CJitStateEnum.FAILED - } - if (cached.isNotEmpty() && !hasPendingCjit) return@withContext null - - // Match against the freshly fetched list so a concurrent refreshOrders() can't clobber state before we read. - return@withContext refreshCjitEntries().matching() - } - - private suspend fun refreshCjitEntries(): List { - repeat(CJIT_REFRESH_ATTEMPTS) { attempt -> - val entries = runCatching { - withTimeout(CJIT_REFRESH_TIMEOUT) { - coreService.blocktank.cjitEntries(refresh = true) - } - }.mapCatching { entries -> - _blocktankState.update { it.copy(cjitEntries = entries.toImmutableList()) } - entries - }.getOrElse { - if (it is CancellationException && it !is TimeoutCancellationException) throw it - if (attempt == CJIT_REFRESH_ATTEMPTS - 1) { - Logger.warn("Failed to refresh CJIT entries; using cached state", it, context = TAG) - } - null - } - - entries?.let { return it } - delay(CJIT_REFRESH_RETRY_DELAY) - } - return _blocktankState.value.cjitEntries + _blocktankState.value.cjitEntries.matching() } suspend fun refreshInfo() = withContext(bgDispatcher) { @@ -183,16 +115,13 @@ class BlocktankRepo @Inject constructor( } } - suspend fun refreshOrders(): Result = withContext(bgDispatcher) { + suspend fun loadCachedOrders(): Result = withContext(bgDispatcher) { if (isRefreshing) return@withContext Result.success(Unit) isRefreshing = true runCatching { - Logger.verbose("Refreshing blocktank orders…", context = TAG) - val paidOrderIds = cacheStore.data.first().paidOrders.keys - // Sync instantly from cache val cachedOrders = coreService.blocktank.orders(refresh = false) val cachedCjitEntries = coreService.blocktank.cjitEntries(refresh = false) _blocktankState.update { state -> @@ -203,24 +132,10 @@ class BlocktankRepo @Inject constructor( ) } - // Then refresh from server - val orders = coreService.blocktank.orders(refresh = true) - val cjitEntries = coreService.blocktank.cjitEntries(refresh = true) - _blocktankState.update { state -> - state.copy( - orders = orders.toImmutableList(), - cjitEntries = cjitEntries.toImmutableList(), - paidOrders = orders.filter { order -> order.id in paidOrderIds }.toImmutableList(), - ) - } - Logger.debug( - "Orders refreshed: ${orders.size} orders, " + - "${cjitEntries.size} cjit entries, " + - "${_blocktankState.value.paidOrders.size} paid orders", - context = TAG + "Loaded cached orders: ${cachedOrders.size} orders, ${cachedCjitEntries.size} cjit entries", + context = TAG, ) - openChannelWithPaidOrders() }.also { isRefreshing = false } @@ -245,7 +160,6 @@ class BlocktankRepo @Inject constructor( description: String = "", ): Result = withContext(bgDispatcher) { runSuspendCatching { - if (coreService.isGeoBlocked()) throw ServiceError.GeoBlocked() val nodeId = lightningService.nodeId ?: throw ServiceError.NodeNotStarted() val maxChannelSizeSat = freshMaxChannelSizeSat() if (maxChannelSizeSat != null && amountSats > maxChannelSizeSat) { @@ -271,7 +185,7 @@ class BlocktankRepo @Inject constructor( channelSizeSat = cjitEntry.channelSizeSat, ).getOrThrow() - repoScope.launch { refreshOrders() } + repoScope.launch { loadCachedOrders() } return@runSuspendCatching cjitEntry }.fold( @@ -311,14 +225,10 @@ class BlocktankRepo @Inject constructor( channelExpiryWeeks: UInt = DEFAULT_CHANNEL_EXPIRY_WEEKS, ): Result = withContext(bgDispatcher) { runSuspendCatching { - if (coreService.isGeoBlocked()) throw ServiceError.GeoBlocked() if (lightningService.nodeId == null) throw ServiceError.NodeNotStarted() currentCoroutineContext().ensureActive() - val refundAddress = getBlocktankRefundAddress() - currentCoroutineContext().ensureActive() - val baseOptions = defaultCreateOrderOptions(clientBalanceSat = spendingBalanceSats) - val options = baseOptions.copy(refundOnchainAddress = refundAddress) + val options = defaultCreateOrderOptions(clientBalanceSat = spendingBalanceSats) currentCoroutineContext().ensureActive() Logger.info( @@ -338,7 +248,7 @@ class BlocktankRepo @Inject constructor( options = options, ) - repoScope.launch { refreshOrders() } + repoScope.launch { loadCachedOrders() } return@runSuspendCatching order }.onFailure { @@ -346,57 +256,6 @@ class BlocktankRepo @Inject constructor( } } - private suspend fun getBlocktankRefundAddress(): String = refundAddressMutex.withLock { - val cached = cacheStore.data.first().blocktankRefundAddress - if (cached == null) return@withLock allocateBlocktankRefundAddress() - - if (cached.index !in 0..Int.MAX_VALUE.toLong()) { - throw AppError("Invalid cached Blocktank refund address index") - } - if (cached.address.isBlank()) { - throw AppError("Invalid cached Blocktank refund address") - } - - val index = cached.index.toInt() - val derived = lightningRepo.addressInfoForType(AddressType.P2WPKH, index).getOrThrow() - if (derived.index != index || derived.address != cached.address) { - throw AppError("Cached Blocktank refund address does not belong to the active wallet") - } - - lightningRepo.revealReceiveAddresses(index, AddressType.P2WPKH).getOrThrow() - if (!coreService.isAddressUsed(cached.address)) return@withLock cached.address - - allocateBlocktankRefundAddress(afterIndex = index) - } - - private suspend fun allocateBlocktankRefundAddress(afterIndex: Int? = null): String { - var previousIndex = afterIndex - repeat(REFUND_ADDRESS_ALLOCATION_LIMIT) { - val derived = lightningRepo.newAddressInfoForType(AddressType.P2WPKH).getOrThrow() - val lastIndex = previousIndex - val hasInvalidIndex = derived.index !in 0..Int.MAX_VALUE - val didNotAdvance = lastIndex != null && derived.index <= lastIndex - if (hasInvalidIndex || derived.address.isBlank() || didNotAdvance) { - throw AppError("Failed to allocate a valid advancing Blocktank refund address") - } - previousIndex = derived.index - - if (coreService.isAddressUsed(derived.address)) return@repeat - - cacheStore.update { - it.copy( - blocktankRefundAddress = BlocktankRefundAddress( - address = derived.address, - index = derived.index.toLong(), - ), - ) - } - return derived.address - } - - throw AppError("Failed to allocate an unused Blocktank refund address") - } - suspend fun estimateOrderFee( spendingBalanceSats: ULong, receivingBalanceSats: ULong, @@ -447,7 +306,7 @@ class BlocktankRepo @Inject constructor( ): Result = withContext(bgDispatcher) { runSuspendCatching { if (refresh) { - refreshOrders().getOrThrow() + loadCachedOrders().getOrThrow() } val order = _blocktankState.value.orders.find { it.id == orderId } return@runSuspendCatching order @@ -456,10 +315,10 @@ class BlocktankRepo @Inject constructor( } } - /** Fetches [orderIds] from the LSP, including orders the local cache has never seen, and caches them. */ + /** Reads [orderIds] from the local order cache, without calling the LSP, and merges them into state. */ suspend fun fetchOrders(orderIds: List): Result> = withContext(bgDispatcher) { runSuspendCatching { - val fetched = coreService.blocktank.orders(orderIds = orderIds, refresh = true) + val fetched = coreService.blocktank.orders(orderIds = orderIds, refresh = false) val fetchedById = fetched.associateBy { it.id } _blocktankState.update { state -> val cachedIds = state.orders.map { it.id }.toSet() @@ -473,12 +332,6 @@ class BlocktankRepo @Inject constructor( } } - private suspend fun openChannelWithPaidOrders() = withContext(bgDispatcher) { - _blocktankState.value.paidOrders.filter { it.state2 == BtOrderState2.PAID }.forEach { order -> - openChannel(order.id) - } - } - private suspend fun defaultCreateOrderOptions(clientBalanceSat: ULong): CreateOrderOptions { val nodeId = lightningService.nodeId ?: throw ServiceError.NodeNotStarted() val timestamp = nowTimestamp().toString() @@ -605,12 +458,8 @@ class BlocktankRepo @Inject constructor( companion object { private const val TAG = "BlocktankRepo" - private const val REFUND_ADDRESS_ALLOCATION_LIMIT = 20 private const val DEFAULT_CHANNEL_EXPIRY_WEEKS = 6u private const val DEFAULT_SOURCE = "bitkit-android" - private const val CJIT_REFRESH_ATTEMPTS = 3 - private val CJIT_REFRESH_TIMEOUT = 5.seconds - private val CJIT_REFRESH_RETRY_DELAY = 1.seconds } } diff --git a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt index c51a06a148..eebea6f844 100644 --- a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt @@ -72,7 +72,6 @@ import to.bitkit.env.Env import to.bitkit.ext.getSatsPerVByteFor import to.bitkit.ext.nowMillis import to.bitkit.ext.runSuspendCatching -import to.bitkit.ext.toPeerDetailsList import to.bitkit.ext.totalNextOutboundHtlcLimitSats import to.bitkit.models.ALL_ADDRESS_TYPE_STRINGS import to.bitkit.models.CoinSelectionPreference @@ -310,12 +309,10 @@ class LightningRepo @Inject constructor( channelMigration: ChannelDataMigration? = null, ) = withContext(bgDispatcher) { runSuspendCatching { - val trustedPeers = fetchTrustedPeers() lightningService.setup( walletIndex, customServerUrl, customRgsServerUrl, - trustedPeers, channelMigration, ) }.onFailure { @@ -323,16 +320,6 @@ class LightningRepo @Inject constructor( } } - private suspend fun fetchTrustedPeers(): List? = runSuspendCatching { - val info = coreService.blocktank.info(refresh = false) - ?: coreService.blocktank.info(refresh = true) - info?.nodes?.toPeerDetailsList()?.also { - Logger.info("Fetched ${it.size} trusted peers from remote", context = TAG) - } - }.onFailure { - Logger.warn("fetchTrustedPeers error", it, context = TAG) - }.getOrNull() - suspend fun start( walletIndex: Int = 0, timeout: Duration? = null, @@ -425,7 +412,6 @@ class LightningRepo @Inject constructor( // Initial state sync syncState() logNodeSupportSummary("node started") - updateGeoBlockState() refreshChannelCache() if (shouldValidateGraph && !lightningService.aresRequiredPeersInNetworkGraph()) { @@ -676,12 +662,6 @@ class LightningRepo @Inject constructor( fun setWiping(enabled: Boolean) = run { isWiping = enabled } - suspend fun updateGeoBlockState() = withContext(bgDispatcher) { - _lightningState.update { - it.copy(isGeoBlocked = coreService.isGeoBlocked()) - } - } - fun setInitNodeLifecycleState() { _lightningState.update { it.copy(nodeLifecycleState = NodeLifecycleState.Initializing) } } @@ -1316,7 +1296,6 @@ class LightningRepo @Inject constructor( description: String, expirySeconds: UInt = Defaults.bolt11ExpirySec, ): Result = executeWhenNodeRunning("createInvoice") { - updateGeoBlockState() runCatching { lightningService.receive(amountSats, description, expirySeconds) } } @@ -1325,7 +1304,6 @@ class LightningRepo @Inject constructor( description: String, expirySeconds: UInt = Defaults.bolt11ExpirySec, ): Result = executeWhenNodeRunning("createInvoiceMsats") { - updateGeoBlockState() runCatching { lightningService.receiveMsats(amountMsats, description, expirySeconds) } } @@ -2779,7 +2757,6 @@ data class LightningState( val channels: ImmutableList = persistentListOf(), val balances: BalanceDetails? = null, val isSyncingWallet: Boolean = false, - val isGeoBlocked: Boolean = false, val lastSyncError: Throwable? = null, val lastSuccessfulSyncAt: Long? = null, ) { diff --git a/app/src/main/java/to/bitkit/repositories/LogsRepo.kt b/app/src/main/java/to/bitkit/repositories/LogsRepo.kt index f77e623e9c..de0d5e8df0 100644 --- a/app/src/main/java/to/bitkit/repositories/LogsRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/LogsRepo.kt @@ -160,7 +160,6 @@ class LogsRepo @Inject constructor( nodeId = state.nodeId, lifecycle = state.nodeLifecycleState.supportName(), isSyncingWallet = state.isSyncingWallet, - isGeoBlocked = state.isGeoBlocked, batterySettings = context.batterySettingsSnapshot(), lastSuccessfulSyncAt = state.lastSuccessfulSyncAt?.toString(), lastSyncError = state.lastSyncError?.javaClass?.simpleName, @@ -325,7 +324,6 @@ private data class SupportSnapshot( val nodeId: String, val lifecycle: String, val isSyncingWallet: Boolean, - val isGeoBlocked: Boolean, val batterySettings: BatterySettingsSnapshot, val lastSuccessfulSyncAt: String?, val lastSyncError: String?, diff --git a/app/src/main/java/to/bitkit/repositories/TransferRepo.kt b/app/src/main/java/to/bitkit/repositories/TransferRepo.kt index f62bd67458..7bf205f1d0 100644 --- a/app/src/main/java/to/bitkit/repositories/TransferRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/TransferRepo.kt @@ -28,7 +28,6 @@ import to.bitkit.data.CacheStore import to.bitkit.data.dao.TransferDao import to.bitkit.data.entities.TransferEntity import to.bitkit.di.BgDispatcher -import to.bitkit.env.Env import to.bitkit.ext.channelId import to.bitkit.ext.latestSpendingTxid import to.bitkit.ext.runSuspendCatching @@ -39,7 +38,6 @@ import to.bitkit.utils.AppError import to.bitkit.utils.BlockTimeHelpers import to.bitkit.utils.Logger import java.util.UUID -import java.util.concurrent.atomic.AtomicLong import javax.inject.Inject import javax.inject.Singleton import kotlin.time.Clock @@ -57,7 +55,6 @@ class TransferRepo @Inject constructor( private val cacheStore: CacheStore, private val connectivityRepo: ConnectivityRepo, ) { - private val lastOrdersFetchMs = AtomicLong(0L) private val fundingMutex = Mutex() private val repoScope = appScope(bgDispatcher, TAG) @@ -296,7 +293,6 @@ class TransferRepo @Inject constructor( transfer.channelId != null && channels.none { it.channelId == transfer.channelId } } val closedChannelIds = if (hasMissingChannel) loadClosedChannelIds() else emptySet() - fetchOrdersAwaitingEvidence(toSpending, channels, closedChannelIds) for (transfer in toSpending) { val channelId = resolveChannelIdForTransfer(transfer, channels) @@ -316,36 +312,6 @@ class TransferRepo @Inject constructor( } } - private suspend fun fetchOrdersAwaitingEvidence( - toSpending: List, - channels: List, - closedChannelIds: Set, - ) { - val orderIds = toSpending.mapNotNull { transfer -> - val orderId = transfer.lspOrderId ?: return@mapNotNull null - val channelId = resolveChannelIdForTransfer(transfer, channels) - if (channelId != null) { - val isChannelReady = channels.any { it.channelId == channelId && it.isChannelReady } - if (channelId in closedChannelIds || isChannelReady) return@mapNotNull null - } - val cached = blocktankRepo.getOrder(orderId, refresh = false).getOrNull() - if (cached != null && (cached.state2 == BtOrderState2.EXPIRED || cached.hasClosedChannel())) { - return@mapNotNull null - } - orderId - }.distinct() - if (orderIds.isEmpty()) return - - val nowMs = clock.now().toEpochMilliseconds() - val lastFetchMs = lastOrdersFetchMs.get() - if (nowMs - lastFetchMs in 0L until Env.lspOrdersRefreshInterval) return - if (!lastOrdersFetchMs.compareAndSet(lastFetchMs, nowMs)) return - - blocktankRepo.fetchOrders(orderIds).onSuccess { fetched -> - Logger.debug("Fetched '${fetched.size}' of '${orderIds.size}' orders for active transfers", context = TAG) - } - } - private suspend fun settleIfChannelClosed( transfer: TransferEntity, channelId: String, diff --git a/app/src/main/java/to/bitkit/repositories/WalletRepo.kt b/app/src/main/java/to/bitkit/repositories/WalletRepo.kt index c7c9173a01..7c9d6d8d40 100644 --- a/app/src/main/java/to/bitkit/repositories/WalletRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/WalletRepo.kt @@ -284,7 +284,6 @@ class WalletRepo @Inject constructor( ) { runSuspendCatching { lightningRepo.syncState() - lightningRepo.updateGeoBlockState() refreshBip21Locked().getOrThrow() }.onFailure { _walletState.update { state -> state.copy(hasReceiveInvoiceError = true) } @@ -304,7 +303,6 @@ class WalletRepo @Inject constructor( return updateReceiveState { runSuspendCatching { lightningRepo.syncState() - lightningRepo.updateGeoBlockState() refreshAddressForInvoice().getOrThrow() updateBip21InvoiceLocked(state.bip21AmountSats, state.bip21Description).getOrThrow() }.onFailure { diff --git a/app/src/main/java/to/bitkit/services/CoreService.kt b/app/src/main/java/to/bitkit/services/CoreService.kt index b59c70edd1..de20cbb6a4 100644 --- a/app/src/main/java/to/bitkit/services/CoreService.kt +++ b/app/src/main/java/to/bitkit/services/CoreService.kt @@ -65,9 +65,6 @@ import com.synonym.bitkitcore.upsertInfo import com.synonym.bitkitcore.upsertOrders import com.synonym.bitkitcore.upsertTransactionDetails import com.synonym.bitkitcore.wipeAllDatabases -import io.ktor.client.HttpClient -import io.ktor.client.request.get -import io.ktor.http.HttpStatusCode import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.first @@ -121,7 +118,6 @@ import com.synonym.bitkitcore.markActivityAsSeen as coreMarkActivityAsSeen @Singleton class CoreService @Inject constructor( private val lightningService: LightningService, - private val httpClient: HttpClient, private val cacheStore: CacheStore, private val settingsStore: SettingsStore, private val privatePaykitContactResolver: Provider, @@ -171,44 +167,6 @@ class CoreService @Inject constructor( } } - @Suppress("KotlinConstantConditions") - suspend fun isGeoBlocked(): Boolean { - val tag = "GeoCheck" - if (!Env.isGeoblockingEnabled) { - Logger.verbose("Geoblocking disabled via build config", context = tag) - return false - } - - return ServiceQueue.CORE.background { - runCatching { - Logger.verbose("Checking geo status…", context = tag) - val response = httpClient.get(Env.geoCheckUrl) - - when (response.status.value) { - HttpStatusCode.OK.value -> { - Logger.verbose("Region allowed", context = tag) - false - } - - HttpStatusCode.Forbidden.value -> { - Logger.warn("Region blocked", context = tag) - true - } - - else -> { - Logger.warn( - "Unexpected status code: ${response.status.value}, defaulting to false", - context = tag - ) - false - } - } - }.onFailure { - Logger.warn("Error. defaulting isGeoBlocked to false", context = tag) - }.getOrDefault(false) - } - } - suspend fun wipeData(): Result = ServiceQueue.CORE.background { runCatching { activity.invalidatePaykitContactBackfill() diff --git a/app/src/main/java/to/bitkit/services/LightningService.kt b/app/src/main/java/to/bitkit/services/LightningService.kt index 78a272c4c9..956b376aff 100644 --- a/app/src/main/java/to/bitkit/services/LightningService.kt +++ b/app/src/main/java/to/bitkit/services/LightningService.kt @@ -248,13 +248,12 @@ class LightningService internal constructor( walletIndex: Int, customServerUrl: String? = null, customRgsServerUrl: String? = null, - trustedPeers: List? = null, channelMigration: ChannelDataMigration? = null, ) { awaitNodeRelease() Logger.debug("Building node…", context = TAG) - val config = config(walletIndex, trustedPeers) + val config = config(walletIndex) val builtNode = build( walletIndex, customServerUrl, @@ -270,13 +269,10 @@ class LightningService internal constructor( private suspend fun config( walletIndex: Int, - trustedPeers: List?, ): Config { val dirPath = Env.ldkStoragePath(walletIndex) - this.trustedPeers = trustedPeers?.takeIf { it.isNotEmpty() } ?: Env.trustedLnPeers.also { - Logger.warn("Missing trusted peers from LSP, falling back to preconfigured env peers", context = TAG) - } + this.trustedPeers = Env.trustedLnPeers val trustedPeerNodeIds = this.trustedPeers.map { it.nodeId } diff --git a/app/src/main/java/to/bitkit/ui/ContentView.kt b/app/src/main/java/to/bitkit/ui/ContentView.kt index db6fde381d..f74d1d6db1 100644 --- a/app/src/main/java/to/bitkit/ui/ContentView.kt +++ b/app/src/main/java/to/bitkit/ui/ContentView.kt @@ -315,7 +315,6 @@ fun ContentView( appWidgetRefreshScheduler.ensureScheduled(AppWidgetRefreshReason.APP_FOREGROUND) appWidgetRefreshScheduler.requestCatchUp(AppWidgetRefreshReason.APP_FOREGROUND) currencyViewModel.triggerRefresh() - blocktankViewModel.refreshOrders() appViewModel.checkAdoptedPubkySource() appViewModel.refreshPrivatePaykitEndpoints() appViewModel.startPaykitPaymentRequestPolling() @@ -1144,7 +1143,6 @@ private fun RootNavHost( } deepLinkableComposable { val hasSeenSpendingIntro by settingsViewModel.hasSeenSpendingIntro.collectAsStateWithLifecycle() - val isGeoBlocked by appViewModel.isGeoBlocked.collectAsStateWithLifecycle() FundingScreen( onTransfer = { @@ -1159,7 +1157,6 @@ private fun RootNavHost( }, onManual = { navController.navigateTo(Routes.ExternalNav) }, onBackClick = { navController.popBackStack() }, - isGeoBlocked = isGeoBlocked, ) } deepLinkableComposable { @@ -1306,12 +1303,10 @@ private fun NavGraphBuilder.home( ) } deepLinkableComposable { - val isGeoBlocked by appViewModel.isGeoBlocked.collectAsStateWithLifecycle() val onchainActivities by activityListViewModel.onchainActivities.collectAsStateWithLifecycle() val forceCloseRemainingDuration by appViewModel.forceCloseRemainingDuration.collectAsStateWithLifecycle() SavingsWalletScreen( - isGeoBlocked = isGeoBlocked, onchainActivities = onchainActivities ?: persistentListOf(), onAllActivityButtonClick = { navController.navigateToAllActivity(activityListViewModel::clearFilters) }, onActivityItemClick = { navController.navToActivityDetail(it) }, diff --git a/app/src/main/java/to/bitkit/ui/MainActivity.kt b/app/src/main/java/to/bitkit/ui/MainActivity.kt index 2f2b47d664..13625dfb07 100644 --- a/app/src/main/java/to/bitkit/ui/MainActivity.kt +++ b/app/src/main/java/to/bitkit/ui/MainActivity.kt @@ -456,10 +456,8 @@ private fun OnboardingNav( } composableWithDefaultTransitions { navBackEntry -> val route = navBackEntry.toRoute() - val isGeoBlocked by appViewModel.isGeoBlocked.collectAsStateWithLifecycle() OnboardingSlidesScreen( currentTab = route.tab, - isGeoBlocked = isGeoBlocked, onAdvancedSetupClick = { startupNavController.navigateTo(StartupRoutes.Advanced) }, onCreateClick = { scope.launch { diff --git a/app/src/main/java/to/bitkit/ui/onboarding/OnboardingSlidesScreen.kt b/app/src/main/java/to/bitkit/ui/onboarding/OnboardingSlidesScreen.kt index 5777cebb6b..d54dbf5943 100644 --- a/app/src/main/java/to/bitkit/ui/onboarding/OnboardingSlidesScreen.kt +++ b/app/src/main/java/to/bitkit/ui/onboarding/OnboardingSlidesScreen.kt @@ -54,7 +54,6 @@ private const val PAGE_COUNT = LAST_PAGE_INDEX + 1 @Composable fun OnboardingSlidesScreen( - isGeoBlocked: Boolean, onAdvancedSetupClick: () -> Unit, onCreateClick: () -> Unit, onRestoreClick: () -> Unit, @@ -87,7 +86,7 @@ fun OnboardingSlidesScreen( title = stringResource(R.string.onboarding__slide1_header), titleAccentColor = Colors.Purple, text = stringResource(R.string.onboarding__slide1_text), - disclaimerText = stringResource(R.string.onboarding__slide1_note).takeIf { isGeoBlocked }, + disclaimerText = null, modifier = Modifier.testTag("Slide1") ) @@ -229,7 +228,6 @@ private fun OnboardingViewPreview() { onAdvancedSetupClick = {}, onCreateClick = {}, onRestoreClick = {}, - isGeoBlocked = true ) } } @@ -243,7 +241,6 @@ private fun OnboardingViewPreview2() { onAdvancedSetupClick = {}, onCreateClick = {}, onRestoreClick = {}, - isGeoBlocked = true ) } } @@ -257,7 +254,6 @@ private fun OnboardingViewPreview3() { onAdvancedSetupClick = {}, onCreateClick = {}, onRestoreClick = {}, - isGeoBlocked = false ) } } @@ -271,7 +267,6 @@ private fun OnboardingViewPreview4() { onAdvancedSetupClick = {}, onCreateClick = {}, onRestoreClick = {}, - isGeoBlocked = false ) } } diff --git a/app/src/main/java/to/bitkit/ui/screens/transfer/FundingScreen.kt b/app/src/main/java/to/bitkit/ui/screens/transfer/FundingScreen.kt index 39f276e3b0..30ef28cfdf 100644 --- a/app/src/main/java/to/bitkit/ui/screens/transfer/FundingScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/transfer/FundingScreen.kt @@ -34,7 +34,6 @@ import to.bitkit.ui.utils.withAccent @Composable fun FundingScreen( - isGeoBlocked: Boolean, onTransfer: () -> Unit = {}, onFund: () -> Unit = {}, onManual: () -> Unit = {}, @@ -61,12 +60,7 @@ fun FundingScreen( Display(text = stringResource(R.string.lightning__funding__title).withAccent(accentColor = Colors.Purple)) Spacer(modifier = Modifier.height(8.dp)) - val text = if (isGeoBlocked) { - stringResource(R.string.lightning__funding__text_blocked) - } else { - stringResource(R.string.lightning__funding__text) - } - BodyM(text = text, color = Colors.White64) + BodyM(text = stringResource(R.string.lightning__funding__text), color = Colors.White64) Spacer(modifier = Modifier.height(32.dp)) @@ -78,7 +72,7 @@ fun FundingScreen( label = stringResource(R.string.lightning__funding__button1), icon = R.drawable.ic_transfer, iconTint = Colors.Purple, - enabled = canTransfer && !isGeoBlocked, + enabled = canTransfer, onClick = onTransfer, modifier = Modifier.testTag("FundTransfer") ) @@ -98,7 +92,6 @@ fun FundingScreen( icon = R.drawable.ic_qr_purple, iconTint = Colors.Purple, iconSize = 13.75.dp, - enabled = !isGeoBlocked, onClick = onFund, modifier = Modifier.testTag("FundReceive") ) @@ -132,6 +125,6 @@ fun FundingScreen( @Composable private fun FundingScreenPreview() { AppThemeSurface { - FundingScreen(isGeoBlocked = false) + FundingScreen() } } diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt index f9aa4c5da8..317b8d5370 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/SavingsWalletScreen.kt @@ -58,7 +58,6 @@ import to.bitkit.ui.utils.withAccent @Composable fun SavingsWalletScreen( - isGeoBlocked: Boolean, onchainActivities: ImmutableList, onAllActivityButtonClick: () -> Unit, onEmptyActivityRowClick: () -> Unit, @@ -74,8 +73,7 @@ fun SavingsWalletScreen( mutableStateOf(!hasFunds && !hasActivity) } val canTransfer by remember(balances.totalOnchainSats) { - val hasFunds = balances.totalOnchainSats > 0uL - mutableStateOf(hasFunds && !isGeoBlocked) + mutableStateOf(balances.totalOnchainSats > 0uL) } val hazeState = rememberHazeState() @@ -198,7 +196,6 @@ private fun Preview() { AppThemeSurface { Box { SavingsWalletScreen( - isGeoBlocked = false, onchainActivities = previewOnchainActivityItems(), onAllActivityButtonClick = {}, onActivityItemClick = {}, @@ -218,7 +215,6 @@ private fun PreviewTransfer() { AppThemeSurface { Box { SavingsWalletScreen( - isGeoBlocked = false, onchainActivities = previewOnchainActivityItems(), onAllActivityButtonClick = {}, onActivityItemClick = {}, @@ -241,7 +237,6 @@ private fun PreviewNoActivity() { AppThemeSurface { Box { SavingsWalletScreen( - isGeoBlocked = false, onchainActivities = persistentListOf(), onAllActivityButtonClick = {}, onActivityItemClick = {}, @@ -255,33 +250,12 @@ private fun PreviewNoActivity() { } } -@Preview(showSystemUi = true) -@Composable -private fun PreviewGeoBlocked() { - AppThemeSurface { - Box { - SavingsWalletScreen( - isGeoBlocked = true, - onchainActivities = previewOnchainActivityItems(), - onAllActivityButtonClick = {}, - onActivityItemClick = {}, - onEmptyActivityRowClick = {}, - onTransferToSpendingClick = {}, - onBackClick = {}, - balances = BalanceState(totalOnchainSats = 50_000u), - ) - TabBar() - } - } -} - @Preview(showSystemUi = true) @Composable private fun PreviewEmpty() { AppThemeSurface { Box { SavingsWalletScreen( - isGeoBlocked = false, onchainActivities = persistentListOf(), onAllActivityButtonClick = {}, onActivityItemClick = {}, diff --git a/app/src/main/java/to/bitkit/ui/settings/lightning/ChannelDetailViewModel.kt b/app/src/main/java/to/bitkit/ui/settings/lightning/ChannelDetailViewModel.kt index 6e350d5ce9..91a7efe6ae 100644 --- a/app/src/main/java/to/bitkit/ui/settings/lightning/ChannelDetailViewModel.kt +++ b/app/src/main/java/to/bitkit/ui/settings/lightning/ChannelDetailViewModel.kt @@ -87,7 +87,6 @@ class ChannelDetailViewModel @Inject constructor( fun onPullToRefresh() = viewModelScope.launch { _uiState.update { it.copy(isRefreshing = true) } lightningRepo.sync() - blocktankRepo.refreshOrders() delay(500.milliseconds) _uiState.update { it.copy(isRefreshing = false) } } diff --git a/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsViewModel.kt b/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsViewModel.kt index 74bc1d85be..39d226b908 100644 --- a/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsViewModel.kt +++ b/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsViewModel.kt @@ -150,7 +150,6 @@ class LightningConnectionsViewModel @Inject constructor( suspend fun refreshObservedState() { lightningRepo.sync() - blocktankRepo.refreshOrders() loadClosedChannels() } diff --git a/app/src/main/java/to/bitkit/utils/Errors.kt b/app/src/main/java/to/bitkit/utils/Errors.kt index 820c1d3e33..b80be93750 100644 --- a/app/src/main/java/to/bitkit/utils/Errors.kt +++ b/app/src/main/java/to/bitkit/utils/Errors.kt @@ -25,7 +25,6 @@ sealed class ServiceError(message: String) : AppError(message) { class BlocktankInfoUnavailable : ServiceError("Blocktank info not available") class ChannelSizeExceedsMaximum : ServiceError("Channel size exceeds maximum") class CjitQuoteInvalid : ServiceError("CJIT quote is invalid") - class GeoBlocked : ServiceError("Geo blocked user") class NodeCapacityUnavailable : ServiceError("Additional spending capacity is unavailable") } diff --git a/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt index 885ce4e07b..2ff725e5f0 100644 --- a/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt @@ -331,9 +331,6 @@ class AppViewModel @Inject constructor( var splashVisible by mutableStateOf(true) private set - val isGeoBlocked = lightningRepo.lightningState.map { it.isGeoBlocked } - .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false) - val forceCloseRemainingDuration = transferRepo.forceCloseRemainingDuration .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null) @@ -644,9 +641,6 @@ class AppViewModel @Inject constructor( delay(AUTH_CHECK_SPLASH_DELAY_MS) splashVisible = false } - viewModelScope.launch { - lightningRepo.updateGeoBlockState() - } viewModelScope.launch { quickPayRepo.unhandledFailures.collect { notifyPaymentFailed((it as? QuickPayPaymentFailedError)?.reason) diff --git a/app/src/main/java/to/bitkit/viewmodels/BlocktankViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/BlocktankViewModel.kt index 2a973c91bc..9cbccb275e 100644 --- a/app/src/main/java/to/bitkit/viewmodels/BlocktankViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/BlocktankViewModel.kt @@ -44,7 +44,7 @@ class BlocktankViewModel @Inject constructor( fun refreshOrders() { viewModelScope.launch { - blocktankRepo.refreshOrders() + blocktankRepo.loadCachedOrders() } } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 2b29f6e8e8..f9b2d18d7d 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -168,7 +168,6 @@ استخدم محفظة أخرى رصيد الإنفاق أسهل خيار هو استخدام مدخرات Bitkit الخاصة بك أو إرسال بيتكوين من محفظة مختلفة. - لا يوفر Bitkit حاليًا خدمات Lightning في بلدك، لكن يمكنك الاتصال بعقد أخرى مباشرة. لا يوفر Bitkit حاليًا خدمات Lightning في بلدك، لكن يمكنك الاتصال بعقد أخرى مباشرة. موّل <accent>رصيد الإنفاق</accent> رمز QR لقناة LNURL @@ -285,7 +284,6 @@ قناة الإشعارات لخدمة عقدة Lightning في المقدمة إشعار عقدة Lightning فشل فتح القناة في الخلفية - فشل فتح القناة تم فتح القناة معلق جاهز للإرسال @@ -329,7 +327,6 @@ الحرية في\n<accent>جيبك</accent> يمنحك Bitkit مفاتيح إدارة أموالك. أنفق الآن أو ادخر للمستقبل. الخيار لك. مدفوعات\n<accent>فورية</accent> - *لا يوفر Bitkit حاليًا خدمات Lightning في بلدك، لكن يمكنك الاتصال بعقد أخرى. أنفق Bitcoin أسرع من أي وقت مضى. استمتع بمدفوعات فورية ورخيصة مع الأصدقاء والعائلة والتجار. الخصوصية\n<accent>ليست جريمة</accent> اسحب لإخفاء رصيدك، واستمتع بمدفوعات أكثر خصوصية، واحمِ محفظتك بتفعيل ميزات الأمان. 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 5843d0adc9..1fa14bec59 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -175,7 +175,6 @@ Usar Otra Billetera Saldo de gastos La forma más fácil es usar tus ahorros de Bitkit o enviar bitcoin desde una billetera diferente. - Bitkit no tiene soporte Lightning en tu región, pero puedes usar nodos externos manualmente. Bitkit no tiene soporte Lightning en tu región, pero puedes usar nodos externos manualmente. Fondee su <accent>saldo de gastos</accent> QR de canal LNURL @@ -293,7 +292,6 @@ Canal de notificación para el servicio en segundo plano del nodo Lightning Notificación del nodo Lightning El canal no se pudo abrir en segundo plano - Apertura del canal fallida Canal abierto Pendiente Listo para enviar @@ -337,7 +335,6 @@ Libertad en\n<accent>tu bolsillo</accent> Bitkit te da las llaves para manejar tu dinero. Gasta ahora o ahorra para después. Tú decides. Pagos\n<accent>al instante</accent> - Bitkit no tiene soporte Lightning en tu región, pero puedes usar nodos externos manualmente. Gasta bitcoin más rápido que nunca. Disfruta pagos instantáneos y baratos con amigos, familia y comercios. La privacidad\n<accent>no es un delito</accent> Tu dinero, tu privacidad. Desliza para ocultar tu saldo y gasta de forma más privada, sin rastreo de datos y sin KYC. diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 178924e27f..2614833cf6 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -172,7 +172,6 @@ Utilitza un altre moneder Balanç de despesa La forma més fàcil és utilitzar els teus estalvis de Bitkit o enviar bitcoin des d\'una altra cartera. - Bitkit no ofereix serveis Lightning al teu país, però encara pots connectar-te a altres nodes directament. Bitkit no ofereix serveis Lightning al teu país, però encara pots connectar-te a altres nodes directament. Finança el teu <accent>saldo de despesa</accent> QR de canal LNURL @@ -289,7 +288,6 @@ Canal de notificació per al servei en primer pla del node Lightning Notificació del node Lightning El canal no s\'ha pogut obrir en segon pla - Obertura de canal fallida Canal obert Pendent Llest per enviar @@ -333,7 +331,6 @@ Llibertat a\n<accent>la teva butxaca</accent> Bitkit et dona les claus per gestionar els teus diners. Gasta ara o estalvia per després. La decisió és teva. Pagaments\n<accent>instantanis</accent> - *Bitkit no ofereix serveis Lightning al teu país, però encara pots connectar-te a altres nodes. Gasta bitcoin més ràpid que mai. Gaudeix de pagaments instantanis i barats amb amics, família i comerciants. La privacitat\n<accent>no és un crim</accent> Llisca per amagar el teu saldo, gaudeix de pagaments més privats i protegeix la teva cartera habilitant funcions de seguretat. diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index fc10ab24b2..b14a2780ea 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -175,7 +175,6 @@ Použijte jinou peněženku Dostupný zůstatek Nejjednodušší možností je použít úspory z Bitkitu nebo poslat bitcoin z jiné peněženky. - Bitkit v současné době neposkytuje služby lightning ve vaší zemi, ale stále se můžete připojit k jiným uzlům přímo. Bitkit v současné době neposkytuje služby lightning ve vaší zemi, ale stále se můžete připojit k jiným uzlům přímo. Navyšte svůj <accent>disponibilní zůstatek</accent> LNURL kanál QR @@ -292,7 +291,6 @@ Notifikační kanál pro službu Lightning uzlu na pozadí Notifikace Lightning uzlu Kanál se nepodařilo otevřít na pozadí - Otevření kanálu selhalo Kanál otevřen Čekající Připraveno k odeslání @@ -336,7 +334,6 @@ Svoboda ve\n<accent>vaší kapse</accent> Bitkit vám předá klíče ke správě vašich peněz. Utrácejte teď nebo si spořte na později. Volba je na vás. Okamžité\n<accent>platby</accent> - *Bitkit v současné době neposkytuje služby lightning ve vaší zemi, ale stále se můžete připojit k jiným uzlům. Utrácejte bitcoiny rychleji než kdykoli předtím. Užívejte si okamžité a levné platby s přáteli, rodinou a obchodníky. Soukromí\n<accent>není zločin</accent> Přejetím prstem můžete skrýt zůstatek, využívat soukromější platby a chránit peněženku zapnutím bezpečnostních funkcí. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 0a1b15ecde..45047f09d9 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -113,7 +113,6 @@ Spending Balance Fund your <accent>spending balance</accent> Am einfachsten ist es, deine Bitkit-Ersparnisse zu verwenden oder Bitcoin von einer anderen Wallet zu senden. - Bitkit bietet derzeit keine Lightning-Dienste in deinem Land an, aber du kannst dich trotzdem direkt mit anderen Knotenpunkten verbinden. Bitkit bietet derzeit keine Lightning-Dienste in deinem Land an, aber du kannst dich trotzdem direkt mit anderen Knotenpunkten verbinden. Von Sparkonto übertragen Anderes Wallet verwenden @@ -292,7 +291,6 @@ Benachrichtigungskanal für den Lightning-Node-Vordergrunddienst Lightning-Node-Benachrichtigung Channel konnte im Hintergrund nicht geöffnet werden - Channel-Öffnung fehlgeschlagen Channel geöffnet Ausstehend Bereit zum Senden @@ -317,7 +315,6 @@ Bitkit gibt dir die Schlüssel zu deinem Geld. Gib es aus oder spare für später. Die Entscheidung liegt bei dir. Instant\n<accent>payments</accent> Gib bitcoin aus, schneller denn je zuvor. Genieße sofortige und günstige Zahlungen mit Freunden, Familie und Händlern. - Bitkit bietet derzeit keine Lightning-Dienste in deinem Land an, aber du kannst dich trotzdem direkt mit anderen Knotenpunkten verbinden. Privacy is\n<accent>not a crime</accent> Wische, um deinen Kontostand zu verbergen, genieße mehr private Zahlungen und schütze dein Wallet, indem du Sicherheitsfunktionen aktivierst. Your keys,\n<accent>your coins</accent> diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 11a0241a62..c198177778 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -168,7 +168,6 @@ Χρήση άλλου πορτοφολιού Υπόλοιπο δαπανών Ο ευκολότερος τρόπος είναι να χρησιμοποιήσεις τις αποταμιεύσεις Bitkit ή να στείλεις bitcoin από διαφορετικό πορτοφόλι. - Το Bitkit δεν παρέχει προς το παρόν υπηρεσίες Lightning στη χώρα σου, αλλά μπορείς να συνδεθείς απευθείας σε άλλους κόμβους. Το Bitkit δεν παρέχει προς το παρόν υπηρεσίες Lightning στη χώρα σου, αλλά μπορείς να συνδεθείς απευθείας σε άλλους κόμβους. Χρηματοδότησε το <accent>υπόλοιπο δαπανών</accent> LNURL Channel QR @@ -286,7 +285,6 @@ Κανάλι ειδοποιήσεων για υπηρεσία προσκηνίου κόμβου Lightning Ειδοποίηση κόμβου Lightning Το κανάλι απέτυχε να ανοίξει στο παρασκήνιο - Αποτυχία ανοίγματος καναλιού Το κανάλι άνοιξε Εκκρεμεί Έτοιμο για αποστολή @@ -330,7 +328,6 @@ Ελευθερία στην\n<accent>τσέπη σου</accent> Το Bitkit σου δίνει τα κλειδιά για να διαχειριστείς τα χρήματά σου. Ξόδεψε τώρα ή αποταμίευσε για αργότερα. Η επιλογή είναι δική σου. Άμεσες\n<accent>πληρωμές</accent> - *Το Bitkit δεν παρέχει προς το παρόν υπηρεσίες Lightning στη χώρα σου, αλλά μπορείς να συνδεθείς σε άλλους κόμβους. Ξόδεψε bitcoin πιο γρήγορα από ποτέ. Απόλαυσε άμεσες και φθηνές πληρωμές με φίλους, οικογένεια και εμπόρους. Το απόρρητο δεν\n<accent>είναι έγκλημα</accent> Τα χρήματά σας, η ιδιωτικότητά σας. Σύρετε για να κρύψετε το υπόλοιπό σας και ξοδέψτε πιο ιδιωτικά, χωρίς παρακολούθηση δεδομένων και μηδενικό KYC. diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 2f44322514..4c14eff7c9 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -174,7 +174,6 @@ Usar Otro Monedero Saldo de gastos La forma más fácil es usar sus ahorros en Bitkit o enviar bitcoin desde un monedero diferente. - Bitkit actualmente no ofrece servicios Lightning en tu país, pero aún puedes conectarte directamente a otros nodos. Bitkit actualmente no ofrece servicios Lightning en tu país, pero aún puedes conectarte directamente a otros nodos. Rellene su <accent>saldo de gastos</accent> QR de canal LNURL @@ -292,7 +291,6 @@ Canal de notificación para el servicio en primer plano del nodo Lightning Notificación del nodo Lightning Error al abrir canal en segundo plano - Error al abrir canal Canal abierto Pendiente Listo para enviar @@ -336,7 +334,6 @@ Libertad en\n<accent>tu bolsillo</accent> Bitkit te da las llaves para gestionar tu dinero. Gasta ahora o ahorra para después. La elección es tuya. Pagos\n<accent>instantáneos</accent> - *Bitkit actualmente no ofrece servicios Lightning en tu país, pero aún puedes conectarte a otros nodos. Gasta bitcoin más rápido que nunca. Disfruta de pagos instantáneos y baratos con amigos, familia y comerciantes. La privacidad\n<accent>no es un crimen</accent> Tu dinero, tu privacidad. Desliza para ocultar tu saldo y gasta de forma más privada, sin rastreo de datos y sin KYC. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index df3af1acab..3bb3e9ec96 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -140,7 +140,6 @@ Comisiones Algunas conexiones no pudieron cerrarse. No se pueden transferir los fondos de vuelta a ahorros. Por favor, inténtalo de nuevo. - Bitkit no ofrece actualmente servicios Lightning en tu país, pero puedes conectarte a otros nodos directamente. Bitkit no ofrece actualmente servicios Lightning en tu país, pero puedes conectarte a otros nodos directamente. QR de Canal LNURL Configuración Manual @@ -290,7 +289,6 @@ Canal de notificaciones para el servicio en segundo plano del nodo Lightning Notificación del nodo Lightning El canal no pudo abrirse en segundo plano - Apertura de canal fallida Canal abierto Pendiente Listo para enviar @@ -312,7 +310,6 @@ Libertad en\n<accent>tu bolsillo</accent> Bitkit te da las llaves para gestionar tu dinero. Gasta ahora o ahorra para después. La elección es tuya. Pagos\n<accent>instantáneos</accent> - *Bitkit no ofrece actualmente servicios Lightning en tu país, pero puedes conectarte a otros nodos. Gasta bitcoin más rápido que nunca. Disfruta de pagos instantáneos y baratos con amigos, familia y comerciantes. La privacidad\n<accent>no es un crimen</accent> Tu dinero, tu privacidad. Desliza para ocultar tu saldo y gasta de forma más privada, sin rastreo de datos y sin KYC. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 89d839761c..870730e14b 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -112,7 +112,6 @@ Solde Dépenses Financez votre <accent>solde Dépenses</accent> Le plus simple est d\'utiliser vos économies Bitkit ou d\'envoyer des bitcoins depuis un autre portefeuille. - Bitkit ne fournit pas actuellement de services Lightning dans votre pays, mais vous pouvez toujours vous connecter directement à d\'autres nœuds. Bitkit ne fournit pas actuellement de services Lightning dans votre pays, mais vous pouvez toujours vous connecter directement à d\'autres nœuds. Transfert depuis l\'épargne Utiliser un autre portefeuille @@ -292,7 +291,6 @@ Canal de notification pour le service de nœud Lightning en arrière-plan Notification du nœud Lightning Échec de l\'ouverture du canal en arrière-plan - Échec de l\'ouverture du canal Canal ouvert En attente Prêt à envoyer @@ -317,7 +315,6 @@ Bitkit vous donne les clés pour contrôler votre argent, votre profil et vos contacts. Prenez le contrôle et devenez sans frontières. Paiements \n<accent>Instantanés</accent> Dépensez des bitcoins plus rapidement que jamais. Profitez de paiements instantanés et bon marché avec vos amis, votre famille et les commerçants. - *Bitkit ne fournit pas actuellement de services Lightning dans votre pays, mais vous pouvez toujours vous connecter à d\'autres nœuds. La vie privée \n<accent>n\'est pas un crime</accent> Votre argent, votre vie privée. Balayez pour masquer votre solde et dépensez de manière plus privée, sans suivi de données et sans KYC. Vos clés, \n<accent>votre argent</accent> diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 70e5a9e2b4..81d3f607c6 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -173,7 +173,6 @@ Usa un altro portafoglio Conto di Spesa Il modo più semplice è usare i tuoi risparmi Bitkit o inviare bitcoin da un portafoglio diverso. - Bitkit attualmente non fornisce servizi Lightning nel tuo paese, ma puoi comunque connetterti direttamente ad altri nodi. Bitkit attualmente non fornisce servizi Lightning nel tuo paese, ma puoi comunque connetterti direttamente ad altri nodi. Manda fondi al tuo <accent>conto di spesa</accent> QR Canale LNURL @@ -291,7 +290,6 @@ Canale di notifica per il servizio in primo piano del nodo Lightning Notifica nodo Lightning Apertura canale in background non riuscita - Apertura canale non riuscita Canale aperto In attesa Pronto per inviare @@ -335,7 +333,6 @@ La libertà\n<accent>nella tua tasca</accent> Bitkit ti dà le chiavi per controllare i tuoi soldi, il tuo profilo e i tuoi contatti. Prendi il controllo e diventa senza confini. Pagamenti\n<accent>istantanei</accent> - *Bitkit attualmente non fornisce servizi Lightning nel tuo paese, ma puoi comunque connetterti ad altri nodi. Spendi bitcoin più velocemente che mai. Goditi pagamenti immediati ed economici con amici, familiari e commercianti. La privacy\n<accent>non è un crimine</accent> I tuoi soldi, la tua privacy. Scorri per nascondere il saldo e spendi in modo più privato, senza tracciamento dei dati e zero KYC. diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index a7b989bd38..242295bda1 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -172,7 +172,6 @@ Andere wallet gebruiken Bestedingssaldo De makkelijkste optie is om je Bitkit-spaargeld te gebruiken of bitcoin te versturen vanuit een andere wallet. - Bitkit biedt momenteel geen Lightning-services in jouw land, maar je kunt nog steeds rechtstreeks verbinding maken met andere nodes. Bitkit biedt momenteel geen Lightning-services in jouw land, maar je kunt nog steeds rechtstreeks verbinding maken met andere nodes. Vul je <accent>bestedingssaldo</accent> LNURL Kanaal QR @@ -290,7 +289,6 @@ Notificatiekanaal voor Lightning node voorgrondservice Lightning node notificatie Kanaal kon niet op de achtergrond worden geopend - Kanaal openen mislukt Kanaal geopend In behandeling Klaar om te versturen @@ -334,7 +332,6 @@ Vrijheid in\n<accent>je zak</accent> Bitkit geeft u de sleutels om uw geld, profiel en contacten te beheren. Neem de controle en word grenzeloos. Directe\n<accent>betalingen</accent> - *Bitkit biedt momenteel geen Lightning-services in jouw land, maar je kunt nog steeds verbinding maken met andere nodes. Besteed bitcoin sneller dan ooit. Geniet van directe en goedkope betalingen met vrienden, familie en winkeliers. Privacy is\n<accent>geen misdaad</accent> Uw geld, uw privacy. Veeg om uw saldo te verbergen en besteed meer privé, zonder datatracking en zonder KYC. diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index f87d1bb890..1ce4472c99 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -114,7 +114,6 @@ Saldo wydatków Zasil swoje <accent>saldo wydatków</accent> Najłatwiejszą opcją jest użycie oszczędności Bitkit lub wysłanie bitcoinów z innego portfela. - Bitkit nie świadczy obecnie usług Lightning w Twoim kraju, ale nadal możesz łączyć się z innymi węzłami. Bitkit nie świadczy obecnie usług Lightning w Twoim kraju, ale nadal możesz łączyć się bezpośrednio z innymi węzłami. Prześlij z Oszczędności Użyj innego portfela @@ -294,7 +293,6 @@ Kanał powiadomień dla usługi Lightning w tle Powiadomienie węzła Lightning Nie udało się otworzyć kanału w tle - Otwarcie kanału nie powiodło się Kanał otwarty Oczekujące Gotowy do wysłania @@ -319,7 +317,6 @@ Bitkit przekazuje Ci klucze do kontrolowania Twoich pieniędzy, profilu i kontaktów. Przejmij kontrolę i stań się wolnym. Płatności\n<accent>natychmiastowe</accent> Wydawaj bitcoiny szybciej niż kiedykolwiek. Ciesz się natychmiastowymi i tanimi płatnościami z przyjaciółmi, rodziną i sprzedawcami. - Bitkit nie świadczy obecnie usług Lightning w Twoim kraju, ale nadal możesz łączyć się z innymi węzłami. Prywatność nie jest\n<accent>przestępstwem</accent> Twoje pieniądze, Twoja prywatność. Przesuń, aby ukryć saldo i wydawaj bardziej prywatnie, bez śledzenia danych i bez KYC. Twoje klucze,\n<accent>twoje monety</accent> diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index cc12b84350..26d6ac8146 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -176,7 +176,6 @@ Transferir de Outra Carteira Saldo de Gastos A forma mais fácil é usar suas economias do Bitkit ou enviar bitcoin de uma carteira diferente. - Bitkit não fornece serviços de Lightning no seu paús atualmente, mas você ainda pode se conectar diretamente a outros nodes Bitkit não fornece serviços de Lightning no seu paús atualmente, mas você ainda pode se conectar diretamente a outros nodes Transfira para seu <accent>saldo de gastos</accent> QR Canal LNURL @@ -294,7 +293,6 @@ Canal de notificação para serviço em primeiro plano do nó Lightning Notificação do nó Lightning Falha ao abrir canal em segundo plano - Falha ao abrir canal Canal aberto Pendente Pronto para enviar @@ -338,7 +336,6 @@ Liberdade no\n<accent>seu bolso</accent> O Bitkit entrega as chaves para controlar seu dinheiro, perfil e contatos. Assuma o controle e torne-se sem fronteiras. Pagamentos\n<accent>instantâneos</accent> - *No momento, a Bitkit não fornece serviços Lightning em seu país, mas você ainda pode se conectar a outros nós. Envie bitcoin mais rápido do que nunca. Aproveite os pagamentos instantâneos e baratos com amigos, familiares e comerciantes. Privacidade\n<accent>não é crime</accent> Seu dinheiro, sua privacidade. Deslize para ocultar seu saldo e gaste de forma mais privada, sem rastreamento de dados e zero KYC. diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 4b48f030ad..e2628d935c 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -109,7 +109,6 @@ Saldo de Gastos Transfira para seu <accent>saldo de gastos</accent> A forma mais fácil é usar as suas poupanças Bitkit ou enviar bitcoin de uma carteira diferente. - Bitkit não fornece serviços de Lightning no seu paús atualmente, mas você ainda pode se conectar diretamente a outros nodes Bitkit não fornece serviços de Lightning no seu paús atualmente, mas você ainda pode se conectar diretamente a outros nodes Transferir do Saldo de Poupança Transferir de Outra Carteira @@ -287,7 +286,6 @@ Canal de notificação para o serviço em primeiro plano do nó Lightning Notificação do nó Lightning Canal falhou ao abrir em segundo plano - Falha na abertura do canal Canal aberto Pendente Pronto para enviar @@ -312,7 +310,6 @@ A Bitkit lhe dá as chaves para gerenciar seu dinheiro. Gaste agora ou economize para depois. A escolha é sua. Pagamentos\n<accent>instantâneos</accent> Envie bitcoin mais rápido do que nunca. Aproveite os pagamentos instantâneos e baratos com amigos, familiares e comerciantes. - *No momento, a Bitkit não fornece serviços Lightning em seu país, mas você ainda pode se conectar a outros nós. Privacidade\n<accent>não é crime</accent> Deslize seu saldo para ocultá-lo, desfrute de pagamentos mais privados e proteja sua carteira ativando os recursos de segurança. Suas chaves,\n<accent>suas moedas</accent> diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 2ca482b3ea..cc5f122d1f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -184,7 +184,6 @@ Использовать Другой Кошелёк Баланс Расходов Самый простой вариант — использовать ваши сбережения Bitkit или отправить биткоин с другого кошелька. - Bitkit в настоящее время не предоставляет услуги Lightning в вашей стране, но вы всё ещё можете подключаться к другим нодам напрямую. Bitkit в настоящее время не предоставляет услуги Lightning в вашей стране, но вы всё ещё можете подключаться к другим нодам напрямую. Пополните свой <accent>Баланс Расходов</accent> QR LNURL Канала @@ -307,7 +306,6 @@ Канал уведомлений для фоновой службы Lightning ноды Уведомление Lightning ноды Не удалось открыть канал в фоновом режиме - Не удалось открыть канал Канал открыт В ожидании Готов к отправке @@ -351,7 +349,6 @@ Свобода в\n<accent>вашем кармане</accent> Bitkit даёт вам ключи для управления вашими деньгами, профилем и контактами. Возьмите контроль и станьте безграничными. Мгновенные\n<accent>платежи</accent> - *Bitkit в настоящее время не предоставляет услуги Lightning в вашей стране, но вы всё ещё можете подключаться к другим нодам. Тратьте биткойны быстрее, чем когда-либо. Наслаждайтесь мгновенными и недорогими платежами с друзьями, семьёй и продавцами. Конфиденциальность — это\n<accent>не преступление</accent> Ваши деньги, ваша конфиденциальность. Проведите пальцем, чтобы скрыть баланс и тратить более конфиденциально, без отслеживания данных и KYC. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4eb2da1cf0..fcc3143b05 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -321,7 +321,6 @@ Use Other Wallet Spending Balance The easiest option is to use your Bitkit savings or send bitcoin from a different wallet. - Bitkit does not currently provide Lightning services in your country, but you can still connect to other nodes directly. Bitkit does not currently provide Lightning services in your country, but you can still connect to other nodes directly. Fund your <accent>spending balance</accent> LNURL Channel QR @@ -461,7 +460,6 @@ Notification channel for Lightning node foreground service Lightning node notification Channel failed to open in the background - Channel open failed Channel opened Pending Ready to send @@ -506,7 +504,6 @@ Freedom in\n<accent>your pocket</accent> Bitkit hands you the keys to control your money, profile, and contacts. Take charge and become borderless. Instant\n<accent>payments</accent> - *Bitkit does not currently provide Lightning services in your country, but you can still connect to other nodes. Enjoy instant and cheap payments with friends, family, and merchants on the Lightning Network. Privacy is\n<accent>not a crime</accent> Your money, your privacy. Swipe to hide your balance and spend more privately, with no data tracking and zero KYC. diff --git a/app/src/test/java/to/bitkit/repositories/BlocktankRepoTest.kt b/app/src/test/java/to/bitkit/repositories/BlocktankRepoTest.kt index d86a32401e..4f2107fdd2 100644 --- a/app/src/test/java/to/bitkit/repositories/BlocktankRepoTest.kt +++ b/app/src/test/java/to/bitkit/repositories/BlocktankRepoTest.kt @@ -1,7 +1,6 @@ package to.bitkit.repositories import app.cash.turbine.test -import com.synonym.bitkitcore.AddressType import com.synonym.bitkitcore.CJitStateEnum import com.synonym.bitkitcore.CreateOrderOptions import com.synonym.bitkitcore.FundingTx @@ -11,11 +10,6 @@ import com.synonym.bitkitcore.IBtInfo import com.synonym.bitkitcore.IBtInfoOptions import com.synonym.bitkitcore.IBtOrder import com.synonym.bitkitcore.IcJitEntry -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.async -import kotlinx.coroutines.cancel -import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import org.junit.Before @@ -25,25 +19,20 @@ import org.lightningdevkit.ldknode.OutPoint import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doReturn -import org.mockito.kotlin.doSuspendableAnswer import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import to.bitkit.data.AppCacheData -import to.bitkit.data.BlocktankRefundAddress import to.bitkit.data.CacheStore import to.bitkit.models.BlocktankBackupV1 -import to.bitkit.services.AddressDerivationInfo import to.bitkit.services.BlocktankService import to.bitkit.services.CoreService import to.bitkit.services.LightningService import to.bitkit.test.BaseUnitTest -import to.bitkit.utils.AppError import to.bitkit.utils.ServiceError import kotlin.test.assertEquals -import kotlin.test.assertFailsWith import kotlin.test.assertIs import kotlin.test.assertNull import kotlin.test.assertTrue @@ -72,7 +61,6 @@ class BlocktankRepoTest : BaseUnitTest() { } whenever(currencyRepo.currencyState).thenReturn(MutableStateFlow(CurrencyState())) whenever(coreService.blocktank).thenReturn(blocktankService) - whenever { coreService.isGeoBlocked() }.thenReturn(false) whenever { coreService.isAddressUsed(any()) }.thenReturn(false) whenever(lightningService.nodeId).thenReturn("node-id") whenever { lightningService.sign(any()) }.thenReturn("signature") @@ -81,7 +69,6 @@ class BlocktankRepoTest : BaseUnitTest() { whenever { blocktankService.info(refresh = true) }.thenReturn(mock()) whenever { blocktankService.orders(refresh = false) }.thenReturn(emptyList()) - whenever { blocktankService.orders(refresh = true) }.thenReturn(emptyList()) whenever { blocktankService.cjitEntries(refresh = false) }.thenReturn(emptyList()) whenever { blocktankService.cjitEntries(refresh = true) }.thenReturn(emptyList()) @@ -95,28 +82,26 @@ class BlocktankRepoTest : BaseUnitTest() { lightningService = lightningService, currencyRepo = currencyRepo, cacheStore = cacheStore, - enablePolling = false, lightningRepo = lightningRepo, ) } @Test - fun `createOrder sends a persisted native SegWit refund address`() = test { - val addressInfo = AddressDerivationInfo(address = "bcrt1qrefund0", index = 0) + fun `createOrder submits the order without a refund address`() = test { val order = mock() - whenever(lightningRepo.newAddressInfoForType(AddressType.P2WPKH)).thenReturn(Result.success(addressInfo)) whenever(blocktankService.newOrder(any(), any(), any())).thenReturn(order) sut = createSut() val result = sut.createOrder(spendingBalanceSats = 50_000u, receivingBalanceSats = 100_000u) assertEquals(order, result.getOrThrow()) - assertEquals(BlocktankRefundAddress(addressInfo.address, 0), cacheData.value.blocktankRefundAddress) val options = argumentCaptor() verify(blocktankService).newOrder(any(), any(), options.capture()) - assertEquals(addressInfo.address, options.firstValue.refundOnchainAddress) - verify(lightningRepo).newAddressInfoForType(AddressType.P2WPKH) - verify(coreService).isAddressUsed(addressInfo.address) + assertNull(options.firstValue.refundOnchainAddress) + verify(lightningRepo, never()).newAddressInfoForType(any()) + verify(lightningRepo, never()).addressInfoForType(any(), any()) + verify(lightningRepo, never()).revealReceiveAddresses(any(), any()) + assertNull(cacheData.value.blocktankRefundAddress) } @Test @@ -139,256 +124,6 @@ class BlocktankRepoTest : BaseUnitTest() { assertNull(cacheData.value.blocktankRefundAddress) } - @Test - fun `failed and repeated orders reuse one unused persisted refund address across repo instances`() = test { - val addressInfo = AddressDerivationInfo(address = "bcrt1qrefund0", index = 0) - val order = mock() - whenever(lightningRepo.newAddressInfoForType(AddressType.P2WPKH)).thenReturn(Result.success(addressInfo)) - whenever(lightningRepo.addressInfoForType(AddressType.P2WPKH, 0)).thenReturn(Result.success(addressInfo)) - whenever(blocktankService.newOrder(any(), any(), any())) - .thenThrow(RuntimeException("backend unavailable")) - .thenReturn(order) - sut = createSut() - - assertTrue(sut.createOrder(50_000u).isFailure) - sut = createSut() - repeat(3) { assertEquals(order, sut.createOrder(50_000u).getOrThrow()) } - - verify(lightningRepo, times(1)).newAddressInfoForType(AddressType.P2WPKH) - verify(lightningRepo, times(3)).addressInfoForType(AddressType.P2WPKH, 0) - verify(lightningRepo, times(3)).revealReceiveAddresses(0, AddressType.P2WPKH) - verify(coreService, times(4)).isAddressUsed(addressInfo.address) - } - - @Test - fun `recorded refund payment skips used candidates and rotates to the first unused address`() = test { - val oldInfo = AddressDerivationInfo(address = "bcrt1qrefund0", index = 0) - val firstUsedInfo = AddressDerivationInfo(address = "bcrt1qrefund1", index = 1) - val secondUsedInfo = AddressDerivationInfo(address = "bcrt1qrefund2", index = 2) - val unusedInfo = AddressDerivationInfo(address = "bcrt1qrefund3", index = 3) - val order = mock() - cacheData.value = AppCacheData(blocktankRefundAddress = BlocktankRefundAddress(oldInfo.address, 0)) - whenever(lightningRepo.addressInfoForType(AddressType.P2WPKH, 0)).thenReturn(Result.success(oldInfo)) - whenever(lightningRepo.addressInfoForType(AddressType.P2WPKH, 3)).thenReturn(Result.success(unusedInfo)) - whenever(coreService.isAddressUsed(oldInfo.address)).thenReturn(true) - whenever(coreService.isAddressUsed(firstUsedInfo.address)).thenReturn(true) - whenever(coreService.isAddressUsed(secondUsedInfo.address)).thenReturn(true) - whenever(coreService.isAddressUsed(unusedInfo.address)).thenReturn(false) - whenever(lightningRepo.newAddressInfoForType(AddressType.P2WPKH)) - .thenReturn(Result.success(firstUsedInfo)) - .thenReturn(Result.success(secondUsedInfo)) - .thenReturn(Result.success(unusedInfo)) - whenever(blocktankService.newOrder(any(), any(), any())).thenReturn(order) - sut = createSut() - - assertEquals(order, sut.createOrder(50_000u).getOrThrow()) - assertEquals(order, sut.createOrder(50_000u).getOrThrow()) - - verify(lightningRepo, times(3)).newAddressInfoForType(AddressType.P2WPKH) - verify(cacheStore, times(1)).update(any()) - assertEquals(BlocktankRefundAddress(unusedInfo.address, 3), cacheData.value.blocktankRefundAddress) - val options = argumentCaptor() - verify(blocktankService, times(2)).newOrder(any(), any(), options.capture()) - assertEquals(listOf(unusedInfo.address, unusedInfo.address), options.allValues.map { it.refundOnchainAddress }) - } - - @Test - fun `concurrent orders allocate one shared refund address`() = test { - val allocationStarted = CompletableDeferred() - val allowAllocation = CompletableDeferred() - val addressInfo = AddressDerivationInfo(address = "bcrt1qrefund0", index = 0) - val order = mock() - whenever(lightningRepo.newAddressInfoForType(AddressType.P2WPKH)).doSuspendableAnswer { - allocationStarted.complete(Unit) - allowAllocation.await() - Result.success(addressInfo) - } - whenever(lightningRepo.addressInfoForType(AddressType.P2WPKH, 0)).thenReturn(Result.success(addressInfo)) - whenever(blocktankService.newOrder(any(), any(), any())).thenReturn(order) - sut = createSut() - - val first = async { sut.createOrder(50_000u) } - allocationStarted.await() - val second = async { sut.createOrder(50_000u) } - allowAllocation.complete(Unit) - - assertEquals(order, first.await().getOrThrow()) - assertEquals(order, second.await().getOrThrow()) - verify(lightningRepo, times(1)).newAddressInfoForType(AddressType.P2WPKH) - } - - @Test - fun `refund address allocation failure blocks order submission`() = test { - whenever(lightningRepo.newAddressInfoForType(AddressType.P2WPKH)) - .thenReturn(Result.failure(RuntimeException("node persistence failed"))) - sut = createSut() - - assertTrue(sut.createOrder(50_000u).isFailure) - - verify(blocktankService, never()).newOrder(any(), any(), any()) - } - - @Test - fun `refund address allocation stops after twenty used candidates`() = test { - val oldInfo = AddressDerivationInfo(address = "bcrt1qrefund0", index = 0) - val candidates = (1..20).map { - AddressDerivationInfo(address = "bcrt1qrefund$it", index = it) - } - cacheData.value = AppCacheData(blocktankRefundAddress = BlocktankRefundAddress(oldInfo.address, 0)) - whenever(lightningRepo.addressInfoForType(AddressType.P2WPKH, 0)).thenReturn(Result.success(oldInfo)) - whenever(coreService.isAddressUsed(oldInfo.address)).thenReturn(true) - candidates.forEach { whenever(coreService.isAddressUsed(it.address)).thenReturn(true) } - candidates.fold( - initial = whenever(lightningRepo.newAddressInfoForType(AddressType.P2WPKH)), - ) { stubbing, candidate -> stubbing.thenReturn(Result.success(candidate)) } - sut = createSut() - - val error = assertIs(sut.createOrder(50_000u).exceptionOrNull()) - - assertEquals("Failed to allocate an unused Blocktank refund address", error.message) - verify(lightningRepo, times(20)).newAddressInfoForType(AddressType.P2WPKH) - verify(cacheStore, never()).update(any()) - assertEquals(BlocktankRefundAddress(oldInfo.address, 0), cacheData.value.blocktankRefundAddress) - verify(blocktankService, never()).newOrder(any(), any(), any()) - } - - @Test - fun `refund address candidate usage lookup failure preserves cached pointer and blocks submission`() = test { - val oldInfo = AddressDerivationInfo(address = "bcrt1qrefund0", index = 0) - val candidate = AddressDerivationInfo(address = "bcrt1qrefund1", index = 1) - cacheData.value = AppCacheData(blocktankRefundAddress = BlocktankRefundAddress(oldInfo.address, 0)) - whenever(lightningRepo.addressInfoForType(AddressType.P2WPKH, 0)).thenReturn(Result.success(oldInfo)) - whenever(coreService.isAddressUsed(oldInfo.address)).thenReturn(true) - whenever(coreService.isAddressUsed(candidate.address)).thenThrow(RuntimeException("activity lookup failed")) - whenever(lightningRepo.newAddressInfoForType(AddressType.P2WPKH)).thenReturn(Result.success(candidate)) - sut = createSut() - - assertTrue(sut.createOrder(50_000u).isFailure) - - verify(lightningRepo, times(1)).newAddressInfoForType(AddressType.P2WPKH) - verify(cacheStore, never()).update(any()) - assertEquals(BlocktankRefundAddress(oldInfo.address, 0), cacheData.value.blocktankRefundAddress) - verify(blocktankService, never()).newOrder(any(), any(), any()) - } - - @Test - fun `refund address allocation rejects a nonadvancing candidate index`() = test { - val oldInfo = AddressDerivationInfo(address = "bcrt1qrefund4", index = 4) - val repeatedIndexCandidate = AddressDerivationInfo(address = "bcrt1qrefund-repeated", index = 4) - cacheData.value = AppCacheData(blocktankRefundAddress = BlocktankRefundAddress(oldInfo.address, 4)) - whenever(lightningRepo.addressInfoForType(AddressType.P2WPKH, 4)).thenReturn(Result.success(oldInfo)) - whenever(coreService.isAddressUsed(oldInfo.address)).thenReturn(true) - whenever(lightningRepo.newAddressInfoForType(AddressType.P2WPKH)) - .thenReturn(Result.success(repeatedIndexCandidate)) - sut = createSut() - - assertTrue(sut.createOrder(50_000u).isFailure) - - verify(lightningRepo, times(1)).newAddressInfoForType(AddressType.P2WPKH) - verify(coreService, never()).isAddressUsed(repeatedIndexCandidate.address) - verify(cacheStore, never()).update(any()) - assertEquals(BlocktankRefundAddress(oldInfo.address, 4), cacheData.value.blocktankRefundAddress) - verify(blocktankService, never()).newOrder(any(), any(), any()) - } - - @Test - fun `refund address allocation cancellation blocks order submission and propagates`() = test { - whenever(lightningRepo.newAddressInfoForType(AddressType.P2WPKH)).doSuspendableAnswer { - throw CancellationException("cancelled") - } - sut = createSut() - - assertFailsWith { sut.createOrder(50_000u) } - - verify(blocktankService, never()).newOrder(any(), any(), any()) - } - - @Test - fun `cancellation after refund cache persistence blocks order submission`() = test { - val addressInfo = AddressDerivationInfo(address = "bcrt1qrefund0", index = 0) - whenever(lightningRepo.newAddressInfoForType(AddressType.P2WPKH)).thenReturn(Result.success(addressInfo)) - whenever { cacheStore.update(any()) }.doSuspendableAnswer { - val transform = it.getArgument<(AppCacheData) -> AppCacheData>(0) - cacheData.value = transform(cacheData.value) - currentCoroutineContext().cancel() - } - sut = createSut() - - val result = async { sut.createOrder(50_000u) } - - assertFailsWith { result.await() } - verify(blocktankService, never()).newOrder(any(), any(), any()) - } - - @Test - fun `cached refund lookup failure blocks order submission`() = test { - cacheData.value = AppCacheData( - blocktankRefundAddress = BlocktankRefundAddress("bcrt1qrefund0", 0), - ) - whenever(lightningRepo.addressInfoForType(AddressType.P2WPKH, 0)) - .thenReturn(Result.failure(RuntimeException("lookup failed"))) - sut = createSut() - - assertTrue(sut.createOrder(50_000u).isFailure) - - verify(blocktankService, never()).newOrder(any(), any(), any()) - } - - @Test - fun `cached refund reveal failure blocks order submission`() = test { - val addressInfo = AddressDerivationInfo(address = "bcrt1qrefund0", index = 0) - cacheData.value = AppCacheData(blocktankRefundAddress = BlocktankRefundAddress(addressInfo.address, 0)) - whenever(lightningRepo.addressInfoForType(AddressType.P2WPKH, 0)).thenReturn(Result.success(addressInfo)) - whenever(lightningRepo.revealReceiveAddresses(0, AddressType.P2WPKH)) - .thenReturn(Result.failure(RuntimeException("reveal failed"))) - sut = createSut() - - assertTrue(sut.createOrder(50_000u).isFailure) - - verify(blocktankService, never()).newOrder(any(), any(), any()) - } - - @Test - fun `refund cache write failure blocks order submission`() = test { - val addressInfo = AddressDerivationInfo(address = "bcrt1qrefund0", index = 0) - whenever(lightningRepo.newAddressInfoForType(AddressType.P2WPKH)).thenReturn(Result.success(addressInfo)) - whenever { cacheStore.update(any()) }.thenThrow(RuntimeException("cache write failed")) - sut = createSut() - - assertTrue(sut.createOrder(50_000u).isFailure) - - verify(blocktankService, never()).newOrder(any(), any(), any()) - } - - @Test - fun `invalid cached refund index blocks order submission`() = test { - cacheData.value = AppCacheData( - blocktankRefundAddress = BlocktankRefundAddress("bcrt1qrefund", Int.MAX_VALUE.toLong() + 1), - ) - sut = createSut() - - assertTrue(sut.createOrder(50_000u).isFailure) - - verify(lightningRepo, never()).addressInfoForType(any(), any()) - verify(blocktankService, never()).newOrder(any(), any(), any()) - } - - @Test - fun `cached refund ownership mismatch blocks order submission`() = test { - cacheData.value = AppCacheData( - blocktankRefundAddress = BlocktankRefundAddress("bcrt1qwrongwallet", 0), - ) - whenever(lightningRepo.addressInfoForType(AddressType.P2WPKH, 0)).thenReturn( - Result.success(AddressDerivationInfo(address = "bcrt1qactivewallet", index = 0)), - ) - sut = createSut() - - assertTrue(sut.createOrder(50_000u).isFailure) - - verify(lightningRepo, never()).revealReceiveAddresses(any(), any()) - verify(blocktankService, never()).newOrder(any(), any(), any()) - } - @Test fun `refreshInfo updates state first from cache then server`() = test { sut = createSut() @@ -413,41 +148,36 @@ class BlocktankRepoTest : BaseUnitTest() { } @Test - fun `refreshOrders updates state first from cache then server`() = test { + fun `loadCachedOrders updates state from cache`() = test { sut = createSut() val cachedOrders = listOf(mock()) - val serverOrders = listOf(mock()) whenever { coreService.blocktank.orders(refresh = false) }.thenReturn(cachedOrders) - whenever { coreService.blocktank.orders(refresh = true) }.thenReturn(serverOrders) sut.blocktankState.test { awaitItem() // Skip initial state - sut.refreshOrders() + sut.loadCachedOrders() val cachedState = awaitItem() assertEquals(cachedOrders, cachedState.orders) - val serverState = awaitItem() - assertEquals(serverOrders, serverState.orders) - cancelAndIgnoreRemainingEvents() } } @Test - fun `refreshOrders filters paid orders based on cacheStore data`() = test { + fun `loadCachedOrders filters paid orders based on cacheStore data`() = test { sut = createSut() val expectedOrders = listOf(testOrder1) - whenever { coreService.blocktank.orders(refresh = true) }.thenReturn(expectedOrders) + whenever { coreService.blocktank.orders(refresh = false) }.thenReturn(expectedOrders) val orderId = "order1" whenever(cacheStore.data).thenReturn(flowOf(AppCacheData(paidOrders = mapOf(orderId to "txId")))) sut.blocktankState.test { awaitItem() // Skip initial state - sut.refreshOrders() + sut.loadCachedOrders() val state = awaitItem() assertEquals(expectedOrders, state.orders) @@ -459,7 +189,7 @@ class BlocktankRepoTest : BaseUnitTest() { @Test fun `openChannel updates state after open`() = test { - whenever { coreService.blocktank.orders(refresh = true) }.thenReturn(listOf(testOrder1)) + whenever { coreService.blocktank.orders(refresh = false) }.thenReturn(listOf(testOrder1)) sut = createSut() val orderId = "order1" val updatedOrder = mock().apply { whenever(id).thenReturn(orderId) } @@ -485,7 +215,7 @@ class BlocktankRepoTest : BaseUnitTest() { fun `getOrder returns order from state after refresh`() = test { sut = createSut() - whenever { coreService.blocktank.orders(refresh = true) }.thenReturn(listOf(testOrder1)) + whenever { coreService.blocktank.orders(refresh = false) }.thenReturn(listOf(testOrder1)) val result = sut.getOrder(testOrder1.id, refresh = true) assertTrue(result.isSuccess) @@ -498,11 +228,11 @@ class BlocktankRepoTest : BaseUnitTest() { val freshOrder = mock { on { id } doReturn "order1" } val uncachedOrder = mock { on { id } doReturn "order2" } val orderIds = listOf("order1", "order2") - whenever(blocktankService.orders(refresh = true)).thenReturn(listOf(staleOrder)) - whenever(blocktankService.orders(orderIds = orderIds, refresh = true)) + whenever(blocktankService.orders(refresh = false)).thenReturn(listOf(staleOrder)) + whenever(blocktankService.orders(orderIds = orderIds, refresh = false)) .thenReturn(listOf(freshOrder, uncachedOrder)) sut = createSut() - sut.refreshOrders() + sut.loadCachedOrders() val result = sut.fetchOrders(orderIds) @@ -513,11 +243,11 @@ class BlocktankRepoTest : BaseUnitTest() { @Test fun `fetchOrders returns failure and keeps cached orders when server throws`() = test { - whenever(blocktankService.orders(refresh = true)).thenReturn(listOf(testOrder1)) - whenever(blocktankService.orders(orderIds = listOf("order2"), refresh = true)) + whenever(blocktankService.orders(refresh = false)).thenReturn(listOf(testOrder1)) + whenever(blocktankService.orders(orderIds = listOf("order2"), refresh = false)) .thenThrow(RuntimeException("Network error")) sut = createSut() - sut.refreshOrders() + sut.loadCachedOrders() val result = sut.fetchOrders(listOf("order2")) @@ -535,11 +265,11 @@ class BlocktankRepoTest : BaseUnitTest() { } @Test - fun `refreshOrders returns failure when server throws`() { + fun `loadCachedOrders returns failure when cache read throws`() { sut = createSut() - whenever { coreService.blocktank.orders(refresh = true) }.thenThrow(RuntimeException("Network error")) + whenever { coreService.blocktank.orders(refresh = false) }.thenThrow(RuntimeException("Network error")) test { - val result = sut.refreshOrders() + val result = sut.loadCachedOrders() assertTrue(result.isFailure) } } @@ -551,7 +281,6 @@ class BlocktankRepoTest : BaseUnitTest() { val freshInfo = btInfo(maxChannelSizeSat = 50_000u) whenever(coreService.blocktank.info(refresh = false)).thenReturn(staleInfo) whenever(coreService.blocktank.info(refresh = true)).thenReturn(staleInfo, freshInfo) - whenever(coreService.isGeoBlocked()).thenReturn(false) whenever(lightningService.nodeId).thenReturn("node-id") sut.refreshInfo() @@ -568,7 +297,6 @@ class BlocktankRepoTest : BaseUnitTest() { whenever(coreService.blocktank.info(refresh = false)).thenReturn(cachedInfo) whenever(coreService.blocktank.info(refresh = true)).thenReturn(cachedInfo) .thenThrow(RuntimeException("Network error")) - whenever(coreService.isGeoBlocked()).thenReturn(false) whenever(lightningService.nodeId).thenReturn("node-id") sut.refreshInfo() @@ -599,7 +327,7 @@ class BlocktankRepoTest : BaseUnitTest() { @Test fun `getOrder returns failure when refresh fails`() { sut = createSut() - whenever { coreService.blocktank.orders(refresh = true) }.thenThrow(RuntimeException("Network error")) + whenever { coreService.blocktank.orders(refresh = false) }.thenThrow(RuntimeException("Network error")) test { val result = sut.getOrder(testOrder1.id, refresh = true) assertTrue(result.isFailure) @@ -624,7 +352,6 @@ class BlocktankRepoTest : BaseUnitTest() { whenever(staleEntry.channel).thenReturn(null) whenever(staleEntry.state).thenReturn(CJitStateEnum.CREATED) seedCjitEntries(staleEntry) - whenever(coreService.blocktank.cjitEntries(refresh = true)).thenReturn(listOf(staleEntry)) val channelDetails = mock() whenever(channelDetails.fundingTxo).thenReturn(OutPoint(txid = "channel-order-funding-tx", vout = 0u)) @@ -635,7 +362,6 @@ class BlocktankRepoTest : BaseUnitTest() { @Test fun `getCjitEntry matches the entry whose channel funding tx matches`() = test { sut = createSut() - seedCjitEntries(pendingCjitEntry()) val fundingTxId = "cjit-funding-tx" val matchingChannel = mock() whenever(matchingChannel.fundingTx).thenReturn(FundingTx(id = fundingTxId, vout = 0u)) @@ -645,7 +371,7 @@ class BlocktankRepoTest : BaseUnitTest() { whenever(matchingEntry.channel).thenReturn(matchingChannel) val otherEntry = mock() whenever(otherEntry.channel).thenReturn(otherChannel) - whenever(coreService.blocktank.cjitEntries(refresh = true)).thenReturn(listOf(otherEntry, matchingEntry)) + seedCjitEntries(otherEntry, matchingEntry) val channelDetails = mock() whenever(channelDetails.fundingTxo).thenReturn(OutPoint(txid = fundingTxId, vout = 0u)) @@ -656,12 +382,11 @@ class BlocktankRepoTest : BaseUnitTest() { @Test fun `getCjitEntry returns null when no CJIT channel funding tx matches`() = test { sut = createSut() - seedCjitEntries(pendingCjitEntry()) val channel = mock() whenever(channel.fundingTx).thenReturn(FundingTx(id = "cjit-funding-tx", vout = 0u)) val entry = mock() whenever(entry.channel).thenReturn(channel) - whenever(coreService.blocktank.cjitEntries(refresh = true)).thenReturn(listOf(entry)) + seedCjitEntries(entry) val channelDetails = mock() whenever(channelDetails.fundingTxo).thenReturn(OutPoint(txid = "different-funding-tx", vout = 0u)) @@ -672,14 +397,13 @@ class BlocktankRepoTest : BaseUnitTest() { @Test fun `getCjitEntry does not match a CJIT entry with the same funding txid but a different vout`() = test { sut = createSut() - seedCjitEntries(pendingCjitEntry()) // A batched funding tx can hold several channel outputs sharing one txid; only vout distinguishes them. val sharedTxId = "shared-funding-tx" val channel = mock() whenever(channel.fundingTx).thenReturn(FundingTx(id = sharedTxId, vout = 0u)) val entry = mock() whenever(entry.channel).thenReturn(channel) - whenever(coreService.blocktank.cjitEntries(refresh = true)).thenReturn(listOf(entry)) + seedCjitEntries(entry) val channelDetails = mock() whenever(channelDetails.fundingTxo).thenReturn(OutPoint(txid = sharedTxId, vout = 1u)) @@ -687,125 +411,6 @@ class BlocktankRepoTest : BaseUnitTest() { assertNull(sut.getCjitEntry(channelDetails)) } - @Test - fun `getCjitEntry does not refresh when no cached CJIT entry is awaiting a channel`() = test { - sut = createSut() - // Only an already-associated entry is cached (none awaiting a channel), so this ChannelReady cannot be a - // CJIT: the server must not be hit even though a refresh would return a matching entry. - val associatedChannel = mock() - whenever(associatedChannel.fundingTx).thenReturn(FundingTx(id = "other-funding-tx", vout = 0u)) - val associatedEntry = mock() - whenever(associatedEntry.channel).thenReturn(associatedChannel) - seedCjitEntries(associatedEntry) - - val fundingTxId = "channel-order-funding-tx" - val matchingChannel = mock() - whenever(matchingChannel.fundingTx).thenReturn(FundingTx(id = fundingTxId, vout = 0u)) - val matchingEntry = mock() - whenever(matchingEntry.channel).thenReturn(matchingChannel) - whenever(coreService.blocktank.cjitEntries(refresh = true)).thenReturn(listOf(matchingEntry)) - - val channelDetails = mock() - whenever(channelDetails.fundingTxo).thenReturn(OutPoint(txid = fundingTxId, vout = 0u)) - - assertNull(sut.getCjitEntry(channelDetails)) - } - - @Test - fun `getCjitEntry does not refresh for an expired CJIT entry awaiting no channel`() = test { - sut = createSut() - // An expired entry has no channel but can never open one, so it must not trigger a server refresh - // even though a refresh would surface a matching entry. - val expiredEntry = mock() - whenever(expiredEntry.channel).thenReturn(null) - whenever(expiredEntry.state).thenReturn(CJitStateEnum.EXPIRED) - seedCjitEntries(expiredEntry) - - val fundingTxId = "channel-order-funding-tx" - val matchingChannel = mock() - whenever(matchingChannel.fundingTx).thenReturn(FundingTx(id = fundingTxId, vout = 0u)) - val matchingEntry = mock() - whenever(matchingEntry.channel).thenReturn(matchingChannel) - whenever(coreService.blocktank.cjitEntries(refresh = true)).thenReturn(listOf(matchingEntry)) - - val channelDetails = mock() - whenever(channelDetails.fundingTxo).thenReturn(OutPoint(txid = fundingTxId, vout = 0u)) - - assertNull(sut.getCjitEntry(channelDetails)) - } - - @Test - fun `getCjitEntry returns cached entry without refreshing when already associated`() = test { - sut = createSut() - val fundingTxId = "cached-funding-tx" - val channel = mock() - whenever(channel.fundingTx).thenReturn(FundingTx(id = fundingTxId, vout = 0u)) - val cachedEntry = mock() - whenever(cachedEntry.channel).thenReturn(channel) - seedCjitEntries(cachedEntry) - - val channelDetails = mock() - whenever(channelDetails.fundingTxo).thenReturn(OutPoint(txid = fundingTxId, vout = 0u)) - - // A server refresh would return no entries (setUp default), so a non-null result can only come - // from the cached state short-circuit, proving the server is not hit when the entry is already known. - assertEquals(cachedEntry, sut.getCjitEntry(channelDetails)) - } - - @Test - fun `getCjitEntry returns null when a pending CJIT is awaiting but refresh fails`() = test { - sut = createSut() - seedCjitEntries(pendingCjitEntry()) - whenever(coreService.blocktank.cjitEntries(refresh = true)).thenThrow(RuntimeException("Network error")) - - val channelDetails = mock() - whenever(channelDetails.fundingTxo).thenReturn(OutPoint(txid = "missing-funding-tx", vout = 0u)) - - assertNull(sut.getCjitEntry(channelDetails)) - } - - @Test - fun `getCjitEntry retries the refresh and matches after a transient failure`() = test { - sut = createSut() - seedCjitEntries(pendingCjitEntry()) - val fundingTxId = "cjit-funding-tx" - val channel = mock() - whenever(channel.fundingTx).thenReturn(FundingTx(id = fundingTxId, vout = 0u)) - val entry = mock() - whenever(entry.channel).thenReturn(channel) - whenever(coreService.blocktank.cjitEntries(refresh = true)) - .thenThrow(RuntimeException("transient")) - .thenReturn(listOf(entry)) - - val channelDetails = mock() - whenever(channelDetails.fundingTxo).thenReturn(OutPoint(txid = fundingTxId, vout = 0u)) - - assertEquals(entry, sut.getCjitEntry(channelDetails)) - } - - @Test - fun `getCjitEntry refreshes when the cache is empty`() = test { - sut = createSut() - // Cold start / right after createCjit: cjitEntries hasn't loaded yet, so an empty cache must not be - // treated as a terminal no-match — the server refresh has to surface the matching entry. - val fundingTxId = "cjit-funding-tx" - val channel = mock() - whenever(channel.fundingTx).thenReturn(FundingTx(id = fundingTxId, vout = 0u)) - val entry = mock() - whenever(entry.channel).thenReturn(channel) - whenever(coreService.blocktank.cjitEntries(refresh = true)).thenReturn(listOf(entry)) - - val channelDetails = mock() - whenever(channelDetails.fundingTxo).thenReturn(OutPoint(txid = fundingTxId, vout = 0u)) - - assertEquals(entry, sut.getCjitEntry(channelDetails)) - } - - private fun pendingCjitEntry(): IcJitEntry = mock().apply { - whenever(channel).thenReturn(null) - whenever(state).thenReturn(CJitStateEnum.CREATED) - } - private fun btInfo(maxChannelSizeSat: ULong): IBtInfo { val options = mock() whenever(options.maxChannelSizeSat).thenReturn(maxChannelSizeSat) diff --git a/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt b/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt index a06dcd724b..f6c3b4307f 100644 --- a/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt +++ b/app/src/test/java/to/bitkit/repositories/LightningRepoTest.kt @@ -4,8 +4,6 @@ import app.cash.turbine.test import com.google.firebase.messaging.FirebaseMessaging import com.synonym.bitkitcore.AddressType import com.synonym.bitkitcore.FeeRates -import com.synonym.bitkitcore.IBtInfo -import com.synonym.bitkitcore.ILspNode import com.synonym.bitkitcore.LightningInvoice import com.synonym.bitkitcore.LnurlException import com.synonym.bitkitcore.LnurlPayData @@ -141,11 +139,10 @@ class LightningRepoTest : BaseUnitTest() { @Before fun setUp() = runBlocking { - whenever(lightningService.setup(any(), anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())) + whenever(lightningService.setup(any(), anyOrNull(), anyOrNull(), anyOrNull())) .thenReturn(Unit) whenever(lightningService.start(anyOrNull(), any())).thenReturn(Unit) whenever { electrumProbeService.probe(any(), any()) }.thenReturn(Result.success(Unit)) - whenever(coreService.isGeoBlocked()).thenReturn(false) whenever(cacheStore.data).thenReturn(flowOf(AppCacheData())) whenever(connectivityRepo.isOnline).thenReturn(MutableStateFlow(ConnectivityState.CONNECTED)) whenever(settingsStore.data).thenReturn(flowOf(SettingsData())) @@ -613,7 +610,7 @@ class LightningRepoTest : BaseUnitTest() { nodeRunning = true Unit } - whenever(coreService.isGeoBlocked()).doSuspendableAnswer { awaitCancellation() } + whenever(lightningService.connectToTrustedPeers()).doSuspendableAnswer { awaitCancellation() } var result: Result? = null val job = launch { result = sut.start() } @@ -628,7 +625,7 @@ class LightningRepoTest : BaseUnitTest() { @Test fun `start restores the initial state when cancelled during node setup`() = test { stubNodeStatus { false } - whenever(lightningService.setup(any(), anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())) + whenever(lightningService.setup(any(), anyOrNull(), anyOrNull(), anyOrNull())) .doSuspendableAnswer { awaitCancellation() } var result: Result? = null @@ -642,28 +639,6 @@ class LightningRepoTest : BaseUnitTest() { verifyBlocking(lightningService, never()) { start(anyOrNull(), any()) } } - @Test - fun `start restores the initial state when cancelled while trusted peers are fetched`() = test { - stubNodeStatus { false } - val blocktank = coreService.blocktank - whenever(blocktank.info(any())).doSuspendableAnswer { awaitCancellation() } - var result: Result? = null - - val job = launch { result = sut.start(shouldRetry = false) } - runCurrent() - job.cancelAndJoin() - testScheduler.advanceUntilIdle() - - assertNull(result) - assertEquals(NodeLifecycleState.Initializing, sut.lightningState.value.nodeLifecycleState) - // The cancellation must unwind out of the trusted peers fetch instead of starting a node - verifyBlocking(lightningService, never()) { - setup(any(), anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) - } - verifyBlocking(lightningService, never()) { start(anyOrNull(), any()) } - } - - // Regression #845: a node started by another path between the status check and start must not latch an error @Test fun `start adopts the running node when start throws AlreadyRunning`() = test { var nodeRunning = false @@ -979,7 +954,7 @@ class LightningRepoTest : BaseUnitTest() { assertTrue(result.isSuccess) verifyBlocking(lightningService, times(2)) { - setup(any(), anyOrNull(), anyOrNull(), anyOrNull(), eq(migration)) + setup(any(), anyOrNull(), anyOrNull(), eq(migration)) } assertEquals(NodeLifecycleState.Running, sut.lightningState.value.nodeLifecycleState) } @@ -1042,7 +1017,7 @@ class LightningRepoTest : BaseUnitTest() { val inOrder = inOrder(lightningService) inOrder.verify(lightningService).stop() - inOrder.verify(lightningService).setup(any(), anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()) + inOrder.verify(lightningService).setup(any(), anyOrNull(), anyOrNull(), anyOrNull()) inOrder.verify(lightningService).start(anyOrNull(), any()) } @@ -1369,7 +1344,7 @@ class LightningRepoTest : BaseUnitTest() { sut.stop() whenever(lightningService.nodeId).thenReturn(null) whenever(lightningService.node).thenReturn(null) - whenever(lightningService.setup(any(), anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())) + whenever(lightningService.setup(any(), anyOrNull(), anyOrNull(), anyOrNull())) .thenThrow(IllegalStateException("electrum server unreachable")) assertTrue(sut.start(shouldRetry = false).isFailure) @@ -2765,7 +2740,7 @@ class LightningRepoTest : BaseUnitTest() { val inOrder = inOrder(lightningService) inOrder.verify(lightningService).stop() inOrder.verify(lightningService) - .setup(any(), eq(customServerUrl), anyOrNull(), anyOrNull(), anyOrNull()) + .setup(any(), eq(customServerUrl), anyOrNull(), anyOrNull()) inOrder.verify(lightningService).start(anyOrNull(), any()) assertEquals(NodeLifecycleState.Running, sut.lightningState.value.nodeLifecycleState) } @@ -2806,12 +2781,12 @@ class LightningRepoTest : BaseUnitTest() { whenever(lightningService.node).thenReturn(null) whenever(lightningService.stop()).thenReturn(Unit) // The switch to the new server fails. - whenever(lightningService.setup(any(), eq(badUrl), anyOrNull(), anyOrNull(), anyOrNull())) + whenever(lightningService.setup(any(), eq(badUrl), anyOrNull(), anyOrNull())) .thenThrow(RuntimeException("start failed")) // The background recovery (previous config) blocks until released. val recoveryStarted = CompletableDeferred() val releaseRecovery = CompletableDeferred() - whenever { lightningService.setup(any(), isNull(), isNull(), anyOrNull(), anyOrNull()) } + whenever { lightningService.setup(any(), isNull(), isNull(), anyOrNull()) } .doSuspendableAnswer { recoveryStarted.complete(Unit) releaseRecovery.await() @@ -2842,7 +2817,7 @@ class LightningRepoTest : BaseUnitTest() { assertEquals(probeError, result.exceptionOrNull()) verify(lightningService, never()).stop() verify(lightningService, never()) - .setup(any(), eq(badUrl), anyOrNull(), anyOrNull(), anyOrNull()) + .setup(any(), eq(badUrl), anyOrNull(), anyOrNull()) assertEquals(NodeLifecycleState.Running, sut.lightningState.value.nodeLifecycleState) } @@ -2859,7 +2834,7 @@ class LightningRepoTest : BaseUnitTest() { assertTrue(result.isFailure) assertTrue(result.exceptionOrNull() is NodeConfigNotAppliedError) verify(lightningService, never()) - .setup(any(), eq(newServerUrl), anyOrNull(), anyOrNull(), anyOrNull()) + .setup(any(), eq(newServerUrl), anyOrNull(), anyOrNull()) } // Two-request barrier: a change issued while the background recovery of a previous failed @@ -2874,12 +2849,12 @@ class LightningRepoTest : BaseUnitTest() { val nextUrl = "ssl://next.example.com:50002" whenever(lightningService.node).thenReturn(null) whenever(lightningService.stop()).thenReturn(Unit) - whenever(lightningService.setup(any(), eq(badUrl), anyOrNull(), anyOrNull(), anyOrNull())) + whenever(lightningService.setup(any(), eq(badUrl), anyOrNull(), anyOrNull())) .thenThrow(RuntimeException("start failed")) // Hold the background recovery mid-flight so the second change is issued while it runs. val recoveryStarted = CompletableDeferred() val releaseRecovery = CompletableDeferred() - whenever { lightningService.setup(any(), isNull(), isNull(), anyOrNull(), anyOrNull()) } + whenever { lightningService.setup(any(), isNull(), isNull(), anyOrNull()) } .doSuspendableAnswer { recoveryStarted.complete(Unit) releaseRecovery.await() @@ -2897,7 +2872,7 @@ class LightningRepoTest : BaseUnitTest() { // Success must mean the node was actually rebuilt with the requested server. assertTrue(result.isSuccess) - verify(lightningService).setup(any(), eq(nextUrl), anyOrNull(), anyOrNull(), anyOrNull()) + verify(lightningService).setup(any(), eq(nextUrl), anyOrNull(), anyOrNull()) } // Regression: the caller is a route-scoped ViewModel, so leaving the screen mid-change cancels @@ -2911,7 +2886,7 @@ class LightningRepoTest : BaseUnitTest() { whenever(lightningService.stop()).thenReturn(Unit) // Hold the rebuild open so the caller can be cancelled while it is in flight. val rebuildStarted = CompletableDeferred() - whenever { lightningService.setup(any(), eq(newUrl), anyOrNull(), anyOrNull(), anyOrNull()) } + whenever { lightningService.setup(any(), eq(newUrl), anyOrNull(), anyOrNull()) } .doSuspendableAnswer { rebuildStarted.complete(Unit) awaitCancellation() @@ -2932,7 +2907,7 @@ class LightningRepoTest : BaseUnitTest() { assertTrue(rethrown) // structured concurrency preserved // The detached recovery rebuilt with the previous config and the node is back up. - verify(lightningService).setup(any(), isNull(), isNull(), anyOrNull(), anyOrNull()) + verify(lightningService).setup(any(), isNull(), isNull(), anyOrNull()) assertEquals(NodeLifecycleState.Running, sut.lightningState.value.nodeLifecycleState) // A cancelled change must never persist the server it failed to apply. verifyBlocking(settingsStore, never()) { update(any()) } @@ -2945,7 +2920,7 @@ class LightningRepoTest : BaseUnitTest() { whenever(lightningService.node).thenReturn(null) whenever(lightningService.stop()).thenReturn(Unit) val rebuildStarted = CompletableDeferred() - whenever { lightningService.setup(any(), anyOrNull(), eq(newRgsUrl), anyOrNull(), anyOrNull()) } + whenever { lightningService.setup(any(), anyOrNull(), eq(newRgsUrl), anyOrNull()) } .doSuspendableAnswer { rebuildStarted.complete(Unit) awaitCancellation() @@ -2965,7 +2940,7 @@ class LightningRepoTest : BaseUnitTest() { testScheduler.advanceUntilIdle() assertTrue(rethrown) - verify(lightningService).setup(any(), isNull(), isNull(), anyOrNull(), anyOrNull()) + verify(lightningService).setup(any(), isNull(), isNull(), anyOrNull()) assertEquals(NodeLifecycleState.Running, sut.lightningState.value.nodeLifecycleState) verifyBlocking(settingsStore, never()) { update(any()) } } @@ -2982,7 +2957,7 @@ class LightningRepoTest : BaseUnitTest() { assertTrue(result.isSuccess) val inOrder = inOrder(lightningService) inOrder.verify(lightningService).stop() - inOrder.verify(lightningService).setup(any(), isNull(), eq(customRgsUrl), anyOrNull(), anyOrNull()) + inOrder.verify(lightningService).setup(any(), isNull(), eq(customRgsUrl), anyOrNull()) inOrder.verify(lightningService).start(anyOrNull(), any()) assertEquals(NodeLifecycleState.Running, sut.lightningState.value.nodeLifecycleState) } @@ -3003,7 +2978,7 @@ class LightningRepoTest : BaseUnitTest() { whenever(lightningService.node).thenReturn(null) whenever(lightningService.stop()).thenReturn(Unit) whenever( - lightningService.setup(any(), isNull(), eq("https://bad.rgs/snapshot"), anyOrNull(), anyOrNull()), + lightningService.setup(any(), isNull(), eq("https://bad.rgs/snapshot"), anyOrNull()), ).thenThrow(RuntimeException("Failed to start node")) val result = sut.restartWithRgsServer("https://bad.rgs/snapshot") @@ -3178,63 +3153,6 @@ class LightningRepoTest : BaseUnitTest() { assertEquals(serviceError, result.exceptionOrNull()) } - @Test - fun `start should load trusted peers from blocktank info`() = test { - sut.setInitNodeLifecycleState() - whenever(lightningService.node).thenReturn(null) - - val blocktank = mock() - whenever(coreService.blocktank).thenReturn(blocktank) - - val mockNodes = listOf( - ILspNode( - alias = "LSP1", - pubkey = "node1pubkey", - connectionStrings = listOf("node1pubkey@node1.example.com:9735"), - readonly = null, - ), - ILspNode( - alias = "LSP2", - pubkey = "node2pubkey", - connectionStrings = listOf("node2pubkey@node2.example.com:9735"), - readonly = null, - ), - ) - val mockInfo = mock { on { nodes } doReturn mockNodes } - whenever(blocktank.info(refresh = false)).thenReturn(mockInfo) - - val result = sut.start() - - assertTrue(result.isSuccess) - verify(lightningService).setup( - any(), - anyOrNull(), - anyOrNull(), - argThat { peers -> - peers?.size == 2 && - peers.any { it.nodeId == "node1pubkey" && it.address == "node1.example.com:9735" } && - peers.any { it.nodeId == "node2pubkey" && it.address == "node2.example.com:9735" } - }, - anyOrNull(), - ) - } - - @Test - fun `start should pass null trusted peers when blocktank returns null`() = test { - sut.setInitNodeLifecycleState() - whenever(lightningService.node).thenReturn(null) - - val blocktank = mock() - whenever(coreService.blocktank).thenReturn(blocktank) - whenever(blocktank.info(refresh = false)).thenReturn(null) - whenever(blocktank.info(refresh = true)).thenReturn(null) - - val result = sut.start() - - assertTrue(result.isSuccess) - verify(lightningService).setup(any(), anyOrNull(), anyOrNull(), isNull(), anyOrNull()) - } - @Test fun `getBalanceForAddressType should succeed when node is running`() = test { startNodeForTesting() diff --git a/app/src/test/java/to/bitkit/repositories/TransferRepoTest.kt b/app/src/test/java/to/bitkit/repositories/TransferRepoTest.kt index dd9e8823a7..dd07833efd 100644 --- a/app/src/test/java/to/bitkit/repositories/TransferRepoTest.kt +++ b/app/src/test/java/to/bitkit/repositories/TransferRepoTest.kt @@ -40,7 +40,6 @@ import to.bitkit.data.AppCacheData import to.bitkit.data.CacheStore import to.bitkit.data.dao.TransferDao import to.bitkit.data.entities.TransferEntity -import to.bitkit.env.Env import to.bitkit.ext.create import to.bitkit.ext.createChannelDetails import to.bitkit.models.NodeLifecycleState @@ -56,7 +55,6 @@ import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue import kotlin.time.Clock -import kotlin.time.Duration.Companion.milliseconds import kotlin.time.ExperimentalTime @OptIn(ExperimentalTime::class, ExperimentalCoroutinesApi::class) @@ -1174,29 +1172,6 @@ class TransferRepoTest : BaseUnitTest() { verify(blocktankRepo, never()).fetchOrders(any()) } - @Test - fun `syncTransferStates settles TO_SPENDING transfer when fetched order reports its stale channel closed`() = test { - val settledAt = setupClockNowMock() - val transfer = transferEntity(id = ID_TRANSFER, fundingTxId = null, channelId = ID_CHANNEL) - .copy(lspOrderId = ID_ORDER) - stubOrderCache( - cached = btOrder(state2 = BtOrderState2.EXECUTED, channel = btChannel(state = BtOpenChannelState.OPEN)), - fetched = btOrder(state2 = BtOrderState2.EXECUTED, channel = btChannel(state = BtOpenChannelState.CLOSED)), - ) - - whenever(transferDao.getActiveTransfers()).thenReturn(flowOf(listOf(transfer))) - whenever(lightningRepo.getChannels()).thenReturn(emptyList()) - whenever(lightningRepo.getBalancesAsync()).thenReturn(Result.success(mock())) - whenever(activityService.closedChannels(SortDirection.DESC)).thenReturn(emptyList()) - whenever(transferDao.markSettled(any(), any())).thenReturn(Unit) - - val result = sut.syncTransferStates() - - assertTrue(result.isSuccess) - verify(blocktankRepo).fetchOrders(listOf(ID_ORDER)) - verify(transferDao).markSettled(eq(ID_TRANSFER), eq(settledAt)) - } - @Test fun `syncTransferStates does not settle TO_SPENDING transfer when channels are unavailable`() = test { val transfer = transferEntity(id = ID_TRANSFER, fundingTxId = null, channelId = ID_CHANNEL) @@ -1276,28 +1251,6 @@ class TransferRepoTest : BaseUnitTest() { verify(blocktankRepo, never()).fetchOrders(any()) } - @Test - fun `syncTransferStates settles TO_SPENDING transfer when fetched executed order channel is closed`() = test { - val settledAt = setupClockNowMock() - val transfer = transferEntity(id = ID_TRANSFER, fundingTxId = null, channelId = null) - .copy(lspOrderId = ID_ORDER) - stubOrderCache( - cached = btOrder(state2 = BtOrderState2.EXECUTED, channel = btChannel(state = BtOpenChannelState.OPEN)), - fetched = btOrder(state2 = BtOrderState2.EXECUTED, channel = btChannel(state = BtOpenChannelState.CLOSED)), - ) - - whenever(transferDao.getActiveTransfers()).thenReturn(flowOf(listOf(transfer))) - whenever(lightningRepo.getChannels()).thenReturn(emptyList()) - whenever(lightningRepo.getBalancesAsync()).thenReturn(Result.success(mock())) - whenever(transferDao.markSettled(any(), any())).thenReturn(Unit) - - val result = sut.syncTransferStates() - - assertTrue(result.isSuccess) - verify(blocktankRepo).fetchOrders(listOf(ID_ORDER)) - verify(transferDao).markSettled(eq(ID_TRANSFER), eq(settledAt)) - } - @Test fun `syncTransferStates settles TO_SPENDING transfer when order channel has close info`() = test { val settledAt = setupClockNowMock() @@ -1335,7 +1288,7 @@ class TransferRepoTest : BaseUnitTest() { state2 = BtOrderState2.PAID, channel = btChannel(state = BtOpenChannelState.OPENING), ) - stubOrderCache(cached = order, fetched = order) + whenever(blocktankRepo.getOrder(ID_ORDER, refresh = false)).thenReturn(Result.success(order)) whenever(transferDao.getActiveTransfers()).thenReturn(flowOf(listOf(transfer))) whenever(lightningRepo.getChannels()).thenReturn(emptyList()) @@ -1366,92 +1319,6 @@ class TransferRepoTest : BaseUnitTest() { verify(transferDao).markSettled(eq(ID_TRANSFER), eq(settledAt)) } - @Test - fun `syncTransferStates does not settle TO_SPENDING transfer when order stays unknown after fetch`() = test { - setupClockNowMock() - val transfer = transferEntity(id = ID_TRANSFER, fundingTxId = null, channelId = null) - .copy(lspOrderId = ID_ORDER) - stubOrderCache(cached = null, fetched = null) - - whenever(transferDao.getActiveTransfers()).thenReturn(flowOf(listOf(transfer))) - whenever(lightningRepo.getChannels()).thenReturn(emptyList()) - whenever(lightningRepo.getBalancesAsync()).thenReturn(Result.success(mock())) - - val result = sut.syncTransferStates() - - assertTrue(result.isSuccess) - verify(blocktankRepo).fetchOrders(listOf(ID_ORDER)) - verify(transferDao, never()).markSettled(any(), any()) - } - - @Test - fun `syncTransferStates does not settle TO_SPENDING transfer when order fetch fails`() = test { - setupClockNowMock() - val transfer = transferEntity(id = ID_TRANSFER, fundingTxId = null, channelId = null) - .copy(lspOrderId = ID_ORDER) - - whenever(transferDao.getActiveTransfers()).thenReturn(flowOf(listOf(transfer))) - whenever(lightningRepo.getChannels()).thenReturn(emptyList()) - whenever(lightningRepo.getBalancesAsync()).thenReturn(Result.success(mock())) - whenever(blocktankRepo.getOrder(ID_ORDER, refresh = false)).thenReturn(Result.success(null)) - whenever(blocktankRepo.fetchOrders(listOf(ID_ORDER))).thenReturn(Result.failure(AppError("Fetch failed"))) - - val result = sut.syncTransferStates() - - assertTrue(result.isSuccess) - verify(transferDao, never()).markSettled(any(), any()) - } - - @Test - fun `syncTransferStates settles TO_SPENDING transfer when uncached order is fetched with closed channel`() = test { - val settledAt = setupClockNowMock() - val transfer = transferEntity(id = ID_TRANSFER, fundingTxId = null, channelId = null) - .copy(lspOrderId = ID_ORDER) - stubOrderCache( - cached = null, - fetched = btOrder(state2 = BtOrderState2.EXECUTED, channel = btChannel(state = BtOpenChannelState.CLOSED)), - ) - - whenever(transferDao.getActiveTransfers()).thenReturn(flowOf(listOf(transfer))) - whenever(lightningRepo.getChannels()).thenReturn(emptyList()) - whenever(lightningRepo.getBalancesAsync()).thenReturn(Result.success(mock())) - whenever(transferDao.markSettled(any(), any())).thenReturn(Unit) - - val result = sut.syncTransferStates() - - assertTrue(result.isSuccess) - verify(transferDao).markSettled(eq(ID_TRANSFER), eq(settledAt)) - } - - @Test - fun `syncTransferStates settles restored TO_SPENDING transfer once fetched order resolves ready channel`() = test { - val settledAt = setupClockNowMock() - val transfer = transferEntity(id = ID_TRANSFER, fundingTxId = "payment-tx-id", channelId = null) - .copy(lspOrderId = ID_ORDER) - val readyChannel = createChannelDetails().copy( - channelId = ID_CHANNEL, - fundingTxo = fundingTxo, - isChannelReady = true, - ) - stubOrderCache( - cached = null, - fetched = btOrder(state2 = BtOrderState2.EXECUTED, channel = btChannel(state = BtOpenChannelState.OPEN)), - ) - - whenever(transferDao.getActiveTransfers()).thenReturn(flowOf(listOf(transfer))) - whenever(lightningRepo.getChannels()).thenReturn(listOf(readyChannel)) - whenever(lightningRepo.getBalancesAsync()).thenReturn(Result.success(mock())) - whenever(activityService.getWalletIds()).thenReturn(setOf(WalletScope.default)) - whenever(transferDao.markSettled(any(), any())).thenReturn(Unit) - - val result = sut.syncTransferStates() - - assertTrue(result.isSuccess) - verify(blocktankRepo).fetchOrders(listOf(ID_ORDER)) - verify(transferDao).update(transfer.copy(channelId = ID_CHANNEL)) - verify(transferDao).markSettled(eq(ID_TRANSFER), eq(settledAt)) - } - @Test fun `syncTransferStates does not fetch order when channel is ready in LDK list`() = test { setupClockNowMock() @@ -1494,29 +1361,6 @@ class TransferRepoTest : BaseUnitTest() { verify(transferDao).markSettled(eq(ID_TRANSFER), eq(settledAt)) } - @Test - fun `syncTransferStates settles TO_SPENDING transfer when fetched order closes its listed channel`() = test { - val settledAt = setupClockNowMock() - val transfer = transferEntity(id = ID_TRANSFER, fundingTxId = null, channelId = ID_CHANNEL) - .copy(lspOrderId = ID_ORDER) - val closingChannel = createChannelDetails().copy(channelId = ID_CHANNEL, isChannelReady = false) - stubOrderCache( - cached = btOrder(state2 = BtOrderState2.EXECUTED, channel = btChannel(state = BtOpenChannelState.OPEN)), - fetched = btOrder(state2 = BtOrderState2.EXECUTED, channel = btChannel(state = BtOpenChannelState.CLOSED)), - ) - - whenever(transferDao.getActiveTransfers()).thenReturn(flowOf(listOf(transfer))) - whenever(lightningRepo.getChannels()).thenReturn(listOf(closingChannel)) - whenever(lightningRepo.getBalancesAsync()).thenReturn(Result.success(mock())) - whenever(transferDao.markSettled(any(), any())).thenReturn(Unit) - - val result = sut.syncTransferStates() - - assertTrue(result.isSuccess) - verify(blocktankRepo).fetchOrders(listOf(ID_ORDER)) - verify(transferDao).markSettled(eq(ID_TRANSFER), eq(settledAt)) - } - @Test fun `syncTransferStates does not settle TO_SPENDING transfer while listed channel is still opening`() = test { setupClockNowMock() @@ -1524,7 +1368,7 @@ class TransferRepoTest : BaseUnitTest() { .copy(lspOrderId = ID_ORDER) val openingChannel = createChannelDetails().copy(channelId = ID_CHANNEL, isChannelReady = false) val order = btOrder(state2 = BtOrderState2.PAID, channel = btChannel(state = BtOpenChannelState.OPENING)) - stubOrderCache(cached = order, fetched = order) + whenever(blocktankRepo.getOrder(ID_ORDER, refresh = false)).thenReturn(Result.success(order)) whenever(transferDao.getActiveTransfers()).thenReturn(flowOf(listOf(transfer))) whenever(lightningRepo.getChannels()).thenReturn(listOf(openingChannel)) @@ -1533,37 +1377,7 @@ class TransferRepoTest : BaseUnitTest() { val result = sut.syncTransferStates() assertTrue(result.isSuccess) - verify(blocktankRepo).fetchOrders(listOf(ID_ORDER)) - verify(transferDao, never()).markSettled(any(), any()) - } - - @Test - fun `syncTransferStates fetches orders in one batch and throttles repeat fetches`() = test { - val now = Clock.System.now() - whenever(clock.now()).thenReturn(now) - val otherOrderId = "other-order-id" - val transfers = listOf( - transferEntity(id = ID_TRANSFER, fundingTxId = null, channelId = null).copy(lspOrderId = ID_ORDER), - transferEntity(id = "other-transfer", fundingTxId = null, channelId = null) - .copy(lspOrderId = otherOrderId), - ) - val orderIds = listOf(ID_ORDER, otherOrderId) - - whenever(transferDao.getActiveTransfers()).thenReturn(flowOf(transfers)) - whenever(lightningRepo.getChannels()).thenReturn(emptyList()) - whenever(lightningRepo.getBalancesAsync()).thenReturn(Result.success(mock())) - whenever(blocktankRepo.getOrder(any(), eq(false))).thenReturn(Result.success(null)) - whenever(blocktankRepo.fetchOrders(orderIds)).thenReturn(Result.success(emptyList())) - - sut.syncTransferStates() - sut.syncTransferStates() - verify(blocktankRepo, times(1)).fetchOrders(any()) - - whenever(clock.now()).thenReturn(now + Env.lspOrdersRefreshInterval.milliseconds) - sut.syncTransferStates() - - verify(blocktankRepo, times(2)).fetchOrders(orderIds) - verify(blocktankRepo, never()).getOrder(any(), eq(true)) + verify(blocktankRepo, never()).fetchOrders(any()) verify(transferDao, never()).markSettled(any(), any()) } @@ -1993,15 +1807,6 @@ class TransferRepoTest : BaseUnitTest() { .also { whenever(clock.now()).thenReturn(it) } .epochSeconds - private suspend fun stubOrderCache(cached: IBtOrder?, fetched: IBtOrder?) { - var order = cached - whenever(blocktankRepo.getOrder(ID_ORDER, refresh = false)).doSuspendableAnswer { Result.success(order) } - whenever(blocktankRepo.fetchOrders(listOf(ID_ORDER))).doSuspendableAnswer { - fetched?.let { order = it } - Result.success(listOfNotNull(fetched)) - } - } - private fun btOrder(state2: BtOrderState2, channel: IBtChannel?) = previewBtOrder().copy( id = ID_ORDER, state2 = state2, diff --git a/app/src/test/java/to/bitkit/repositories/WalletRepoTest.kt b/app/src/test/java/to/bitkit/repositories/WalletRepoTest.kt index e4506b8a6b..3b79c5ed7b 100644 --- a/app/src/test/java/to/bitkit/repositories/WalletRepoTest.kt +++ b/app/src/test/java/to/bitkit/repositories/WalletRepoTest.kt @@ -116,7 +116,6 @@ class WalletRepoTest : BaseUnitTest() { @Before fun setUp() = runBlocking { - whenever(coreService.isGeoBlocked()).thenReturn(false) whenever(coreService.decode(INVOICE)).thenReturn(Scanner.Lightning(lightningInvoice(INVOICE))) whenever(coreService.decode(INVOICE_REPLACEMENT)) .thenReturn(Scanner.Lightning(lightningInvoice(INVOICE_REPLACEMENT))) @@ -952,17 +951,20 @@ class WalletRepoTest : BaseUnitTest() { } @Test - fun `opening refresh delayed by geo cannot replace a newer invoice edit`() = test { + fun `opening refresh delayed by invoice creation cannot replace a newer invoice edit`() = test { sut.loadFromCache() whenever(coreService.isAddressUsed(any())).thenReturn(false) whenever(lightningRepo.getChannels()).thenReturn(channels) + val releaseInvoice = CompletableDeferred() whenever(lightningRepo.createInvoice(anyOrNull(), any(), any())) - .thenReturn(Result.success(INVOICE), Result.success(INVOICE_REPLACEMENT)) - val releaseGeo = CompletableDeferred() - whenever(lightningRepo.updateGeoBlockState()).doSuspendableAnswer { releaseGeo.await() } + .doSuspendableAnswer { + releaseInvoice.await() + Result.success(INVOICE) + } + .thenReturn(Result.success(INVOICE_REPLACEMENT)) val earlier = launch { sut.refreshReceiveState() } val newer = launch { sut.updateBip21Invoice(SATS, "latest edit") } - releaseGeo.complete(Unit) + releaseInvoice.complete(Unit) earlier.join() newer.join() @@ -1053,15 +1055,18 @@ class WalletRepoTest : BaseUnitTest() { sut.loadFromCache() whenever(coreService.isAddressUsed(any())).thenReturn(false) whenever(lightningRepo.getChannels()).thenReturn(channels) + val releaseInvoice = CompletableDeferred() whenever(lightningRepo.createInvoice(anyOrNull(), any(), any())) - .thenReturn(Result.success(INVOICE), Result.success(INVOICE_REPLACEMENT)) - val releaseGeo = CompletableDeferred() - whenever(lightningRepo.updateGeoBlockState()).doSuspendableAnswer { releaseGeo.await() } + .doSuspendableAnswer { + releaseInvoice.await() + Result.success(INVOICE) + } + .thenReturn(Result.success(INVOICE_REPLACEMENT)) val opening = launch { sut.refreshReceiveState() } val queued = launch { sut.updateBip21Invoice(SATS, "requested") } sut.setBip21Description("typed later") assertTrue(sut.walletState.value.isReceiveInvoiceUpdating) - releaseGeo.complete(Unit) + releaseInvoice.complete(Unit) opening.join() queued.join() diff --git a/app/src/test/java/to/bitkit/viewmodels/AppViewModelSendFlowTest.kt b/app/src/test/java/to/bitkit/viewmodels/AppViewModelSendFlowTest.kt index ea979a6e41..2663d59f26 100644 --- a/app/src/test/java/to/bitkit/viewmodels/AppViewModelSendFlowTest.kt +++ b/app/src/test/java/to/bitkit/viewmodels/AppViewModelSendFlowTest.kt @@ -716,7 +716,6 @@ class AppViewModelSendFlowTest : BaseUnitTest() { whenever { migrationService.needsPostMigrationSync() }.thenReturn(false) whenever { migrationService.isMigrationChecked() }.thenReturn(true) whenever { widgetsRepo.refreshEnabledWidgets() }.thenReturn(Unit) - whenever { lightningRepo.updateGeoBlockState() }.thenReturn(Unit) whenever(pubkyRepo.sessionRestorationFailed).thenReturn(MutableStateFlow(false)) whenever(pubkyRepo.adoptedSourceLost).thenReturn(MutableStateFlow(false)) whenever(pubkyRepo.contactImportFailure).thenReturn(pubkyContactImportFailure) From 1adc54a0c272e6ef6ff9cb924b6b8d4c8bb296cc Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Sat, 24 Oct 2026 19:37:12 +0200 Subject: [PATCH 8/8] fix: keep channels with blocktank lnd4 connected --- app/src/main/java/to/bitkit/env/Env.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/to/bitkit/env/Env.kt b/app/src/main/java/to/bitkit/env/Env.kt index 3d04477257..1a0b102818 100644 --- a/app/src/main/java/to/bitkit/env/Env.kt +++ b/app/src/main/java/to/bitkit/env/Env.kt @@ -284,7 +284,7 @@ object Peers { val stag = PeerDetails.of("028a8910b0048630d4eb17af25668cdd7ea6f2d8ae20956e7a06e2ae46ebcb69fc@34.65.86.104:9400") val lnd1 = PeerDetails.of("039b8b4dd1d88c2c5db374290cda397a8f5d79f312d6ea5d5bfdfc7c6ff363eae3@34.65.111.104:9735") val lnd3 = PeerDetails.of("03816141f1dce7782ec32b66a300783b1d436b19777e7c686ed00115bd4b88ff4b@34.65.191.64:9735") - val lnd4 = PeerDetails.of("02a371038863605300d0b3fc9de0cf5ccb57728b7f8906535709a831b16e311187@34.65.153.174:9735") + val lnd4 = PeerDetails.of("02a371038863605300d0b3fc9de0cf5ccb57728b7f8906535709a831b16e311187@34.65.147.55:9735") object Known { val stag = NodePeer(Peers.stag, name = "Synonym-Own-Regtest-0")