mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-04-23 23:10:58 +02:00
Adding payment requests from nostr.wine
This commit is contained in:
parent
7d4fbc3e04
commit
074a7d41fd
@ -150,6 +150,15 @@ class Account(
|
||||
|
||||
var transientHiddenUsers: ImmutableSet<String> = persistentSetOf()
|
||||
|
||||
data class PaymentRequest(
|
||||
val relayUrl: String,
|
||||
val lnInvoice: String?,
|
||||
val description: String?,
|
||||
val otherOptionsUrl: String?
|
||||
)
|
||||
var transientPaymentRequestDismissals: Set<PaymentRequest> = emptySet()
|
||||
val transientPaymentRequests: MutableStateFlow<Set<PaymentRequest>> = MutableStateFlow(emptySet())
|
||||
|
||||
// Observers line up here.
|
||||
val live: AccountLiveData = AccountLiveData(this)
|
||||
val liveLanguages: AccountLiveData = AccountLiveData(this)
|
||||
@ -383,6 +392,21 @@ class Account(
|
||||
}
|
||||
}
|
||||
|
||||
fun addPaymentRequestIfNew(paymentRequest: PaymentRequest) {
|
||||
if (!this.transientPaymentRequests.value.contains(paymentRequest) &&
|
||||
!this.transientPaymentRequestDismissals.contains(paymentRequest)
|
||||
) {
|
||||
this.transientPaymentRequests.value = transientPaymentRequests.value + paymentRequest
|
||||
}
|
||||
}
|
||||
|
||||
fun dismissPaymentRequest(request: PaymentRequest) {
|
||||
if (this.transientPaymentRequests.value.contains(request)) {
|
||||
this.transientPaymentRequests.value = transientPaymentRequests.value - request
|
||||
this.transientPaymentRequestDismissals = transientPaymentRequestDismissals + request
|
||||
}
|
||||
}
|
||||
|
||||
var userProfileCache: User? = null
|
||||
|
||||
fun updateOptOutOptions(warnReports: Boolean, filterSpam: Boolean) {
|
||||
|
@ -244,4 +244,12 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun pay(relay: Relay, lnInvoice: String?, description: String?, otherOptionsUrl: String?) {
|
||||
super.pay(relay, lnInvoice, description, otherOptionsUrl)
|
||||
|
||||
if (this::account.isInitialized) {
|
||||
account.addPaymentRequestIfNew(Account.PaymentRequest(relay.url, lnInvoice, description, otherOptionsUrl))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,6 +76,15 @@ abstract class NostrDataSource(val debugName: String) {
|
||||
override fun onAuth(relay: Relay, challenge: String) {
|
||||
auth(relay, challenge)
|
||||
}
|
||||
|
||||
override fun onPaymentRequired(
|
||||
relay: Relay,
|
||||
lnInvoice: String?,
|
||||
description: String?,
|
||||
otherOptionsUrl: String?
|
||||
) {
|
||||
pay(relay, lnInvoice, description, otherOptionsUrl)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
@ -190,4 +199,5 @@ abstract class NostrDataSource(val debugName: String) {
|
||||
|
||||
abstract fun updateChannelFilters()
|
||||
open fun auth(relay: Relay, challenge: String) = Unit
|
||||
open fun pay(relay: Relay, lnInvoice: String?, description: String?, otherOptionsUrl: String?) = Unit
|
||||
}
|
||||
|
@ -115,14 +115,18 @@ object NostrSearchEventOrUserDataSource : NostrDataSource("SearchEventFeed") {
|
||||
}
|
||||
|
||||
fun search(searchString: String) {
|
||||
println("DataSource: ${this.javaClass.simpleName} Search for $searchString")
|
||||
this.searchString = searchString
|
||||
invalidateFilters()
|
||||
if (this.searchString != searchString) {
|
||||
println("DataSource: ${this.javaClass.simpleName} Search for $searchString")
|
||||
this.searchString = searchString
|
||||
invalidateFilters()
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
println("DataSource: ${this.javaClass.simpleName} Clear")
|
||||
searchString = null
|
||||
invalidateFilters()
|
||||
if (searchString != null) {
|
||||
println("DataSource: ${this.javaClass.simpleName} Clear")
|
||||
searchString = null
|
||||
invalidateFilters()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -177,6 +177,14 @@ object Client : RelayPool.Listener {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPaymentRequired(relay: Relay, lnInvoice: String?, description: String?, otherOptionsUrl: String?) {
|
||||
// Releases the Web thread for the new payload.
|
||||
// May need to add a processing queue if processing new events become too costly.
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
listeners.forEach { it.onPaymentRequired(relay, lnInvoice, description, otherOptionsUrl) }
|
||||
}
|
||||
}
|
||||
|
||||
fun subscribe(listener: Listener) {
|
||||
listeners = listeners.plus(listener)
|
||||
}
|
||||
@ -215,5 +223,7 @@ object Client : RelayPool.Listener {
|
||||
open fun onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay) = Unit
|
||||
|
||||
open fun onAuth(relay: Relay, challenge: String) = Unit
|
||||
|
||||
open fun onPaymentRequired(relay: Relay, lnInvoice: String?, description: String?, otherOptionsUrl: String?) = Unit
|
||||
}
|
||||
}
|
||||
|
@ -244,8 +244,12 @@ class Relay(
|
||||
// Log.w("Relay", "Relay$url, ${msg[1].asString}")
|
||||
it.onAuth(this@Relay, msgArray[1].asText())
|
||||
}
|
||||
"PAY" -> listeners.forEach {
|
||||
// Log.w("Relay", "Relay$url, ${msg[1].asString}")
|
||||
it.onPaymentRequired(this@Relay, msgArray[1].asText(), msgArray[2].asText(), msgArray[3].asText())
|
||||
}
|
||||
else -> listeners.forEach {
|
||||
// Log.w("Relay", "Relay something else $url, $channel")
|
||||
Log.w("Relay", "Unsupported message: $newMessage")
|
||||
it.onError(
|
||||
this@Relay,
|
||||
channel,
|
||||
@ -393,5 +397,10 @@ class Relay(
|
||||
* @param type is 0 for disconnect and 1 for connect
|
||||
*/
|
||||
fun onRelayStateChange(relay: Relay, type: StateType, channel: String?)
|
||||
|
||||
/**
|
||||
* Relay sent an invoice
|
||||
*/
|
||||
fun onPaymentRequired(relay: Relay, lnInvoice: String?, description: String?, otherOptionsUrl: String?)
|
||||
}
|
||||
}
|
||||
|
@ -112,6 +112,8 @@ object RelayPool : Relay.Listener {
|
||||
fun onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay)
|
||||
|
||||
fun onAuth(relay: Relay, challenge: String)
|
||||
|
||||
fun onPaymentRequired(relay: Relay, lnInvoice: String?, description: String?, otherOptionsUrl: String?)
|
||||
}
|
||||
|
||||
override fun onEvent(relay: Relay, subscriptionId: String, event: Event) {
|
||||
@ -138,6 +140,10 @@ object RelayPool : Relay.Listener {
|
||||
listeners.forEach { it.onAuth(relay, challenge) }
|
||||
}
|
||||
|
||||
override fun onPaymentRequired(relay: Relay, lnInvoice: String?, description: String?, otherOptionsUrl: String?) {
|
||||
listeners.forEach { it.onPaymentRequired(relay, lnInvoice, description, otherOptionsUrl) }
|
||||
}
|
||||
|
||||
private fun updateStatus() {
|
||||
val connected = connectedRelays()
|
||||
val available = availableRelays()
|
||||
|
@ -98,10 +98,6 @@ open class NewPostViewModel() : ViewModel() {
|
||||
var canAddInvoice by mutableStateOf(false)
|
||||
var wantsInvoice by mutableStateOf(false)
|
||||
|
||||
data class ForwardZapSetup(val user: User) {
|
||||
var percentage by mutableStateOf(100)
|
||||
}
|
||||
|
||||
// Forward Zap to
|
||||
var wantsForwardZapTo by mutableStateOf(false)
|
||||
var forwardZapTo by mutableStateOf<Split<User>>(Split())
|
||||
|
@ -0,0 +1,107 @@
|
||||
package com.vitorpamplona.amethyst.ui.actions
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.OpenInNew
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonColors
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextDirection
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.components.InvoicePreview
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadValueFromInvoice
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size16dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
|
||||
@Composable
|
||||
fun PayRequestDialog(
|
||||
title: String,
|
||||
textContent: String,
|
||||
lnInvoice: String?,
|
||||
textContent2: String,
|
||||
otherOptions: String?,
|
||||
buttonColors: ButtonColors = ButtonDefaults.buttonColors(),
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val uri = LocalUriHandler.current
|
||||
|
||||
val uriOpener: @Composable (() -> Unit) = otherOptions?.let {
|
||||
{
|
||||
Button(
|
||||
onClick = {
|
||||
runCatching {
|
||||
uri.openUri(it)
|
||||
}
|
||||
},
|
||||
colors = buttonColors,
|
||||
contentPadding = PaddingValues(horizontal = Size16dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.OpenInNew,
|
||||
contentDescription = null
|
||||
)
|
||||
Spacer(StdHorzSpacer)
|
||||
Text(stringResource(R.string.other_options))
|
||||
}
|
||||
}
|
||||
}
|
||||
} ?: {
|
||||
Row() {}
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Text(title)
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
Text(textContent)
|
||||
Spacer(modifier = StdVertSpacer)
|
||||
if (lnInvoice != null) {
|
||||
LoadValueFromInvoice(lnbcWord = lnInvoice) { invoiceAmount ->
|
||||
Crossfade(targetState = invoiceAmount, label = "PayRequestDialog") {
|
||||
if (it != null) {
|
||||
InvoicePreview(it.invoice, it.amount)
|
||||
} else {
|
||||
Text(
|
||||
text = lnInvoice,
|
||||
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = StdVertSpacer)
|
||||
Text(textContent2)
|
||||
}
|
||||
},
|
||||
confirmButton = uriOpener,
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onDismiss()
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(R.string.dismiss))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
@ -16,6 +16,7 @@ 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.remember
|
||||
@ -41,9 +42,12 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.text.NumberFormat
|
||||
|
||||
@Stable
|
||||
data class InvoiceAmount(val invoice: String, val amount: String?)
|
||||
|
||||
@Composable
|
||||
fun MayBeInvoicePreview(lnbcWord: String) {
|
||||
var lnInvoice by remember { mutableStateOf<Pair<String, String?>?>(null) }
|
||||
fun LoadValueFromInvoice(lnbcWord: String, inner: @Composable (invoiceAmount: InvoiceAmount?) -> Unit) {
|
||||
var lnInvoice by remember { mutableStateOf<InvoiceAmount?>(null) }
|
||||
|
||||
LaunchedEffect(key1 = lnbcWord) {
|
||||
launch(Dispatchers.IO) {
|
||||
@ -56,19 +60,26 @@ fun MayBeInvoicePreview(lnbcWord: String) {
|
||||
null
|
||||
}
|
||||
|
||||
lnInvoice = Pair(myInvoice, myInvoiceAmount)
|
||||
lnInvoice = InvoiceAmount(myInvoice, myInvoiceAmount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Crossfade(targetState = lnInvoice) {
|
||||
if (it != null) {
|
||||
InvoicePreview(it.first, it.second)
|
||||
} else {
|
||||
Text(
|
||||
text = lnbcWord,
|
||||
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
||||
)
|
||||
inner(lnInvoice)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MayBeInvoicePreview(lnbcWord: String) {
|
||||
LoadValueFromInvoice(lnbcWord = lnbcWord) { invoiceAmount ->
|
||||
Crossfade(targetState = invoiceAmount, label = "MayBeInvoicePreview") {
|
||||
if (it != null) {
|
||||
InvoicePreview(it.invoice, it.amount)
|
||||
} else {
|
||||
Text(
|
||||
text = lnbcWord,
|
||||
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -952,6 +952,12 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun dismissPaymentRequest(request: Account.PaymentRequest) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
account.dismissPaymentRequest(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HasNotificationDot(bottomNavigationItems: ImmutableList<Route>) {
|
||||
|
@ -44,6 +44,7 @@ import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
@ -52,8 +53,10 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.BooleanType
|
||||
import com.vitorpamplona.amethyst.ui.actions.InformationDialog
|
||||
import com.vitorpamplona.amethyst.ui.actions.PayRequestDialog
|
||||
import com.vitorpamplona.amethyst.ui.buttons.ChannelFabColumn
|
||||
import com.vitorpamplona.amethyst.ui.buttons.NewCommunityNoteButton
|
||||
import com.vitorpamplona.amethyst.ui.buttons.NewImageButton
|
||||
@ -133,6 +136,7 @@ fun MainScreen(
|
||||
}
|
||||
|
||||
DisplayErrorMessages(accountViewModel)
|
||||
DisplayPayMessages(accountViewModel)
|
||||
|
||||
val navPopBack = remember(navController) {
|
||||
{
|
||||
@ -416,6 +420,25 @@ private fun DisplayErrorMessages(accountViewModel: AccountViewModel) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DisplayPayMessages(accountViewModel: AccountViewModel) {
|
||||
val openDialogMsg = accountViewModel.account.transientPaymentRequests.collectAsStateWithLifecycle(null)
|
||||
|
||||
openDialogMsg.value?.firstOrNull()?.let { request ->
|
||||
PayRequestDialog(
|
||||
stringResource(id = R.string.payment_required_title, request.relayUrl.removePrefix("wss://").removeSuffix("/")),
|
||||
request.description?.let {
|
||||
stringResource(id = R.string.payment_required_explain, it)
|
||||
} ?: stringResource(id = R.string.payment_required_explain_null_description),
|
||||
request.lnInvoice,
|
||||
stringResource(id = R.string.payment_required_explain2),
|
||||
request.otherOptionsUrl
|
||||
) {
|
||||
accountViewModel.dismissPaymentRequest(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WatchNavStateToUpdateBarVisibility(navState: State<NavBackStackEntry?>, onReset: () -> Unit) {
|
||||
LaunchedEffect(key1 = navState.value) {
|
||||
|
@ -661,4 +661,11 @@
|
||||
To receive push notifications, install any app that supports [Unified Push](https://unifiedpush.org/), such as [Nfty](https://ntfy.sh/).
|
||||
After installing, select the app you want to use in the Settings.
|
||||
</string>
|
||||
|
||||
<string name="payment_required_title">Payment Required for %1$s</string>
|
||||
<string name="payment_required_explain">Relay has requested a payment of the invoice below for the %1$s.</string>
|
||||
<string name="payment_required_explain_null_description">Relay has requested a payment of the invoice below</string>
|
||||
<string name="payment_required_explain2">If you do not intent to use this relay anymore, please remove it from your relay list</string>
|
||||
<string name="dismiss">Dismiss</string>
|
||||
<string name="other_options">See other options</string>
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user