diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index d7107e8a1..12072b1fe 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -148,7 +148,7 @@ val DefaultReactions = "\uD83D\uDE31", ) -val DefaultZapAmounts = listOf(500L, 1000L, 5000L) +val DefaultZapAmounts = listOf(100L, 500L, 1000L) fun getLanguagesSpokenByUser(): Set { val languageList = ConfigurationCompat.getLocales(Resources.getSystem().getConfiguration()) @@ -2435,6 +2435,10 @@ class Account( return (activeRelays() ?: convertLocalRelays()).filter { it.write } } + fun activeAllRelays(): List { + return ((activeRelays() ?: convertLocalRelays()).toList()) + } + fun isAllHidden(users: Set): Boolean { return users.all { isHidden(it) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/ZapPaymentHandler.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/ZapPaymentHandler.kt index bb8d0d259..a8b112053 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/ZapPaymentHandler.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/ZapPaymentHandler.kt @@ -103,7 +103,7 @@ class ZapPaymentHandler(val account: Account) { } if (account.hasWalletConnectSetup()) { - payViaNWC(it.values.map { it.second }, note, onError, onProgress = { + payViaNWC(it.values.map { it.invoice }, note, onError, onProgress = { onProgress(it * 0.25f + 0.75f) // keeps within range. }, context) { // onProgress(1f) @@ -113,9 +113,9 @@ class ZapPaymentHandler(val account: Account) { it.map { Payable( info = it.key.first, - user = null, - amountMilliSats = it.value.first, - invoice = it.value.second, + user = it.key.second.user, + amountMilliSats = it.value.zapValue, + invoice = it.value.invoice, ) }.toImmutableList(), ) @@ -136,28 +136,33 @@ class ZapPaymentHandler(val account: Account) { return roundedZapValue } + class SignAllZapRequestsReturn( + val zapRequestJson: String, + val user: User? = null, + ) + suspend fun signAllZapRequests( note: Note, pollOption: Int?, message: String, zapType: LnZapEvent.ZapType, zapsToSend: List, - onAllDone: suspend (MutableMap) -> Unit, + onAllDone: suspend (MutableMap) -> Unit, ) { - collectSuccessfulSigningOperations( + collectSuccessfulSigningOperations( operationsInput = zapsToSend, runRequestFor = { next: ZapSplitSetup, onReady -> if (next.isLnAddress) { prepareZapRequestIfNeeded(note, pollOption, message, zapType) { zapRequestJson -> if (zapRequestJson != null) { - onReady(zapRequestJson) + onReady(SignAllZapRequestsReturn(zapRequestJson)) } } } else { val user = LocalCache.getUserIfExists(next.lnAddressOrPubKeyHex) prepareZapRequestIfNeeded(note, pollOption, message, zapType, user) { zapRequestJson -> if (zapRequestJson != null) { - onReady(zapRequestJson) + onReady(SignAllZapRequestsReturn(zapRequestJson, user)) } } } @@ -167,23 +172,23 @@ class ZapPaymentHandler(val account: Account) { } suspend fun assembleAllInvoices( - invoices: List>, + invoices: List>, totalAmountMilliSats: Long, message: String, onError: (String, String) -> Unit, onProgress: (percent: Float) -> Unit, context: Context, - onAllDone: suspend (MutableMap, Pair>) -> Unit, + onAllDone: suspend (MutableMap, AssembleInvoiceReturn>) -> Unit, ) { var progressAllPayments = 0.00f val totalWeight = invoices.sumOf { it.first.weight } - collectSuccessfulSigningOperations, Pair>( + collectSuccessfulSigningOperations, AssembleInvoiceReturn>( operationsInput = invoices, - runRequestFor = { splitZapRequestPair: Pair, onReady -> + runRequestFor = { splitZapRequestPair: Pair, onReady -> assembleInvoice( splitSetup = splitZapRequestPair.first, - nostrZapRequest = splitZapRequestPair.second, + nostrZapRequest = splitZapRequestPair.second.zapRequestJson, zapValue = calculateZapValue(totalAmountMilliSats, splitZapRequestPair.first.weight, totalWeight), message = message, onError = onError, @@ -243,6 +248,11 @@ class ZapPaymentHandler(val account: Account) { ) } + class AssembleInvoiceReturn( + val zapValue: Long, + val invoice: String, + ) + private fun assembleInvoice( splitSetup: ZapSplitSetup, nostrZapRequest: String, @@ -251,7 +261,7 @@ class ZapPaymentHandler(val account: Account) { onError: (String, String) -> Unit, onProgressStep: (percent: Float) -> Unit, context: Context, - onReady: (Pair) -> Unit, + onReady: (AssembleInvoiceReturn) -> Unit, ) { var progressThisPayment = 0.00f @@ -280,7 +290,7 @@ class ZapPaymentHandler(val account: Account) { context = context, onSuccess = { onProgressStep(1 - progressThisPayment) - onReady(Pair(zapValue, it)) + onReady(AssembleInvoiceReturn(zapValue, it)) }, ) } else { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt index 21eaeb714..e9e63fede 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt @@ -449,6 +449,38 @@ class Relay( } } + // This function sends the event regardless of the relay being write or not. + fun sendOverride(signedEvent: EventInterface) { + checkNotInMainThread() + + if (signedEvent is RelayAuthEvent) { + authResponse.put(signedEvent.id, false) + // specific protocol for this event. + val event = """["AUTH",${signedEvent.toJson()}]""" + socket?.send(event) + eventUploadCounterInBytes += event.bytesUsedInMemory() + } else { + val event = """["EVENT",${signedEvent.toJson()}]""" + if (isConnected()) { + if (isReady) { + socket?.send(event) + eventUploadCounterInBytes += event.bytesUsedInMemory() + } + } else { + // sends all filters after connection is successful. + connectAndRun { + checkNotInMainThread() + + socket?.send(event) + eventUploadCounterInBytes += event.bytesUsedInMemory() + + // Sends everything. + renewFilters() + } + } + } + } + fun send(signedEvent: EventInterface) { checkNotInMainThread() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt index faf184671..969a726b7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt @@ -150,13 +150,17 @@ object RelayPool : Relay.Listener { list: List, signedEvent: EventInterface, ) { - list.forEach { relay -> relays.filter { it.url == relay.url }.forEach { it.send(signedEvent) } } + list.forEach { relay -> relays.filter { it.url == relay.url }.forEach { it.sendOverride(signedEvent) } } } fun send(signedEvent: EventInterface) { relays.forEach { it.send(signedEvent) } } + fun sendOverride(signedEvent: EventInterface) { + relays.forEach { it.sendOverride(signedEvent) } + } + fun close(subscriptionId: String) { relays.forEach { it.close(subscriptionId) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt index b64c63cec..b2286c749 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt @@ -78,7 +78,7 @@ fun RelaySelectionDialog( var relays by remember { mutableStateOf( - accountViewModel.account.activeWriteRelays().map { + accountViewModel.account.activeAllRelays().map { RelayList( relay = it, relayInfo = RelayBriefInfoCache.RelayBriefInfo(it.url), diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableWithdrawal.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableWithdrawal.kt index c9c2b3250..a46bddec4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableWithdrawal.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableWithdrawal.kt @@ -79,7 +79,7 @@ fun ClickableWithdrawal(withdrawalString: String) { ClickableText( text = withdraw, - onClick = { payViaIntent(withdrawalString, context) { showErrorMessageDialog = it } }, + onClick = { payViaIntent(withdrawalString, context, { }) { showErrorMessageDialog = it } }, style = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.primary), ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt index c8285ada9..d4d9a3a99 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt @@ -26,7 +26,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme @@ -42,13 +41,13 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.richtext.ExpandableTextCutOffCalculator import com.vitorpamplona.amethyst.ui.note.getGradient import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonPadding +import com.vitorpamplona.amethyst.ui.theme.StdTopPadding import com.vitorpamplona.amethyst.ui.theme.secondaryButtonBackground import com.vitorpamplona.quartz.events.ImmutableListOfLists @@ -125,7 +124,7 @@ fun ExpandableRichTextViewer( @Composable fun ShowMoreButton(onClick: () -> Unit) { Button( - modifier = Modifier.padding(top = 10.dp), + modifier = StdTopPadding, onClick = onClick, shape = ButtonBorder, colors = diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoicePreview.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoicePreview.kt index e761e196e..f7cc281c5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoicePreview.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoicePreview.kt @@ -169,7 +169,7 @@ fun InvoicePreview( Modifier .fillMaxWidth() .padding(vertical = 10.dp), - onClick = { payViaIntent(lnInvoice, context) { showErrorMessageDialog = it } }, + onClick = { payViaIntent(lnInvoice, context, { }) { showErrorMessageDialog = it } }, shape = QuoteBorder, colors = ButtonDefaults.buttonColors( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListNewFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListNewFeedFilter.kt index 567132745..a6ef95299 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListNewFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListNewFeedFilter.kt @@ -46,12 +46,12 @@ class ChatroomListNewFeedFilter(val account: Account) : AdditiveFeedFilter val privateMessages = newChatrooms.mapNotNull { it -> - it.value.roomMessages.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).lastOrNull { + it.value.roomMessages.sortedWith(DefaultFeedOrder).firstOrNull { it.event != null } } - return privateMessages.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() + return privateMessages.sortedWith(DefaultFeedOrder) } override fun updateListWith( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/DefaultFeedOrder.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/DefaultFeedOrder.kt index 873f671e8..30a15bc5a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/DefaultFeedOrder.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/DefaultFeedOrder.kt @@ -21,5 +21,23 @@ package com.vitorpamplona.amethyst.ui.dal import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.quartz.events.Event -val DefaultFeedOrder = compareBy({ it.createdAt() }, { it.idHex }).reversed() +val DefaultFeedOrder: Comparator = + compareBy( + { + val noteEvent = it.event + if (noteEvent == null) { + null + } else { + if (noteEvent is Event) { + noteEvent.createdAt + } else { + null + } + } + }, + { + it.idHex + }, + ).reversed() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt index 5a53f964b..956c421bd 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt @@ -439,6 +439,15 @@ fun ZapVote( ) } }, + justShowError = { + scope.launch { + showErrorMessageDialog = + StringToastMsg( + context.getString(R.string.error_dialog_zap_error), + it, + ) + } + }, ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index 4256f83a1..48cd16e9a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -140,9 +140,6 @@ import kotlinx.collections.immutable.toImmutableMap import kotlinx.collections.immutable.toImmutableSet import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import java.math.BigDecimal -import java.math.RoundingMode -import java.text.DecimalFormat import kotlin.math.roundToInt @Composable @@ -985,7 +982,8 @@ fun ZapReaction( if (wantsToZap) { ZapAmountChoicePopup( baseNote = baseNote, - iconSize = iconSize, + zapAmountChoices = accountViewModel.account.zapAmountChoices, + popupYOffset = iconSize, accountViewModel = accountViewModel, onDismiss = { wantsToZap = false @@ -1042,6 +1040,11 @@ fun ZapReaction( showErrorMessageDialog = showErrorMessageDialog + it } }, + justShowError = { + scope.launch { + showErrorMessageDialog = showErrorMessageDialog + it + } + }, ) } @@ -1430,8 +1433,9 @@ private fun ActionableReactionButton( @Composable fun ZapAmountChoicePopup( baseNote: Note, + zapAmountChoices: List, accountViewModel: AccountViewModel, - iconSize: Dp, + popupYOffset: Dp, onDismiss: () -> Unit, onChangeAmount: () -> Unit, onError: (title: String, text: String) -> Unit, @@ -1441,15 +1445,15 @@ fun ZapAmountChoicePopup( val context = LocalContext.current val zapMessage = "" - val iconSizePx = with(LocalDensity.current) { -iconSize.toPx().toInt() } + val yOffset = with(LocalDensity.current) { -popupYOffset.toPx().toInt() } Popup( alignment = Alignment.BottomCenter, - offset = IntOffset(0, iconSizePx), + offset = IntOffset(0, yOffset), onDismissRequest = { onDismiss() }, ) { FlowRow(horizontalArrangement = Arrangement.Center) { - accountViewModel.account.zapAmountChoices.forEach { amountInSats -> + zapAmountChoices.forEach { amountInSats -> Button( modifier = Modifier.padding(horizontal = 3.dp), onClick = { @@ -1512,25 +1516,3 @@ fun showCount(count: Int?): String { else -> "$count" } } - -val OneGiga = BigDecimal(1000000000) -val OneMega = BigDecimal(1000000) -val TenKilo = BigDecimal(10000) -val OneKilo = BigDecimal(1000) - -var dfG: DecimalFormat = DecimalFormat("#.0G") -var dfM: DecimalFormat = DecimalFormat("#.0M") -var dfK: DecimalFormat = DecimalFormat("#.0k") -var dfN: DecimalFormat = DecimalFormat("#") - -fun showAmount(amount: BigDecimal?): String { - if (amount == null) return "" - if (amount.abs() < BigDecimal(0.01)) return "" - - return when { - amount >= OneGiga -> dfG.format(amount.div(OneGiga).setScale(0, RoundingMode.HALF_UP)) - amount >= OneMega -> dfM.format(amount.div(OneMega).setScale(0, RoundingMode.HALF_UP)) - amount >= TenKilo -> dfK.format(amount.div(OneKilo).setScale(0, RoundingMode.HALF_UP)) - else -> dfN.format(amount) - } -} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt index 2c605afc7..84e592d7a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt @@ -347,12 +347,12 @@ fun PayViaIntentDialog( accountViewModel: AccountViewModel, onClose: () -> Unit, onError: (String) -> Unit, + justShowError: (String) -> Unit, ) { val context = LocalContext.current if (payingInvoices.size == 1) { - payViaIntent(payingInvoices.first().invoice, context, onError) - onClose() + payViaIntent(payingInvoices.first().invoice, context, onClose, onError) } else { Dialog( onDismissRequest = onClose, @@ -422,9 +422,7 @@ fun PayViaIntentDialog( Spacer(modifier = DoubleHorzSpacer) PayButton(isActive = !paid.value) { - paid.value = true - - payViaIntent(it.invoice, context, onError) + payViaIntent(it.invoice, context, { paid.value = true }, justShowError) } } } @@ -437,6 +435,7 @@ fun PayViaIntentDialog( fun payViaIntent( invoice: String, context: Context, + onPaid: () -> Unit, onError: (String) -> Unit, ) { try { @@ -444,6 +443,7 @@ fun payViaIntent( intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK ContextCompat.startActivity(context, intent, null) + onPaid() } catch (e: Exception) { if (e is CancellationException) throw e // don't display ugly error messages diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapFormatter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapFormatter.kt new file mode 100644 index 000000000..0ad29ac7f --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapFormatter.kt @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.note + +import java.math.BigDecimal +import java.math.RoundingMode +import java.text.DecimalFormat + +val TenGiga = BigDecimal(10000000000) +val OneGiga = BigDecimal(1000000000) +val TenMega = BigDecimal(10000000) +val OneMega = BigDecimal(1000000) +val TenKilo = BigDecimal(10000) +val OneKilo = BigDecimal(1000) + +private val dfGBig = + object : ThreadLocal() { + override fun initialValue() = DecimalFormat("#.#G") + } + +private val dfGSmall = + object : ThreadLocal() { + override fun initialValue() = DecimalFormat("#.0G") + } + +private val dfMBig = + object : ThreadLocal() { + override fun initialValue() = DecimalFormat("#.#M") + } + +private val dfMSmall = + object : ThreadLocal() { + override fun initialValue() = DecimalFormat("#.0M") + } + +private val dfK = + object : ThreadLocal() { + override fun initialValue() = DecimalFormat("#.#k") + } + +private val dfN = + object : ThreadLocal() { + override fun initialValue() = DecimalFormat("#") + } + +fun showAmount(amount: BigDecimal?): String { + if (amount == null) return "" + if (amount.abs() < BigDecimal(0.01)) return "" + + return when { + amount >= TenGiga -> dfGBig.get().format(amount.div(OneGiga).setScale(0, RoundingMode.HALF_UP)) + amount >= OneGiga -> dfGSmall.get().format(amount.div(OneGiga).setScale(0, RoundingMode.HALF_UP)) + amount >= TenMega -> dfMBig.get().format(amount.div(OneMega).setScale(0, RoundingMode.HALF_UP)) + amount >= OneMega -> dfMSmall.get().format(amount.div(OneMega).setScale(0, RoundingMode.HALF_UP)) + amount >= TenKilo -> dfK.get().format(amount.div(OneKilo).setScale(0, RoundingMode.HALF_UP)) + else -> dfN.get().format(amount) + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapTheDevsCard.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapTheDevsCard.kt index 6af37c656..2f6d271f8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapTheDevsCard.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapTheDevsCard.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.ui.note +import android.content.Context import androidx.compose.animation.Crossfade import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement @@ -77,7 +78,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.ModifierWidth3dp import com.vitorpamplona.amethyst.ui.theme.Size10dp import com.vitorpamplona.amethyst.ui.theme.Size20Modifier -import com.vitorpamplona.amethyst.ui.theme.Size20dp +import com.vitorpamplona.amethyst.ui.theme.Size35dp import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.ui.theme.imageModifier @@ -301,12 +302,12 @@ fun ZapDonationButton( baseNote: Note, grayTint: Color, accountViewModel: AccountViewModel, - iconSize: Dp = Size20dp, + iconSize: Dp = Size35dp, iconSizeModifier: Modifier = Size20Modifier, animationSize: Dp = 14.dp, nav: (String) -> Unit, ) { - var wantsToZap by remember { mutableStateOf(false) } + var wantsToZap by remember { mutableStateOf?>(null) } var showErrorMessageDialog by remember { mutableStateOf(null) } var wantsToPay by remember(baseNote) { @@ -323,14 +324,14 @@ fun ZapDonationButton( Button( onClick = { - zapClick( + customZapClick( baseNote, accountViewModel, context, onZappingProgress = { progress: Float -> scope.launch { zappingProgress = progress } }, - onMultipleChoices = { wantsToZap = true }, + onMultipleChoices = { options -> wantsToZap = options }, onError = { _, message -> scope.launch { zappingProgress = 0f @@ -342,17 +343,18 @@ fun ZapDonationButton( }, modifier = Modifier.fillMaxWidth(), ) { - if (wantsToZap) { + if (wantsToZap != null) { ZapAmountChoicePopup( baseNote = baseNote, - iconSize = iconSize, + zapAmountChoices = wantsToZap ?: accountViewModel.account.zapAmountChoices, + popupYOffset = iconSize, accountViewModel = accountViewModel, onDismiss = { - wantsToZap = false + wantsToZap = null zappingProgress = 0f }, onChangeAmount = { - wantsToZap = false + wantsToZap = null }, onError = { _, message -> scope.launch { @@ -395,6 +397,11 @@ fun ZapDonationButton( showErrorMessageDialog = it } }, + justShowError = { + scope.launch { + showErrorMessageDialog = it + } + }, ) } @@ -448,3 +455,58 @@ fun ZapDonationButton( } } } + +fun customZapClick( + baseNote: Note, + accountViewModel: AccountViewModel, + context: Context, + onZappingProgress: (Float) -> Unit, + onMultipleChoices: (List) -> Unit, + onError: (String, String) -> Unit, + onPayViaIntent: (ImmutableList) -> Unit, +) { + if (baseNote.isDraft()) { + accountViewModel.toast( + R.string.draft_note, + R.string.it_s_not_possible_to_zap_to_a_draft_note, + ) + return + } + + if (accountViewModel.account.zapAmountChoices.isEmpty()) { + accountViewModel.toast( + context.getString(R.string.error_dialog_zap_error), + context.getString(R.string.no_zap_amount_setup_long_press_to_change), + ) + } else if (!accountViewModel.isWriteable()) { + accountViewModel.toast( + context.getString(R.string.error_dialog_zap_error), + context.getString(R.string.login_with_a_private_key_to_be_able_to_send_zaps), + ) + } else if (accountViewModel.account.zapAmountChoices.size == 1) { + val amount = accountViewModel.account.zapAmountChoices.first() + + if (amount > 600) { + accountViewModel.zap( + baseNote, + amount * 1000, + null, + "", + context, + onError = onError, + onProgress = { onZappingProgress(it) }, + zapType = accountViewModel.account.defaultZapType, + onPayViaIntent = onPayViaIntent, + ) + } else { + onMultipleChoices(listOf(1000L, 5_000L, 10_000L)) + // recommends amounts for a monthly release. + } + } else if (accountViewModel.account.zapAmountChoices.size > 1) { + if (accountViewModel.account.zapAmountChoices.any { it > 600 }) { + onMultipleChoices(accountViewModel.account.zapAmountChoices) + } else { + onMultipleChoices(listOf(1000L, 5_000L, 10_000L)) + } + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index c19313acf..3e8348892 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -1193,7 +1193,7 @@ fun DisplayLNAddress( } } } else { - payViaIntent(it, context) { showErrorMessageDialog = it } + payViaIntent(it, context, { zapExpanded = false }, { showErrorMessageDialog = it }) } }, onClose = { zapExpanded = false }, diff --git a/app/src/main/res/values-pl-rPL/strings.xml b/app/src/main/res/values-pl-rPL/strings.xml index 66a570486..6b02d4f8a 100644 --- a/app/src/main/res/values-pl-rPL/strings.xml +++ b/app/src/main/res/values-pl-rPL/strings.xml @@ -1,2 +1,109 @@ - + + Najedź na kod QR + Pokaż QR kod + Zdjęcie profilowe + Twoje zdjęcie profilowe + Zeskanuj QR kod + Pokaż mimo wszystko + Ten post został ukryty, ponieważ dotyczy ukrytych użytkowników lub słów + Post został wyciszony lub zgłoszony przez + Zdjęcie kanału + Przywołane zdarzenie nie zostało znalezione + Nie można odszyfrować wiadomości + Zdjęcie grupy + Niedozwolona zawartość + Spam + Podszywanie się + Nielegalne zachowanie + Ikona retransmitera + Autor nieznany + Skopiuj tekst + Zablokuj / Zgłoś + + Zgłoś spam/oszustwo + Zgłoś podszywanie się + Zgłoś niedozwoloną zawartość + Dodaj + Płatność zakończona pomyślnie + Wyloguj się + Zapłać + Dziękuję bardzo! + Nowy kanał + Nazwa kanału + Moja wspaniała Grupa + Adres URL zdjęcia + "O nas. " + Co masz na myśli? + Zapisz + Utwórz + Anuluj + Adres retransmitera + Dodaj Retransmiter + Nazwa użytkownika + Adres URL strony + Dodaj zdjęcie + Więcej opcji + " Retransmitery" + Odblokuj + Kopiuj ID użytkownika + Odblokuj użytkownika + "npub, nazwa użytkownika, tekst" + Wyczyść + Logo aplikacji + nsec. lub npub. + Pokaż hasło + Ukryj hasło + Nieprawidłowy klucz + "Akceptuję " + warunki użytkowania + Wymagane jest zaakceptowanie warunków użytkowania + Hasło jest wymagane + Zaloguj się + Zarejestruj się + Utwórz konto + Jak się do ciebie zwracać? + Nie posiadasz konta Nostr? + Masz już konto Nostr? + Utwórz nowe konto + Wygeneruj nowy klucz + Spróbuj ponownie + Odśwież + i zdjęcie + zmieniono nazwę czatu na + Wyjdź + Przestań obserwować + Czat Publiczny + Usuń + Zaznacz tekst + Dodaj nowe konto + Konta + Wybierz Konto + Dodaj konto + Wstecz + Wybierz + Skopiuj tekst + Usuń + Przestań obserwować + Śledź + Nie pokazuj ponownie + Dodaj wiadomość publiczną + Dodaj prywatną wiadomość + Dziękujemy za całą twoją pracę! + Utwórz i Dodaj + Dodaj wideo + Dodaj dokument + Każdy może zobaczyć transakcję i wiadomość + Nadawca i odbiorca mogą zobaczyć się nawzajem i przeczytać wiadomość + Twoje retransmitery (NIP-95) + Pliki są przechowywane przez Twoje retransmitery. Nowy NIP: sprawdź, czy jest obsługiwany + Odczytaj z Retransmitera + Zapisz do Retransmitera + Wystąpił błąd podczas próby uzyskania informacji o retransmiterze z %1$s + Retransmitery + Wybierz retransmiter, aby kontynuować + Nie można pobrać dokumentu retransmitera + Retransmiter %1$s + Rozwiń listę retransmiterów + Wybór listy retransmiterów + diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/preview/MetaTagsParser.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/preview/MetaTagsParser.kt index 75b9e7325..68fba9e94 100644 --- a/commons/src/main/java/com/vitorpamplona/amethyst/commons/preview/MetaTagsParser.kt +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/preview/MetaTagsParser.kt @@ -130,7 +130,7 @@ object MetaTagsParser { // - commonly used character references in attribute values are resolved private class Attrs { companion object { - val RE_CHAR_REF = Regex("""&(\w+)(;?)""") + val RE_CHAR_REF = Regex("""&(#?)(\w+)(;?)""") val BASE_CHAR_REFS = mapOf( "amp" to "&", @@ -141,6 +141,8 @@ object MetaTagsParser { "LT" to "<", "gt" to ">", "GT" to ">", + "nbsp" to " ", + "NBSP" to " ", ) val CHAR_REFS = mapOf( @@ -148,16 +150,26 @@ object MetaTagsParser { "equals" to "=", "grave" to "`", "DiacriticalGrave" to "`", + "039" to "'", + "8217" to "’", + "8216" to "‘", + "39" to "'", + "ldquo" to "“", + "rdquo" to "”", + "mdash" to "—", + "hellip" to "…", + "x27" to "'", + "nbsp" to " ", ) fun replaceCharRefs(match: MatchResult): String { - val bcr = BASE_CHAR_REFS[match.groupValues[1]] + val bcr = BASE_CHAR_REFS[match.groupValues[2]] if (bcr != null) { return bcr } // non-base char refs must be terminated by ';' - if (match.groupValues[2].isNotEmpty()) { - val cr = CHAR_REFS[match.groupValues[1]] + if (match.groupValues[3].isNotEmpty()) { + val cr = CHAR_REFS[match.groupValues[2]] if (cr != null) { return cr } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8c11ae7b0..123e9636f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ espressoCore = "3.5.1" firebaseBom = "33.0.0" fragmentKtx = "1.7.0" gms = "4.4.1" -jacksonModuleKotlin = "2.17.0" +jacksonModuleKotlin = "2.17.1" jna = "5.14.0" junit = "4.13.2" kotlin = "1.9.23"