From 27235b0f77b6f426a021674a1bcbab81afa6e090 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 31 Mar 2023 10:06:30 -0400 Subject: [PATCH 01/11] Changes the download folder to remove Amethyst's version. --- .../java/com/vitorpamplona/amethyst/ui/actions/ImageSaver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageSaver.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageSaver.kt index 61b73bb73..485f21062 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageSaver.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/ImageSaver.kt @@ -138,5 +138,5 @@ object ImageSaver { MediaScannerConnection.scanFile(context, arrayOf(outputFile.toString()), null, null) } - private const val PICTURES_SUBDIRECTORY = "Amethyst " + BuildConfig.VERSION_NAME + private const val PICTURES_SUBDIRECTORY = "Amethyst" } From 6884d5e8dd7f7695f8afb69ff027001d74ab4878 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 31 Mar 2023 10:53:34 -0400 Subject: [PATCH 02/11] Fixes thread loading issue where the clicked note's children is not loaded. --- .../java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt index 8b7617784..b8d0f4086 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt @@ -53,6 +53,7 @@ class ThreadAssembler { val threadRoot = searchRoot(note, thread) ?: note loadDown(threadRoot, thread) + loadDown(note, thread) thread.toSet() } else { From bd62736002602d5f885003fd344a3cf86a06dd83 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 31 Mar 2023 12:26:13 -0400 Subject: [PATCH 03/11] Uses per subscription EOSE to avoid downloading everything again when Resuming the app. --- .../com/vitorpamplona/amethyst/model/Note.kt | 3 ++- .../com/vitorpamplona/amethyst/model/User.kt | 3 ++- .../service/NostrAccountDataSource.kt | 18 ++++++++++--- .../service/NostrChatroomListDataSource.kt | 25 +++++++++++++++---- .../amethyst/service/NostrHomeDataSource.kt | 18 ++++++++++--- .../service/NostrSingleEventDataSource.kt | 9 ++++++- .../service/NostrSingleUserDataSource.kt | 9 ++++++- .../amethyst/service/relays/JsonFilter.kt | 6 +++-- .../amethyst/service/relays/Relay.kt | 17 ++++++++++++- 9 files changed, 90 insertions(+), 18 deletions(-) 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 6382390d3..4b63c8547 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.model import androidx.lifecycle.LiveData import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource import com.vitorpamplona.amethyst.service.model.* +import com.vitorpamplona.amethyst.service.relays.EOSETime import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.note.toShortenHex @@ -46,7 +47,7 @@ open class Note(val idHex: String) { var relays = setOf() private set - var lastReactionsDownloadTime: Map = emptyMap() + var lastReactionsDownloadTime: Map = emptyMap() fun id() = Hex.decode(idHex) open fun idNote() = id().toNote() 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 6c2f7dca3..4b9d5a5ac 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -7,6 +7,7 @@ import com.vitorpamplona.amethyst.service.model.ContactListEvent import com.vitorpamplona.amethyst.service.model.LnZapEvent import com.vitorpamplona.amethyst.service.model.MetadataEvent import com.vitorpamplona.amethyst.service.model.ReportEvent +import com.vitorpamplona.amethyst.service.relays.EOSETime import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.note.toShortenHex @@ -31,7 +32,7 @@ class User(val pubkeyHex: String) { var reports = mapOf>() private set - var latestEOSEs: Map = emptyMap() + var latestEOSEs: Map = emptyMap() var zaps = mapOf() private set diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt index 749e6ec72..2c3558b41 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt @@ -12,6 +12,7 @@ import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.ReportEvent import com.vitorpamplona.amethyst.service.model.RepostEvent import com.vitorpamplona.amethyst.service.model.TextNoteEvent +import com.vitorpamplona.amethyst.service.relays.EOSETime import com.vitorpamplona.amethyst.service.relays.FeedType import com.vitorpamplona.amethyst.service.relays.JsonFilter import com.vitorpamplona.amethyst.service.relays.TypedFilter @@ -19,6 +20,8 @@ import com.vitorpamplona.amethyst.service.relays.TypedFilter object NostrAccountDataSource : NostrDataSource("AccountData") { lateinit var account: Account + var latestEOSEs: Map = emptyMap() + fun createAccountContactListFilter(): TypedFilter { return TypedFilter( types = FeedType.values().toSet(), @@ -68,7 +71,8 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { types = FeedType.values().toSet(), filter = JsonFilter( kinds = listOf(ReportEvent.kind), - authors = listOf(account.userProfile().pubkeyHex) + authors = listOf(account.userProfile().pubkeyHex), + since = latestEOSEs ) ) } @@ -86,11 +90,19 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { BadgeAwardEvent.kind ), tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)), - limit = 200 + limit = 400, + since = latestEOSEs ) ) - val accountChannel = requestNewChannel() + val accountChannel = requestNewChannel { time, relayUrl -> + val eose = latestEOSEs[relayUrl] + if (eose == null) { + latestEOSEs = latestEOSEs + Pair(relayUrl, EOSETime(time)) + } else { + eose.time = time + } + } override fun updateChannelFilters() { // gets everthing about the user logged in 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 b390e494a..31d38d844 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatroomListDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatroomListDataSource.kt @@ -5,6 +5,7 @@ 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.PrivateDmEvent +import com.vitorpamplona.amethyst.service.relays.EOSETime import com.vitorpamplona.amethyst.service.relays.FeedType import com.vitorpamplona.amethyst.service.relays.JsonFilter import com.vitorpamplona.amethyst.service.relays.TypedFilter @@ -12,11 +13,14 @@ import com.vitorpamplona.amethyst.service.relays.TypedFilter object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") { lateinit var account: Account + var latestEOSEs: Map = emptyMap() + fun createMessagesToMeFilter() = TypedFilter( types = setOf(FeedType.PRIVATE_DMS), filter = JsonFilter( kinds = listOf(PrivateDmEvent.kind), - tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)) + tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)), + since = latestEOSEs ) ) @@ -24,7 +28,8 @@ object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") { types = setOf(FeedType.PRIVATE_DMS), filter = JsonFilter( kinds = listOf(PrivateDmEvent.kind), - authors = listOf(account.userProfile().pubkeyHex) + authors = listOf(account.userProfile().pubkeyHex), + since = latestEOSEs ) ) @@ -32,7 +37,8 @@ object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") { types = setOf(FeedType.PUBLIC_CHATS), filter = JsonFilter( kinds = listOf(ChannelCreateEvent.kind, ChannelMetadataEvent.kind), - authors = listOf(account.userProfile().pubkeyHex) + authors = listOf(account.userProfile().pubkeyHex), + since = latestEOSEs ) ) @@ -40,7 +46,8 @@ object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") { types = FeedType.values().toSet(), // Metadata comes from any relay filter = JsonFilter( kinds = listOf(ChannelCreateEvent.kind), - ids = account.followingChannels.toList() + ids = account.followingChannels.toList(), + since = latestEOSEs ) ) @@ -64,13 +71,21 @@ object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") { filter = JsonFilter( kinds = listOf(ChannelMessageEvent.kind), tags = mapOf("e" to listOf(it)), + since = latestEOSEs, limit = 25 // Remember to consider spam that is being removed from the UI ) ) } } - val chatroomListChannel = requestNewChannel() + val chatroomListChannel = requestNewChannel() { time, relayUrl -> + val eose = latestEOSEs[relayUrl] + if (eose == null) { + latestEOSEs = latestEOSEs + Pair(relayUrl, EOSETime(time)) + } else { + eose.time = time + } + } override fun updateChannelFilters() { val list = listOf( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt index f94e1a8c0..f59fa5961 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt @@ -4,6 +4,7 @@ import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.UserState import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent import com.vitorpamplona.amethyst.service.model.TextNoteEvent +import com.vitorpamplona.amethyst.service.relays.EOSETime import com.vitorpamplona.amethyst.service.relays.FeedType import com.vitorpamplona.amethyst.service.relays.JsonFilter import com.vitorpamplona.amethyst.service.relays.TypedFilter @@ -15,6 +16,8 @@ import kotlinx.coroutines.launch object NostrHomeDataSource : NostrDataSource("HomeFeed") { lateinit var account: Account + var latestEOSEs: Map = emptyMap() + private val cacheListener: (UserState) -> Unit = { invalidateFilters() } @@ -53,7 +56,8 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") { filter = JsonFilter( kinds = listOf(TextNoteEvent.kind, LongTextNoteEvent.kind), authors = followSet, - limit = 400 + limit = 400, + since = latestEOSEs ) ) } @@ -72,12 +76,20 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") { listOf(it, it.lowercase(), it.uppercase(), it.capitalize()) }.flatten() ), - limit = 100 + limit = 100, + since = latestEOSEs ) ) } - val followAccountChannel = requestNewChannel() + val followAccountChannel = requestNewChannel() { time, relayUrl -> + val eose = latestEOSEs[relayUrl] + if (eose == null) { + latestEOSEs = latestEOSEs + Pair(relayUrl, EOSETime(time)) + } else { + eose.time = time + } + } override fun updateChannelFilters() { followAccountChannel.typedFilters = listOfNotNull(createFollowAccountsFilter(), createFollowTagsFilter()).ifEmpty { null } 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 59abaed3c..d4dcccc88 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt @@ -15,6 +15,7 @@ import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.ReportEvent import com.vitorpamplona.amethyst.service.model.RepostEvent import com.vitorpamplona.amethyst.service.model.TextNoteEvent +import com.vitorpamplona.amethyst.service.relays.EOSETime import com.vitorpamplona.amethyst.service.relays.FeedType import com.vitorpamplona.amethyst.service.relays.JsonFilter import com.vitorpamplona.amethyst.service.relays.TypedFilter @@ -136,8 +137,14 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") { val singleEventChannel = requestNewChannel { time, relayUrl -> eventsToWatch.forEach { - it.lastReactionsDownloadTime = it.lastReactionsDownloadTime + Pair(relayUrl, time) + val eose = it.lastReactionsDownloadTime[relayUrl] + if (eose == null) { + it.lastReactionsDownloadTime = it.lastReactionsDownloadTime + Pair(relayUrl, EOSETime(time)) + } else { + eose.time = time + } } + // Many relays operate with limits in the amount of filters. // As information comes, the filters will be rotated to get more data. invalidateFilters() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleUserDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleUserDataSource.kt index 61cb179e2..3742ebe5d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleUserDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleUserDataSource.kt @@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.service import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.model.MetadataEvent import com.vitorpamplona.amethyst.service.model.ReportEvent +import com.vitorpamplona.amethyst.service.relays.EOSETime import com.vitorpamplona.amethyst.service.relays.FeedType import com.vitorpamplona.amethyst.service.relays.JsonFilter import com.vitorpamplona.amethyst.service.relays.TypedFilter @@ -42,8 +43,14 @@ object NostrSingleUserDataSource : NostrDataSource("SingleUserFeed") { val userChannel = requestNewChannel() { time, relayUrl -> usersToWatch.forEach { - it.latestEOSEs = it.latestEOSEs + Pair(relayUrl, time) + val eose = it.latestEOSEs[relayUrl] + if (eose == null) { + it.latestEOSEs = it.latestEOSEs + Pair(relayUrl, EOSETime(time)) + } else { + eose.time = time + } } + // Many relays operate with limits in the amount of filters. // As information comes, the filters will be rotated to get more data. invalidateFilters() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/JsonFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/JsonFilter.kt index b3053f1e7..8f58036d3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/JsonFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/JsonFilter.kt @@ -6,12 +6,14 @@ import com.google.gson.JsonArray import com.google.gson.JsonObject import java.util.* +class EOSETime(var time: Long) + class JsonFilter( val ids: List? = null, val authors: List? = null, val kinds: List? = null, val tags: Map>? = null, - val since: Map? = null, + val since: Map? = null, val until: Long? = null, val limit: Int? = null, val search: String? = null @@ -37,7 +39,7 @@ class JsonFilter( if (forRelay != null) { val relaySince = get(forRelay) if (relaySince != null) { - jsonObject.addProperty("since", relaySince) + jsonObject.addProperty("since", relaySince.time) } } else { val jsonObjectSince = JsonObject() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt index 8ab0b4d97..87407844b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt @@ -40,6 +40,8 @@ class Relay( var closingTime = 0L + var afterEOSE = false + fun register(listener: Listener) { listeners = listeners.plus(listener) } @@ -74,6 +76,7 @@ class Relay( val listener = object : WebSocketListener() { override fun onOpen(webSocket: WebSocket, response: Response) { + afterEOSE = false isReady = true ping = response.receivedResponseAtMillis - response.sentRequestAtMillis // Log.w("Relay", "Relay OnOpen, Loading All subscriptions $url") @@ -89,12 +92,19 @@ class Relay( val msg = Event.gson.fromJson(text, JsonElement::class.java).asJsonArray val type = msg[0].asString val channel = msg[1].asString + when (type) { "EVENT" -> { // Log.w("Relay", "Relay onEVENT $url, $channel") - listeners.forEach { it.onEvent(this@Relay, channel, Event.fromJson(msg[2], Client.lenient)) } + listeners.forEach { + it.onEvent(this@Relay, channel, Event.fromJson(msg[2], Client.lenient)) + if (afterEOSE) { + it.onRelayStateChange(this@Relay, Type.EOSE, channel) + } + } } "EOSE" -> listeners.forEach { + afterEOSE = true // Log.w("Relay", "Relay onEOSE $url, $channel") it.onRelayStateChange(this@Relay, Type.EOSE, channel) } @@ -136,6 +146,7 @@ class Relay( override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { socket = null isReady = false + afterEOSE = false closingTime = Date().time / 1000 listeners.forEach { it.onRelayStateChange(this@Relay, Type.DISCONNECT, null) } } @@ -147,6 +158,7 @@ class Relay( // Failures disconnect the relay. socket = null isReady = false + afterEOSE = false closingTime = Date().time / 1000 Log.w("Relay", "Relay onFailure $url, ${response?.message} $response") @@ -161,6 +173,7 @@ class Relay( } catch (e: Exception) { errorCounter++ isReady = false + afterEOSE = false closingTime = Date().time / 1000 Log.e("Relay", "Relay Invalid $url") e.printStackTrace() @@ -173,6 +186,7 @@ class Relay( socket?.close(1000, "Normal close") socket = null isReady = false + afterEOSE = false } fun sendFilter(requestId: String) { @@ -186,6 +200,7 @@ class Relay( // println("FILTERSSENT $url $request") socket?.send(request) eventUploadCounterInBytes += request.bytesUsedInMemory() + afterEOSE = false } } } else { From 3145e381a043eedb5cd1cc425514e39aa7e6b981 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 31 Mar 2023 13:50:24 -0400 Subject: [PATCH 04/11] Refactoring EOSEs to consider account switching in the app. --- .../service/NostrAccountDataSource.kt | 15 ++++------- .../service/NostrChatroomListDataSource.kt | 23 +++++++--------- .../amethyst/service/NostrHomeDataSource.kt | 17 +++++------- .../amethyst/service/relays/EOSE.kt | 27 +++++++++++++++++++ .../amethyst/service/relays/JsonFilter.kt | 3 --- 5 files changed, 47 insertions(+), 38 deletions(-) create mode 100644 app/src/main/java/com/vitorpamplona/amethyst/service/relays/EOSE.kt diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt index 2c3558b41..9317680eb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt @@ -12,7 +12,7 @@ import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.ReportEvent import com.vitorpamplona.amethyst.service.model.RepostEvent import com.vitorpamplona.amethyst.service.model.TextNoteEvent -import com.vitorpamplona.amethyst.service.relays.EOSETime +import com.vitorpamplona.amethyst.service.relays.EOSEAccount import com.vitorpamplona.amethyst.service.relays.FeedType import com.vitorpamplona.amethyst.service.relays.JsonFilter import com.vitorpamplona.amethyst.service.relays.TypedFilter @@ -20,7 +20,7 @@ import com.vitorpamplona.amethyst.service.relays.TypedFilter object NostrAccountDataSource : NostrDataSource("AccountData") { lateinit var account: Account - var latestEOSEs: Map = emptyMap() + val latestEOSEs = EOSEAccount() fun createAccountContactListFilter(): TypedFilter { return TypedFilter( @@ -72,7 +72,7 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { filter = JsonFilter( kinds = listOf(ReportEvent.kind), authors = listOf(account.userProfile().pubkeyHex), - since = latestEOSEs + since = latestEOSEs.users[account.userProfile()]?.relayList ) ) } @@ -91,17 +91,12 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { ), tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)), limit = 400, - since = latestEOSEs + since = latestEOSEs.users[account.userProfile()]?.relayList ) ) val accountChannel = requestNewChannel { time, relayUrl -> - val eose = latestEOSEs[relayUrl] - if (eose == null) { - latestEOSEs = latestEOSEs + Pair(relayUrl, EOSETime(time)) - } else { - eose.time = time - } + latestEOSEs.addOrUpdate(account.userProfile(), relayUrl, time) } override fun updateChannelFilters() { 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 31d38d844..a97fa5f41 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatroomListDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatroomListDataSource.kt @@ -5,7 +5,7 @@ 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.PrivateDmEvent -import com.vitorpamplona.amethyst.service.relays.EOSETime +import com.vitorpamplona.amethyst.service.relays.EOSEAccount import com.vitorpamplona.amethyst.service.relays.FeedType import com.vitorpamplona.amethyst.service.relays.JsonFilter import com.vitorpamplona.amethyst.service.relays.TypedFilter @@ -13,14 +13,14 @@ import com.vitorpamplona.amethyst.service.relays.TypedFilter object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") { lateinit var account: Account - var latestEOSEs: Map = emptyMap() + val latestEOSEs = EOSEAccount() fun createMessagesToMeFilter() = TypedFilter( types = setOf(FeedType.PRIVATE_DMS), filter = JsonFilter( kinds = listOf(PrivateDmEvent.kind), tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)), - since = latestEOSEs + since = latestEOSEs.users[account.userProfile()]?.relayList ) ) @@ -29,7 +29,7 @@ object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") { filter = JsonFilter( kinds = listOf(PrivateDmEvent.kind), authors = listOf(account.userProfile().pubkeyHex), - since = latestEOSEs + since = latestEOSEs.users[account.userProfile()]?.relayList ) ) @@ -38,7 +38,7 @@ object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") { filter = JsonFilter( kinds = listOf(ChannelCreateEvent.kind, ChannelMetadataEvent.kind), authors = listOf(account.userProfile().pubkeyHex), - since = latestEOSEs + since = latestEOSEs.users[account.userProfile()]?.relayList ) ) @@ -47,7 +47,7 @@ object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") { filter = JsonFilter( kinds = listOf(ChannelCreateEvent.kind), ids = account.followingChannels.toList(), - since = latestEOSEs + since = latestEOSEs.users[account.userProfile()]?.relayList ) ) @@ -71,20 +71,15 @@ object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") { filter = JsonFilter( kinds = listOf(ChannelMessageEvent.kind), tags = mapOf("e" to listOf(it)), - since = latestEOSEs, + since = latestEOSEs.users[account.userProfile()]?.relayList, limit = 25 // Remember to consider spam that is being removed from the UI ) ) } } - val chatroomListChannel = requestNewChannel() { time, relayUrl -> - val eose = latestEOSEs[relayUrl] - if (eose == null) { - latestEOSEs = latestEOSEs + Pair(relayUrl, EOSETime(time)) - } else { - eose.time = time - } + val chatroomListChannel = requestNewChannel { time, relayUrl -> + latestEOSEs.addOrUpdate(account.userProfile(), relayUrl, time) } override fun updateChannelFilters() { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt index f59fa5961..7b3428a68 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt @@ -4,7 +4,7 @@ import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.UserState import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent import com.vitorpamplona.amethyst.service.model.TextNoteEvent -import com.vitorpamplona.amethyst.service.relays.EOSETime +import com.vitorpamplona.amethyst.service.relays.EOSEAccount import com.vitorpamplona.amethyst.service.relays.FeedType import com.vitorpamplona.amethyst.service.relays.JsonFilter import com.vitorpamplona.amethyst.service.relays.TypedFilter @@ -16,7 +16,7 @@ import kotlinx.coroutines.launch object NostrHomeDataSource : NostrDataSource("HomeFeed") { lateinit var account: Account - var latestEOSEs: Map = emptyMap() + val latestEOSEs = EOSEAccount() private val cacheListener: (UserState) -> Unit = { invalidateFilters() @@ -57,7 +57,7 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") { kinds = listOf(TextNoteEvent.kind, LongTextNoteEvent.kind), authors = followSet, limit = 400, - since = latestEOSEs + since = latestEOSEs.users[account.userProfile()]?.relayList ) ) } @@ -77,18 +77,13 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") { }.flatten() ), limit = 100, - since = latestEOSEs + since = latestEOSEs.users[account.userProfile()]?.relayList ) ) } - val followAccountChannel = requestNewChannel() { time, relayUrl -> - val eose = latestEOSEs[relayUrl] - if (eose == null) { - latestEOSEs = latestEOSEs + Pair(relayUrl, EOSETime(time)) - } else { - eose.time = time - } + val followAccountChannel = requestNewChannel { time, relayUrl -> + latestEOSEs.addOrUpdate(account.userProfile(), relayUrl, time) } override fun updateChannelFilters() { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/EOSE.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/EOSE.kt new file mode 100644 index 000000000..b55904bb7 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/EOSE.kt @@ -0,0 +1,27 @@ +package com.vitorpamplona.amethyst.service.relays + +import com.vitorpamplona.amethyst.model.User + +class EOSETime(var time: Long) + +class EOSERelayList(var relayList: Map = emptyMap()) { + fun addOrUpdate(relayUrl: String, time: Long) { + val eose = relayList[relayUrl] + if (eose == null) { + relayList = relayList + Pair(relayUrl, EOSETime(time)) + } else { + eose.time = time + } + } +} + +class EOSEAccount(var users: Map = emptyMap()) { + fun addOrUpdate(user: User, relayUrl: String, time: Long) { + val relayList = users[user] + if (relayList == null) { + users = users + mapOf(user to EOSERelayList(mapOf(relayUrl to EOSETime(time)))) + } else { + relayList.addOrUpdate(relayUrl, time) + } + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/JsonFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/JsonFilter.kt index 8f58036d3..74bc52613 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/JsonFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/JsonFilter.kt @@ -4,9 +4,6 @@ import com.google.gson.Gson import com.google.gson.GsonBuilder import com.google.gson.JsonArray import com.google.gson.JsonObject -import java.util.* - -class EOSETime(var time: Long) class JsonFilter( val ids: List? = null, From 97017e2bd5df3b00042ef276a7450e39d2d6c0b0 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 31 Mar 2023 14:12:59 -0400 Subject: [PATCH 05/11] v0.31.0 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 65d76a278..c0631eec4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,8 +12,8 @@ android { applicationId "com.vitorpamplona.amethyst" minSdk 26 targetSdk 33 - versionCode 109 - versionName "0.30.2" + versionCode 110 + versionName "0.31.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From 6c5fecc4d20c9f2cc49f919fb9288c3ed1352a39 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 31 Mar 2023 15:30:57 -0400 Subject: [PATCH 06/11] Loads the replies of the selected note. --- .../com/vitorpamplona/amethyst/model/ThreadAssembler.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt index b8d0f4086..636ee52b9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt @@ -53,7 +53,11 @@ class ThreadAssembler { val threadRoot = searchRoot(note, thread) ?: note loadDown(threadRoot, thread) - loadDown(note, thread) + // adds the replies of the note in case the search for Root + // did not added them. + note.replies.forEach { + loadDown(it, thread) + } thread.toSet() } else { From 22adbddd88098f7257880da456f05183751b2f27 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 31 Mar 2023 15:41:07 -0400 Subject: [PATCH 07/11] v0.31.1 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c0631eec4..f8759ba32 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,8 +12,8 @@ android { applicationId "com.vitorpamplona.amethyst" minSdk 26 targetSdk 33 - versionCode 110 - versionName "0.31.0" + versionCode 111 + versionName "0.31.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From 737244fb603af76f668f4777232cabce5c8936c7 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 31 Mar 2023 16:59:23 -0400 Subject: [PATCH 08/11] Fixes "author_is_elon" bug --- .../amethyst/model/UrlCachedPreviewer.kt | 29 ------------------- .../amethyst/ui/actions/NewPostView.kt | 6 ++-- .../amethyst/ui/components/RichTextViewer.kt | 12 ++++---- .../ui/components/ZoomableImageView.kt | 4 +-- 4 files changed, 10 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/UrlCachedPreviewer.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/UrlCachedPreviewer.kt index e49cdf630..833d1155b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/UrlCachedPreviewer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/UrlCachedPreviewer.kt @@ -3,10 +3,6 @@ package com.vitorpamplona.amethyst.model import com.baha.url.preview.BahaUrlPreview import com.baha.url.preview.IUrlPreviewCallback import com.baha.url.preview.UrlInfoItem -import com.vitorpamplona.amethyst.ui.components.imageExtension -import com.vitorpamplona.amethyst.ui.components.isValidURL -import com.vitorpamplona.amethyst.ui.components.noProtocolUrlValidator -import com.vitorpamplona.amethyst.ui.components.videoExtension import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -47,29 +43,4 @@ object UrlCachedPreviewer { ).fetchUrlPreview() } } - - fun findUrlsInMessage(message: String): List { - return message.split('\n').map { paragraph -> - paragraph.split(' ').filter { word: String -> - isValidURL(word) || noProtocolUrlValidator.matcher(word).matches() - } - }.flatten() - } - - fun preloadPreviewsFor(note: Note) { - note.event?.content()?.let { - findUrlsInMessage(it).forEach { - val removedParamsFromUrl = it.split("?")[0].lowercase() - if (imageExtension.matcher(removedParamsFromUrl).matches()) { - // Preload Images? Isn't this too heavy? - } else if (videoExtension.matcher(removedParamsFromUrl).matches()) { - // Do nothing for now. - } else if (isValidURL(removedParamsFromUrl)) { - previewInfo(it) - } else { - previewInfo("https://$it") - } - } - } - } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index 35597a447..100b3fabe 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -167,7 +167,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n if (isValidURL(myUrlPreview)) { val removedParamsFromUrl = myUrlPreview.split("?")[0].lowercase() - if (imageExtension.matcher(removedParamsFromUrl).matches()) { + if (imageExtensions.any { removedParamsFromUrl.endsWith(it, true) }) { AsyncImage( model = myUrlPreview, contentDescription = myUrlPreview, @@ -182,9 +182,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n RoundedCornerShape(15.dp) ) ) - } else if (videoExtension.matcher(removedParamsFromUrl) - .matches() - ) { + } else if (videoExtensions.any { removedParamsFromUrl.endsWith(it, true) }) { VideoView(myUrlPreview) } else { UrlPreview(myUrlPreview, myUrlPreview) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt index 3443aed7e..02ff82e40 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt @@ -46,8 +46,8 @@ import java.net.URISyntaxException import java.net.URL import java.util.regex.Pattern -val imageExtension = Pattern.compile("(.*/)*.+\\.(png|jpg|gif|bmp|jpeg|webp|svg)$", Pattern.CASE_INSENSITIVE) -val videoExtension = Pattern.compile("(.*/)*.+\\.(mp4|avi|wmv|mpg|amv|webm|mov)$", Pattern.CASE_INSENSITIVE) +val imageExtensions = listOf("png", "jpg", "gif", "bmp", "jpeg", "webp", "svg") +val videoExtensions = listOf("mp4", "avi", "wmv", "mpg", "amv", "webm", "mov") // Group 1 = url, group 4 additional chars val noProtocolUrlValidator = Pattern.compile("(([\\w\\d-]+\\.)*[a-zA-Z][\\w-]+[\\.\\:]\\w+([\\/\\?\\=\\&\\#\\.]?[\\w-]+)*\\/?)(.*)") @@ -138,10 +138,10 @@ fun RichTextViewer( // sequence of images will render in a slideview if (isValidURL(word)) { val removedParamsFromUrl = word.split("?")[0].lowercase() - if (imageExtension.matcher(removedParamsFromUrl).matches()) { + if (imageExtensions.any { word.endsWith(it, true) }) { imagesForPager.add(word) } - if (videoExtension.matcher(removedParamsFromUrl).matches()) { + if (videoExtensions.any { word.endsWith(it, true) }) { imagesForPager.add(word) } } @@ -160,9 +160,9 @@ fun RichTextViewer( if (isValidURL(word)) { val removedParamsFromUrl = word.split("?")[0].lowercase() - if (imageExtension.matcher(removedParamsFromUrl).matches()) { + if (imageExtensions.any { word.endsWith(it, true) }) { ZoomableImageView(word, imagesForPager) - } else if (videoExtension.matcher(removedParamsFromUrl).matches()) { + } else if (videoExtensions.any { word.endsWith(it, true) }) { ZoomableImageView(word, imagesForPager) } else { UrlPreview(word, "$word ") diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableImageView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableImageView.kt index 729ecadfc..02f5cafe8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableImageView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableImageView.kt @@ -62,7 +62,7 @@ fun ZoomableImageView(word: String, images: List = listOf(word)) { mutableStateOf(null) } - if (imageExtension.matcher(word).matches()) { + if (imageExtensions.any { word.endsWith(it, true) }) { AsyncImage( model = word, contentDescription = word, @@ -171,7 +171,7 @@ fun ZoomableImageDialog(imageUrl: String, allImages: List = listOf(image @Composable private fun RenderImageOrVideo(imageUrl: String) { - if (imageExtension.matcher(imageUrl).matches()) { + if (imageExtensions.any { imageUrl.endsWith(it, true) }) { AsyncImage( model = imageUrl, contentDescription = stringResource(id = R.string.profile_image), From 4bced4dbead24d324624f7a74aebd816bcb6e3dd Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 31 Mar 2023 17:00:17 -0400 Subject: [PATCH 09/11] v0.31.2 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index f8759ba32..db70d5595 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,8 +12,8 @@ android { applicationId "com.vitorpamplona.amethyst" minSdk 26 targetSdk 33 - versionCode 111 - versionName "0.31.1" + versionCode 112 + versionName "0.31.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { From 3626f24b5f9ee7d6e9ac19eeb6b02bb3d8b4e208 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 31 Mar 2023 18:36:05 -0400 Subject: [PATCH 10/11] Adds citations to the notification feed --- .../vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt index 72e7766a8..1372f166d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt @@ -23,7 +23,9 @@ object NotificationFeedFilter : FeedFilter() { } .filter { it -> it.event !is TextNoteEvent || - it.replyTo?.any { it.author == loggedInUser } == true || + (it.event as? TextNoteEvent)?.taggedEvents()?.any { + LocalCache.checkGetOrCreateNote(it)?.author == loggedInUser + } == true || loggedInUser in it.directlyCiteUsers() } .filter { From 1ee3dc0ff3293c1ed958104dea0d8008cab73b6e Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 31 Mar 2023 18:36:58 -0400 Subject: [PATCH 11/11] v0.31.3 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index db70d5595..3f269ebef 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,8 +12,8 @@ android { applicationId "com.vitorpamplona.amethyst" minSdk 26 targetSdk 33 - versionCode 112 - versionName "0.31.2" + versionCode 113 + versionName "0.31.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables {