From c0aea75c163e1c28f22d102666f6a97441973041 Mon Sep 17 00:00:00 2001 From: David Kaspar Date: Tue, 23 Apr 2024 17:16:53 +0200 Subject: [PATCH 01/12] added test for html entity numbers to regular expression added a few common html entities and entity numbers --- .../amethyst/commons/preview/MetaTagsParser.kt | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) 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..047625140 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 "&", @@ -148,16 +148,25 @@ 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 "'", ) 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]] + val cr = CHAR_REFS[match.groupValues[2]] if (cr != null) { return cr } From 94af0eb220753e6b84469d0647a02cfbbc102c3a Mon Sep 17 00:00:00 2001 From: David Kaspar Date: Tue, 23 Apr 2024 19:59:12 +0200 Subject: [PATCH 02/12] incremented matching group due to added matching group added nbsp entity --- .../vitorpamplona/amethyst/commons/preview/MetaTagsParser.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 047625140..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 @@ -141,6 +141,8 @@ object MetaTagsParser { "LT" to "<", "gt" to ">", "GT" to ">", + "nbsp" to " ", + "NBSP" to " ", ) val CHAR_REFS = mapOf( @@ -157,6 +159,7 @@ object MetaTagsParser { "mdash" to "—", "hellip" to "…", "x27" to "'", + "nbsp" to " ", ) fun replaceCharRefs(match: MatchResult): String { @@ -165,7 +168,7 @@ object MetaTagsParser { return bcr } // non-base char refs must be terminated by ';' - if (match.groupValues[2].isNotEmpty()) { + if (match.groupValues[3].isNotEmpty()) { val cr = CHAR_REFS[match.groupValues[2]] if (cr != null) { return cr From 326e38f293e928637f88e55df7f7351f341e62e7 Mon Sep 17 00:00:00 2001 From: jeremyd Date: Fri, 10 May 2024 14:46:06 -0700 Subject: [PATCH 03/12] allow relay selection dialog to pick any relays (instead of just write relays) --- .../vitorpamplona/amethyst/model/Account.kt | 4 +++ .../amethyst/service/relays/Relay.kt | 32 +++++++++++++++++++ .../amethyst/service/relays/RelayPool.kt | 6 +++- .../ui/actions/RelaySelectionDialog.kt | 2 +- 4 files changed, 42 insertions(+), 2 deletions(-) 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..5d61b0187 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -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/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), From 8ca53e970729424a884e0249fd20ede6ed7e5e6c Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 10 May 2024 19:47:40 -0400 Subject: [PATCH 04/12] Updates jackson --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From ab4d01583bb3c742769dac5ee7314430e9b79209 Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Mon, 13 May 2024 14:44:00 +0000 Subject: [PATCH 05/12] New Crowdin translations by GitHub Action --- app/src/main/res/values-pl-rPL/strings.xml | 109 ++++++++++++++++++++- 1 file changed, 108 insertions(+), 1 deletion(-) 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 + From 9ad62ef2633646b842a5df4860f580f560c07275 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 13 May 2024 13:22:58 -0400 Subject: [PATCH 06/12] Reduces default zap amounts due to the change of bitcoin price --- app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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..8f29730a3 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()) From 6bac18c5dfea0429d1b5ae2259325c09868c97bd Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 13 May 2024 13:24:20 -0400 Subject: [PATCH 07/12] Fixes the offset position of the payment amounts on the Zap the Devs message --- .../java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt | 6 +++--- .../com/vitorpamplona/amethyst/ui/note/ZapTheDevsCard.kt | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) 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..5b3b41a8c 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 @@ -1431,7 +1431,7 @@ private fun ActionableReactionButton( fun ZapAmountChoicePopup( baseNote: Note, accountViewModel: AccountViewModel, - iconSize: Dp, + popupYOffset: Dp, onDismiss: () -> Unit, onChangeAmount: () -> Unit, onError: (title: String, text: String) -> Unit, @@ -1441,11 +1441,11 @@ 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) { 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..645819dcb 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 @@ -77,7 +77,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,7 +301,7 @@ fun ZapDonationButton( baseNote: Note, grayTint: Color, accountViewModel: AccountViewModel, - iconSize: Dp = Size20dp, + iconSize: Dp = Size35dp, iconSizeModifier: Modifier = Size20Modifier, animationSize: Dp = 14.dp, nav: (String) -> Unit, @@ -345,7 +345,7 @@ fun ZapDonationButton( if (wantsToZap) { ZapAmountChoicePopup( baseNote = baseNote, - iconSize = iconSize, + popupYOffset = iconSize, accountViewModel = accountViewModel, onDismiss = { wantsToZap = false From 065ba1c1650ec0a79d5726c32fbc872f7b4e1e11 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 13 May 2024 13:25:50 -0400 Subject: [PATCH 08/12] Better format zap amounts (don't show .0 if the previous numbers are large) --- .../amethyst/ui/note/ReactionsRow.kt | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) 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 5b3b41a8c..c2f6ad862 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 @@ -1513,14 +1513,18 @@ fun showCount(count: Int?): String { } } +val TenGiga = BigDecimal(10000000000) val OneGiga = BigDecimal(1000000000) +val TenMega = BigDecimal(10000000) 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 dfGBig: DecimalFormat = DecimalFormat("#.#G") +var dfGSmall: DecimalFormat = DecimalFormat("#.0G") +var dfMBig: DecimalFormat = DecimalFormat("#.#M") +var dfMSmall: DecimalFormat = DecimalFormat("#.0M") +var dfK: DecimalFormat = DecimalFormat("#.#k") var dfN: DecimalFormat = DecimalFormat("#") fun showAmount(amount: BigDecimal?): String { @@ -1528,8 +1532,10 @@ fun showAmount(amount: BigDecimal?): String { 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 >= TenGiga -> dfGBig.format(amount.div(OneGiga).setScale(0, RoundingMode.HALF_UP)) + amount >= OneGiga -> dfGSmall.format(amount.div(OneGiga).setScale(0, RoundingMode.HALF_UP)) + amount >= TenMega -> dfMBig.format(amount.div(OneMega).setScale(0, RoundingMode.HALF_UP)) + amount >= OneMega -> dfMSmall.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) } From 58ed27dc756e0b931a363df2d725f2debbfbc162 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 13 May 2024 13:27:03 -0400 Subject: [PATCH 09/12] Adds recommended amounts for the Zap the Devs Block error messages from closing the Zap split payment screen. --- .../amethyst/service/ZapPaymentHandler.kt | 40 ++++++---- .../ui/components/ClickableWithdrawal.kt | 2 +- .../amethyst/ui/components/InvoicePreview.kt | 2 +- .../amethyst/ui/note/PollNote.kt | 9 +++ .../amethyst/ui/note/ReactionsRow.kt | 11 ++- .../amethyst/ui/note/ZapCustomDialog.kt | 10 +-- .../amethyst/ui/note/ZapTheDevsCard.kt | 74 +++++++++++++++++-- .../ui/screen/loggedIn/ProfileScreen.kt | 2 +- 8 files changed, 119 insertions(+), 31 deletions(-) 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/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/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/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 c2f6ad862..fb0765cdc 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 @@ -985,7 +985,8 @@ fun ZapReaction( if (wantsToZap) { ZapAmountChoicePopup( baseNote = baseNote, - iconSize = iconSize, + zapAmountChoices = accountViewModel.account.zapAmountChoices, + popupYOffset = iconSize, accountViewModel = accountViewModel, onDismiss = { wantsToZap = false @@ -1042,6 +1043,11 @@ fun ZapReaction( showErrorMessageDialog = showErrorMessageDialog + it } }, + justShowError = { + scope.launch { + showErrorMessageDialog = showErrorMessageDialog + it + } + }, ) } @@ -1430,6 +1436,7 @@ private fun ActionableReactionButton( @Composable fun ZapAmountChoicePopup( baseNote: Note, + zapAmountChoices: List, accountViewModel: AccountViewModel, popupYOffset: Dp, onDismiss: () -> Unit, @@ -1449,7 +1456,7 @@ fun ZapAmountChoicePopup( onDismissRequest = { onDismiss() }, ) { FlowRow(horizontalArrangement = Arrangement.Center) { - accountViewModel.account.zapAmountChoices.forEach { amountInSats -> + zapAmountChoices.forEach { amountInSats -> Button( modifier = Modifier.padding(horizontal = 3.dp), onClick = { 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/ZapTheDevsCard.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapTheDevsCard.kt index 645819dcb..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 @@ -306,7 +307,7 @@ fun ZapDonationButton( 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, + 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 }, From 7310ef175f80d39cb7a6eebc45d55c585fa27119 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 13 May 2024 15:39:06 -0400 Subject: [PATCH 10/12] DecimalFormats are not thread safe --- .../amethyst/ui/note/ReactionsRow.kt | 31 -------- .../amethyst/ui/note/ZapFormatter.kt | 76 +++++++++++++++++++ 2 files changed, 76 insertions(+), 31 deletions(-) create mode 100644 app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapFormatter.kt 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 fb0765cdc..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 @@ -1519,31 +1516,3 @@ fun showCount(count: Int?): String { else -> "$count" } } - -val TenGiga = BigDecimal(10000000000) -val OneGiga = BigDecimal(1000000000) -val TenMega = BigDecimal(10000000) -val OneMega = BigDecimal(1000000) -val TenKilo = BigDecimal(10000) -val OneKilo = BigDecimal(1000) - -var dfGBig: DecimalFormat = DecimalFormat("#.#G") -var dfGSmall: DecimalFormat = DecimalFormat("#.0G") -var dfMBig: DecimalFormat = DecimalFormat("#.#M") -var dfMSmall: DecimalFormat = DecimalFormat("#.0M") -var dfK: DecimalFormat = DecimalFormat("#.#k") -var dfN: DecimalFormat = DecimalFormat("#") - -fun showAmount(amount: BigDecimal?): String { - if (amount == null) return "" - if (amount.abs() < BigDecimal(0.01)) return "" - - return when { - amount >= TenGiga -> dfGBig.format(amount.div(OneGiga).setScale(0, RoundingMode.HALF_UP)) - amount >= OneGiga -> dfGSmall.format(amount.div(OneGiga).setScale(0, RoundingMode.HALF_UP)) - amount >= TenMega -> dfMBig.format(amount.div(OneMega).setScale(0, RoundingMode.HALF_UP)) - amount >= OneMega -> dfMSmall.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/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) + } +} From 0c1187e4f5c3100d0f4190d122a18003fa879fd5 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 13 May 2024 15:39:29 -0400 Subject: [PATCH 11/12] Reducing the creation of new buttons when modifier objects change --- .../amethyst/ui/components/ExpandableRichTextViewer.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 = From 22c96d2489fb26100c6c04fccaf9e4b633c9b9f4 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 13 May 2024 15:40:24 -0400 Subject: [PATCH 12/12] Use the default comparator instead of creating a new object every time --- .../ui/dal/ChatroomListNewFeedFilter.kt | 4 ++-- .../amethyst/ui/dal/DefaultFeedOrder.kt | 20 ++++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) 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()