Showing progress bars when Zaps are requested

This commit is contained in:
Vitor Pamplona
2023-03-22 14:24:33 -04:00
parent ba40b1815f
commit 1e8bc10ad1
4 changed files with 104 additions and 25 deletions

View File

@@ -130,12 +130,14 @@ class LightningAddressResolver {
) )
} }
fun lnAddressInvoice(lnaddress: String, milliSats: Long, message: String, nostrRequest: String? = null, onSuccess: (String) -> Unit, onError: (String) -> Unit) { fun lnAddressInvoice(lnaddress: String, milliSats: Long, message: String, nostrRequest: String? = null, onSuccess: (String) -> Unit, onError: (String) -> Unit, onProgress: (percent: Float) -> Unit) {
val mapper = jacksonObjectMapper() val mapper = jacksonObjectMapper()
fetchLightningAddressJson( fetchLightningAddressJson(
lnaddress, lnaddress,
onSuccess = { lnAddressJson -> onSuccess = { lnAddressJson ->
onProgress(0.4f)
val lnurlp = try { val lnurlp = try {
mapper.readTree(lnAddressJson) mapper.readTree(lnAddressJson)
} catch (t: Throwable) { } catch (t: Throwable) {
@@ -158,6 +160,8 @@ class LightningAddressResolver {
message, message,
if (allowsNostr) nostrRequest else null, if (allowsNostr) nostrRequest else null,
onSuccess = { onSuccess = {
onProgress(0.6f)
val lnInvoice = try { val lnInvoice = try {
mapper.readTree(it) mapper.readTree(it)
} catch (t: Throwable) { } catch (t: Throwable) {
@@ -169,6 +173,7 @@ class LightningAddressResolver {
// Forces LN Invoice amount to be the requested amount. // Forces LN Invoice amount to be the requested amount.
val invoiceAmount = LnInvoiceUtil.getAmountInSats(pr) val invoiceAmount = LnInvoiceUtil.getAmountInSats(pr)
if (invoiceAmount.multiply(BigDecimal(1000)).toLong() == BigDecimal(milliSats).toLong()) { if (invoiceAmount.multiply(BigDecimal(1000)).toLong() == BigDecimal(milliSats).toLong()) {
onProgress(0.7f)
onSuccess(pr) onSuccess(pr)
} else { } else {
onError("Incorrect invoice amount (${invoiceAmount.toLong()} sats) from server") onError("Incorrect invoice amount (${invoiceAmount.toLong()} sats) from server")

View File

@@ -149,6 +149,8 @@ fun InvoiceRequest(lud16: String, toUserPubKeyHex: String, account: Account, onC
Toast.makeText(context, it, Toast.LENGTH_SHORT).show() Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
onClose() onClose()
} }
},
onProgress = {
} }
) )
}, },

View File

@@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults import androidx.compose.material.ButtonDefaults
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.IconButton import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
@@ -33,6 +34,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
@@ -307,7 +309,10 @@ fun ZapReaction(
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var zappingProgress by remember { mutableStateOf(0f) }
Row( Row(
verticalAlignment = CenterVertically,
modifier = Modifier modifier = Modifier
.then(Modifier.size(20.dp)) .then(Modifier.size(20.dp))
.combinedClickable( .combinedClickable(
@@ -336,17 +341,26 @@ fun ZapReaction(
.show() .show()
} }
} else if (account.zapAmountChoices.size == 1) { } else if (account.zapAmountChoices.size == 1) {
scope.launch(Dispatchers.IO) {
accountViewModel.zap( accountViewModel.zap(
baseNote, baseNote,
account.zapAmountChoices.first() * 1000, account.zapAmountChoices.first() * 1000,
"", "",
context context,
) { onError = {
scope.launch { scope.launch {
zappingProgress = 0f
Toast Toast
.makeText(context, it, Toast.LENGTH_SHORT) .makeText(context, it, Toast.LENGTH_SHORT)
.show() .show()
} }
},
onProgress = {
scope.launch(Dispatchers.Main) {
zappingProgress = it
}
}
)
} }
} else if (account.zapAmountChoices.size > 1) { } else if (account.zapAmountChoices.size > 1) {
wantsToZap = true wantsToZap = true
@@ -363,6 +377,7 @@ fun ZapReaction(
accountViewModel, accountViewModel,
onDismiss = { onDismiss = {
wantsToZap = false wantsToZap = false
zappingProgress = 0f
}, },
onChangeAmount = { onChangeAmount = {
wantsToZap = false wantsToZap = false
@@ -370,8 +385,14 @@ fun ZapReaction(
}, },
onError = { onError = {
scope.launch { scope.launch {
zappingProgress = 0f
Toast.makeText(context, it, Toast.LENGTH_SHORT).show() Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
} }
},
onProgress = {
scope.launch(Dispatchers.Main) {
zappingProgress = it
}
} }
) )
} }
@@ -380,6 +401,7 @@ fun ZapReaction(
} }
if (zappedNote?.isZappedBy(account.userProfile()) == true) { if (zappedNote?.isZappedBy(account.userProfile()) == true) {
zappingProgress = 1f
Icon( Icon(
imageVector = Icons.Default.Bolt, imageVector = Icons.Default.Bolt,
contentDescription = stringResource(R.string.zaps), contentDescription = stringResource(R.string.zaps),
@@ -387,12 +409,19 @@ fun ZapReaction(
tint = BitcoinOrange tint = BitcoinOrange
) )
} else { } else {
if (zappingProgress < 0.1 || zappingProgress > 0.99) {
Icon( Icon(
imageVector = Icons.Outlined.Bolt, imageVector = Icons.Outlined.Bolt,
contentDescription = stringResource(id = R.string.zaps), contentDescription = stringResource(id = R.string.zaps),
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
tint = grayTint tint = grayTint
) )
} else {
CircularProgressIndicator(
progress = zappingProgress,
modifier = Modifier.size(15.dp)
)
}
} }
} }
@@ -485,12 +514,21 @@ private fun BoostTypeChoicePopup(baseNote: Note, accountViewModel: AccountViewMo
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class) @OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
@Composable @Composable
fun ZapAmountChoicePopup(baseNote: Note, accountViewModel: AccountViewModel, onDismiss: () -> Unit, onChangeAmount: () -> Unit, onError: (text: String) -> Unit) { fun ZapAmountChoicePopup(
baseNote: Note,
accountViewModel: AccountViewModel,
onDismiss: () -> Unit,
onChangeAmount: () -> Unit,
onError: (text: String) -> Unit,
onProgress: (percent: Float) -> Unit
) {
val context = LocalContext.current val context = LocalContext.current
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
val scope = rememberCoroutineScope()
Popup( Popup(
alignment = Alignment.BottomCenter, alignment = Alignment.BottomCenter,
offset = IntOffset(0, -50), offset = IntOffset(0, -50),
@@ -501,8 +539,17 @@ fun ZapAmountChoicePopup(baseNote: Note, accountViewModel: AccountViewModel, onD
Button( Button(
modifier = Modifier.padding(horizontal = 3.dp), modifier = Modifier.padding(horizontal = 3.dp),
onClick = { onClick = {
accountViewModel.zap(baseNote, amountInSats * 1000, "", context, onError) scope.launch(Dispatchers.IO) {
accountViewModel.zap(
baseNote,
amountInSats * 1000,
"",
context,
onError,
onProgress
)
onDismiss() onDismiss()
}
}, },
shape = RoundedCornerShape(20.dp), shape = RoundedCornerShape(20.dp),
colors = ButtonDefaults colors = ButtonDefaults
@@ -516,8 +563,17 @@ fun ZapAmountChoicePopup(baseNote: Note, accountViewModel: AccountViewModel, onD
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = Modifier.combinedClickable( modifier = Modifier.combinedClickable(
onClick = { onClick = {
accountViewModel.zap(baseNote, amountInSats * 1000, "", context, onError) scope.launch(Dispatchers.IO) {
accountViewModel.zap(
baseNote,
amountInSats * 1000,
"",
context,
onError,
onProgress
)
onDismiss() onDismiss()
}
}, },
onLongClick = { onLongClick = {
onChangeAmount() onChangeAmount()

View File

@@ -7,6 +7,7 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.map import androidx.lifecycle.map
import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AccountState import com.vitorpamplona.amethyst.model.AccountState
@@ -14,6 +15,9 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
import com.vitorpamplona.amethyst.service.model.ReportEvent import com.vitorpamplona.amethyst.service.model.ReportEvent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.Locale import java.util.Locale
class AccountViewModel(private val account: Account) : ViewModel() { class AccountViewModel(private val account: Account) : ViewModel() {
@@ -48,7 +52,7 @@ class AccountViewModel(private val account: Account) : ViewModel() {
account.delete(account.boostsTo(note)) account.delete(account.boostsTo(note))
} }
fun zap(note: Note, amount: Long, message: String, context: Context, onError: (String) -> Unit) { suspend fun zap(note: Note, amount: Long, message: String, context: Context, onError: (String) -> Unit, onProgress: (percent: Float) -> Unit) {
val lud16 = note.author?.info?.lud16?.trim() ?: note.author?.info?.lud06?.trim() val lud16 = note.author?.info?.lud16?.trim() ?: note.author?.info?.lud06?.trim()
if (lud16.isNullOrBlank()) { if (lud16.isNullOrBlank()) {
@@ -58,22 +62,34 @@ class AccountViewModel(private val account: Account) : ViewModel() {
val zapRequest = account.createZapRequestFor(note) val zapRequest = account.createZapRequestFor(note)
onProgress(0.10f)
LightningAddressResolver().lnAddressInvoice( LightningAddressResolver().lnAddressInvoice(
lud16, lud16,
amount, amount,
message, message,
zapRequest?.toJson(), zapRequest?.toJson(),
onSuccess = { onSuccess = {
onProgress(0.7f)
if (account.hasWalletConnectSetup()) { if (account.hasWalletConnectSetup()) {
account.sendZapPaymentRequestFor(it) account.sendZapPaymentRequestFor(it)
onProgress(0.8f)
// Awaits for the event to come back to LocalCache.
viewModelScope.launch(Dispatchers.IO) {
delay(1000)
onProgress(0f)
}
} else { } else {
runCatching { runCatching {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("lightning:$it")) val intent = Intent(Intent.ACTION_VIEW, Uri.parse("lightning:$it"))
ContextCompat.startActivity(context, intent, null) ContextCompat.startActivity(context, intent, null)
} }
onProgress(0f)
} }
}, },
onError = onError onError = onError,
onProgress = onProgress
) )
} }