Merge branch 'vitorpamplona:main' into profiles-list-management

This commit is contained in:
KotlinGeekDev
2025-01-04 21:34:56 +00:00
committed by GitHub
34 changed files with 564 additions and 73 deletions
+3 -3
View File
@@ -16,9 +16,9 @@ android {
applicationId "com.vitorpamplona.amethyst"
minSdk libs.versions.android.minSdk.get().toInteger()
targetSdk libs.versions.android.targetSdk.get().toInteger()
versionCode 414
versionName "0.93.1"
buildConfigField "String", "RELEASE_NOTES_ID", "\"f9e228d0579b0256044b54a78037f06edfa06c24862c368ad7e02b5caa7bd309\""
versionCode 416
versionName "0.94.1"
buildConfigField "String", "RELEASE_NOTES_ID", "\"fd42b23b9ef792059b1c1a89555443abbb11578f4b3c8430b452559eec7325f3\""
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@@ -31,6 +31,7 @@ import com.fasterxml.jackson.module.kotlin.readValue
import com.fonfon.kgeohash.GeoHash
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.service.LocationState
import com.vitorpamplona.amethyst.service.NostrLnZapPaymentResponseDataSource
@@ -1065,6 +1066,80 @@ class Account(
}
}
class EmojiMedia(
val code: String,
val url: MediaUrlImage,
)
fun getEmojiPackSelection(): EmojiPackSelectionEvent? = getEmojiPackSelectionNote().event as? EmojiPackSelectionEvent
fun getEmojiPackSelectionFlow(): StateFlow<NoteState> = getEmojiPackSelectionNote().flow().metadata.stateFlow
fun getEmojiPackSelectionNote(): AddressableNote = LocalCache.getOrCreateAddressableNote(EmojiPackSelectionEvent.createAddressATag(userProfile().pubkeyHex))
fun convertEmojiSelectionPack(selection: EmojiPackSelectionEvent?): List<StateFlow<NoteState>>? =
selection?.taggedAddresses()?.map {
LocalCache
.getOrCreateAddressableNote(it)
.flow()
.metadata.stateFlow
}
@OptIn(ExperimentalCoroutinesApi::class)
val liveEmojiSelectionPack: StateFlow<List<StateFlow<NoteState>>?> by lazy {
getEmojiPackSelectionFlow()
.transformLatest {
emit(convertEmojiSelectionPack(it.note.event as? EmojiPackSelectionEvent))
}.flowOn(Dispatchers.Default)
.stateIn(
scope,
SharingStarted.Eagerly,
runBlocking(Dispatchers.Default) {
convertEmojiSelectionPack(getEmojiPackSelection())
},
)
}
fun convertEmojiPack(pack: EmojiPackEvent): List<EmojiMedia> =
pack.taggedEmojis().map {
EmojiMedia(it.code, MediaUrlImage(it.url))
}
fun mergePack(list: Array<NoteState>): List<EmojiMedia> =
list
.mapNotNull {
val ev = it.note.event as? EmojiPackEvent
if (ev != null) {
convertEmojiPack(ev)
} else {
null
}
}.flatten()
.distinctBy { it.url }
@OptIn(ExperimentalCoroutinesApi::class)
val myEmojis by lazy {
liveEmojiSelectionPack
.transformLatest { emojiList ->
if (emojiList != null) {
emitAll(
combineTransform(emojiList) {
emit(mergePack(it))
},
)
} else {
emit(emptyList())
}
}.flowOn(Dispatchers.Default)
.stateIn(
scope,
SharingStarted.Eagerly,
runBlocking(Dispatchers.Default) {
mergePack(convertEmojiSelectionPack(getEmojiPackSelection())?.map { it.value }?.toTypedArray() ?: emptyArray())
},
)
}
fun addPaymentRequestIfNew(paymentRequest: PaymentRequest) {
if (
!this.transientPaymentRequests.value.contains(paymentRequest) &&
@@ -2112,6 +2187,7 @@ class Account(
relayList: List<RelaySetupInfo>,
geohash: String? = null,
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
draftTag: String?,
) {
if (!isWriteable()) return
@@ -2139,6 +2215,7 @@ class Account(
directMentions = directMentions,
geohash = geohash,
imetas = imetas,
emojis = emojis,
signer = signer,
isDraft = draftTag != null,
) {
@@ -2179,6 +2256,7 @@ class Account(
relayList: List<RelaySetupInfo>,
geohash: String? = null,
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
draftTag: String?,
) {
if (!isWriteable()) return
@@ -2201,6 +2279,7 @@ class Account(
directMentions = directMentions,
geohash = geohash,
imetas = imetas,
emojis = emojis,
forkedFrom = forkedFrom,
signer = signer,
isDraft = draftTag != null,
@@ -2247,6 +2326,7 @@ class Account(
relayList: List<RelaySetupInfo>,
geohash: String? = null,
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
draftTag: String?,
) {
if (!isWriteable()) return
@@ -2267,6 +2347,7 @@ class Account(
directMentions = directMentions,
geohash = geohash,
imetas = imetas,
emojis = emojis,
forkedFrom = forkedFrom,
signer = signer,
isDraft = draftTag != null,
@@ -2330,6 +2411,7 @@ class Account(
directMentionsUsers: Set<User> = emptySet(),
directMentionsNotes: Set<Note> = emptySet(),
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
geohash: String? = null,
zapReceiver: List<ZapSplitSetup>? = null,
wantsToMarkAsSensitive: Boolean = false,
@@ -2373,6 +2455,7 @@ class Account(
addressesMentioned = addressesMentioned,
eventsMentioned = eventsMentioned,
imetas = imetas,
emojis = emojis,
geohash = geohash,
zapReceiver = zapReceiver,
markAsSensitive = wantsToMarkAsSensitive,
@@ -2405,6 +2488,7 @@ class Account(
addressesMentioned = addressesMentioned,
eventsMentioned = eventsMentioned,
imetas = imetas,
emojis = emojis,
geohash = geohash,
zapReceiver = zapReceiver,
markAsSensitive = wantsToMarkAsSensitive,
@@ -2439,6 +2523,7 @@ class Account(
directMentionsUsers: Set<User> = emptySet(),
directMentionsNotes: Set<Note> = emptySet(),
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
zapReceiver: List<ZapSplitSetup>? = null,
wantsToMarkAsSensitive: Boolean = false,
zapRaiserAmount: Long? = null,
@@ -2481,6 +2566,7 @@ class Account(
addressesMentioned = addressesMentioned,
eventsMentioned = eventsMentioned,
imetas = imetas,
emojis = emojis,
zapReceiver = zapReceiver,
markAsSensitive = wantsToMarkAsSensitive,
zapRaiserAmount = zapRaiserAmount,
@@ -2685,6 +2771,7 @@ class Account(
relayList: List<RelaySetupInfo>,
geohash: String? = null,
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
draftTag: String?,
) {
if (!isWriteable()) return
@@ -2707,6 +2794,7 @@ class Account(
directMentions = directMentions,
geohash = geohash,
imetas = imetas,
emojis = emojis,
forkedFrom = forkedFrom,
signer = signer,
isDraft = draftTag != null,
@@ -2777,6 +2865,7 @@ class Account(
relayList: List<RelaySetupInfo>,
geohash: String? = null,
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
draftTag: String?,
) {
if (!isWriteable()) return
@@ -2837,6 +2926,7 @@ class Account(
directMentions: Set<HexKey>,
geohash: String? = null,
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
draftTag: String?,
) {
if (!isWriteable()) return
@@ -2855,6 +2945,7 @@ class Account(
directMentions = directMentions,
geohash = geohash,
imetas = imetas,
emojis = emojis,
signer = signer,
isDraft = draftTag != null,
) {
@@ -2883,6 +2974,7 @@ class Account(
zapRaiserAmount: Long? = null,
geohash: String? = null,
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
draftTag: String?,
) {
if (!isWriteable()) return
@@ -2901,6 +2993,7 @@ class Account(
zapRaiserAmount = zapRaiserAmount,
geohash = geohash,
imetas = imetas,
emojis = emojis,
signer = signer,
isDraft = draftTag != null,
) {
@@ -3045,6 +3138,7 @@ class Account(
zapRaiserAmount: Long? = null,
geohash: String? = null,
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
draftTag: String? = null,
) {
if (!isWriteable()) return
@@ -3063,6 +3157,7 @@ class Account(
zapRaiserAmount = zapRaiserAmount,
geohash = geohash,
imetas = imetas,
emojis = emojis,
draftTag = draftTag,
signer = signer,
) {
@@ -57,6 +57,7 @@ import com.vitorpamplona.quartz.encoders.Hex
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.IMetaTag
import com.vitorpamplona.quartz.encoders.IMetaTagBuilder
import com.vitorpamplona.quartz.encoders.Nip30CustomEmoji
import com.vitorpamplona.quartz.encoders.toNpub
import com.vitorpamplona.quartz.events.AddressableEvent
import com.vitorpamplona.quartz.events.AdvertisedRelayListEvent
@@ -65,6 +66,7 @@ import com.vitorpamplona.quartz.events.ClassifiedsEvent
import com.vitorpamplona.quartz.events.CommentEvent
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
import com.vitorpamplona.quartz.events.DraftEvent
import com.vitorpamplona.quartz.events.EmojiUrl
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.FileStorageEvent
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
@@ -82,7 +84,12 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.UUID
@@ -120,6 +127,27 @@ open class NewPostViewModel : ViewModel() {
var userSuggestionAnchor: TextRange? = null
var userSuggestionsMainMessage: UserSuggestionAnchor? = null
val emojiSearch: MutableStateFlow<String> = MutableStateFlow("")
val emojiSuggestions: StateFlow<List<Account.EmojiMedia>> by lazy {
account!!
.myEmojis
.combine(emojiSearch) { list, search ->
if (search.length == 1) {
list
} else if (search.isNotEmpty()) {
val code = search.removePrefix(":")
list.filter { it.code.startsWith(code) }
} else {
emptyList()
}
}.flowOn(Dispatchers.Default)
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5000),
emptyList(),
)
}
// DMs
var wantsDirectMessage by mutableStateOf(false)
var toUsers by mutableStateOf(TextFieldValue(""))
@@ -552,6 +580,7 @@ open class NewPostViewModel : ViewModel() {
}
}
val emojis = findEmoji(tagger.message, account?.myEmojis?.value)
val urls = findURLs(tagger.message)
val usedAttachments = iMetaAttachments.filter { it.url in urls.toSet() }
@@ -569,6 +598,7 @@ open class NewPostViewModel : ViewModel() {
wantsToMarkAsSensitive = wantsToMarkAsSensitive,
zapRaiserAmount = localZapRaiserAmount,
relayList = relayList,
emojis = emojis,
draftTag = localDraft,
)
} else if (wantsExclusiveGeoPost && geoHash != null && (originalNote == null || originalNote?.event is CommentEvent)) {
@@ -583,6 +613,7 @@ open class NewPostViewModel : ViewModel() {
wantsToMarkAsSensitive = wantsToMarkAsSensitive,
zapRaiserAmount = localZapRaiserAmount,
relayList = relayList,
emojis = emojis,
draftTag = localDraft,
)
} else if (originalNote?.channelHex() != null) {
@@ -597,6 +628,7 @@ open class NewPostViewModel : ViewModel() {
zapRaiserAmount = localZapRaiserAmount,
geohash = geoHash,
imetas = usedAttachments,
emojis = emojis,
draftTag = localDraft,
)
} else {
@@ -611,6 +643,7 @@ open class NewPostViewModel : ViewModel() {
directMentions = tagger.directMentions,
geohash = geoHash,
imetas = usedAttachments,
emojis = emojis,
draftTag = localDraft,
)
}
@@ -639,6 +672,7 @@ open class NewPostViewModel : ViewModel() {
zapRaiserAmount = localZapRaiserAmount,
geohash = geoHash,
imetas = usedAttachments,
emojis = emojis,
draftTag = localDraft,
)
} else if (!dmUsers.isNullOrEmpty()) {
@@ -654,6 +688,7 @@ open class NewPostViewModel : ViewModel() {
zapRaiserAmount = localZapRaiserAmount,
geohash = geoHash,
imetas = usedAttachments,
emojis = emojis,
draftTag = localDraft,
)
} else {
@@ -708,6 +743,7 @@ open class NewPostViewModel : ViewModel() {
relayList = relayList,
geohash = geoHash,
imetas = usedAttachments,
emojis = emojis,
draftTag = localDraft,
)
} else if (originalNote?.event is TorrentCommentEvent) {
@@ -747,6 +783,7 @@ open class NewPostViewModel : ViewModel() {
relayList = relayList,
geohash = geoHash,
imetas = usedAttachments,
emojis = emojis,
draftTag = localDraft,
)
}
@@ -776,6 +813,7 @@ open class NewPostViewModel : ViewModel() {
relayList = relayList,
geohash = geoHash,
imetas = usedAttachments,
emojis = emojis,
draftTag = localDraft,
)
} else {
@@ -795,6 +833,7 @@ open class NewPostViewModel : ViewModel() {
relayList = relayList,
geohash = geoHash,
imetas = usedAttachments,
emojis = emojis,
draftTag = localDraft,
)
} else if (wantsProduct) {
@@ -814,6 +853,7 @@ open class NewPostViewModel : ViewModel() {
relayList = relayList,
geohash = geoHash,
imetas = usedAttachments,
emojis = emojis,
draftTag = localDraft,
)
} else {
@@ -851,12 +891,23 @@ open class NewPostViewModel : ViewModel() {
relayList = relayList,
geohash = geoHash,
imetas = usedAttachments,
emojis = emojis,
draftTag = localDraft,
)
}
}
}
fun findEmoji(
message: String,
myEmojiSet: List<Account.EmojiMedia>?,
): List<EmojiUrl> {
if (myEmojiSet == null) return emptyList()
return Nip30CustomEmoji.findAllEmojiCodes(message).mapNotNull { possibleEmoji ->
myEmojiSet.firstOrNull { it.code == possibleEmoji }?.let { EmojiUrl(it.code, it.url.url) }
}
}
fun upload(
alt: String?,
sensitiveContent: Boolean,
@@ -978,6 +1029,10 @@ open class NewPostViewModel : ViewModel() {
userSuggestionAnchor = null
userSuggestionsMainMessage = null
if (emojiSearch.value.isNotEmpty()) {
emojiSearch.tryEmit("")
}
draftTag = UUID.randomUUID().toString()
NostrSearchEventOrUserDataSource.clear()
@@ -1029,6 +1084,14 @@ open class NewPostViewModel : ViewModel() {
NostrSearchEventOrUserDataSource.clear()
userSuggestions = emptyList()
}
if (lastWord.startsWith(":")) {
emojiSearch.tryEmit(lastWord)
} else {
if (emojiSearch.value.isNotBlank()) {
emojiSearch.tryEmit("")
}
}
}
saveDraft()
@@ -1132,6 +1195,54 @@ open class NewPostViewModel : ViewModel() {
saveDraft()
}
open fun autocompleteWithEmoji(item: Account.EmojiMedia) {
userSuggestionAnchor?.let {
val lastWord =
message.text
.substring(0, it.end)
.substringAfterLast("\n")
.substringAfterLast(" ")
val lastWordStart = it.end - lastWord.length
val wordToInsert = ":${item.code}:"
message =
TextFieldValue(
message.text.replaceRange(lastWordStart, it.end, wordToInsert),
TextRange(lastWordStart + wordToInsert.length, lastWordStart + wordToInsert.length),
)
userSuggestionAnchor = null
emojiSearch.tryEmit("")
}
saveDraft()
}
open fun autocompleteWithEmojiUrl(item: Account.EmojiMedia) {
userSuggestionAnchor?.let {
val lastWord =
message.text
.substring(0, it.end)
.substringAfterLast("\n")
.substringAfterLast(" ")
val lastWordStart = it.end - lastWord.length
val wordToInsert = item.url.url + " "
message =
TextFieldValue(
message.text.replaceRange(lastWordStart, it.end, wordToInsert),
TextRange(lastWordStart + wordToInsert.length, lastWordStart + wordToInsert.length),
)
userSuggestionAnchor = null
emojiSearch.tryEmit("")
}
urlPreview = findUrlInMessage()
saveDraft()
}
private fun newStateMapPollOptions(): SnapshotStateMap<Int, String> = mutableStateMapOf(Pair(0, ""), Pair(1, ""))
fun canPost(): Boolean =
@@ -0,0 +1,136 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.note
import androidx.compose.foundation.clickable
import androidx.compose.foundation.content.MediaType.Companion.Text
import androidx.compose.foundation.layout.Arrangement.spacedBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.OpenInFull
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.map
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.gallery.UrlImageView
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
@Composable
fun WatchAndLoadMyEmojiList(accountViewModel: AccountViewModel) {
LoadAddressableNote(
EmojiPackSelectionEvent.createAddressATag(accountViewModel.userProfile().pubkeyHex),
accountViewModel,
) { emptyNote ->
emptyNote?.let { usersEmojiList ->
val collections by usersEmojiList
.live()
.metadata
.map { (it.note.event as? EmojiPackSelectionEvent)?.taggedAddresses()?.toImmutableList() }
.distinctUntilChanged()
.observeAsState((usersEmojiList.event as? EmojiPackSelectionEvent)?.taggedAddresses()?.toImmutableList())
collections?.forEach {
LoadAddressableNote(aTag = it, accountViewModel) {
it?.live()?.metadata?.observeAsState()
}
}
}
}
}
@Composable
fun ShowEmojiSuggestionList(
emojiSuggestions: Flow<List<Account.EmojiMedia>>,
onSelect: (Account.EmojiMedia) -> Unit,
onFullSize: (Account.EmojiMedia) -> Unit,
accountViewModel: AccountViewModel,
modifier: Modifier = Modifier.heightIn(0.dp, 200.dp),
) {
val suggestions by emojiSuggestions.collectAsStateWithLifecycle(emptyList())
if (suggestions.isNotEmpty()) {
LazyColumn(
contentPadding = PaddingValues(top = 10.dp),
modifier = modifier,
) {
items(suggestions) {
Row(
modifier =
Modifier.clickable { onSelect(it) }.padding(
start = 12.dp,
end = 12.dp,
top = 10.dp,
bottom = 10.dp,
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = spacedBy(Size10dp),
) {
Box(Modifier.size(40.dp)) {
UrlImageView(it.url, accountViewModel)
}
Text(it.code, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f))
Box(Modifier.size(40.dp), contentAlignment = Alignment.Center) {
IconButton(
onClick = {
onFullSize(it)
},
) {
Icon(
imageVector = Icons.Outlined.OpenInFull,
contentDescription = stringRes(R.string.use_direct_url),
modifier = Modifier.size(20.dp),
)
}
}
}
HorizontalDivider(
thickness = DividerThickness,
)
}
}
}
}
@@ -144,7 +144,6 @@ class UpdateReactionTypeViewModel : ViewModel() {
?.value
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun UpdateReactionTypeDialog(
onClose: () -> Unit,
@@ -157,8 +157,10 @@ import com.vitorpamplona.amethyst.ui.note.LoadCityName
import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.note.PollIcon
import com.vitorpamplona.amethyst.ui.note.RegularPostIcon
import com.vitorpamplona.amethyst.ui.note.ShowEmojiSuggestionList
import com.vitorpamplona.amethyst.ui.note.ShowUserSuggestionList
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
import com.vitorpamplona.amethyst.ui.note.WatchAndLoadMyEmojiList
import com.vitorpamplona.amethyst.ui.note.ZapSplitIcon
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.MyTextField
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow
@@ -209,6 +211,7 @@ fun NewPostScreen(
nav: Nav,
) {
val postViewModel: NewPostViewModel = viewModel()
postViewModel.account = accountViewModel.account
postViewModel.wantsDirectMessage = enableMessageInterface
postViewModel.wantsToAddGeoHash = enableGeolocation
@@ -278,6 +281,8 @@ fun NewPostScreen(
onDispose { activity.removeOnNewIntentListener(consumer) }
}
WatchAndLoadMyEmojiList(accountViewModel)
Scaffold(
topBar = {
TopAppBar(
@@ -575,6 +580,14 @@ fun NewPostScreen(
modifier = Modifier.heightIn(0.dp, 300.dp),
)
ShowEmojiSuggestionList(
postViewModel.emojiSuggestions,
postViewModel::autocompleteWithEmoji,
postViewModel::autocompleteWithEmojiUrl,
accountViewModel,
modifier = Modifier.heightIn(0.dp, 300.dp),
)
BottomRowActions(postViewModel)
}
}
@@ -130,6 +130,7 @@ import com.vitorpamplona.amethyst.ui.note.LikeReaction
import com.vitorpamplona.amethyst.ui.note.LoadChannel
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay
import com.vitorpamplona.amethyst.ui.note.ShowEmojiSuggestionList
import com.vitorpamplona.amethyst.ui.note.ShowUserSuggestionList
import com.vitorpamplona.amethyst.ui.note.UserPicture
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
@@ -397,6 +398,7 @@ private suspend fun innerSendPost(
val urls = findURLs(tagger.message)
val usedAttachments = newPostModel.iMetaAttachments.filter { it.url in urls.toSet() }
val emojis = newPostModel.findEmoji(newPostModel.message.text, accountViewModel.account.myEmojis.value)
if (channel is PublicChatChannel) {
accountViewModel.account.sendChannelMessage(
@@ -407,6 +409,7 @@ private suspend fun innerSendPost(
directMentions = tagger.directMentions,
wantsToMarkAsSensitive = false,
imetas = usedAttachments,
emojis = emojis,
draftTag = draftTag,
)
} else if (channel is LiveActivitiesChannel) {
@@ -417,6 +420,7 @@ private suspend fun innerSendPost(
mentions = tagger.pTags,
wantsToMarkAsSensitive = false,
imetas = usedAttachments,
emojis = emojis,
draftTag = draftTag,
)
}
@@ -485,6 +489,13 @@ fun EditFieldRow(
accountViewModel,
)
ShowEmojiSuggestionList(
channelScreenModel.emojiSuggestions,
channelScreenModel::autocompleteWithEmoji,
channelScreenModel::autocompleteWithEmojiUrl,
accountViewModel,
)
MyTextField(
value = channelScreenModel.message,
onValueChange = { channelScreenModel.updateMessage(it) },
@@ -95,6 +95,7 @@ import com.vitorpamplona.amethyst.ui.note.IncognitoIconOff
import com.vitorpamplona.amethyst.ui.note.IncognitoIconOn
import com.vitorpamplona.amethyst.ui.note.NonClickableUserPictures
import com.vitorpamplona.amethyst.ui.note.QuickActionAlertDialog
import com.vitorpamplona.amethyst.ui.note.ShowEmojiSuggestionList
import com.vitorpamplona.amethyst.ui.note.ShowUserSuggestionList
import com.vitorpamplona.amethyst.ui.note.UserCompose
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
@@ -517,6 +518,7 @@ private fun innerSendPost(
) {
val urls = findURLs(newPostModel.message.text)
val usedAttachments = newPostModel.iMetaAttachments.filter { it.url !in urls.toSet() }
val emojis = newPostModel.findEmoji(newPostModel.message.text, accountViewModel.account.myEmojis.value)
if (newPostModel.nip17 || room.users.size > 1 || replyTo.value?.event is NIP17Group) {
accountViewModel.account.sendNIP17PrivateMessage(
@@ -526,6 +528,7 @@ private fun innerSendPost(
mentions = null,
wantsToMarkAsSensitive = false,
imetas = usedAttachments,
emojis = emojis,
draftTag = dTag,
)
} else {
@@ -557,6 +560,13 @@ fun PrivateMessageEditFieldRow(
accountViewModel,
)
ShowEmojiSuggestionList(
channelScreenModel.emojiSuggestions,
channelScreenModel::autocompleteWithEmoji,
channelScreenModel::autocompleteWithEmojiUrl,
accountViewModel,
)
MyTextField(
value = channelScreenModel.message,
onValueChange = { channelScreenModel.updateMessage(it) },
@@ -53,6 +53,7 @@ import com.vitorpamplona.amethyst.commons.richtext.RichTextParser.Companion.isVi
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.components.AutoNonlazyGrid
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
import com.vitorpamplona.amethyst.ui.components.DisplayBlurHash
import com.vitorpamplona.amethyst.ui.components.DisplayUrlWithLoadingSymbol
@@ -60,6 +61,7 @@ import com.vitorpamplona.amethyst.ui.components.GetMediaItem
import com.vitorpamplona.amethyst.ui.components.GetVideoController
import com.vitorpamplona.amethyst.ui.components.ImageUrlWithDownloadButton
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
import com.vitorpamplona.amethyst.ui.components.UrlImageView
import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.note.DownloadForOfflineIcon
import com.vitorpamplona.amethyst.ui.note.WatchAuthor
@@ -82,35 +84,31 @@ fun GalleryThumbnail(
val content =
if (noteEvent is ProfileGalleryEntryEvent) {
val url = noteEvent.url()
if (url == null) {
null
} else if (isVideoUrl(url)) {
MediaUrlVideo(
url = url,
description = noteEvent.content,
hash = null,
blurhash = noteEvent.blurhash(),
dim = noteEvent.dimensions(),
uri = null,
mimeType = noteEvent.mimeType(),
)
} else {
MediaUrlImage(
url = url,
description = noteEvent.content,
hash = null, // We don't want to show the hash banner here
blurhash = noteEvent.blurhash(),
dim = noteEvent.dimensions(),
uri = null,
mimeType = noteEvent.mimeType(),
)
noteEvent.urls().map { url ->
if (isVideoUrl(url)) {
MediaUrlVideo(
url = url,
description = noteEvent.content,
hash = null,
blurhash = noteEvent.blurhash(),
dim = noteEvent.dimensions(),
uri = null,
mimeType = noteEvent.mimeType(),
)
} else {
MediaUrlImage(
url = url,
description = noteEvent.content,
hash = null, // We don't want to show the hash banner here
blurhash = noteEvent.blurhash(),
dim = noteEvent.dimensions(),
uri = null,
mimeType = noteEvent.mimeType(),
)
}
}
} else if (noteEvent is PictureEvent) {
val imeta = noteEvent.imetaTags().firstOrNull()
if (imeta?.url == null) {
null
} else {
noteEvent.imetaTags().map { imeta ->
MediaUrlImage(
url = imeta.url,
description = noteEvent.content,
@@ -122,11 +120,7 @@ fun GalleryThumbnail(
)
}
} else if (noteEvent is VideoEvent) {
val imeta = noteEvent.imetaTags().firstOrNull()
if (imeta?.url == null) {
null
} else {
noteEvent.imetaTags().map { imeta ->
MediaUrlVideo(
url = imeta.url,
description = noteEvent.content,
@@ -138,7 +132,7 @@ fun GalleryThumbnail(
)
}
} else {
null
emptyList()
}
InnerRenderGalleryThumb(content, baseNote, accountViewModel)
@@ -146,15 +140,12 @@ fun GalleryThumbnail(
@Composable
fun InnerRenderGalleryThumb(
content: MediaUrlContent?,
content: List<MediaUrlContent>,
note: Note,
accountViewModel: AccountViewModel,
) {
if (content != null) {
GalleryContentView(
content = content,
accountViewModel = accountViewModel,
)
if (content.isNotEmpty()) {
GalleryContentView(content, accountViewModel)
} else {
DisplayGalleryAuthorBanner(note)
}
@@ -170,18 +161,20 @@ fun DisplayGalleryAuthorBanner(note: Note) {
@androidx.annotation.OptIn(UnstableApi::class)
@Composable
fun GalleryContentView(
content: MediaUrlContent,
contentList: List<MediaUrlContent>,
accountViewModel: AccountViewModel,
) {
when (content) {
is MediaUrlImage ->
SensitivityWarning(content.contentWarning != null, accountViewModel) {
UrlImageView(content, accountViewModel)
}
is MediaUrlVideo ->
SensitivityWarning(content.contentWarning != null, accountViewModel) {
UrlVideoView(content, accountViewModel)
}
AutoNonlazyGrid(contentList.size) { contentIndex ->
when (val content = contentList[contentIndex]) {
is MediaUrlImage ->
SensitivityWarning(content.contentWarning != null, accountViewModel) {
UrlImageView(content, accountViewModel)
}
is MediaUrlVideo ->
SensitivityWarning(content.contentWarning != null, accountViewModel) {
UrlVideoView(content, accountViewModel)
}
}
}
}
@@ -21,9 +21,9 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.gallery
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement.spacedBy
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
@@ -31,6 +31,7 @@ import androidx.compose.foundation.lazy.grid.itemsIndexed
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
@@ -41,7 +42,6 @@ import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.screen.FeedViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.Size5dp
@Composable
fun RenderGalleryFeed(
@@ -92,6 +92,8 @@ private fun GalleryFeedLoaded(
columns = GridCells.Fixed(3),
contentPadding = FeedPadding,
state = listState,
verticalArrangement = spacedBy(1.dp),
horizontalArrangement = spacedBy(1.dp),
) {
itemsIndexed(items.list, key = { _, item -> item.idHex }) { _, item ->
GalleryCardCompose(
@@ -100,8 +102,7 @@ private fun GalleryFeedLoaded(
Modifier
.aspectRatio(1f)
.fillMaxSize()
.animateItem()
.padding(Size5dp),
.animateItem(),
accountViewModel = accountViewModel,
nav = nav,
)
@@ -608,6 +608,7 @@
<string name="poll_zap_value_min_max_explainer">Hlasy jsou váženy podle hodnoty zapu. Můžete nastavit minimální částku, abyste se vyhnuli spammerům, a maximální částku, abyste zabránili velkým zapperům, kteří by mohli ovládnout hlasování. Použijte stejnou částku v obou polích, aby byla hodnota každého hlasu stejná. Nechte prázdné pro přijetí libovolné částky.</string>
<string name="error_dialog_zap_error">Nelze odeslat zap</string>
<string name="error_dialog_talk_to_user">Poslat zprávu uživateli</string>
<string name="error_dialog_talk_to_user_name">Zpráva %1$s</string>
<string name="error_dialog_button_ok">OK</string>
<string name="relay_information_document_error_assemble_url">Nepodařilo se dosáhnout %1$s: %2$s</string>
<string name="relay_information_document_error_failed_to_assemble_url">Nepodařilo se sestavit adresu URL NIP-11 pro %1$s: %2$s</string>
@@ -667,6 +668,7 @@
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct">Nepodařilo se vyřešit %1$s. Zkontrolujte, zda jste připojeni, zda je server aktivní a zda je adresa %2$s správná</string>
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct_exception">Nepodařilo se vyřešit %1$s. Zkontrolujte, zda jste připojeni, zda je server aktivní a zda je adresa %2$s správná.\n\nVýjimka byla: %3$s</string>
<string name="could_not_fetch_invoice_from">Nepodařilo se načíst fakturu z %1$s</string>
<string name="could_not_fetch_invoice_from_details">Nelze získat fakturu z %1$s: %2$s</string>
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup">Chyba při analýze JSON z Lightning adresy. Zkontrolujte uživatelovo bleskové nastavení</string>
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup_with_user">Chyba při analýze JSON z %1$s. Zkontrolujte uživatelské bleskové nastavení</string>
<string name="callback_url_not_found_in_the_user_s_lightning_address_server_configuration">URL adresa zpětného volání nebyla nalezena v konfiguraci serveru pro bleskovou adresu uživatele</string>
@@ -806,6 +808,7 @@
<string name="search_relays_not_found_description">Vytvoření seznamu relé speciálně navržených pro vyhledávání a označování uživatelů zlepší tyto výsledky.</string>
<string name="search_relays_not_found_editing">Vložte mezi 13 relé, která chcete použít při vyhledávání obsahu nebo označování uživatelů. Ujistěte se, že vaše vybraná relé implementují NIP-50</string>
<string name="search_relays_not_found_examples">Dobré možnosti jsou:\n - nostr.wine\n - relay.nostr.band\n - relay.noswhere.com</string>
<string name="dm_upload">Nahrát v DM</string>
<string name="relay_settings">Nastavení relé</string>
<string name="public_home_section">Veřejná domácí relé</string>
<string name="public_home_section_explainer">Tento typ relé ukládá veškerý váš obsah. Amethyst sem pošle vaše příspěvky a ostatní použijí tato relé, aby našli váš obsah. Vložte mezi 13 relé. Mohou to být osobní relé, placená relé nebo veřejná relé.</string>
@@ -613,6 +613,7 @@ anz der Bedingungen ist erforderlich</string>
<string name="poll_zap_value_min_max_explainer">Die Abstimmungen werden nach der Höhe des Zaps gewichtet. Sie können einen Mindestbetrag festlegen, um Spam zu verhindern, und einen Höchstbetrag, um zu verhindern, dass große Zapper die Abstimmung dominieren. Verwenden Sie denselben Betrag in beiden Feldern, um sicherzustellen, dass jeder Stimme der gleiche Wert zukommt. Lassen Sie es leer, um jeden Betrag zu akzeptieren.</string>
<string name="error_dialog_zap_error">Zap konnte nicht gesendet werden</string>
<string name="error_dialog_talk_to_user">Mit dem Benutzer kommunizieren</string>
<string name="error_dialog_talk_to_user_name">Nachricht %1$s</string>
<string name="error_dialog_button_ok">OK</string>
<string name="relay_information_document_error_assemble_url">Konnte %1$s nicht erreichen: %2$s</string>
<string name="relay_information_document_error_failed_to_assemble_url">Fehler beim Zusammenstellen der NIP-11-URL für %1$s: %2$s</string>
@@ -672,6 +673,7 @@ anz der Bedingungen ist erforderlich</string>
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct">Konnte %1$s nicht auflösen. Überprüfen Sie, ob Sie verbunden sind, ob der Server online ist und ob die Lightning-Adresse %2$s korrekt ist</string>
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct_exception">Konnte %1$s nicht auflösen. Überprüfen Sie, ob Sie verbunden sind, ob der Server online ist und ob die Lightning-Adresse %2$s korrekt ist.\n\nAusnahme war: %3$s</string>
<string name="could_not_fetch_invoice_from">Konnte Rechnung nicht von %1$s abholen</string>
<string name="could_not_fetch_invoice_from_details">Rechnung konnte nicht von %1$s abgerufen werden: %2$s</string>
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup">Fehler beim analysieren von JSON aus der Lightning-Adresse. Überprüfen Sie die Lightning-Konfiguration des Benutzers</string>
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup_with_user">Fehler beim Parsen von JSON von %1$s. Überprüfen Sie die Blitzeinrichtung des Benutzers</string>
<string name="callback_url_not_found_in_the_user_s_lightning_address_server_configuration">Callback-URL nicht in der Serverkonfiguration der Lightning-Adresse des Benutzers gefunden</string>
@@ -811,6 +813,7 @@ anz der Bedingungen ist erforderlich</string>
<string name="search_relays_not_found_description">Das Erstellen einer Relaisliste, die speziell für die Suche und Benutzerkennzeichnung entwickelt wurde, wird diese Ergebnisse verbessern.</string>
<string name="search_relays_not_found_editing">Fügen Sie 13 Relais ein, die beim Suchen nach Inhalten oder beim Taggen von Benutzern verwendet werden sollen. Stellen Sie sicher, dass Ihre ausgewählten Relais NIP-50 implementieren</string>
<string name="search_relays_not_found_examples">Gute Optionen sind:\n - nostr.wine\n - relay.nostr.band\n - relay.noswhere.com</string>
<string name="dm_upload">DM-Upload</string>
<string name="relay_settings">Relaiseinstellungen</string>
<string name="public_home_section">Öffentliche Heimrelais</string>
<string name="public_home_section_explainer">Dieser Relais-Typ speichert alle Ihre Inhalte. Amethyst sendet Ihre Beiträge hierher und andere werden diese Relais verwenden, um Ihre Inhalte zu finden. Fügen Sie 13 Relais ein. Sie können persönliche Relais, bezahlte Relais oder öffentliche Relais sein.</string>
@@ -121,6 +121,8 @@
<string name="ln_url_outdated">آدرس لایتنینگ(نقل شده)</string>
<string name="save_to_gallery">در گالری ذخیره کن</string>
<string name="image_saved_to_the_gallery">تصویر در گالری ذخیره شد</string>
<string name="video_download_has_started_toast">بارگیری ویدئو آغاز شد…</string>
<string name="media_download_has_started_toast">بارگیری رسانه آغاز شد…</string>
<string name="failed_to_save_the_image">تصویر ذخیره نشد</string>
<string name="video_saved_to_the_gallery">ویدئو در گالری گوشی ذخیره شد</string>
<string name="failed_to_save_the_video">ویدئو ذخیره نشد</string>
@@ -148,6 +150,8 @@
<string name="lightning_address">آدرس لایتنینگ</string>
<string name="copies_the_nsec_id_your_password_to_the_clipboard_for_backup">شناسه کلید خصوصی (پسوردتان) را در کلیپبورد کپی می کند(</string>
<string name="copy_private_key_to_the_clipboard">کپی کردن کلید خصوصی در کلیپبورد</string>
<string name="show_private_key_qr_code">نمایش کد QR کلید خصوصی</string>
<string name="show_encrypted_private_key_qr_code">نمایش کد QR کلید خصوصی رمزنگاری شده</string>
<string name="copies_the_public_key_to_the_clipboard_for_sharing">کلید عمومی را برای اشتراک گذاری در کلیپبورد ذخیره می کند</string>
<string name="copy_public_key_npub_to_the_clipboard">کپی کردن کلید عمومی در کلیپبورد</string>
<string name="send_a_direct_message">ارسال پیام مستقیم</string>
@@ -335,10 +339,13 @@
<string name="hash_verification_info_title">این به چه معناست؟</string>
<string name="hash_verification_passed">تصویر از زمان ارسال یکسان است</string>
<string name="hash_verification_failed">تصویر تغییر کرده است. ممکن است نویسنده تغییر را ندیده باشد.</string>
<string name="content_description_add_media">افزودن رسانه</string>
<string name="content_description_add_image">افزودن تصویر</string>
<string name="content_description_add_video">افزودن فیلم</string>
<string name="content_description_add_document">افزودن مدارک</string>
<string name="add_content">افزودن به پیام</string>
<string name="add_caption">افزودن عنوان</string>
<string name="add_caption_example">دوست عزیز من</string>
<string name="content_description">توصیف محتوا</string>
<string name="content_description_example">قایقی آبی در ساحل شنی سفید هنگام غروب</string>
<string name="zap_type">نوع زپ</string>
@@ -352,6 +359,7 @@
<string name="zap_type_nonzap">غیرزپ</string>
<string name="zap_type_nonzap_explainer">هیچ ردی در نوستر نمی ماند، فقط در شبکه لایتنینگ انجام می شود</string>
<string name="file_server">سرور فایل</string>
<string name="file_server_description">یک سرور برای بارگذاری این فایل انتخاب کنید</string>
<string name="zap_forward_lnAddress">آدرس لایتنینگ یا @User</string>
<string name="media_servers">سرورهای رسانه</string>
<string name="set_preferred_media_servers">سرورهای مورد علاقه خود برای بارگزاری رسانه را انتخاب کنید.</string>
@@ -362,6 +370,14 @@
<string name="use_default_servers">استفاده از لیست پیش فرض</string>
<string name="add_media_server">افزودن سرور رسانه</string>
<string name="delete_media_server">حذف سرور رسانه</string>
<string name="uploading_state_ready">آغاز نشده</string>
<string name="uploading_state_compressing">فشرده سازی</string>
<string name="uploading_state_uploading">درحال بارگذاری</string>
<string name="uploading_state_server_processing">در حال پردازش</string>
<string name="uploading_state_downloading">در حال بارگیری</string>
<string name="uploading_state_hashing">در حال هش کردن</string>
<string name="uploading_state_finished">انجام شد</string>
<string name="uploading_state_error">خطا</string>
<string name="upload_server_relays_nip95">رله های شما (NIP-95)</string>
<string name="upload_server_relays_nip95_explainer">فایل ها در رله های شما میزبانی می شوند. NIPجدید: بررسی کنید آیا پشتیبانی می کنند یا خیر </string>
<string name="privacy_options">گزینه های حریم خصوصی</string>
@@ -583,6 +599,8 @@
<string name="copy_url_to_clipboard">کپی URL به کلیپبورد</string>
<string name="copy_the_note_id_to_the_clipboard">کپی شناسه یادداشت به کلیپبورد</string>
<string name="add_media_to_gallery">افزودن رسانه به گالری</string>
<string name="media_added">رسانه افزوده شد</string>
<string name="media_added_to_profile_gallery">رسانه به گالری نمایه شما افزوده شد</string>
<string name="created_at">ایجاد شده در</string>
<string name="rules">قوانین</string>
<string name="login_with_external_signer">ورود با Amber</string>
@@ -591,6 +609,7 @@
<string name="poll_zap_value_min_max_explainer">آراء با مبلغ زپ سنجیده می شوند. می توانید برای جلوگیری از اسپم یک مبلغ حداقل تعیین کنید و برای جلوگیری از دستکاری نتیجه با زپ های بزرگ یک مبلغ حداکثر تعیین کنید. برای اینکه همه آراء ارزش یکسان داشته باشند در هر دو قسمت یک مبلغ را بزنید. یا این قسمت را خالی بگذارید تا هر مبلغ زپی را بپذیرد.</string>
<string name="error_dialog_zap_error">زپ فرستاده نمی شود</string>
<string name="error_dialog_talk_to_user">پیام به کاربر</string>
<string name="error_dialog_talk_to_user_name">پیغام %1$s</string>
<string name="error_dialog_button_ok">قبول</string>
<string name="relay_information_document_error_assemble_url">به %1$s نرسید: %2$s</string>
<string name="relay_information_document_error_failed_to_assemble_url">آدرس NIP-11 ساخته نشد به دلیل %1$s: %2$s</string>
@@ -650,6 +669,7 @@
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct">مشکل %1$s حل نشد. بررسی کنید که به اینترنت متصل باشید، سرور کار کند و آدرس لایتنینگ %2$s صحیح باشد.</string>
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct_exception">مشکل %1$s حل نشد. بررسی کنید که به اینترنت متصل باشید، سرور کار کند و آدرس لایتنینگ %2$s صحیح باشد. \n\nخطای %3$s</string>
<string name="could_not_fetch_invoice_from">صورتحساب از %1$s گرفته نشد</string>
<string name="could_not_fetch_invoice_from_details">صورتحساب از %1$s گرفته نشد: %2$s</string>
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup">خطا در تفسیر JSON از آدرس لایتنینگ. تنظیمات لایتنینگ کاربر را بررسی کنید.</string>
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup_with_user">خطا در تفسیر JSON از %1$s. تنظیمات لایتنینگ کاربر را بررسی کنید</string>
<string name="callback_url_not_found_in_the_user_s_lightning_address_server_configuration">URL بازخوانی در پیکربندی سرور آدرس لایتنینگ یافت نشد</string>
@@ -717,7 +737,9 @@
<string name="failed_to_upload_media">خطای بارگیری: %1$s</string>
<string name="server_did_not_provide_a_url_after_uploading">سرور پس از بارگذاری URL نداد</string>
<string name="could_not_download_from_the_server">فایل بارگذاری شده از سرور بارگیری نشد</string>
<string name="could_not_check_downloaded_file">فایل بارگیری شده پس از بارگذاری بررسی نشد: %1$s</string>
<string name="could_not_prepare_local_file_to_upload">فایل محلی برای بارگذاری آماده نشد: %1$s</string>
<string name="failed_to_upload_to_server_with_message">بارگذاری نشد %1$s: %2$s</string>
<string name="failed_to_upload_with_message">بارگذاری نشد: %1$s</string>
<string name="failed_to_delete_with_message">حذف نشد: %1$s</string>
<string name="media_too_big_for_nip95">رسانه برای NIP-95 بسیار بزرگ است</string>
@@ -793,6 +815,7 @@
<string name="search_relays_not_found_description">ساخت لیستی از رله های مخصوص جستجو و تگ کردن کاربر باعث بهبود این نتایج خواهد شد.</string>
<string name="search_relays_not_found_editing">بین ۱-۳ رله بیافزایید تا هنگام جستجوی محتوا یا تگ کردن کاربران از آنها استفاده شود. حتما رله های انتخابی باید NIP-50 را پیاده‌سازی کرده باشند.</string>
<string name="search_relays_not_found_examples">انتخاب های مناسب: \n - nostr.wine\n - relay.nostr.band\n - relay.noswhere.com</string>
<string name="dm_upload">بارگذاری پیام خصوصی</string>
<string name="relay_settings">تنظیمات رله</string>
<string name="public_home_section">رله های خانه/ صندوق خروجی عمومی</string>
<string name="public_home_section_explainer">این نوع از رله تمام محتوای شما را ذخریه می کند. امتیست پست های شما را اینجا می فرستد و دیگران از این رله ها برای یافتن محتوای شما استفاده می کنند. بین ۱-۳ رله وارد کنید. این رله ها ممکن است رله های شخصی، پولی، یا عمومی باشند.</string>
@@ -875,9 +898,23 @@
<string name="http_status_416">بازه براورده نشد - سرور نتوانست مقدار مورد نظر در سربرگ بازه را براورده کند. </string>
<string name="http_status_417">انتظارات براورده نشد - سرور نتوانست انتظارات تعیین شده در سربرگ توقع درخواست را براورده کند</string>
<string name="http_status_426">ارتقاء الزامی - سرور درخواست را با پروتکل فعلی پردازش نمی کند تا کلاینت پروتکل را ارتقاء دهد.</string>
<string name="http_status_500">خطای سرور داخلی - سرور به خطایی غیرمنتظره برخورد و نمی تواند درخواست را کامل کند</string>
<string name="http_status_501">انجام نشد - سرور نمی تواند درخواست را انجام دهد یا روش درخواست را تشخیص نمی دهد</string>
<string name="http_status_502">درگاه غیرمجاز - سرور به عنوان یک درگاه پاسخی نامعتبر از هاست دریافت کرد</string>
<string name="http_status_503">خدمت در دسترس نیست - این اغلب هنگامی که یک سرور بیش از حد شلوغ است یا برای تعمیرات تعطیل است پیش می آید</string>
<string name="http_status_504">مهلت درگاه تمام شد - مهلت سرور در نقش یک درگاه یا پراکسی تمام شد، در انتظار پاسخ</string>
<string name="http_status_505">نسخه HTTP پشتیبانی نمی شود - سرور ورژن HTTP درخواست را پشتیبانی نمی کند</string>
<string name="http_status_507">فضای ذخیره ناکافی - سرور فضای کافی برای پردازش موفق درخواست ندارد</string>
<string name="http_status_508">حلقه دیده شد - سرور یک حلقه بینهایت در پردازش این درخواست شناسایی کرد</string>
<string name="http_status_511">احراز هویت الزامی - کلاینت برای دسترسی به شبکه می بایست احراز هویت شده باشد</string>
<string name="media_servers_nip96_section">سرورهای NIP-96</string>
<string name="media_servers_nip96_explainer">هر تعداد سرور که می خواهید اضافه کنید. می توانید بعدا هنگام بارگذاری تصویرتان انتخاب کنید که از کدام سرور استفاده شود</string>
<string name="media_servers_blossom_section">سرورهای Blossom</string>
<string name="media_servers_blossom_explainer">هر تعداد سرور که می خواهید اضافه کنید. می توانید بعدا هنگام بارگذاری تصویرتان انتخاب کنید که از کدام سرور استفاده شود</string>
<string name="add_a_nip96_server">یک سرور NIP-96 بیافزایید</string>
<string name="add_a_blossom_server">یک سرور Blossom بیافزایید</string>
<string name="delete_all">حذف همه</string>
<string name="delete_all_drafts_confirmation">مطمئنید می خواهید حذف کنید؟</string>
<string name="stack">استک:</string>
<string name="torrent_file">فایل تورنت</string>
<string name="torrent_download">بارگيری</string>
@@ -121,6 +121,8 @@
<string name="ln_url_outdated">लै॰जाल पता (पुराना)</string>
<string name="save_to_gallery">चित्रालय में अभिलेखन करें</string>
<string name="image_saved_to_the_gallery">चित्र का अभिलेखन किया गया चित्रालय क्रमक में</string>
<string name="video_download_has_started_toast">चलचित्र अवरोहण आरम्भ हुआ …</string>
<string name="media_download_has_started_toast">अभिलेख अवरोहण आरम्भ हुआ …</string>
<string name="failed_to_save_the_image">चित्र का अभिलेखन असफल</string>
<string name="video_saved_to_the_gallery">चलचित्र को संचारयन्त्र के चलचित्रालय में सुरक्षित रखा गया</string>
<string name="failed_to_save_the_video">चलचित्र को सुरक्षित रखने में असफल</string>
@@ -598,6 +600,8 @@
<string name="copy_url_to_clipboard">टाँकाफलक में जालपता की अनुकृति करें</string>
<string name="copy_the_note_id_to_the_clipboard">टाँकाफलक में टीका विभेदक की अनुकृति करें</string>
<string name="add_media_to_gallery">अभिलेख को चित्रालय में जोडें</string>
<string name="media_added">अभिलेख जोडा गया</string>
<string name="media_added_to_profile_gallery">अभिलेख जोडा गया आपके परिचय चित्रालय में</string>
<string name="created_at">तब बनाया गया</string>
<string name="rules">नियमावली</string>
<string name="login_with_external_signer">आम्बेर के साथ प्रवेशांकन करें</string>
@@ -606,6 +610,7 @@
<string name="poll_zap_value_min_max_explainer">निर्वाचन का भार ज्साप मात्रा के अनुसार माना जाता है। आप एक न्यूनतम मात्रा स्थापित कर सकते हैं कचरालेख भेजनेवालों से बचने के लिए तथा एक अधिकतम मात्रा बडे ज्सापकर्ताओं के द्वारा मतदान का अधिग्रहण होने से बचने के लिए। दोनों स्थानों में एक ही मात्रा का प्रयोग करें प्रत्येक निर्वाचन का मूल्य समान मानने को सुनिश्चित करने के लिए। रिक्त रख दें कुछ भी मात्रा स्वीकार करने के लिए।</string>
<string name="error_dialog_zap_error">ज्साप भेजने में असफल</string>
<string name="error_dialog_talk_to_user">उपयोगकर्ता को संदेश भेजें</string>
<string name="error_dialog_talk_to_user_name">सूचना %1$s</string>
<string name="error_dialog_button_ok">ठीक है</string>
<string name="relay_information_document_error_assemble_url">%1$s तक जा नहीं पाए : %2$s</string>
<string name="relay_information_document_error_failed_to_assemble_url">%1$s के लिए निप॰-११ जालपता बनाने में असफल : %2$s</string>
@@ -665,6 +670,7 @@
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct">%1$s का सुलझाव नहीं कर पाए। जाँच करें यदि आप संयोजित हैं, यदि सेवा संगणक चल रहा है तथा यदि लैटनिंग पता %2$s सम्यक है</string>
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct_exception">%1$s का सुलझाव नहीं कर पाए। जाँच करें यदि आप संयोजित हैं, यदि सेवा संगणक चल रहा है तथा यदि लैटनिंग पता %2$s सम्यक है।\n\nअपवर्ग था : %3$s</string>
<string name="could_not_fetch_invoice_from">%1$s से चालान नहीं लाया जा सका</string>
<string name="could_not_fetch_invoice_from_details">चालान नहीं लाया जा सका %1$s से : %2$s</string>
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup">लैटनिंग पता से जेसोन॰ परखनें में अपक्रम। उपयोगकर्ता की लैटनिंग स्थापना की जाँच करें</string>
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup_with_user">%1$s से जेसोन॰ परखनें में अपक्रम। उपयोगकर्ता की लैटनिंग स्थापना की जाँच करें</string>
<string name="callback_url_not_found_in_the_user_s_lightning_address_server_configuration">प्रत्याह्वान जालपता उपलब्ध नहीं उपयोगकर्ता के लैटनिंग पता सेवासंगणक की समाकृति में</string>
@@ -804,6 +810,7 @@
<string name="search_relays_not_found_description">खोज तथा उपयोगकर्ता सूचक जोडने के लिए स्पष्टतः रूपांकित पुनःप्रसारक सूची बनाने से इन परिणामों में शोधन होगा।</string>
<string name="search_relays_not_found_editing">विषयवस्तु खोजने के लिए अथवा उपयोगकर्ता सूचक जोडने में उपयोग करनें के लिए १ - ३ पुनःप्रसारकों को जोडें। सुनिश्चित करें कि आपके चयनित पुनःप्रसारक निप॰-५० को कार्यान्वित किये हैं।</string>
<string name="search_relays_not_found_examples">ये अच्छे विकल्प हैं :\n - nostr.wine\n - relay.nostr.band\n - relay.noswhere.com</string>
<string name="dm_upload">सीधा संदेश आरोहण</string>
<string name="relay_settings">पुनःप्रसारक स्थापना विकल्प</string>
<string name="public_home_section">सार्वजनिक मुख्य पुनःप्रसारक</string>
<string name="public_home_section_explainer">यह पुनःप्रसारक प्रकार आपके सभी विषयवस्तु रखता है। अमेथिस्ट आपके पत्रों को प्रकाशित करने के लिए यहाँ भेजेगा तथा अन्य लोग आपके विषयवस्तु ढूँढने के लिए इनका प्रयोग करेंगे। १ - ३ पुनःप्रसारकों को जोडें। ये व्यक्तिगत अथवा सशुल्क अथवा सार्वजनिक पुनःप्रसारक हो सकते हैं।</string>
@@ -121,6 +121,8 @@
<string name="ln_url_outdated">LN-webcím (elavult)</string>
<string name="save_to_gallery">Mentés a galériába</string>
<string name="image_saved_to_the_gallery">Kép elmentve a képgalériába</string>
<string name="video_download_has_started_toast">A videó letöltése megkezdődött…</string>
<string name="media_download_has_started_toast">A média letöltése megkezdődött…</string>
<string name="failed_to_save_the_image">Nem sikerült elmenteni a képet</string>
<string name="video_saved_to_the_gallery">Videó elmentve a videógalériába</string>
<string name="failed_to_save_the_video">Nem sikerült elmenteni a videót</string>
@@ -598,6 +600,8 @@
<string name="copy_url_to_clipboard">Webcím másolása a vágólapra</string>
<string name="copy_the_note_id_to_the_clipboard">Bejegyzés-azonosító másolása a vágólapra</string>
<string name="add_media_to_gallery">Média hozzáadása a galériához</string>
<string name="media_added">Média hozzáadva</string>
<string name="media_added_to_profile_gallery">Média hozzáadva az Ön profil-galériájába</string>
<string name="created_at">Létrehozva ekkor:</string>
<string name="rules">Szabályok</string>
<string name="login_with_external_signer">Bejelentkezés Amberrel</string>
@@ -606,6 +610,7 @@
<string name="poll_zap_value_min_max_explainer">A szavazatok egy Zap összeggel vannak súlyozva. Beállíthat egy minimális összeget, hogy elkerülje a spamelőket, és egy maximális összeget, hogy elkerülje, hogy a nagy Zap-elők átvegyék a szavazást. Használja ugyanazt az összeget mindkét mezőben, hogy minden szavazatot ugyanannyira értékeljen. Hagyja üresen, hogy bármilyen összeget elfogadjon.</string>
<string name="error_dialog_zap_error">Nem sikerült Zap-et küldeni</string>
<string name="error_dialog_talk_to_user">Üzenet a felhasználónak</string>
<string name="error_dialog_talk_to_user_name">Üzenet: %1$s</string>
<string name="error_dialog_button_ok">OK</string>
<string name="relay_information_document_error_assemble_url">Nem sikerült elérni a következőt: %1$s: %2$s</string>
<string name="relay_information_document_error_failed_to_assemble_url">Nem sikerült összeállítani a NIP-11 webcímet a következőhöz: %1$s: %2$s</string>
@@ -665,6 +670,7 @@
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct">Nem sikerült megoldani a következőt: %1$s. Ellenőrizze, hogy kapcsolódik-e, vagy működik-e a kiszolgáló, és hogy a(z) %2$s lightning-cím helyes-e</string>
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct_exception">Nem sikerült megoldani a következőt: %1$s. Ellenőrizze, hogy kapcsolódik-e, vagy működik-e a kiszolgáló, és hogy a(z) %2$s lightning-cím helyes-e.\n\nKivéve: %3$s</string>
<string name="could_not_fetch_invoice_from">Nem sikerült a számlát a következőtől lekérni: %1$s</string>
<string name="could_not_fetch_invoice_from_details">Nem sikerült lekérni a számlát a következőtől: %1$s: %2$s</string>
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup">Hiba a Lightning-címből származó JSON elemzésekor. Ellenőrizze a felhasználó Lightning-beállítását</string>
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup_with_user">Hiba történt a(z) %1$s JSON elemzésekor. Ellenőrizze a felhasználó Lightning-beállítását</string>
<string name="callback_url_not_found_in_the_user_s_lightning_address_server_configuration">A visszahívási webcím nem található a felhasználó lightning-cím-kiszolgálójának konfigurációjában</string>
@@ -734,6 +740,7 @@
<string name="could_not_download_from_the_server">Nem sikerült letölteni a kiszolgálóról az oda feltöltött médiát</string>
<string name="could_not_check_downloaded_file">Nem lehetett ellenőrizni a letöltött fájlt a feltöltés után: %1$s</string>
<string name="could_not_prepare_local_file_to_upload">Nem sikerült előkészíteni feltöltésre a helyi fájlt: %1$s</string>
<string name="failed_to_upload_to_server_with_message">Nem sikerült feltölteni a következőre: %1$s: %2$s</string>
<string name="failed_to_upload_with_message">Nem sikerült feltölteni: %1$s</string>
<string name="failed_to_delete_with_message">Nem sikerült törölni: %1$s</string>
<string name="media_too_big_for_nip95">Média túl nagy a NIP-95 számára</string>
@@ -803,6 +810,7 @@
<string name="search_relays_not_found_description">A kifejezetten a kereséshez és a felhasználói címkézéshez tervezett átjátszólista létrehozása javítani fogja ezeket az eredményeket.</string>
<string name="search_relays_not_found_editing">Adjon hozzá 13 átjátszót a tartalom kereséshez vagy a felhasználók címkézéséhez. Győződjön meg arról, hogy a kiválasztott átjátszók alkalmazzák a NIP-50-et</string>
<string name="search_relays_not_found_examples">Jó választási lehetőségek:\n - nostr.wine\n - relay.nostr.band\n - relay.noswhere.com</string>
<string name="dm_upload">Feltöltés közvetlen üzenetbe</string>
<string name="relay_settings">Átjátszó-beállítások</string>
<string name="public_home_section">Nyilvános kimenő és saját átjátszók</string>
<string name="public_home_section_explainer">Ez az átjátszótípus tárolja az összes tartalmat. Az Amethyst ide küldi az Ön bejegyzéseit, és mások ezeket az átjátszókat fogják használni, hogy megtalálják az Ön tartalmát. Adjon hozzá 13 átjátszót. Ezek lehetnek személyes-, fizetett- vagy nyilvános átjátszók.</string>
@@ -121,6 +121,8 @@
<string name="ln_url_outdated">LN URL (verouderd)</string>
<string name="save_to_gallery">Opslaan in galerij</string>
<string name="image_saved_to_the_gallery">Afbeelding opgeslagen in galerij</string>
<string name="video_download_has_started_toast">Video download is gestart…</string>
<string name="media_download_has_started_toast">Media download is gestart…</string>
<string name="failed_to_save_the_image">De afbeelding is niet opgeslagen</string>
<string name="video_saved_to_the_gallery">Video\'s opgeslagen in galerij</string>
<string name="failed_to_save_the_video">De video is niet opgeslagen</string>
@@ -598,6 +600,8 @@
<string name="copy_url_to_clipboard">Kopieer URL naar klembord</string>
<string name="copy_the_note_id_to_the_clipboard">Note naar klembord kopiëren</string>
<string name="add_media_to_gallery">Media toevoegen aan galerij</string>
<string name="media_added">Media toegevoegd</string>
<string name="media_added_to_profile_gallery">Media toegevoegd aan je profielgalerij</string>
<string name="created_at">Gemaakt op</string>
<string name="rules">Regels</string>
<string name="login_with_external_signer">Login met Amber</string>
@@ -606,6 +610,7 @@
<string name="poll_zap_value_min_max_explainer">Stemmen worden gewogen door het aantal zap. U kunt een minimumbedrag instellen om spammers te voorkomen en een maximumbedrag om te voorkomen dat een grote zappers de enquête overneemt. Gebruik hetzelfde bedrag in beide velden om ervoor te zorgen dat elke stem hetzelfde wordt gewaardeerd. Laat het leeg om elk bedrag te accepteren.</string>
<string name="error_dialog_zap_error">Kan zap niet verzenden</string>
<string name="error_dialog_talk_to_user">Bericht de gebruiker</string>
<string name="error_dialog_talk_to_user_name">Bericht %1$s</string>
<string name="error_dialog_button_ok">Ok</string>
<string name="relay_information_document_error_assemble_url">Mislukt om %1$s te bereiken: %2$s</string>
<string name="relay_information_document_error_failed_to_assemble_url">Fout bij het verzamelen van de NIP-11 url voor %1$s: %2$s</string>
@@ -665,6 +670,7 @@
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct">Kan %1$s niet oplossen. Controleer of u verbonden bent, of de server online is en of het Lightning Adress %2$s juist is</string>
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct_exception">Kon %1$s niet oplossen. Controleer of u verbonden bent, of de server online is en of het Lightning Adress %2$s juist is. \n\nUitzondering was: %3$s</string>
<string name="could_not_fetch_invoice_from">Kan invoice niet ophalen van %1$s</string>
<string name="could_not_fetch_invoice_from_details">Kan invoice niet ophalen van %1$s: %2$s</string>
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup">Fout bij het verwerken van JSON uit Lightning Adress. Controleer de Lightning setup van de gebruiker</string>
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup_with_user">Fout bij het verwerken van JSON uit %1$s. Controleer de Lightning set-up van de gebruiker</string>
<string name="callback_url_not_found_in_the_user_s_lightning_address_server_configuration">Callback URL niet gevonden in de Lightning Adress configuratie van de gebruiker</string>
@@ -734,6 +740,7 @@
<string name="could_not_download_from_the_server">Kan de geüploade media niet downloaden van de server</string>
<string name="could_not_check_downloaded_file">Kan het gedownloade bestand niet controleren na upload: %1$s</string>
<string name="could_not_prepare_local_file_to_upload">Kon lokaal bestand niet voorbereiden voor upload: %1$s</string>
<string name="failed_to_upload_to_server_with_message">Mislukt om naar %1$s te uploaden: %2$s</string>
<string name="failed_to_upload_with_message">Uploaden van media mislukt: %1$s</string>
<string name="failed_to_delete_with_message">Verwijderen van media mislukt: %1$s</string>
<string name="media_too_big_for_nip95">Media is te groot voor NIP-95</string>
@@ -803,6 +810,7 @@
<string name="search_relays_not_found_description">Het maken van een relay lijst speciaal ontworpen voor zoekopdracht en gebruikers-tagging zal deze resultaten verbeteren.</string>
<string name="search_relays_not_found_editing">Voeg tussen de 1 en 3 relays toe om te gebruiken bij het zoeken naar content of bij het taggen van gebruikers. Zorg ervoor dat uw gekozen relays implementatie van NIP-50</string>
<string name="search_relays_not_found_examples">Goede opties zijn:\n - nostr.wine\n - relay.nostr.band\n - relay.noswhere.com</string>
<string name="dm_upload">DM uploaden</string>
<string name="relay_settings">Relay-instellingen</string>
<string name="public_home_section">Openbare thuis relays</string>
<string name="public_home_section_explainer">Dit relay type slaat al je inhoud op. Amethyst stuurt je berichten hierheen en anderen gebruiken deze relays om je inhoud te vinden. Voeg tussen de 1 en 3 relays toe. Het gaat om persoonlijke relays, betaalde relays of openbare relays.</string>
@@ -608,6 +608,7 @@
<string name="poll_zap_value_min_max_explainer">Os votos são ponderados pelo valor do zap. Você pode definir um valor mínimo para evitar spammers e um valor máximo para evitar que grandes zappers assumam o controle da enquete. Use o mesmo valor em ambos os campos para garantir que cada voto tenha o mesmo valor. Deixe em branco para aceitar qualquer valor.</string>
<string name="error_dialog_zap_error">Não foi possível enviar o Zap</string>
<string name="error_dialog_talk_to_user">Mensagem ao usuário</string>
<string name="error_dialog_talk_to_user_name">Mensagem %1$s</string>
<string name="error_dialog_button_ok">OK</string>
<string name="relay_information_document_error_assemble_url">Falha ao acessar %1$s: %2$s</string>
<string name="relay_information_document_error_failed_to_assemble_url">Falha ao montar NIP-11 url para %1$s: %2$s</string>
@@ -667,6 +668,7 @@
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct">Não foi possível resolver %1$s. Verifique se você está conectado, se o servidor está funcionando e se o endereço Lightning %2$s está correto</string>
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct_exception">Não foi possível resolver %1$s. Verifique se você está conectado, se o servidor está funcionando e se o endereço Lightning %2$s está correto.\n\nExceção foi: %3$s</string>
<string name="could_not_fetch_invoice_from">Não foi possível obter a fatura de %1$s</string>
<string name="could_not_fetch_invoice_from_details">Não foi possível obter a fatura de %1$s: %2$s</string>
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup">Erro ao analisar JSON do Endereço Lightning. Verifique a configuração de Lightning do usuário</string>
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup_with_user">Erro ao analisar JSON do %1$s. Verifique a configuração lightning do usuário</string>
<string name="callback_url_not_found_in_the_user_s_lightning_address_server_configuration">URL de retorno não encontrada na configuração do servidor de endereço Lightning do usuário</string>
@@ -806,6 +808,7 @@
<string name="search_relays_not_found_description">Criar uma lista de relés especificamente projetada para pesquisa e marcação de usuários melhorará esses resultados.</string>
<string name="search_relays_not_found_editing">Insira entre 13 relés para usar ao pesquisar conteúdo ou marcar usuários. Certifique-se de que seus relés escolhidos implementem o NIP-50</string>
<string name="search_relays_not_found_examples">Boas opções são:\n - nostr.wine\n - relay.nostr.band\n - relay.noswhere.com</string>
<string name="dm_upload">Envio de DM</string>
<string name="relay_settings">Configurações de Relay</string>
<string name="public_home_section">Relés Públicos de Casa</string>
<string name="public_home_section_explainer">Esse tipo de relé armazena todo o seu conteúdo. Amethyst enviará suas postagens aqui e outros usarão esses relés para encontrar seu conteúdo. Insira entre 13 relés. Eles podem ser relés pessoais, relés pagos ou relés públicos.</string>
@@ -607,6 +607,7 @@
<string name="poll_zap_value_min_max_explainer">Röster viktas av zap-beloppet. Du kan ställa in en minsta mängd för att undvika spammare och en maximal mängd för att undvika att en stor zapper tar över omröstningen. Använd samma belopp i båda fälten för att se till att varje röst värderas till samma belopp. Lämna tomt för att acceptera valfritt belopp.</string>
<string name="error_dialog_zap_error">Kunde inte skicka zap</string>
<string name="error_dialog_talk_to_user">Meddela användaren</string>
<string name="error_dialog_talk_to_user_name">Meddelande %1$s</string>
<string name="error_dialog_button_ok">Ok</string>
<string name="relay_information_document_error_assemble_url">Misslyckades att nå %1$s: %2$s</string>
<string name="relay_information_document_error_failed_to_assemble_url">Det gick inte att montera NIP-11 url för %1$s: %2$s</string>
@@ -666,6 +667,7 @@
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct">Kunde inte lösa %1$s. Kontrollera om du är ansluten, om servern är uppe och om Lightning Addressen %2$s är korrekt</string>
<string name="could_not_resolve_check_if_you_are_connected_if_the_server_is_up_and_if_the_lightning_address_is_correct_exception">Kunde inte lösa %1$s. Kontrollera om du är ansluten, om servern är uppe och om Lightning Addressen %2$s är korrekt.\n\nUndantag var: %3$s</string>
<string name="could_not_fetch_invoice_from">Kunde inte hämta fakturan från %1$s</string>
<string name="could_not_fetch_invoice_from_details">Kunde inte hämta faktura från %1$s: %2$s</string>
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup">Fel vid tolkning av JSON från Lightning Address. Kontrollera användarens Lightning-konfiguration</string>
<string name="error_parsing_json_from_lightning_address_check_the_user_s_lightning_setup_with_user">Fel vid tolkning av JSON från %1$s. Kontrollera användarens blixtkonfiguration</string>
<string name="callback_url_not_found_in_the_user_s_lightning_address_server_configuration">Återuppringnings-URL hittades inte i användarens konfiguration för Lightning Address-servern</string>
@@ -805,6 +807,7 @@
<string name="search_relays_not_found_description">Att skapa en relälista speciellt utformad för sökning och användartaggning kommer att förbättra dessa resultat.</string>
<string name="search_relays_not_found_editing">Sätt in mellan 13 reläer att använda när du söker efter innehåll eller taggar användare. Se till att dina valda reläer implementerar NIP-50</string>
<string name="search_relays_not_found_examples">Bra alternativ är:\n - nostr.wine\n - relay.nostr.band\n - relay.noswhere.com</string>
<string name="dm_upload">DM uppladdning</string>
<string name="relay_settings">Relä inställningar</string>
<string name="public_home_section">Offentliga hemreläer</string>
<string name="public_home_section_explainer">Denna typ av relä lagrar allt ditt innehåll. Amethyst skickar dina inlägg hit och andra kommer att använda dessa reläer för att hitta ditt innehåll. Sätt in mellan 13 reläer. De kan vara personliga reläer, betalda reläer eller offentliga reläer.</string>
+2
View File
@@ -377,6 +377,8 @@
<string name="add_caption">Add a Caption</string>
<string name="add_caption_example">My lovely friend</string>
<string name="use_direct_url">Use direct URL</string>
<string name="content_description">Description of the contents</string>
<string name="content_description_example">A blue boat in a white sandy beach at sunset</string>
@@ -50,6 +50,24 @@ class Nip30CustomEmoji {
fun createEmojiMap(tags: ImmutableListOfLists<String>): Map<String, String> = tags.lists.filter { it.size > 2 && it[0] == "emoji" }.associate { ":${it[1]}:" to it[2] }
fun findAllEmojis(input: String): List<String> {
val matcher = customEmojiPattern.matcher(input)
val emojiNamesInOrder = mutableListOf<String>()
while (matcher.find()) {
emojiNamesInOrder.add(matcher.group())
}
return emojiNamesInOrder
}
fun findAllEmojiCodes(input: String): List<String> {
val matcher = customEmojiPattern.matcher(input)
val emojiNamesInOrder = mutableListOf<String>()
while (matcher.find()) {
matcher.group(1)?.let { emojiNamesInOrder.add(it) }
}
return emojiNamesInOrder
}
fun assembleAnnotatedList(
input: String,
allTags: ImmutableListOfLists<String>?,
@@ -65,12 +83,7 @@ class Nip30CustomEmoji {
input: String,
emojiPairs: Map<String, String>,
): ImmutableList<Renderable>? {
val matcher = customEmojiPattern.matcher(input)
val emojiNamesInOrder = mutableListOf<String>()
while (matcher.find()) {
emojiNamesInOrder.add(matcher.group())
}
val emojiNamesInOrder = findAllEmojis(input)
if (emojiNamesInOrder.isEmpty()) {
return null
}
@@ -62,6 +62,7 @@ class ChannelMessageEvent(
directMentions: Set<HexKey> = emptySet(),
geohash: String? = null,
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
isDraft: Boolean,
onReady: (ChannelMessageEvent) -> Unit,
) {
@@ -88,6 +89,7 @@ class ChannelMessageEvent(
imetas?.forEach {
tags.add(Nip92MediaAttachments.createTag(it))
}
emojis?.forEach { tags.add(it.toTagArray()) }
tags.add(
arrayOf("alt", ALT),
)
@@ -84,6 +84,7 @@ class ChatMessageEvent(
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
isDraft: Boolean,
onReady: (ChatMessageEvent) -> Unit,
) {
@@ -103,6 +104,7 @@ class ChatMessageEvent(
imetas?.forEach {
tags.add(Nip92MediaAttachments.createTag(it))
}
emojis?.forEach { tags.add(it.toTagArray()) }
// tags.add(arrayOf("alt", alt))
if (isDraft) {
@@ -115,6 +115,7 @@ class ClassifiedsEvent(
zapRaiserAmount: Long?,
geohash: String? = null,
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
isDraft: Boolean,
@@ -190,9 +191,8 @@ class ClassifiedsEvent(
}
zapRaiserAmount?.let { tags.add(arrayOf("zapraiser", "$it")) }
geohash?.let { tags.addAll(geohashMipMap(it)) }
imetas?.forEach {
tags.add(Nip92MediaAttachments.createTag(it))
}
imetas?.forEach { tags.add(Nip92MediaAttachments.createTag(it)) }
emojis?.forEach { tags.add(it.toTagArray()) }
tags.add(arrayOf("alt", ALT))
if (isDraft) {
@@ -98,6 +98,7 @@ class CommentEvent(
addressesMentioned: Set<ATag> = emptySet(),
eventsMentioned: Set<ETag> = emptySet(),
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
geohash: String? = null,
zapReceiver: List<ZapSplitSetup>? = null,
markAsSensitive: Boolean = false,
@@ -120,7 +121,7 @@ class CommentEvent(
tags.add(removeTrailingNullsAndEmptyOthers("e", replyingTo.event.id, replyingTo.relay, replyingTo.event.pubKey))
tags.add(arrayOf("k", "${replyingTo.event.kind}"))
create(msg, tags, usersMentioned, addressesMentioned, eventsMentioned, imetas, geohash, zapReceiver, markAsSensitive, zapRaiserAmount, isDraft, signer, createdAt, onReady)
create(msg, tags, usersMentioned, addressesMentioned, eventsMentioned, imetas, emojis, geohash, zapReceiver, markAsSensitive, zapRaiserAmount, isDraft, signer, createdAt, onReady)
}
fun replyComment(
@@ -130,6 +131,7 @@ class CommentEvent(
addressesMentioned: Set<ATag> = emptySet(),
eventsMentioned: Set<ETag> = emptySet(),
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
geohash: String? = null,
zapReceiver: List<ZapSplitSetup>? = null,
markAsSensitive: Boolean = false,
@@ -147,7 +149,7 @@ class CommentEvent(
tags.add(removeTrailingNullsAndEmptyOthers("e", replyingTo.event.id, replyingTo.relay, replyingTo.event.pubKey))
tags.add(arrayOf("k", "${replyingTo.event.kind}"))
create(msg, tags, usersMentioned, addressesMentioned, eventsMentioned, imetas, geohash, zapReceiver, markAsSensitive, zapRaiserAmount, isDraft, signer, createdAt, onReady)
create(msg, tags, usersMentioned, addressesMentioned, eventsMentioned, imetas, emojis, geohash, zapReceiver, markAsSensitive, zapRaiserAmount, isDraft, signer, createdAt, onReady)
}
fun createGeoComment(
@@ -157,6 +159,7 @@ class CommentEvent(
addressesMentioned: Set<ATag> = emptySet(),
eventsMentioned: Set<ETag> = emptySet(),
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
zapReceiver: List<ZapSplitSetup>? = null,
markAsSensitive: Boolean = false,
zapRaiserAmount: Long? = null,
@@ -169,7 +172,7 @@ class CommentEvent(
geohash?.let { tags.addAll(rootGeohashMipMap(it)) }
tags.add(arrayOf("K", "geo"))
create(msg, tags, usersMentioned, addressesMentioned, eventsMentioned, imetas, null, zapReceiver, markAsSensitive, zapRaiserAmount, isDraft, signer, createdAt, onReady)
create(msg, tags, usersMentioned, addressesMentioned, eventsMentioned, imetas, emojis, null, zapReceiver, markAsSensitive, zapRaiserAmount, isDraft, signer, createdAt, onReady)
}
private fun create(
@@ -179,6 +182,7 @@ class CommentEvent(
addressesMentioned: Set<ATag> = emptySet(),
eventsMentioned: Set<ETag> = emptySet(),
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
geohash: String? = null,
zapReceiver: List<ZapSplitSetup>? = null,
markAsSensitive: Boolean = false,
@@ -202,6 +206,8 @@ class CommentEvent(
findURLs(msg).forEach { tags.add(arrayOf("r", it)) }
emojis?.forEach { tags.add(it.toTagArray()) }
zapReceiver?.forEach {
tags.add(arrayOf("zap", it.lnAddressOrPubKeyHex, it.relay ?: "", it.weight.toString()))
}
@@ -62,6 +62,8 @@ data class EmojiUrl(
) {
fun encode(): String = ":$code:$url"
fun toTagArray() = arrayOf("emoji", code, url)
companion object {
fun decode(encodedEmojiSetup: String): EmojiUrl? {
val emojiParts = encodedEmojiSetup.split(":", limit = 3)
@@ -71,5 +73,12 @@ data class EmojiUrl(
null
}
}
fun parse(tag: Array<String>): EmojiUrl? =
if (tag.size > 2 && tag[0] == "emoji") {
EmojiUrl(tag[1], tag[2])
} else {
null
}
}
}
@@ -42,6 +42,10 @@ class EmojiPackSelectionEvent(
const val FIXED_D_TAG = ""
const val ALT = "Emoji selection"
fun createAddressATag(pubKey: HexKey): ATag = ATag(KIND, pubKey, AdvertisedRelayListEvent.FIXED_D_TAG, null)
fun createAddressTag(pubKey: HexKey): String = ATag.assembleATag(KIND, pubKey, AdvertisedRelayListEvent.FIXED_D_TAG)
fun create(
listOfEmojiPacks: List<ATag>?,
signer: NostrSigner,
@@ -164,7 +164,7 @@ open class Event(
ATag.parse(aTagValue, relay)
}
override fun taggedEmojis() = tags.filter { it.size > 2 && it[0] == "emoji" }.map { EmojiUrl(it[1], it[2]) }
override fun taggedEmojis() = tags.filter { it.size > 2 && it[0] == "emoji" }.mapNotNull { EmojiUrl.parse(it) }
override fun isSensitive() =
tags.any {
@@ -92,6 +92,7 @@ class GitReplyEvent(
directMentions: Set<HexKey> = emptySet(),
geohash: String? = null,
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
forkedFrom: Event? = null,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
@@ -152,6 +153,7 @@ class GitReplyEvent(
imetas?.forEach {
tags.add(Nip92MediaAttachments.createTag(it))
}
emojis?.forEach { tags.add(it.toTagArray()) }
tags.add(arrayOf("alt", "a git issue reply"))
if (isDraft) {
@@ -53,6 +53,7 @@ open class InteractiveStoryBaseEvent(
zapRaiserAmount: Long? = null,
geohash: String? = null,
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
): Array<Array<String>> {
val tags = mutableListOf<Array<String>>()
findHashtags(content).forEach {
@@ -75,6 +76,7 @@ open class InteractiveStoryBaseEvent(
imetas?.forEach {
tags.add(Nip92MediaAttachments.createTag(it))
}
emojis?.forEach { tags.add(it.toTagArray()) }
return tags.toTypedArray()
}
@@ -75,6 +75,7 @@ class LiveActivitiesChatMessageEvent(
zapRaiserAmount: Long?,
geohash: String? = null,
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
isDraft: Boolean,
onReady: (LiveActivitiesChatMessageEvent) -> Unit,
) {
@@ -96,6 +97,7 @@ class LiveActivitiesChatMessageEvent(
imetas?.forEach {
tags.add(Nip92MediaAttachments.createTag(it))
}
emojis?.forEach { tags.add(it.toTagArray()) }
tags.add(arrayOf("alt", ALT))
if (isDraft) {
@@ -82,6 +82,7 @@ class NIP17Factory {
zapRaiserAmount: Long? = null,
geohash: String? = null,
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
draftTag: String? = null,
onReady: (Result) -> Unit,
) {
@@ -100,6 +101,7 @@ class NIP17Factory {
geohash = geohash,
isDraft = draftTag != null,
imetas = imetas,
emojis = emojis,
) { senderMessage ->
if (draftTag != null) {
onReady(
@@ -81,6 +81,7 @@ class PollNoteEvent(
zapRaiserAmount: Long?,
geohash: String? = null,
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
isDraft: Boolean,
onReady: (PollNoteEvent) -> Unit,
) {
@@ -108,6 +109,7 @@ class PollNoteEvent(
imetas?.forEach {
tags.add(Nip92MediaAttachments.createTag(it))
}
emojis?.forEach { tags.add(it.toTagArray()) }
tags.add(arrayOf("alt", ALT))
if (isDraft) {
@@ -58,6 +58,7 @@ class TextNoteEvent(
directMentions: Set<HexKey> = emptySet(),
geohash: String? = null,
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
forkedFrom: Event? = null,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
@@ -123,9 +124,8 @@ class TextNoteEvent(
}
zapRaiserAmount?.let { tags.add(arrayOf("zapraiser", "$it")) }
geohash?.let { tags.addAll(geohashMipMap(it)) }
imetas?.forEach {
tags.add(Nip92MediaAttachments.createTag(it))
}
imetas?.forEach { tags.add(Nip92MediaAttachments.createTag(it)) }
emojis?.forEach { tags.add(it.toTagArray()) }
if (isDraft) {
signer.assembleRumor(createdAt, KIND, tags.toTypedArray(), msg, onReady)
@@ -63,6 +63,7 @@ class TorrentCommentEvent(
zapRaiserAmount: Long?,
geohash: String? = null,
imetas: List<IMetaTag>? = null,
emojis: List<EmojiUrl>? = null,
forkedFrom: Event? = null,
isDraft: Boolean,
onReady: (TorrentCommentEvent) -> Unit,
@@ -126,6 +127,7 @@ class TorrentCommentEvent(
imetas?.forEach {
tags.add(Nip92MediaAttachments.createTag(it))
}
emojis?.forEach { tags.add(it.toTagArray()) }
tags.add(arrayOf("alt", ALT))
if (isDraft) {