Adding payment requests from nostr.wine
This commit is contained in:
@@ -150,6 +150,15 @@ class Account(
|
|||||||
|
|
||||||
var transientHiddenUsers: ImmutableSet<String> = persistentSetOf()
|
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.
|
// Observers line up here.
|
||||||
val live: AccountLiveData = AccountLiveData(this)
|
val live: AccountLiveData = AccountLiveData(this)
|
||||||
val liveLanguages: 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
|
var userProfileCache: User? = null
|
||||||
|
|
||||||
fun updateOptOutOptions(warnReports: Boolean, filterSpam: Boolean) {
|
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) {
|
override fun onAuth(relay: Relay, challenge: String) {
|
||||||
auth(relay, challenge)
|
auth(relay, challenge)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPaymentRequired(
|
||||||
|
relay: Relay,
|
||||||
|
lnInvoice: String?,
|
||||||
|
description: String?,
|
||||||
|
otherOptionsUrl: String?
|
||||||
|
) {
|
||||||
|
pay(relay, lnInvoice, description, otherOptionsUrl)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -190,4 +199,5 @@ abstract class NostrDataSource(val debugName: String) {
|
|||||||
|
|
||||||
abstract fun updateChannelFilters()
|
abstract fun updateChannelFilters()
|
||||||
open fun auth(relay: Relay, challenge: String) = Unit
|
open fun auth(relay: Relay, challenge: String) = Unit
|
||||||
|
open fun pay(relay: Relay, lnInvoice: String?, description: String?, otherOptionsUrl: String?) = Unit
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-6
@@ -115,14 +115,18 @@ object NostrSearchEventOrUserDataSource : NostrDataSource("SearchEventFeed") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun search(searchString: String) {
|
fun search(searchString: String) {
|
||||||
println("DataSource: ${this.javaClass.simpleName} Search for $searchString")
|
if (this.searchString != searchString) {
|
||||||
this.searchString = searchString
|
println("DataSource: ${this.javaClass.simpleName} Search for $searchString")
|
||||||
invalidateFilters()
|
this.searchString = searchString
|
||||||
|
invalidateFilters()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
println("DataSource: ${this.javaClass.simpleName} Clear")
|
if (searchString != null) {
|
||||||
searchString = null
|
println("DataSource: ${this.javaClass.simpleName} Clear")
|
||||||
invalidateFilters()
|
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) {
|
fun subscribe(listener: Listener) {
|
||||||
listeners = listeners.plus(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 onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay) = Unit
|
||||||
|
|
||||||
open fun onAuth(relay: Relay, challenge: String) = 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}")
|
// Log.w("Relay", "Relay$url, ${msg[1].asString}")
|
||||||
it.onAuth(this@Relay, msgArray[1].asText())
|
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 {
|
else -> listeners.forEach {
|
||||||
// Log.w("Relay", "Relay something else $url, $channel")
|
Log.w("Relay", "Unsupported message: $newMessage")
|
||||||
it.onError(
|
it.onError(
|
||||||
this@Relay,
|
this@Relay,
|
||||||
channel,
|
channel,
|
||||||
@@ -393,5 +397,10 @@ class Relay(
|
|||||||
* @param type is 0 for disconnect and 1 for connect
|
* @param type is 0 for disconnect and 1 for connect
|
||||||
*/
|
*/
|
||||||
fun onRelayStateChange(relay: Relay, type: StateType, channel: String?)
|
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 onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay)
|
||||||
|
|
||||||
fun onAuth(relay: Relay, challenge: String)
|
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) {
|
override fun onEvent(relay: Relay, subscriptionId: String, event: Event) {
|
||||||
@@ -138,6 +140,10 @@ object RelayPool : Relay.Listener {
|
|||||||
listeners.forEach { it.onAuth(relay, challenge) }
|
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() {
|
private fun updateStatus() {
|
||||||
val connected = connectedRelays()
|
val connected = connectedRelays()
|
||||||
val available = availableRelays()
|
val available = availableRelays()
|
||||||
|
|||||||
@@ -98,10 +98,6 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
var canAddInvoice by mutableStateOf(false)
|
var canAddInvoice by mutableStateOf(false)
|
||||||
var wantsInvoice by mutableStateOf(false)
|
var wantsInvoice by mutableStateOf(false)
|
||||||
|
|
||||||
data class ForwardZapSetup(val user: User) {
|
|
||||||
var percentage by mutableStateOf(100)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forward Zap to
|
// Forward Zap to
|
||||||
var wantsForwardZapTo by mutableStateOf(false)
|
var wantsForwardZapTo by mutableStateOf(false)
|
||||||
var forwardZapTo by mutableStateOf<Split<User>>(Split())
|
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.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -41,9 +42,12 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class InvoiceAmount(val invoice: String, val amount: String?)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MayBeInvoicePreview(lnbcWord: String) {
|
fun LoadValueFromInvoice(lnbcWord: String, inner: @Composable (invoiceAmount: InvoiceAmount?) -> Unit) {
|
||||||
var lnInvoice by remember { mutableStateOf<Pair<String, String?>?>(null) }
|
var lnInvoice by remember { mutableStateOf<InvoiceAmount?>(null) }
|
||||||
|
|
||||||
LaunchedEffect(key1 = lnbcWord) {
|
LaunchedEffect(key1 = lnbcWord) {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
@@ -56,19 +60,26 @@ fun MayBeInvoicePreview(lnbcWord: String) {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
lnInvoice = Pair(myInvoice, myInvoiceAmount)
|
lnInvoice = InvoiceAmount(myInvoice, myInvoiceAmount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Crossfade(targetState = lnInvoice) {
|
inner(lnInvoice)
|
||||||
if (it != null) {
|
}
|
||||||
InvoicePreview(it.first, it.second)
|
|
||||||
} else {
|
@Composable
|
||||||
Text(
|
fun MayBeInvoicePreview(lnbcWord: String) {
|
||||||
text = lnbcWord,
|
LoadValueFromInvoice(lnbcWord = lnbcWord) { invoiceAmount ->
|
||||||
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
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>) {
|
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.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
@@ -52,8 +53,10 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
import androidx.navigation.NavBackStackEntry
|
import androidx.navigation.NavBackStackEntry
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.BooleanType
|
import com.vitorpamplona.amethyst.model.BooleanType
|
||||||
import com.vitorpamplona.amethyst.ui.actions.InformationDialog
|
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.ChannelFabColumn
|
||||||
import com.vitorpamplona.amethyst.ui.buttons.NewCommunityNoteButton
|
import com.vitorpamplona.amethyst.ui.buttons.NewCommunityNoteButton
|
||||||
import com.vitorpamplona.amethyst.ui.buttons.NewImageButton
|
import com.vitorpamplona.amethyst.ui.buttons.NewImageButton
|
||||||
@@ -133,6 +136,7 @@ fun MainScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
DisplayErrorMessages(accountViewModel)
|
DisplayErrorMessages(accountViewModel)
|
||||||
|
DisplayPayMessages(accountViewModel)
|
||||||
|
|
||||||
val navPopBack = remember(navController) {
|
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
|
@Composable
|
||||||
fun WatchNavStateToUpdateBarVisibility(navState: State<NavBackStackEntry?>, onReset: () -> Unit) {
|
fun WatchNavStateToUpdateBarVisibility(navState: State<NavBackStackEntry?>, onReset: () -> Unit) {
|
||||||
LaunchedEffect(key1 = navState.value) {
|
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/).
|
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.
|
After installing, select the app you want to use in the Settings.
|
||||||
</string>
|
</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>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user