merge with recent changes to main

This commit is contained in:
toadlyBroodle
2023-04-01 09:54:13 +09:00
18 changed files with 134 additions and 65 deletions
+2 -2
View File
@@ -12,8 +12,8 @@ android {
applicationId "com.vitorpamplona.amethyst" applicationId "com.vitorpamplona.amethyst"
minSdk 26 minSdk 26
targetSdk 33 targetSdk 33
versionCode 109 versionCode 113
versionName "0.30.2" versionName "0.31.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
@@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.model
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
import com.vitorpamplona.amethyst.service.model.* import com.vitorpamplona.amethyst.service.model.*
import com.vitorpamplona.amethyst.service.relays.EOSETime
import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.components.BundledUpdate
import com.vitorpamplona.amethyst.ui.note.toShortenHex import com.vitorpamplona.amethyst.ui.note.toShortenHex
@@ -46,7 +47,7 @@ open class Note(val idHex: String) {
var relays = setOf<String>() var relays = setOf<String>()
private set private set
var lastReactionsDownloadTime: Map<String, Long> = emptyMap() var lastReactionsDownloadTime: Map<String, EOSETime> = emptyMap()
fun id() = Hex.decode(idHex) fun id() = Hex.decode(idHex)
open fun idNote() = id().toNote() open fun idNote() = id().toNote()
@@ -53,6 +53,11 @@ class ThreadAssembler {
val threadRoot = searchRoot(note, thread) ?: note val threadRoot = searchRoot(note, thread) ?: note
loadDown(threadRoot, thread) 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() thread.toSet()
} else { } else {
@@ -3,10 +3,6 @@ package com.vitorpamplona.amethyst.model
import com.baha.url.preview.BahaUrlPreview import com.baha.url.preview.BahaUrlPreview
import com.baha.url.preview.IUrlPreviewCallback import com.baha.url.preview.IUrlPreviewCallback
import com.baha.url.preview.UrlInfoItem 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.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@@ -47,29 +43,4 @@ object UrlCachedPreviewer {
).fetchUrlPreview() ).fetchUrlPreview()
} }
} }
fun findUrlsInMessage(message: String): List<String> {
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")
}
}
}
}
} }
@@ -7,6 +7,7 @@ import com.vitorpamplona.amethyst.service.model.ContactListEvent
import com.vitorpamplona.amethyst.service.model.LnZapEvent import com.vitorpamplona.amethyst.service.model.LnZapEvent
import com.vitorpamplona.amethyst.service.model.MetadataEvent import com.vitorpamplona.amethyst.service.model.MetadataEvent
import com.vitorpamplona.amethyst.service.model.ReportEvent 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.service.relays.Relay
import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.components.BundledUpdate
import com.vitorpamplona.amethyst.ui.note.toShortenHex import com.vitorpamplona.amethyst.ui.note.toShortenHex
@@ -31,7 +32,7 @@ class User(val pubkeyHex: String) {
var reports = mapOf<User, Set<Note>>() var reports = mapOf<User, Set<Note>>()
private set private set
var latestEOSEs: Map<String, Long> = emptyMap() var latestEOSEs: Map<String, EOSETime> = emptyMap()
var zaps = mapOf<Note, Note?>() var zaps = mapOf<Note, Note?>()
private set private set
@@ -13,6 +13,7 @@ import com.vitorpamplona.amethyst.service.model.ReactionEvent
import com.vitorpamplona.amethyst.service.model.ReportEvent import com.vitorpamplona.amethyst.service.model.ReportEvent
import com.vitorpamplona.amethyst.service.model.RepostEvent import com.vitorpamplona.amethyst.service.model.RepostEvent
import com.vitorpamplona.amethyst.service.model.TextNoteEvent 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.FeedType
import com.vitorpamplona.amethyst.service.relays.JsonFilter import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.relays.TypedFilter import com.vitorpamplona.amethyst.service.relays.TypedFilter
@@ -20,6 +21,8 @@ import com.vitorpamplona.amethyst.service.relays.TypedFilter
object NostrAccountDataSource : NostrDataSource("AccountData") { object NostrAccountDataSource : NostrDataSource("AccountData") {
lateinit var account: Account lateinit var account: Account
val latestEOSEs = EOSEAccount()
fun createAccountContactListFilter(): TypedFilter { fun createAccountContactListFilter(): TypedFilter {
return TypedFilter( return TypedFilter(
types = FeedType.values().toSet(), types = FeedType.values().toSet(),
@@ -69,7 +72,8 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
types = FeedType.values().toSet(), types = FeedType.values().toSet(),
filter = JsonFilter( filter = JsonFilter(
kinds = listOf(ReportEvent.kind), 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 BadgeAwardEvent.kind
), ),
tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)), 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() { override fun updateChannelFilters() {
// gets everthing about the user logged in // gets everthing about the user logged in
@@ -5,6 +5,7 @@ import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent 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.FeedType
import com.vitorpamplona.amethyst.service.relays.JsonFilter import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.relays.TypedFilter import com.vitorpamplona.amethyst.service.relays.TypedFilter
@@ -12,11 +13,14 @@ import com.vitorpamplona.amethyst.service.relays.TypedFilter
object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") { object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") {
lateinit var account: Account lateinit var account: Account
val latestEOSEs = EOSEAccount()
fun createMessagesToMeFilter() = TypedFilter( fun createMessagesToMeFilter() = TypedFilter(
types = setOf(FeedType.PRIVATE_DMS), types = setOf(FeedType.PRIVATE_DMS),
filter = JsonFilter( filter = JsonFilter(
kinds = listOf(PrivateDmEvent.kind), 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), types = setOf(FeedType.PRIVATE_DMS),
filter = JsonFilter( filter = JsonFilter(
kinds = listOf(PrivateDmEvent.kind), 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), types = setOf(FeedType.PUBLIC_CHATS),
filter = JsonFilter( filter = JsonFilter(
kinds = listOf(ChannelCreateEvent.kind, ChannelMetadataEvent.kind), 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 types = FeedType.values().toSet(), // Metadata comes from any relay
filter = JsonFilter( filter = JsonFilter(
kinds = listOf(ChannelCreateEvent.kind), 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( filter = JsonFilter(
kinds = listOf(ChannelMessageEvent.kind), kinds = listOf(ChannelMessageEvent.kind),
tags = mapOf("e" to listOf(it)), 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 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() { override fun updateChannelFilters() {
val list = listOf( val list = listOf(
@@ -5,6 +5,7 @@ import com.vitorpamplona.amethyst.model.UserState
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
import com.vitorpamplona.amethyst.service.model.PollNoteEvent import com.vitorpamplona.amethyst.service.model.PollNoteEvent
import com.vitorpamplona.amethyst.service.model.TextNoteEvent 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.FeedType
import com.vitorpamplona.amethyst.service.relays.JsonFilter import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.relays.TypedFilter import com.vitorpamplona.amethyst.service.relays.TypedFilter
@@ -16,6 +17,8 @@ import kotlinx.coroutines.launch
object NostrHomeDataSource : NostrDataSource("HomeFeed") { object NostrHomeDataSource : NostrDataSource("HomeFeed") {
lateinit var account: Account lateinit var account: Account
val latestEOSEs = EOSEAccount()
private val cacheListener: (UserState) -> Unit = { private val cacheListener: (UserState) -> Unit = {
invalidateFilters() invalidateFilters()
} }
@@ -54,7 +57,8 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") {
filter = JsonFilter( filter = JsonFilter(
kinds = listOf(TextNoteEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind), kinds = listOf(TextNoteEvent.kind, LongTextNoteEvent.kind, PollNoteEvent.kind),
authors = followSet, 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()) listOf(it, it.lowercase(), it.uppercase(), it.capitalize())
}.flatten() }.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() { override fun updateChannelFilters() {
followAccountChannel.typedFilters = listOfNotNull(createFollowAccountsFilter(), createFollowTagsFilter()).ifEmpty { null } followAccountChannel.typedFilters = listOfNotNull(createFollowAccountsFilter(), createFollowTagsFilter()).ifEmpty { null }
@@ -2,7 +2,20 @@ package com.vitorpamplona.amethyst.service
import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.Note 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.FeedType
import com.vitorpamplona.amethyst.service.relays.JsonFilter import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.relays.TypedFilter import com.vitorpamplona.amethyst.service.relays.TypedFilter
@@ -127,8 +140,14 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
val singleEventChannel = requestNewChannel { time, relayUrl -> val singleEventChannel = requestNewChannel { time, relayUrl ->
eventsToWatch.forEach { 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. // Many relays operate with limits in the amount of filters.
// As information comes, the filters will be rotated to get more data. // As information comes, the filters will be rotated to get more data.
invalidateFilters() invalidateFilters()
@@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.service
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.model.MetadataEvent import com.vitorpamplona.amethyst.service.model.MetadataEvent
import com.vitorpamplona.amethyst.service.model.ReportEvent 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.FeedType
import com.vitorpamplona.amethyst.service.relays.JsonFilter import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.relays.TypedFilter import com.vitorpamplona.amethyst.service.relays.TypedFilter
@@ -42,8 +43,14 @@ object NostrSingleUserDataSource : NostrDataSource("SingleUserFeed") {
val userChannel = requestNewChannel() { time, relayUrl -> val userChannel = requestNewChannel() { time, relayUrl ->
usersToWatch.forEach { 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. // Many relays operate with limits in the amount of filters.
// As information comes, the filters will be rotated to get more data. // As information comes, the filters will be rotated to get more data.
invalidateFilters() invalidateFilters()
@@ -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<String, EOSETime> = 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<User, EOSERelayList> = 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)
}
}
}
@@ -4,14 +4,13 @@ import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonObject import com.google.gson.JsonObject
import java.util.*
class JsonFilter( class JsonFilter(
val ids: List<String>? = null, val ids: List<String>? = null,
val authors: List<String>? = null, val authors: List<String>? = null,
val kinds: List<Int>? = null, val kinds: List<Int>? = null,
val tags: Map<String, List<String>>? = null, val tags: Map<String, List<String>>? = null,
val since: Map<String, Long>? = null, val since: Map<String, EOSETime>? = null,
val until: Long? = null, val until: Long? = null,
val limit: Int? = null, val limit: Int? = null,
val search: String? = null val search: String? = null
@@ -37,7 +36,7 @@ class JsonFilter(
if (forRelay != null) { if (forRelay != null) {
val relaySince = get(forRelay) val relaySince = get(forRelay)
if (relaySince != null) { if (relaySince != null) {
jsonObject.addProperty("since", relaySince) jsonObject.addProperty("since", relaySince.time)
} }
} else { } else {
val jsonObjectSince = JsonObject() val jsonObjectSince = JsonObject()
@@ -40,6 +40,8 @@ class Relay(
var closingTime = 0L var closingTime = 0L
var afterEOSE = false
fun register(listener: Listener) { fun register(listener: Listener) {
listeners = listeners.plus(listener) listeners = listeners.plus(listener)
} }
@@ -74,6 +76,7 @@ class Relay(
val listener = object : WebSocketListener() { val listener = object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) { override fun onOpen(webSocket: WebSocket, response: Response) {
afterEOSE = false
isReady = true isReady = true
ping = response.receivedResponseAtMillis - response.sentRequestAtMillis ping = response.receivedResponseAtMillis - response.sentRequestAtMillis
// Log.w("Relay", "Relay OnOpen, Loading All subscriptions $url") // 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 msg = Event.gson.fromJson(text, JsonElement::class.java).asJsonArray
val type = msg[0].asString val type = msg[0].asString
val channel = msg[1].asString val channel = msg[1].asString
when (type) { when (type) {
"EVENT" -> { "EVENT" -> {
// Log.w("Relay", "Relay onEVENT $url, $channel") // 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 { "EOSE" -> listeners.forEach {
afterEOSE = true
// Log.w("Relay", "Relay onEOSE $url, $channel") // Log.w("Relay", "Relay onEOSE $url, $channel")
it.onRelayStateChange(this@Relay, Type.EOSE, channel) it.onRelayStateChange(this@Relay, Type.EOSE, channel)
} }
@@ -136,6 +146,7 @@ class Relay(
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
socket = null socket = null
isReady = false isReady = false
afterEOSE = false
closingTime = Date().time / 1000 closingTime = Date().time / 1000
listeners.forEach { it.onRelayStateChange(this@Relay, Type.DISCONNECT, null) } listeners.forEach { it.onRelayStateChange(this@Relay, Type.DISCONNECT, null) }
} }
@@ -147,6 +158,7 @@ class Relay(
// Failures disconnect the relay. // Failures disconnect the relay.
socket = null socket = null
isReady = false isReady = false
afterEOSE = false
closingTime = Date().time / 1000 closingTime = Date().time / 1000
Log.w("Relay", "Relay onFailure $url, ${response?.message} $response") Log.w("Relay", "Relay onFailure $url, ${response?.message} $response")
@@ -161,6 +173,7 @@ class Relay(
} catch (e: Exception) { } catch (e: Exception) {
errorCounter++ errorCounter++
isReady = false isReady = false
afterEOSE = false
closingTime = Date().time / 1000 closingTime = Date().time / 1000
Log.e("Relay", "Relay Invalid $url") Log.e("Relay", "Relay Invalid $url")
e.printStackTrace() e.printStackTrace()
@@ -173,6 +186,7 @@ class Relay(
socket?.close(1000, "Normal close") socket?.close(1000, "Normal close")
socket = null socket = null
isReady = false isReady = false
afterEOSE = false
} }
fun sendFilter(requestId: String) { fun sendFilter(requestId: String) {
@@ -186,6 +200,7 @@ class Relay(
// println("FILTERSSENT $url $request") // println("FILTERSSENT $url $request")
socket?.send(request) socket?.send(request)
eventUploadCounterInBytes += request.bytesUsedInMemory() eventUploadCounterInBytes += request.bytesUsedInMemory()
afterEOSE = false
} }
} }
} else { } else {
@@ -138,5 +138,5 @@ object ImageSaver {
MediaScannerConnection.scanFile(context, arrayOf(outputFile.toString()), null, null) MediaScannerConnection.scanFile(context, arrayOf(outputFile.toString()), null, null)
} }
private const val PICTURES_SUBDIRECTORY = "Amethyst " + BuildConfig.VERSION_NAME private const val PICTURES_SUBDIRECTORY = "Amethyst"
} }
@@ -167,7 +167,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
if (isValidURL(myUrlPreview)) { if (isValidURL(myUrlPreview)) {
val removedParamsFromUrl = val removedParamsFromUrl =
myUrlPreview.split("?")[0].lowercase() myUrlPreview.split("?")[0].lowercase()
if (imageExtension.matcher(removedParamsFromUrl).matches()) { if (imageExtensions.any { removedParamsFromUrl.endsWith(it, true) }) {
AsyncImage( AsyncImage(
model = myUrlPreview, model = myUrlPreview,
contentDescription = myUrlPreview, contentDescription = myUrlPreview,
@@ -182,9 +182,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
RoundedCornerShape(15.dp) RoundedCornerShape(15.dp)
) )
) )
} else if (videoExtension.matcher(removedParamsFromUrl) } else if (videoExtensions.any { removedParamsFromUrl.endsWith(it, true) }) {
.matches()
) {
VideoView(myUrlPreview) VideoView(myUrlPreview)
} else { } else {
UrlPreview(myUrlPreview, myUrlPreview) UrlPreview(myUrlPreview, myUrlPreview)
@@ -46,8 +46,8 @@ import java.net.URISyntaxException
import java.net.URL import java.net.URL
import java.util.regex.Pattern import java.util.regex.Pattern
val imageExtension = Pattern.compile("(.*/)*.+\\.(png|jpg|gif|bmp|jpeg|webp|svg)$", Pattern.CASE_INSENSITIVE) val imageExtensions = listOf("png", "jpg", "gif", "bmp", "jpeg", "webp", "svg")
val videoExtension = Pattern.compile("(.*/)*.+\\.(mp4|avi|wmv|mpg|amv|webm|mov)$", Pattern.CASE_INSENSITIVE) val videoExtensions = listOf("mp4", "avi", "wmv", "mpg", "amv", "webm", "mov")
// Group 1 = url, group 4 additional chars // Group 1 = url, group 4 additional chars
val noProtocolUrlValidator = Pattern.compile("(([\\w\\d-]+\\.)*[a-zA-Z][\\w-]+[\\.\\:]\\w+([\\/\\?\\=\\&\\#\\.]?[\\w-]+)*\\/?)(.*)") 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 // sequence of images will render in a slideview
if (isValidURL(word)) { if (isValidURL(word)) {
val removedParamsFromUrl = word.split("?")[0].lowercase() val removedParamsFromUrl = word.split("?")[0].lowercase()
if (imageExtension.matcher(removedParamsFromUrl).matches()) { if (imageExtensions.any { word.endsWith(it, true) }) {
imagesForPager.add(word) imagesForPager.add(word)
} }
if (videoExtension.matcher(removedParamsFromUrl).matches()) { if (videoExtensions.any { word.endsWith(it, true) }) {
imagesForPager.add(word) imagesForPager.add(word)
} }
} }
@@ -160,9 +160,9 @@ fun RichTextViewer(
if (isValidURL(word)) { if (isValidURL(word)) {
val removedParamsFromUrl = word.split("?")[0].lowercase() val removedParamsFromUrl = word.split("?")[0].lowercase()
if (imageExtension.matcher(removedParamsFromUrl).matches()) { if (imageExtensions.any { word.endsWith(it, true) }) {
ZoomableImageView(word, imagesForPager) ZoomableImageView(word, imagesForPager)
} else if (videoExtension.matcher(removedParamsFromUrl).matches()) { } else if (videoExtensions.any { word.endsWith(it, true) }) {
ZoomableImageView(word, imagesForPager) ZoomableImageView(word, imagesForPager)
} else { } else {
UrlPreview(word, "$word ") UrlPreview(word, "$word ")
@@ -62,7 +62,7 @@ fun ZoomableImageView(word: String, images: List<String> = listOf(word)) {
mutableStateOf<AsyncImagePainter.State?>(null) mutableStateOf<AsyncImagePainter.State?>(null)
} }
if (imageExtension.matcher(word).matches()) { if (imageExtensions.any { word.endsWith(it, true) }) {
AsyncImage( AsyncImage(
model = word, model = word,
contentDescription = word, contentDescription = word,
@@ -171,7 +171,7 @@ fun ZoomableImageDialog(imageUrl: String, allImages: List<String> = listOf(image
@Composable @Composable
private fun RenderImageOrVideo(imageUrl: String) { private fun RenderImageOrVideo(imageUrl: String) {
if (imageExtension.matcher(imageUrl).matches()) { if (imageExtensions.any { imageUrl.endsWith(it, true) }) {
AsyncImage( AsyncImage(
model = imageUrl, model = imageUrl,
contentDescription = stringResource(id = R.string.profile_image), contentDescription = stringResource(id = R.string.profile_image),
@@ -23,7 +23,9 @@ object NotificationFeedFilter : FeedFilter<Note>() {
} }
.filter { it -> .filter { it ->
it.event !is TextNoteEvent || 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() loggedInUser in it.directlyCiteUsers()
} }
.filter { it -> .filter { it ->