Compare commits

...
15 Commits
Author SHA1 Message Date
Vitor Pamplona ae6f37a52f v1.04.2 to appease the PlayStore 2025-11-15 17:28:55 -05:00
Vitor PamplonaandGitHub f6e0124734 Merge pull request #1571 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-11-15 16:55:10 -05:00
Crowdin Bot b6a7d9146f New Crowdin translations by GitHub Action 2025-11-15 21:52:58 +00:00
Vitor PamplonaandGitHub 8492a597c5 Merge pull request #1570 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-11-15 16:52:28 -05:00
Vitor Pamplona 5264bd2cbc v1.04.1 2025-11-15 16:51:45 -05:00
Crowdin Bot f630c34045 New Crowdin translations by GitHub Action 2025-11-15 21:51:29 +00:00
Vitor Pamplona 042ff4c5ed Merge branch 'main' of https://github.com/vitorpamplona/amethyst
* 'main' of https://github.com/vitorpamplona/amethyst:
  New Crowdin translations by GitHub Action
2025-11-15 16:49:33 -05:00
Vitor Pamplona f7e72c57d3 Turn the Pay zaps via intent dialog into a full screen with route 2025-11-15 16:41:47 -05:00
Vitor Pamplona fef78cd557 Fixes test case for payload type 2025-11-15 16:41:02 -05:00
Vitor Pamplona 673d8d2c30 Makes sure the Zap the Devs dialog doesn't appear behind the status bar 2025-11-15 16:11:58 -05:00
Vitor PamplonaandGitHub 414aef1818 Merge pull request #1569 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2025-11-15 15:41:18 -05:00
Crowdin Bot bbc4f4281a New Crowdin translations by GitHub Action 2025-11-15 20:30:16 +00:00
Vitor Pamplona 14e3098924 Fixes deserialization issues with old jsons that didn't have these properties 2025-11-15 15:27:57 -05:00
Vitor Pamplona 7742e27780 Moves all hashtags to lowercase and unfollows with ignore case just in case some other client added in uppercase 2025-11-15 15:20:56 -05:00
Vitor Pamplona a1879a93df sending a new version for quartz 2025-11-15 12:26:38 -05:00
26 changed files with 324 additions and 229 deletions
+18
View File
@@ -1,3 +1,21 @@
<a id="v1.04.1"></a>
# [Release v1.04.1: Bugfixes](https://github.com/vitorpamplona/amethyst/releases/tag/v1.04.1) - 2025-11-15
#Amethyst v1.04.1: Bug fixes
- Fixes crashing when starting
- Fixes hashtag unfollowing for mixed case tags
- Fixes release Id for the zap the devs button
- Fixes quartz version for a release to Maven
- Fixes disappearing of the Zap the Devs Manual Payment screen
- Fixes back button of the Zap the Devs Manual Payment screen staying behind the status bar
Translations:
- Polish by @npub16gjyljum0ksrrm28zzvejydgxwfm7xse98zwc4hlgq8epxeuggushqwyrm
- Hungarian by @npub1dnvslq0vvrs8d603suykc4harv94yglcxwna9sl2xu8grt2afm3qgfh0tp
Download: http://amethyst.social
<a id="v1.04.0"></a>
# [Release v1.04.0: List Management](https://github.com/vitorpamplona/amethyst/releases/tag/v1.04.0) - 2025-11-15
+2 -2
View File
@@ -44,8 +44,8 @@ android {
applicationId = "com.vitorpamplona.amethyst"
minSdk = libs.versions.android.minSdk.get().toInteger()
targetSdk = libs.versions.android.targetSdk.get().toInteger()
versionCode = 429
versionName = generateVersionName("1.04.0")
versionCode = 430
versionName = generateVersionName("1.04.2")
buildConfigField "String", "RELEASE_NOTES_ID", "\"3a03c75d85aaf6b181d3b232d064c4d4feea5c73f0bea2bd91ed61b8da7cd6a6\""
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -110,7 +110,7 @@ class ImageUploadTesting {
.upload(
inputStream = inputStream,
hash = initialHash,
length = paylod.size,
length = paylod.size.toLong(),
baseFileName = "filename.png",
contentType = "image/png",
alt = null,
@@ -77,9 +77,9 @@ private const val DEBUG_PREFERENCES_NAME = "debug_prefs"
@Serializable
data class AccountInfo(
val npub: String,
val hasPrivKey: Boolean,
val loggedInWithExternalSigner: Boolean,
val isTransient: Boolean,
val hasPrivKey: Boolean = false,
val loggedInWithExternalSigner: Boolean = false,
val isTransient: Boolean = false,
)
private object PrefKeys {
@@ -89,9 +89,9 @@ class HashtagListState(
val hashtagList = getHashtagList()
return if (hashtagList == null) {
HashtagListEvent.create(hashtag, true, signer)
HashtagListEvent.create(hashtag.lowercase(), true, signer)
} else {
HashtagListEvent.add(hashtagList, hashtag, true, signer)
HashtagListEvent.add(hashtagList, hashtag.lowercase(), true, signer)
}
}
@@ -141,7 +141,7 @@ fun uriToRoute(
return Route.Notification
}
if (isHashtagRoute(uri)) {
return Route.Hashtag(uri.removePrefix("nostr:").removePrefix("hashtag?id="))
return Route.Hashtag(uri.removePrefix("nostr:").removePrefix("hashtag?id=").lowercase())
}
val nip19 = Nip19Parser.uriToRoute(uri)?.entity
@@ -50,12 +50,14 @@ import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderFilterAssemblerSubscription
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.components.toasts.multiline.UserBasedErrorMessage
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.note.ObserveZapIcon
import com.vitorpamplona.amethyst.ui.note.PayViaIntentDialog
import com.vitorpamplona.amethyst.ui.note.ZapAmountChoicePopup
import com.vitorpamplona.amethyst.ui.note.ZapIcon
import com.vitorpamplona.amethyst.ui.note.ZappedIcon
import com.vitorpamplona.amethyst.ui.note.payViaIntent
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.ModifierWidth3dp
@@ -64,11 +66,12 @@ import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
import com.vitorpamplona.amethyst.ui.theme.Size35dp
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
/**
* Configuration for zap button behavior and appearance
@@ -91,6 +94,7 @@ data class ZapButtonCallbacks(
val onZapComplete: ((Boolean) -> Unit)? = null,
)
@OptIn(ExperimentalUuidApi::class)
@Composable
fun ReusableZapButton(
baseNote: Note,
@@ -100,9 +104,6 @@ fun ReusableZapButton(
callbacks: ZapButtonCallbacks = ZapButtonCallbacks(),
) {
var wantsToZap by remember { mutableStateOf<ImmutableList<Long>?>(null) }
var wantsToPay by remember(baseNote) {
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(persistentListOf())
}
// Makes sure the user is loaded to get his ln address ahead of time (for DVM buttons)
if (config.showUserFinderSubscription) {
@@ -138,7 +139,19 @@ fun ReusableZapButton(
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, toUser)
}
},
onPayViaIntent = { wantsToPay = it },
onPayViaIntent = {
if (it.size == 1) {
val payable = it.first()
payViaIntent(payable.invoice, context, { }) {
zappingProgress = 0f
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, UserBasedErrorMessage(it, payable.info.user))
}
} else {
val uid = Uuid.random().toString()
accountViewModel.tempManualPaymentCache.put(uid, it)
nav.nav(Route.ManualZapSplitPayment(uid))
}
},
)
},
modifier = Modifier.fillMaxWidth(),
@@ -166,25 +179,17 @@ fun ReusableZapButton(
onProgress = {
scope.launch(Dispatchers.Main) { zappingProgress = it }
},
onPayViaIntent = { wantsToPay = it },
)
}
if (wantsToPay.isNotEmpty()) {
PayViaIntentDialog(
payingInvoices = wantsToPay,
accountViewModel = accountViewModel,
onClose = { wantsToPay = persistentListOf() },
onError = {
wantsToPay = persistentListOf()
scope.launch {
zappingProgress = 0f
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
}
},
justShowError = {
scope.launch {
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
onPayViaIntent = {
if (it.size == 1) {
val payable = it.first()
payViaIntent(payable.invoice, context, { }) {
zappingProgress = 0f
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, UserBasedErrorMessage(it, payable.info.user))
}
} else {
val uid = Uuid.random().toString()
accountViewModel.tempManualPaymentCache.put(uid, it)
nav.nav(Route.ManualZapSplitPayment(uid))
}
},
)
@@ -692,7 +692,7 @@ fun HashTag(
modifier =
remember {
Modifier.clickable {
nav.nav(Route.Hashtag(segment.hashtag))
nav.nav(Route.Hashtag(segment.hashtag.lowercase()))
}
},
inlineContent =
@@ -53,6 +53,7 @@ import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.navigation.routes.getRouteWithArguments
import com.vitorpamplona.amethyst.ui.navigation.routes.isBaseRoute
import com.vitorpamplona.amethyst.ui.navigation.routes.isSameRoute
import com.vitorpamplona.amethyst.ui.note.PayViaIntentScreen
import com.vitorpamplona.amethyst.ui.note.nip22Comments.ReplyCommentPostScreen
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountSwitcherAndLeftDrawerLayout
@@ -137,6 +138,8 @@ fun AppNavigation(
composableFromBottomArgs<Route.PeopleListMetadataEdit> { PeopleListMetadataScreen(it.dTag, accountViewModel, nav) }
composableFromBottomArgs<Route.FollowPackMetadataEdit> { FollowPackMetadataScreen(it.dTag, accountViewModel, nav) }
composableFromBottomArgs<Route.ManualZapSplitPayment> { PayViaIntentScreen(it.paymentId, accountViewModel, nav) }
composableFromBottomArgs<Route.EditProfile> { NewUserMetadataScreen(nav, accountViewModel) }
composable<Route.Search> { SearchScreen(accountViewModel, nav) }
@@ -252,6 +252,11 @@ sealed class Route {
val version: String? = null,
val draft: String? = null,
) : Route()
@Serializable
data class ManualZapSplitPayment(
val paymentId: String,
) : Route()
}
inline fun <reified T : Route> isBaseRoute(navController: NavHostController): Boolean = navController.currentBackStackEntry?.destination?.hasRoute<T>() == true
@@ -310,6 +315,7 @@ fun getRouteWithArguments(navController: NavHostController): Route? {
dest.hasRoute<Route.PeopleListManagement>() -> entry.toRoute<Route.PeopleListManagement>()
dest.hasRoute<Route.NewGroupDM>() -> entry.toRoute<Route.NewGroupDM>()
dest.hasRoute<Route.UserSettings>() -> entry.toRoute<Route.UserSettings>()
dest.hasRoute<Route.ManualZapSplitPayment>() -> entry.toRoute<Route.ManualZapSplitPayment>()
else -> {
null
@@ -52,7 +52,6 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -81,6 +80,7 @@ import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
import com.vitorpamplona.amethyst.ui.components.toasts.StringToastMsg
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.navigation.routes.routeToMessage
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
@@ -102,14 +102,14 @@ import com.vitorpamplona.quartz.nip01Core.core.ImmutableListOfLists
import com.vitorpamplona.quartz.nip01Core.core.toImmutableListOfLists
import com.vitorpamplona.quartz.nip31Alts.AltTag
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlin.math.roundToInt
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
@Preview
@Composable
@@ -499,7 +499,7 @@ private fun RenderOptionBeforeVote(
}
@Composable
@OptIn(ExperimentalFoundationApi::class)
@OptIn(ExperimentalFoundationApi::class, ExperimentalUuidApi::class)
fun ZapVote(
baseNote: Note,
poolOption: PollOption,
@@ -513,14 +513,8 @@ fun ZapVote(
val isLoggedUser by remember { derivedStateOf { accountViewModel.isLoggedUser(baseNote.author) } }
var wantsToZap by remember { mutableStateOf(false) }
var wantsToPay by remember {
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(
persistentListOf(),
)
}
var zappingProgress by remember { mutableFloatStateOf(0f) }
var zapStartingTime by remember { mutableLongStateOf(0L) }
var showErrorMessageDialog by remember { mutableStateOf<StringToastMsg?>(null) }
@@ -563,7 +557,6 @@ fun ZapVote(
accountViewModel.zapAmountChoices().size == 1 &&
pollViewModel.isValidInputVoteAmount(accountViewModel.zapAmountChoices().first())
) {
zapStartingTime = TimeUtils.now()
accountViewModel.zap(
baseNote,
accountViewModel.zapAmountChoices().first() * 1000,
@@ -584,12 +577,13 @@ fun ZapVote(
),
) {
if (wantsToZap) {
val context = LocalContext.current
FilteredZapAmountChoicePopup(
baseNote,
accountViewModel,
pollViewModel,
poolOption.option,
onZapStarts = { zapStartingTime = TimeUtils.now() },
onZapStarts = { },
onDismiss = {
wantsToZap = false
zappingProgress = 0f
@@ -600,33 +594,17 @@ fun ZapVote(
zappingProgress = 0f
},
onProgress = { scope.launch(Dispatchers.Main) { zappingProgress = it } },
onPayViaIntent = { wantsToPay = it },
)
}
if (wantsToPay.isNotEmpty()) {
PayViaIntentDialog(
payingInvoices = wantsToPay,
accountViewModel = accountViewModel,
onClose = { wantsToPay = persistentListOf() },
onError = {
wantsToPay = persistentListOf()
scope.launch {
zappingProgress = 0f
showErrorMessageDialog =
StringToastMsg(
stringRes(context, R.string.error_dialog_zap_error),
it.error,
)
}
},
justShowError = {
scope.launch {
showErrorMessageDialog =
StringToastMsg(
stringRes(context, R.string.error_dialog_zap_error),
it.error,
)
onPayViaIntent = {
if (it.size == 1) {
val payable = it.first()
payViaIntent(payable.invoice, context, { }) { error ->
zappingProgress = 0f
showErrorMessageDialog = StringToastMsg(stringRes(context, R.string.error_dialog_zap_error), error)
}
} else {
val uid = Uuid.random().toString()
accountViewModel.tempManualPaymentCache.put(uid, it)
nav.nav(Route.ManualZapSplitPayment(uid))
}
},
)
@@ -114,6 +114,7 @@ import com.vitorpamplona.amethyst.ui.components.AnimatedBorderTextCornerRadius
import com.vitorpamplona.amethyst.ui.components.ClickableBox
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
import com.vitorpamplona.amethyst.ui.components.toasts.multiline.UserBasedErrorMessage
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.navigation.routes.routeReplyTo
@@ -169,6 +170,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid
@Composable
fun ReactionsRow(
@@ -970,7 +973,7 @@ private fun likeClick(
}
@Composable
@OptIn(ExperimentalFoundationApi::class)
@OptIn(ExperimentalFoundationApi::class, ExperimentalUuidApi::class)
fun ZapReaction(
baseNote: Note,
grayTint: Color,
@@ -983,12 +986,6 @@ fun ZapReaction(
var wantsToZap by remember { mutableStateOf(false) }
var wantsToChangeZapAmount by remember { mutableStateOf(false) }
var wantsToSetCustomZap by remember { mutableStateOf(false) }
var wantsToPay by
remember(baseNote) {
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(
persistentListOf(),
)
}
val context = LocalContext.current
val scope = rememberCoroutineScope()
@@ -1022,7 +1019,19 @@ fun ZapReaction(
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, message, user)
}
},
onPayViaIntent = { wantsToPay = it },
onPayViaIntent = {
if (it.size == 1) {
val payable = it.first()
payViaIntent(payable.invoice, context, { }) { error ->
zappingProgress = 0f
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, UserBasedErrorMessage(error, payable.info.user))
}
} else {
val uid = Uuid.random().toString()
accountViewModel.tempManualPaymentCache.put(uid, it)
nav.nav(Route.ManualZapSplitPayment(uid))
}
},
)
}
},
@@ -1053,7 +1062,19 @@ fun ZapReaction(
}
},
onProgress = { scope.launch(Dispatchers.Main) { zappingProgress = it } },
onPayViaIntent = { wantsToPay = it },
onPayViaIntent = {
if (it.size == 1) {
val payable = it.first()
payViaIntent(payable.invoice, context, { }) { error ->
zappingProgress = 0f
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, UserBasedErrorMessage(error, payable.info.user))
}
} else {
val uid = Uuid.random().toString()
accountViewModel.tempManualPaymentCache.put(uid, it)
nav.nav(Route.ManualZapSplitPayment(uid))
}
},
)
}
@@ -1064,26 +1085,6 @@ fun ZapReaction(
)
}
if (wantsToPay.isNotEmpty()) {
PayViaIntentDialog(
payingInvoices = wantsToPay,
accountViewModel = accountViewModel,
onClose = { wantsToPay = persistentListOf() },
onError = {
wantsToPay = persistentListOf()
scope.launch {
zappingProgress = 0f
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
}
},
justShowError = {
scope.launch {
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, it)
}
},
)
}
if (wantsToSetCustomZap) {
ZapCustomDialog(
onZapStarts = { zapStartingTime = TimeUtils.now() },
@@ -1095,7 +1096,19 @@ fun ZapReaction(
}
},
onProgress = { scope.launch(Dispatchers.Main) { zappingProgress = it } },
onPayViaIntent = { wantsToPay = it },
onPayViaIntent = {
if (it.size == 1) {
val payable = it.first()
payViaIntent(payable.invoice, context, { }) { error ->
zappingProgress = 0f
accountViewModel.toastManager.toast(R.string.error_dialog_zap_error, UserBasedErrorMessage(error, payable.info.user))
}
} else {
val uid = Uuid.random().toString()
accountViewModel.tempManualPaymentCache.put(uid, it)
nav.nav(Route.ManualZapSplitPayment(uid))
}
},
accountViewModel = accountViewModel,
baseNote = baseNote,
)
@@ -26,15 +26,18 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -42,6 +45,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -64,16 +68,16 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
import com.vitorpamplona.amethyst.ui.components.TextSpinner
import com.vitorpamplona.amethyst.ui.components.TitleExplainer
import com.vitorpamplona.amethyst.ui.components.toasts.multiline.UserBasedErrorMessage
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
import com.vitorpamplona.amethyst.ui.note.buttons.CloseButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.Size55dp
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
@@ -281,104 +285,115 @@ fun ZapButton(
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PayViaIntentDialog(
payingInvoices: ImmutableList<ZapPaymentHandler.Payable>,
fun PayViaIntentScreen(
paymentId: String,
accountViewModel: AccountViewModel,
onClose: () -> Unit,
onError: (UserBasedErrorMessage) -> Unit,
justShowError: (UserBasedErrorMessage) -> Unit,
nav: INav,
) {
val context = LocalContext.current
Scaffold(
topBar = {
TopBarWithBackButton(stringRes(id = R.string.manual_zaps), nav::popBack)
},
) { pad ->
val list = accountViewModel.tempManualPaymentCache.get(paymentId)
if (payingInvoices.size == 1) {
val payable = payingInvoices.first()
payViaIntent(payable.invoice, context, onClose) {
onError(UserBasedErrorMessage(it, payable.info.user))
}
} else {
Dialog(
onDismissRequest = onClose,
properties =
DialogProperties(
dismissOnClickOutside = false,
usePlatformDefaultWidth = false,
decorFitsSystemWindows = false,
if (list == null) {
Column(
Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Text(stringRes(R.string.feed_is_empty))
}
} else {
LazyColumn(
Modifier.padding(
16.dp,
pad.calculateTopPadding(),
16.dp,
pad.calculateBottomPadding(),
),
) {
SetDialogToEdgeToEdge()
Surface {
Column(modifier = Modifier.padding(10.dp).verticalScroll(rememberScrollState())) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
CloseButton(onPress = onClose)
}
Spacer(modifier = DoubleVertSpacer)
payingInvoices.forEachIndexed { index, payable ->
val paid = remember { mutableStateOf(false) }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = Size10dp),
) {
if (payable.info.user != null) {
BaseUserPicture(payable.info.user, Size55dp, accountViewModel = accountViewModel)
} else {
DisplayBlankAuthor(size = Size55dp, accountViewModel = accountViewModel)
}
Spacer(modifier = DoubleHorzSpacer)
Column(modifier = Modifier.weight(1f)) {
if (payable.info.user != null) {
UsernameDisplay(payable.info.user, accountViewModel = accountViewModel)
} else {
Text(
text = stringRes(id = R.string.wallet_number, index + 1),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
)
}
Row {
Text(
text = showAmount((payable.amountMilliSats / 1000.0f).toBigDecimal()),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
)
Spacer(modifier = StdHorzSpacer)
Text(
text = stringRes(id = R.string.sats),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
)
}
}
Spacer(modifier = DoubleHorzSpacer)
PayButton(isActive = !paid.value) {
payViaIntent(payable.invoice, context, { paid.value = true }) {
justShowError(UserBasedErrorMessage(it, null))
}
}
}
}
) {
itemsIndexed(
accountViewModel.tempManualPaymentCache.get(paymentId) ?: emptyList(),
key = { _: Int, invoice: ZapPaymentHandler.Payable ->
invoice.invoice
},
) { index, payable ->
DisplayPayable(index, payable, accountViewModel)
}
}
}
}
}
@Composable
fun DisplayPayable(
index: Int,
payable: ZapPaymentHandler.Payable,
accountViewModel: AccountViewModel,
) {
val paid = rememberSaveable(payable) { mutableStateOf(false) }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = Size10dp),
) {
if (payable.info.user != null) {
BaseUserPicture(payable.info.user, Size55dp, accountViewModel = accountViewModel)
} else {
DisplayBlankAuthor(size = Size55dp, accountViewModel = accountViewModel)
}
Spacer(modifier = DoubleHorzSpacer)
Column(modifier = Modifier.weight(1f)) {
if (payable.info.user != null) {
UsernameDisplay(payable.info.user, accountViewModel = accountViewModel)
} else {
Text(
text = stringRes(id = R.string.wallet_number, index + 1),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
)
}
Row {
Text(
text = showAmount((payable.amountMilliSats / 1000.0f).toBigDecimal()),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
)
Spacer(modifier = StdHorzSpacer)
Text(
text = stringRes(id = R.string.sats),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
)
}
}
Spacer(modifier = DoubleHorzSpacer)
val context = LocalContext.current
PayButton(isActive = !paid.value) {
payViaIntent(payable.invoice, context, { paid.value = true }) {
accountViewModel.toastManager.toast(
R.string.error_dialog_zap_error,
UserBasedErrorMessage(it, payable.info.user),
)
}
}
}
}
fun payViaIntent(
invoice: String,
context: Context,
@@ -86,6 +86,6 @@ private fun DisplayTagList(
overflow = TextOverflow.Ellipsis,
maxLines = 1,
) {
nav.nav(Route.Hashtag(firstTag))
nav.nav(Route.Hashtag(firstTag.lowercase()))
}
}
@@ -96,7 +96,7 @@ fun DisplayUncitedHashtags(
unusedHashtags.forEach { hashtag ->
ClickableTextColor(
text = "#$hashtag ",
onClick = { nav.nav(Route.Hashtag(hashtag)) },
onClick = { nav.nav(Route.Hashtag(hashtag.lowercase())) },
linkColor = MaterialTheme.colorScheme.lessImportantLink,
)
}
@@ -55,7 +55,7 @@ fun DisplayHashtagExternalId(
nav: INav,
) {
DisplayHashtagExternalId(externalId.topic) {
nav.nav(Route.Hashtag(externalId.topic))
nav.nav(Route.Hashtag(externalId.topic.lowercase()))
}
}
@@ -175,6 +175,8 @@ class AccountViewModel(
val toastManager = ToastManager()
val feedStates = AccountFeedContentStates(account, viewModelScope)
val tempManualPaymentCache = LruCache<String, List<ZapPaymentHandler.Payable>>(5)
@OptIn(ExperimentalCoroutinesApi::class)
val notificationHasNewItems =
combineTransform(
@@ -57,7 +57,7 @@ fun TabFollowedTags(
HashtagHeader(
tag = hashtag,
account = accountViewModel,
onClick = { nav.nav(Route.Hashtag(hashtag)) },
onClick = { nav.nav(Route.Hashtag(hashtag.lowercase())) },
)
HorizontalDivider(
thickness = DividerThickness,
@@ -257,7 +257,7 @@ private fun DisplaySearchResults(
hashTags,
key = { _, item -> "#$item" },
) { _, item ->
HashtagLine(item) { nav.nav(Route.Hashtag(item)) }
HashtagLine(item.lowercase()) { nav.nav(Route.Hashtag(item.lowercase())) }
HorizontalDivider(
modifier = Modifier.padding(top = 10.dp),
@@ -444,11 +444,13 @@
<string name="follow_list_selection">Követési lista</string>
<string name="follow_list_kind3follows">Követettek bejegyzései</string>
<string name="follow_list_kind3follows_users_only">Összes követett felhasználó</string>
<string name="follow_list_kind3_follows_users_only">Alapértelmezett követési lista</string>
<string name="follow_list_kind3follows_proxy">Követés proxyn keresztül</string>
<string name="follow_list_aroundme">Közelben lévők bejegyzései</string>
<string name="follow_list_global">Globális</string>
<string name="follow_list_mute_list">Némítottak bejegyzései</string>
<string name="follow_sets">Követési gyüjtemények</string>
<string name="follow_sets_explainer">Ezek a követési listák az Ön saját alkalmazás-használatából lettek kialakítva. A felhasználókat privát vagy nyilvánosan követheti</string>
<string name="labeled_bookmarks">Címkézett könyvjelzők</string>
<string name="general_bookmarks">Általános könyvjelzők</string>
<string name="follow_set_type_public">Nyílvános</string>
@@ -483,6 +485,7 @@
<string name="follow_set_creation_item_label">Új %1$s lista létrehozása felhasználóval</string>
<string name="follow_set_creation_item_description">%1$s követési gyüjtemény létrehozása, és hozzáadás a következhöz: %2$s.</string>
<string name="follow_set_creation_dialog_title">Új %1$s lista</string>
<string name="follow_pack_creation_dialog_title">Új követési lista</string>
<string name="follow_set_copy_dialog_title">Követési gyüjtemény másolása/klónozása</string>
<string name="follow_set_desc_modify_label">Leírás módosítása</string>
<string name="follow_set_empty_desc_label">Ennek a listának nincs leírása</string>
@@ -495,6 +498,7 @@
<string name="follow_set_creation_action_btn_label">Gyüjtemény létrehozása</string>
<string name="follow_set_copy_action_btn_label">Gyüjtemény másolása/klónozása</string>
<string name="follow_set_rename_btn_label">Gyüjtemény átnevezése</string>
<string name="follow_set_edit_list_metadata">Lista szerkesztése</string>
<string name="follow_set_desc_modify_btn_label">Módosítás</string>
<string name="follow_set_rename_dialog_indicator_first_part">Ön átnevezi a követési gyüjteményt erről: </string>
<string name="follow_set_rename_dialog_indicator_second_part"> erre:</string>
@@ -633,6 +637,7 @@
<string name="followed_tags">Követett címke</string>
<string name="relay_setup">Átjátszók</string>
<string name="discover_follows">Követett csomagok</string>
<string name="discover_follows_explainer">Ezek a listák azon felhasználók listái, akiket másoknak ajánlhat. Csak nyilvános felhasználók szerepelhetnek a listán.</string>
<string name="discover_reads">Olvasmányok</string>
<string name="discover_content_v2">Hírforrás-algoritmus</string>
<string name="discover_marketplace">Piac</string>
@@ -1119,5 +1124,23 @@
<string name="in_the_list">Már rajta van a listán</string>
<string name="private_members">Privát tagok</string>
<string name="public_members">Nyilvános tagok</string>
<string name="private_members_count">Privát tagok (%1$s)</string>
<string name="public_members_count">Nyilvános tagok (%1$s)</string>
<string name="add_user_to_the_list">Felhasználó hozzáadása a listához</string>
<string name="remove_user_from_the_list">Felhasználó törlése a listáról</string>
<string name="follow_list_item_label">Követési csomag</string>
<string name="members">Tagok</string>
<string name="people_list_title">Követési lista metaadatai</string>
<string name="people_list_explainer">A követési listák metaadatai a Nostr-on bárki számára láthatók. Csak a privát tagok adatai vannak titkosítva.</string>
<string name="follow_pack_title">Követési csomag metaadatai</string>
<string name="follow_pack_explainer">A követési csomag metaadatai a Nostr-on bárki számára láthatók, és gyakran megjelennek számos weboldalon, új felhasználók számára készült kezdőcsomagként.</string>
<string name="follow_pack_edit_list_metadata">Követési csomag szerkesztése</string>
<string name="follow_pack_creation_name_label">Csomagnév</string>
<string name="follow_pack_copy_name_label">Új csomagnév</string>
<string name="follow_pack_creation_desc_label">Csomagleírás</string>
<string name="follow_pack_copy_desc_label">Új csomagleírás</string>
<string name="follow_set_broadcast">Közvetítési lista</string>
<string name="follow_pack_broadcast">Közvetítési csomag</string>
<string name="follow_set_delete">Lista törlése</string>
<string name="follow_pack_delete">Csomag törlése</string>
</resources>
+45 -25
View File
@@ -55,7 +55,7 @@
<string name="login_with_a_private_key_to_be_able_to_upload">Używasz klucza publicznego i kluczy publicznych tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc wgrać</string>
<string name="login_with_a_private_key_to_be_able_to_sign_events">Używasz klucza publicznego i kluczy publicznych tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc zapisać się na udział w wydarzeniu</string>
<string name="unauthorized_exception">Nieautoryzowane odszyfrowywanie</string>
<string name="unauthorized_exception_description">Sygnatariusz nie autoryzował deszyfrowania wymaganego do wykonania tej operacji. Aktywuj deszyfrowanie NIP-44 w aplikacji podpisującej i spróbuj ponownie</string>
<string name="unauthorized_exception_description">Sygnatariusz nie autoryzował deszyfrowania wymaganego do wykonania tej operacji. Aktywuj deszyfrowanie NIP-44 w aplikacji logującej i spróbuj ponownie</string>
<string name="signer_not_found_exception">Nie odnaleziono sygnatariusza</string>
<string name="signer_not_found_exception_description">Czy aplikacja sygnatariusza została odinstalowana? Sprawdź, czy aplikacja sygnatariusza jest zainstalowana i czy ma to konto. Wyloguj się i zaloguj ponownie, jeśli aplikacja sygnatariusza uległa zmianie.</string>
<string name="zaps">Zapy</string>
@@ -80,7 +80,7 @@
<string name="followers">" Obserwujące"</string>
<string name="number_following">"%1$s Obserwowanych"</string>
<string name="number_followers">"%1$s Obserwujących"</string>
<string name="profile">Twój Profil</string>
<string name="profile">Mój Profil</string>
<string name="security_filters">Filtry bezpieczeństwa</string>
<string name="log_out">Wyloguj się</string>
<string name="show_more">Pokaż Więcej</string>
@@ -440,22 +440,24 @@
<string name="no">Nie</string>
<string name="follow_list_selection">Lista obserwowanych</string>
<string name="follow_list_kind3follows">Obserwowane</string>
<string name="follow_list_kind3follows_users_only">Wszyscy obserwujący</string>
<string name="follow_list_kind3follows_users_only">Obserwujące</string>
<string name="follow_list_kind3_follows_users_only">Domyślna lista obserwowanych</string>
<string name="follow_list_kind3follows_proxy">Obserwuje przez proxy</string>
<string name="follow_list_aroundme">W pobliżu</string>
<string name="follow_list_global">Wszystkie</string>
<string name="follow_list_mute_list">Zablokowane</string>
<string name="follow_sets">Zbiory obserwowanych</string>
<string name="follow_sets">Listy obserwowanych</string>
<string name="follow_sets_explainer">Są to listy obserwowanych przeznaczone do prywatnego użytku. Możesz obserwować użytkowników dyskretnie lub otwarcie</string>
<string name="labeled_bookmarks">Oznaczone zakładki</string>
<string name="general_bookmarks">Ogólne zakładki</string>
<string name="follow_set_type_public">Publiczna</string>
<string name="follow_set_type_private">Prywatna</string>
<string name="follow_set_type_mixed">Mieszana</string>
<string name="follow_set_empty_feed_msg"> Wygląda na to, że nie masz jeszcze żadnych zbiorów obserwowanych.
\nDotknij poniżej, aby odświeżyć, lub naciśnij przycisk Dodaj, aby utworzyć nowy.
<string name="follow_set_empty_feed_msg">Wygląda na to, że nie masz jeszcze żadnej listy obserwowanych.
\nDotknij poniżej, aby odświeżyć, lub naciśnij przycisk Dodaj, aby utworzyć listę.
</string>
<string name="follow_set_create_btn_label">Nowy</string>
<string name="follow_set_add_author_from_note_action">Dodaj autora do zbioru obserwowanych</string>
<string name="follow_set_create_btn_label">Nowa</string>
<string name="follow_set_add_author_from_note_action">Dodaj autora do listy obserwowanych</string>
<string name="follow_set_profile_actions_menu_description">Dodaj lub usuń użytkownika z list, lub utwórz nową listę z tym użytkownikiem.</string>
<string name="follow_set_icon_description">Ikona dla listy %1$s</string>
<string name="follow_set_public_presence_indicator">%1$s jest uczestnikiem publicznym</string>
@@ -473,25 +475,27 @@
<string name="follow_set_absence_indicator">%1$s nie jest na liście</string>
<string name="follow_set_absence_indicator2">%1$s nie jest uczestnikiem</string>
<string name="follow_set_man_dialog_title2">Twoje listy oraz %1$s</string>
<string name="follow_set_man_dialog_title">Twój zbiór obserwowanych</string>
<string name="follow_set_empty_dialog_msg">Nie znaleziono zbiorów obserwowanych lub nie masz żadnych zbiorów obserwowanych. Dotknij poniżej, aby odświeżyć lub użyj menu, aby go utworzyć.</string>
<string name="follow_set_man_dialog_title">Twoja lista obserwowanych</string>
<string name="follow_set_empty_dialog_msg">Nie znaleziono list obserwowanych lub nie masz żadnych list obserwowanych. Naciśnij poniżej, aby odświeżyć lub użyj menu, aby utworzyć nową.</string>
<string name="follow_set_error_dialog_msg">Podczas pobierania wystąpił błąd: %1$s</string>
<string name="follow_set_creation_menu_title">Utwórz nową listę</string>
<string name="follow_set_creation_item_label">\"Utwórz nową listę %1$s z użytkownikiem</string>
<string name="follow_set_creation_item_description">Tworzy zbiór obserwowanych %1$s i dodaje do niego %2$s.</string>
<string name="follow_set_creation_dialog_title">Nowa lista %1$s</string>
<string name="follow_set_copy_dialog_title">Kopiuj/Klonuj zbiór obserwowanych</string>
<string name="follow_set_creation_dialog_title">Nowa lista</string>
<string name="follow_pack_creation_dialog_title">Nowa Kategoria</string>
<string name="follow_set_copy_dialog_title">Kopiuj/Sklonuj listę obserwowanych</string>
<string name="follow_set_desc_modify_label">Modyfikuj opis</string>
<string name="follow_set_empty_desc_label">Ta lista nie ma opisu</string>
<string name="follow_set_current_desc_label">Aktualny opis:</string>
<string name="follow_set_copy_indicator_description">Możesz wybrać własną nazwę/opis dla sklonowanego zbioru poniżej.</string>
<string name="follow_set_creation_name_label">Nazwa zbioru</string>
<string name="follow_set_copy_indicator_description">Ustaw nową nazwę/opis dla sklonowanej listy poniżej.</string>
<string name="follow_set_creation_name_label">Nazwa listy</string>
<string name="follow_set_copy_name_label">(Nazwa Oryginalna)</string>
<string name="follow_set_creation_desc_label">Opis zbioru (opcjonalnie)</string>
<string name="follow_set_copy_desc_label">(Opis oryginalnego zbioru)</string>
<string name="follow_set_creation_action_btn_label">Utwórz zbiór</string>
<string name="follow_set_copy_action_btn_label">Kopiuj/Klonuj zbiór</string>
<string name="follow_set_rename_btn_label">Zmień nazwę zbioru</string>
<string name="follow_set_creation_desc_label">Opis listy (opcjonalnie)</string>
<string name="follow_set_copy_desc_label">Opis nowej listy</string>
<string name="follow_set_creation_action_btn_label">Utwórz listę</string>
<string name="follow_set_copy_action_btn_label">Kopiuj/Sklonuj listę</string>
<string name="follow_set_rename_btn_label">Zmień nazwę listy</string>
<string name="follow_set_edit_list_metadata">Edytuj listę</string>
<string name="follow_set_desc_modify_btn_label">Modyfikuj</string>
<string name="follow_set_rename_dialog_indicator_first_part">Zmieniasz nazwę z </string>
<string name="follow_set_rename_dialog_indicator_second_part"> do..</string>
@@ -630,6 +634,7 @@
<string name="followed_tags">Obserwowane tagi</string>
<string name="relay_setup">Transmitery</string>
<string name="discover_follows">Godne uwagi</string>
<string name="discover_follows_explainer">Są to listy profili, które polecasz innym użytkownikom. Dozwolone są wyłącznie profile publiczne.</string>
<string name="discover_reads">Popularne</string>
<string name="discover_content_v2">Wybrane przez algorytm</string>
<string name="discover_marketplace">Market</string>
@@ -777,11 +782,11 @@
<string name="lightning_wallets_not_found2">Nie znaleziono portfeli Lightning</string>
<string name="paid">Zapłacone</string>
<string name="wallet_number">Portfel %1$s</string>
<string name="error_opening_external_signer">Błąd podczas otwierania aplikacji podpisującej</string>
<string name="error_opening_external_signer_description">Nie można odnaleźć aplikacji podpisującej. Sprawdź, czy aplikacja nie została odinstalowana</string>
<string name="error_opening_external_signer">Błąd podczas otwierania aplikacji logującej</string>
<string name="error_opening_external_signer_description">Nie można odnaleźć aplikacji logującej. Sprawdź, czy aplikacja nie została odinstalowana</string>
<string name="sign_request_rejected2">Prośba o zalogowanie została odrzucona</string>
<string name="sign_request_rejected">Odrzucono aplikację podpisującego</string>
<string name="sign_request_rejected_description">Upewnij się, że aplikacja podpisującego autoryzuje tę transakcję</string>
<string name="sign_request_rejected">Prośba o zalogowanie odrzucona</string>
<string name="sign_request_rejected_description">Upewnij się, że aplikacja logującego autoryzuje tę operację</string>
<string name="no_wallet_found_with_error">Nie znaleziono portfeli do zapłacenia faktury z Lightning (Error: %1$s). Proszę zainstalować Lightning wallet, aby używać zapów</string>
<string name="no_wallet_found">Nie znaleziono portfeli do zapłacenia faktury z Lightning. Proszę zainstalować Lightning wallet, aby używać zapów</string>
<string name="hidden_words">Ukryte słowa</string>
@@ -1088,10 +1093,10 @@
<string name="torrent_failure">Nie udało się otworzyć pliku</string>
<string name="torrent_no_apps">Brak zainstalowanych aplikacji torrent do otwarcia i pobrania pliku.</string>
<string name="torrent_no_info">Zdarzenie nie ma wystarczającej ilości informacji, aby zbudować link magnetyczny</string>
<string name="my_lists_and_sets">Moje Listy/Zbiory</string>
<string name="my_lists_and_sets">Moje Listy/Zestawy</string>
<string name="my_lists">Moje listy</string>
<string name="people_list_label">Użytkownicy</string>
<string name="select_list_to_filter">Wybierz listę, aby filtrować aktualności</string>
<string name="select_list_to_filter">Wybierz listę profili, aby filtrować aktualności</string>
<string name="temporary_account">Wyloguj się przy blokowaniu urządzenia</string>
<string name="private_message">Wiadomość prywatna</string>
<string name="public_message">Publiczna wiadomość</string>
@@ -1116,6 +1121,21 @@
<string name="in_the_list">Już jest na liście</string>
<string name="private_members">Prywatni uczestnicy</string>
<string name="public_members">Uczestnicy publiczni</string>
<string name="private_members_count">Prywatni uczestnicy (%1$s)</string>
<string name="add_user_to_the_list">Dodaj użytkownika do listy</string>
<string name="remove_user_from_the_list">Usuń użytkownika z listy</string>
<string name="follow_list_item_label">Kategoria profilów</string>
<string name="members">Uczestnicy</string>
<string name="people_list_title">Metadane listy profili</string>
<string name="follow_pack_title">Metadane kategorii profilów</string>
<string name="follow_pack_explainer">Metadane kategorii profilów są widoczne dla wszystkich użytkowników serwisu Nostr i często publikowane na wielu stronach internetowych jako zestawy startowe dla nowych użytkowników.</string>
<string name="follow_pack_edit_list_metadata">Edytuj kategorię profili</string>
<string name="follow_pack_creation_name_label">Nazwa kategorii</string>
<string name="follow_pack_copy_name_label">Nowa nazwa kategorii</string>
<string name="follow_pack_creation_desc_label">Opis kategorii</string>
<string name="follow_pack_copy_desc_label">Opis nowej kategorii</string>
<string name="follow_set_broadcast">Upowszechnij listę</string>
<string name="follow_pack_broadcast">Upowszechnij kategorię</string>
<string name="follow_set_delete">Usuń listę</string>
<string name="follow_pack_delete">Usuń kategorię</string>
</resources>
+1
View File
@@ -378,6 +378,7 @@
<string name="report_dialog_title">Block and Report</string>
<string name="block_only">Block</string>
<string name="manual_zaps">Manual Zap Splits</string>
<string name="bookmarks">Bookmarks</string>
<string name="drafts">Drafts</string>
<string name="private_bookmarks">Private Bookmarks</string>
+1 -1
View File
@@ -255,7 +255,7 @@ mavenPublishing {
coordinates(
groupId = "com.vitorpamplona.quartz",
artifactId = "quartz",
version = "1.03.0"
version = "1.04.2"
)
// Configure publishing to Maven Central
@@ -24,6 +24,7 @@ import com.vitorpamplona.quartz.nip01Core.core.Tag
import com.vitorpamplona.quartz.nip01Core.core.TagArray
import com.vitorpamplona.quartz.utils.startsWith
import com.vitorpamplona.quartz.utils.startsWithAny
import com.vitorpamplona.quartz.utils.startsWithIgnoreCase
inline fun TagArray.filterToArray(predicate: (Array<String>) -> Boolean): TagArray = filterTo(ArrayList(), predicate).toTypedArray()
@@ -31,6 +32,8 @@ inline fun TagArray.remove(predicate: (Array<String>) -> Boolean): TagArray = fi
fun TagArray.remove(startsWith: Array<String>): TagArray = filterNotTo(ArrayList(this.size), { it.startsWith(startsWith) }).toTypedArray()
fun TagArray.removeIgnoreCase(startsWith: Array<String>): TagArray = filterNotTo(ArrayList(this.size), { it.startsWithIgnoreCase(startsWith) }).toTypedArray()
fun <R> TagArray.removeParsing(
transform: (Tag) -> R,
equalsTo: R,
@@ -36,8 +36,8 @@ import com.vitorpamplona.quartz.nip31Alts.alt
import com.vitorpamplona.quartz.nip51Lists.PrivateTagArrayEvent
import com.vitorpamplona.quartz.nip51Lists.encryption.PrivateTagsInContent
import com.vitorpamplona.quartz.nip51Lists.encryption.signNip51List
import com.vitorpamplona.quartz.nip51Lists.remove
import com.vitorpamplona.quartz.nip51Lists.removeAny
import com.vitorpamplona.quartz.nip51Lists.removeIgnoreCase
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
@@ -140,8 +140,8 @@ class HashtagListEvent(
): HashtagListEvent {
val privateTags = earlierVersion.privateTags(signer) ?: throw SignerExceptions.UnauthorizedDecryptionException()
return resign(
privateTags = privateTags.remove(HashtagTag.assemble(hashtag)),
tags = earlierVersion.tags.remove(HashtagTag.assemble(hashtag)),
privateTags = privateTags.removeIgnoreCase(HashtagTag.assemble(hashtag)),
tags = earlierVersion.tags.removeIgnoreCase(HashtagTag.assemble(hashtag)),
signer = signer,
createdAt = createdAt,
)
@@ -40,6 +40,14 @@ fun Array<String>.startsWith(startsWith: Array<String>): Boolean {
return true
}
fun Array<String>.startsWithIgnoreCase(startsWith: Array<String>): Boolean {
if (startsWith.size > this.size) return false
for (tagIdx in startsWith.indices) {
if (!startsWith[tagIdx].equals(this[tagIdx], ignoreCase = true)) return false
}
return true
}
fun Array<String>.startsWithAny(startsWithList: List<Array<String>>): Boolean = startsWithList.any { startsWith(it) }
public inline fun <T, R> Array<out T>.lastNotNullOfOrNull(transform: (T) -> R?): R? {