mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-10-10 09:42:58 +02:00
Merge branch 'vitorpamplona:main' into NIP90-ContentDiscovery
This commit is contained in:
@@ -148,7 +148,7 @@ val DefaultReactions =
|
|||||||
"\uD83D\uDE31",
|
"\uD83D\uDE31",
|
||||||
)
|
)
|
||||||
|
|
||||||
val DefaultZapAmounts = listOf(500L, 1000L, 5000L)
|
val DefaultZapAmounts = listOf(100L, 500L, 1000L)
|
||||||
|
|
||||||
fun getLanguagesSpokenByUser(): Set<String> {
|
fun getLanguagesSpokenByUser(): Set<String> {
|
||||||
val languageList = ConfigurationCompat.getLocales(Resources.getSystem().getConfiguration())
|
val languageList = ConfigurationCompat.getLocales(Resources.getSystem().getConfiguration())
|
||||||
@@ -2435,6 +2435,10 @@ class Account(
|
|||||||
return (activeRelays() ?: convertLocalRelays()).filter { it.write }
|
return (activeRelays() ?: convertLocalRelays()).filter { it.write }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun activeAllRelays(): List<Relay> {
|
||||||
|
return ((activeRelays() ?: convertLocalRelays()).toList())
|
||||||
|
}
|
||||||
|
|
||||||
fun isAllHidden(users: Set<HexKey>): Boolean {
|
fun isAllHidden(users: Set<HexKey>): Boolean {
|
||||||
return users.all { isHidden(it) }
|
return users.all { isHidden(it) }
|
||||||
}
|
}
|
||||||
|
@@ -103,7 +103,7 @@ class ZapPaymentHandler(val account: Account) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (account.hasWalletConnectSetup()) {
|
if (account.hasWalletConnectSetup()) {
|
||||||
payViaNWC(it.values.map { it.second }, note, onError, onProgress = {
|
payViaNWC(it.values.map { it.invoice }, note, onError, onProgress = {
|
||||||
onProgress(it * 0.25f + 0.75f) // keeps within range.
|
onProgress(it * 0.25f + 0.75f) // keeps within range.
|
||||||
}, context) {
|
}, context) {
|
||||||
// onProgress(1f)
|
// onProgress(1f)
|
||||||
@@ -113,9 +113,9 @@ class ZapPaymentHandler(val account: Account) {
|
|||||||
it.map {
|
it.map {
|
||||||
Payable(
|
Payable(
|
||||||
info = it.key.first,
|
info = it.key.first,
|
||||||
user = null,
|
user = it.key.second.user,
|
||||||
amountMilliSats = it.value.first,
|
amountMilliSats = it.value.zapValue,
|
||||||
invoice = it.value.second,
|
invoice = it.value.invoice,
|
||||||
)
|
)
|
||||||
}.toImmutableList(),
|
}.toImmutableList(),
|
||||||
)
|
)
|
||||||
@@ -136,28 +136,33 @@ class ZapPaymentHandler(val account: Account) {
|
|||||||
return roundedZapValue
|
return roundedZapValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SignAllZapRequestsReturn(
|
||||||
|
val zapRequestJson: String,
|
||||||
|
val user: User? = null,
|
||||||
|
)
|
||||||
|
|
||||||
suspend fun signAllZapRequests(
|
suspend fun signAllZapRequests(
|
||||||
note: Note,
|
note: Note,
|
||||||
pollOption: Int?,
|
pollOption: Int?,
|
||||||
message: String,
|
message: String,
|
||||||
zapType: LnZapEvent.ZapType,
|
zapType: LnZapEvent.ZapType,
|
||||||
zapsToSend: List<ZapSplitSetup>,
|
zapsToSend: List<ZapSplitSetup>,
|
||||||
onAllDone: suspend (MutableMap<ZapSplitSetup, String>) -> Unit,
|
onAllDone: suspend (MutableMap<ZapSplitSetup, SignAllZapRequestsReturn>) -> Unit,
|
||||||
) {
|
) {
|
||||||
collectSuccessfulSigningOperations<ZapSplitSetup, String>(
|
collectSuccessfulSigningOperations<ZapSplitSetup, SignAllZapRequestsReturn>(
|
||||||
operationsInput = zapsToSend,
|
operationsInput = zapsToSend,
|
||||||
runRequestFor = { next: ZapSplitSetup, onReady ->
|
runRequestFor = { next: ZapSplitSetup, onReady ->
|
||||||
if (next.isLnAddress) {
|
if (next.isLnAddress) {
|
||||||
prepareZapRequestIfNeeded(note, pollOption, message, zapType) { zapRequestJson ->
|
prepareZapRequestIfNeeded(note, pollOption, message, zapType) { zapRequestJson ->
|
||||||
if (zapRequestJson != null) {
|
if (zapRequestJson != null) {
|
||||||
onReady(zapRequestJson)
|
onReady(SignAllZapRequestsReturn(zapRequestJson))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val user = LocalCache.getUserIfExists(next.lnAddressOrPubKeyHex)
|
val user = LocalCache.getUserIfExists(next.lnAddressOrPubKeyHex)
|
||||||
prepareZapRequestIfNeeded(note, pollOption, message, zapType, user) { zapRequestJson ->
|
prepareZapRequestIfNeeded(note, pollOption, message, zapType, user) { zapRequestJson ->
|
||||||
if (zapRequestJson != null) {
|
if (zapRequestJson != null) {
|
||||||
onReady(zapRequestJson)
|
onReady(SignAllZapRequestsReturn(zapRequestJson, user))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,23 +172,23 @@ class ZapPaymentHandler(val account: Account) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun assembleAllInvoices(
|
suspend fun assembleAllInvoices(
|
||||||
invoices: List<Pair<ZapSplitSetup, String>>,
|
invoices: List<Pair<ZapSplitSetup, SignAllZapRequestsReturn>>,
|
||||||
totalAmountMilliSats: Long,
|
totalAmountMilliSats: Long,
|
||||||
message: String,
|
message: String,
|
||||||
onError: (String, String) -> Unit,
|
onError: (String, String) -> Unit,
|
||||||
onProgress: (percent: Float) -> Unit,
|
onProgress: (percent: Float) -> Unit,
|
||||||
context: Context,
|
context: Context,
|
||||||
onAllDone: suspend (MutableMap<Pair<ZapSplitSetup, String>, Pair<Long, String>>) -> Unit,
|
onAllDone: suspend (MutableMap<Pair<ZapSplitSetup, SignAllZapRequestsReturn>, AssembleInvoiceReturn>) -> Unit,
|
||||||
) {
|
) {
|
||||||
var progressAllPayments = 0.00f
|
var progressAllPayments = 0.00f
|
||||||
val totalWeight = invoices.sumOf { it.first.weight }
|
val totalWeight = invoices.sumOf { it.first.weight }
|
||||||
|
|
||||||
collectSuccessfulSigningOperations<Pair<ZapSplitSetup, String>, Pair<Long, String>>(
|
collectSuccessfulSigningOperations<Pair<ZapSplitSetup, SignAllZapRequestsReturn>, AssembleInvoiceReturn>(
|
||||||
operationsInput = invoices,
|
operationsInput = invoices,
|
||||||
runRequestFor = { splitZapRequestPair: Pair<ZapSplitSetup, String>, onReady ->
|
runRequestFor = { splitZapRequestPair: Pair<ZapSplitSetup, SignAllZapRequestsReturn>, onReady ->
|
||||||
assembleInvoice(
|
assembleInvoice(
|
||||||
splitSetup = splitZapRequestPair.first,
|
splitSetup = splitZapRequestPair.first,
|
||||||
nostrZapRequest = splitZapRequestPair.second,
|
nostrZapRequest = splitZapRequestPair.second.zapRequestJson,
|
||||||
zapValue = calculateZapValue(totalAmountMilliSats, splitZapRequestPair.first.weight, totalWeight),
|
zapValue = calculateZapValue(totalAmountMilliSats, splitZapRequestPair.first.weight, totalWeight),
|
||||||
message = message,
|
message = message,
|
||||||
onError = onError,
|
onError = onError,
|
||||||
@@ -243,6 +248,11 @@ class ZapPaymentHandler(val account: Account) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AssembleInvoiceReturn(
|
||||||
|
val zapValue: Long,
|
||||||
|
val invoice: String,
|
||||||
|
)
|
||||||
|
|
||||||
private fun assembleInvoice(
|
private fun assembleInvoice(
|
||||||
splitSetup: ZapSplitSetup,
|
splitSetup: ZapSplitSetup,
|
||||||
nostrZapRequest: String,
|
nostrZapRequest: String,
|
||||||
@@ -251,7 +261,7 @@ class ZapPaymentHandler(val account: Account) {
|
|||||||
onError: (String, String) -> Unit,
|
onError: (String, String) -> Unit,
|
||||||
onProgressStep: (percent: Float) -> Unit,
|
onProgressStep: (percent: Float) -> Unit,
|
||||||
context: Context,
|
context: Context,
|
||||||
onReady: (Pair<Long, String>) -> Unit,
|
onReady: (AssembleInvoiceReturn) -> Unit,
|
||||||
) {
|
) {
|
||||||
var progressThisPayment = 0.00f
|
var progressThisPayment = 0.00f
|
||||||
|
|
||||||
@@ -280,7 +290,7 @@ class ZapPaymentHandler(val account: Account) {
|
|||||||
context = context,
|
context = context,
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
onProgressStep(1 - progressThisPayment)
|
onProgressStep(1 - progressThisPayment)
|
||||||
onReady(Pair(zapValue, it))
|
onReady(AssembleInvoiceReturn(zapValue, it))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@@ -449,6 +449,38 @@ class Relay(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function sends the event regardless of the relay being write or not.
|
||||||
|
fun sendOverride(signedEvent: EventInterface) {
|
||||||
|
checkNotInMainThread()
|
||||||
|
|
||||||
|
if (signedEvent is RelayAuthEvent) {
|
||||||
|
authResponse.put(signedEvent.id, false)
|
||||||
|
// specific protocol for this event.
|
||||||
|
val event = """["AUTH",${signedEvent.toJson()}]"""
|
||||||
|
socket?.send(event)
|
||||||
|
eventUploadCounterInBytes += event.bytesUsedInMemory()
|
||||||
|
} else {
|
||||||
|
val event = """["EVENT",${signedEvent.toJson()}]"""
|
||||||
|
if (isConnected()) {
|
||||||
|
if (isReady) {
|
||||||
|
socket?.send(event)
|
||||||
|
eventUploadCounterInBytes += event.bytesUsedInMemory()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// sends all filters after connection is successful.
|
||||||
|
connectAndRun {
|
||||||
|
checkNotInMainThread()
|
||||||
|
|
||||||
|
socket?.send(event)
|
||||||
|
eventUploadCounterInBytes += event.bytesUsedInMemory()
|
||||||
|
|
||||||
|
// Sends everything.
|
||||||
|
renewFilters()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun send(signedEvent: EventInterface) {
|
fun send(signedEvent: EventInterface) {
|
||||||
checkNotInMainThread()
|
checkNotInMainThread()
|
||||||
|
|
||||||
|
@@ -150,13 +150,17 @@ object RelayPool : Relay.Listener {
|
|||||||
list: List<Relay>,
|
list: List<Relay>,
|
||||||
signedEvent: EventInterface,
|
signedEvent: EventInterface,
|
||||||
) {
|
) {
|
||||||
list.forEach { relay -> relays.filter { it.url == relay.url }.forEach { it.send(signedEvent) } }
|
list.forEach { relay -> relays.filter { it.url == relay.url }.forEach { it.sendOverride(signedEvent) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun send(signedEvent: EventInterface) {
|
fun send(signedEvent: EventInterface) {
|
||||||
relays.forEach { it.send(signedEvent) }
|
relays.forEach { it.send(signedEvent) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun sendOverride(signedEvent: EventInterface) {
|
||||||
|
relays.forEach { it.sendOverride(signedEvent) }
|
||||||
|
}
|
||||||
|
|
||||||
fun close(subscriptionId: String) {
|
fun close(subscriptionId: String) {
|
||||||
relays.forEach { it.close(subscriptionId) }
|
relays.forEach { it.close(subscriptionId) }
|
||||||
}
|
}
|
||||||
|
@@ -78,7 +78,7 @@ fun RelaySelectionDialog(
|
|||||||
|
|
||||||
var relays by remember {
|
var relays by remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
accountViewModel.account.activeWriteRelays().map {
|
accountViewModel.account.activeAllRelays().map {
|
||||||
RelayList(
|
RelayList(
|
||||||
relay = it,
|
relay = it,
|
||||||
relayInfo = RelayBriefInfoCache.RelayBriefInfo(it.url),
|
relayInfo = RelayBriefInfoCache.RelayBriefInfo(it.url),
|
||||||
|
@@ -79,7 +79,7 @@ fun ClickableWithdrawal(withdrawalString: String) {
|
|||||||
|
|
||||||
ClickableText(
|
ClickableText(
|
||||||
text = withdraw,
|
text = withdraw,
|
||||||
onClick = { payViaIntent(withdrawalString, context) { showErrorMessageDialog = it } },
|
onClick = { payViaIntent(withdrawalString, context, { }) { showErrorMessageDialog = it } },
|
||||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.primary),
|
style = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.primary),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,6 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -42,13 +41,13 @@ 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.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.ExpandableTextCutOffCalculator
|
import com.vitorpamplona.amethyst.commons.richtext.ExpandableTextCutOffCalculator
|
||||||
import com.vitorpamplona.amethyst.ui.note.getGradient
|
import com.vitorpamplona.amethyst.ui.note.getGradient
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
|
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.StdTopPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.secondaryButtonBackground
|
import com.vitorpamplona.amethyst.ui.theme.secondaryButtonBackground
|
||||||
import com.vitorpamplona.quartz.events.ImmutableListOfLists
|
import com.vitorpamplona.quartz.events.ImmutableListOfLists
|
||||||
|
|
||||||
@@ -125,7 +124,7 @@ fun ExpandableRichTextViewer(
|
|||||||
@Composable
|
@Composable
|
||||||
fun ShowMoreButton(onClick: () -> Unit) {
|
fun ShowMoreButton(onClick: () -> Unit) {
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.padding(top = 10.dp),
|
modifier = StdTopPadding,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
shape = ButtonBorder,
|
shape = ButtonBorder,
|
||||||
colors =
|
colors =
|
||||||
|
@@ -169,7 +169,7 @@ fun InvoicePreview(
|
|||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 10.dp),
|
.padding(vertical = 10.dp),
|
||||||
onClick = { payViaIntent(lnInvoice, context) { showErrorMessageDialog = it } },
|
onClick = { payViaIntent(lnInvoice, context, { }) { showErrorMessageDialog = it } },
|
||||||
shape = QuoteBorder,
|
shape = QuoteBorder,
|
||||||
colors =
|
colors =
|
||||||
ButtonDefaults.buttonColors(
|
ButtonDefaults.buttonColors(
|
||||||
|
@@ -46,12 +46,12 @@ class ChatroomListNewFeedFilter(val account: Account) : AdditiveFeedFilter<Note>
|
|||||||
|
|
||||||
val privateMessages =
|
val privateMessages =
|
||||||
newChatrooms.mapNotNull { it ->
|
newChatrooms.mapNotNull { it ->
|
||||||
it.value.roomMessages.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).lastOrNull {
|
it.value.roomMessages.sortedWith(DefaultFeedOrder).firstOrNull {
|
||||||
it.event != null
|
it.event != null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return privateMessages.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
|
return privateMessages.sortedWith(DefaultFeedOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateListWith(
|
override fun updateListWith(
|
||||||
|
@@ -21,5 +21,23 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.dal
|
package com.vitorpamplona.amethyst.ui.dal
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.quartz.events.Event
|
||||||
|
|
||||||
val DefaultFeedOrder = compareBy<Note>({ it.createdAt() }, { it.idHex }).reversed()
|
val DefaultFeedOrder: Comparator<Note> =
|
||||||
|
compareBy<Note>(
|
||||||
|
{
|
||||||
|
val noteEvent = it.event
|
||||||
|
if (noteEvent == null) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
if (noteEvent is Event) {
|
||||||
|
noteEvent.createdAt
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
it.idHex
|
||||||
|
},
|
||||||
|
).reversed()
|
||||||
|
@@ -439,6 +439,15 @@ fun ZapVote(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
justShowError = {
|
||||||
|
scope.launch {
|
||||||
|
showErrorMessageDialog =
|
||||||
|
StringToastMsg(
|
||||||
|
context.getString(R.string.error_dialog_zap_error),
|
||||||
|
it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -140,9 +140,6 @@ import kotlinx.collections.immutable.toImmutableMap
|
|||||||
import kotlinx.collections.immutable.toImmutableSet
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.math.BigDecimal
|
|
||||||
import java.math.RoundingMode
|
|
||||||
import java.text.DecimalFormat
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -985,7 +982,8 @@ fun ZapReaction(
|
|||||||
if (wantsToZap) {
|
if (wantsToZap) {
|
||||||
ZapAmountChoicePopup(
|
ZapAmountChoicePopup(
|
||||||
baseNote = baseNote,
|
baseNote = baseNote,
|
||||||
iconSize = iconSize,
|
zapAmountChoices = accountViewModel.account.zapAmountChoices,
|
||||||
|
popupYOffset = iconSize,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
onDismiss = {
|
onDismiss = {
|
||||||
wantsToZap = false
|
wantsToZap = false
|
||||||
@@ -1042,6 +1040,11 @@ fun ZapReaction(
|
|||||||
showErrorMessageDialog = showErrorMessageDialog + it
|
showErrorMessageDialog = showErrorMessageDialog + it
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
justShowError = {
|
||||||
|
scope.launch {
|
||||||
|
showErrorMessageDialog = showErrorMessageDialog + it
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1430,8 +1433,9 @@ private fun ActionableReactionButton(
|
|||||||
@Composable
|
@Composable
|
||||||
fun ZapAmountChoicePopup(
|
fun ZapAmountChoicePopup(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
|
zapAmountChoices: List<Long>,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
iconSize: Dp,
|
popupYOffset: Dp,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onChangeAmount: () -> Unit,
|
onChangeAmount: () -> Unit,
|
||||||
onError: (title: String, text: String) -> Unit,
|
onError: (title: String, text: String) -> Unit,
|
||||||
@@ -1441,15 +1445,15 @@ fun ZapAmountChoicePopup(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val zapMessage = ""
|
val zapMessage = ""
|
||||||
|
|
||||||
val iconSizePx = with(LocalDensity.current) { -iconSize.toPx().toInt() }
|
val yOffset = with(LocalDensity.current) { -popupYOffset.toPx().toInt() }
|
||||||
|
|
||||||
Popup(
|
Popup(
|
||||||
alignment = Alignment.BottomCenter,
|
alignment = Alignment.BottomCenter,
|
||||||
offset = IntOffset(0, iconSizePx),
|
offset = IntOffset(0, yOffset),
|
||||||
onDismissRequest = { onDismiss() },
|
onDismissRequest = { onDismiss() },
|
||||||
) {
|
) {
|
||||||
FlowRow(horizontalArrangement = Arrangement.Center) {
|
FlowRow(horizontalArrangement = Arrangement.Center) {
|
||||||
accountViewModel.account.zapAmountChoices.forEach { amountInSats ->
|
zapAmountChoices.forEach { amountInSats ->
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.padding(horizontal = 3.dp),
|
modifier = Modifier.padding(horizontal = 3.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -1512,25 +1516,3 @@ fun showCount(count: Int?): String {
|
|||||||
else -> "$count"
|
else -> "$count"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val OneGiga = BigDecimal(1000000000)
|
|
||||||
val OneMega = BigDecimal(1000000)
|
|
||||||
val TenKilo = BigDecimal(10000)
|
|
||||||
val OneKilo = BigDecimal(1000)
|
|
||||||
|
|
||||||
var dfG: DecimalFormat = DecimalFormat("#.0G")
|
|
||||||
var dfM: DecimalFormat = DecimalFormat("#.0M")
|
|
||||||
var dfK: DecimalFormat = DecimalFormat("#.0k")
|
|
||||||
var dfN: DecimalFormat = DecimalFormat("#")
|
|
||||||
|
|
||||||
fun showAmount(amount: BigDecimal?): String {
|
|
||||||
if (amount == null) return ""
|
|
||||||
if (amount.abs() < BigDecimal(0.01)) return ""
|
|
||||||
|
|
||||||
return when {
|
|
||||||
amount >= OneGiga -> dfG.format(amount.div(OneGiga).setScale(0, RoundingMode.HALF_UP))
|
|
||||||
amount >= OneMega -> dfM.format(amount.div(OneMega).setScale(0, RoundingMode.HALF_UP))
|
|
||||||
amount >= TenKilo -> dfK.format(amount.div(OneKilo).setScale(0, RoundingMode.HALF_UP))
|
|
||||||
else -> dfN.format(amount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -347,12 +347,12 @@ fun PayViaIntentDialog(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
onClose: () -> Unit,
|
onClose: () -> Unit,
|
||||||
onError: (String) -> Unit,
|
onError: (String) -> Unit,
|
||||||
|
justShowError: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
if (payingInvoices.size == 1) {
|
if (payingInvoices.size == 1) {
|
||||||
payViaIntent(payingInvoices.first().invoice, context, onError)
|
payViaIntent(payingInvoices.first().invoice, context, onClose, onError)
|
||||||
onClose()
|
|
||||||
} else {
|
} else {
|
||||||
Dialog(
|
Dialog(
|
||||||
onDismissRequest = onClose,
|
onDismissRequest = onClose,
|
||||||
@@ -422,9 +422,7 @@ fun PayViaIntentDialog(
|
|||||||
Spacer(modifier = DoubleHorzSpacer)
|
Spacer(modifier = DoubleHorzSpacer)
|
||||||
|
|
||||||
PayButton(isActive = !paid.value) {
|
PayButton(isActive = !paid.value) {
|
||||||
paid.value = true
|
payViaIntent(it.invoice, context, { paid.value = true }, justShowError)
|
||||||
|
|
||||||
payViaIntent(it.invoice, context, onError)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -437,6 +435,7 @@ fun PayViaIntentDialog(
|
|||||||
fun payViaIntent(
|
fun payViaIntent(
|
||||||
invoice: String,
|
invoice: String,
|
||||||
context: Context,
|
context: Context,
|
||||||
|
onPaid: () -> Unit,
|
||||||
onError: (String) -> Unit,
|
onError: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
@@ -444,6 +443,7 @@ fun payViaIntent(
|
|||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
|
|
||||||
ContextCompat.startActivity(context, intent, null)
|
ContextCompat.startActivity(context, intent, null)
|
||||||
|
onPaid()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (e is CancellationException) throw e
|
if (e is CancellationException) throw e
|
||||||
// don't display ugly error messages
|
// don't display ugly error messages
|
||||||
|
@@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2024 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.math.RoundingMode
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
|
||||||
|
val TenGiga = BigDecimal(10000000000)
|
||||||
|
val OneGiga = BigDecimal(1000000000)
|
||||||
|
val TenMega = BigDecimal(10000000)
|
||||||
|
val OneMega = BigDecimal(1000000)
|
||||||
|
val TenKilo = BigDecimal(10000)
|
||||||
|
val OneKilo = BigDecimal(1000)
|
||||||
|
|
||||||
|
private val dfGBig =
|
||||||
|
object : ThreadLocal<DecimalFormat>() {
|
||||||
|
override fun initialValue() = DecimalFormat("#.#G")
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dfGSmall =
|
||||||
|
object : ThreadLocal<DecimalFormat>() {
|
||||||
|
override fun initialValue() = DecimalFormat("#.0G")
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dfMBig =
|
||||||
|
object : ThreadLocal<DecimalFormat>() {
|
||||||
|
override fun initialValue() = DecimalFormat("#.#M")
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dfMSmall =
|
||||||
|
object : ThreadLocal<DecimalFormat>() {
|
||||||
|
override fun initialValue() = DecimalFormat("#.0M")
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dfK =
|
||||||
|
object : ThreadLocal<DecimalFormat>() {
|
||||||
|
override fun initialValue() = DecimalFormat("#.#k")
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dfN =
|
||||||
|
object : ThreadLocal<DecimalFormat>() {
|
||||||
|
override fun initialValue() = DecimalFormat("#")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showAmount(amount: BigDecimal?): String {
|
||||||
|
if (amount == null) return ""
|
||||||
|
if (amount.abs() < BigDecimal(0.01)) return ""
|
||||||
|
|
||||||
|
return when {
|
||||||
|
amount >= TenGiga -> dfGBig.get().format(amount.div(OneGiga).setScale(0, RoundingMode.HALF_UP))
|
||||||
|
amount >= OneGiga -> dfGSmall.get().format(amount.div(OneGiga).setScale(0, RoundingMode.HALF_UP))
|
||||||
|
amount >= TenMega -> dfMBig.get().format(amount.div(OneMega).setScale(0, RoundingMode.HALF_UP))
|
||||||
|
amount >= OneMega -> dfMSmall.get().format(amount.div(OneMega).setScale(0, RoundingMode.HALF_UP))
|
||||||
|
amount >= TenKilo -> dfK.get().format(amount.div(OneKilo).setScale(0, RoundingMode.HALF_UP))
|
||||||
|
else -> dfN.get().format(amount)
|
||||||
|
}
|
||||||
|
}
|
@@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -77,7 +78,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.ModifierWidth3dp
|
import com.vitorpamplona.amethyst.ui.theme.ModifierWidth3dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||||
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
||||||
@@ -301,12 +302,12 @@ fun ZapDonationButton(
|
|||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
grayTint: Color,
|
grayTint: Color,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
iconSize: Dp = Size20dp,
|
iconSize: Dp = Size35dp,
|
||||||
iconSizeModifier: Modifier = Size20Modifier,
|
iconSizeModifier: Modifier = Size20Modifier,
|
||||||
animationSize: Dp = 14.dp,
|
animationSize: Dp = 14.dp,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
var wantsToZap by remember { mutableStateOf(false) }
|
var wantsToZap by remember { mutableStateOf<List<Long>?>(null) }
|
||||||
var showErrorMessageDialog by remember { mutableStateOf<String?>(null) }
|
var showErrorMessageDialog by remember { mutableStateOf<String?>(null) }
|
||||||
var wantsToPay by
|
var wantsToPay by
|
||||||
remember(baseNote) {
|
remember(baseNote) {
|
||||||
@@ -323,14 +324,14 @@ fun ZapDonationButton(
|
|||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
zapClick(
|
customZapClick(
|
||||||
baseNote,
|
baseNote,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
context,
|
context,
|
||||||
onZappingProgress = { progress: Float ->
|
onZappingProgress = { progress: Float ->
|
||||||
scope.launch { zappingProgress = progress }
|
scope.launch { zappingProgress = progress }
|
||||||
},
|
},
|
||||||
onMultipleChoices = { wantsToZap = true },
|
onMultipleChoices = { options -> wantsToZap = options },
|
||||||
onError = { _, message ->
|
onError = { _, message ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
@@ -342,17 +343,18 @@ fun ZapDonationButton(
|
|||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
if (wantsToZap) {
|
if (wantsToZap != null) {
|
||||||
ZapAmountChoicePopup(
|
ZapAmountChoicePopup(
|
||||||
baseNote = baseNote,
|
baseNote = baseNote,
|
||||||
iconSize = iconSize,
|
zapAmountChoices = wantsToZap ?: accountViewModel.account.zapAmountChoices,
|
||||||
|
popupYOffset = iconSize,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
onDismiss = {
|
onDismiss = {
|
||||||
wantsToZap = false
|
wantsToZap = null
|
||||||
zappingProgress = 0f
|
zappingProgress = 0f
|
||||||
},
|
},
|
||||||
onChangeAmount = {
|
onChangeAmount = {
|
||||||
wantsToZap = false
|
wantsToZap = null
|
||||||
},
|
},
|
||||||
onError = { _, message ->
|
onError = { _, message ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -395,6 +397,11 @@ fun ZapDonationButton(
|
|||||||
showErrorMessageDialog = it
|
showErrorMessageDialog = it
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
justShowError = {
|
||||||
|
scope.launch {
|
||||||
|
showErrorMessageDialog = it
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,3 +455,58 @@ fun ZapDonationButton(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun customZapClick(
|
||||||
|
baseNote: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
context: Context,
|
||||||
|
onZappingProgress: (Float) -> Unit,
|
||||||
|
onMultipleChoices: (List<Long>) -> Unit,
|
||||||
|
onError: (String, String) -> Unit,
|
||||||
|
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
||||||
|
) {
|
||||||
|
if (baseNote.isDraft()) {
|
||||||
|
accountViewModel.toast(
|
||||||
|
R.string.draft_note,
|
||||||
|
R.string.it_s_not_possible_to_zap_to_a_draft_note,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accountViewModel.account.zapAmountChoices.isEmpty()) {
|
||||||
|
accountViewModel.toast(
|
||||||
|
context.getString(R.string.error_dialog_zap_error),
|
||||||
|
context.getString(R.string.no_zap_amount_setup_long_press_to_change),
|
||||||
|
)
|
||||||
|
} else if (!accountViewModel.isWriteable()) {
|
||||||
|
accountViewModel.toast(
|
||||||
|
context.getString(R.string.error_dialog_zap_error),
|
||||||
|
context.getString(R.string.login_with_a_private_key_to_be_able_to_send_zaps),
|
||||||
|
)
|
||||||
|
} else if (accountViewModel.account.zapAmountChoices.size == 1) {
|
||||||
|
val amount = accountViewModel.account.zapAmountChoices.first()
|
||||||
|
|
||||||
|
if (amount > 600) {
|
||||||
|
accountViewModel.zap(
|
||||||
|
baseNote,
|
||||||
|
amount * 1000,
|
||||||
|
null,
|
||||||
|
"",
|
||||||
|
context,
|
||||||
|
onError = onError,
|
||||||
|
onProgress = { onZappingProgress(it) },
|
||||||
|
zapType = accountViewModel.account.defaultZapType,
|
||||||
|
onPayViaIntent = onPayViaIntent,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
onMultipleChoices(listOf(1000L, 5_000L, 10_000L))
|
||||||
|
// recommends amounts for a monthly release.
|
||||||
|
}
|
||||||
|
} else if (accountViewModel.account.zapAmountChoices.size > 1) {
|
||||||
|
if (accountViewModel.account.zapAmountChoices.any { it > 600 }) {
|
||||||
|
onMultipleChoices(accountViewModel.account.zapAmountChoices)
|
||||||
|
} else {
|
||||||
|
onMultipleChoices(listOf(1000L, 5_000L, 10_000L))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1193,7 +1193,7 @@ fun DisplayLNAddress(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
payViaIntent(it, context) { showErrorMessageDialog = it }
|
payViaIntent(it, context, { zapExpanded = false }, { showErrorMessageDialog = it })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClose = { zapExpanded = false },
|
onClose = { zapExpanded = false },
|
||||||
|
@@ -1,2 +1,109 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools"></resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="point_to_the_qr_code">Najedź na kod QR</string>
|
||||||
|
<string name="show_qr">Pokaż QR kod</string>
|
||||||
|
<string name="profile_image">Zdjęcie profilowe</string>
|
||||||
|
<string name="your_profile_image">Twoje zdjęcie profilowe</string>
|
||||||
|
<string name="scan_qr">Zeskanuj QR kod</string>
|
||||||
|
<string name="show_anyway">Pokaż mimo wszystko</string>
|
||||||
|
<string name="post_was_hidden">Ten post został ukryty, ponieważ dotyczy ukrytych użytkowników lub słów</string>
|
||||||
|
<string name="post_was_flagged_as_inappropriate_by">Post został wyciszony lub zgłoszony przez</string>
|
||||||
|
<string name="channel_image">Zdjęcie kanału</string>
|
||||||
|
<string name="referenced_event_not_found">Przywołane zdarzenie nie zostało znalezione</string>
|
||||||
|
<string name="could_not_decrypt_the_message">Nie można odszyfrować wiadomości</string>
|
||||||
|
<string name="group_picture">Zdjęcie grupy</string>
|
||||||
|
<string name="explicit_content">Niedozwolona zawartość</string>
|
||||||
|
<string name="spam">Spam</string>
|
||||||
|
<string name="impersonation">Podszywanie się</string>
|
||||||
|
<string name="illegal_behavior">Nielegalne zachowanie</string>
|
||||||
|
<string name="relay_icon">Ikona retransmitera</string>
|
||||||
|
<string name="unknown_author">Autor nieznany</string>
|
||||||
|
<string name="copy_text">Skopiuj tekst</string>
|
||||||
|
<string name="block_report">Zablokuj / Zgłoś</string>
|
||||||
|
<string name="block_hide_user"><![CDATA[Zablokuj i ukryj użytkownika]]></string>
|
||||||
|
<string name="report_spam_scam">Zgłoś spam/oszustwo</string>
|
||||||
|
<string name="report_impersonation">Zgłoś podszywanie się</string>
|
||||||
|
<string name="report_explicit_content">Zgłoś niedozwoloną zawartość</string>
|
||||||
|
<string name="add">Dodaj</string>
|
||||||
|
<string name="payment_successful">Płatność zakończona pomyślnie</string>
|
||||||
|
<string name="log_out">Wyloguj się</string>
|
||||||
|
<string name="pay">Zapłać</string>
|
||||||
|
<string name="thank_you_so_much">Dziękuję bardzo!</string>
|
||||||
|
<string name="new_channel">Nowy kanał</string>
|
||||||
|
<string name="channel_name">Nazwa kanału</string>
|
||||||
|
<string name="my_awesome_group">Moja wspaniała Grupa</string>
|
||||||
|
<string name="picture_url">Adres URL zdjęcia</string>
|
||||||
|
<string name="about_us">"O nas. "</string>
|
||||||
|
<string name="what_s_on_your_mind">Co masz na myśli?</string>
|
||||||
|
<string name="save">Zapisz</string>
|
||||||
|
<string name="create">Utwórz</string>
|
||||||
|
<string name="cancel">Anuluj</string>
|
||||||
|
<string name="relay_address">Adres retransmitera</string>
|
||||||
|
<string name="add_a_relay">Dodaj Retransmiter</string>
|
||||||
|
<string name="username">Nazwa użytkownika</string>
|
||||||
|
<string name="website_url">Adres URL strony</string>
|
||||||
|
<string name="upload_image">Dodaj zdjęcie</string>
|
||||||
|
<string name="more_options">Więcej opcji</string>
|
||||||
|
<string name="relays">" Retransmitery"</string>
|
||||||
|
<string name="unblock">Odblokuj</string>
|
||||||
|
<string name="copy_user_id">Kopiuj ID użytkownika</string>
|
||||||
|
<string name="unblock_user">Odblokuj użytkownika</string>
|
||||||
|
<string name="npub_hex_username">"npub, nazwa użytkownika, tekst"</string>
|
||||||
|
<string name="clear">Wyczyść</string>
|
||||||
|
<string name="app_logo">Logo aplikacji</string>
|
||||||
|
<string name="nsec_npub_hex_private_key">nsec. lub npub.</string>
|
||||||
|
<string name="show_password">Pokaż hasło</string>
|
||||||
|
<string name="hide_password">Ukryj hasło</string>
|
||||||
|
<string name="invalid_key">Nieprawidłowy klucz</string>
|
||||||
|
<string name="i_accept_the">"Akceptuję "</string>
|
||||||
|
<string name="terms_of_use">warunki użytkowania</string>
|
||||||
|
<string name="acceptance_of_terms_is_required">Wymagane jest zaakceptowanie warunków użytkowania</string>
|
||||||
|
<string name="password_is_required">Hasło jest wymagane</string>
|
||||||
|
<string name="login">Zaloguj się</string>
|
||||||
|
<string name="sign_up">Zarejestruj się</string>
|
||||||
|
<string name="create_account">Utwórz konto</string>
|
||||||
|
<string name="how_should_we_call_you">Jak się do ciebie zwracać?</string>
|
||||||
|
<string name="don_t_have_an_account">Nie posiadasz konta Nostr?</string>
|
||||||
|
<string name="already_have_an_account">Masz już konto Nostr?</string>
|
||||||
|
<string name="create_a_new_account">Utwórz nowe konto</string>
|
||||||
|
<string name="generate_a_new_key">Wygeneruj nowy klucz</string>
|
||||||
|
<string name="try_again">Spróbuj ponownie</string>
|
||||||
|
<string name="refresh">Odśwież</string>
|
||||||
|
<string name="and_picture">i zdjęcie</string>
|
||||||
|
<string name="changed_chat_name_to">zmieniono nazwę czatu na</string>
|
||||||
|
<string name="leave">Wyjdź</string>
|
||||||
|
<string name="unfollow">Przestań obserwować</string>
|
||||||
|
<string name="public_chat">Czat Publiczny</string>
|
||||||
|
<string name="remove">Usuń</string>
|
||||||
|
<string name="select_text_dialog_top">Zaznacz tekst</string>
|
||||||
|
<string name="account_switch_add_account_dialog_title">Dodaj nowe konto</string>
|
||||||
|
<string name="drawer_accounts">Konta</string>
|
||||||
|
<string name="account_switch_select_account">Wybierz Konto</string>
|
||||||
|
<string name="account_switch_add_account_btn">Dodaj konto</string>
|
||||||
|
<string name="back">Wstecz</string>
|
||||||
|
<string name="quick_action_select">Wybierz</string>
|
||||||
|
<string name="quick_action_copy_text">Skopiuj tekst</string>
|
||||||
|
<string name="quick_action_delete">Usuń</string>
|
||||||
|
<string name="quick_action_unfollow">Przestań obserwować</string>
|
||||||
|
<string name="quick_action_follow">Śledź</string>
|
||||||
|
<string name="quick_action_dont_show_again_button">Nie pokazuj ponownie</string>
|
||||||
|
<string name="custom_zaps_add_a_message">Dodaj wiadomość publiczną</string>
|
||||||
|
<string name="custom_zaps_add_a_message_private">Dodaj prywatną wiadomość</string>
|
||||||
|
<string name="custom_zaps_add_a_message_example">Dziękujemy za całą twoją pracę!</string>
|
||||||
|
<string name="lightning_create_and_add_invoice">Utwórz i Dodaj</string>
|
||||||
|
<string name="content_description_add_video">Dodaj wideo</string>
|
||||||
|
<string name="content_description_add_document">Dodaj dokument</string>
|
||||||
|
<string name="zap_type_public_explainer">Każdy może zobaczyć transakcję i wiadomość</string>
|
||||||
|
<string name="zap_type_private_explainer">Nadawca i odbiorca mogą zobaczyć się nawzajem i przeczytać wiadomość</string>
|
||||||
|
<string name="upload_server_relays_nip95">Twoje retransmitery (NIP-95)</string>
|
||||||
|
<string name="upload_server_relays_nip95_explainer">Pliki są przechowywane przez Twoje retransmitery. Nowy NIP: sprawdź, czy jest obsługiwany</string>
|
||||||
|
<string name="read_from_relay">Odczytaj z Retransmitera</string>
|
||||||
|
<string name="write_to_relay">Zapisz do Retransmitera</string>
|
||||||
|
<string name="an_error_occurred_trying_to_get_relay_information">Wystąpił błąd podczas próby uzyskania informacji o retransmiterze z %1$s</string>
|
||||||
|
<string name="relay_setup">Retransmitery</string>
|
||||||
|
<string name="select_a_relay_to_continue">Wybierz retransmiter, aby kontynuować</string>
|
||||||
|
<string name="unable_to_download_relay_document">Nie można pobrać dokumentu retransmitera</string>
|
||||||
|
<string name="relay_info">Retransmiter %1$s</string>
|
||||||
|
<string name="expand_relay_list">Rozwiń listę retransmiterów</string>
|
||||||
|
<string name="relay_list_selector">Wybór listy retransmiterów</string>
|
||||||
|
</resources>
|
||||||
|
@@ -130,7 +130,7 @@ object MetaTagsParser {
|
|||||||
// - commonly used character references in attribute values are resolved
|
// - commonly used character references in attribute values are resolved
|
||||||
private class Attrs {
|
private class Attrs {
|
||||||
companion object {
|
companion object {
|
||||||
val RE_CHAR_REF = Regex("""&(\w+)(;?)""")
|
val RE_CHAR_REF = Regex("""&(#?)(\w+)(;?)""")
|
||||||
val BASE_CHAR_REFS =
|
val BASE_CHAR_REFS =
|
||||||
mapOf(
|
mapOf(
|
||||||
"amp" to "&",
|
"amp" to "&",
|
||||||
@@ -141,6 +141,8 @@ object MetaTagsParser {
|
|||||||
"LT" to "<",
|
"LT" to "<",
|
||||||
"gt" to ">",
|
"gt" to ">",
|
||||||
"GT" to ">",
|
"GT" to ">",
|
||||||
|
"nbsp" to " ",
|
||||||
|
"NBSP" to " ",
|
||||||
)
|
)
|
||||||
val CHAR_REFS =
|
val CHAR_REFS =
|
||||||
mapOf(
|
mapOf(
|
||||||
@@ -148,16 +150,26 @@ object MetaTagsParser {
|
|||||||
"equals" to "=",
|
"equals" to "=",
|
||||||
"grave" to "`",
|
"grave" to "`",
|
||||||
"DiacriticalGrave" to "`",
|
"DiacriticalGrave" to "`",
|
||||||
|
"039" to "'",
|
||||||
|
"8217" to "’",
|
||||||
|
"8216" to "‘",
|
||||||
|
"39" to "'",
|
||||||
|
"ldquo" to "“",
|
||||||
|
"rdquo" to "”",
|
||||||
|
"mdash" to "—",
|
||||||
|
"hellip" to "…",
|
||||||
|
"x27" to "'",
|
||||||
|
"nbsp" to " ",
|
||||||
)
|
)
|
||||||
|
|
||||||
fun replaceCharRefs(match: MatchResult): String {
|
fun replaceCharRefs(match: MatchResult): String {
|
||||||
val bcr = BASE_CHAR_REFS[match.groupValues[1]]
|
val bcr = BASE_CHAR_REFS[match.groupValues[2]]
|
||||||
if (bcr != null) {
|
if (bcr != null) {
|
||||||
return bcr
|
return bcr
|
||||||
}
|
}
|
||||||
// non-base char refs must be terminated by ';'
|
// non-base char refs must be terminated by ';'
|
||||||
if (match.groupValues[2].isNotEmpty()) {
|
if (match.groupValues[3].isNotEmpty()) {
|
||||||
val cr = CHAR_REFS[match.groupValues[1]]
|
val cr = CHAR_REFS[match.groupValues[2]]
|
||||||
if (cr != null) {
|
if (cr != null) {
|
||||||
return cr
|
return cr
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,7 @@ espressoCore = "3.5.1"
|
|||||||
firebaseBom = "33.0.0"
|
firebaseBom = "33.0.0"
|
||||||
fragmentKtx = "1.7.0"
|
fragmentKtx = "1.7.0"
|
||||||
gms = "4.4.1"
|
gms = "4.4.1"
|
||||||
jacksonModuleKotlin = "2.17.0"
|
jacksonModuleKotlin = "2.17.1"
|
||||||
jna = "5.14.0"
|
jna = "5.14.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
kotlin = "1.9.23"
|
kotlin = "1.9.23"
|
||||||
|
Reference in New Issue
Block a user