diff --git a/app/build.gradle b/app/build.gradle index b229f89ee..d09e47ec4 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 113 + versionName "0.31.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { 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/ThreadAssembler.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt index 8b7617784..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,6 +53,11 @@ class ThreadAssembler { val threadRoot = searchRoot(note, thread) ?: note loadDown(threadRoot, 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 { 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/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 5e50fa84b..cb867066c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt @@ -13,6 +13,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.EOSEAccount import com.vitorpamplona.amethyst.service.relays.FeedType import com.vitorpamplona.amethyst.service.relays.JsonFilter import com.vitorpamplona.amethyst.service.relays.TypedFilter @@ -20,6 +21,8 @@ import com.vitorpamplona.amethyst.service.relays.TypedFilter object NostrAccountDataSource : NostrDataSource("AccountData") { lateinit var account: Account + val latestEOSEs = EOSEAccount() + fun createAccountContactListFilter(): TypedFilter { return TypedFilter( types = FeedType.values().toSet(), @@ -69,7 +72,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.users[account.userProfile()]?.relayList ) ) } @@ -88,11 +92,14 @@ object NostrAccountDataSource : NostrDataSource("AccountData") { BadgeAwardEvent.kind ), tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)), - limit = 200 + limit = 400, + since = latestEOSEs.users[account.userProfile()]?.relayList ) ) - val accountChannel = requestNewChannel() + val accountChannel = requestNewChannel { time, relayUrl -> + latestEOSEs.addOrUpdate(account.userProfile(), relayUrl, 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..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,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.EOSEAccount 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 + 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)) + tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)), + since = latestEOSEs.users[account.userProfile()]?.relayList ) ) @@ -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.users[account.userProfile()]?.relayList ) ) @@ -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.users[account.userProfile()]?.relayList ) ) @@ -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.users[account.userProfile()]?.relayList ) ) @@ -64,13 +71,16 @@ object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") { filter = JsonFilter( kinds = listOf(ChannelMessageEvent.kind), tags = mapOf("e" to listOf(it)), + since = latestEOSEs.users[account.userProfile()]?.relayList, limit = 25 // Remember to consider spam that is being removed from the UI ) ) } } - val chatroomListChannel = requestNewChannel() + val chatroomListChannel = requestNewChannel { time, relayUrl -> + latestEOSEs.addOrUpdate(account.userProfile(), relayUrl, 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 7e8556efc..b07eab060 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt @@ -5,6 +5,7 @@ import com.vitorpamplona.amethyst.model.UserState import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent import com.vitorpamplona.amethyst.service.model.PollNoteEvent import com.vitorpamplona.amethyst.service.model.TextNoteEvent +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,6 +17,8 @@ import kotlinx.coroutines.launch object NostrHomeDataSource : NostrDataSource("HomeFeed") { lateinit var account: Account + val latestEOSEs = EOSEAccount() + private val cacheListener: (UserState) -> Unit = { invalidateFilters() } @@ -54,7 +57,8 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") { filter = JsonFilter( kinds = listOf(TextNoteEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind), authors = followSet, - limit = 400 + limit = 400, + since = latestEOSEs.users[account.userProfile()]?.relayList ) ) } @@ -73,12 +77,15 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") { listOf(it, it.lowercase(), it.uppercase(), it.capitalize()) }.flatten() ), - limit = 100 + limit = 100, + since = latestEOSEs.users[account.userProfile()]?.relayList ) ) } - val followAccountChannel = requestNewChannel() + val followAccountChannel = requestNewChannel { time, relayUrl -> + latestEOSEs.addOrUpdate(account.userProfile(), relayUrl, 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 ca34b801b..47edf38ab 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt @@ -2,7 +2,20 @@ package com.vitorpamplona.amethyst.service import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.Note -import com.vitorpamplona.amethyst.service.model.* +import com.vitorpamplona.amethyst.service.model.BadgeAwardEvent +import com.vitorpamplona.amethyst.service.model.BadgeDefinitionEvent +import com.vitorpamplona.amethyst.service.model.BadgeProfilesEvent +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.LnZapEvent +import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent +import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent +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 @@ -127,8 +140,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/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 b3053f1e7..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,14 +4,13 @@ import com.google.gson.Gson import com.google.gson.GsonBuilder import com.google.gson.JsonArray import com.google.gson.JsonObject -import java.util.* 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 +36,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 { 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" } 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), 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 507e5ccab..e5aba595b 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 { it ->