diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/CachedLnInvoice.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/CachedLnInvoice.kt new file mode 100644 index 000000000..67a7edcc4 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/lnurl/CachedLnInvoice.kt @@ -0,0 +1,60 @@ +/** + * 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.service.lnurl + +import android.util.LruCache +import androidx.compose.runtime.Stable +import com.vitorpamplona.quartz.encoders.LnInvoiceUtil +import kotlinx.coroutines.CancellationException +import java.text.NumberFormat + +@Stable +data class InvoiceAmount(val invoice: String, val amount: String?) + +object CachedLnInvoiceParser { + val lnInvoicesCache = LruCache(20) + + fun cached(lnurl: String): InvoiceAmount? { + return lnInvoicesCache[lnurl] + } + + fun parse(lnbcWord: String): InvoiceAmount? { + val myInvoice = LnInvoiceUtil.findInvoice(lnbcWord) + if (myInvoice != null) { + val myInvoiceAmount = + try { + NumberFormat.getInstance().format(LnInvoiceUtil.getAmountInSats(myInvoice)) + } catch (e: Exception) { + if (e is CancellationException) throw e + e.printStackTrace() + null + } + + val lnInvoice = InvoiceAmount(myInvoice, myInvoiceAmount) + + lnInvoicesCache.put(lnbcWord, lnInvoice) + + return lnInvoice + } + + return null + } +} 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 497f70f51..e3afdfa93 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 @@ -34,10 +34,9 @@ import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -52,43 +51,30 @@ import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.service.lnurl.CachedLnInvoiceParser +import com.vitorpamplona.amethyst.service.lnurl.InvoiceAmount import com.vitorpamplona.amethyst.ui.note.ErrorMessageDialog import com.vitorpamplona.amethyst.ui.note.payViaIntent import com.vitorpamplona.amethyst.ui.theme.QuoteBorder import com.vitorpamplona.amethyst.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.ui.theme.subtleBorder -import com.vitorpamplona.quartz.encoders.LnInvoiceUtil -import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import java.text.NumberFormat - -@Stable data class InvoiceAmount(val invoice: String, val amount: String?) +import kotlinx.coroutines.withContext @Composable fun LoadValueFromInvoice( lnbcWord: String, inner: @Composable (invoiceAmount: InvoiceAmount?) -> Unit, ) { - var lnInvoice by remember { mutableStateOf(null) } - - LaunchedEffect(key1 = lnbcWord) { - launch(Dispatchers.IO) { - val myInvoice = LnInvoiceUtil.findInvoice(lnbcWord) - if (myInvoice != null) { - val myInvoiceAmount = - try { - NumberFormat.getInstance().format(LnInvoiceUtil.getAmountInSats(myInvoice)) - } catch (e: Exception) { - if (e is CancellationException) throw e - e.printStackTrace() - null - } - - lnInvoice = InvoiceAmount(myInvoice, myInvoiceAmount) + val lnInvoice by + produceState(initialValue = CachedLnInvoiceParser.cached(lnbcWord), key1 = lnbcWord) { + withContext(Dispatchers.IO) { + val newLnInvoice = CachedLnInvoiceParser.parse(lnbcWord) + if (value != newLnInvoice) { + value = newLnInvoice + } } } - } inner(lnInvoice) } @@ -128,17 +114,24 @@ fun InvoicePreview( Column( modifier = - Modifier.fillMaxWidth() + Modifier + .fillMaxWidth() .padding(start = 20.dp, end = 20.dp, top = 10.dp, bottom = 10.dp) .clip(shape = QuoteBorder) .border(1.dp, MaterialTheme.colorScheme.subtleBorder, QuoteBorder), ) { Column( - modifier = Modifier.fillMaxWidth().padding(20.dp), + modifier = + Modifier + .fillMaxWidth() + .padding(20.dp), ) { Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp), + modifier = + Modifier + .fillMaxWidth() + .padding(bottom = 10.dp), ) { Icon( painter = painterResource(R.drawable.lightning), @@ -162,12 +155,18 @@ fun InvoicePreview( text = "$it ${stringResource(id = R.string.sats)}", fontSize = 25.sp, fontWeight = FontWeight.W500, - modifier = Modifier.fillMaxWidth().padding(vertical = 10.dp), + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 10.dp), ) } Button( - modifier = Modifier.fillMaxWidth().padding(vertical = 10.dp), + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 10.dp), onClick = { payViaIntent(lnInvoice, context) { showErrorMessageDialog = it } }, shape = QuoteBorder, colors =