From 360dea9de99ec86552f1f8af4e58747fa282c6ba Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 20 May 2026 19:24:10 +0000 Subject: [PATCH] feat: surface on-chain zaps from the reactions zap button Adds a second row of bitcoin-orange chips to the zap amount popup that opens the existing OnchainZapSendDialog prefilled with the chosen amount and the note's author + zappedEvent. The on-chain stack (kind 8333, OnchainZapSender, Account.sendOnchainZap, OnchainZapSendDialog) already existed end-to-end; only the entry point from the reactions row and the on-chain preset amounts were missing. Settings: - AccountZapPreferencesInternal: new onchainZapAmountChoices (defaults 10k/50k/250k sats); back-compat via kotlinx.serialization defaults - AccountSyncedSettings / AccountSettings / Account.updateZapAmounts: wired through end-to-end alongside the existing Lightning list - UpdateZapAmountDialog: new "Quick On-chain Zap Amounts" section with its own chip list and add-amount field; the existing zap-privacy dropdown stays Lightning-only since on-chain has no zap type Reactions UI: - ZapAmountChoicePopup now collects both choice lists and offers an on-chain row in addition to the Lightning one - New on-chain callback opens OnchainZapSendDialog with the prefilled amount; the existing dialog gains a prefillAmountSats parameter and now reads the on-chain preset list for its own quick-pick chips - Other zap entry points (ReusableZapButton, NestActionBar, FilteredZapAmountChoicePopup) are unchanged: on-chain row defaults off so they keep their existing Lightning-only behavior --- .../vitorpamplona/amethyst/model/Account.kt | 2 + .../amethyst/model/AccountSettings.kt | 9 + .../amethyst/model/AccountSyncedSettings.kt | 8 + .../model/AccountSyncedSettingsInternal.kt | 2 + .../amethyst/ui/note/ReactionsRow.kt | 179 +++++++++++++++--- .../amethyst/ui/note/UpdateZapAmountDialog.kt | 91 ++++++++- .../ui/note/UpdateZapAmountViewModel.kt | 21 +- .../ui/screen/loggedIn/AccountViewModel.kt | 3 +- .../loggedIn/wallet/OnchainZapSendDialog.kt | 9 +- amethyst/src/main/res/values/strings.xml | 3 + 10 files changed, 291 insertions(+), 36 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 79600dd18a..d8307d1687 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -603,12 +603,14 @@ class Account( suspend fun updateZapAmounts( amountSet: List, + onchainAmountSet: List, selectedZapType: LnZapEvent.ZapType, nip47Update: Nip47WalletConnect.Nip47URINorm?, ) { var changed = false if (settings.changeZapAmounts(amountSet)) changed = true + if (settings.changeOnchainZapAmounts(onchainAmountSet)) changed = true if (settings.changeDefaultZapType(selectedZapType)) changed = true if (settings.changeZapPaymentRequest(nip47Update)) changed = true diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt index 07ae07b865..6d80a12bc5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt @@ -267,6 +267,15 @@ class AccountSettings( return false } + fun changeOnchainZapAmounts(newAmounts: List): Boolean { + if (syncedSettings.zaps.onchainZapAmountChoices.value != newAmounts) { + syncedSettings.zaps.onchainZapAmountChoices.tryEmit(newAmounts.toImmutableList()) + saveAccountSettings() + return true + } + return false + } + fun changeReactionTypes(newTypes: List): Boolean { if (syncedSettings.reactions.reactionChoices.value != newTypes) { syncedSettings.reactions.reactionChoices.tryEmit(newTypes.toImmutableList()) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettings.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettings.kt index ca5b08b360..c8fa71e767 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettings.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettings.kt @@ -40,6 +40,7 @@ class AccountSyncedSettings( val zaps = AccountZapPreferences( MutableStateFlow(internalSettings.zaps.zapAmountChoices.toImmutableList()), + MutableStateFlow(internalSettings.zaps.onchainZapAmountChoices.toImmutableList()), MutableStateFlow(internalSettings.zaps.defaultZapType), ) val languages = @@ -69,6 +70,7 @@ class AccountSyncedSettings( zaps = AccountZapPreferencesInternal( zaps.zapAmountChoices.value, + zaps.onchainZapAmountChoices.value, zaps.defaultZapType.value, ), languages = @@ -107,6 +109,11 @@ class AccountSyncedSettings( zaps.zapAmountChoices.tryEmit(newZapChoices) } + val newOnchainZapChoices = syncedSettingsInternal.zaps.onchainZapAmountChoices.toImmutableList() + if (!equalImmutableLists(zaps.onchainZapAmountChoices.value, newOnchainZapChoices)) { + zaps.onchainZapAmountChoices.tryEmit(newOnchainZapChoices) + } + if (zaps.defaultZapType.value != syncedSettingsInternal.zaps.defaultZapType) { zaps.defaultZapType.tryEmit(syncedSettingsInternal.zaps.defaultZapType) } @@ -175,6 +182,7 @@ class AccountVideoPlayerPreferences( @Stable class AccountZapPreferences( var zapAmountChoices: MutableStateFlow>, + var onchainZapAmountChoices: MutableStateFlow>, val defaultZapType: MutableStateFlow, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettingsInternal.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettingsInternal.kt index 29ebf1bbc4..c411336809 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettingsInternal.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSyncedSettingsInternal.kt @@ -38,6 +38,7 @@ val DefaultReactions = ) val DefaultZapAmounts = listOf(100L, 500L, 1000L) +val DefaultOnchainZapAmounts = listOf(10_000L, 50_000L, 250_000L) val DefaultReportWarningThreshold = 5 @Serializable @@ -153,6 +154,7 @@ class AccountReactionPreferencesInternal( @Serializable class AccountZapPreferencesInternal( var zapAmountChoices: List = DefaultZapAmounts, + var onchainZapAmountChoices: List = DefaultOnchainZapAmounts, val defaultZapType: LnZapEvent.ZapType = LnZapEvent.ZapType.PUBLIC, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index 77f49a84af..e24844d6fa 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -136,6 +136,7 @@ import com.vitorpamplona.amethyst.ui.navigation.routes.routeReplyTo import com.vitorpamplona.amethyst.ui.note.types.EditState import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.PaymentTargetsDialog +import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.OnchainZapSendDialog import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.ui.theme.ButtonBorder @@ -172,6 +173,7 @@ import com.vitorpamplona.amethyst.ui.theme.reactionBox import com.vitorpamplona.amethyst.ui.theme.ripple24dp import com.vitorpamplona.amethyst.ui.theme.selectedReactionBoxModifier import com.vitorpamplona.quartz.experimental.nipA3.PaymentTargetsEvent +import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable @@ -1121,6 +1123,11 @@ private fun likeClick( } } +@Immutable +private data class OnchainZapRequest( + val amountSats: Long?, +) + @Composable @OptIn(ExperimentalFoundationApi::class, ExperimentalUuidApi::class) fun ZapReaction( @@ -1135,6 +1142,9 @@ fun ZapReaction( ) { var wantsToZap by remember { mutableStateOf(false) } var wantsToSetCustomZap by remember { mutableStateOf(false) } + // null = closed; OnchainZapRequest(amount=null) = open with no prefill; + // OnchainZapRequest(amount=N) = open prefilled to N sats. + var onchainZapRequest by remember { mutableStateOf(null) } val context = LocalContext.current val scope = rememberCoroutineScope() @@ -1207,6 +1217,10 @@ fun ZapReaction( nav.nav(Route.UpdateZapAmount()) } }, + onOnchainAmount = { amount -> + wantsToZap = false + onchainZapRequest = OnchainZapRequest(amount) + }, onError = { _, message, user -> scope.launch { zappingProgress = 0f @@ -1230,6 +1244,16 @@ fun ZapReaction( ) } + onchainZapRequest?.let { request -> + OnchainZapSendDialog( + accountViewModel = accountViewModel, + onDismiss = { onchainZapRequest = null }, + recipientPubKey = baseNote.author?.pubkeyHex, + zappedEvent = baseNote.toEventHint(), + prefillAmountSats = request.amountSats, + ) + } + if (wantsToSetCustomZap) { ZapCustomDialog( onZapStarts = { zapStartingTime = TimeUtils.now() }, @@ -1834,12 +1858,29 @@ fun ZapAmountChoicePopup( onError: (title: String, text: String, user: User?) -> Unit, onProgress: (percent: Float) -> Unit, onPayViaIntent: (ImmutableList) -> Unit, + onOnchainAmount: ((Long?) -> Unit)? = null, ) { val zapAmountChoices by accountViewModel.account.settings.syncedSettings.zaps.zapAmountChoices .collectAsStateWithLifecycle() + val onchainZapAmountChoices by + accountViewModel.account.settings.syncedSettings.zaps.onchainZapAmountChoices + .collectAsStateWithLifecycle() - ZapAmountChoicePopup(baseNote, zapAmountChoices, accountViewModel, popupYOffset, onZapStarts, onDismiss, onChangeAmount, onError, onProgress, onPayViaIntent) + ZapAmountChoicePopup( + baseNote = baseNote, + zapAmountChoices = zapAmountChoices, + accountViewModel = accountViewModel, + popupYOffset = popupYOffset, + onZapStarts = onZapStarts, + onDismiss = onDismiss, + onChangeAmount = onChangeAmount, + onError = onError, + onProgress = onProgress, + onPayViaIntent = onPayViaIntent, + onchainZapAmountChoices = if (onOnchainAmount != null) onchainZapAmountChoices else persistentListOf(), + onOnchainAmount = onOnchainAmount ?: {}, + ) } @Composable @@ -1854,9 +1895,11 @@ fun ZapAmountChoicePopup( onError: (title: String, text: String, user: User?) -> Unit, onProgress: (percent: Float) -> Unit, onPayViaIntent: (ImmutableList) -> Unit, + onchainZapAmountChoices: ImmutableList = persistentListOf(), + onOnchainAmount: (Long?) -> Unit = {}, ) { val visibilityState = rememberVisibilityState(onDismiss) - ZapAmountChoicePopup(baseNote, zapAmountChoices, accountViewModel, popupYOffset, visibilityState, onZapStarts, onChangeAmount, onError, onProgress, onPayViaIntent) + ZapAmountChoicePopup(baseNote, zapAmountChoices, onchainZapAmountChoices, accountViewModel, popupYOffset, visibilityState, onZapStarts, onChangeAmount, onOnchainAmount, onError, onProgress, onPayViaIntent) } @OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class) @@ -1864,11 +1907,13 @@ fun ZapAmountChoicePopup( fun ZapAmountChoicePopup( baseNote: Note, zapAmountChoices: ImmutableList, + onchainZapAmountChoices: ImmutableList, accountViewModel: AccountViewModel, popupYOffset: Dp, visibilityState: MutableTransitionState, onZapStarts: () -> Unit, onChangeAmount: () -> Unit, + onOnchainAmount: (Long?) -> Unit, onError: (title: String, text: String, user: User?) -> Unit, onProgress: (percent: Float) -> Unit, onPayViaIntent: (ImmutableList) -> Unit, @@ -1889,6 +1934,7 @@ fun ZapAmountChoicePopup( ) { ZapAmountChoicePopupContent( zapAmountChoices = zapAmountChoices, + onchainZapAmountChoices = onchainZapAmountChoices, onZap = { amountInSats -> onZapStarts() accountViewModel.zap( @@ -1905,6 +1951,10 @@ fun ZapAmountChoicePopup( visibilityState.targetState = false }, onChangeAmount = onChangeAmount, + onOnchainAmount = { amount -> + onOnchainAmount(amount) + visibilityState.targetState = false + }, ) } } @@ -1916,6 +1966,8 @@ fun ZapAmountChoicePopupContent( zapAmountChoices: ImmutableList, onZap: (Long) -> Unit, onChangeAmount: () -> Unit, + onchainZapAmountChoices: ImmutableList = persistentListOf(), + onOnchainAmount: (Long?) -> Unit = {}, ) { Box(HalfPadding, contentAlignment = Center) { ElevatedCard( @@ -1923,33 +1975,66 @@ fun ZapAmountChoicePopupContent( elevation = CardDefaults.elevatedCardElevation(defaultElevation = 8.dp), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant), ) { - FlowRow( - modifier = Modifier.padding(horizontal = 5.dp, vertical = 5.dp), - horizontalArrangement = Arrangement.Center, - verticalArrangement = Arrangement.Center, - itemVerticalAlignment = CenterVertically, - ) { - zapAmountChoices.forEach { amountInSats -> - ZapAmountChip( - amountInSats = amountInSats, - onClick = { onZap(amountInSats) }, - onLongClick = onChangeAmount, - ) - } - ClickableBox( - modifier = - Modifier - .padding(horizontal = 4.dp, vertical = 6.dp) - .size(32.dp) - .padding(7.dp), - onClick = onChangeAmount, + Column { + FlowRow( + modifier = Modifier.padding(horizontal = 5.dp, vertical = 5.dp), + horizontalArrangement = Arrangement.Center, + verticalArrangement = Arrangement.Center, + itemVerticalAlignment = CenterVertically, ) { - Icon( - symbol = MaterialSymbols.Tune, - contentDescription = stringRes(R.string.quick_zap_amounts), - modifier = Size18Modifier, - tint = MaterialTheme.colorScheme.placeholderText, - ) + zapAmountChoices.forEach { amountInSats -> + ZapAmountChip( + amountInSats = amountInSats, + onClick = { onZap(amountInSats) }, + onLongClick = onChangeAmount, + ) + } + ClickableBox( + modifier = + Modifier + .padding(horizontal = 4.dp, vertical = 6.dp) + .size(32.dp) + .padding(7.dp), + onClick = onChangeAmount, + ) { + Icon( + symbol = MaterialSymbols.Tune, + contentDescription = stringRes(R.string.quick_zap_amounts), + modifier = Size18Modifier, + tint = MaterialTheme.colorScheme.placeholderText, + ) + } + } + if (onchainZapAmountChoices.isNotEmpty()) { + FlowRow( + modifier = Modifier.padding(horizontal = 5.dp, vertical = 5.dp), + horizontalArrangement = Arrangement.Center, + verticalArrangement = Arrangement.Center, + itemVerticalAlignment = CenterVertically, + ) { + onchainZapAmountChoices.forEach { amountInSats -> + OnchainZapAmountChip( + amountInSats = amountInSats, + onClick = { onOnchainAmount(amountInSats) }, + onLongClick = onChangeAmount, + ) + } + ClickableBox( + modifier = + Modifier + .padding(horizontal = 4.dp, vertical = 6.dp) + .size(32.dp) + .padding(7.dp), + onClick = { onOnchainAmount(null) }, + ) { + Icon( + symbol = MaterialSymbols.Tune, + contentDescription = stringRes(R.string.quick_zap_amounts_onchain), + modifier = Size18Modifier, + tint = MaterialTheme.colorScheme.placeholderText, + ) + } + } } } } @@ -1992,14 +2077,52 @@ private fun ZapAmountChip( } } +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun OnchainZapAmountChip( + amountInSats: Long, + onClick: () -> Unit, + onLongClick: () -> Unit, +) { + Surface( + shape = ButtonBorder, + color = BitcoinOrange, + modifier = Modifier.padding(horizontal = 4.dp, vertical = 6.dp), + ) { + Row( + modifier = + Modifier + .combinedClickable(onClick = onClick, onLongClick = onLongClick) + .padding(horizontal = 12.dp, vertical = 6.dp), + verticalAlignment = CenterVertically, + ) { + Icon( + symbol = MaterialSymbols.CurrencyBitcoin, + contentDescription = null, + modifier = Size18Modifier, + tint = Color.White, + ) + Spacer(Modifier.width(2.dp)) + Text( + text = showAmount(amountInSats.toBigDecimal().setScale(1)), + color = Color.White, + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.Center, + ) + } + } +} + @Preview @Composable fun ZapAmountChoicePopupPreview() { ThemeComparisonColumn { ZapAmountChoicePopupContent( zapAmountChoices = persistentListOf(50L, 100L, 500L, 1_000L, 5_000L, 10_000L, 100_000L), + onchainZapAmountChoices = persistentListOf(10_000L, 50_000L, 250_000L), onZap = {}, onChangeAmount = {}, + onOnchainAmount = {}, ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt index 571be707fb..d4e5b3c5a0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt @@ -306,7 +306,96 @@ fun UpdateZapAmountContent( ) } - // ── Section 2: Zap Privacy ──────────────────────────────────────────── + // ── Section 2: Quick On-chain Zap Amounts ───────────────────────────── + + Text( + text = stringRes(R.string.quick_zap_amounts_onchain), + color = MaterialTheme.colorScheme.primary, + style = MaterialTheme.typography.titleSmall, + modifier = SettingsCategorySpacingModifier, + ) + Text( + text = stringRes(R.string.quick_zap_amounts_onchain_explainer), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.placeholderText, + modifier = Modifier.padding(bottom = 6.dp), + ) + + FlowRow( + modifier = + Modifier + .fillMaxWidth() + .animateContentSize(animationSpec = spring(stiffness = Spring.StiffnessMediumLow)), + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + postViewModel.onchainAmountSet.forEach { amountInSats -> + InputChip( + selected = false, + onClick = { postViewModel.removeOnchainAmount(amountInSats) }, + label = { + Text( + text = "₿ ${showAmount(amountInSats.toBigDecimal().setScale(1))}", + ) + }, + trailingIcon = { + Icon( + symbol = MaterialSymbols.Close, + contentDescription = stringRes(R.string.remove), + modifier = Modifier.size(InputChipDefaults.AvatarSize), + ) + }, + colors = + InputChipDefaults.inputChipColors( + containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.12f), + labelColor = MaterialTheme.colorScheme.primary, + trailingIconColor = MaterialTheme.colorScheme.primary, + ), + ) + } + } + + Spacer(modifier = Modifier.height(6.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + OutlinedTextField( + label = { Text(text = stringRes(R.string.new_amount_in_sats_onchain)) }, + value = postViewModel.nextOnchainAmount, + onValueChange = { postViewModel.nextOnchainAmount = it }, + keyboardOptions = + KeyboardOptions.Default.copy( + capitalization = KeyboardCapitalization.None, + keyboardType = KeyboardType.Number, + ), + placeholder = { + Text( + text = "10000, 50000, 250000", + color = MaterialTheme.colorScheme.placeholderText, + ) + }, + trailingIcon = { + IconButton( + onClick = postViewModel::addOnchainAmount, + shape = ButtonBorder, + enabled = postViewModel.nextOnchainAmount.text.isNotBlank(), + ) { + Icon( + symbol = MaterialSymbols.AddCircle, + contentDescription = stringRes(R.string.add), + modifier = Size20Modifier, + ) + } + }, + singleLine = true, + modifier = Modifier.weight(1f), + ) + } + + // ── Section 3: Zap Privacy ──────────────────────────────────────────── Text( text = stringRes(R.string.zap_privacy_section), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountViewModel.kt index 8d50017d9a..963e6c2864 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountViewModel.kt @@ -41,6 +41,8 @@ class UpdateZapAmountViewModel : ViewModel() { var nextAmount by mutableStateOf(TextFieldValue("")) var amountSet by mutableStateOf(listOf()) + var nextOnchainAmount by mutableStateOf(TextFieldValue("")) + var onchainAmountSet by mutableStateOf(listOf()) var walletConnectRelay by mutableStateOf(TextFieldValue("")) var walletConnectPubkey by mutableStateOf(TextFieldValue("")) var walletConnectSecret by mutableStateOf(TextFieldValue("")) @@ -59,6 +61,7 @@ class UpdateZapAmountViewModel : ViewModel() { fun load() { this.amountSet = accountViewModel.account.settings.syncedSettings.zaps.zapAmountChoices.value + this.onchainAmountSet = accountViewModel.account.settings.syncedSettings.zaps.onchainZapAmountChoices.value this.selectedZapType = accountViewModel.account.settings.syncedSettings.zaps.defaultZapType.value val nip47 = accountViewModel.account.settings.defaultZapPaymentRequest() @@ -83,6 +86,19 @@ class UpdateZapAmountViewModel : ViewModel() { amountSet = amountSet - amount } + fun addOnchainAmount() { + val newValue = nextOnchainAmount.text.trim().toLongOrNull() + if (newValue != null) { + onchainAmountSet = onchainAmountSet + newValue + } + + nextOnchainAmount = TextFieldValue("") + } + + fun removeOnchainAmount(amount: Long) { + onchainAmountSet = onchainAmountSet - amount + } + fun sendPost() { accountViewModel.launchSigner { sendPostSuspend() @@ -116,13 +132,15 @@ class UpdateZapAmountViewModel : ViewModel() { null } - accountViewModel.account.updateZapAmounts(amountSet, selectedZapType, nip47Update) + accountViewModel.account.updateZapAmounts(amountSet, onchainAmountSet, selectedZapType, nip47Update) nextAmount = TextFieldValue("") + nextOnchainAmount = TextFieldValue("") } fun cancel() { nextAmount = TextFieldValue("") + nextOnchainAmount = TextFieldValue("") } fun hasChanged(): Boolean { @@ -130,6 +148,7 @@ class UpdateZapAmountViewModel : ViewModel() { return ( selectedZapType != accountViewModel.account.settings.syncedSettings.zaps.defaultZapType.value || amountSet != accountViewModel.account.settings.syncedSettings.zaps.zapAmountChoices.value || + onchainAmountSet != accountViewModel.account.settings.syncedSettings.zaps.onchainZapAmountChoices.value || walletConnectPubkey.text != (defaultUri?.pubKeyHex ?: "") || walletConnectRelay.text != (defaultUri?.relayUri?.url ?: "") || walletConnectSecret.text != (defaultUri?.secret ?: "") diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index b695b85191..af93b2af10 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -1236,9 +1236,10 @@ class AccountViewModel( fun updateZapAmounts( amountSet: List, + onchainAmountSet: List, selectedZapType: LnZapEvent.ZapType, nip47Update: Nip47WalletConnect.Nip47URINorm?, - ) = launchSigner { account.updateZapAmounts(amountSet, selectedZapType, nip47Update) } + ) = launchSigner { account.updateZapAmounts(amountSet, onchainAmountSet, selectedZapType, nip47Update) } fun toggleDontTranslateFrom(languageCode: String) = launchSigner { account.toggleDontTranslateFrom(languageCode) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt index f0738c3956..85e4782e2f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt @@ -127,6 +127,7 @@ fun OnchainZapSendDialog( onDismiss: () -> Unit, recipientPubKey: HexKey? = null, zappedEvent: EventHintBundle? = null, + prefillAmountSats: Long? = null, ) { val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val scope = rememberCoroutineScope() @@ -141,7 +142,7 @@ fun OnchainZapSendDialog( var searchInput by remember { mutableStateOf("") } var selectedUser by remember { mutableStateOf(null) } - var amountInput by remember { mutableStateOf("") } + var amountInput by remember { mutableStateOf(prefillAmountSats?.toString().orEmpty()) } var comment by remember { mutableStateOf("") } var feeTier by remember { mutableStateOf(FeeTier.NORMAL) } var fees by remember { mutableStateOf(null) } @@ -155,10 +156,8 @@ fun OnchainZapSendDialog( runCatching { withContext(Dispatchers.IO) { backend.feeEstimates() } }.getOrNull() } - val presetAmounts = - remember(accountViewModel) { - accountViewModel.zapAmountChoices() - } + val presetAmounts by accountViewModel.account.settings.syncedSettings.zaps.onchainZapAmountChoices + .collectAsStateWithLifecycle() // Mirror the dropdown's NIP-05 / Namecoin (.bit) resolution so Send can // enable as soon as the typed name resolves, without forcing the user to diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 1bc3b6a9db..b66a7902a7 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -821,6 +821,9 @@ Advanced: enter connection details manually Quick Zap Amounts Shown when pressing the zap button. Tap an amount to remove it. If you leave it empty, it will open the dialog to insert an amount every time. + Quick On-chain Zap Amounts + On-chain zaps pay miner fees, so amounts are usually larger than Lightning. Tap an amount to remove it. + New on-chain amount in sats Zap Privacy Controls how your identity is shown when you send a zap. Connect Wallet