From 20c2d19a9ca383e7cab0a05f019c4c3fed00605d Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 15 Sep 2023 17:08:19 -0400 Subject: [PATCH] Refining the Payment via Intent --- .../amethyst/service/ZapPaymentHandler.kt | 207 ++++++++++++++++++ .../amethyst/ui/note/PollNote.kt | 26 ++- .../amethyst/ui/note/ReactionsRow.kt | 42 +++- .../amethyst/ui/note/ZapCustomDialog.kt | 132 +++++++++++ .../ui/screen/loggedIn/AccountViewModel.kt | 162 +------------- app/src/main/res/values/strings.xml | 2 + 6 files changed, 410 insertions(+), 161 deletions(-) create mode 100644 app/src/main/java/com/vitorpamplona/amethyst/service/ZapPaymentHandler.kt diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/ZapPaymentHandler.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/ZapPaymentHandler.kt new file mode 100644 index 000000000..a0e0790d4 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/ZapPaymentHandler.kt @@ -0,0 +1,207 @@ +package com.vitorpamplona.amethyst.service + +import android.content.Context +import androidx.compose.runtime.Immutable +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver +import com.vitorpamplona.quartz.events.LnZapEvent +import com.vitorpamplona.quartz.events.PayInvoiceErrorResponse +import com.vitorpamplona.quartz.events.ZapSplitSetup +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlin.math.round + +class ZapPaymentHandler(val account: Account) { + + @Immutable + data class Payable( + val info: ZapSplitSetup, + val user: User?, + val amountMilliSats: Long, + val invoice: String + ) + + suspend fun zap( + note: Note, + amountMilliSats: Long, + pollOption: Int?, + message: String, + context: Context, + onError: (String) -> Unit, + onProgress: (percent: Float) -> Unit, + onPayViaIntent: (ImmutableList) -> Unit, + zapType: LnZapEvent.ZapType + ) = withContext(Dispatchers.IO) { + val zapSplitSetup = note.event?.zapSplitSetup() + + val zapsToSend = if (!zapSplitSetup.isNullOrEmpty()) { + zapSplitSetup + } else { + val lud16 = note.author?.info?.lud16?.trim() ?: note.author?.info?.lud06?.trim() + + if (lud16.isNullOrBlank()) { + onError(context.getString(R.string.user_does_not_have_a_lightning_address_setup_to_receive_sats)) + return@withContext + } + + listOf(ZapSplitSetup(lud16, null, weight = 1.0, true)) + } + + val totalWeight = zapsToSend.sumOf { it.weight } + + val invoicesToPayOnIntent = mutableListOf() + + zapsToSend.forEachIndexed { index, value -> + val outerProgressMin = index / zapsToSend.size.toFloat() + val outerProgressMax = (index + 1) / zapsToSend.size.toFloat() + + val zapValue = + round((amountMilliSats * value.weight / totalWeight) / 1000f).toLong() * 1000 + + if (value.isLnAddress) { + innerZap( + lud16 = value.lnAddressOrPubKeyHex, + note = note, + amount = zapValue, + pollOption = pollOption, + message = message, + context = context, + onError = onError, + onProgress = { + onProgress((it * (outerProgressMax - outerProgressMin)) + outerProgressMin) + }, + zapType = zapType, + onPayInvoiceThroughIntent = { + invoicesToPayOnIntent.add( + Payable( + info = value, + user = null, + amountMilliSats = zapValue, + invoice = it + ) + ) + } + ) + } else { + val user = LocalCache.getUserIfExists(value.lnAddressOrPubKeyHex) + val lud16 = user?.info?.lnAddress() + + if (lud16 != null) { + innerZap( + lud16 = lud16, + note = note, + amount = zapValue, + pollOption = pollOption, + message = message, + context = context, + onError = onError, + onProgress = { + onProgress((it * (outerProgressMax - outerProgressMin)) + outerProgressMin) + }, + zapType = zapType, + overrideUser = user, + onPayInvoiceThroughIntent = { + invoicesToPayOnIntent.add( + Payable( + info = value, + user = user, + amountMilliSats = zapValue, + invoice = it + ) + ) + } + ) + } else { + onError( + context.getString( + R.string.user_x_does_not_have_a_lightning_address_setup_to_receive_sats, + user?.toBestDisplayName() ?: value.lnAddressOrPubKeyHex + ) + ) + } + } + } + + if (invoicesToPayOnIntent.isNotEmpty()) { + onPayViaIntent(invoicesToPayOnIntent.toImmutableList()) + onProgress(1f) + } else { + launch(Dispatchers.IO) { + // Awaits for the event to come back to LocalCache. + delay(5000) + onProgress(1f) + } + } + } + + private suspend fun innerZap( + lud16: String, + note: Note, + amount: Long, + pollOption: Int?, + message: String, + context: Context, + onError: (String) -> Unit, + onProgress: (percent: Float) -> Unit, + onPayInvoiceThroughIntent: (String) -> Unit, + zapType: LnZapEvent.ZapType, + overrideUser: User? = null + ) { + var zapRequestJson = "" + + if (zapType != LnZapEvent.ZapType.NONZAP) { + val zapRequest = account.createZapRequestFor(note, pollOption, message, zapType, overrideUser) + if (zapRequest != null) { + zapRequestJson = zapRequest.toJson() + } + } + + onProgress(0.10f) + + LightningAddressResolver().lnAddressInvoice( + lud16, + amount, + message, + zapRequestJson, + onSuccess = { + onProgress(0.7f) + if (account.hasWalletConnectSetup()) { + account.sendZapPaymentRequestFor( + bolt11 = it, + note, + onResponse = { response -> + if (response is PayInvoiceErrorResponse) { + onProgress(0.0f) + onError( + response.error?.message + ?: response.error?.code?.toString() + ?: "Error parsing error message" + ) + } else { + onProgress(1f) + } + } + ) + onProgress(0.8f) + } else { + try { + onPayInvoiceThroughIntent(it) + } catch (e: Exception) { + onError(context.getString(R.string.lightning_wallets_not_found2)) + } + onProgress(0f) + } + }, + onError = onError, + onProgress = onProgress + ) + } +} \ No newline at end of file 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 13253cfb2..1184fa6b2 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 @@ -24,6 +24,7 @@ import androidx.compose.ui.window.Popup import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.service.ZapPaymentHandler import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange @@ -35,6 +36,8 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.events.ImmutableListOfLists import com.vitorpamplona.quartz.events.LnZapEvent import com.vitorpamplona.quartz.events.toImmutableListOfLists +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.util.* @@ -283,6 +286,12 @@ fun ZapVote( } var wantsToZap by remember { mutableStateOf(false) } + var wantsToPay by remember { + mutableStateOf>( + persistentListOf() + ) + } + var zappingProgress by remember { mutableStateOf(0f) } var showErrorMessageDialog by remember { mutableStateOf(null) } @@ -361,6 +370,9 @@ fun ZapVote( scope.launch(Dispatchers.Main) { zappingProgress = it } + }, + onPayViaIntent = { + }, zapType = accountViewModel.account.defaultZapType ) @@ -393,10 +405,19 @@ fun ZapVote( scope.launch(Dispatchers.Main) { zappingProgress = it } + }, + onPayViaIntent = { + wantsToPay = it } ) } + if (wantsToPay.isNotEmpty()) { + PayViaIntentDialog(payingInvoices = wantsToPay, accountViewModel = accountViewModel) { + wantsToPay = persistentListOf() + } + } + if (showErrorMessageDialog != null) { ErrorMessageDialog( title = stringResource(id = R.string.error_dialog_zap_error), @@ -463,7 +484,8 @@ fun FilteredZapAmountChoicePopup( onDismiss: () -> Unit, onChangeAmount: () -> Unit, onError: (text: String) -> Unit, - onProgress: (percent: Float) -> Unit + onProgress: (percent: Float) -> Unit, + onPayViaIntent: (ImmutableList) -> Unit ) { val context = LocalContext.current @@ -502,6 +524,7 @@ fun FilteredZapAmountChoicePopup( context, onError, onProgress, + onPayViaIntent, defaultZapType ) onDismiss() @@ -526,6 +549,7 @@ fun FilteredZapAmountChoicePopup( context, onError, onProgress, + onPayViaIntent, defaultZapType ) onDismiss() 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 6b3c2078a..7ee1a3027 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 @@ -78,6 +78,7 @@ import coil.request.CachePolicy import coil.request.ImageRequest import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.service.ZapPaymentHandler import com.vitorpamplona.amethyst.ui.actions.NewPostView import com.vitorpamplona.amethyst.ui.components.ImageUrlType import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer @@ -107,8 +108,11 @@ import com.vitorpamplona.amethyst.ui.theme.TinyBorders import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.placeholderTextColorFilter +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableMap +import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -907,6 +911,11 @@ fun ZapReaction( var wantsToChangeZapAmount by remember { mutableStateOf(false) } var wantsToSetCustomZap by remember { mutableStateOf(false) } var showErrorMessageDialog by remember { mutableStateOf(null) } + var wantsToPay by remember(baseNote) { + mutableStateOf>( + persistentListOf() + ) + } val context = LocalContext.current val scope = rememberCoroutineScope() @@ -938,6 +947,9 @@ fun ZapReaction( zappingProgress = 0f showErrorMessageDialog = it } + }, + onPayViaIntent = { + wantsToPay = it } ) }, @@ -971,6 +983,9 @@ fun ZapReaction( scope.launch(Dispatchers.Main) { zappingProgress = it } + }, + onPayViaIntent = { + wantsToPay = it } ) } @@ -981,7 +996,10 @@ fun ZapReaction( textContent = showErrorMessageDialog ?: "", onClickStartMessage = { baseNote.author?.let { - nav(routeToMessage(it, showErrorMessageDialog, accountViewModel)) + scope.launch(Dispatchers.IO) { + val route = routeToMessage(it, showErrorMessageDialog, accountViewModel) + nav(route) + } } }, onDismiss = { showErrorMessageDialog = null } @@ -995,6 +1013,12 @@ fun ZapReaction( ) } + if (wantsToPay.isNotEmpty()) { + PayViaIntentDialog(payingInvoices = wantsToPay, accountViewModel = accountViewModel) { + wantsToPay = persistentListOf() + } + } + if (wantsToSetCustomZap) { ZapCustomDialog( onClose = { wantsToSetCustomZap = false }, @@ -1009,6 +1033,9 @@ fun ZapReaction( zappingProgress = it } }, + onPayViaIntent = { + wantsToPay = it + }, accountViewModel = accountViewModel, baseNote = baseNote ) @@ -1045,7 +1072,8 @@ private fun zapClick( context: Context, onZappingProgress: (Float) -> Unit, onMultipleChoices: () -> Unit, - onError: (String) -> Unit + onError: (String) -> Unit, + onPayViaIntent: (ImmutableList) -> Unit ) { if (accountViewModel.account.zapAmountChoices.isEmpty()) { scope.launch { @@ -1080,7 +1108,8 @@ private fun zapClick( onZappingProgress(it) } }, - zapType = accountViewModel.account.defaultZapType + zapType = accountViewModel.account.defaultZapType, + onPayViaIntent = onPayViaIntent ) } else if (accountViewModel.account.zapAmountChoices.size > 1) { onMultipleChoices() @@ -1391,7 +1420,8 @@ fun ZapAmountChoicePopup( onDismiss: () -> Unit, onChangeAmount: () -> Unit, onError: (text: String) -> Unit, - onProgress: (percent: Float) -> Unit + onProgress: (percent: Float) -> Unit, + onPayViaIntent: (ImmutableList) -> Unit ) { val context = LocalContext.current @@ -1417,6 +1447,7 @@ fun ZapAmountChoicePopup( context, onError, onProgress, + onPayViaIntent, account.defaultZapType ) onDismiss() @@ -1441,7 +1472,8 @@ fun ZapAmountChoicePopup( context, onError, onProgress, - account.defaultZapType + onPayViaIntent, + account.defaultZapType, ) onDismiss() }, 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 828182dac..17b3d4ecc 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 @@ -1,5 +1,7 @@ package com.vitorpamplona.amethyst.ui.note +import android.content.Intent +import android.net.Uri import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* @@ -10,25 +12,41 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties +import androidx.core.content.ContextCompat +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.ViewModel import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.service.NostrHomeDataSource +import com.vitorpamplona.amethyst.service.ZapPaymentHandler import com.vitorpamplona.amethyst.ui.actions.CloseButton +import com.vitorpamplona.amethyst.ui.actions.PostButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner import com.vitorpamplona.amethyst.ui.theme.ButtonBorder +import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer +import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer +import com.vitorpamplona.amethyst.ui.theme.Size10dp +import com.vitorpamplona.amethyst.ui.theme.Size55dp +import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.events.LnZapEvent +import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList class ZapOptionstViewModel : ViewModel() { @@ -62,6 +80,7 @@ fun ZapCustomDialog( onClose: () -> Unit, onError: (text: String) -> Unit, onProgress: (percent: Float) -> Unit, + onPayViaIntent: (ImmutableList) -> Unit, accountViewModel: AccountViewModel, baseNote: Note ) { @@ -113,6 +132,7 @@ fun ZapCustomDialog( context, onError = onError, onProgress = onProgress, + onPayViaIntent = onPayViaIntent, zapType = selectedZapType ) onClose() @@ -266,3 +286,115 @@ fun ErrorMessageDialog( } ) } + +@Composable +fun PayViaIntentDialog( + payingInvoices: ImmutableList, + accountViewModel: AccountViewModel, + onClose: () -> Unit, +) { + val context = LocalContext.current + + Dialog( + onDismissRequest = onClose, + properties = DialogProperties( + dismissOnClickOutside = false, + usePlatformDefaultWidth = false + ) + ) { + Surface() { + Column(modifier = Modifier.padding(10.dp)) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + CloseButton(onPress = onClose) + } + + Spacer(modifier = DoubleVertSpacer) + + payingInvoices.forEachIndexed { index, it -> + val paid = remember { + mutableStateOf(false) + } + + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = Size10dp)) { + if (it.user != null) { + BaseUserPicture(it.user, Size55dp, accountViewModel = accountViewModel) + } else { + DisplayBlankAuthor(size = Size55dp) + } + + Spacer(modifier = DoubleHorzSpacer) + + Column(modifier = Modifier.weight(1f)) { + if (it.user != null) { + UsernameDisplay(it.user, showPlayButton = false) + } else { + Text( + text = stringResource(id = R.string.wallet_number, index+1), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + } + Row() { + Text( + text = showAmount((it.amountMilliSats/1000.0f).toBigDecimal()), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + Spacer(modifier = StdHorzSpacer) + Text( + text = stringResource(id = R.string.sats), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + } + + } + + Spacer(modifier = DoubleHorzSpacer) + + PayButton(isActive = !paid.value) { + paid.value = true + + val uri = "lightning:" + it.invoice + + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uri)) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + + ContextCompat.startActivity(context, intent, null) + } + } + } + } + } + } +} + +@Composable +fun PayButton(isActive: Boolean, modifier: Modifier = Modifier, onPost: () -> Unit = {}) { + Button( + modifier = modifier, + onClick = { + onPost() + }, + shape = ButtonBorder, + colors = ButtonDefaults + .buttonColors( + backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray + ), + contentPadding = PaddingValues(0.dp) + ) { + if (isActive) + Text(text = stringResource(R.string.pay), color = Color.White) + else + Text(text = stringResource(R.string.paid), color = Color.White) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index 9bb07880b..f3639cfa2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -27,6 +27,7 @@ import com.vitorpamplona.amethyst.service.Nip05Verifier import com.vitorpamplona.amethyst.service.Nip11CachedRetriever import com.vitorpamplona.amethyst.service.Nip11Retriever import com.vitorpamplona.amethyst.service.OnlineChecker +import com.vitorpamplona.amethyst.service.ZapPaymentHandler import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver import com.vitorpamplona.amethyst.ui.components.UrlPreviewState import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification @@ -43,6 +44,7 @@ import com.vitorpamplona.quartz.events.SealedGossipEvent import com.vitorpamplona.quartz.events.UserMetadata import com.vitorpamplona.quartz.events.ZapSplitSetup import com.vitorpamplona.quartz.utils.TimeUtils +import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.toImmutableSet @@ -215,111 +217,7 @@ class AccountViewModel(val account: Account) : ViewModel() { return null } - fun zap(note: Note, amount: Long, pollOption: Int?, message: String, context: Context, onError: (String) -> Unit, onProgress: (percent: Float) -> Unit, zapType: LnZapEvent.ZapType) { - viewModelScope.launch(Dispatchers.IO) { - innerZap(note, amount, pollOption, message, context, onError, onProgress, zapType) - } - } - - private suspend fun innerZap(note: Note, amountMilliSats: Long, pollOption: Int?, message: String, context: Context, onError: (String) -> Unit, onProgress: (percent: Float) -> Unit, zapType: LnZapEvent.ZapType) { - val zapSplitSetup = note.event?.zapSplitSetup() - - val zapsToSend = if (!zapSplitSetup.isNullOrEmpty()) { - zapSplitSetup - } else { - val lud16 = note.author?.info?.lud16?.trim() ?: note.author?.info?.lud06?.trim() - - if (lud16.isNullOrBlank()) { - onError(context.getString(R.string.user_does_not_have_a_lightning_address_setup_to_receive_sats)) - return - } - - listOf(ZapSplitSetup(lud16, null, weight = 1.0, true)) - } - - val totalWeight = zapsToSend.sumOf { it.weight } - - val invoicesToPayOnIntent = mutableListOf() - - zapsToSend.forEachIndexed { index, value -> - val outerProgressMin = index / zapsToSend.size.toFloat() - val outerProgressMax = (index + 1) / zapsToSend.size.toFloat() - - val zapValue = - round((amountMilliSats * value.weight / totalWeight) / 1000f).toLong() * 1000 - - if (value.isLnAddress) { - innerZap( - lud16 = value.lnAddressOrPubKeyHex, - note = note, - amount = zapValue, - pollOption = pollOption, - message = message, - context = context, - onError = onError, - onProgress = { - onProgress((it * (outerProgressMax - outerProgressMin)) + outerProgressMin) - }, - zapType = zapType, - onPayInvoiceThroughIntent = { - invoicesToPayOnIntent.add(it) - } - ) - } else { - val user = LocalCache.getUserIfExists(value.lnAddressOrPubKeyHex) - val lud16 = user?.info?.lnAddress() - - if (lud16 != null) { - innerZap( - lud16 = lud16, - note = note, - amount = zapValue, - pollOption = pollOption, - message = message, - context = context, - onError = onError, - onProgress = { - onProgress((it * (outerProgressMax - outerProgressMin)) + outerProgressMin) - }, - zapType = zapType, - overrideUser = user, - onPayInvoiceThroughIntent = { - invoicesToPayOnIntent.add(it) - } - ) - } else { - onError( - context.getString( - R.string.user_x_does_not_have_a_lightning_address_setup_to_receive_sats, - user?.toBestDisplayName() ?: value.lnAddressOrPubKeyHex - ) - ) - } - } - } - - if (invoicesToPayOnIntent.isNotEmpty()) { - payInvoices(bolt11s = invoicesToPayOnIntent, context = context) - } - - // Awaits for the event to come back to LocalCache. - viewModelScope.launch(Dispatchers.IO) { - delay(5000) - onProgress(1f) - } - } - - private suspend fun payInvoices(bolt11s: List, context: Context) { - val uri = "lightning:" + bolt11s.joinToString("&") - - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uri)) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - - ContextCompat.startActivity(context, intent, null) - } - - private suspend fun innerZap( - lud16: String, + fun zap( note: Note, amount: Long, pollOption: Int?, @@ -327,58 +225,12 @@ class AccountViewModel(val account: Account) : ViewModel() { context: Context, onError: (String) -> Unit, onProgress: (percent: Float) -> Unit, - onPayInvoiceThroughIntent: (String) -> Unit, - zapType: LnZapEvent.ZapType, - overrideUser: User? = null + onPayViaIntent: (ImmutableList) -> Unit, + zapType: LnZapEvent.ZapType ) { - var zapRequestJson = "" - - if (zapType != LnZapEvent.ZapType.NONZAP) { - val zapRequest = account.createZapRequestFor(note, pollOption, message, zapType, overrideUser) - if (zapRequest != null) { - zapRequestJson = zapRequest.toJson() - } + viewModelScope.launch(Dispatchers.IO) { + ZapPaymentHandler(account).zap(note, amount, pollOption, message, context, onError, onProgress, onPayViaIntent, zapType) } - - onProgress(0.10f) - - LightningAddressResolver().lnAddressInvoice( - lud16, - amount, - message, - zapRequestJson, - onSuccess = { - onProgress(0.7f) - if (account.hasWalletConnectSetup()) { - account.sendZapPaymentRequestFor( - bolt11 = it, - note, - onResponse = { response -> - if (response is PayInvoiceErrorResponse) { - onProgress(0.0f) - onError( - response.error?.message - ?: response.error?.code?.toString() - ?: "Error parsing error message" - ) - } else { - onProgress(1f) - } - } - ) - onProgress(0.8f) - } else { - try { - onPayInvoiceThroughIntent(it) - } catch (e: Exception) { - onError(context.getString(R.string.lightning_wallets_not_found2)) - } - onProgress(0f) - } - }, - onError = onError, - onProgress = onProgress - ) } fun report(note: Note, type: ReportEvent.ReportType, content: String = "") { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 620e0e43f..6ee43e242 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -582,4 +582,6 @@ Forwarding zaps to Lightning wallets not found + Paid + Wallet %1$s