Moves many Toasts to better designed Information Dialogs.
This commit is contained in:
@@ -895,7 +895,7 @@ class Account(
|
|||||||
return returningContactList
|
return returningContactList
|
||||||
}
|
}
|
||||||
|
|
||||||
fun follow(user: User) {
|
suspend fun follow(user: User) {
|
||||||
if (!isWriteable() && !loginWithExternalSigner) return
|
if (!isWriteable() && !loginWithExternalSigner) return
|
||||||
|
|
||||||
val contactList = migrateCommunitiesAndChannelsIfNeeded(userProfile().latestContactList)
|
val contactList = migrateCommunitiesAndChannelsIfNeeded(userProfile().latestContactList)
|
||||||
@@ -1064,7 +1064,7 @@ class Account(
|
|||||||
LocalCache.consume(event)
|
LocalCache.consume(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unfollow(user: User) {
|
suspend fun unfollow(user: User) {
|
||||||
if (!isWriteable() && !loginWithExternalSigner) return
|
if (!isWriteable() && !loginWithExternalSigner) return
|
||||||
|
|
||||||
val contactList = migrateCommunitiesAndChannelsIfNeeded(userProfile().latestContactList)
|
val contactList = migrateCommunitiesAndChannelsIfNeeded(userProfile().latestContactList)
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package com.vitorpamplona.amethyst.service
|
package com.vitorpamplona.amethyst.service
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.fasterxml.jackson.databind.JsonNode
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
|
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
|
||||||
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
||||||
import com.vitorpamplona.quartz.events.Event
|
import com.vitorpamplona.quartz.events.Event
|
||||||
@@ -45,7 +47,7 @@ class CashuProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun melt(token: CashuToken, lud16: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) {
|
suspend fun melt(token: CashuToken, lud16: String, onSuccess: (String, String) -> Unit, onError: (String, String) -> Unit, context: Context) {
|
||||||
checkNotInMainThread()
|
checkNotInMainThread()
|
||||||
|
|
||||||
runCatching {
|
runCatching {
|
||||||
@@ -54,16 +56,17 @@ class CashuProcessor {
|
|||||||
milliSats = token.redeemInvoiceAmount * 1000, // Make invoice and leave room for fees
|
milliSats = token.redeemInvoiceAmount * 1000, // Make invoice and leave room for fees
|
||||||
message = "Redeem Cashu",
|
message = "Redeem Cashu",
|
||||||
onSuccess = { invoice ->
|
onSuccess = { invoice ->
|
||||||
meltInvoice(token, invoice, onSuccess, onError)
|
meltInvoice(token, invoice, onSuccess, onError, context)
|
||||||
},
|
},
|
||||||
onProgress = {
|
onProgress = {
|
||||||
},
|
},
|
||||||
onError = onError
|
onError = onError,
|
||||||
|
context = context
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun meltInvoice(token: CashuToken, invoice: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) {
|
private fun meltInvoice(token: CashuToken, invoice: String, onSuccess: (String, String) -> Unit, onError: (String, String) -> Unit, context: Context) {
|
||||||
try {
|
try {
|
||||||
val client = HttpClient.getHttpClient()
|
val client = HttpClient.getHttpClient()
|
||||||
val url = token.mint + "/melt" // Melt cashu tokens at Mint
|
val url = token.mint + "/melt" // Melt cashu tokens at Mint
|
||||||
@@ -88,13 +91,24 @@ class CashuProcessor {
|
|||||||
val successful = tree?.get("paid")?.asText() == "true"
|
val successful = tree?.get("paid")?.asText() == "true"
|
||||||
|
|
||||||
if (successful) {
|
if (successful) {
|
||||||
onSuccess("Redeemed ${token.totalAmount} Sats" + " (Fees: ${token.fees} Sats)")
|
onSuccess(
|
||||||
|
context.getString(R.string.cashu_sucessful_redemption),
|
||||||
|
context.getString(R.string.cashu_sucessful_redemption_explainer, token.totalAmount.toString(), token.fees.toString())
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
onError(tree?.get("detail")?.asText()?.split('.')?.getOrNull(0) ?: "Cashu: Tokens already spent.")
|
val msg = tree?.get("detail")?.asText()?.split('.')?.getOrNull(0)?.ifBlank { null }
|
||||||
|
onError(
|
||||||
|
context.getString(R.string.cashu_failed_redemption),
|
||||||
|
if (msg != null) {
|
||||||
|
context.getString(R.string.cashu_failed_redemption_explainer_error_msg, msg)
|
||||||
|
} else {
|
||||||
|
context.getString(R.string.cashu_failed_redemption_explainer_error_msg)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
onError("Token melt failure: " + e.message)
|
onError(context.getString(R.string.cashu_sucessful_redemption), context.getString(R.string.cashu_failed_redemption_explainer_error_msg, e.message))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class ZapPaymentHandler(val account: Account) {
|
|||||||
pollOption: Int?,
|
pollOption: Int?,
|
||||||
message: String,
|
message: String,
|
||||||
context: Context,
|
context: Context,
|
||||||
onError: (String) -> Unit,
|
onError: (String, String) -> Unit,
|
||||||
onProgress: (percent: Float) -> Unit,
|
onProgress: (percent: Float) -> Unit,
|
||||||
onPayViaIntent: (ImmutableList<Payable>) -> Unit,
|
onPayViaIntent: (ImmutableList<Payable>) -> Unit,
|
||||||
zapType: LnZapEvent.ZapType
|
zapType: LnZapEvent.ZapType
|
||||||
@@ -48,7 +48,10 @@ class ZapPaymentHandler(val account: Account) {
|
|||||||
val lud16 = note.author?.info?.lud16?.trim() ?: note.author?.info?.lud06?.trim()
|
val lud16 = note.author?.info?.lud16?.trim() ?: note.author?.info?.lud06?.trim()
|
||||||
|
|
||||||
if (lud16.isNullOrBlank()) {
|
if (lud16.isNullOrBlank()) {
|
||||||
onError(context.getString(R.string.user_does_not_have_a_lightning_address_setup_to_receive_sats))
|
onError(
|
||||||
|
context.getString(R.string.missing_lud16),
|
||||||
|
context.getString(R.string.user_does_not_have_a_lightning_address_setup_to_receive_sats)
|
||||||
|
)
|
||||||
return@withContext
|
return@withContext
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +124,9 @@ class ZapPaymentHandler(val account: Account) {
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
onError(
|
onError(
|
||||||
|
context.getString(
|
||||||
|
R.string.missing_lud16
|
||||||
|
),
|
||||||
context.getString(
|
context.getString(
|
||||||
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() ?: value.lnAddressOrPubKeyHex
|
user?.toBestDisplayName() ?: value.lnAddressOrPubKeyHex
|
||||||
@@ -149,7 +155,7 @@ class ZapPaymentHandler(val account: Account) {
|
|||||||
pollOption: Int?,
|
pollOption: Int?,
|
||||||
message: String,
|
message: String,
|
||||||
context: Context,
|
context: Context,
|
||||||
onError: (String) -> Unit,
|
onError: (String, String) -> Unit,
|
||||||
onProgress: (percent: Float) -> Unit,
|
onProgress: (percent: Float) -> Unit,
|
||||||
onPayInvoiceThroughIntent: (String) -> Unit,
|
onPayInvoiceThroughIntent: (String) -> Unit,
|
||||||
zapType: LnZapEvent.ZapType,
|
zapType: LnZapEvent.ZapType,
|
||||||
@@ -181,9 +187,13 @@ class ZapPaymentHandler(val account: Account) {
|
|||||||
if (response is PayInvoiceErrorResponse) {
|
if (response is PayInvoiceErrorResponse) {
|
||||||
onProgress(0.0f)
|
onProgress(0.0f)
|
||||||
onError(
|
onError(
|
||||||
response.error?.message
|
context.getString(R.string.error_dialog_pay_invoice_error),
|
||||||
?: response.error?.code?.toString()
|
context.getString(
|
||||||
?: "Error parsing error message"
|
R.string.wallet_connect_pay_invoice_error_error,
|
||||||
|
response.error?.message
|
||||||
|
?: response.error?.code?.toString()
|
||||||
|
?: "Error parsing error message"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
onProgress(1f)
|
onProgress(1f)
|
||||||
@@ -192,16 +202,13 @@ class ZapPaymentHandler(val account: Account) {
|
|||||||
)
|
)
|
||||||
onProgress(0.8f)
|
onProgress(0.8f)
|
||||||
} else {
|
} else {
|
||||||
try {
|
onPayInvoiceThroughIntent(it)
|
||||||
onPayInvoiceThroughIntent(it)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
onError(context.getString(R.string.lightning_wallets_not_found2))
|
|
||||||
}
|
|
||||||
onProgress(0f)
|
onProgress(0f)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError = onError,
|
onError = onError,
|
||||||
onProgress = onProgress
|
onProgress = onProgress,
|
||||||
|
context = context
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+87
-18
@@ -1,7 +1,9 @@
|
|||||||
package com.vitorpamplona.amethyst.service.lnurl
|
package com.vitorpamplona.amethyst.service.lnurl
|
||||||
|
|
||||||
|
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.service.HttpClient
|
import com.vitorpamplona.amethyst.service.HttpClient
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.quartz.encoders.LnInvoiceUtil
|
import com.vitorpamplona.quartz.encoders.LnInvoiceUtil
|
||||||
@@ -31,13 +33,24 @@ class LightningAddressResolver() {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchLightningAddressJson(lnaddress: String, onSuccess: suspend (String) -> Unit, onError: (String) -> Unit) = withContext(Dispatchers.IO) {
|
private suspend fun fetchLightningAddressJson(
|
||||||
|
lnaddress: String,
|
||||||
|
onSuccess: suspend (String) -> Unit,
|
||||||
|
onError: (String, String) -> Unit,
|
||||||
|
context: Context
|
||||||
|
) = withContext(Dispatchers.IO) {
|
||||||
checkNotInMainThread()
|
checkNotInMainThread()
|
||||||
|
|
||||||
val url = assembleUrl(lnaddress)
|
val url = assembleUrl(lnaddress)
|
||||||
|
|
||||||
if (url == null) {
|
if (url == null) {
|
||||||
onError("Could not assemble LNUrl from Lightning Address \"${lnaddress}\". Check the user's setup")
|
onError(
|
||||||
|
context.getString(R.string.error_unable_to_fetch_invoice),
|
||||||
|
context.getString(
|
||||||
|
R.string.could_not_assemble_lnurl_from_lightning_address_check_the_user_s_setup,
|
||||||
|
lnaddress
|
||||||
|
)
|
||||||
|
)
|
||||||
return@withContext
|
return@withContext
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,16 +64,39 @@ class LightningAddressResolver() {
|
|||||||
if (it.isSuccessful) {
|
if (it.isSuccessful) {
|
||||||
onSuccess(it.body.string())
|
onSuccess(it.body.string())
|
||||||
} else {
|
} else {
|
||||||
onError("The receiver's lightning service at $url is not available. It was calculated from the lightning address \"${lnaddress}\". Error: ${it.code}. Check if the server is up and if the lightning address is correct")
|
onError(
|
||||||
|
context.getString(R.string.error_unable_to_fetch_invoice),
|
||||||
|
context.getString(
|
||||||
|
R.string.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,
|
||||||
|
lnaddress,
|
||||||
|
it.code.toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
onError("Could not resolve $url. Check if you are connected, if the server is up and if the lightning address $lnaddress is correct")
|
onError(
|
||||||
|
context.getString(R.string.error_unable_to_fetch_invoice),
|
||||||
|
context.getString(
|
||||||
|
R.string.could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct,
|
||||||
|
url,
|
||||||
|
lnaddress
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun fetchLightningInvoice(lnCallback: String, milliSats: Long, message: String, nostrRequest: String? = null, onSuccess: suspend (String) -> Unit, onError: (String) -> Unit) = withContext(Dispatchers.IO) {
|
suspend fun fetchLightningInvoice(
|
||||||
|
lnCallback: String,
|
||||||
|
milliSats: Long,
|
||||||
|
message: String,
|
||||||
|
nostrRequest: String? = null,
|
||||||
|
onSuccess: suspend (String) -> Unit,
|
||||||
|
onError: (String, String) -> Unit,
|
||||||
|
context: Context
|
||||||
|
) = withContext(Dispatchers.IO) {
|
||||||
val encodedMessage = URLEncoder.encode(message, "utf-8")
|
val encodedMessage = URLEncoder.encode(message, "utf-8")
|
||||||
|
|
||||||
val urlBinder = if (lnCallback.contains("?")) "&" else "?"
|
val urlBinder = if (lnCallback.contains("?")) "&" else "?"
|
||||||
@@ -80,18 +116,22 @@ class LightningAddressResolver() {
|
|||||||
if (it.isSuccessful) {
|
if (it.isSuccessful) {
|
||||||
onSuccess(it.body.string())
|
onSuccess(it.body.string())
|
||||||
} else {
|
} else {
|
||||||
onError("Could not fetch invoice from $lnCallback")
|
onError(
|
||||||
|
context.getString(R.string.error_unable_to_fetch_invoice),
|
||||||
|
context.getString(R.string.could_not_fetch_invoice_from, lnCallback)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun lnAddressToLnUrl(lnaddress: String, onSuccess: (String) -> Unit, onError: (String) -> Unit) {
|
suspend fun lnAddressToLnUrl(lnaddress: String, onSuccess: (String) -> Unit, onError: (String, String) -> Unit, context: Context) {
|
||||||
fetchLightningAddressJson(
|
fetchLightningAddressJson(
|
||||||
lnaddress,
|
lnaddress,
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
onSuccess(it.toByteArray().toLnUrl())
|
onSuccess(it.toByteArray().toLnUrl())
|
||||||
},
|
},
|
||||||
onError = onError
|
onError = onError,
|
||||||
|
context = context
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,8 +141,9 @@ class LightningAddressResolver() {
|
|||||||
message: String,
|
message: String,
|
||||||
nostrRequest: String? = null,
|
nostrRequest: String? = null,
|
||||||
onSuccess: suspend (String) -> Unit,
|
onSuccess: suspend (String) -> Unit,
|
||||||
onError: (String) -> Unit,
|
onError: (String, String) -> Unit,
|
||||||
onProgress: (percent: Float) -> Unit
|
onProgress: (percent: Float) -> Unit,
|
||||||
|
context: Context
|
||||||
) {
|
) {
|
||||||
val mapper = jacksonObjectMapper()
|
val mapper = jacksonObjectMapper()
|
||||||
|
|
||||||
@@ -114,14 +155,20 @@ class LightningAddressResolver() {
|
|||||||
val lnurlp = try {
|
val lnurlp = try {
|
||||||
mapper.readTree(lnAddressJson)
|
mapper.readTree(lnAddressJson)
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
onError("Error Parsing JSON from Lightning Address. Check the user's lightning setup")
|
onError(
|
||||||
|
context.getString(R.string.error_unable_to_fetch_invoice),
|
||||||
|
context.getString(R.string.error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup)
|
||||||
|
)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
val callback = lnurlp?.get("callback")?.asText()
|
val callback = lnurlp?.get("callback")?.asText()
|
||||||
|
|
||||||
if (callback == null) {
|
if (callback == null) {
|
||||||
onError("Callback URL not found in the User's lightning address server configuration")
|
onError(
|
||||||
|
context.getString(R.string.error_unable_to_fetch_invoice),
|
||||||
|
context.getString(R.string.callback_url_not_found_in_the_user_s_lightning_address_server_configuration)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val allowsNostr = lnurlp?.get("allowsNostr")?.asBoolean() ?: false
|
val allowsNostr = lnurlp?.get("allowsNostr")?.asBoolean() ?: false
|
||||||
@@ -138,7 +185,10 @@ class LightningAddressResolver() {
|
|||||||
val lnInvoice = try {
|
val lnInvoice = try {
|
||||||
mapper.readTree(it)
|
mapper.readTree(it)
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
onError("Error Parsing JSON from Lightning Address's invoice fetch. Check the user's lightning setup")
|
onError(
|
||||||
|
context.getString(R.string.error_unable_to_fetch_invoice),
|
||||||
|
context.getString(R.string.error_parsing_json_from_lightning_address_s_invoice_fetch_check_the_user_s_lightning_setup)
|
||||||
|
)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,21 +201,40 @@ class LightningAddressResolver() {
|
|||||||
onSuccess(pr)
|
onSuccess(pr)
|
||||||
} else {
|
} else {
|
||||||
onProgress(0.0f)
|
onProgress(0.0f)
|
||||||
onError("Incorrect invoice amount (${invoiceAmount.toLong()} sats) from $lnaddress. It should have been $expectedAmountInSats")
|
onError(
|
||||||
|
context.getString(R.string.error_unable_to_fetch_invoice),
|
||||||
|
context.getString(
|
||||||
|
R.string.incorrect_invoice_amount_sats_from_it_should_have_been,
|
||||||
|
invoiceAmount.toLong().toString(),
|
||||||
|
lnaddress,
|
||||||
|
expectedAmountInSats.toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} ?: lnInvoice?.get("reason")?.asText()?.ifBlank { null }?.let { reason ->
|
} ?: lnInvoice?.get("reason")?.asText()?.ifBlank { null }?.let { reason ->
|
||||||
onProgress(0.0f)
|
onProgress(0.0f)
|
||||||
onError("Unable to create a lightning invoice before sending the zap. The receiver's lightning wallet sent the following error: $reason")
|
onError(
|
||||||
|
context.getString(R.string.error_unable_to_fetch_invoice),
|
||||||
|
context.getString(
|
||||||
|
R.string.unable_to_create_a_lightning_invoice_before_sending_the_zap_the_receiver_s_lightning_wallet_sent_the_following_error,
|
||||||
|
reason
|
||||||
|
)
|
||||||
|
)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
onProgress(0.0f)
|
onProgress(0.0f)
|
||||||
onError("nable to create a lightning invoice before sending the zap. Element pr not found in the resulting JSON.")
|
onError(
|
||||||
|
context.getString(R.string.error_unable_to_fetch_invoice),
|
||||||
|
context.getString(R.string.unable_to_create_a_lightning_invoice_before_sending_the_zap_element_pr_not_found_in_the_resulting_json)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError = onError
|
onError = onError,
|
||||||
|
context
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError = onError
|
onError = onError,
|
||||||
|
context
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.actions
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
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.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size16dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InformationDialog(
|
||||||
|
title: String,
|
||||||
|
textContent: String,
|
||||||
|
buttonColors: ButtonColors = ButtonDefaults.buttonColors(),
|
||||||
|
onDismiss: () -> Unit
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = {
|
||||||
|
Text(title)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
SelectionContainer {
|
||||||
|
Text(textContent)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Button(onClick = onDismiss, colors = buttonColors, contentPadding = PaddingValues(horizontal = Size16dp)) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Done,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
Spacer(StdHorzSpacer)
|
||||||
|
Text(stringResource(R.string.error_dialog_button_ok))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -380,6 +380,9 @@ fun NewPostView(
|
|||||||
},
|
},
|
||||||
onClose = {
|
onClose = {
|
||||||
postViewModel.wantsInvoice = false
|
postViewModel.wantsInvoice = false
|
||||||
|
},
|
||||||
|
onError = { title, message ->
|
||||||
|
accountViewModel.toast(title, message)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -350,15 +350,10 @@ fun ServerConfig(
|
|||||||
Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
context.getString(R.string.unable_to_download_relay_document),
|
||||||
.makeText(
|
msg
|
||||||
context,
|
)
|
||||||
msg,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.actions
|
package com.vitorpamplona.amethyst.ui.actions
|
||||||
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -16,15 +15,14 @@ import androidx.compose.material3.Surface
|
|||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
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
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
@@ -34,7 +32,6 @@ import com.vitorpamplona.amethyst.model.RelayInformation
|
|||||||
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
||||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
data class RelayList(
|
data class RelayList(
|
||||||
val relay: Relay,
|
val relay: Relay,
|
||||||
@@ -55,7 +52,6 @@ fun RelaySelectionDialog(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
var relays by remember {
|
var relays by remember {
|
||||||
@@ -69,6 +65,13 @@ fun RelaySelectionDialog(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val hasSelectedRelay by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
relays.any { it.isSelected }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var relayInfo: RelayInfoDialog? by remember { mutableStateOf(null) }
|
var relayInfo: RelayInfoDialog? by remember { mutableStateOf(null) }
|
||||||
|
|
||||||
relayInfo?.let {
|
relayInfo?.let {
|
||||||
@@ -121,21 +124,15 @@ fun RelaySelectionDialog(
|
|||||||
SaveButton(
|
SaveButton(
|
||||||
onPost = {
|
onPost = {
|
||||||
val selectedRelays = relays.filter { it.isSelected }
|
val selectedRelays = relays.filter { it.isSelected }
|
||||||
if (selectedRelays.isEmpty()) {
|
|
||||||
scope.launch {
|
|
||||||
Toast.makeText(context, context.getString(R.string.select_a_relay_to_continue), Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
return@SaveButton
|
|
||||||
}
|
|
||||||
onPost(selectedRelays.map { it.relay })
|
onPost(selectedRelays.map { it.relay })
|
||||||
onClose()
|
onClose()
|
||||||
},
|
},
|
||||||
isActive = true
|
isActive = hasSelectedRelay
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
RelaySwitch(
|
RelaySwitch(
|
||||||
text = stringResource(R.string.select_deselect_all),
|
text = context.getString(R.string.select_deselect_all),
|
||||||
checked = selected,
|
checked = selected,
|
||||||
onClick = {
|
onClick = {
|
||||||
selected = !selected
|
selected = !selected
|
||||||
@@ -181,15 +178,10 @@ fun RelaySelectionDialog(
|
|||||||
Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
context.getString(R.string.unable_to_download_relay_document),
|
||||||
.makeText(
|
msg
|
||||||
context,
|
)
|
||||||
msg,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.vitorpamplona.amethyst.ui.components
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -133,26 +132,20 @@ fun CashuPreview(token: CashuToken, accountViewModel: AccountViewModel) {
|
|||||||
CashuProcessor().melt(
|
CashuProcessor().melt(
|
||||||
token,
|
token,
|
||||||
lud16,
|
lud16,
|
||||||
onSuccess = {
|
onSuccess = { title, message ->
|
||||||
scope.launch {
|
accountViewModel.toast(title, message)
|
||||||
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onError = {
|
onError = { title, message ->
|
||||||
scope.launch {
|
accountViewModel.toast(title, message)
|
||||||
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
|
},
|
||||||
}
|
context
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast.makeText(
|
context.getString(R.string.no_lightning_address_set),
|
||||||
context,
|
context.getString(R.string.user_x_does_not_have_a_lightning_address_setup_to_receive_sats, accountViewModel.account.userProfile().toBestDisplayName())
|
||||||
context.getString(R.string.no_lightning_address_set),
|
)
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
shape = QuoteBorder,
|
shape = QuoteBorder,
|
||||||
@@ -177,11 +170,7 @@ fun CashuPreview(token: CashuToken, accountViewModel: AccountViewModel) {
|
|||||||
startActivity(context, intent, null)
|
startActivity(context, intent, null)
|
||||||
} else {
|
} else {
|
||||||
// Copying the token to clipboard for now
|
// Copying the token to clipboard for now
|
||||||
var orignaltoken = token.token
|
clipboardManager.setText(AnnotatedString(token.token))
|
||||||
clipboardManager.setText(AnnotatedString("$orignaltoken"))
|
|
||||||
scope.launch {
|
|
||||||
Toast.makeText(context, context.getString(R.string.copied_token_to_clipboard), Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
shape = QuoteBorder,
|
shape = QuoteBorder,
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.components
|
package com.vitorpamplona.amethyst.ui.components
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.text.ClickableText
|
import androidx.compose.foundation.text.ClickableText
|
||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.LocalTextStyle
|
||||||
@@ -12,8 +9,9 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.style.TextDirection
|
import androidx.compose.ui.text.style.TextDirection
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.ErrorMessageDialog
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.payViaIntent
|
||||||
import com.vitorpamplona.quartz.encoders.LnWithdrawalUtil
|
import com.vitorpamplona.quartz.encoders.LnWithdrawalUtil
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -43,27 +41,26 @@ fun MayBeWithdrawal(lnurlWord: String) {
|
|||||||
@Composable
|
@Composable
|
||||||
fun ClickableWithdrawal(withdrawalString: String) {
|
fun ClickableWithdrawal(withdrawalString: String) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
val withdraw = remember(withdrawalString) {
|
val withdraw = remember(withdrawalString) {
|
||||||
AnnotatedString("$withdrawalString ")
|
AnnotatedString("$withdrawalString ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var showErrorMessageDialog by remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
|
if (showErrorMessageDialog != null) {
|
||||||
|
ErrorMessageDialog(
|
||||||
|
title = context.getString(R.string.error_dialog_pay_withdraw_error),
|
||||||
|
textContent = showErrorMessageDialog ?: "",
|
||||||
|
onDismiss = { showErrorMessageDialog = null }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
ClickableText(
|
ClickableText(
|
||||||
text = withdraw,
|
text = withdraw,
|
||||||
onClick = {
|
onClick = {
|
||||||
try {
|
payViaIntent(withdrawalString, context) {
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("lightning:$withdrawalString"))
|
showErrorMessageDialog = it
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
|
||||||
ContextCompat.startActivity(context, intent, null)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
scope.launch {
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
context.getString(R.string.lightning_wallets_not_found),
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.primary)
|
style = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.primary)
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.components
|
package com.vitorpamplona.amethyst.ui.components
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -23,8 +20,9 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.text.style.TextDirection
|
import androidx.compose.ui.text.style.TextDirection
|
||||||
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.core.content.ContextCompat.startActivity
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.ErrorMessageDialog
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.payViaIntent
|
||||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||||
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
|
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
|
||||||
import com.vitorpamplona.quartz.encoders.LnInvoiceUtil
|
import com.vitorpamplona.quartz.encoders.LnInvoiceUtil
|
||||||
@@ -67,7 +65,16 @@ fun MayBeInvoicePreview(lnbcWord: String) {
|
|||||||
@Composable
|
@Composable
|
||||||
fun InvoicePreview(lnInvoice: String, amount: String?) {
|
fun InvoicePreview(lnInvoice: String, amount: String?) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
var showErrorMessageDialog by remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
|
if (showErrorMessageDialog != null) {
|
||||||
|
ErrorMessageDialog(
|
||||||
|
title = context.getString(R.string.error_dialog_pay_invoice_error),
|
||||||
|
textContent = showErrorMessageDialog ?: "",
|
||||||
|
onDismiss = { showErrorMessageDialog = null }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -120,18 +127,8 @@ fun InvoicePreview(lnInvoice: String, amount: String?) {
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 10.dp),
|
.padding(vertical = 10.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
try {
|
payViaIntent(lnInvoice, context) {
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("lightning:$lnInvoice"))
|
showErrorMessageDialog = it
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
|
||||||
startActivity(context, intent, null)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
scope.launch {
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
context.getString(R.string.lightning_wallets_not_found),
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
shape = QuoteBorder,
|
shape = QuoteBorder,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.components
|
package com.vitorpamplona.amethyst.ui.components
|
||||||
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -50,7 +49,8 @@ fun InvoiceRequestCard(
|
|||||||
titleText: String? = null,
|
titleText: String? = null,
|
||||||
buttonText: String? = null,
|
buttonText: String? = null,
|
||||||
onSuccess: (String) -> Unit,
|
onSuccess: (String) -> Unit,
|
||||||
onClose: () -> Unit
|
onClose: () -> Unit,
|
||||||
|
onError: (String, String) -> Unit
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -64,7 +64,7 @@ fun InvoiceRequestCard(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(30.dp)
|
.padding(30.dp)
|
||||||
) {
|
) {
|
||||||
InvoiceRequest(lud16, toUserPubKeyHex, account, titleText, buttonText, onSuccess, onClose)
|
InvoiceRequest(lud16, toUserPubKeyHex, account, titleText, buttonText, onSuccess, onClose, onError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,8 @@ fun InvoiceRequest(
|
|||||||
titleText: String? = null,
|
titleText: String? = null,
|
||||||
buttonText: String? = null,
|
buttonText: String? = null,
|
||||||
onSuccess: (String) -> Unit,
|
onSuccess: (String) -> Unit,
|
||||||
onClose: () -> Unit
|
onClose: () -> Unit,
|
||||||
|
onError: (String, String) -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@@ -162,14 +163,10 @@ fun InvoiceRequest(
|
|||||||
message,
|
message,
|
||||||
zapRequest?.toJson(),
|
zapRequest?.toJson(),
|
||||||
onSuccess = onSuccess,
|
onSuccess = onSuccess,
|
||||||
onError = {
|
onError = onError,
|
||||||
scope.launch {
|
|
||||||
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
|
|
||||||
onClose()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onProgress = {
|
onProgress = {
|
||||||
}
|
},
|
||||||
|
context = context
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.content.ContextWrapper
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
@@ -49,7 +48,6 @@ import androidx.compose.runtime.MutableState
|
|||||||
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
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -78,6 +76,7 @@ import com.vitorpamplona.amethyst.model.ConnectivityType
|
|||||||
import com.vitorpamplona.amethyst.service.BlurHashRequester
|
import com.vitorpamplona.amethyst.service.BlurHashRequester
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.InformationDialog
|
||||||
import com.vitorpamplona.amethyst.ui.actions.LoadingAnimation
|
import com.vitorpamplona.amethyst.ui.actions.LoadingAnimation
|
||||||
import com.vitorpamplona.amethyst.ui.actions.SaveToGallery
|
import com.vitorpamplona.amethyst.ui.actions.SaveToGallery
|
||||||
import com.vitorpamplona.amethyst.ui.note.BlankNote
|
import com.vitorpamplona.amethyst.ui.note.BlankNote
|
||||||
@@ -808,7 +807,17 @@ private fun verifyHash(content: ZoomableUrlContent, context: Context): Boolean?
|
|||||||
@Composable
|
@Composable
|
||||||
private fun HashVerificationSymbol(verifiedHash: Boolean, modifier: Modifier) {
|
private fun HashVerificationSymbol(verifiedHash: Boolean, modifier: Modifier) {
|
||||||
val localContext = LocalContext.current
|
val localContext = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
val openDialogMsg = remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
|
openDialogMsg.value?.let {
|
||||||
|
InformationDialog(
|
||||||
|
title = localContext.getString(R.string.hash_verification_info_title),
|
||||||
|
textContent = it
|
||||||
|
) {
|
||||||
|
openDialogMsg.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier
|
modifier
|
||||||
@@ -819,13 +828,7 @@ private fun HashVerificationSymbol(verifiedHash: Boolean, modifier: Modifier) {
|
|||||||
if (verifiedHash) {
|
if (verifiedHash) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
openDialogMsg.value = localContext.getString(R.string.hash_verification_passed)
|
||||||
Toast.makeText(
|
|
||||||
localContext,
|
|
||||||
localContext.getString(R.string.hash_verification_passed),
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
HashCheckIcon(Size30dp)
|
HashCheckIcon(Size30dp)
|
||||||
@@ -833,13 +836,7 @@ private fun HashVerificationSymbol(verifiedHash: Boolean, modifier: Modifier) {
|
|||||||
} else {
|
} else {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
openDialogMsg.value = localContext.getString(R.string.hash_verification_failed)
|
||||||
Toast.makeText(
|
|
||||||
localContext,
|
|
||||||
localContext.getString(R.string.hash_verification_failed),
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
HashCheckFailedIcon(Size30dp)
|
HashCheckFailedIcon(Size30dp)
|
||||||
|
|||||||
@@ -566,7 +566,7 @@ fun ListContent(
|
|||||||
NewRelayListView({ wantsToEditRelays = false }, accountViewModel, nav = nav)
|
NewRelayListView({ wantsToEditRelays = false }, accountViewModel, nav = nav)
|
||||||
}
|
}
|
||||||
if (backupDialogOpen) {
|
if (backupDialogOpen) {
|
||||||
AccountBackupDialog(accountViewModel.account, onClose = { backupDialogOpen = false })
|
AccountBackupDialog(accountViewModel, onClose = { backupDialogOpen = false })
|
||||||
}
|
}
|
||||||
if (conectOrbotDialogOpen) {
|
if (conectOrbotDialogOpen) {
|
||||||
ConnectOrbotDialog(
|
ConnectOrbotDialog(
|
||||||
@@ -577,6 +577,12 @@ fun ListContent(
|
|||||||
checked = true
|
checked = true
|
||||||
enableTor(accountViewModel.account, true, proxyPort, context, coroutineScope)
|
enableTor(accountViewModel.account, true, proxyPort, context, coroutineScope)
|
||||||
},
|
},
|
||||||
|
onError = {
|
||||||
|
accountViewModel.toast(
|
||||||
|
context.getString(R.string.could_not_connect_to_tor),
|
||||||
|
it
|
||||||
|
)
|
||||||
|
},
|
||||||
proxyPort
|
proxyPort
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ import com.vitorpamplona.amethyst.model.ConnectivityType
|
|||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.RelayBriefInfo
|
import com.vitorpamplona.amethyst.model.RelayBriefInfo
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.OnlineChecker
|
|
||||||
import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil
|
import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
||||||
import com.vitorpamplona.amethyst.ui.actions.NewRelayListView
|
import com.vitorpamplona.amethyst.ui.actions.NewRelayListView
|
||||||
@@ -162,6 +161,7 @@ import com.vitorpamplona.amethyst.ui.theme.replyBackground
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
|
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
|
||||||
import com.vitorpamplona.quartz.encoders.ATag
|
import com.vitorpamplona.quartz.encoders.ATag
|
||||||
|
import com.vitorpamplona.quartz.encoders.HexKey
|
||||||
import com.vitorpamplona.quartz.encoders.toNpub
|
import com.vitorpamplona.quartz.encoders.toNpub
|
||||||
import com.vitorpamplona.quartz.events.AppDefinitionEvent
|
import com.vitorpamplona.quartz.events.AppDefinitionEvent
|
||||||
import com.vitorpamplona.quartz.events.AudioHeaderEvent
|
import com.vitorpamplona.quartz.events.AudioHeaderEvent
|
||||||
@@ -1286,8 +1286,8 @@ fun routeFor(note: Note, loggedIn: User): String? {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun routeToMessage(user: User, draftMessage: String?, accountViewModel: AccountViewModel): String {
|
fun routeToMessage(user: HexKey, draftMessage: String?, accountViewModel: AccountViewModel): String {
|
||||||
val withKey = ChatroomKey(persistentSetOf(user.pubkeyHex))
|
val withKey = ChatroomKey(persistentSetOf(user))
|
||||||
accountViewModel.account.userProfile().createChatroom(withKey)
|
accountViewModel.account.userProfile().createChatroom(withKey)
|
||||||
return if (draftMessage != null) {
|
return if (draftMessage != null) {
|
||||||
"Room/${withKey.hashCode()}?message=$draftMessage"
|
"Room/${withKey.hashCode()}?message=$draftMessage"
|
||||||
@@ -1296,6 +1296,10 @@ fun routeToMessage(user: User, draftMessage: String?, accountViewModel: AccountV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun routeToMessage(user: User, draftMessage: String?, accountViewModel: AccountViewModel): String {
|
||||||
|
return routeToMessage(user.pubkeyHex, draftMessage, accountViewModel)
|
||||||
|
}
|
||||||
|
|
||||||
fun routeFor(note: Channel): String {
|
fun routeFor(note: Channel): String {
|
||||||
return "Channel/${note.idHex}"
|
return "Channel/${note.idHex}"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,20 +291,16 @@ private fun RenderMainPopup(
|
|||||||
Icons.Default.PersonRemove,
|
Icons.Default.PersonRemove,
|
||||||
stringResource(R.string.quick_action_unfollow)
|
stringResource(R.string.quick_action_unfollow)
|
||||||
) {
|
) {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.unfollow(note.author!!)
|
||||||
accountViewModel.unfollow(note.author!!)
|
onDismiss()
|
||||||
onDismiss()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
NoteQuickActionItem(
|
NoteQuickActionItem(
|
||||||
Icons.Default.PersonAdd,
|
Icons.Default.PersonAdd,
|
||||||
stringResource(R.string.quick_action_follow)
|
stringResource(R.string.quick_action_follow)
|
||||||
) {
|
) {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.follow(note.author!!)
|
||||||
accountViewModel.follow(note.author!!)
|
onDismiss()
|
||||||
onDismiss()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.note
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Bolt
|
import androidx.compose.material.icons.filled.Bolt
|
||||||
import androidx.compose.material.icons.outlined.Bolt
|
import androidx.compose.material.icons.outlined.Bolt
|
||||||
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
@@ -27,6 +28,7 @@ import com.vitorpamplona.amethyst.model.Note
|
|||||||
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.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.StringToastMsg
|
||||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
||||||
@@ -294,7 +296,7 @@ fun ZapVote(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var zappingProgress by remember { mutableStateOf(0f) }
|
var zappingProgress by remember { mutableStateOf(0f) }
|
||||||
var showErrorMessageDialog by remember { mutableStateOf<String?>(null) }
|
var showErrorMessageDialog by remember { mutableStateOf<StringToastMsg?>(null) }
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@@ -305,50 +307,30 @@ fun ZapVote(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.combinedClickable(
|
modifier = Modifier.combinedClickable(
|
||||||
role = Role.Button,
|
role = Role.Button,
|
||||||
// interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
// indication = rememberRipple(bounded = false, radius = 24.dp),
|
indication = rememberRipple(bounded = false, radius = 24.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
if (!accountViewModel.isWriteable() && !accountViewModel.loggedInWithExternalSigner()) {
|
if (!accountViewModel.isWriteable() && !accountViewModel.loggedInWithExternalSigner()) {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
R.string.read_only_user,
|
||||||
.makeText(
|
R.string.login_with_a_private_key_to_be_able_to_send_zaps
|
||||||
context,
|
)
|
||||||
context.getString(R.string.login_with_a_private_key_to_be_able_to_send_zaps),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
} else if (pollViewModel.isPollClosed()) {
|
} else if (pollViewModel.isPollClosed()) {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
R.string.poll_unable_to_vote,
|
||||||
.makeText(
|
R.string.poll_is_closed_explainer
|
||||||
context,
|
)
|
||||||
context.getString(R.string.poll_is_closed),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
} else if (isLoggedUser) {
|
} else if (isLoggedUser) {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
R.string.poll_unable_to_vote,
|
||||||
.makeText(
|
R.string.poll_author_no_vote
|
||||||
context,
|
)
|
||||||
context.getString(R.string.poll_author_no_vote),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
} else if (pollViewModel.isVoteAmountAtomic() && poolOption.zappedByLoggedIn) {
|
} else if (pollViewModel.isVoteAmountAtomic() && poolOption.zappedByLoggedIn) {
|
||||||
// only allow one vote per option when min==max, i.e. atomic vote amount specified
|
// only allow one vote per option when min==max, i.e. atomic vote amount specified
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
R.string.poll_unable_to_vote,
|
||||||
.makeText(
|
R.string.one_vote_per_user_on_atomic_votes
|
||||||
context,
|
)
|
||||||
R.string.one_vote_per_user_on_atomic_votes,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
return@combinedClickable
|
return@combinedClickable
|
||||||
} else if (accountViewModel.account.zapAmountChoices.size == 1 &&
|
} else if (accountViewModel.account.zapAmountChoices.size == 1 &&
|
||||||
pollViewModel.isValidInputVoteAmount(accountViewModel.account.zapAmountChoices.first())
|
pollViewModel.isValidInputVoteAmount(accountViewModel.account.zapAmountChoices.first())
|
||||||
@@ -359,13 +341,9 @@ fun ZapVote(
|
|||||||
poolOption.option,
|
poolOption.option,
|
||||||
"",
|
"",
|
||||||
context,
|
context,
|
||||||
onError = {
|
onError = { title, message ->
|
||||||
scope.launch {
|
zappingProgress = 0f
|
||||||
zappingProgress = 0f
|
showErrorMessageDialog = StringToastMsg(title, message)
|
||||||
Toast
|
|
||||||
.makeText(context, it, Toast.LENGTH_SHORT)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onProgress = {
|
onProgress = {
|
||||||
scope.launch(Dispatchers.Main) {
|
scope.launch(Dispatchers.Main) {
|
||||||
@@ -395,11 +373,9 @@ fun ZapVote(
|
|||||||
onChangeAmount = {
|
onChangeAmount = {
|
||||||
wantsToZap = false
|
wantsToZap = false
|
||||||
},
|
},
|
||||||
onError = {
|
onError = { title, message ->
|
||||||
scope.launch {
|
showErrorMessageDialog = StringToastMsg(title, message)
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
showErrorMessageDialog = it
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onProgress = {
|
onProgress = {
|
||||||
scope.launch(Dispatchers.Main) {
|
scope.launch(Dispatchers.Main) {
|
||||||
@@ -423,19 +399,22 @@ fun ZapVote(
|
|||||||
wantsToPay = persistentListOf()
|
wantsToPay = persistentListOf()
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
showErrorMessageDialog = it
|
showErrorMessageDialog = StringToastMsg(
|
||||||
|
context.getString(R.string.error_dialog_zap_error),
|
||||||
|
it
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showErrorMessageDialog != null) {
|
showErrorMessageDialog?.let { toast ->
|
||||||
ErrorMessageDialog(
|
ErrorMessageDialog(
|
||||||
title = stringResource(id = R.string.error_dialog_zap_error),
|
title = toast.title,
|
||||||
textContent = showErrorMessageDialog ?: "",
|
textContent = toast.msg,
|
||||||
onClickStartMessage = {
|
onClickStartMessage = {
|
||||||
baseNote.author?.let {
|
baseNote.author?.let {
|
||||||
nav(routeToMessage(it, showErrorMessageDialog, accountViewModel))
|
nav(routeToMessage(it, toast.msg, accountViewModel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDismiss = { showErrorMessageDialog = null }
|
onDismiss = { showErrorMessageDialog = null }
|
||||||
@@ -494,7 +473,7 @@ fun FilteredZapAmountChoicePopup(
|
|||||||
pollOption: Int,
|
pollOption: Int,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onChangeAmount: () -> Unit,
|
onChangeAmount: () -> Unit,
|
||||||
onError: (text: String) -> Unit,
|
onError: (title: String, text: String) -> Unit,
|
||||||
onProgress: (percent: Float) -> Unit,
|
onProgress: (percent: Float) -> Unit,
|
||||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit
|
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.vitorpamplona.amethyst.ui.note
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||||
import androidx.compose.animation.ContentTransform
|
import androidx.compose.animation.ContentTransform
|
||||||
@@ -110,7 +109,6 @@ import kotlinx.collections.immutable.ImmutableList
|
|||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableMap
|
import kotlinx.collections.immutable.toImmutableMap
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
@@ -540,9 +538,6 @@ fun ReplyReaction(
|
|||||||
iconSize: Dp = Size17dp,
|
iconSize: Dp = Size17dp,
|
||||||
onPress: () -> Unit
|
onPress: () -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = remember {
|
modifier = remember {
|
||||||
Modifier.size(iconSize)
|
Modifier.size(iconSize)
|
||||||
@@ -554,13 +549,10 @@ fun ReplyReaction(
|
|||||||
if (accountViewModel.loggedInWithExternalSigner()) {
|
if (accountViewModel.loggedInWithExternalSigner()) {
|
||||||
onPress()
|
onPress()
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast.makeText(
|
R.string.read_only_user,
|
||||||
context,
|
R.string.login_with_a_private_key_to_be_able_to_reply
|
||||||
context.getString(R.string.login_with_a_private_key_to_be_able_to_reply),
|
)
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -643,9 +635,6 @@ fun BoostReaction(
|
|||||||
iconSize: Dp = 20.dp,
|
iconSize: Dp = 20.dp,
|
||||||
onQuotePress: () -> Unit
|
onQuotePress: () -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
var wantsToBoost by remember { mutableStateOf(false) }
|
var wantsToBoost by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val iconButtonModifier = remember {
|
val iconButtonModifier = remember {
|
||||||
@@ -657,29 +646,22 @@ fun BoostReaction(
|
|||||||
onClick = {
|
onClick = {
|
||||||
if (accountViewModel.isWriteable()) {
|
if (accountViewModel.isWriteable()) {
|
||||||
if (accountViewModel.hasBoosted(baseNote)) {
|
if (accountViewModel.hasBoosted(baseNote)) {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.deleteBoostsTo(baseNote)
|
||||||
accountViewModel.deleteBoostsTo(baseNote)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
wantsToBoost = true
|
wantsToBoost = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (accountViewModel.loggedInWithExternalSigner()) {
|
if (accountViewModel.loggedInWithExternalSigner()) {
|
||||||
if (accountViewModel.hasBoosted(baseNote)) {
|
if (accountViewModel.hasBoosted(baseNote)) {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.deleteBoostsTo(baseNote)
|
||||||
accountViewModel.deleteBoostsTo(baseNote)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
wantsToBoost = true
|
wantsToBoost = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast.makeText(
|
R.string.read_only_user,
|
||||||
context,
|
R.string.login_with_a_private_key_to_be_able_to_boost_posts
|
||||||
context.getString(R.string.login_with_a_private_key_to_be_able_to_boost_posts),
|
)
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -699,9 +681,7 @@ fun BoostReaction(
|
|||||||
onQuotePress()
|
onQuotePress()
|
||||||
},
|
},
|
||||||
onRepost = {
|
onRepost = {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.boost(baseNote)
|
||||||
accountViewModel.boost(baseNote)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -741,9 +721,6 @@ fun LikeReaction(
|
|||||||
heartSize: Dp = 16.dp,
|
heartSize: Dp = 16.dp,
|
||||||
iconFontSize: TextUnit = Font14SP
|
iconFontSize: TextUnit = Font14SP
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
val iconButtonModifier = remember {
|
val iconButtonModifier = remember {
|
||||||
Modifier.size(iconSize)
|
Modifier.size(iconSize)
|
||||||
}
|
}
|
||||||
@@ -761,21 +738,12 @@ fun LikeReaction(
|
|||||||
likeClick(
|
likeClick(
|
||||||
baseNote,
|
baseNote,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
scope,
|
|
||||||
context,
|
|
||||||
onMultipleChoices = {
|
onMultipleChoices = {
|
||||||
wantsToReact = true
|
wantsToReact = true
|
||||||
},
|
},
|
||||||
onWantsToSignReaction = {
|
onWantsToSignReaction = {
|
||||||
if (accountViewModel.account.reactionChoices.size == 1) {
|
if (accountViewModel.account.reactionChoices.size == 1) {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.reactToOrDelete(baseNote)
|
||||||
val reaction = accountViewModel.account.reactionChoices.first()
|
|
||||||
if (accountViewModel.hasReactedTo(baseNote, reaction)) {
|
|
||||||
accountViewModel.deleteReactionTo(baseNote, reaction)
|
|
||||||
} else {
|
|
||||||
accountViewModel.reactTo(baseNote, reaction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (accountViewModel.account.reactionChoices.size > 1) {
|
} else if (accountViewModel.account.reactionChoices.size > 1) {
|
||||||
wantsToReact = true
|
wantsToReact = true
|
||||||
}
|
}
|
||||||
@@ -896,44 +864,25 @@ fun LikeText(baseNote: Note, grayTint: Color) {
|
|||||||
private fun likeClick(
|
private fun likeClick(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
scope: CoroutineScope,
|
|
||||||
context: Context,
|
|
||||||
onMultipleChoices: () -> Unit,
|
onMultipleChoices: () -> Unit,
|
||||||
onWantsToSignReaction: () -> Unit
|
onWantsToSignReaction: () -> Unit
|
||||||
) {
|
) {
|
||||||
if (accountViewModel.account.reactionChoices.isEmpty()) {
|
if (accountViewModel.account.reactionChoices.isEmpty()) {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
R.string.no_reactions_setup,
|
||||||
.makeText(
|
R.string.no_reaction_type_setup_long_press_to_change
|
||||||
context,
|
)
|
||||||
context.getString(R.string.no_reaction_type_setup_long_press_to_change),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
} else if (!accountViewModel.isWriteable()) {
|
} else if (!accountViewModel.isWriteable()) {
|
||||||
if (accountViewModel.loggedInWithExternalSigner()) {
|
if (accountViewModel.loggedInWithExternalSigner()) {
|
||||||
onWantsToSignReaction()
|
onWantsToSignReaction()
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
R.string.read_only_user,
|
||||||
.makeText(
|
R.string.login_with_a_private_key_to_like_posts
|
||||||
context,
|
)
|
||||||
context.getString(R.string.login_with_a_private_key_to_like_posts),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (accountViewModel.account.reactionChoices.size == 1) {
|
} else if (accountViewModel.account.reactionChoices.size == 1) {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.reactToOrDelete(baseNote)
|
||||||
val reaction = accountViewModel.account.reactionChoices.first()
|
|
||||||
if (accountViewModel.hasReactedTo(baseNote, reaction)) {
|
|
||||||
accountViewModel.deleteReactionTo(baseNote, reaction)
|
|
||||||
} else {
|
|
||||||
accountViewModel.reactTo(baseNote, reaction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (accountViewModel.account.reactionChoices.size > 1) {
|
} else if (accountViewModel.account.reactionChoices.size > 1) {
|
||||||
onMultipleChoices()
|
onMultipleChoices()
|
||||||
}
|
}
|
||||||
@@ -976,18 +925,19 @@ fun ZapReaction(
|
|||||||
zapClick(
|
zapClick(
|
||||||
baseNote,
|
baseNote,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
scope,
|
|
||||||
context,
|
context,
|
||||||
onZappingProgress = { progress: Float ->
|
onZappingProgress = { progress: Float ->
|
||||||
zappingProgress = progress
|
scope.launch {
|
||||||
|
zappingProgress = progress
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onMultipleChoices = {
|
onMultipleChoices = {
|
||||||
wantsToZap = true
|
wantsToZap = true
|
||||||
},
|
},
|
||||||
onError = {
|
onError = { title, message ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
showErrorMessageDialog = it
|
showErrorMessageDialog = message
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPayViaIntent = {
|
onPayViaIntent = {
|
||||||
@@ -1015,10 +965,10 @@ fun ZapReaction(
|
|||||||
wantsToZap = false
|
wantsToZap = false
|
||||||
wantsToChangeZapAmount = true
|
wantsToChangeZapAmount = true
|
||||||
},
|
},
|
||||||
onError = {
|
onError = { title, message ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
showErrorMessageDialog = it
|
showErrorMessageDialog = message
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onProgress = {
|
onProgress = {
|
||||||
@@ -1075,10 +1025,10 @@ fun ZapReaction(
|
|||||||
if (wantsToSetCustomZap) {
|
if (wantsToSetCustomZap) {
|
||||||
ZapCustomDialog(
|
ZapCustomDialog(
|
||||||
onClose = { wantsToSetCustomZap = false },
|
onClose = { wantsToSetCustomZap = false },
|
||||||
onError = {
|
onError = { title, message ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
showErrorMessageDialog = it
|
showErrorMessageDialog = message
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onProgress = {
|
onProgress = {
|
||||||
@@ -1121,33 +1071,22 @@ fun ZapReaction(
|
|||||||
private fun zapClick(
|
private fun zapClick(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
scope: CoroutineScope,
|
|
||||||
context: Context,
|
context: Context,
|
||||||
onZappingProgress: (Float) -> Unit,
|
onZappingProgress: (Float) -> Unit,
|
||||||
onMultipleChoices: () -> Unit,
|
onMultipleChoices: () -> Unit,
|
||||||
onError: (String) -> Unit,
|
onError: (String, String) -> Unit,
|
||||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit
|
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit
|
||||||
) {
|
) {
|
||||||
if (accountViewModel.account.zapAmountChoices.isEmpty()) {
|
if (accountViewModel.account.zapAmountChoices.isEmpty()) {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
context.getString(R.string.error_dialog_zap_error),
|
||||||
.makeText(
|
context.getString(R.string.no_zap_amount_setup_long_press_to_change)
|
||||||
context,
|
)
|
||||||
context.getString(R.string.no_zap_amount_setup_long_press_to_change),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
} else if (!accountViewModel.isWriteable() && !accountViewModel.loggedInWithExternalSigner()) {
|
} else if (!accountViewModel.isWriteable() && !accountViewModel.loggedInWithExternalSigner()) {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
context.getString(R.string.error_dialog_zap_error),
|
||||||
.makeText(
|
context.getString(R.string.login_with_a_private_key_to_be_able_to_send_zaps)
|
||||||
context,
|
)
|
||||||
context.getString(R.string.login_with_a_private_key_to_be_able_to_send_zaps),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
} else if (accountViewModel.account.zapAmountChoices.size == 1) {
|
} else if (accountViewModel.account.zapAmountChoices.size == 1) {
|
||||||
accountViewModel.zap(
|
accountViewModel.zap(
|
||||||
baseNote,
|
baseNote,
|
||||||
@@ -1157,9 +1096,7 @@ private fun zapClick(
|
|||||||
context,
|
context,
|
||||||
onError = onError,
|
onError = onError,
|
||||||
onProgress = {
|
onProgress = {
|
||||||
scope.launch(Dispatchers.Main) {
|
onZappingProgress(it)
|
||||||
onZappingProgress(it)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
zapType = accountViewModel.account.defaultZapType,
|
zapType = accountViewModel.account.defaultZapType,
|
||||||
onPayViaIntent = onPayViaIntent
|
onPayViaIntent = onPayViaIntent
|
||||||
@@ -1289,15 +1226,12 @@ private fun BoostTypeChoicePopup(baseNote: Note, iconSize: Dp, accountViewModel:
|
|||||||
onDismissRequest = { onDismiss() }
|
onDismissRequest = { onDismiss() }
|
||||||
) {
|
) {
|
||||||
FlowRow {
|
FlowRow {
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.padding(horizontal = 3.dp),
|
modifier = Modifier.padding(horizontal = 3.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
if (accountViewModel.isWriteable()) {
|
if (accountViewModel.isWriteable()) {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.boost(baseNote)
|
||||||
accountViewModel.boost(baseNote)
|
onDismiss()
|
||||||
onDismiss()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
onRepost()
|
onRepost()
|
||||||
onDismiss()
|
onDismiss()
|
||||||
@@ -1470,12 +1404,11 @@ fun ZapAmountChoicePopup(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onChangeAmount: () -> Unit,
|
onChangeAmount: () -> Unit,
|
||||||
onError: (text: String) -> Unit,
|
onError: (title: String, text: String) -> Unit,
|
||||||
onProgress: (percent: Float) -> Unit,
|
onProgress: (percent: Float) -> Unit,
|
||||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit
|
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
val account = accountState?.account ?: return
|
val account = accountState?.account ?: return
|
||||||
val zapMessage = ""
|
val zapMessage = ""
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.note
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
@@ -49,7 +48,6 @@ import com.vitorpamplona.amethyst.ui.theme.Size15dp
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.StdStartPadding
|
import com.vitorpamplona.amethyst.ui.theme.StdStartPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
public fun RelayBadgesHorizontal(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
public fun RelayBadgesHorizontal(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
@@ -159,15 +157,10 @@ fun RenderRelay(relay: RelayBriefInfo, accountViewModel: AccountViewModel, nav:
|
|||||||
Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS -> context.getString(R.string.relay_information_document_error_assemble_url, url, exceptionMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
context.getString(R.string.unable_to_download_relay_document),
|
||||||
.makeText(
|
msg
|
||||||
context,
|
)
|
||||||
msg,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.app.KeyguardManager
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.ActivityResult
|
import androidx.activity.result.ActivityResult
|
||||||
@@ -82,7 +81,6 @@ import com.vitorpamplona.quartz.encoders.decodePublicKey
|
|||||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||||
import com.vitorpamplona.quartz.events.LnZapEvent
|
import com.vitorpamplona.quartz.events.LnZapEvent
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import androidx.compose.runtime.rememberCoroutineScope as rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope as rememberCoroutineScope
|
||||||
|
|
||||||
@@ -228,9 +226,16 @@ fun UpdateZapAmountDialog(
|
|||||||
try {
|
try {
|
||||||
postViewModel.updateNIP47(nip47uri)
|
postViewModel.updateNIP47(nip47uri)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
scope.launch {
|
if (e.message != null) {
|
||||||
Toast.makeText(context, e.message, Toast.LENGTH_SHORT)
|
accountViewModel.toast(
|
||||||
.show()
|
context.getString(R.string.error_parsing_nip47_title),
|
||||||
|
context.getString(R.string.error_parsing_nip47, nip47uri, e.message!!)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
accountViewModel.toast(
|
||||||
|
context.getString(R.string.error_parsing_nip47_title),
|
||||||
|
context.getString(R.string.error_parsing_nip47_no_error, nip47uri)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -444,9 +449,16 @@ fun UpdateZapAmountDialog(
|
|||||||
try {
|
try {
|
||||||
postViewModel.updateNIP47(it)
|
postViewModel.updateNIP47(it)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
scope.launch {
|
if (e.message != null) {
|
||||||
Toast.makeText(context, e.message, Toast.LENGTH_SHORT)
|
accountViewModel.toast(
|
||||||
.show()
|
context.getString(R.string.error_parsing_nip47_title),
|
||||||
|
context.getString(R.string.error_parsing_nip47, it, e.message!!)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
accountViewModel.toast(
|
||||||
|
context.getString(R.string.error_parsing_nip47_title),
|
||||||
|
context.getString(R.string.error_parsing_nip47_no_error, it)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -505,7 +517,6 @@ fun UpdateZapAmountDialog(
|
|||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val keyguardLauncher =
|
val keyguardLauncher =
|
||||||
@@ -544,13 +555,16 @@ fun UpdateZapAmountDialog(
|
|||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
if (!showPassword) {
|
if (!showPassword) {
|
||||||
authenticate(
|
authenticate(
|
||||||
authTitle,
|
title = authTitle,
|
||||||
context,
|
context = context,
|
||||||
scope,
|
keyguardLauncher = keyguardLauncher,
|
||||||
keyguardLauncher
|
onApproved = {
|
||||||
) {
|
showPassword = true
|
||||||
showPassword = true
|
},
|
||||||
}
|
onError = { title, message ->
|
||||||
|
accountViewModel.toast(title, message)
|
||||||
|
}
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
showPassword = false
|
showPassword = false
|
||||||
}
|
}
|
||||||
@@ -580,9 +594,9 @@ fun UpdateZapAmountDialog(
|
|||||||
fun authenticate(
|
fun authenticate(
|
||||||
title: String,
|
title: String,
|
||||||
context: Context,
|
context: Context,
|
||||||
scope: CoroutineScope,
|
|
||||||
keyguardLauncher: ManagedActivityResultLauncher<Intent, ActivityResult>,
|
keyguardLauncher: ManagedActivityResultLauncher<Intent, ActivityResult>,
|
||||||
onApproved: () -> Unit
|
onApproved: () -> Unit,
|
||||||
|
onError: (String, String) -> Unit
|
||||||
) {
|
) {
|
||||||
val fragmentContext = context.getFragmentActivity()!!
|
val fragmentContext = context.getFragmentActivity()!!
|
||||||
val keyguardManager =
|
val keyguardManager =
|
||||||
@@ -626,26 +640,19 @@ fun authenticate(
|
|||||||
when (errorCode) {
|
when (errorCode) {
|
||||||
BiometricPrompt.ERROR_NEGATIVE_BUTTON -> keyguardPrompt()
|
BiometricPrompt.ERROR_NEGATIVE_BUTTON -> keyguardPrompt()
|
||||||
BiometricPrompt.ERROR_LOCKOUT -> keyguardPrompt()
|
BiometricPrompt.ERROR_LOCKOUT -> keyguardPrompt()
|
||||||
else ->
|
else -> onError(
|
||||||
scope.launch {
|
context.getString(R.string.biometric_authentication_failed),
|
||||||
Toast.makeText(
|
context.getString(R.string.biometric_authentication_failed_explainer_with_error, errString)
|
||||||
context,
|
)
|
||||||
"${context.getString(R.string.biometric_error)}: $errString",
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAuthenticationFailed() {
|
override fun onAuthenticationFailed() {
|
||||||
super.onAuthenticationFailed()
|
super.onAuthenticationFailed()
|
||||||
scope.launch {
|
onError(
|
||||||
Toast.makeText(
|
context.getString(R.string.biometric_authentication_failed),
|
||||||
context,
|
context.getString(R.string.biometric_authentication_failed_explainer)
|
||||||
context.getString(R.string.biometric_authentication_failed),
|
)
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||||
|
|||||||
@@ -435,10 +435,8 @@ fun NoteDropDownMenu(note: Note, popupExpanded: MutableState<Boolean>, accountVi
|
|||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
val author = note.author ?: return@DropdownMenuItem
|
val author = note.author ?: return@DropdownMenuItem
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.follow(author)
|
||||||
accountViewModel.follow(author)
|
onDismiss()
|
||||||
onDismiss()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Divider()
|
Divider()
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ class ZapOptionstViewModel : ViewModel() {
|
|||||||
@Composable
|
@Composable
|
||||||
fun ZapCustomDialog(
|
fun ZapCustomDialog(
|
||||||
onClose: () -> Unit,
|
onClose: () -> Unit,
|
||||||
onError: (text: String) -> Unit,
|
onError: (title: String, text: String) -> Unit,
|
||||||
onProgress: (percent: Float) -> Unit,
|
onProgress: (percent: Float) -> Unit,
|
||||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
@@ -254,7 +254,7 @@ fun ErrorMessageDialog(
|
|||||||
title: String,
|
title: String,
|
||||||
textContent: String,
|
textContent: String,
|
||||||
buttonColors: ButtonColors = ButtonDefaults.buttonColors(),
|
buttonColors: ButtonColors = ButtonDefaults.buttonColors(),
|
||||||
onClickStartMessage: () -> Unit,
|
onClickStartMessage: (() -> Unit)? = null,
|
||||||
onDismiss: () -> Unit
|
onDismiss: () -> Unit
|
||||||
) {
|
) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
@@ -274,13 +274,15 @@ fun ErrorMessageDialog(
|
|||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
) {
|
) {
|
||||||
TextButton(onClick = onClickStartMessage) {
|
onClickStartMessage?.let {
|
||||||
Icon(
|
TextButton(onClick = onClickStartMessage) {
|
||||||
painter = painterResource(R.drawable.ic_dm),
|
Icon(
|
||||||
contentDescription = null
|
painter = painterResource(R.drawable.ic_dm),
|
||||||
)
|
contentDescription = null
|
||||||
Spacer(StdHorzSpacer)
|
)
|
||||||
Text(stringResource(R.string.error_dialog_talk_to_user))
|
Spacer(StdHorzSpacer)
|
||||||
|
Text(stringResource(R.string.error_dialog_talk_to_user))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Button(onClick = onDismiss, colors = buttonColors, contentPadding = PaddingValues(horizontal = Size16dp)) {
|
Button(onClick = onDismiss, colors = buttonColors, contentPadding = PaddingValues(horizontal = Size16dp)) {
|
||||||
Row(
|
Row(
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.note
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -20,7 +19,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -182,9 +180,6 @@ fun ShowFollowingOrUnfollowingButton(
|
|||||||
baseAuthor: User,
|
baseAuthor: User,
|
||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
var isFollowing by remember { mutableStateOf(false) }
|
var isFollowing by remember { mutableStateOf(false) }
|
||||||
val accountFollowsState by accountViewModel.account.userProfile().live().follows.observeAsState()
|
val accountFollowsState by accountViewModel.account.userProfile().live().follows.observeAsState()
|
||||||
|
|
||||||
@@ -203,48 +198,30 @@ fun ShowFollowingOrUnfollowingButton(
|
|||||||
UnfollowButton {
|
UnfollowButton {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
if (accountViewModel.loggedInWithExternalSigner()) {
|
if (accountViewModel.loggedInWithExternalSigner()) {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.unfollow(baseAuthor)
|
||||||
accountViewModel.unfollow(baseAuthor)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
R.string.read_only_user,
|
||||||
.makeText(
|
R.string.login_with_a_private_key_to_be_able_to_unfollow
|
||||||
context,
|
)
|
||||||
context.getString(R.string.login_with_a_private_key_to_be_able_to_unfollow),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.unfollow(baseAuthor)
|
||||||
accountViewModel.unfollow(baseAuthor)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
FollowButton {
|
FollowButton {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
if (accountViewModel.loggedInWithExternalSigner()) {
|
if (accountViewModel.loggedInWithExternalSigner()) {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.follow(baseAuthor)
|
||||||
accountViewModel.account.follow(baseAuthor)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
R.string.read_only_user,
|
||||||
.makeText(
|
R.string.login_with_a_private_key_to_be_able_to_follow
|
||||||
context,
|
)
|
||||||
context.getString(R.string.login_with_a_private_key_to_be_able_to_follow),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.follow(baseAuthor)
|
||||||
accountViewModel.follow(baseAuthor)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-9
@@ -52,7 +52,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AccountBackupDialog(account: Account, onClose: () -> Unit) {
|
fun AccountBackupDialog(accountViewModel: AccountViewModel, onClose: () -> Unit) {
|
||||||
Dialog(
|
Dialog(
|
||||||
onDismissRequest = onClose,
|
onDismissRequest = onClose,
|
||||||
properties = DialogProperties(usePlatformDefaultWidth = false)
|
properties = DialogProperties(usePlatformDefaultWidth = false)
|
||||||
@@ -90,7 +90,7 @@ fun AccountBackupDialog(account: Account, onClose: () -> Unit) {
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(30.dp))
|
Spacer(modifier = Modifier.height(30.dp))
|
||||||
|
|
||||||
NSecCopyButton(account)
|
NSecCopyButton(accountViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +99,7 @@ fun AccountBackupDialog(account: Account, onClose: () -> Unit) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun NSecCopyButton(
|
private fun NSecCopyButton(
|
||||||
account: Account
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
val clipboardManager = LocalClipboardManager.current
|
val clipboardManager = LocalClipboardManager.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -108,7 +108,7 @@ private fun NSecCopyButton(
|
|||||||
val keyguardLauncher =
|
val keyguardLauncher =
|
||||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
|
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
|
||||||
if (result.resultCode == Activity.RESULT_OK) {
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
copyNSec(context, scope, account, clipboardManager)
|
copyNSec(context, scope, accountViewModel.account, clipboardManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,11 +118,14 @@ private fun NSecCopyButton(
|
|||||||
authenticate(
|
authenticate(
|
||||||
title = context.getString(R.string.copy_my_secret_key),
|
title = context.getString(R.string.copy_my_secret_key),
|
||||||
context = context,
|
context = context,
|
||||||
scope = scope,
|
keyguardLauncher = keyguardLauncher,
|
||||||
keyguardLauncher = keyguardLauncher
|
onApproved = {
|
||||||
) {
|
copyNSec(context, scope, accountViewModel.account, clipboardManager)
|
||||||
copyNSec(context, scope, account, clipboardManager)
|
},
|
||||||
}
|
onError = { title, message ->
|
||||||
|
accountViewModel.toast(title, message)
|
||||||
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
shape = ButtonBorder,
|
shape = ButtonBorder,
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
|||||||
+90
-5
@@ -60,12 +60,22 @@ import kotlinx.collections.immutable.toImmutableList
|
|||||||
import kotlinx.collections.immutable.toImmutableSet
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.time.measureTimedValue
|
import kotlin.time.measureTimedValue
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
open class ToastMsg()
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
class StringToastMsg(val title: String, val msg: String) : ToastMsg()
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
class ResourceToastMsg(val titleResId: Int, val resourceId: Int) : ToastMsg()
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class AccountViewModel(val account: Account) : ViewModel(), Dao {
|
class AccountViewModel(val account: Account) : ViewModel(), Dao {
|
||||||
val accountLiveData: LiveData<AccountState> = account.live.map { it }
|
val accountLiveData: LiveData<AccountState> = account.live.map { it }
|
||||||
@@ -75,6 +85,8 @@ class AccountViewModel(val account: Account) : ViewModel(), Dao {
|
|||||||
val userFollows: LiveData<UserState> = account.userProfile().live().follows.map { it }
|
val userFollows: LiveData<UserState> = account.userProfile().live().follows.map { it }
|
||||||
val userRelays: LiveData<UserState> = account.userProfile().live().relays.map { it }
|
val userRelays: LiveData<UserState> = account.userProfile().live().relays.map { it }
|
||||||
|
|
||||||
|
val toasts = MutableSharedFlow<ToastMsg?>()
|
||||||
|
|
||||||
val discoveryListLiveData = account.live.map {
|
val discoveryListLiveData = account.live.map {
|
||||||
it.account.defaultDiscoveryFollowList
|
it.account.defaultDiscoveryFollowList
|
||||||
}.distinctUntilChanged()
|
}.distinctUntilChanged()
|
||||||
@@ -95,6 +107,24 @@ class AccountViewModel(val account: Account) : ViewModel(), Dao {
|
|||||||
it.account.showSensitiveContent
|
it.account.showSensitiveContent
|
||||||
}.distinctUntilChanged()
|
}.distinctUntilChanged()
|
||||||
|
|
||||||
|
fun clearToasts() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
toasts.emit(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toast(title: String, message: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
toasts.emit(StringToastMsg(title, message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toast(titleResId: Int, resourceId: Int) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
toasts.emit(ResourceToastMsg(titleResId, resourceId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun updateAutomaticallyStartPlayback(
|
fun updateAutomaticallyStartPlayback(
|
||||||
automaticallyStartPlayback: ConnectivityType
|
automaticallyStartPlayback: ConnectivityType
|
||||||
) {
|
) {
|
||||||
@@ -152,6 +182,17 @@ class AccountViewModel(val account: Account) : ViewModel(), Dao {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun reactToOrDelete(note: Note) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val reaction = account.reactionChoices.first()
|
||||||
|
if (hasReactedTo(note, reaction)) {
|
||||||
|
deleteReactionTo(note, reaction)
|
||||||
|
} else {
|
||||||
|
reactTo(note, reaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun isNoteHidden(note: Note): Boolean {
|
fun isNoteHidden(note: Note): Boolean {
|
||||||
val isSensitive = note.event?.isSensitive() ?: false
|
val isSensitive = note.event?.isSensitive() ?: false
|
||||||
return account.isHidden(note.author!!) || (isSensitive && account.showSensitiveContent == false)
|
return account.isHidden(note.author!!) || (isSensitive && account.showSensitiveContent == false)
|
||||||
@@ -170,7 +211,9 @@ class AccountViewModel(val account: Account) : ViewModel(), Dao {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun deleteBoostsTo(note: Note) {
|
fun deleteBoostsTo(note: Note) {
|
||||||
account.delete(account.boostsTo(note))
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
account.delete(account.boostsTo(note))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun calculateIfNoteWasZappedByAccount(zappedNote: Note, onWasZapped: (Boolean) -> Unit) {
|
fun calculateIfNoteWasZappedByAccount(zappedNote: Note, onWasZapped: (Boolean) -> Unit) {
|
||||||
@@ -286,7 +329,7 @@ class AccountViewModel(val account: Account) : ViewModel(), Dao {
|
|||||||
pollOption: Int?,
|
pollOption: Int?,
|
||||||
message: String,
|
message: String,
|
||||||
context: Context,
|
context: Context,
|
||||||
onError: (String) -> Unit,
|
onError: (String, String) -> Unit,
|
||||||
onProgress: (percent: Float) -> Unit,
|
onProgress: (percent: Float) -> Unit,
|
||||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
||||||
zapType: LnZapEvent.ZapType
|
zapType: LnZapEvent.ZapType
|
||||||
@@ -308,7 +351,9 @@ class AccountViewModel(val account: Account) : ViewModel(), Dao {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun boost(note: Note) {
|
fun boost(note: Note) {
|
||||||
account.boost(note)
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
account.boost(note)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeEmojiPack(usersEmojiList: Note, emojiList: Note) {
|
fun removeEmojiPack(usersEmojiList: Note, emojiList: Note) {
|
||||||
@@ -388,11 +433,51 @@ class AccountViewModel(val account: Account) : ViewModel(), Dao {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun follow(user: User) {
|
fun follow(user: User) {
|
||||||
account.follow(user)
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
account.follow(user)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unfollow(user: User) {
|
fun unfollow(user: User) {
|
||||||
account.unfollow(user)
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
account.unfollow(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun followGeohash(tag: String) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
account.followGeohash(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unfollowGeohash(tag: String) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
account.unfollowGeohash(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun followHashtag(tag: String) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
account.followHashtag(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unfollowHashtag(tag: String) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
account.unfollowHashtag(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showWord(word: String) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
account.showWord(word)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hideWord(word: String) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
account.hideWord(word)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isLoggedUser(user: User?): Boolean {
|
fun isLoggedUser(user: User?): Boolean {
|
||||||
|
|||||||
+2
-14
@@ -1,6 +1,5 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||||
|
|
||||||
import android.widget.Toast
|
|
||||||
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.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -17,11 +16,9 @@ import androidx.compose.material3.Surface
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.Alignment
|
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.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
@@ -37,12 +34,9 @@ import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||||
import com.vitorpamplona.amethyst.ui.theme.RichTextDefaults
|
import com.vitorpamplona.amethyst.ui.theme.RichTextDefaults
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ConnectOrbotDialog(onClose: () -> Unit, onPost: () -> Unit, portNumber: MutableState<String>) {
|
fun ConnectOrbotDialog(onClose: () -> Unit, onPost: () -> Unit, onError: (String) -> Unit, portNumber: MutableState<String>) {
|
||||||
val context = LocalContext.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
Dialog(
|
Dialog(
|
||||||
onDismissRequest = onClose,
|
onDismissRequest = onClose,
|
||||||
properties = DialogProperties(usePlatformDefaultWidth = false, decorFitsSystemWindows = false)
|
properties = DialogProperties(usePlatformDefaultWidth = false, decorFitsSystemWindows = false)
|
||||||
@@ -67,13 +61,7 @@ fun ConnectOrbotDialog(onClose: () -> Unit, onPost: () -> Unit, portNumber: Muta
|
|||||||
try {
|
try {
|
||||||
Integer.parseInt(portNumber.value)
|
Integer.parseInt(portNumber.value)
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
scope.launch {
|
onError(toastMessage)
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
toastMessage,
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
return@UseOrbotButton
|
return@UseOrbotButton
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||||
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -18,7 +17,6 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -160,9 +158,6 @@ fun GeoHashActionOptions(
|
|||||||
tag: String,
|
tag: String,
|
||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val userState by accountViewModel.userProfile().live().follows.observeAsState()
|
val userState by accountViewModel.userProfile().live().follows.observeAsState()
|
||||||
val isFollowingTag by remember(userState) {
|
val isFollowingTag by remember(userState) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
@@ -174,48 +169,30 @@ fun GeoHashActionOptions(
|
|||||||
UnfollowButton {
|
UnfollowButton {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
if (accountViewModel.loggedInWithExternalSigner()) {
|
if (accountViewModel.loggedInWithExternalSigner()) {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.unfollowGeohash(tag)
|
||||||
accountViewModel.account.unfollowGeohash(tag)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
R.string.read_only_user,
|
||||||
.makeText(
|
R.string.login_with_a_private_key_to_be_able_to_unfollow
|
||||||
context,
|
)
|
||||||
context.getString(R.string.login_with_a_private_key_to_be_able_to_unfollow),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.unfollowGeohash(tag)
|
||||||
accountViewModel.account.unfollowGeohash(tag)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
FollowButton {
|
FollowButton {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
if (accountViewModel.loggedInWithExternalSigner()) {
|
if (accountViewModel.loggedInWithExternalSigner()) {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.followGeohash(tag)
|
||||||
accountViewModel.account.followGeohash(tag)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
R.string.read_only_user,
|
||||||
.makeText(
|
R.string.login_with_a_private_key_to_be_able_to_follow
|
||||||
context,
|
)
|
||||||
context.getString(R.string.login_with_a_private_key_to_be_able_to_follow),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.followGeohash(tag)
|
||||||
accountViewModel.account.followGeohash(tag)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||||
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -16,10 +15,8 @@ import androidx.compose.runtime.derivedStateOf
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -31,8 +28,6 @@ import com.vitorpamplona.amethyst.service.NostrHashtagDataSource
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.NostrHashtagFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrHashtagFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView
|
import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HashtagScreen(tag: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
fun HashtagScreen(tag: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
@@ -136,9 +131,6 @@ fun HashtagActionOptions(
|
|||||||
tag: String,
|
tag: String,
|
||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val userState by accountViewModel.userProfile().live().follows.observeAsState()
|
val userState by accountViewModel.userProfile().live().follows.observeAsState()
|
||||||
val isFollowingTag by remember(userState) {
|
val isFollowingTag by remember(userState) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
@@ -150,48 +142,30 @@ fun HashtagActionOptions(
|
|||||||
UnfollowButton {
|
UnfollowButton {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
if (accountViewModel.loggedInWithExternalSigner()) {
|
if (accountViewModel.loggedInWithExternalSigner()) {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.unfollowHashtag(tag)
|
||||||
accountViewModel.account.unfollowHashtag(tag)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
R.string.read_only_user,
|
||||||
.makeText(
|
R.string.login_with_a_private_key_to_be_able_to_unfollow
|
||||||
context,
|
)
|
||||||
context.getString(R.string.login_with_a_private_key_to_be_able_to_unfollow),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.unfollowHashtag(tag)
|
||||||
accountViewModel.account.unfollowHashtag(tag)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
FollowButton {
|
FollowButton {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
if (accountViewModel.loggedInWithExternalSigner()) {
|
if (accountViewModel.loggedInWithExternalSigner()) {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.followHashtag(tag)
|
||||||
accountViewModel.account.followHashtag(tag)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
R.string.read_only_user,
|
||||||
.makeText(
|
R.string.login_with_a_private_key_to_be_able_to_follow
|
||||||
context,
|
)
|
||||||
context.getString(R.string.login_with_a_private_key_to_be_able_to_follow),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.followHashtag(tag)
|
||||||
accountViewModel.account.followHashtag(tag)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-36
@@ -1,6 +1,5 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||||
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -35,7 +34,6 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
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.LocalLifecycleOwner
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
@@ -65,7 +63,6 @@ import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.TabRowHeight
|
import com.vitorpamplona.amethyst.ui.theme.TabRowHeight
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -307,9 +304,6 @@ fun MutedWordActionOptions(
|
|||||||
word: String,
|
word: String,
|
||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val isMutedWord by accountViewModel.account.liveHiddenUsers.map {
|
val isMutedWord by accountViewModel.account.liveHiddenUsers.map {
|
||||||
word in it.hiddenWords
|
word in it.hiddenWords
|
||||||
}.distinctUntilChanged().observeAsState()
|
}.distinctUntilChanged().observeAsState()
|
||||||
@@ -318,48 +312,30 @@ fun MutedWordActionOptions(
|
|||||||
ShowWordButton {
|
ShowWordButton {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
if (accountViewModel.loggedInWithExternalSigner()) {
|
if (accountViewModel.loggedInWithExternalSigner()) {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.showWord(word)
|
||||||
accountViewModel.account.showWord(word)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
R.string.read_only_user,
|
||||||
.makeText(
|
R.string.login_with_a_private_key_to_be_able_to_show_word
|
||||||
context,
|
)
|
||||||
context.getString(R.string.login_with_a_private_key_to_be_able_to_unfollow),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.showWord(word)
|
||||||
accountViewModel.account.showWord(word)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
HideWordButton {
|
HideWordButton {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
if (accountViewModel.loggedInWithExternalSigner()) {
|
if (accountViewModel.loggedInWithExternalSigner()) {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.hideWord(word)
|
||||||
accountViewModel.account.hideWord(word)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
R.string.read_only_user,
|
||||||
.makeText(
|
R.string.login_with_a_private_key_to_be_able_to_hide_word
|
||||||
context,
|
)
|
||||||
context.getString(R.string.login_with_a_private_key_to_be_able_to_follow),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.hideWord(word)
|
||||||
accountViewModel.account.hideWord(word)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
|
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.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -49,6 +50,7 @@ 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.model.BooleanType
|
import com.vitorpamplona.amethyst.model.BooleanType
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.InformationDialog
|
||||||
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
|
||||||
@@ -119,6 +121,8 @@ fun MainScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DisplayErrorMessages(accountViewModel)
|
||||||
|
|
||||||
val navPopBack = remember(navController) {
|
val navPopBack = remember(navController) {
|
||||||
{
|
{
|
||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
@@ -356,6 +360,30 @@ fun MainScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DisplayErrorMessages(accountViewModel: AccountViewModel) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val openDialogMsg = accountViewModel.toasts.collectAsState(initial = null)
|
||||||
|
|
||||||
|
openDialogMsg.value?.let { obj ->
|
||||||
|
when (obj) {
|
||||||
|
is ResourceToastMsg -> InformationDialog(
|
||||||
|
context.getString(obj.titleResId),
|
||||||
|
context.getString(obj.resourceId)
|
||||||
|
) {
|
||||||
|
accountViewModel.clearToasts()
|
||||||
|
}
|
||||||
|
|
||||||
|
is StringToastMsg -> InformationDialog(
|
||||||
|
obj.title,
|
||||||
|
obj.msg
|
||||||
|
) {
|
||||||
|
accountViewModel.clearToasts()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WatchNavStateToUpdateBarVisibility(navState: State<NavBackStackEntry?>, bottomBarOffsetHeightPx: MutableState<Float>) {
|
fun WatchNavStateToUpdateBarVisibility(navState: State<NavBackStackEntry?>, bottomBarOffsetHeightPx: MutableState<Float>) {
|
||||||
LaunchedEffect(key1 = navState.value) {
|
LaunchedEffect(key1 = navState.value) {
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
@@ -46,7 +43,6 @@ import androidx.compose.ui.unit.Dp
|
|||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
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.core.content.ContextCompat
|
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
import androidx.lifecycle.distinctUntilChanged
|
import androidx.lifecycle.distinctUntilChanged
|
||||||
@@ -62,6 +58,7 @@ import com.vitorpamplona.amethyst.model.Note
|
|||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
|
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
|
||||||
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
import com.vitorpamplona.amethyst.service.connectivitystatus.ConnectivityStatus
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.InformationDialog
|
||||||
import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataView
|
import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataView
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||||
import com.vitorpamplona.amethyst.ui.components.DisplayNip05ProfileStatus
|
import com.vitorpamplona.amethyst.ui.components.DisplayNip05ProfileStatus
|
||||||
@@ -74,8 +71,11 @@ import com.vitorpamplona.amethyst.ui.components.figureOutMimeType
|
|||||||
import com.vitorpamplona.amethyst.ui.dal.UserProfileReportsFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.UserProfileReportsFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.ShowQRDialog
|
import com.vitorpamplona.amethyst.ui.navigation.ShowQRDialog
|
||||||
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.ErrorMessageDialog
|
||||||
import com.vitorpamplona.amethyst.ui.note.LightningAddressIcon
|
import com.vitorpamplona.amethyst.ui.note.LightningAddressIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.payViaIntent
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.routeToMessage
|
||||||
import com.vitorpamplona.amethyst.ui.screen.FeedState
|
import com.vitorpamplona.amethyst.ui.screen.FeedState
|
||||||
import com.vitorpamplona.amethyst.ui.screen.LnZapFeedView
|
import com.vitorpamplona.amethyst.ui.screen.LnZapFeedView
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrUserAppRecommendationsFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrUserAppRecommendationsFeedViewModel
|
||||||
@@ -739,9 +739,6 @@ private fun DisplayFollowUnfollowButton(
|
|||||||
baseUser: User,
|
baseUser: User,
|
||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val isLoggedInFollowingUser by accountViewModel.account.userProfile().live().follows.map {
|
val isLoggedInFollowingUser by accountViewModel.account.userProfile().live().follows.map {
|
||||||
it.user.isFollowing(baseUser)
|
it.user.isFollowing(baseUser)
|
||||||
}.distinctUntilChanged().observeAsState(initial = accountViewModel.account.isFollowing(baseUser))
|
}.distinctUntilChanged().observeAsState(initial = accountViewModel.account.isFollowing(baseUser))
|
||||||
@@ -754,24 +751,15 @@ private fun DisplayFollowUnfollowButton(
|
|||||||
UnfollowButton {
|
UnfollowButton {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
if (accountViewModel.loggedInWithExternalSigner()) {
|
if (accountViewModel.loggedInWithExternalSigner()) {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.unfollow(baseUser)
|
||||||
accountViewModel.account.unfollow(baseUser)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
R.string.read_only_user,
|
||||||
.makeText(
|
R.string.login_with_a_private_key_to_be_able_to_unfollow
|
||||||
context,
|
)
|
||||||
context.getString(R.string.login_with_a_private_key_to_be_able_to_unfollow),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.unfollow(baseUser)
|
||||||
accountViewModel.account.unfollow(baseUser)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -779,48 +767,30 @@ private fun DisplayFollowUnfollowButton(
|
|||||||
FollowButton(R.string.follow_back) {
|
FollowButton(R.string.follow_back) {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
if (accountViewModel.loggedInWithExternalSigner()) {
|
if (accountViewModel.loggedInWithExternalSigner()) {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.follow(baseUser)
|
||||||
accountViewModel.account.follow(baseUser)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
R.string.read_only_user,
|
||||||
.makeText(
|
R.string.login_with_a_private_key_to_be_able_to_follow
|
||||||
context,
|
)
|
||||||
context.getString(R.string.login_with_a_private_key_to_be_able_to_follow),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.follow(baseUser)
|
||||||
accountViewModel.account.follow(baseUser)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
FollowButton(R.string.follow) {
|
FollowButton(R.string.follow) {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
if (accountViewModel.loggedInWithExternalSigner()) {
|
if (accountViewModel.loggedInWithExternalSigner()) {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.follow(baseUser)
|
||||||
accountViewModel.account.follow(baseUser)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
accountViewModel.toast(
|
||||||
Toast
|
R.string.read_only_user,
|
||||||
.makeText(
|
R.string.login_with_a_private_key_to_be_able_to_follow
|
||||||
context,
|
)
|
||||||
context.getString(R.string.login_with_a_private_key_to_be_able_to_follow),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scope.launch(Dispatchers.IO) {
|
accountViewModel.follow(baseUser)
|
||||||
accountViewModel.account.follow(baseUser)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -974,7 +944,7 @@ private fun DrawAdditionalInfo(
|
|||||||
|
|
||||||
val lud16 = remember(userState) { user.info?.lud16?.trim() ?: user.info?.lud06?.trim() }
|
val lud16 = remember(userState) { user.info?.lud16?.trim() ?: user.info?.lud06?.trim() }
|
||||||
val pubkeyHex = remember { baseUser.pubkeyHex }
|
val pubkeyHex = remember { baseUser.pubkeyHex }
|
||||||
DisplayLNAddress(lud16, pubkeyHex, accountViewModel.account)
|
DisplayLNAddress(lud16, pubkeyHex, accountViewModel, nav)
|
||||||
|
|
||||||
val identities = user.info?.latestMetadata?.identityClaims()
|
val identities = user.info?.latestMetadata?.identityClaims()
|
||||||
if (!identities.isNullOrEmpty()) {
|
if (!identities.isNullOrEmpty()) {
|
||||||
@@ -1026,12 +996,39 @@ private fun DrawAdditionalInfo(
|
|||||||
fun DisplayLNAddress(
|
fun DisplayLNAddress(
|
||||||
lud16: String?,
|
lud16: String?,
|
||||||
userHex: String,
|
userHex: String,
|
||||||
account: Account
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
var zapExpanded by remember { mutableStateOf(false) }
|
var zapExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
var showErrorMessageDialog by remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
|
if (showErrorMessageDialog != null) {
|
||||||
|
ErrorMessageDialog(
|
||||||
|
title = stringResource(id = R.string.error_dialog_zap_error),
|
||||||
|
textContent = showErrorMessageDialog ?: "",
|
||||||
|
onClickStartMessage = {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
val route = routeToMessage(userHex, showErrorMessageDialog, accountViewModel)
|
||||||
|
nav(route)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismiss = { showErrorMessageDialog = null }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var showInfoMessageDialog by remember { mutableStateOf<String?>(null) }
|
||||||
|
if (showInfoMessageDialog != null) {
|
||||||
|
InformationDialog(
|
||||||
|
title = context.getString(R.string.payment_successful),
|
||||||
|
textContent = showInfoMessageDialog ?: ""
|
||||||
|
) {
|
||||||
|
showInfoMessageDialog = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!lud16.isNullOrEmpty()) {
|
if (!lud16.isNullOrEmpty()) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
LightningAddressIcon(modifier = Size16Modifier, tint = BitcoinOrange)
|
LightningAddressIcon(modifier = Size16Modifier, tint = BitcoinOrange)
|
||||||
@@ -1054,50 +1051,31 @@ fun DisplayLNAddress(
|
|||||||
InvoiceRequestCard(
|
InvoiceRequestCard(
|
||||||
lud16,
|
lud16,
|
||||||
userHex,
|
userHex,
|
||||||
account,
|
accountViewModel.account,
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
zapExpanded = false
|
zapExpanded = false
|
||||||
// pay directly
|
// pay directly
|
||||||
if (account.hasWalletConnectSetup()) {
|
if (accountViewModel.account.hasWalletConnectSetup()) {
|
||||||
account.sendZapPaymentRequestFor(it, null) { response ->
|
accountViewModel.account.sendZapPaymentRequestFor(it, null) { response ->
|
||||||
if (response is PayInvoiceSuccessResponse) {
|
if (response is PayInvoiceSuccessResponse) {
|
||||||
scope.launch {
|
showInfoMessageDialog = context.getString(R.string.payment_successful)
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
context.getString(R.string.payment_successful), // Turn this into a UI animation
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
} else if (response is PayInvoiceErrorResponse) {
|
} else if (response is PayInvoiceErrorResponse) {
|
||||||
scope.launch {
|
showErrorMessageDialog = response.error?.message
|
||||||
Toast.makeText(
|
?: response.error?.code?.toString()
|
||||||
context,
|
?: context.getString(R.string.error_parsing_error_message)
|
||||||
response.error?.message
|
|
||||||
?: response.error?.code?.toString()
|
|
||||||
?: context.getString(R.string.error_parsing_error_message),
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
payViaIntent(it, context) {
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("lightning:$it"))
|
showErrorMessageDialog = it
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
|
||||||
ContextCompat.startActivity(context, intent, null)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
scope.launch {
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
context.getString(R.string.lightning_wallets_not_found),
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClose = {
|
onClose = {
|
||||||
zapExpanded = false
|
zapExpanded = false
|
||||||
|
},
|
||||||
|
onError = { title, message ->
|
||||||
|
accountViewModel.toast(title, message)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -311,6 +311,15 @@ fun LoginPage(
|
|||||||
connectOrbotDialogOpen = false
|
connectOrbotDialogOpen = false
|
||||||
useProxy.value = true
|
useProxy.value = true
|
||||||
},
|
},
|
||||||
|
onError = {
|
||||||
|
scope.launch {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
it,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
},
|
||||||
proxyPort
|
proxyPort
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,13 +30,17 @@
|
|||||||
<string name="report_impersonation">Report Impersonation</string>
|
<string name="report_impersonation">Report Impersonation</string>
|
||||||
<string name="report_explicit_content">Report Explicit Content</string>
|
<string name="report_explicit_content">Report Explicit Content</string>
|
||||||
<string name="report_illegal_behaviour">Report Illegal Behaviour</string>
|
<string name="report_illegal_behaviour">Report Illegal Behaviour</string>
|
||||||
<string name="login_with_a_private_key_to_be_able_to_reply">Login with a Private key to be able to reply</string>
|
<string name="login_with_a_private_key_to_be_able_to_reply">You are using a public key and public keys are read-only. Login with a Private key to be able to reply</string>
|
||||||
<string name="login_with_a_private_key_to_be_able_to_boost_posts">Login with a Private key to be able to boost posts</string>
|
<string name="login_with_a_private_key_to_be_able_to_boost_posts">You are using a public key and public keys are read-only. Login with a Private key to be able to boost posts</string>
|
||||||
<string name="login_with_a_private_key_to_like_posts">Login with a Private key to like Posts</string>
|
<string name="login_with_a_private_key_to_like_posts">You are using a public key and public keys are read-only. Login with a Private key to like posts</string>
|
||||||
<string name="no_zap_amount_setup_long_press_to_change">No Zap Amount Setup. Long Press to change</string>
|
<string name="no_zap_amount_setup_long_press_to_change">No Zap Amount Setup. Long Press to change</string>
|
||||||
<string name="login_with_a_private_key_to_be_able_to_send_zaps">Login with a Private key to be able to send Zaps</string>
|
<string name="login_with_a_private_key_to_be_able_to_send_zaps">You are using a public key and public keys are read-only. Login with a Private key to be able to send zaps</string>
|
||||||
<string name="login_with_a_private_key_to_be_able_to_follow">Login with a Private key to be able to Follow</string>
|
<string name="login_with_a_private_key_to_be_able_to_follow">You are using a public key and public keys are read-only. Login with a Private key to be able to follow</string>
|
||||||
<string name="login_with_a_private_key_to_be_able_to_unfollow">Login with a Private key to be able to Unfollow</string>
|
<string name="login_with_a_private_key_to_be_able_to_unfollow">You are using a public key and public keys are read-only. Login with a Private key to be able to unfollow</string>
|
||||||
|
<string name="login_with_a_private_key_to_be_able_to_hide_word">You are using a public key and public keys are read-only. Login with a Private key to be able to hide a word or sentence</string>
|
||||||
|
<string name="login_with_a_private_key_to_be_able_to_show_word">You are using a public key and public keys are read-only. Login with a Private key to be able to show a word or sentence</string>
|
||||||
|
|
||||||
|
|
||||||
<string name="zaps">Zaps</string>
|
<string name="zaps">Zaps</string>
|
||||||
<string name="view_count">View count</string>
|
<string name="view_count">View count</string>
|
||||||
<string name="boost">Boost</string>
|
<string name="boost">Boost</string>
|
||||||
@@ -195,6 +199,8 @@
|
|||||||
<string name="secret_key_copied_to_clipboard">Secret key (nsec) copied to clipboard</string>
|
<string name="secret_key_copied_to_clipboard">Secret key (nsec) copied to clipboard</string>
|
||||||
<string name="copy_my_secret_key">Copy my secret key</string>
|
<string name="copy_my_secret_key">Copy my secret key</string>
|
||||||
<string name="biometric_authentication_failed">Authentication failed</string>
|
<string name="biometric_authentication_failed">Authentication failed</string>
|
||||||
|
<string name="biometric_authentication_failed_explainer">Biometrics failed to authenticate the owner of this phone</string>
|
||||||
|
<string name="biometric_authentication_failed_explainer_with_error">Biometrics failed to authenticate the owner of this phone. Error: %1$s</string>
|
||||||
<string name="biometric_error">Error</string>
|
<string name="biometric_error">Error</string>
|
||||||
<string name="badge_created_by">"Created by %1$s"</string>
|
<string name="badge_created_by">"Created by %1$s"</string>
|
||||||
<string name="badge_award_image_for">"Badge award image for %1$s"</string>
|
<string name="badge_award_image_for">"Badge award image for %1$s"</string>
|
||||||
@@ -285,7 +291,8 @@
|
|||||||
<string name="poll_consensus_threshold_percent">(0–100)%</string>
|
<string name="poll_consensus_threshold_percent">(0–100)%</string>
|
||||||
<string name="poll_closing_time">Close after</string>
|
<string name="poll_closing_time">Close after</string>
|
||||||
<string name="poll_closing_time_days">days</string>
|
<string name="poll_closing_time_days">days</string>
|
||||||
<string name="poll_is_closed">Poll is closed to new votes</string>
|
<string name="poll_unable_to_vote">Unable to vote</string>
|
||||||
|
<string name="poll_is_closed_explainer">Poll is closed to new votes</string>
|
||||||
<string name="poll_zap_amount">Zap amount</string>
|
<string name="poll_zap_amount">Zap amount</string>
|
||||||
<string name="one_vote_per_user_on_atomic_votes">Only one vote per user is allowed on this type of poll</string>
|
<string name="one_vote_per_user_on_atomic_votes">Only one vote per user is allowed on this type of poll</string>
|
||||||
|
|
||||||
@@ -301,6 +308,7 @@
|
|||||||
<string name="poll_author_no_vote">Poll authors can\'t vote in their own polls.</string>
|
<string name="poll_author_no_vote">Poll authors can\'t vote in their own polls.</string>
|
||||||
<string name="poll_hashtag" translatable="false">#zappoll</string>
|
<string name="poll_hashtag" translatable="false">#zappoll</string>
|
||||||
|
|
||||||
|
<string name="hash_verification_info_title">What does this mean?</string>
|
||||||
<string name="hash_verification_passed">This content is the same since the post</string>
|
<string name="hash_verification_passed">This content is the same since the post</string>
|
||||||
<string name="hash_verification_failed">This content has changed. The author might not have seen or approved the change</string>
|
<string name="hash_verification_failed">This content has changed. The author might not have seen or approved the change</string>
|
||||||
|
|
||||||
@@ -426,7 +434,7 @@
|
|||||||
<string name="warn_when_posts_have_reports_from_your_follows">Warn when posts have reports from your follows</string>
|
<string name="warn_when_posts_have_reports_from_your_follows">Warn when posts have reports from your follows</string>
|
||||||
|
|
||||||
<string name="new_reaction_symbol">New Reaction Symbol</string>
|
<string name="new_reaction_symbol">New Reaction Symbol</string>
|
||||||
<string name="no_reaction_type_setup_long_press_to_change">No reaction types selected. Long Press to change</string>
|
<string name="no_reaction_type_setup_long_press_to_change">No reaction types pre-selected for this user. Long press on the heart button to change</string>
|
||||||
|
|
||||||
<string name="zapraiser">Zapraiser</string>
|
<string name="zapraiser">Zapraiser</string>
|
||||||
<string name="zapraiser_explainer">Adds a target amount of sats to raise for this post. Supporting clients may show this as a progress bar to incentivize donations</string>
|
<string name="zapraiser_explainer">Adds a target amount of sats to raise for this post. Supporting clients may show this as a progress bar to incentivize donations</string>
|
||||||
@@ -581,6 +589,7 @@
|
|||||||
<string name="zap_split_explainer">Supporting clients will split and forward zaps to the users added here instead of yours</string>
|
<string name="zap_split_explainer">Supporting clients will split and forward zaps to the users added here instead of yours</string>
|
||||||
<string name="zap_split_serarch_and_add_user">Search and Add User</string>
|
<string name="zap_split_serarch_and_add_user">Search and Add User</string>
|
||||||
<string name="zap_split_serarch_and_add_user_placeholder">Username or display name</string>
|
<string name="zap_split_serarch_and_add_user_placeholder">Username or display name</string>
|
||||||
|
<string name="missing_lud16">Missing lightning setup</string>
|
||||||
<string name="user_x_does_not_have_a_lightning_address_setup_to_receive_sats">User %1$s does not have a lightning address set up to receive sats</string>
|
<string name="user_x_does_not_have_a_lightning_address_setup_to_receive_sats">User %1$s does not have a lightning address set up to receive sats</string>
|
||||||
<string name="zap_split_weight">Percentage</string>
|
<string name="zap_split_weight">Percentage</string>
|
||||||
<string name="zap_split_weight_placeholder">25</string>
|
<string name="zap_split_weight_placeholder">25</string>
|
||||||
@@ -602,4 +611,37 @@
|
|||||||
<string name="automatically_show_profile_picture_description">Show Profile pictures</string>
|
<string name="automatically_show_profile_picture_description">Show Profile pictures</string>
|
||||||
|
|
||||||
<string name="select_an_option">Select an Option</string>
|
<string name="select_an_option">Select an Option</string>
|
||||||
|
|
||||||
|
<string name="error_dialog_pay_invoice_error">Could not pay invoice</string>
|
||||||
|
<string name="error_dialog_pay_withdraw_error">Could not withdraw</string>
|
||||||
|
|
||||||
|
<string name="error_parsing_nip47_title">Could not setup Wallet Connect</string>
|
||||||
|
<string name="error_parsing_nip47">Error parsing NIP-47 connection string. Check if this is correct with your Wallet provider: %1$s. Error: %2$s</string>
|
||||||
|
<string name="error_parsing_nip47_no_error">Error parsing NIP-47 connection string. Check if this is correct with your Wallet provider: %1$s.</string>
|
||||||
|
|
||||||
|
<string name="cashu_failed_redemption">Could not redeem Cashu</string>
|
||||||
|
<string name="cashu_failed_redemption_explainer_error_msg">Mint provided the following error message: %1$s</string>
|
||||||
|
<string name="cashu_failed_redemption_explainer_already_spent">Cashu tokens already spent.</string>
|
||||||
|
|
||||||
|
<string name="cashu_sucessful_redemption">Cashu Received</string>
|
||||||
|
<string name="cashu_sucessful_redemption_explainer">%1$s sats were sent to your wallet. (Fees: %2$s sats)</string>
|
||||||
|
|
||||||
|
<string name="error_unable_to_fetch_invoice">Unable to fetch invoice from receiver\'s servers</string>
|
||||||
|
|
||||||
|
<string name="wallet_connect_pay_invoice_error_error">Your wallet connect provider returned the following error: %1$s</string>
|
||||||
|
|
||||||
|
<string name="could_not_connect_to_tor">Could not connect to Tor</string>
|
||||||
|
<string name="unable_to_download_relay_document">Download relay document unavailable</string>
|
||||||
|
<string name="could_not_assemble_lnurl_from_lightning_address_check_the_user_s_setup">Could not assemble LNUrl from Lightning Address \"%1$s\". Check the user\'s setup</string>
|
||||||
|
<string name="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 %1$s is not available. It was calculated from the lightning address \"%2$s\". Error: %3$s. Check if the server is up and if the lightning address 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_fetch_invoice_from">Could not fetch invoice from %1$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="callback_url_not_found_in_the_user_s_lightning_address_server_configuration">Callback URL not found in the User\'s lightning address server configuration</string>
|
||||||
|
<string name="error_parsing_json_from_lightning_address_s_invoice_fetch_check_the_user_s_lightning_setup">Error Parsing JSON from Lightning Address\'s invoice fetch. Check the user\'s lightning setup</string>
|
||||||
|
<string name="incorrect_invoice_amount_sats_from_it_should_have_been">Incorrect invoice amount (%1$s sats) from %2$s. It should have been %3$s</string>
|
||||||
|
<string name="unable_to_create_a_lightning_invoice_before_sending_the_zap_the_receiver_s_lightning_wallet_sent_the_following_error">Unable to create a lightning invoice before sending the zap. The receiver\'s lightning wallet sent the following error: %1$s</string>
|
||||||
|
<string name="unable_to_create_a_lightning_invoice_before_sending_the_zap_element_pr_not_found_in_the_resulting_json">Unable to create a lightning invoice before sending the zap. Element pr not found in the resulting JSON.</string>
|
||||||
|
<string name="read_only_user">Read-only user</string>
|
||||||
|
<string name="no_reactions_setup">No reactions setup</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user