Merge branch 'vitorpamplona:main' into main
This commit is contained in:
@@ -117,7 +117,7 @@ object ServiceManager {
|
||||
LocalCache.pruneOldAndHiddenMessages(it)
|
||||
LocalCache.pruneHiddenMessages(it)
|
||||
LocalCache.pruneContactLists(it)
|
||||
// LocalCache.pruneNonFollows(it)
|
||||
LocalCache.pruneRepliesAndReactions(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1110,6 +1110,23 @@ object LocalCache {
|
||||
refreshObservers(note)
|
||||
}
|
||||
|
||||
private fun consume(event: ChatMessageEvent, relay: Relay?) {
|
||||
val note = getOrCreateNote(event.id)
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
|
||||
if (relay != null) {
|
||||
author.addRelayBeingUsed(relay, event.createdAt)
|
||||
note.addRelay(relay)
|
||||
}
|
||||
|
||||
// Already processed this event.
|
||||
if (note.event != null) return
|
||||
|
||||
note.loadEvent(event, author, emptyList())
|
||||
|
||||
refreshObservers(note)
|
||||
}
|
||||
|
||||
private fun consume(event: SealedGossipEvent, relay: Relay?) {
|
||||
val note = getOrCreateNote(event.id)
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
@@ -1219,13 +1236,12 @@ object LocalCache {
|
||||
}
|
||||
|
||||
return notes.values.filter {
|
||||
(it.event is TextNoteEvent && it.event?.content()?.contains(text, true) ?: false) ||
|
||||
(it.event is PollNoteEvent && it.event?.content()?.contains(text, true) ?: false) ||
|
||||
(it.event is ChannelMessageEvent && it.event?.content()?.contains(text, true) ?: false) ||
|
||||
it.event?.content()?.contains(text, true) ?: false ||
|
||||
it.event?.matchTag1With(text) ?: false ||
|
||||
it.idHex.startsWith(text, true) ||
|
||||
it.idNote().startsWith(text, true)
|
||||
} + addressables.values.filter {
|
||||
(it.event as? LongTextNoteEvent)?.content?.contains(text, true) ?: false ||
|
||||
it.event?.content()?.contains(text, true) ?: false ||
|
||||
it.event?.matchTag1With(text) ?: false ||
|
||||
it.idHex.startsWith(text, true)
|
||||
}
|
||||
@@ -1256,6 +1272,10 @@ object LocalCache {
|
||||
it.value.clearLive()
|
||||
}
|
||||
|
||||
addressables.forEach {
|
||||
it.value.clearLive()
|
||||
}
|
||||
|
||||
users.forEach {
|
||||
it.value.clearLive()
|
||||
}
|
||||
@@ -1267,6 +1287,8 @@ object LocalCache {
|
||||
channels.forEach { it ->
|
||||
val toBeRemoved = it.value.pruneOldAndHiddenMessages(account)
|
||||
|
||||
val childrenToBeRemoved = mutableListOf<Note>()
|
||||
|
||||
toBeRemoved.forEach {
|
||||
notes.remove(it.idHex)
|
||||
// Doesn't need to clean up the replies and mentions.. Too small to matter.
|
||||
@@ -1275,17 +1297,84 @@ object LocalCache {
|
||||
it.replyTo?.forEach { _ ->
|
||||
it.removeReply(it)
|
||||
}
|
||||
|
||||
childrenToBeRemoved.addAll(it.removeAllChildNotes())
|
||||
}
|
||||
|
||||
removeChildrenOf(childrenToBeRemoved)
|
||||
|
||||
if (toBeRemoved.size > 100 || it.value.notes.size > 100) {
|
||||
println("PRUNE: ${toBeRemoved.size} messages removed from ${it.value.toBestDisplayName()}. ${it.value.notes.size} kept")
|
||||
}
|
||||
}
|
||||
|
||||
users.forEach { userPair ->
|
||||
userPair.value.privateChatrooms.values.map {
|
||||
val toBeRemoved = it.pruneMessagesToTheLatestOnly()
|
||||
|
||||
val childrenToBeRemoved = mutableListOf<Note>()
|
||||
|
||||
toBeRemoved.forEach {
|
||||
notes.remove(it.idHex)
|
||||
|
||||
// Counts the replies
|
||||
it.replyTo?.forEach { _ ->
|
||||
it.removeReply(it)
|
||||
}
|
||||
|
||||
childrenToBeRemoved.addAll(it.removeAllChildNotes())
|
||||
}
|
||||
|
||||
removeChildrenOf(childrenToBeRemoved)
|
||||
|
||||
if (toBeRemoved.size > 1) {
|
||||
println("PRUNE: ${toBeRemoved.size} private messages with ${userPair.value.toBestDisplayName()} removed. ${it.roomMessages.size} kept")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun pruneRepliesAndReactions(account: Account) {
|
||||
checkNotInMainThread()
|
||||
val user = account.userProfile()
|
||||
|
||||
val toBeRemoved = notes.filter {
|
||||
(
|
||||
(it.value.event is TextNoteEvent && !it.value.isNewThread()) ||
|
||||
it.value.event is ReactionEvent || it.value.event is LnZapEvent || it.value.event is LnZapRequestEvent ||
|
||||
it.value.event is ReportEvent || it.value.event is GenericRepostEvent
|
||||
) &&
|
||||
it.value.liveSet?.isInUse() != true && // don't delete if observing.
|
||||
it.value.author != user && // don't delete if it is the logged in account
|
||||
it.value.event?.isTaggedUser(user.pubkeyHex) != true // don't delete if it's a notification to the logged in user
|
||||
}.values
|
||||
|
||||
val childrenToBeRemoved = mutableListOf<Note>()
|
||||
|
||||
toBeRemoved.forEach {
|
||||
notes.remove(it.idHex)
|
||||
|
||||
it.replyTo?.forEach { masterNote ->
|
||||
masterNote.removeReply(it)
|
||||
masterNote.removeBoost(it)
|
||||
masterNote.removeReaction(it)
|
||||
masterNote.removeZap(it)
|
||||
it.clearEOSE() // allows reloading of these events
|
||||
}
|
||||
|
||||
childrenToBeRemoved.addAll(it.removeAllChildNotes())
|
||||
}
|
||||
|
||||
if (toBeRemoved.size > 1) {
|
||||
println("PRUNE: ${toBeRemoved.size} thread replies removed.")
|
||||
}
|
||||
}
|
||||
|
||||
fun pruneHiddenMessages(account: Account) {
|
||||
checkNotInMainThread()
|
||||
|
||||
val childrenToBeRemoved = mutableListOf<Note>()
|
||||
|
||||
val toBeRemoved = account.hiddenUsers.map { userHex ->
|
||||
(
|
||||
notes.values.filter {
|
||||
@@ -1307,11 +1396,30 @@ object LocalCache {
|
||||
}
|
||||
|
||||
notes.remove(it.idHex)
|
||||
|
||||
childrenToBeRemoved.addAll(it.removeAllChildNotes())
|
||||
}
|
||||
|
||||
removeChildrenOf(childrenToBeRemoved)
|
||||
|
||||
println("PRUNE: ${toBeRemoved.size} messages removed because they were Hidden")
|
||||
}
|
||||
|
||||
fun removeChildrenOf(nextToBeRemoved: List<Note>) {
|
||||
nextToBeRemoved.forEach { note ->
|
||||
if (note.event is LnZapEvent) {
|
||||
(note.event as LnZapEvent).zappedAuthor().mapNotNull { getUserIfExists(it)?.removeZap(note) }
|
||||
}
|
||||
if (note.event is LnZapRequestEvent) {
|
||||
(note.event as LnZapRequestEvent).zappedAuthor().mapNotNull { getUserIfExists(it)?.removeZap(note) }
|
||||
}
|
||||
if (note.event is ReportEvent) {
|
||||
(note.event as ReportEvent).reportedAuthor().mapNotNull { getUserIfExists(it.key)?.removeZap(note) }
|
||||
}
|
||||
notes.remove(note.idHex)
|
||||
}
|
||||
}
|
||||
|
||||
fun pruneContactLists(userAccount: Account) {
|
||||
checkNotInMainThread()
|
||||
|
||||
@@ -1359,6 +1467,7 @@ object LocalCache {
|
||||
is ChannelMessageEvent -> consume(event, relay)
|
||||
is ChannelMetadataEvent -> consume(event)
|
||||
is ChannelMuteUserEvent -> consume(event)
|
||||
is ChatMessageEvent -> consume(event, relay)
|
||||
is ClassifiedsEvent -> consume(event)
|
||||
is CommunityDefinitionEvent -> consume(event, relay)
|
||||
is CommunityPostApprovalEvent -> {
|
||||
|
||||
@@ -158,6 +158,35 @@ open class Note(val idHex: String) {
|
||||
boosts = boosts - note
|
||||
liveSet?.boosts?.invalidateData()
|
||||
}
|
||||
|
||||
fun removeAllChildNotes(): Set<Note> {
|
||||
val toBeRemoved = replies +
|
||||
reactions.values.flatten() +
|
||||
boosts +
|
||||
reports.values.flatten() +
|
||||
zaps.keys +
|
||||
zaps.values.filterNotNull() +
|
||||
zapPayments.keys +
|
||||
zapPayments.values.filterNotNull()
|
||||
|
||||
replies = setOf<Note>()
|
||||
reactions = mapOf<String, Set<Note>>()
|
||||
boosts = setOf<Note>()
|
||||
reports = mapOf<User, Set<Note>>()
|
||||
zaps = mapOf<Note, Note?>()
|
||||
zapPayments = mapOf<Note, Note?>()
|
||||
relays = setOf<String>()
|
||||
lastReactionsDownloadTime = emptyMap()
|
||||
|
||||
liveSet?.replies?.invalidateData()
|
||||
liveSet?.reactions?.invalidateData()
|
||||
liveSet?.boosts?.invalidateData()
|
||||
liveSet?.reports?.invalidateData()
|
||||
liveSet?.zaps?.invalidateData()
|
||||
|
||||
return toBeRemoved
|
||||
}
|
||||
|
||||
fun removeReaction(note: Note) {
|
||||
val tags = note.event?.tags() ?: emptyList()
|
||||
val reaction = note.event?.content()?.firstFullCharOrEmoji(ImmutableListOfLists(tags)) ?: "+"
|
||||
@@ -554,6 +583,10 @@ open class Note(val idHex: String) {
|
||||
zaps = emptyMap()
|
||||
}
|
||||
|
||||
fun clearEOSE() {
|
||||
lastReactionsDownloadTime = emptyMap()
|
||||
}
|
||||
|
||||
var liveSet: NoteLiveSet? = null
|
||||
|
||||
fun live(): NoteLiveSet {
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package com.vitorpamplona.amethyst.model
|
||||
|
||||
object TimeUtils {
|
||||
const val fiveMinutes = 60 * 5
|
||||
const val oneHour = 60 * 60
|
||||
const val oneDay = 24 * 60 * 60
|
||||
const val oneMinute = 60
|
||||
const val fiveMinutes = 5 * oneMinute
|
||||
const val oneHour = 60 * oneMinute
|
||||
const val eightHours = 8 * oneHour
|
||||
const val oneDay = 24 * oneHour
|
||||
const val oneWeek = 7 * oneDay
|
||||
|
||||
fun now() = System.currentTimeMillis() / 1000
|
||||
fun fiveMinutesAgo() = now() - fiveMinutes
|
||||
fun oneHourAgo() = now() - oneHour
|
||||
fun oneDayAgo() = now() - oneDay
|
||||
fun eightHoursAgo() = now() - (oneHour * 8)
|
||||
fun eightHoursAgo() = now() - eightHours
|
||||
fun oneWeekAgo() = now() - oneWeek
|
||||
}
|
||||
|
||||
@@ -137,6 +137,16 @@ class User(val pubkeyHex: String) {
|
||||
}
|
||||
}
|
||||
|
||||
fun removeZap(zapRequestOrZapEvent: Note) {
|
||||
if (zapRequestOrZapEvent in zaps.keys) {
|
||||
zaps = zaps.minus(zapRequestOrZapEvent)
|
||||
liveSet?.zaps?.invalidateData()
|
||||
} else if (zapRequestOrZapEvent in zaps.values) {
|
||||
zaps = zaps.filter { it.value != zapRequestOrZapEvent }
|
||||
liveSet?.zaps?.invalidateData()
|
||||
}
|
||||
}
|
||||
|
||||
fun zappedAmount(): BigDecimal {
|
||||
return zaps.mapNotNull { it.value?.event }
|
||||
.filterIsInstance<LnZapEvent>()
|
||||
@@ -383,6 +393,22 @@ class Chatroom() {
|
||||
roomMessages = roomMessages + msg
|
||||
}
|
||||
}
|
||||
|
||||
fun pruneMessagesToTheLatestOnly(): Set<Note> {
|
||||
val sorted = roomMessages.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
|
||||
|
||||
val toKeep = if ((sorted.firstOrNull()?.createdAt() ?: 0) > TimeUtils.oneWeekAgo()) {
|
||||
// Recent messages, keep last 100
|
||||
sorted.take(100).toSet()
|
||||
} else {
|
||||
// Old messages, keep the last one.
|
||||
sorted.take(1).toSet()
|
||||
} + sorted.filter { it.liveSet?.isInUse() ?: false }
|
||||
|
||||
val toRemove = roomMessages.minus(toKeep)
|
||||
roomMessages = toKeep
|
||||
return toRemove
|
||||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.location.Geocoder
|
||||
import android.location.Location
|
||||
import android.location.LocationListener
|
||||
import android.location.LocationManager
|
||||
import android.os.Bundle
|
||||
import android.os.HandlerThread
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -53,9 +52,6 @@ class LocationUtil(context: Context) {
|
||||
locationStateFlow.value = location
|
||||
}
|
||||
|
||||
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
|
||||
}
|
||||
|
||||
override fun onProviderEnabled(provider: String) {
|
||||
providerState.value = true
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.service
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.model.GiftWrapEvent
|
||||
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||
import com.vitorpamplona.amethyst.service.relays.JsonFilter
|
||||
@@ -24,7 +25,7 @@ object NostrChatroomDataSource : NostrDataSource("ChatroomFeed") {
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.PRIVATE_DMS),
|
||||
filter = JsonFilter(
|
||||
kinds = listOf(PrivateDmEvent.kind),
|
||||
kinds = listOf(PrivateDmEvent.kind, GiftWrapEvent.kind),
|
||||
authors = listOf(myPeer.pubkeyHex),
|
||||
tags = mapOf("p" to listOf(account.userProfile().pubkeyHex))
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||
import com.vitorpamplona.amethyst.service.model.GiftWrapEvent
|
||||
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||
import com.vitorpamplona.amethyst.service.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.amethyst.service.relays.EOSEAccount
|
||||
@@ -20,7 +21,7 @@ object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") {
|
||||
fun createMessagesToMeFilter() = TypedFilter(
|
||||
types = setOf(FeedType.PRIVATE_DMS),
|
||||
filter = JsonFilter(
|
||||
kinds = listOf(PrivateDmEvent.kind),
|
||||
kinds = listOf(PrivateDmEvent.kind, GiftWrapEvent.kind),
|
||||
tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)),
|
||||
since = latestEOSEs.users[account.userProfile()]?.followList?.get(chatRoomList)?.relayList
|
||||
)
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package com.vitorpamplona.amethyst.service
|
||||
|
||||
import com.vitorpamplona.amethyst.service.model.AudioTrackEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ClassifiedsEvent
|
||||
import com.vitorpamplona.amethyst.service.model.HighlightEvent
|
||||
import com.vitorpamplona.amethyst.service.model.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
||||
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||
@@ -22,7 +26,7 @@ object NostrGeohashDataSource : NostrDataSource("SingleGeoHashFeed") {
|
||||
hashToLoad
|
||||
)
|
||||
),
|
||||
kinds = listOf(TextNoteEvent.kind, ChannelMessageEvent.kind, LongTextNoteEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind),
|
||||
kinds = listOf(TextNoteEvent.kind, ChannelMessageEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind, LiveActivitiesChatMessageEvent.kind, ClassifiedsEvent.kind, HighlightEvent.kind, AudioTrackEvent.kind),
|
||||
limit = 200
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package com.vitorpamplona.amethyst.service
|
||||
|
||||
import androidx.compose.ui.text.capitalize
|
||||
import com.vitorpamplona.amethyst.service.model.AudioTrackEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ClassifiedsEvent
|
||||
import com.vitorpamplona.amethyst.service.model.HighlightEvent
|
||||
import com.vitorpamplona.amethyst.service.model.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
||||
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||
@@ -26,7 +30,7 @@ object NostrHashtagDataSource : NostrDataSource("SingleHashtagFeed") {
|
||||
hashToLoad.capitalize()
|
||||
)
|
||||
),
|
||||
kinds = listOf(TextNoteEvent.kind, ChannelMessageEvent.kind, LongTextNoteEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind),
|
||||
kinds = listOf(TextNoteEvent.kind, ChannelMessageEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind, LiveActivitiesChatMessageEvent.kind, ClassifiedsEvent.kind, HighlightEvent.kind, AudioTrackEvent.kind),
|
||||
limit = 200
|
||||
)
|
||||
)
|
||||
|
||||
+23
-1
@@ -9,7 +9,7 @@ import com.vitorpamplona.amethyst.service.relays.JsonFilter
|
||||
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
||||
import fr.acinq.secp256k1.Hex
|
||||
|
||||
object NostrSearchEventOrUserDataSource : NostrDataSource("SingleEventFeed") {
|
||||
object NostrSearchEventOrUserDataSource : NostrDataSource("SearchEventFeed") {
|
||||
private var searchString: String? = null
|
||||
|
||||
private fun createAnythingWithIDFilter(): List<TypedFilter>? {
|
||||
@@ -54,6 +54,28 @@ object NostrSearchEventOrUserDataSource : NostrDataSource("SingleEventFeed") {
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.SEARCH),
|
||||
filter = JsonFilter(
|
||||
kinds = listOf(
|
||||
TextNoteEvent.kind, LongTextNoteEvent.kind, BadgeDefinitionEvent.kind,
|
||||
PeopleListEvent.kind, BookmarkListEvent.kind, AudioTrackEvent.kind, PinListEvent.kind,
|
||||
PollNoteEvent.kind, ChannelCreateEvent.kind
|
||||
),
|
||||
search = mySearchString,
|
||||
limit = 100
|
||||
)
|
||||
),
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.SEARCH),
|
||||
filter = JsonFilter(
|
||||
kinds = listOf(
|
||||
ChannelMetadataEvent.kind,
|
||||
ClassifiedsEvent.kind,
|
||||
CommunityDefinitionEvent.kind,
|
||||
EmojiPackEvent.kind,
|
||||
HighlightEvent.kind,
|
||||
LiveActivitiesEvent.kind,
|
||||
PollNoteEvent.kind,
|
||||
NNSEvent.kind
|
||||
),
|
||||
search = mySearchString,
|
||||
limit = 100
|
||||
)
|
||||
|
||||
+16
-16
@@ -12,7 +12,7 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
|
||||
private var eventsToWatch = setOf<Note>()
|
||||
private var addressesToWatch = setOf<Note>()
|
||||
|
||||
private fun createTagToAddressFilter(): List<TypedFilter>? {
|
||||
private fun createReactionsToWatchInAddressFilter(): List<TypedFilter>? {
|
||||
val addressesToWatch = eventsToWatch.filter { it.address() != null } + addressesToWatch
|
||||
|
||||
if (addressesToWatch.isEmpty()) {
|
||||
@@ -25,12 +25,15 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter = JsonFilter(
|
||||
kinds = listOf(
|
||||
TextNoteEvent.kind, LongTextNoteEvent.kind,
|
||||
ReactionEvent.kind, RepostEvent.kind, GenericRepostEvent.kind, ReportEvent.kind,
|
||||
LnZapEvent.kind, LnZapRequestEvent.kind,
|
||||
BadgeAwardEvent.kind, BadgeDefinitionEvent.kind, BadgeProfilesEvent.kind,
|
||||
PollNoteEvent.kind, AudioTrackEvent.kind, PinListEvent.kind,
|
||||
PeopleListEvent.kind, BookmarkListEvent.kind
|
||||
TextNoteEvent.kind,
|
||||
ReactionEvent.kind,
|
||||
RepostEvent.kind,
|
||||
GenericRepostEvent.kind,
|
||||
ReportEvent.kind,
|
||||
LnZapEvent.kind,
|
||||
PollNoteEvent.kind,
|
||||
CommunityPostApprovalEvent.kind,
|
||||
LiveActivitiesChatMessageEvent.kind
|
||||
),
|
||||
tags = mapOf("a" to listOf(aTag.toTag())),
|
||||
since = it.lastReactionsDownloadTime,
|
||||
@@ -55,7 +58,8 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
|
||||
types = COMMON_FEED_TYPES,
|
||||
filter = JsonFilter(
|
||||
kinds = listOf(aTag.kind),
|
||||
authors = listOf(aTag.pubKeyHex)
|
||||
authors = listOf(aTag.pubKeyHex),
|
||||
limit = 1000 // Max amount of "replies" to download on a specific event.
|
||||
)
|
||||
)
|
||||
} else {
|
||||
@@ -64,7 +68,8 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
|
||||
filter = JsonFilter(
|
||||
kinds = listOf(aTag.kind),
|
||||
tags = mapOf("d" to listOf(aTag.dTag)),
|
||||
authors = listOf(aTag.pubKeyHex)
|
||||
authors = listOf(aTag.pubKeyHex),
|
||||
limit = 1000 // Max amount of "replies" to download on a specific event.
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -85,17 +90,12 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
|
||||
filter = JsonFilter(
|
||||
kinds = listOf(
|
||||
TextNoteEvent.kind,
|
||||
LongTextNoteEvent.kind,
|
||||
ReactionEvent.kind,
|
||||
RepostEvent.kind,
|
||||
GenericRepostEvent.kind,
|
||||
ReportEvent.kind,
|
||||
LnZapEvent.kind,
|
||||
LnZapRequestEvent.kind,
|
||||
PollNoteEvent.kind,
|
||||
HighlightEvent.kind,
|
||||
AudioTrackEvent.kind,
|
||||
PinListEvent.kind
|
||||
PollNoteEvent.kind
|
||||
),
|
||||
tags = mapOf("e" to listOf(it.idHex)),
|
||||
since = it.lastReactionsDownloadTime,
|
||||
@@ -154,7 +154,7 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
|
||||
val reactions = createRepliesAndReactionsFilter()
|
||||
val missing = createLoadEventsIfNotLoadedFilter()
|
||||
val addresses = createAddressFilter()
|
||||
val addressReactions = createTagToAddressFilter()
|
||||
val addressReactions = createReactionsToWatchInAddressFilter()
|
||||
|
||||
singleEventChannel.typedFilters = listOfNotNull(missing, addresses, reactions, addressReactions).flatten().ifEmpty { null }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.vitorpamplona.amethyst.service.model
|
||||
|
||||
import com.vitorpamplona.amethyst.model.toHexKey
|
||||
|
||||
class EventFactory {
|
||||
companion object {
|
||||
fun create(
|
||||
@@ -11,6 +13,34 @@ class EventFactory {
|
||||
content: String,
|
||||
sig: String,
|
||||
lenient: Boolean
|
||||
): Event {
|
||||
val internedTags = tags.map {
|
||||
it.map {
|
||||
it.intern()
|
||||
}
|
||||
}
|
||||
|
||||
return internedCreate(
|
||||
id = id.intern(),
|
||||
pubKey = pubKey.intern(),
|
||||
createdAt = createdAt,
|
||||
kind = kind,
|
||||
tags = internedTags,
|
||||
content = content,
|
||||
sig = sig,
|
||||
lenient = lenient
|
||||
)
|
||||
}
|
||||
|
||||
fun internedCreate(
|
||||
id: String,
|
||||
pubKey: String,
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: List<List<String>>,
|
||||
content: String,
|
||||
sig: String,
|
||||
lenient: Boolean
|
||||
) = when (kind) {
|
||||
AppDefinitionEvent.kind -> AppDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
AppRecommendationEvent.kind -> AppRecommendationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
@@ -24,6 +54,14 @@ class EventFactory {
|
||||
ChannelMessageEvent.kind -> ChannelMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ChannelMetadataEvent.kind -> ChannelMetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ChannelMuteUserEvent.kind -> ChannelMuteUserEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
ChatMessageEvent.kind -> {
|
||||
if (id.isBlank()) {
|
||||
val id = Event.generateId(pubKey, createdAt, kind, tags, content).toHexKey()
|
||||
ChatMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
} else {
|
||||
ChatMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
}
|
||||
}
|
||||
ClassifiedsEvent.kind -> ClassifiedsEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
CommunityDefinitionEvent.kind -> CommunityDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
CommunityPostApprovalEvent.kind -> CommunityPostApprovalEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
|
||||
@@ -19,13 +19,14 @@ class GiftWrapEvent(
|
||||
content: String,
|
||||
sig: HexKey
|
||||
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||
private var innerEvent: Event? = null
|
||||
private var cachedInnerEvent: Map<HexKey, Event?> = mapOf()
|
||||
|
||||
fun cachedInnerEvent(privKey: ByteArray): Event? {
|
||||
if (innerEvent != null) return innerEvent
|
||||
fun cachedGossip(privKey: ByteArray): Event? {
|
||||
val hex = privKey.toHexKey()
|
||||
if (cachedInnerEvent.contains(hex)) return cachedInnerEvent[hex]
|
||||
|
||||
val myInnerEvent = unwrap(privKey = privKey)
|
||||
innerEvent = myInnerEvent
|
||||
cachedInnerEvent = cachedInnerEvent + Pair(hex, myInnerEvent)
|
||||
return myInnerEvent
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,14 @@ package com.vitorpamplona.amethyst.service.model
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.vitorpamplona.amethyst.model.HexKey
|
||||
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||
import com.vitorpamplona.amethyst.model.hexToByteArray
|
||||
import com.vitorpamplona.amethyst.model.toHexKey
|
||||
import com.vitorpamplona.amethyst.service.CryptoUtils
|
||||
import com.vitorpamplona.amethyst.service.EncryptedInfo
|
||||
import com.vitorpamplona.amethyst.service.relays.Client
|
||||
|
||||
@Immutable
|
||||
class SealedGossipEvent(
|
||||
@@ -18,14 +20,16 @@ class SealedGossipEvent(
|
||||
content: String,
|
||||
sig: HexKey
|
||||
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||
private var innerEvent: Gossip? = null
|
||||
private var cachedInnerEvent: Map<HexKey, Event?> = mapOf()
|
||||
|
||||
fun cachedGossip(privKey: ByteArray): Gossip? {
|
||||
if (innerEvent != null) return innerEvent
|
||||
fun cachedGossip(privKey: ByteArray): Event? {
|
||||
val hex = privKey.toHexKey()
|
||||
if (cachedInnerEvent.contains(hex)) return cachedInnerEvent[hex]
|
||||
|
||||
val myInnerEvent = unseal(privKey = privKey)
|
||||
innerEvent = myInnerEvent
|
||||
return myInnerEvent
|
||||
val gossip = unseal(privKey = privKey)
|
||||
val event = gossip?.mergeWith(this)
|
||||
cachedInnerEvent = cachedInnerEvent + Pair(hex, event)
|
||||
return event
|
||||
}
|
||||
|
||||
fun unseal(privKey: ByteArray): Gossip? = try {
|
||||
@@ -88,14 +92,28 @@ class SealedGossipEvent(
|
||||
}
|
||||
}
|
||||
|
||||
open class Gossip(
|
||||
val id: HexKey,
|
||||
val pubKey: HexKey,
|
||||
val createdAt: Long,
|
||||
val kind: Int,
|
||||
val tags: List<List<String>>,
|
||||
val content: String
|
||||
class Gossip(
|
||||
val id: HexKey?,
|
||||
@SerializedName("pubkey")
|
||||
val pubKey: HexKey?,
|
||||
@SerializedName("created_at")
|
||||
val createdAt: Long?,
|
||||
val kind: Int?,
|
||||
val tags: List<List<String>>?,
|
||||
val content: String?
|
||||
) {
|
||||
fun mergeWith(event: SealedGossipEvent): Event {
|
||||
val newPubKey = pubKey?.ifBlank { null } ?: event.pubKey
|
||||
val newCreatedAt = if (createdAt != null && createdAt > 1000) createdAt else event.createdAt
|
||||
val newKind = kind ?: 0
|
||||
val newTags = (tags ?: emptyList()).plus(event.tags)
|
||||
val newContent = content ?: ""
|
||||
val newID = id?.ifBlank { null } ?: Event.generateId(newPubKey, newCreatedAt, newKind, newTags, newContent).toHexKey()
|
||||
val sig = ""
|
||||
|
||||
return EventFactory.create(newID, newPubKey, newCreatedAt, newKind, newTags, newContent, sig, Client.lenient)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(event: Event): Gossip {
|
||||
return Gossip(event.id, event.pubKey, event.createdAt, event.kind, event.tags, event.content)
|
||||
|
||||
+12
-4
@@ -18,6 +18,8 @@ import com.vitorpamplona.amethyst.ui.MainActivity
|
||||
object NotificationUtils {
|
||||
private var dmChannel: NotificationChannel? = null
|
||||
private var zapChannel: NotificationChannel? = null
|
||||
private const val DM_GROUP_KEY = "com.vitorpamplona.amethyst.DM_NOTIFICATION"
|
||||
private const val ZAP_GROUP_KEY = "com.vitorpamplona.amethyst.ZAP_NOTIFICATION"
|
||||
|
||||
private fun getOrCreateDMChannel(applicationContext: Context): NotificationChannel {
|
||||
if (dmChannel != null) return dmChannel!!
|
||||
@@ -70,7 +72,7 @@ object NotificationUtils {
|
||||
val zapChannel = getOrCreateZapChannel(applicationContext)
|
||||
val channelId = applicationContext.getString(R.string.app_notification_zaps_channel_id)
|
||||
|
||||
sendNotification(id, messageBody, messageTitle, pictureUrl, uri, channelId, applicationContext)
|
||||
sendNotification(id, messageBody, messageTitle, pictureUrl, uri, channelId, ZAP_GROUP_KEY, applicationContext)
|
||||
}
|
||||
|
||||
fun NotificationManager.sendDMNotification(
|
||||
@@ -84,7 +86,7 @@ object NotificationUtils {
|
||||
val dmChannel = getOrCreateDMChannel(applicationContext)
|
||||
val channelId = applicationContext.getString(R.string.app_notification_dms_channel_id)
|
||||
|
||||
sendNotification(id, messageBody, messageTitle, pictureUrl, uri, channelId, applicationContext)
|
||||
sendNotification(id, messageBody, messageTitle, pictureUrl, uri, channelId, DM_GROUP_KEY, applicationContext)
|
||||
}
|
||||
|
||||
fun NotificationManager.sendNotification(
|
||||
@@ -94,6 +96,7 @@ object NotificationUtils {
|
||||
pictureUrl: String?,
|
||||
uri: String,
|
||||
channelId: String,
|
||||
notificationGroupKey: String,
|
||||
applicationContext: Context
|
||||
) {
|
||||
if (pictureUrl != null) {
|
||||
@@ -110,6 +113,7 @@ object NotificationUtils {
|
||||
picture = imageResult.drawable as? BitmapDrawable,
|
||||
uri = uri,
|
||||
channelId,
|
||||
notificationGroupKey,
|
||||
applicationContext = applicationContext
|
||||
)
|
||||
} else {
|
||||
@@ -120,6 +124,7 @@ object NotificationUtils {
|
||||
picture = null,
|
||||
uri = uri,
|
||||
channelId,
|
||||
notificationGroupKey,
|
||||
applicationContext = applicationContext
|
||||
)
|
||||
}
|
||||
@@ -132,6 +137,7 @@ object NotificationUtils {
|
||||
picture: BitmapDrawable?,
|
||||
uri: String,
|
||||
channelId: String,
|
||||
notificationGroupKey: String,
|
||||
applicationContext: Context
|
||||
) {
|
||||
val notId = id.hashCode()
|
||||
@@ -164,7 +170,8 @@ object NotificationUtils {
|
||||
.setContentTitle(messageTitle)
|
||||
.setContentText(applicationContext.getString(R.string.app_notification_private_message))
|
||||
.setLargeIcon(picture?.bitmap)
|
||||
.setGroup(messageTitle)
|
||||
// .setGroup(messageTitle)
|
||||
// .setGroup(notificationGroupKey) //-> Might need a Group summary as well before we activate this
|
||||
.setContentIntent(contentPendingIntent)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setAutoCancel(true)
|
||||
@@ -178,7 +185,8 @@ object NotificationUtils {
|
||||
.setContentTitle(messageTitle)
|
||||
.setContentText(messageBody)
|
||||
.setLargeIcon(picture?.bitmap)
|
||||
.setGroup(messageTitle)
|
||||
// .setGroup(messageTitle)
|
||||
// .setGroup(notificationGroupKey) //-> Might need a Group summary as well before we activate this
|
||||
.setContentIntent(contentPendingIntent)
|
||||
.setPublicVersion(builderPublic.build())
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
|
||||
@@ -323,7 +323,7 @@ fun loadRelayInfo(
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.an_error_ocurred_trying_to_get_relay_information, dirtyUrl),
|
||||
context.getString(R.string.an_error_occurred_trying_to_get_relay_information, dirtyUrl),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
@@ -334,7 +334,7 @@ fun loadRelayInfo(
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.an_error_ocurred_trying_to_get_relay_information, dirtyUrl),
|
||||
context.getString(R.string.an_error_occurred_trying_to_get_relay_information, dirtyUrl),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
@@ -348,7 +348,7 @@ fun loadRelayInfo(
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.an_error_ocurred_trying_to_get_relay_information, dirtyUrl),
|
||||
context.getString(R.string.an_error_occurred_trying_to_get_relay_information, dirtyUrl),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
@@ -361,7 +361,7 @@ fun loadRelayInfo(
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
context.getString(R.string.an_error_ocurred_trying_to_get_relay_information, dirtyUrl),
|
||||
context.getString(R.string.an_error_occurred_trying_to_get_relay_information, dirtyUrl),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ fun RelaySelectionDialog(
|
||||
val selectedRelays = relays.filter { it.isSelected }
|
||||
if (selectedRelays.isEmpty()) {
|
||||
scope.launch {
|
||||
Toast.makeText(context, "Select a relay to continue", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, context.getString(R.string.select_a_relay_to_continue), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
return@PostButton
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ fun CashuPreview(token: CashuToken, accountViewModel: AccountViewModel) {
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
"No Lightning Address set",
|
||||
context.getString(R.string.no_lightning_address_set),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
@@ -180,7 +180,7 @@ fun CashuPreview(token: CashuToken, accountViewModel: AccountViewModel) {
|
||||
var orignaltoken = token.token
|
||||
clipboardManager.setText(AnnotatedString("$orignaltoken"))
|
||||
scope.launch {
|
||||
Toast.makeText(context, "Copied token to clipboard", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, context.getString(R.string.copied_token_to_clipboard), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -25,17 +25,17 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
|
||||
}
|
||||
|
||||
override fun feed(): List<Note> {
|
||||
val notes = innerApplyFilter(LocalCache.notes.values)
|
||||
val longFormNotes = innerApplyFilter(LocalCache.addressables.values)
|
||||
val notes = innerApplyFilter(LocalCache.notes.values, true)
|
||||
val longFormNotes = innerApplyFilter(LocalCache.addressables.values, false)
|
||||
|
||||
return sort(notes + longFormNotes)
|
||||
}
|
||||
|
||||
override fun applyFilter(collection: Set<Note>): Set<Note> {
|
||||
return innerApplyFilter(collection)
|
||||
return innerApplyFilter(collection, false)
|
||||
}
|
||||
|
||||
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
|
||||
private fun innerApplyFilter(collection: Collection<Note>, ignoreAddressables: Boolean): Set<Note> {
|
||||
val isGlobal = account.defaultHomeFollowList == GLOBAL_FOLLOWS
|
||||
val isHiddenList = showHiddenKey()
|
||||
|
||||
@@ -52,6 +52,7 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
|
||||
.filter { it ->
|
||||
val noteEvent = it.event
|
||||
(noteEvent is TextNoteEvent || noteEvent is ClassifiedsEvent || noteEvent is RepostEvent || noteEvent is GenericRepostEvent || noteEvent is LongTextNoteEvent || noteEvent is PollNoteEvent || noteEvent is HighlightEvent || noteEvent is AudioTrackEvent) &&
|
||||
(!ignoreAddressables || noteEvent.kind() < 10000) &&
|
||||
(isGlobal || it.author?.pubkeyHex in followingKeySet || noteEvent.isTaggedHashes(followingTagSet) || noteEvent.isTaggedGeoHashes(followingGeoSet) || noteEvent.isTaggedAddressableNotes(followingCommunities)) &&
|
||||
// && account.isAcceptable(it) // This filter follows only. No need to check if acceptable
|
||||
(isHiddenList || it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true) &&
|
||||
|
||||
@@ -511,7 +511,13 @@ fun debugState(context: Context) {
|
||||
Log.d("STATE DUMP", "Image Memory Cache ${(imageLoader.memoryCache?.size ?: 0) / (1024 * 1024)}/${(imageLoader.memoryCache?.maxSize ?: 0) / (1024 * 1024)} MB")
|
||||
|
||||
Log.d("STATE DUMP", "Notes: " + LocalCache.notes.filter { it.value.event != null }.size + "/" + LocalCache.notes.size)
|
||||
Log.d("STATE DUMP", "Addressables: " + LocalCache.addressables.filter { it.value.event != null }.size + "/" + LocalCache.addressables.size)
|
||||
Log.d("STATE DUMP", "Users: " + LocalCache.users.filter { it.value.info?.latestMetadata != null }.size + "/" + LocalCache.users.size)
|
||||
|
||||
Log.d("STATE DUMP", "Notes: " + LocalCache.notes.filter { it.value.event != null }.size + "/" + LocalCache.notes.size)
|
||||
LocalCache.notes.values.groupBy { it.event?.kind() }.forEach {
|
||||
Log.d("STATE DUMP", "Kind ${it.key}: \t${it.value.size} elements ")
|
||||
}
|
||||
LocalCache.addressables.values.groupBy { it.event?.kind() }.forEach {
|
||||
Log.d("STATE DUMP", "Kind ${it.key}: \t${it.value.size} elements ")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1015,7 +1015,7 @@ fun DisplayLNAddress(
|
||||
scope.launch {
|
||||
Toast.makeText(
|
||||
context,
|
||||
"Payment Successful", // Turn this into a UI animation
|
||||
context.getString(R.string.payment_successful), // Turn this into a UI animation
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
@@ -1025,7 +1025,7 @@ fun DisplayLNAddress(
|
||||
context,
|
||||
response.error?.message
|
||||
?: response.error?.code?.toString()
|
||||
?: "Error parsing error message",
|
||||
?: context.getString(R.string.error_parsing_error_message),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@
|
||||
<string name="and">" a "</string>
|
||||
<string name="in_channel">"v kanálu "</string>
|
||||
<string name="profile_banner">Banner profilu</string>
|
||||
<string name="payment_successful">Platba úspěšná</string>
|
||||
<string name="error_parsing_error_message">Chyba při zpracování chybové zprávy</string>
|
||||
<string name="following">" Sleduje"</string>
|
||||
<string name="followers">" Sledující"</string>
|
||||
<string name="profile">Profil</string>
|
||||
@@ -412,7 +414,7 @@
|
||||
<string name="sats_to_complete">Zapraiser na %1$s. Do cíle zbývá %2$s sats</string>
|
||||
<string name="read_from_relay">Číst z Relay</string>
|
||||
<string name="write_to_relay">Zapisovat do Relay</string>
|
||||
<string name="an_error_ocurred_trying_to_get_relay_information">Při pokusu o získání informací z Relay se vyskytla chyba z %1$s</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">Při pokusu o získání informací z Relay se vyskytla chyba z %1$s</string>
|
||||
<string name="owner">Vlastník</string>
|
||||
<string name="version">Verze</string>
|
||||
<string name="software">Software</string>
|
||||
@@ -437,6 +439,8 @@
|
||||
<string name="payment">Platba</string>
|
||||
<string name="cashu">Cashu Token</string>
|
||||
<string name="cashu_redeem">Vyměnit</string>
|
||||
<string name="no_lightning_address_set">Není nastavena žádná Lightning adresa</string>
|
||||
<string name="copied_token_to_clipboard">Token zkopírován do schránky</string>
|
||||
|
||||
<string name="live_stream_live_tag">ŽIVĚ</string>
|
||||
<string name="live_stream_offline_tag">OFFLINE</string>
|
||||
@@ -463,6 +467,8 @@
|
||||
<string name="settings">Nastavení</string>
|
||||
<string name="connectivity_type_always">Vždy</string>
|
||||
<string name="connectivity_type_wifi_only">Pouze Wi-Fi</string>
|
||||
<string name="connectivity_type_never">Nikdy</string>
|
||||
|
||||
<string name="system">Systém</string>
|
||||
<string name="light">Světlý</string>
|
||||
<string name="dark">Tmavý</string>
|
||||
@@ -483,4 +489,15 @@
|
||||
<string name="nip05_verified">Adresa Nostr byla ověřena</string>
|
||||
<string name="nip05_failed">Ověření adresy Nostr se nezdařilo</string>
|
||||
<string name="nip05_checking">Kontrola adresy Nostr</string>
|
||||
<string name="select_deselect_all">Vybrat/Zrušit vše</string>
|
||||
<string name="default_relays">Výchozí</string>
|
||||
<string name="select_a_relay_to_continue">Vyberte relé pro pokračování</string>
|
||||
|
||||
<string name="zap_forward_title">Přeposílat Zapy na:</string>
|
||||
<string name="zap_forward_explainer">Podporující klienti budou přeposílat Zapy na níže uvedenou LN adresu nebo uživatelský profil místo vaší</string>
|
||||
|
||||
<string name="geohash_title">Zveřejnit polohu jako </string>
|
||||
<string name="geohash_explainer">Přidá Geohash vaší polohy do příspěvku. Veřejnost bude vědět, že se nacházíte do 5 km od aktuální polohy</string>
|
||||
|
||||
<string name="add_sensitive_content_explainer">Přidat varování o citlivém obsahu před zobrazením vašeho obsahu. Toto je ideální pro obsah NSFW (nebezpečné pro práci) nebo obsah, který někteří lidé mohou považovat za urážlivý nebo znepokojující</string>
|
||||
</resources>
|
||||
|
||||
@@ -47,6 +47,8 @@
|
||||
<string name="and">" und "</string>
|
||||
<string name="in_channel">"im Kanal "</string>
|
||||
<string name="profile_banner">Profilbanner</string>
|
||||
<string name="payment_successful">Zahlung erfolgreich</string>
|
||||
<string name="error_parsing_error_message">Fehler beim Parsen der Fehlermeldung</string>
|
||||
<string name="following">" Folgen"</string>
|
||||
<string name="followers">" Anhänger"</string>
|
||||
<string name="profile">Profil</string>
|
||||
@@ -421,7 +423,7 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="sats_to_complete">Zapraiser bei %1$s. %2$s Sats bis zum Ziel</string>
|
||||
<string name="read_from_relay">Von Relay lesen</string>
|
||||
<string name="write_to_relay">In Relay schreiben</string>
|
||||
<string name="an_error_ocurred_trying_to_get_relay_information">Ein Fehler ist beim Abrufen von Relay-Informationen von %1$s aufgetreten</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">Ein Fehler ist beim Abrufen von Relay-Informationen von %1$s aufgetreten</string>
|
||||
<string name="owner">Inhaber</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="software">Software</string>
|
||||
@@ -446,6 +448,8 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="payment">Zahlung</string>
|
||||
<string name="cashu">Cashu-Token</string>
|
||||
<string name="cashu_redeem">Einlösen</string>
|
||||
<string name="no_lightning_address_set">Keine Lightning-Adresse festgelegt</string>
|
||||
<string name="copied_token_to_clipboard">Token in die Zwischenablage kopiert</string>
|
||||
|
||||
<string name="live_stream_live_tag">LIVE</string>
|
||||
<string name="live_stream_offline_tag">OFFLINE</string>
|
||||
@@ -472,6 +476,8 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="settings">Einstellungen</string>
|
||||
<string name="connectivity_type_always">Immer</string>
|
||||
<string name="connectivity_type_wifi_only">Nur WLAN</string>
|
||||
<string name="connectivity_type_never">Nie</string>
|
||||
|
||||
<string name="system">System</string>
|
||||
<string name="light">Hell</string>
|
||||
<string name="dark">Dunkel</string>
|
||||
@@ -492,4 +498,15 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="nip05_verified">Nostr-Adresse verifiziert</string>
|
||||
<string name="nip05_failed">Nostr-Adresse konnte nicht verifiziert werden</string>
|
||||
<string name="nip05_checking">Nostr-Adresse wird überprüft</string>
|
||||
<string name="select_deselect_all">Alle auswählen/abwählen</string>
|
||||
<string name="default_relays">Standard</string>
|
||||
<string name="select_a_relay_to_continue">Wählen Sie ein Relais aus, um fortzufahren</string>
|
||||
|
||||
<string name="zap_forward_title">Weiterleiten von Zaps an:</string>
|
||||
<string name="zap_forward_explainer">Unterstützende Clients leiten Zaps an die LNAddress oder das Benutzerprofil unten weiter, anstatt an Ihre eigene Adresse</string>
|
||||
|
||||
<string name="geohash_title">Ort preisgeben als </string>
|
||||
<string name="geohash_explainer">Fügt dem Beitrag einen Geohash Ihres Standorts hinzu. Die Öffentlichkeit wird wissen, dass Sie sich innerhalb von 5 km (3 mi) vom aktuellen Standort befinden</string>
|
||||
|
||||
<string name="add_sensitive_content_explainer">Fügt eine Warnung für sensiblen Inhalt hinzu, bevor Ihr Inhalt angezeigt wird. Dies ist ideal für NSFW-Inhalte (nicht sicher für die Arbeit) oder Inhalte, die manche Menschen als anstößig oder verstörend empfinden könnten</string>
|
||||
</resources>
|
||||
@@ -406,7 +406,6 @@
|
||||
<string name="sats_to_complete">A ZapGyűjtés %1$s-nál. %2$s sats kell a célig</string>
|
||||
<string name="read_from_relay">Olvassás a csomópontból</string>
|
||||
<string name="write_to_relay">Írás a csomópontra</string>
|
||||
<string name="an_error_ocurred_trying_to_get_relay_information">Hiba történt a csomópont információ szerzés közben a %1$s -tól</string>
|
||||
<string name="owner">Tulajdonos</string>
|
||||
<string name="version">Verzió</string>
|
||||
<string name="software">Szoftver</string>
|
||||
|
||||
@@ -411,7 +411,6 @@
|
||||
<string name="sats_to_complete">Zapraiser at %1$s. %2$s sats to goal</string>
|
||||
<string name="read_from_relay">リレーから読み込み</string>
|
||||
<string name="write_to_relay">リレーに書き込み</string>
|
||||
<string name="an_error_ocurred_trying_to_get_relay_information">%1$sからリレー情報を取得する際にエラーが発生しました</string>
|
||||
<string name="owner">管理者</string>
|
||||
<string name="version">バージョン</string>
|
||||
<string name="software">ソフトウェア</string>
|
||||
|
||||
@@ -414,7 +414,6 @@
|
||||
<string name="sats_to_complete">Zapraiser op %1$s. %2$s sats tot doel</string>
|
||||
<string name="read_from_relay">Lees van relay</string>
|
||||
<string name="write_to_relay">Schrijf naar relay</string>
|
||||
<string name="an_error_ocurred_trying_to_get_relay_information">Er is een fout opgetreden bij het ophalen van de relay-informatie van %1$s</string>
|
||||
<string name="owner">Eigenaar</string>
|
||||
<string name="version">Versie</string>
|
||||
<string name="software">Software</string>
|
||||
|
||||
@@ -372,7 +372,6 @@
|
||||
<string name="sats_to_complete">Arrecadação de Zaps em %1$s. %2$s sats para meta</string>
|
||||
<string name="read_from_relay">Ler do Relay</string>
|
||||
<string name="write_to_relay">Enviar para o Relay</string>
|
||||
<string name="an_error_ocurred_trying_to_get_relay_information">Ocorreu um erro ao tentar obter informações do relay de %1$s</string>
|
||||
<string name="owner">Proprietário</string>
|
||||
<string name="version">Versão</string>
|
||||
<string name="software">Programa</string>
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
<string name="and">" och "</string>
|
||||
<string name="in_channel">"i kanalen "</string>
|
||||
<string name="profile_banner">Profil Banner</string>
|
||||
<string name="payment_successful">Betalning lyckades</string>
|
||||
<string name="error_parsing_error_message">Fel vid tolkning av felmeddelande</string>
|
||||
<string name="following">" Följer"</string>
|
||||
<string name="followers">" Följare"</string>
|
||||
<string name="profile">Profil</string>
|
||||
@@ -107,7 +109,7 @@
|
||||
<string name="new_requests">Nya förfrågningar</string>
|
||||
<string name="blocked_users">Blockerade användare</string>
|
||||
<string name="new_threads">Nya trådar</string>
|
||||
<string name="conversations">konversationer</string>
|
||||
<string name="conversations">Konversationer</string>
|
||||
<string name="notes">Anteckningar</string>
|
||||
<string name="replies">Svar</string>
|
||||
<string name="follows">"Följer"</string>
|
||||
@@ -313,7 +315,7 @@
|
||||
|
||||
|
||||
<string name="file_server">Fil Server</string>
|
||||
<string name="zap_forward_lnAddress">LnAdress or @Användare</string>
|
||||
<string name="zap_forward_lnAddress">LnAdress eller @Användare</string>
|
||||
|
||||
<string name="upload_server_imgur">imgur.com - betrodd</string>
|
||||
<string name="upload_server_imgur_explainer">Imgur kan ändra filen</string>
|
||||
@@ -386,7 +388,7 @@
|
||||
|
||||
<string name="channel_list_join_conversation">Gå med i konversation</string>
|
||||
<string name="channel_list_user_or_group_id">Användare eller grupp ID</string>
|
||||
<string name="channel_list_user_or_group_id_demo">npub, nevent or hex</string>
|
||||
<string name="channel_list_user_or_group_id_demo">npub, nevent eller hex</string>
|
||||
<string name="channel_list_create_channel">Skapa</string>
|
||||
<string name="channel_list_join_channel">Gå med</string>
|
||||
<string name="today">Idag</string>
|
||||
@@ -409,7 +411,7 @@
|
||||
<string name="sats_to_complete">Zapraiser på %1$s. %2$s sats kvar till målet</string>
|
||||
<string name="read_from_relay">Läs från Relay</string>
|
||||
<string name="write_to_relay">Skriv till Relay</string>
|
||||
<string name="an_error_ocurred_trying_to_get_relay_information">Ett fel inträffade vid försök att hämta information från Relay %1$s</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">Ett fel inträffade vid försök att hämta information från Relay %1$s</string>
|
||||
<string name="owner">Ägare</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="software">Programvara</string>
|
||||
@@ -434,6 +436,8 @@
|
||||
<string name="payment">Betalning</string>
|
||||
<string name="cashu">Cashu Token</string>
|
||||
<string name="cashu_redeem">Inlösen</string>
|
||||
<string name="no_lightning_address_set">Ingen Lightning-adress angiven</string>
|
||||
<string name="copied_token_to_clipboard">Kopierade token till urklipp</string>
|
||||
|
||||
<string name="live_stream_live_tag">LIVE</string>
|
||||
<string name="live_stream_offline_tag">OFFLINE</string>
|
||||
@@ -460,6 +464,8 @@
|
||||
<string name="settings">Inställningar</string>
|
||||
<string name="connectivity_type_always">Alltid</string>
|
||||
<string name="connectivity_type_wifi_only">Endast Wi-Fi</string>
|
||||
<string name="connectivity_type_never">Aldrig</string>
|
||||
|
||||
<string name="system">System</string>
|
||||
<string name="light">Ljus</string>
|
||||
<string name="dark">Mörk</string>
|
||||
@@ -480,6 +486,17 @@
|
||||
<string name="nip05_verified">Nostr-adress verifierad</string>
|
||||
<string name="nip05_failed">Verifikation av Nostr-adress misslyckades</string>
|
||||
<string name="nip05_checking">Kontrollerar Nostr-adress</string>
|
||||
<string name="select_deselect_all">Välj/Avmarkera alla</string>
|
||||
<string name="default_relays">Standard</string>
|
||||
<string name="select_a_relay_to_continue">Välj ett relä för att fortsätta</string>
|
||||
|
||||
<string name="zap_forward_title">Vidarebefordra Zaps till:</string>
|
||||
<string name="zap_forward_explainer">Stödjande klienter kommer att vidarebefordra Zaps till LNAddress eller användarprofilen nedan istället för din egen</string>
|
||||
|
||||
<string name="geohash_title">Exponera plats som </string>
|
||||
<string name="geohash_explainer">Lägger till en Geohash av din plats i inlägget. Allmänheten kommer att veta att du befinner dig inom 5 km från nuvarande plats</string>
|
||||
|
||||
<string name="add_sensitive_content_explainer">Lägger till en varning för känsligt innehåll innan ditt innehåll visas. Detta är idealiskt för NSFW-innehåll (inte säkert för arbete) eller innehåll som vissa personer kan uppleva som stötande eller störande</string>
|
||||
</resources>
|
||||
|
||||
|
||||
|
||||
@@ -404,7 +404,6 @@
|
||||
<string name="sats_to_complete">ஜாப்-திரட்டுதல் %1$s. இலக்கை அடைய %2$s</string>
|
||||
<string name="read_from_relay">படிப்பதற்கான ரிலே</string>
|
||||
<string name="write_to_relay">போஸ்ட் செய்வதற்கான ரிலே</string>
|
||||
<string name="an_error_ocurred_trying_to_get_relay_information">%1$s ரிலே விவரங்கள் பெறுகையில் பிழை</string>
|
||||
<string name="owner">உரிமையாளர்</string>
|
||||
<string name="version">பதிப்பு</string>
|
||||
<string name="software">மென்பொருள்</string>
|
||||
|
||||
@@ -48,6 +48,8 @@
|
||||
<string name="and">" and "</string>
|
||||
<string name="in_channel">"in channel "</string>
|
||||
<string name="profile_banner">Profile Banner</string>
|
||||
<string name="payment_successful">Payment Successful</string>
|
||||
<string name="error_parsing_error_message">Error parsing error message</string>
|
||||
<string name="following">" Following"</string>
|
||||
<string name="followers">" Followers"</string>
|
||||
<string name="profile">Profile</string>
|
||||
@@ -433,7 +435,7 @@
|
||||
<string name="sats_to_complete">Zapraiser at %1$s. %2$s sats to goal</string>
|
||||
<string name="read_from_relay">Read from Relay</string>
|
||||
<string name="write_to_relay">Write to Relay</string>
|
||||
<string name="an_error_ocurred_trying_to_get_relay_information">An error ocurred trying to get relay information from %1$s</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">An error occurred trying to get relay information from %1$s</string>
|
||||
<string name="owner">Owner</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="software">Software</string>
|
||||
@@ -458,6 +460,8 @@
|
||||
<string name="payment">Payment</string>
|
||||
<string name="cashu">Cashu Token</string>
|
||||
<string name="cashu_redeem">Redeem</string>
|
||||
<string name="no_lightning_address_set">No Lightning Address set</string>
|
||||
<string name="copied_token_to_clipboard">Copied token to clipboard</string>
|
||||
|
||||
<string name="live_stream_live_tag">LIVE</string>
|
||||
<string name="live_stream_offline_tag">OFFLINE</string>
|
||||
@@ -508,6 +512,7 @@
|
||||
<string name="nip05_checking">Checking Nostr address</string>
|
||||
<string name="select_deselect_all">Select/Deselect all</string>
|
||||
<string name="default_relays">Default</string>
|
||||
<string name="select_a_relay_to_continue">Select a relay to continue</string>
|
||||
|
||||
<string name="zap_forward_title">Forward Zaps to:</string>
|
||||
<string name="zap_forward_explainer">Supporting clients will forward zaps to the LNAddress or User Profile below instead of yours</string>
|
||||
|
||||
Reference in New Issue
Block a user