diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index 217e5c51f..fdf8d994a 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 609a10005..2daee8c53 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,7 +1,7 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
- id 'org.jlleitschuh.gradle.ktlint' version "11.3.1"
+ id 'org.jlleitschuh.gradle.ktlint' version "11.5.0"
id 'com.google.gms.google-services'
}
@@ -20,7 +20,7 @@ android {
vectorDrawables {
useSupportLibrary true
}
- resConfigs("ar", "cs", "de", "eo", "es", "fa", "fr", "hu", "night", "nl", "pt-rBR", "ru", "sv-rSE", "ta", "tr", "uk", "zh", "sh-rHK", "zh-rTW", "ja")
+ resourceConfigurations += ['ar', 'cs', 'de', 'eo', 'es', 'fa', 'fr', 'hu', 'night', 'nl', 'pt-rBR', 'ru', 'sv-rSE', 'ta', 'tr', 'uk', 'zh', 'sh-rHK', 'zh-rTW', 'ja']
}
buildTypes {
@@ -37,7 +37,7 @@ android {
}
}
- flavorDimensions "channel"
+ flavorDimensions = ["channel"]
productFlavors {
play {
@@ -73,7 +73,7 @@ android {
}
composeOptions {
- kotlinCompilerExtensionVersion "1.4.7"
+ kotlinCompilerExtensionVersion "1.5.1"
}
packagingOptions {
@@ -96,7 +96,7 @@ dependencies {
implementation "androidx.fragment:fragment-ktx:$fragment_version"
// Navigation
- implementation("androidx.navigation:navigation-compose:$nav_version")
+ implementation "androidx.navigation:navigation-compose:$nav_version"
// Observe Live data as State
implementation "androidx.compose.runtime:runtime-livedata:$compose_ui_version"
@@ -108,7 +108,7 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
- implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "net.engawapg.lib:zoomable:1.4.3"
@@ -153,7 +153,7 @@ dependencies {
implementation "io.github.url-detector:url-detector:0.1.23"
// For QR generation
- implementation 'com.google.zxing:core:3.5.1'
+ implementation 'com.google.zxing:core:3.5.2'
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
// Markdown
@@ -196,16 +196,13 @@ dependencies {
// LibSodium for XChaCha encryption
implementation "com.goterl:lazysodium-android:5.1.0@aar"
- implementation "net.java.dev.jna:jna:5.12.1@aar"
+ implementation 'net.java.dev.jna:jna:5.13.0@aar'
// Video compression lib
implementation 'com.github.AbedElazizShe:LightCompressor:1.3.1'
// Image compression lib
implementation 'id.zelory:compressor:3.0.1'
- // Automatic memory leak detection
- debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
-
testImplementation 'junit:junit:4.13.2'
testImplementation 'io.mockk:mockk:1.13.5'
androidTestImplementation 'androidx.test.ext:junit:1.2.0-alpha01'
diff --git a/app/src/androidTest/java/com/vitorpamplona/amethyst/GiftWrapEventTest.kt b/app/src/androidTest/java/com/vitorpamplona/amethyst/GiftWrapEventTest.kt
index fbda04af0..e349e0339 100644
--- a/app/src/androidTest/java/com/vitorpamplona/amethyst/GiftWrapEventTest.kt
+++ b/app/src/androidTest/java/com/vitorpamplona/amethyst/GiftWrapEventTest.kt
@@ -499,13 +499,46 @@ class GiftWrapEventTest {
val gossip = unwrapUnsealGossip(json, privateKey)
assertEquals("asdfasdfasdf", gossip?.content)
- assertEquals(0L, gossip?.createdAt)
+ assertEquals(1690659269L, gossip?.createdAt)
assertEquals("827ba09d32ab81d62c60f657b350198c8aaba84372dab9ad3f4f6b8b7274b707", gossip?.id)
assertEquals(14, gossip?.kind)
assertEquals("subject", gossip?.tags?.firstOrNull()?.get(0))
assertEquals("test", gossip?.tags?.firstOrNull()?.get(1))
}
+ @Test
+ fun testFromCoracle3() {
+ val json = """
+{
+ "content": "{\"ciphertext\":\"PGCodiacmCB/sClw0C6DQRpP/XIfNAKCVZdKLQpOqytgbFryhs9Z+cPldq2FajXzxs3jQsF7/EVQyWpuV8pXetvFT9tvzjg4Xcm7ZcooLUnAeAo2xZNcJontN4cGubuDqKuXy5n59yXP1fIxfnJxRTRRdCZ2edhsKeNR5NSByUi+StjV10rnfHt8AhZCpiXiZ/giTOsC4wdaeONPgMzMeljaJWLvl6n11VjmXhkx1mXIQt43CNB1hIqO3p89Mbd9p+nlLrOsR+Xs0TB4DCh4XTPbvgf7B7Z+PgOfl3GZfJy9x6TciLcF4E3Ba1zrPe4f79czCIEiJ1yrIKrzzYvv+it35DZQ8fgveFXpyHnNL29hml8PNjyOsFbCHVYLMGw88evI5PijOcpe1TtdoioX8kX5kVEQSKJXuoSjTorvbRPCgGzaa1m0J0uTpzri5VD22a/Jh2CcAnubg6w4JDdUWCogdSV3NqiJllo7ZF7WnZ3apPdRD23MEfphVBJrcLBUNlmwajnY5IvVTKTkZOP50r9dBapvMWXIo6M6zhy/5vVWJz57863pelYCRG4upaXZuNK9sMBtbiphxmFR83i8RML8KN8Q391Cd/xBN7TxJNo5p2YU25VeGZUAmHY8DYlMQDm8Br0nStAXp3T+DzTRL8FTECa8DJV+KTAPoCxqhv3B28Ehr0XAP75CsHoLU00G48cR7h3vQ0CnfKh6KXU6nnDA5OWfpMYpirACCpsnpSD0OaCQ3gkQp3zZNMS3HcOpnPK/IY7R0esbzgAkvNhkyxaIfPDdf+eRUSOA9+2Ji28MwjjY8Dw3SLdUqCOzIDjQeR/T5oNmaQJm3lZ8G0FxxC6ejD4VJX/NI/x+STeB9jWHWmHZvqKzV6JHNh6qmZb6TKSIPOHpafWFoeJFOmiiigf46sju9vRXmVEAx59HXWnvnvCBNJg877yCMulB6xyQuSdVDuotQU4tQZwCKedTHJ6GqjesM98UlJrDtdWQURwwW1qc7N8tS6PukmUVEf0jmbIWVIBmUlkcVuiSs1g1h1kjt8c4MnGTz3CSgpOd1MqxLrl9WwrTqM+YnE+yeZYUjFoewyKZIQ==\",\"nonce\":\"OdCZczJiUGR4bOGIElQ4UUH4dQmG5U/3\",\"v\":1}",
+ "kind": 1059,
+ "created_at": 1690772945,
+ "pubkey": "e01475e87896800b7285eb0daf263c59f811c8fc5bc8daa105d2c98b6d7c4952",
+ "tags": [
+ [
+ "p",
+ "b08d8857a92b4d6aa580ff55cc3c18c4edf313c83388c34abc118621f74f1a78"
+ ]
+ ],
+ "id": "d9fc85ece892ce45ffa737b3ddc0f8b752623181d75363b966191f8c03d2debe",
+ "sig": "1b20416b83f4b5b8eead11e29c185f46b5e76d1960e4505210ddd00f7a6973cc11268f52a8989e3799b774d5f3a55db95bed4d66a1b6e88ab54becec5c771c17"
+}
+ """.trimIndent()
+
+ val privateKey = "7dd22cafc512c0bc363a259f6dcda515b13ae3351066d7976fd0bb79cbd0d700"
+
+ val gossip = unwrapUnsealGossip(json, privateKey)
+
+ assertEquals("8d1a56008d4e31dae2fb8bef36b3efea519eff75f57033107e2aa16702466ef2", gossip?.id)
+ assertEquals("Howdy", gossip?.content)
+ assertEquals(1690833960L, gossip?.createdAt)
+ assertEquals(14, gossip?.kind)
+ assertEquals("p", gossip?.tags?.firstOrNull()?.get(0))
+ assertEquals("b08d8857a92b4d6aa580ff55cc3c18c4edf313c83388c34abc118621f74f1a78", gossip?.tags?.firstOrNull()?.get(1))
+ assertEquals("subject", gossip?.tags?.getOrNull(1)?.get(0))
+ assertEquals("Stuff", gossip?.tags?.getOrNull(1)?.get(1))
+ }
+
fun unwrapUnsealGossip(json: String, privateKey: HexKey): Gossip? {
val pkBytes = privateKey.hexToByteArray()
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt b/app/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt
index c3ff33225..44eecf7fa 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt
@@ -117,7 +117,7 @@ object ServiceManager {
LocalCache.pruneOldAndHiddenMessages(it)
LocalCache.pruneHiddenMessages(it)
LocalCache.pruneContactLists(it)
- // LocalCache.pruneNonFollows(it)
+ LocalCache.pruneRepliesAndReactions(it)
}
}
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt
index 7e4d04904..70cbd2aa5 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt
@@ -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()
+
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()
+
+ 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()
+
+ 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()
+
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) {
+ 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 -> {
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt
index 5448367b5..d09483d37 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt
@@ -158,6 +158,35 @@ open class Note(val idHex: String) {
boosts = boosts - note
liveSet?.boosts?.invalidateData()
}
+
+ fun removeAllChildNotes(): Set {
+ val toBeRemoved = replies +
+ reactions.values.flatten() +
+ boosts +
+ reports.values.flatten() +
+ zaps.keys +
+ zaps.values.filterNotNull() +
+ zapPayments.keys +
+ zapPayments.values.filterNotNull()
+
+ replies = setOf()
+ reactions = mapOf>()
+ boosts = setOf()
+ reports = mapOf>()
+ zaps = mapOf()
+ zapPayments = mapOf()
+ relays = setOf()
+ 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 {
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/TimeUtils.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/TimeUtils.kt
index 348a46837..18a763647 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/model/TimeUtils.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/model/TimeUtils.kt
@@ -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
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt
index e8e4dc59a..cec48cd70 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt
@@ -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()
@@ -383,6 +393,22 @@ class Chatroom() {
roomMessages = roomMessages + msg
}
}
+
+ fun pruneMessagesToTheLatestOnly(): Set {
+ 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
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/LocationUtil.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/LocationUtil.kt
index cfda4b271..7645a12f4 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/LocationUtil.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/LocationUtil.kt
@@ -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
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatroomDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatroomDataSource.kt
index 48b60c33d..94879c6d1 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatroomDataSource.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatroomDataSource.kt
@@ -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))
)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatroomListDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatroomListDataSource.kt
index 5e0f92f44..309e63044 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatroomListDataSource.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatroomListDataSource.kt
@@ -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
)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrGeohashDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrGeohashDataSource.kt
index 4341e0462..3e04dee9e 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrGeohashDataSource.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrGeohashDataSource.kt
@@ -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
)
)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHashtagDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHashtagDataSource.kt
index 9b278e09c..94784f29a 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHashtagDataSource.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHashtagDataSource.kt
@@ -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
)
)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSearchEventOrUserDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSearchEventOrUserDataSource.kt
index 76934bd34..51ae523f1 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSearchEventOrUserDataSource.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSearchEventOrUserDataSource.kt
@@ -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? {
@@ -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
)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt
index c09d38206..c5274166c 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt
@@ -12,7 +12,7 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
private var eventsToWatch = setOf()
private var addressesToWatch = setOf()
- private fun createTagToAddressFilter(): List? {
+ private fun createReactionsToWatchInAddressFilter(): List? {
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 }
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventFactory.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventFactory.kt
index 42fa69c69..5ea09edd7 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventFactory.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/EventFactory.kt
@@ -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>,
+ 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)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/GiftWrapEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/GiftWrapEvent.kt
index 1365dd3c2..e33cec26a 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/GiftWrapEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/GiftWrapEvent.kt
@@ -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 = 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
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/SealedGossipEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/SealedGossipEvent.kt
index 4e12cfdd5..0e51426c7 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/SealedGossipEvent.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/SealedGossipEvent.kt
@@ -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 = 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>,
- 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>?,
+ 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)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationUtils.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationUtils.kt
index 5e772035b..7c7051b5c 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationUtils.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationUtils.kt
@@ -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)
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt
index eb26e9f49..0fbd4db7f 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt
@@ -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()
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt
index 79f546610..46fe688d0 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt
@@ -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
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt
index 34661474a..9405b7903 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt
@@ -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()
}
}
},
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt
index ab0cf1881..90f2b2f02 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt
@@ -25,17 +25,17 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter()
}
override fun feed(): List {
- 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): Set {
- return innerApplyFilter(collection)
+ return innerApplyFilter(collection, false)
}
- private fun innerApplyFilter(collection: Collection): Set {
+ private fun innerApplyFilter(collection: Collection, ignoreAddressables: Boolean): Set {
val isGlobal = account.defaultHomeFollowList == GLOBAL_FOLLOWS
val isHiddenList = showHiddenKey()
@@ -52,6 +52,7 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter()
.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) &&
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt
index b8e0d82b0..9389d7164 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt
@@ -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 ")
+ }
}
diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt
index aac3c8cb7..bf34dba7f 100644
--- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt
+++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt
@@ -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()
}
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 3799d2b5b..a5aa1b489 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -47,6 +47,8 @@
" a "
"v kanálu "
Banner profilu
+ Platba úspěšná
+ Chyba při zpracování chybové zprávy
" Sleduje"
" Sledující"
Profil
@@ -412,7 +414,7 @@
Zapraiser na %1$s. Do cíle zbývá %2$s sats
Číst z Relay
Zapisovat do Relay
- Při pokusu o získání informací z Relay se vyskytla chyba z %1$s
+ Při pokusu o získání informací z Relay se vyskytla chyba z %1$s
Vlastník
Verze
Software
@@ -437,6 +439,8 @@
Platba
Cashu Token
Vyměnit
+ Není nastavena žádná Lightning adresa
+ Token zkopírován do schránky
ŽIVĚ
OFFLINE
@@ -463,6 +467,8 @@
Nastavení
Vždy
Pouze Wi-Fi
+ Nikdy
+
Systém
Světlý
Tmavý
@@ -483,4 +489,15 @@
Adresa Nostr byla ověřena
Ověření adresy Nostr se nezdařilo
Kontrola adresy Nostr
+ Vybrat/Zrušit vše
+ Výchozí
+ Vyberte relé pro pokračování
+
+ Přeposílat Zapy na:
+ Podporující klienti budou přeposílat Zapy na níže uvedenou LN adresu nebo uživatelský profil místo vaší
+
+ Zveřejnit polohu jako
+ 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
+
+ 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í
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 0b3d95b61..e8462631e 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -47,6 +47,8 @@
" und "
"im Kanal "
Profilbanner
+ Zahlung erfolgreich
+ Fehler beim Parsen der Fehlermeldung
" Folgen"
" Anhänger"
Profil
@@ -421,7 +423,7 @@ anz der Bedingungen ist erforderlich
Zapraiser bei %1$s. %2$s Sats bis zum Ziel
Von Relay lesen
In Relay schreiben
- Ein Fehler ist beim Abrufen von Relay-Informationen von %1$s aufgetreten
+ Ein Fehler ist beim Abrufen von Relay-Informationen von %1$s aufgetreten
Inhaber
Version
Software
@@ -446,6 +448,8 @@ anz der Bedingungen ist erforderlich
Zahlung
Cashu-Token
Einlösen
+ Keine Lightning-Adresse festgelegt
+ Token in die Zwischenablage kopiert
LIVE
OFFLINE
@@ -472,6 +476,8 @@ anz der Bedingungen ist erforderlich
Einstellungen
Immer
Nur WLAN
+ Nie
+
System
Hell
Dunkel
@@ -492,4 +498,15 @@ anz der Bedingungen ist erforderlich
Nostr-Adresse verifiziert
Nostr-Adresse konnte nicht verifiziert werden
Nostr-Adresse wird überprüft
+ Alle auswählen/abwählen
+ Standard
+ Wählen Sie ein Relais aus, um fortzufahren
+
+ Weiterleiten von Zaps an:
+ Unterstützende Clients leiten Zaps an die LNAddress oder das Benutzerprofil unten weiter, anstatt an Ihre eigene Adresse
+
+ Ort preisgeben als
+ 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
+
+ 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
\ No newline at end of file
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index 91c3e9368..9098a2aee 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -406,7 +406,6 @@
A ZapGyűjtés %1$s-nál. %2$s sats kell a célig
Olvassás a csomópontból
Írás a csomópontra
- Hiba történt a csomópont információ szerzés közben a %1$s -tól
Tulajdonos
Verzió
Szoftver
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 31bdc929b..aa1f3697a 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -411,7 +411,6 @@
Zapraiser at %1$s. %2$s sats to goal
リレーから読み込み
リレーに書き込み
- %1$sからリレー情報を取得する際にエラーが発生しました
管理者
バージョン
ソフトウェア
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 3e501aa9a..55b995983 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -414,7 +414,6 @@
Zapraiser op %1$s. %2$s sats tot doel
Lees van relay
Schrijf naar relay
- Er is een fout opgetreden bij het ophalen van de relay-informatie van %1$s
Eigenaar
Versie
Software
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 78b311507..4c41da273 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -372,7 +372,6 @@
Arrecadação de Zaps em %1$s. %2$s sats para meta
Ler do Relay
Enviar para o Relay
- Ocorreu um erro ao tentar obter informações do relay de %1$s
Proprietário
Versão
Programa
diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml
index 1907a0559..16820baec 100644
--- a/app/src/main/res/values-sv-rSE/strings.xml
+++ b/app/src/main/res/values-sv-rSE/strings.xml
@@ -46,6 +46,8 @@
" och "
"i kanalen "
Profil Banner
+ Betalning lyckades
+ Fel vid tolkning av felmeddelande
" Följer"
" Följare"
Profil
@@ -107,7 +109,7 @@
Nya förfrågningar
Blockerade användare
Nya trådar
- konversationer
+ Konversationer
Anteckningar
Svar
"Följer"
@@ -313,7 +315,7 @@
Fil Server
- LnAdress or @Användare
+ LnAdress eller @Användare
imgur.com - betrodd
Imgur kan ändra filen
@@ -386,7 +388,7 @@
Gå med i konversation
Användare eller grupp ID
- npub, nevent or hex
+ npub, nevent eller hex
Skapa
Gå med
Idag
@@ -409,7 +411,7 @@
Zapraiser på %1$s. %2$s sats kvar till målet
Läs från Relay
Skriv till Relay
- Ett fel inträffade vid försök att hämta information från Relay %1$s
+ Ett fel inträffade vid försök att hämta information från Relay %1$s
Ägare
Version
Programvara
@@ -434,6 +436,8 @@
Betalning
Cashu Token
Inlösen
+ Ingen Lightning-adress angiven
+ Kopierade token till urklipp
LIVE
OFFLINE
@@ -460,6 +464,8 @@
Inställningar
Alltid
Endast Wi-Fi
+ Aldrig
+
System
Ljus
Mörk
@@ -480,6 +486,17 @@
Nostr-adress verifierad
Verifikation av Nostr-adress misslyckades
Kontrollerar Nostr-adress
+ Välj/Avmarkera alla
+ Standard
+ Välj ett relä för att fortsätta
+
+ Vidarebefordra Zaps till:
+ Stödjande klienter kommer att vidarebefordra Zaps till LNAddress eller användarprofilen nedan istället för din egen
+
+ Exponera plats som
+ 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
+
+ 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
diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml
index 5dfd7b932..543f9ebf4 100644
--- a/app/src/main/res/values-ta/strings.xml
+++ b/app/src/main/res/values-ta/strings.xml
@@ -404,7 +404,6 @@
ஜாப்-திரட்டுதல் %1$s. இலக்கை அடைய %2$s
படிப்பதற்கான ரிலே
போஸ்ட் செய்வதற்கான ரிலே
- %1$s ரிலே விவரங்கள் பெறுகையில் பிழை
உரிமையாளர்
பதிப்பு
மென்பொருள்
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 64d45e820..f5ae18cbe 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -48,6 +48,8 @@
" and "
"in channel "
Profile Banner
+ Payment Successful
+ Error parsing error message
" Following"
" Followers"
Profile
@@ -433,7 +435,7 @@
Zapraiser at %1$s. %2$s sats to goal
Read from Relay
Write to Relay
- An error ocurred trying to get relay information from %1$s
+ An error occurred trying to get relay information from %1$s
Owner
Version
Software
@@ -458,6 +460,8 @@
Payment
Cashu Token
Redeem
+ No Lightning Address set
+ Copied token to clipboard
LIVE
OFFLINE
@@ -508,6 +512,7 @@
Checking Nostr address
Select/Deselect all
Default
+ Select a relay to continue
Forward Zaps to:
Supporting clients will forward zaps to the LNAddress or User Profile below instead of yours
diff --git a/build.gradle b/build.gradle
index a405f0fb0..b401c47bd 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,13 +1,15 @@
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
buildscript {
ext {
- fragment_version = "1.6.0"
+ fragment_version = "1.6.1"
lifecycle_version = '2.6.1'
compose_ui_version = '1.4.3'
nav_version = "2.6.0"
room_version = "2.4.3"
accompanist_version = '0.30.1'
coil_version = '2.4.0'
- vico_version = '1.7.1'
+ vico_version = '1.7.3'
exoplayer_version = '1.1.0'
}
dependencies {
@@ -15,13 +17,13 @@ buildscript {
}
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
- id 'com.android.application' version '8.0.2' apply false
- id 'com.android.library' version '8.0.2' apply false
- id 'org.jetbrains.kotlin.android' version '1.8.21' apply false
- id 'org.jetbrains.kotlin.jvm' version '1.8.21' apply false
+ id 'com.android.application' version '8.1.0' apply false
+ id 'com.android.library' version '8.1.0' apply false
+ id 'org.jetbrains.kotlin.android' version '1.9.0' apply false
+ id 'org.jetbrains.kotlin.jvm' version '1.9.0' apply false
}
-task installGitHook(type: Copy) {
+tasks.register('installGitHook', Copy) {
from new File(rootProject.rootDir, 'git-hooks/pre-commit')
from new File(rootProject.rootDir, 'git-hooks/pre-push')
into { new File(rootProject.rootDir, '.git/hooks') }
@@ -30,7 +32,7 @@ task installGitHook(type: Copy) {
tasks.getByPath(':app:preBuild').dependsOn installGitHook
subprojects {
- tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
+ tasks.withType(KotlinCompile).configureEach {
kotlinOptions {
if (project.findProperty("composeCompilerReports") == "true") {
freeCompilerArgs += [