mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-04 05:46:17 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae6f37a52f | ||
|
|
f6e0124734 | ||
|
|
b6a7d9146f | ||
|
|
8492a597c5 | ||
|
|
5264bd2cbc | ||
|
|
f630c34045 | ||
|
|
042ff4c5ed | ||
|
|
f7e72c57d3 | ||
|
|
fef78cd557 | ||
|
|
673d8d2c30 | ||
|
|
414aef1818 | ||
|
|
bbc4f4281a | ||
|
|
14e3098924 | ||
|
|
7742e27780 | ||
|
|
a1879a93df |
@@ -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
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+30
-25
@@ -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,
|
||||
|
||||
+1
-1
@@ -86,6 +86,6 @@ private fun DisplayTagList(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
) {
|
||||
nav.nav(Route.Hashtag(firstTag))
|
||||
nav.nav(Route.Hashtag(firstTag.lowercase()))
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ fun DisplayHashtagExternalId(
|
||||
nav: INav,
|
||||
) {
|
||||
DisplayHashtagExternalId(externalId.topic) {
|
||||
nav.nav(Route.Hashtag(externalId.topic))
|
||||
nav.nav(Route.Hashtag(externalId.topic.lowercase()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -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(
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+1
-1
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
+3
-3
@@ -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? {
|
||||
|
||||
Reference in New Issue
Block a user