mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-03-28 18:51:45 +01:00
Refactoring Zap Error message screen to allow sending messages directly to each receiver with an error
This commit is contained in:
parent
497ae937fd
commit
160d4722c0
@ -72,8 +72,8 @@ suspend inline fun <T> tryAndWait(
|
|||||||
suspend fun <T, K> collectSuccessfulOperations(
|
suspend fun <T, K> collectSuccessfulOperations(
|
||||||
items: List<T>,
|
items: List<T>,
|
||||||
runRequestFor: (T, (K) -> Unit) -> Unit,
|
runRequestFor: (T, (K) -> Unit) -> Unit,
|
||||||
output: MutableMap<T, K> = mutableMapOf(),
|
output: MutableList<K> = mutableListOf(),
|
||||||
onReady: suspend (MutableMap<T, K>) -> Unit,
|
onReady: suspend (List<K>) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (items.isEmpty()) {
|
if (items.isEmpty()) {
|
||||||
onReady(output)
|
onReady(output)
|
||||||
@ -87,7 +87,7 @@ suspend fun <T, K> collectSuccessfulOperations(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
output[it] = result
|
output.add(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ import com.vitorpamplona.amethyst.model.Account
|
|||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource.user
|
||||||
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
|
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.quartz.events.AdvertisedRelayListEvent
|
import com.vitorpamplona.quartz.events.AdvertisedRelayListEvent
|
||||||
@ -61,7 +62,7 @@ class ZapPaymentHandler(
|
|||||||
context: Context,
|
context: Context,
|
||||||
showErrorIfNoLnAddress: Boolean,
|
showErrorIfNoLnAddress: Boolean,
|
||||||
forceProxy: (String) -> Boolean,
|
forceProxy: (String) -> Boolean,
|
||||||
onError: (String, String) -> Unit,
|
onError: (String, String, User?) -> Unit,
|
||||||
onProgress: (percent: Float) -> Unit,
|
onProgress: (percent: Float) -> Unit,
|
||||||
onPayViaIntent: (ImmutableList<Payable>) -> Unit,
|
onPayViaIntent: (ImmutableList<Payable>) -> Unit,
|
||||||
zapType: LnZapEvent.ZapType,
|
zapType: LnZapEvent.ZapType,
|
||||||
@ -89,6 +90,7 @@ class ZapPaymentHandler(
|
|||||||
context,
|
context,
|
||||||
R.string.user_does_not_have_a_lightning_address_setup_to_receive_sats,
|
R.string.user_does_not_have_a_lightning_address_setup_to_receive_sats,
|
||||||
),
|
),
|
||||||
|
note.author,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return@withContext
|
return@withContext
|
||||||
@ -107,6 +109,7 @@ class ZapPaymentHandler(
|
|||||||
context,
|
context,
|
||||||
R.string.user_does_not_have_a_lightning_address_setup_to_receive_sats,
|
R.string.user_does_not_have_a_lightning_address_setup_to_receive_sats,
|
||||||
),
|
),
|
||||||
|
note.author,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return@withContext
|
return@withContext
|
||||||
@ -124,10 +127,10 @@ class ZapPaymentHandler(
|
|||||||
onProgress(0.05f)
|
onProgress(0.05f)
|
||||||
}
|
}
|
||||||
|
|
||||||
assembleAllInvoices(splitZapRequestPairs.toList(), amountMilliSats, message, showErrorIfNoLnAddress, forceProxy, onError, onProgress = {
|
assembleAllInvoices(splitZapRequestPairs, amountMilliSats, message, showErrorIfNoLnAddress, forceProxy, onError, onProgress = {
|
||||||
onProgress(it * 0.7f + 0.05f) // keeps within range.
|
onProgress(it * 0.7f + 0.05f) // keeps within range.
|
||||||
}, context) {
|
}, context) { payables ->
|
||||||
if (it.isEmpty()) {
|
if (payables.isEmpty()) {
|
||||||
onProgress(0.00f)
|
onProgress(0.00f)
|
||||||
return@assembleAllInvoices
|
return@assembleAllInvoices
|
||||||
} else {
|
} else {
|
||||||
@ -135,22 +138,14 @@ class ZapPaymentHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (account.hasWalletConnectSetup()) {
|
if (account.hasWalletConnectSetup()) {
|
||||||
payViaNWC(it.values.map { it.invoice }, note, onError, onProgress = {
|
payViaNWC(payables, note, onError = onError, onProgress = {
|
||||||
onProgress(it * 0.25f + 0.75f) // keeps within range.
|
onProgress(it * 0.25f + 0.75f) // keeps within range.
|
||||||
}, context) {
|
}, context) {
|
||||||
// onProgress(1f)
|
// onProgress(1f)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onPayViaIntent(
|
onPayViaIntent(
|
||||||
it
|
payables.toImmutableList(),
|
||||||
.map {
|
|
||||||
Payable(
|
|
||||||
info = it.key.first,
|
|
||||||
user = it.key.second.user,
|
|
||||||
amountMilliSats = it.value.zapValue,
|
|
||||||
invoice = it.value.invoice,
|
|
||||||
)
|
|
||||||
}.toImmutableList(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
onProgress(0f)
|
onProgress(0f)
|
||||||
@ -169,7 +164,8 @@ class ZapPaymentHandler(
|
|||||||
return roundedZapValue
|
return roundedZapValue
|
||||||
}
|
}
|
||||||
|
|
||||||
class SignAllZapRequestsReturn(
|
class ZapRequestReady(
|
||||||
|
val inputSetup: ZapSplitSetup,
|
||||||
val zapRequestJson: String?,
|
val zapRequestJson: String?,
|
||||||
val user: User? = null,
|
val user: User? = null,
|
||||||
)
|
)
|
||||||
@ -180,7 +176,7 @@ class ZapPaymentHandler(
|
|||||||
message: String,
|
message: String,
|
||||||
zapType: LnZapEvent.ZapType,
|
zapType: LnZapEvent.ZapType,
|
||||||
zapsToSend: List<ZapSplitSetup>,
|
zapsToSend: List<ZapSplitSetup>,
|
||||||
onAllDone: suspend (MutableMap<ZapSplitSetup, SignAllZapRequestsReturn>) -> Unit,
|
onAllDone: suspend (List<ZapRequestReady>) -> Unit,
|
||||||
) {
|
) {
|
||||||
val authorRelayList =
|
val authorRelayList =
|
||||||
note.author
|
note.author
|
||||||
@ -194,13 +190,13 @@ class ZapPaymentHandler(
|
|||||||
)?.readRelays()
|
)?.readRelays()
|
||||||
}?.toSet()
|
}?.toSet()
|
||||||
|
|
||||||
collectSuccessfulOperations<ZapSplitSetup, SignAllZapRequestsReturn>(
|
collectSuccessfulOperations<ZapSplitSetup, ZapRequestReady>(
|
||||||
items = zapsToSend,
|
items = zapsToSend,
|
||||||
runRequestFor = { next: ZapSplitSetup, onReady ->
|
runRequestFor = { next: ZapSplitSetup, onReady ->
|
||||||
if (next.isLnAddress) {
|
if (next.isLnAddress) {
|
||||||
prepareZapRequestIfNeeded(note, pollOption, message, zapType) { zapRequestJson ->
|
prepareZapRequestIfNeeded(note, pollOption, message, zapType) { zapRequestJson ->
|
||||||
if (zapRequestJson != null) {
|
if (zapRequestJson != null) {
|
||||||
onReady(SignAllZapRequestsReturn(zapRequestJson))
|
onReady(ZapRequestReady(next, zapRequestJson))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -216,7 +212,7 @@ class ZapPaymentHandler(
|
|||||||
) + (authorRelayList ?: emptySet())
|
) + (authorRelayList ?: emptySet())
|
||||||
|
|
||||||
prepareZapRequestIfNeeded(note, pollOption, message, zapType, user, userRelayList) { zapRequestJson ->
|
prepareZapRequestIfNeeded(note, pollOption, message, zapType, user, userRelayList) { zapRequestJson ->
|
||||||
onReady(SignAllZapRequestsReturn(zapRequestJson, user))
|
onReady(ZapRequestReady(next, zapRequestJson, user))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -225,32 +221,33 @@ class ZapPaymentHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun assembleAllInvoices(
|
suspend fun assembleAllInvoices(
|
||||||
invoices: List<Pair<ZapSplitSetup, SignAllZapRequestsReturn>>,
|
requests: List<ZapRequestReady>,
|
||||||
totalAmountMilliSats: Long,
|
totalAmountMilliSats: Long,
|
||||||
message: String,
|
message: String,
|
||||||
showErrorIfNoLnAddress: Boolean,
|
showErrorIfNoLnAddress: Boolean,
|
||||||
forceProxy: (String) -> Boolean,
|
forceProxy: (String) -> Boolean,
|
||||||
onError: (String, String) -> Unit,
|
onError: (String, String, User?) -> Unit,
|
||||||
onProgress: (percent: Float) -> Unit,
|
onProgress: (percent: Float) -> Unit,
|
||||||
context: Context,
|
context: Context,
|
||||||
onAllDone: suspend (MutableMap<Pair<ZapSplitSetup, SignAllZapRequestsReturn>, AssembleInvoiceReturn>) -> Unit,
|
onAllDone: suspend (List<Payable>) -> Unit,
|
||||||
) {
|
) {
|
||||||
var progressAllPayments = 0.00f
|
var progressAllPayments = 0.00f
|
||||||
val totalWeight = invoices.sumOf { it.first.weight }
|
val totalWeight = requests.sumOf { it.inputSetup.weight }
|
||||||
|
|
||||||
collectSuccessfulOperations<Pair<ZapSplitSetup, SignAllZapRequestsReturn>, AssembleInvoiceReturn>(
|
collectSuccessfulOperations<ZapRequestReady, Payable>(
|
||||||
items = invoices,
|
items = requests,
|
||||||
runRequestFor = { splitZapRequestPair: Pair<ZapSplitSetup, SignAllZapRequestsReturn>, onReady ->
|
runRequestFor = { splitZapRequestPair: ZapRequestReady, onReady ->
|
||||||
assembleInvoice(
|
assembleInvoice(
|
||||||
splitSetup = splitZapRequestPair.first,
|
splitSetup = splitZapRequestPair.inputSetup,
|
||||||
nostrZapRequest = splitZapRequestPair.second.zapRequestJson,
|
nostrZapRequest = splitZapRequestPair.zapRequestJson,
|
||||||
zapValue = calculateZapValue(totalAmountMilliSats, splitZapRequestPair.first.weight, totalWeight),
|
toUser = splitZapRequestPair.user,
|
||||||
|
zapValue = calculateZapValue(totalAmountMilliSats, splitZapRequestPair.inputSetup.weight, totalWeight),
|
||||||
message = message,
|
message = message,
|
||||||
showErrorIfNoLnAddress = showErrorIfNoLnAddress,
|
showErrorIfNoLnAddress = showErrorIfNoLnAddress,
|
||||||
forceProxy = forceProxy,
|
forceProxy = forceProxy,
|
||||||
onError = onError,
|
onError = onError,
|
||||||
onProgressStep = { percentStepForThisPayment ->
|
onProgressStep = { percentStepForThisPayment ->
|
||||||
progressAllPayments += percentStepForThisPayment / invoices.size
|
progressAllPayments += percentStepForThisPayment / requests.size
|
||||||
onProgress(progressAllPayments)
|
onProgress(progressAllPayments)
|
||||||
},
|
},
|
||||||
context = context,
|
context = context,
|
||||||
@ -261,30 +258,35 @@ class ZapPaymentHandler(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Paid(
|
||||||
|
payable: Payable,
|
||||||
|
success: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
suspend fun payViaNWC(
|
suspend fun payViaNWC(
|
||||||
invoices: List<String>,
|
payables: List<Payable>,
|
||||||
note: Note,
|
note: Note,
|
||||||
onError: (String, String) -> Unit,
|
onError: (String, String, User?) -> Unit,
|
||||||
onProgress: (percent: Float) -> Unit,
|
onProgress: (percent: Float) -> Unit,
|
||||||
context: Context,
|
context: Context,
|
||||||
onAllDone: suspend (MutableMap<String, Boolean>) -> Unit,
|
onAllDone: suspend (List<Paid>) -> Unit,
|
||||||
) {
|
) {
|
||||||
var progressAllPayments = 0.00f
|
var progressAllPayments = 0.00f
|
||||||
|
|
||||||
collectSuccessfulOperations<String, Boolean>(
|
collectSuccessfulOperations<Payable, Paid>(
|
||||||
items = invoices,
|
items = payables,
|
||||||
runRequestFor = { invoice: String, onReady ->
|
runRequestFor = { payable: Payable, onReady ->
|
||||||
account.sendZapPaymentRequestFor(
|
account.sendZapPaymentRequestFor(
|
||||||
bolt11 = invoice,
|
bolt11 = payable.invoice,
|
||||||
zappedNote = note,
|
zappedNote = note,
|
||||||
onSent = {
|
onSent = {
|
||||||
progressAllPayments += 0.5f / invoices.size
|
progressAllPayments += 0.5f / payables.size
|
||||||
onProgress(progressAllPayments)
|
onProgress(progressAllPayments)
|
||||||
onReady(true)
|
onReady(Paid(payable, true))
|
||||||
},
|
},
|
||||||
onResponse = { response ->
|
onResponse = { response ->
|
||||||
if (response is PayInvoiceErrorResponse) {
|
if (response is PayInvoiceErrorResponse) {
|
||||||
progressAllPayments += 0.5f / invoices.size
|
progressAllPayments += 0.5f / payables.size
|
||||||
onProgress(progressAllPayments)
|
onProgress(progressAllPayments)
|
||||||
onError(
|
onError(
|
||||||
stringRes(context, R.string.error_dialog_pay_invoice_error),
|
stringRes(context, R.string.error_dialog_pay_invoice_error),
|
||||||
@ -294,9 +296,10 @@ class ZapPaymentHandler(
|
|||||||
response.error?.message
|
response.error?.message
|
||||||
?: response.error?.code?.toString() ?: "Error parsing error message",
|
?: response.error?.code?.toString() ?: "Error parsing error message",
|
||||||
),
|
),
|
||||||
|
payable.user,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
progressAllPayments += 0.5f / invoices.size
|
progressAllPayments += 0.5f / payables.size
|
||||||
onProgress(progressAllPayments)
|
onProgress(progressAllPayments)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -306,32 +309,26 @@ class ZapPaymentHandler(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class AssembleInvoiceReturn(
|
|
||||||
val zapValue: Long,
|
|
||||||
val invoice: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun assembleInvoice(
|
private fun assembleInvoice(
|
||||||
splitSetup: ZapSplitSetup,
|
splitSetup: ZapSplitSetup,
|
||||||
nostrZapRequest: String?,
|
nostrZapRequest: String?,
|
||||||
|
toUser: User?,
|
||||||
zapValue: Long,
|
zapValue: Long,
|
||||||
message: String,
|
message: String,
|
||||||
showErrorIfNoLnAddress: Boolean = true,
|
showErrorIfNoLnAddress: Boolean = true,
|
||||||
forceProxy: (String) -> Boolean,
|
forceProxy: (String) -> Boolean,
|
||||||
onError: (String, String) -> Unit,
|
onError: (String, String, User?) -> Unit,
|
||||||
onProgressStep: (percent: Float) -> Unit,
|
onProgressStep: (percent: Float) -> Unit,
|
||||||
context: Context,
|
context: Context,
|
||||||
onReady: (AssembleInvoiceReturn) -> Unit,
|
onReady: (Payable) -> Unit,
|
||||||
) {
|
) {
|
||||||
var progressThisPayment = 0.00f
|
var progressThisPayment = 0.00f
|
||||||
|
|
||||||
var user: User? = null
|
|
||||||
val lud16 =
|
val lud16 =
|
||||||
if (splitSetup.isLnAddress) {
|
if (splitSetup.isLnAddress) {
|
||||||
splitSetup.lnAddressOrPubKeyHex
|
splitSetup.lnAddressOrPubKeyHex
|
||||||
} else {
|
} else {
|
||||||
user = LocalCache.getUserIfExists(splitSetup.lnAddressOrPubKeyHex)
|
toUser?.info?.lnAddress()
|
||||||
user?.info?.lnAddress()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lud16 != null) {
|
if (lud16 != null) {
|
||||||
@ -342,7 +339,9 @@ class ZapPaymentHandler(
|
|||||||
message = message,
|
message = message,
|
||||||
nostrRequest = nostrZapRequest,
|
nostrRequest = nostrZapRequest,
|
||||||
forceProxy = forceProxy,
|
forceProxy = forceProxy,
|
||||||
onError = onError,
|
onError = { title, msg ->
|
||||||
|
onError(title, msg, toUser)
|
||||||
|
},
|
||||||
onProgress = {
|
onProgress = {
|
||||||
val step = it - progressThisPayment
|
val step = it - progressThisPayment
|
||||||
progressThisPayment = it
|
progressThisPayment = it
|
||||||
@ -351,7 +350,14 @@ class ZapPaymentHandler(
|
|||||||
context = context,
|
context = context,
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
onProgressStep(1 - progressThisPayment)
|
onProgressStep(1 - progressThisPayment)
|
||||||
onReady(AssembleInvoiceReturn(zapValue, it))
|
onReady(
|
||||||
|
Payable(
|
||||||
|
info = splitSetup,
|
||||||
|
user = toUser,
|
||||||
|
amountMilliSats = zapValue,
|
||||||
|
invoice = it,
|
||||||
|
),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -366,6 +372,7 @@ class ZapPaymentHandler(
|
|||||||
R.string.user_x_does_not_have_a_lightning_address_setup_to_receive_sats,
|
R.string.user_x_does_not_have_a_lightning_address_setup_to_receive_sats,
|
||||||
user?.toBestDisplayName() ?: splitSetup.lnAddressOrPubKeyHex,
|
user?.toBestDisplayName() ?: splitSetup.lnAddressOrPubKeyHex,
|
||||||
),
|
),
|
||||||
|
null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,12 +24,14 @@ import android.content.Context
|
|||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import com.vitorpamplona.amethyst.BuildConfig
|
import com.vitorpamplona.amethyst.BuildConfig
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.service.HttpStatusMessages
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
|
import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.quartz.encoders.LnInvoiceUtil
|
import com.vitorpamplona.quartz.encoders.LnInvoiceUtil
|
||||||
import com.vitorpamplona.quartz.encoders.Lud06
|
import com.vitorpamplona.quartz.encoders.Lud06
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.math.RoundingMode
|
import java.math.RoundingMode
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
@ -95,7 +97,7 @@ class LightningAddressResolver {
|
|||||||
.the_receiver_s_lightning_service_at_is_not_available_it_was_calculated_from_the_lightning_address_error_check_if_the_server_is_up_and_if_the_lightning_address_is_correct,
|
.the_receiver_s_lightning_service_at_is_not_available_it_was_calculated_from_the_lightning_address_error_check_if_the_server_is_up_and_if_the_lightning_address_is_correct,
|
||||||
url,
|
url,
|
||||||
lnaddress,
|
lnaddress,
|
||||||
it.code.toString(),
|
errorMessage(it, context),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -154,12 +156,36 @@ class LightningAddressResolver {
|
|||||||
} else {
|
} else {
|
||||||
onError(
|
onError(
|
||||||
stringRes(context, R.string.error_unable_to_fetch_invoice),
|
stringRes(context, R.string.error_unable_to_fetch_invoice),
|
||||||
stringRes(context, R.string.could_not_fetch_invoice_from, urlBinder),
|
stringRes(context, R.string.could_not_fetch_invoice_from_details, lnCallback, errorMessage(it, context)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun errorMessage(
|
||||||
|
response: Response,
|
||||||
|
context: Context,
|
||||||
|
): String {
|
||||||
|
val errorMessage =
|
||||||
|
runCatching {
|
||||||
|
jacksonObjectMapper().readTree(response.body.string())
|
||||||
|
}.getOrNull()?.let { tree ->
|
||||||
|
val status = tree.get("status")?.asText()
|
||||||
|
val message = tree.get("message")?.asText()
|
||||||
|
|
||||||
|
if (status == "error" && message != null) {
|
||||||
|
message
|
||||||
|
} else {
|
||||||
|
tree.get("error")?.get("message")?.asText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorMessage
|
||||||
|
?: HttpStatusMessages.resourceIdFor(response.code)?.let { stringRes(context, it) }
|
||||||
|
?: response.message.ifBlank { null }
|
||||||
|
?: response.code.toString()
|
||||||
|
}
|
||||||
|
|
||||||
fun lnAddressInvoice(
|
fun lnAddressInvoice(
|
||||||
lnaddress: String,
|
lnaddress: String,
|
||||||
milliSats: Long,
|
milliSats: Long,
|
||||||
@ -195,7 +221,7 @@ class LightningAddressResolver {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
val callback = lnurlp?.get("callback")?.asText()
|
val callback = lnurlp?.get("callback")?.asText()?.ifBlank { null }
|
||||||
|
|
||||||
if (callback == null) {
|
if (callback == null) {
|
||||||
onError(
|
onError(
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
* 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.ui.note
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Done
|
||||||
|
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.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size16dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Preview
|
||||||
|
fun ErrorMessageContentPreview() {
|
||||||
|
ThemeComparisonColumn {
|
||||||
|
ErrorMessageDialog(
|
||||||
|
title = "Title",
|
||||||
|
textContent = "This is an Error Message",
|
||||||
|
onClickStartMessage = { },
|
||||||
|
onDismiss = { },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ErrorMessageDialog(
|
||||||
|
title: String,
|
||||||
|
textContent: String,
|
||||||
|
buttonColors: ButtonColors = ButtonDefaults.buttonColors(),
|
||||||
|
onClickStartMessage: (() -> Unit)? = null,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = { Text(title) },
|
||||||
|
text = { SelectionContainer { Text(textContent) } },
|
||||||
|
confirmButton = {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp).fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
) {
|
||||||
|
onClickStartMessage?.let {
|
||||||
|
TextButton(onClick = onClickStartMessage) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_dm),
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Spacer(StdHorzSpacer)
|
||||||
|
Text(stringRes(R.string.error_dialog_talk_to_user))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
onClick = onDismiss,
|
||||||
|
colors = buttonColors,
|
||||||
|
contentPadding = PaddingValues(horizontal = Size16dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Done,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Spacer(StdHorzSpacer)
|
||||||
|
Text(stringRes(R.string.error_dialog_button_ok))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,241 @@
|
|||||||
|
/**
|
||||||
|
* 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.ui.note
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
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.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Done
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource.user
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size16dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size30Modifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size30dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size40dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Preview
|
||||||
|
fun MultiUserErrorMessageContentPreview() {
|
||||||
|
val accountViewModel = mockAccountViewModel()
|
||||||
|
val nav = EmptyNav
|
||||||
|
|
||||||
|
var user1: User? = null
|
||||||
|
var user2: User? = null
|
||||||
|
var user3: User? = null
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
user1 = LocalCache.getOrCreateUser("aabbccaabbccaabbcc")
|
||||||
|
user2 = LocalCache.getOrCreateUser("bbbccabbbccabbbcca")
|
||||||
|
user3 = LocalCache.getOrCreateUser("ccaadaccaadaccaada")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val model: UserBasedErrorMessageViewModel = viewModel()
|
||||||
|
model.add("Could not fetch invoice from https://minibits.cash/.well-known/lnurlp/victorieeman: There are too many unpaid invoices for this name.", user1)
|
||||||
|
model.add("Could not fetch invoice from https://minibits.cash/.well-known/lnurlp/victorieeman: There are too many unpaid invoices for this name.", user2)
|
||||||
|
model.add("Could not fetch invoice from https://minibits.cash/.well-known/lnurlp/victorieeman: There are too many unpaid invoices for this name.", user3)
|
||||||
|
|
||||||
|
ThemeComparisonColumn {
|
||||||
|
MultiUserErrorMessageDialogInner(
|
||||||
|
title = "Couldn't not zap",
|
||||||
|
model = model,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
class UserBasedErrorMessageViewModel : ViewModel() {
|
||||||
|
val errors = MutableStateFlow<List<UserBasedErrorMessage>>(emptyList())
|
||||||
|
val hasErrors = errors.map { it.isNotEmpty() }
|
||||||
|
|
||||||
|
fun add(
|
||||||
|
message: String,
|
||||||
|
user: User?,
|
||||||
|
) {
|
||||||
|
add(UserBasedErrorMessage(message, user))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun add(newError: UserBasedErrorMessage) {
|
||||||
|
errors.update {
|
||||||
|
it + newError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearErrors() {
|
||||||
|
errors.update {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserBasedErrorMessage(
|
||||||
|
val error: String,
|
||||||
|
val user: User?,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MultiUserErrorMessageDialog(
|
||||||
|
title: String,
|
||||||
|
model: UserBasedErrorMessageViewModel,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
val hasErrors by model.hasErrors.collectAsStateWithLifecycle(false)
|
||||||
|
if (hasErrors) {
|
||||||
|
MultiUserErrorMessageDialogInner(title, model, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MultiUserErrorMessageDialogInner(
|
||||||
|
title: String,
|
||||||
|
model: UserBasedErrorMessageViewModel,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = model::clearErrors,
|
||||||
|
title = { Text(title) },
|
||||||
|
text = {
|
||||||
|
val errorState by model.errors.collectAsStateWithLifecycle(emptyList())
|
||||||
|
LazyColumn {
|
||||||
|
itemsIndexed(errorState) { index, it ->
|
||||||
|
ErrorRow(it, accountViewModel, nav)
|
||||||
|
if (index < errorState.size - 1) {
|
||||||
|
HorizontalDivider(thickness = DividerThickness)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Button(
|
||||||
|
onClick = model::clearErrors,
|
||||||
|
contentPadding = PaddingValues(horizontal = Size16dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Done,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Spacer(StdHorzSpacer)
|
||||||
|
Text(stringRes(R.string.error_dialog_button_ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ErrorRow(
|
||||||
|
errorState: UserBasedErrorMessage,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
errorState.user?.let {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
Column(Modifier.width(Size40dp), horizontalAlignment = Alignment.Start) {
|
||||||
|
// Box(Size30Modifier.background(Color.Red))
|
||||||
|
UserPicture(errorState.user, Size30dp, Modifier, accountViewModel, nav)
|
||||||
|
Spacer(StdVertSpacer)
|
||||||
|
IconButton(
|
||||||
|
modifier = Size30Modifier,
|
||||||
|
onClick = {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
nav.nav(routeToMessage(it, errorState.error, accountViewModel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
val descriptor =
|
||||||
|
it.info?.bestName()?.let {
|
||||||
|
stringRes(R.string.error_dialog_talk_to_user_name, it)
|
||||||
|
} ?: stringRes(R.string.error_dialog_talk_to_user)
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_dm),
|
||||||
|
contentDescription = descriptor,
|
||||||
|
modifier = Size20Modifier,
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(Modifier.padding(top = Size5dp).weight(1f)) {
|
||||||
|
SelectionContainer {
|
||||||
|
Text(errorState.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -73,6 +73,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
||||||
@ -559,7 +560,7 @@ fun ZapVote(
|
|||||||
poolOption.option,
|
poolOption.option,
|
||||||
"",
|
"",
|
||||||
context,
|
context,
|
||||||
onError = { title, message ->
|
onError = { title, message, user ->
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
showErrorMessageDialog = StringToastMsg(title, message)
|
showErrorMessageDialog = StringToastMsg(title, message)
|
||||||
},
|
},
|
||||||
@ -583,7 +584,7 @@ fun ZapVote(
|
|||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
},
|
},
|
||||||
onChangeAmount = { wantsToZap = false },
|
onChangeAmount = { wantsToZap = false },
|
||||||
onError = { title, message ->
|
onError = { title, message, user ->
|
||||||
showErrorMessageDialog = StringToastMsg(title, message)
|
showErrorMessageDialog = StringToastMsg(title, message)
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
},
|
},
|
||||||
@ -604,7 +605,7 @@ fun ZapVote(
|
|||||||
showErrorMessageDialog =
|
showErrorMessageDialog =
|
||||||
StringToastMsg(
|
StringToastMsg(
|
||||||
stringRes(context, R.string.error_dialog_zap_error),
|
stringRes(context, R.string.error_dialog_zap_error),
|
||||||
it,
|
it.error,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -613,7 +614,7 @@ fun ZapVote(
|
|||||||
showErrorMessageDialog =
|
showErrorMessageDialog =
|
||||||
StringToastMsg(
|
StringToastMsg(
|
||||||
stringRes(context, R.string.error_dialog_zap_error),
|
stringRes(context, R.string.error_dialog_zap_error),
|
||||||
it,
|
it.error,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -681,7 +682,7 @@ fun FilteredZapAmountChoicePopup(
|
|||||||
pollOption: Int,
|
pollOption: Int,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onChangeAmount: () -> Unit,
|
onChangeAmount: () -> Unit,
|
||||||
onError: (title: String, text: String) -> Unit,
|
onError: (title: String, text: String, toUser: User?) -> Unit,
|
||||||
onProgress: (percent: Float) -> Unit,
|
onProgress: (percent: Float) -> Unit,
|
||||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -98,9 +98,12 @@ import androidx.lifecycle.MediatorLiveData
|
|||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.distinctUntilChanged
|
import androidx.lifecycle.distinctUntilChanged
|
||||||
import androidx.lifecycle.map
|
import androidx.lifecycle.map
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource.user
|
||||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||||
import com.vitorpamplona.amethyst.ui.components.ClickableBox
|
import com.vitorpamplona.amethyst.ui.components.ClickableBox
|
||||||
@ -108,7 +111,6 @@ import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
|||||||
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
|
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.buildNewPostRoute
|
import com.vitorpamplona.amethyst.ui.navigation.buildNewPostRoute
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.EditState
|
import com.vitorpamplona.amethyst.ui.note.types.EditState
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
@ -996,7 +998,7 @@ fun ZapReaction(
|
|||||||
var wantsToZap by remember { mutableStateOf(false) }
|
var wantsToZap by remember { mutableStateOf(false) }
|
||||||
var wantsToChangeZapAmount by remember { mutableStateOf(false) }
|
var wantsToChangeZapAmount by remember { mutableStateOf(false) }
|
||||||
var wantsToSetCustomZap by remember { mutableStateOf(false) }
|
var wantsToSetCustomZap by remember { mutableStateOf(false) }
|
||||||
var showErrorMessageDialog by remember { mutableStateOf<List<String>>(emptyList()) }
|
val errorViewModel: UserBasedErrorMessageViewModel = viewModel()
|
||||||
var wantsToPay by
|
var wantsToPay by
|
||||||
remember(baseNote) {
|
remember(baseNote) {
|
||||||
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(
|
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(
|
||||||
@ -1028,10 +1030,10 @@ fun ZapReaction(
|
|||||||
wantsToZap = true
|
wantsToZap = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError = { _, message ->
|
onError = { _, message, user ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
showErrorMessageDialog = showErrorMessageDialog + message
|
errorViewModel.add(message, user)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPayViaIntent = { wantsToPay = it },
|
onPayViaIntent = { wantsToPay = it },
|
||||||
@ -1057,10 +1059,10 @@ fun ZapReaction(
|
|||||||
wantsToChangeZapAmount = true
|
wantsToChangeZapAmount = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError = { _, message ->
|
onError = { _, message, user ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
showErrorMessageDialog = showErrorMessageDialog + message
|
errorViewModel.add(message, user)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onProgress = { scope.launch(Dispatchers.Main) { zappingProgress = it } },
|
onProgress = { scope.launch(Dispatchers.Main) { zappingProgress = it } },
|
||||||
@ -1068,22 +1070,12 @@ fun ZapReaction(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showErrorMessageDialog.isNotEmpty()) {
|
MultiUserErrorMessageDialog(
|
||||||
val msg = showErrorMessageDialog.joinToString("\n")
|
title = stringRes(id = R.string.error_dialog_zap_error),
|
||||||
ErrorMessageDialog(
|
model = errorViewModel,
|
||||||
title = stringRes(id = R.string.error_dialog_zap_error),
|
accountViewModel = accountViewModel,
|
||||||
textContent = msg,
|
nav = nav,
|
||||||
onClickStartMessage = {
|
)
|
||||||
baseNote.author?.let {
|
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
val route = routeToMessage(it, msg, accountViewModel)
|
|
||||||
nav.nav(route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDismiss = { showErrorMessageDialog = emptyList() },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wantsToChangeZapAmount) {
|
if (wantsToChangeZapAmount) {
|
||||||
UpdateZapAmountDialog(
|
UpdateZapAmountDialog(
|
||||||
@ -1101,12 +1093,12 @@ fun ZapReaction(
|
|||||||
wantsToPay = persistentListOf()
|
wantsToPay = persistentListOf()
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
showErrorMessageDialog = showErrorMessageDialog + it
|
errorViewModel.add(it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
justShowError = {
|
justShowError = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
showErrorMessageDialog = showErrorMessageDialog + it
|
errorViewModel.add(it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -1115,10 +1107,10 @@ fun ZapReaction(
|
|||||||
if (wantsToSetCustomZap) {
|
if (wantsToSetCustomZap) {
|
||||||
ZapCustomDialog(
|
ZapCustomDialog(
|
||||||
onClose = { wantsToSetCustomZap = false },
|
onClose = { wantsToSetCustomZap = false },
|
||||||
onError = { _, message ->
|
onError = { _, message, user ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
showErrorMessageDialog = showErrorMessageDialog + message
|
errorViewModel.add(message, user)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onProgress = { scope.launch(Dispatchers.Main) { zappingProgress = it } },
|
onProgress = { scope.launch(Dispatchers.Main) { zappingProgress = it } },
|
||||||
@ -1168,7 +1160,7 @@ fun zapClick(
|
|||||||
context: Context,
|
context: Context,
|
||||||
onZappingProgress: (Float) -> Unit,
|
onZappingProgress: (Float) -> Unit,
|
||||||
onMultipleChoices: () -> Unit,
|
onMultipleChoices: () -> Unit,
|
||||||
onError: (String, String) -> Unit,
|
onError: (String, String, User?) -> Unit,
|
||||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (baseNote.isDraft()) {
|
if (baseNote.isDraft()) {
|
||||||
@ -1564,7 +1556,7 @@ fun ZapAmountChoicePopup(
|
|||||||
popupYOffset: Dp,
|
popupYOffset: Dp,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onChangeAmount: () -> Unit,
|
onChangeAmount: () -> Unit,
|
||||||
onError: (title: String, text: String) -> Unit,
|
onError: (title: String, text: String, user: User?) -> Unit,
|
||||||
onProgress: (percent: Float) -> Unit,
|
onProgress: (percent: Float) -> Unit,
|
||||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -1583,7 +1575,7 @@ fun ZapAmountChoicePopup(
|
|||||||
popupYOffset: Dp,
|
popupYOffset: Dp,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onChangeAmount: () -> Unit,
|
onChangeAmount: () -> Unit,
|
||||||
onError: (title: String, text: String) -> Unit,
|
onError: (title: String, text: String, user: User?) -> Unit,
|
||||||
onProgress: (percent: Float) -> Unit,
|
onProgress: (percent: Float) -> Unit,
|
||||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -1600,7 +1592,7 @@ fun ZapAmountChoicePopup(
|
|||||||
popupYOffset: Dp,
|
popupYOffset: Dp,
|
||||||
visibilityState: MutableTransitionState<Boolean>,
|
visibilityState: MutableTransitionState<Boolean>,
|
||||||
onChangeAmount: () -> Unit,
|
onChangeAmount: () -> Unit,
|
||||||
onError: (title: String, text: String) -> Unit,
|
onError: (title: String, text: String, user: User?) -> Unit,
|
||||||
onProgress: (percent: Float) -> Unit,
|
onProgress: (percent: Float) -> Unit,
|
||||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -25,27 +25,19 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.outlined.Done
|
|
||||||
import androidx.compose.material3.AlertDialog
|
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonColors
|
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@ -56,7 +48,6 @@ import androidx.compose.ui.Alignment
|
|||||||
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.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
@ -72,6 +63,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
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.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||||
@ -82,7 +74,6 @@ import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size16dp
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ZeroPadding
|
import com.vitorpamplona.amethyst.ui.theme.ZeroPadding
|
||||||
@ -112,7 +103,7 @@ class ZapOptionstViewModel : ViewModel() {
|
|||||||
@Composable
|
@Composable
|
||||||
fun ZapCustomDialog(
|
fun ZapCustomDialog(
|
||||||
onClose: () -> Unit,
|
onClose: () -> Unit,
|
||||||
onError: (title: String, text: String) -> Unit,
|
onError: (title: String, text: String, user: User?) -> Unit,
|
||||||
onProgress: (percent: Float) -> Unit,
|
onProgress: (percent: Float) -> Unit,
|
||||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
@ -290,66 +281,21 @@ fun ZapButton(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ErrorMessageDialog(
|
|
||||||
title: String,
|
|
||||||
textContent: String,
|
|
||||||
buttonColors: ButtonColors = ButtonDefaults.buttonColors(),
|
|
||||||
onClickStartMessage: (() -> Unit)? = null,
|
|
||||||
onDismiss: () -> Unit,
|
|
||||||
) {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = onDismiss,
|
|
||||||
title = { Text(title) },
|
|
||||||
text = { SelectionContainer { Text(textContent) } },
|
|
||||||
confirmButton = {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.padding(vertical = 8.dp).fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
) {
|
|
||||||
onClickStartMessage?.let {
|
|
||||||
TextButton(onClick = onClickStartMessage) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_dm),
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
Spacer(StdHorzSpacer)
|
|
||||||
Text(stringRes(R.string.error_dialog_talk_to_user))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Button(
|
|
||||||
onClick = onDismiss,
|
|
||||||
colors = buttonColors,
|
|
||||||
contentPadding = PaddingValues(horizontal = Size16dp),
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.Done,
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
Spacer(StdHorzSpacer)
|
|
||||||
Text(stringRes(R.string.error_dialog_button_ok))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PayViaIntentDialog(
|
fun PayViaIntentDialog(
|
||||||
payingInvoices: ImmutableList<ZapPaymentHandler.Payable>,
|
payingInvoices: ImmutableList<ZapPaymentHandler.Payable>,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
onClose: () -> Unit,
|
onClose: () -> Unit,
|
||||||
onError: (String) -> Unit,
|
onError: (UserBasedErrorMessage) -> Unit,
|
||||||
justShowError: (String) -> Unit,
|
justShowError: (UserBasedErrorMessage) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
if (payingInvoices.size == 1) {
|
if (payingInvoices.size == 1) {
|
||||||
payViaIntent(payingInvoices.first().invoice, context, onClose, onError)
|
val payable = payingInvoices.first()
|
||||||
|
payViaIntent(payable.invoice, context, onClose) {
|
||||||
|
onError(UserBasedErrorMessage(it, payable.user))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Dialog(
|
Dialog(
|
||||||
onDismissRequest = onClose,
|
onDismissRequest = onClose,
|
||||||
@ -370,15 +316,15 @@ fun PayViaIntentDialog(
|
|||||||
|
|
||||||
Spacer(modifier = DoubleVertSpacer)
|
Spacer(modifier = DoubleVertSpacer)
|
||||||
|
|
||||||
payingInvoices.forEachIndexed { index, it ->
|
payingInvoices.forEachIndexed { index, payable ->
|
||||||
val paid = remember { mutableStateOf(false) }
|
val paid = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.padding(vertical = Size10dp),
|
modifier = Modifier.padding(vertical = Size10dp),
|
||||||
) {
|
) {
|
||||||
if (it.user != null) {
|
if (payable.user != null) {
|
||||||
BaseUserPicture(it.user, Size55dp, accountViewModel = accountViewModel)
|
BaseUserPicture(payable.user, Size55dp, accountViewModel = accountViewModel)
|
||||||
} else {
|
} else {
|
||||||
DisplayBlankAuthor(size = Size55dp, accountViewModel = accountViewModel)
|
DisplayBlankAuthor(size = Size55dp, accountViewModel = accountViewModel)
|
||||||
}
|
}
|
||||||
@ -386,8 +332,8 @@ fun PayViaIntentDialog(
|
|||||||
Spacer(modifier = DoubleHorzSpacer)
|
Spacer(modifier = DoubleHorzSpacer)
|
||||||
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
if (it.user != null) {
|
if (payable.user != null) {
|
||||||
UsernameDisplay(it.user, accountViewModel = accountViewModel)
|
UsernameDisplay(payable.user, accountViewModel = accountViewModel)
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = stringRes(id = R.string.wallet_number, index + 1),
|
text = stringRes(id = R.string.wallet_number, index + 1),
|
||||||
@ -399,7 +345,7 @@ fun PayViaIntentDialog(
|
|||||||
}
|
}
|
||||||
Row {
|
Row {
|
||||||
Text(
|
Text(
|
||||||
text = showAmount((it.amountMilliSats / 1000.0f).toBigDecimal()),
|
text = showAmount((payable.amountMilliSats / 1000.0f).toBigDecimal()),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
@ -419,7 +365,9 @@ fun PayViaIntentDialog(
|
|||||||
Spacer(modifier = DoubleHorzSpacer)
|
Spacer(modifier = DoubleHorzSpacer)
|
||||||
|
|
||||||
PayButton(isActive = !paid.value) {
|
PayButton(isActive = !paid.value) {
|
||||||
payViaIntent(it.invoice, context, { paid.value = true }, justShowError)
|
payViaIntent(payable.invoice, context, { paid.value = true }) {
|
||||||
|
justShowError(UserBasedErrorMessage(it, payable.user))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,10 +58,12 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.BuildConfig
|
import com.vitorpamplona.amethyst.BuildConfig
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||||
import com.vitorpamplona.amethyst.ui.components.ClickableText
|
import com.vitorpamplona.amethyst.ui.components.ClickableText
|
||||||
@ -69,11 +71,11 @@ import com.vitorpamplona.amethyst.ui.components.LoadNote
|
|||||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.CloseIcon
|
import com.vitorpamplona.amethyst.ui.note.CloseIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.ErrorMessageDialog
|
import com.vitorpamplona.amethyst.ui.note.MultiUserErrorMessageDialog
|
||||||
import com.vitorpamplona.amethyst.ui.note.ObserveZapIcon
|
import com.vitorpamplona.amethyst.ui.note.ObserveZapIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.PayViaIntentDialog
|
import com.vitorpamplona.amethyst.ui.note.PayViaIntentDialog
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.UserBasedErrorMessageViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapAmountChoicePopup
|
import com.vitorpamplona.amethyst.ui.note.ZapAmountChoicePopup
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapIcon
|
import com.vitorpamplona.amethyst.ui.note.ZapIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZappedIcon
|
import com.vitorpamplona.amethyst.ui.note.ZappedIcon
|
||||||
@ -291,7 +293,7 @@ fun ZapDonationButton(
|
|||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
var wantsToZap by remember { mutableStateOf<ImmutableList<Long>?>(null) }
|
var wantsToZap by remember { mutableStateOf<ImmutableList<Long>?>(null) }
|
||||||
var showErrorMessageDialog by remember { mutableStateOf<String?>(null) }
|
val errorViewModel: UserBasedErrorMessageViewModel = viewModel()
|
||||||
var wantsToPay by
|
var wantsToPay by
|
||||||
remember(baseNote) {
|
remember(baseNote) {
|
||||||
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(
|
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(
|
||||||
@ -315,10 +317,10 @@ fun ZapDonationButton(
|
|||||||
scope.launch { zappingProgress = progress }
|
scope.launch { zappingProgress = progress }
|
||||||
},
|
},
|
||||||
onMultipleChoices = { options -> wantsToZap = options.toImmutableList() },
|
onMultipleChoices = { options -> wantsToZap = options.toImmutableList() },
|
||||||
onError = { _, message ->
|
onError = { _, message, toUser ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
showErrorMessageDialog = message
|
errorViewModel.add(message, toUser)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPayViaIntent = { wantsToPay = it },
|
onPayViaIntent = { wantsToPay = it },
|
||||||
@ -339,10 +341,10 @@ fun ZapDonationButton(
|
|||||||
onChangeAmount = {
|
onChangeAmount = {
|
||||||
wantsToZap = null
|
wantsToZap = null
|
||||||
},
|
},
|
||||||
onError = { _, message ->
|
onError = { _, message, user ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
showErrorMessageDialog = message
|
errorViewModel.add(message, user)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onProgress = {
|
onProgress = {
|
||||||
@ -352,21 +354,12 @@ fun ZapDonationButton(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showErrorMessageDialog != null) {
|
MultiUserErrorMessageDialog(
|
||||||
ErrorMessageDialog(
|
title = stringRes(id = R.string.error_dialog_zap_error),
|
||||||
title = stringRes(id = R.string.error_dialog_zap_error),
|
model = errorViewModel,
|
||||||
textContent = showErrorMessageDialog ?: "",
|
accountViewModel = accountViewModel,
|
||||||
onClickStartMessage = {
|
nav = nav,
|
||||||
baseNote.author?.let {
|
)
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
val route = routeToMessage(it, showErrorMessageDialog, accountViewModel)
|
|
||||||
nav.nav(route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDismiss = { showErrorMessageDialog = null },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wantsToPay.isNotEmpty()) {
|
if (wantsToPay.isNotEmpty()) {
|
||||||
PayViaIntentDialog(
|
PayViaIntentDialog(
|
||||||
@ -377,12 +370,12 @@ fun ZapDonationButton(
|
|||||||
wantsToPay = persistentListOf()
|
wantsToPay = persistentListOf()
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
showErrorMessageDialog = it
|
errorViewModel.add(it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
justShowError = {
|
justShowError = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
showErrorMessageDialog = it
|
errorViewModel.add(it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -444,7 +437,7 @@ fun customZapClick(
|
|||||||
context: Context,
|
context: Context,
|
||||||
onZappingProgress: (Float) -> Unit,
|
onZappingProgress: (Float) -> Unit,
|
||||||
onMultipleChoices: (List<Long>) -> Unit,
|
onMultipleChoices: (List<Long>) -> Unit,
|
||||||
onError: (String, String) -> Unit,
|
onError: (String, String, User?) -> Unit,
|
||||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (baseNote.isDraft()) {
|
if (baseNote.isDraft()) {
|
||||||
|
@ -506,6 +506,12 @@ class AccountViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DecryptedInfo(
|
||||||
|
val zapRequest: Note,
|
||||||
|
val zapEvent: Note?,
|
||||||
|
val info: ZapAmountCommentNotification,
|
||||||
|
)
|
||||||
|
|
||||||
fun decryptAmountMessageInGroup(
|
fun decryptAmountMessageInGroup(
|
||||||
zaps: ImmutableList<CombinedZap>,
|
zaps: ImmutableList<CombinedZap>,
|
||||||
onNewState: (ImmutableList<ZapAmountCommentNotification>) -> Unit,
|
onNewState: (ImmutableList<ZapAmountCommentNotification>) -> Unit,
|
||||||
@ -524,17 +530,19 @@ class AccountViewModel(
|
|||||||
)
|
)
|
||||||
}.toMutableMap()
|
}.toMutableMap()
|
||||||
|
|
||||||
collectSuccessfulOperations<CombinedZap, ZapAmountCommentNotification>(
|
collectSuccessfulOperations<CombinedZap, DecryptedInfo>(
|
||||||
items = zaps.filter { (it.request.event as? LnZapRequestEvent)?.isPrivateZap() == true },
|
items = zaps.filter { (it.request.event as? LnZapRequestEvent)?.isPrivateZap() == true },
|
||||||
runRequestFor = { next, onReady ->
|
runRequestFor = { next, onReady ->
|
||||||
checkNotInMainThread()
|
checkNotInMainThread()
|
||||||
|
|
||||||
innerDecryptAmountMessage(next.request, next.response, onReady)
|
innerDecryptAmountMessage(next.request, next.response) {
|
||||||
|
onReady(DecryptedInfo(next.request, next.response, it))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
checkNotInMainThread()
|
checkNotInMainThread()
|
||||||
|
|
||||||
it.forEach { decrypted -> initialResults[decrypted.key.request] = decrypted.value }
|
it.forEach { decrypted -> initialResults[decrypted.zapRequest] = decrypted.info }
|
||||||
|
|
||||||
onNewState(initialResults.values.toImmutableList())
|
onNewState(initialResults.values.toImmutableList())
|
||||||
}
|
}
|
||||||
@ -628,13 +636,15 @@ class AccountViewModel(
|
|||||||
)
|
)
|
||||||
}.toMutableMap()
|
}.toMutableMap()
|
||||||
|
|
||||||
collectSuccessfulOperations<Pair<Note, Note?>, ZapAmountCommentNotification>(
|
collectSuccessfulOperations<Pair<Note, Note?>, DecryptedInfo>(
|
||||||
items = myList,
|
items = myList,
|
||||||
runRequestFor = { next, onReady ->
|
runRequestFor = { next, onReady ->
|
||||||
innerDecryptAmountMessage(next.first, next.second, onReady)
|
innerDecryptAmountMessage(next.first, next.second) {
|
||||||
|
onReady(DecryptedInfo(next.first, next.second, it))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
it.forEach { decrypted -> initialResults[decrypted.key.first] = decrypted.value }
|
it.forEach { decrypted -> initialResults[decrypted.zapRequest] = decrypted.info }
|
||||||
|
|
||||||
onNewState(initialResults.values.toImmutableList())
|
onNewState(initialResults.values.toImmutableList())
|
||||||
}
|
}
|
||||||
@ -693,7 +703,7 @@ class AccountViewModel(
|
|||||||
message: String,
|
message: String,
|
||||||
context: Context,
|
context: Context,
|
||||||
showErrorIfNoLnAddress: Boolean = true,
|
showErrorIfNoLnAddress: Boolean = true,
|
||||||
onError: (String, String) -> Unit,
|
onError: (String, String, User?) -> Unit,
|
||||||
onProgress: (percent: Float) -> Unit,
|
onProgress: (percent: Float) -> Unit,
|
||||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
||||||
zapType: LnZapEvent.ZapType? = null,
|
zapType: LnZapEvent.ZapType? = null,
|
||||||
|
@ -69,12 +69,12 @@ import com.vitorpamplona.amethyst.ui.components.LoadNote
|
|||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
|
import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.DVMCard
|
import com.vitorpamplona.amethyst.ui.note.DVMCard
|
||||||
import com.vitorpamplona.amethyst.ui.note.ErrorMessageDialog
|
import com.vitorpamplona.amethyst.ui.note.MultiUserErrorMessageDialog
|
||||||
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
|
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
|
||||||
import com.vitorpamplona.amethyst.ui.note.ObserveZapIcon
|
import com.vitorpamplona.amethyst.ui.note.ObserveZapIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.PayViaIntentDialog
|
import com.vitorpamplona.amethyst.ui.note.PayViaIntentDialog
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.UserBasedErrorMessageViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.note.WatchNoteEvent
|
import com.vitorpamplona.amethyst.ui.note.WatchNoteEvent
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapAmountChoicePopup
|
import com.vitorpamplona.amethyst.ui.note.ZapAmountChoicePopup
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapIcon
|
import com.vitorpamplona.amethyst.ui.note.ZapIcon
|
||||||
@ -439,7 +439,7 @@ fun ZapDVMButton(
|
|||||||
val noteAuthor = baseNote.author ?: return
|
val noteAuthor = baseNote.author ?: return
|
||||||
|
|
||||||
var wantsToZap by remember { mutableStateOf<List<Long>?>(null) }
|
var wantsToZap by remember { mutableStateOf<List<Long>?>(null) }
|
||||||
var showErrorMessageDialog by remember { mutableStateOf<String?>(null) }
|
val errorViewModel: UserBasedErrorMessageViewModel = viewModel()
|
||||||
var wantsToPay by
|
var wantsToPay by
|
||||||
remember(baseNote) {
|
remember(baseNote) {
|
||||||
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(
|
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(
|
||||||
@ -466,10 +466,10 @@ fun ZapDVMButton(
|
|||||||
scope.launch { zappingProgress = progress }
|
scope.launch { zappingProgress = progress }
|
||||||
},
|
},
|
||||||
onMultipleChoices = { options -> wantsToZap = options },
|
onMultipleChoices = { options -> wantsToZap = options },
|
||||||
onError = { _, message ->
|
onError = { _, message, toUser ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
showErrorMessageDialog = message
|
errorViewModel.add(message, toUser)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPayViaIntent = { wantsToPay = it },
|
onPayViaIntent = { wantsToPay = it },
|
||||||
@ -490,10 +490,10 @@ fun ZapDVMButton(
|
|||||||
onChangeAmount = {
|
onChangeAmount = {
|
||||||
wantsToZap = null
|
wantsToZap = null
|
||||||
},
|
},
|
||||||
onError = { _, message ->
|
onError = { _, message, user ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
showErrorMessageDialog = message
|
errorViewModel.add(message, user)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onProgress = {
|
onProgress = {
|
||||||
@ -503,21 +503,12 @@ fun ZapDVMButton(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showErrorMessageDialog != null) {
|
MultiUserErrorMessageDialog(
|
||||||
ErrorMessageDialog(
|
title = stringRes(id = R.string.error_dialog_zap_error),
|
||||||
title = stringRes(id = R.string.error_dialog_zap_error),
|
model = errorViewModel,
|
||||||
textContent = showErrorMessageDialog ?: "",
|
accountViewModel,
|
||||||
onClickStartMessage = {
|
nav,
|
||||||
baseNote.author?.let {
|
)
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
val route = routeToMessage(it, showErrorMessageDialog, accountViewModel)
|
|
||||||
nav.nav(route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDismiss = { showErrorMessageDialog = null },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wantsToPay.isNotEmpty()) {
|
if (wantsToPay.isNotEmpty()) {
|
||||||
PayViaIntentDialog(
|
PayViaIntentDialog(
|
||||||
@ -528,12 +519,12 @@ fun ZapDVMButton(
|
|||||||
wantsToPay = persistentListOf()
|
wantsToPay = persistentListOf()
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
showErrorMessageDialog = it
|
errorViewModel.add(it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
justShowError = {
|
justShowError = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
showErrorMessageDialog = it
|
errorViewModel.add(it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -726,6 +726,7 @@
|
|||||||
|
|
||||||
<string name="error_dialog_zap_error">Unable to send zap</string>
|
<string name="error_dialog_zap_error">Unable to send zap</string>
|
||||||
<string name="error_dialog_talk_to_user">Message the User</string>
|
<string name="error_dialog_talk_to_user">Message the User</string>
|
||||||
|
<string name="error_dialog_talk_to_user_name">Message %1$s</string>
|
||||||
<string name="error_dialog_button_ok">OK</string>
|
<string name="error_dialog_button_ok">OK</string>
|
||||||
|
|
||||||
<string name="relay_information_document_error_assemble_url">Failed to reach %1$s: %2$s</string>
|
<string name="relay_information_document_error_assemble_url">Failed to reach %1$s: %2$s</string>
|
||||||
@ -801,6 +802,7 @@
|
|||||||
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct">Could not resolve %1$s. Check if you are connected, if the server is up and if the lightning address %2$s is correct</string>
|
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct">Could not resolve %1$s. Check if you are connected, if the server is up and if the lightning address %2$s is correct</string>
|
||||||
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct_exception">Could not resolve %1$s. Check if you are connected, if the server is up and if the lightning address %2$s is correct.\n\nException was: %3$s</string>
|
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct_exception">Could not resolve %1$s. Check if you are connected, if the server is up and if the lightning address %2$s is correct.\n\nException was: %3$s</string>
|
||||||
<string name="could_not_fetch_invoice_from">Could not fetch invoice from %1$s</string>
|
<string name="could_not_fetch_invoice_from">Could not fetch invoice from %1$s</string>
|
||||||
|
<string name="could_not_fetch_invoice_from_details">Could not fetch invoice from %1$s: %2$s</string>
|
||||||
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup">Error Parsing JSON from Lightning Address. Check the user\'s lightning setup</string>
|
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup">Error Parsing JSON from Lightning Address. Check the user\'s lightning setup</string>
|
||||||
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup_with_user">Error Parsing JSON from %1$s. Check the user\'s lightning setup</string>
|
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup_with_user">Error Parsing JSON from %1$s. Check the user\'s lightning setup</string>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user