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 edc4dc3b8..1e343e8e1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.model +import android.util.LruCache import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.lifecycle.LiveData @@ -41,7 +42,6 @@ import com.vitorpamplona.quartz.events.RepostEvent import com.vitorpamplona.quartz.events.WrappedEvent import com.vitorpamplona.quartz.signers.NostrSigner import com.vitorpamplona.quartz.utils.TimeUtils -import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import java.math.BigDecimal @@ -93,7 +93,7 @@ open class Note(val idHex: String) { var zapPayments = mapOf() private set - var relays = listOf() + var relays = listOf() private set var lastReactionsDownloadTime: Map = emptyMap() @@ -110,13 +110,13 @@ open class Note(val idHex: String) { host.id, host.pubKey, host.kind(), - relays.firstOrNull() + relays.firstOrNull()?.url ) } else { - Nip19.createNEvent(idHex, author?.pubkeyHex, event?.kind(), relays.firstOrNull()) + Nip19.createNEvent(idHex, author?.pubkeyHex, event?.kind(), relays.firstOrNull()?.url) } } else { - Nip19.createNEvent(idHex, author?.pubkeyHex, event?.kind(), relays.firstOrNull()) + Nip19.createNEvent(idHex, author?.pubkeyHex, event?.kind(), relays.firstOrNull()?.url) } } @@ -271,7 +271,7 @@ open class Note(val idHex: String) { zaps = mapOf() zapPayments = mapOf() zapsAmount = BigDecimal.ZERO - relays = listOf() + relays = listOf() lastReactionsDownloadTime = emptyMap() liveSet?.innerReplies?.invalidateData() @@ -428,15 +428,15 @@ open class Note(val idHex: String) { } @Synchronized - fun addRelaySync(url: String) { - if (url !in relays) { - relays = relays + url + fun addRelaySync(briefInfo: RelayBriefInfoCache.RelayBriefInfo) { + if (briefInfo !in relays) { + relays = relays + briefInfo } } fun addRelay(relay: Relay) { - if (relay.url !in relays) { - addRelaySync(relay.url) + if (relay.brief !in relays) { + addRelaySync(relay.brief) liveSet?.innerRelays?.invalidateData() } } @@ -870,14 +870,8 @@ class NoteLiveSet(u: Note) { it.note.boosts.size }.distinctUntilChanged() - val boostList = innerBoosts.map { - it.note.boosts.toImmutableList() - }.distinctUntilChanged() - val relayInfo = innerRelays.map { - it.note.relays.map { - RelayBriefInfo(it) - }.toImmutableList() + it.note.relays } val content = innerMetadata.map { @@ -897,8 +891,7 @@ class NoteLiveSet(u: Note) { hasReactions.hasObservers() || replyCount.hasObservers() || reactionCount.hasObservers() || - boostCount.hasObservers() || - boostList.hasObservers() + boostCount.hasObservers() } fun destroy() { @@ -986,9 +979,22 @@ class NoteLoadingLiveData(val note: Note, initialValue: Y?) : MediatorLiveDat @Immutable class NoteState(val note: Note) -@Immutable -data class RelayBriefInfo( - val url: String, - val displayUrl: String = url.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/").intern(), - val favIcon: String = "https://$displayUrl/favicon.ico".intern() -) +object RelayBriefInfoCache { + val cache = LruCache(50) + + @Immutable + data class RelayBriefInfo( + val url: String, + val displayUrl: String = url.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/").intern(), + val favIcon: String = "https://$displayUrl/favicon.ico".intern() + ) + + fun get(url: String): RelayBriefInfo { + val info = cache[url] + if (info != null) return info + + val newInfo = RelayBriefInfo(url) + cache.put(url, newInfo) + return newInfo + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/RelaySetupInfo.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/RelaySetupInfo.kt index 18782e29f..79f36917d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/RelaySetupInfo.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/RelaySetupInfo.kt @@ -15,5 +15,5 @@ data class RelaySetupInfo( val feedTypes: Set, val paidRelay: Boolean = false ) { - val briefInfo: RelayBriefInfo = RelayBriefInfo(url) + val briefInfo: RelayBriefInfoCache.RelayBriefInfo = RelayBriefInfoCache.RelayBriefInfo(url) } 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 d0fbcc5df..127c1d2e4 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 @@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.service.relays import android.util.Log import com.vitorpamplona.amethyst.BuildConfig +import com.vitorpamplona.amethyst.model.RelayBriefInfoCache import com.vitorpamplona.amethyst.service.HttpClient import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.quartz.encoders.HexKey @@ -23,11 +24,13 @@ enum class FeedType { val COMMON_FEED_TYPES = setOf(FeedType.FOLLOWS, FeedType.PUBLIC_CHATS, FeedType.PRIVATE_DMS, FeedType.GLOBAL) class Relay( - var url: String, - var read: Boolean = true, - var write: Boolean = true, - var activeTypes: Set = FeedType.values().toSet() + val url: String, + val read: Boolean = true, + val write: Boolean = true, + val activeTypes: Set = FeedType.values().toSet() ) { + val brief = RelayBriefInfoCache.get(url) + companion object { // waits 3 minutes to reconnect once things fail const val RECONNECTING_IN_SECONDS = 60 * 3 diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListView.kt index 4549b862b..ade53fb70 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListView.kt @@ -57,7 +57,7 @@ import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.model.RelayBriefInfo +import com.vitorpamplona.amethyst.model.RelayBriefInfoCache import com.vitorpamplona.amethyst.model.RelaySetupInfo import com.vitorpamplona.amethyst.service.Nip11Retriever import com.vitorpamplona.amethyst.service.relays.Constants @@ -329,7 +329,7 @@ fun ServerConfig( accountViewModel.retrieveRelayDocument( item.url, onInfo = { - relayInfo = RelayInfoDialog(RelayBriefInfo(item.url), it) + relayInfo = RelayInfoDialog(RelayBriefInfoCache.RelayBriefInfo(item.url), it) }, onError = { url, errorCode, exceptionMessage -> val msg = when (errorCode) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt index b5519ce5f..36cb94192 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt @@ -26,7 +26,7 @@ import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.model.RelayBriefInfo +import com.vitorpamplona.amethyst.model.RelayBriefInfoCache import com.vitorpamplona.amethyst.model.RelayInformation import com.vitorpamplona.amethyst.ui.components.ClickableEmail import com.vitorpamplona.amethyst.ui.components.ClickableUrl @@ -43,7 +43,7 @@ import com.vitorpamplona.amethyst.ui.theme.StdPadding @Composable fun RelayInformationDialog( onClose: () -> Unit, - relayBriefInfo: RelayBriefInfo, + relayBriefInfo: RelayBriefInfoCache.RelayBriefInfo, relayInfo: RelayInformation, accountViewModel: AccountViewModel, nav: (String) -> Unit diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt index 032779358..9d4a8e80b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt @@ -26,7 +26,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.model.RelayBriefInfo +import com.vitorpamplona.amethyst.model.RelayBriefInfoCache import com.vitorpamplona.amethyst.model.RelayInformation import com.vitorpamplona.amethyst.service.Nip11Retriever import com.vitorpamplona.amethyst.service.relays.Relay @@ -37,12 +37,12 @@ import kotlinx.collections.immutable.toImmutableList data class RelayList( val relay: Relay, - val relayInfo: RelayBriefInfo, + val relayInfo: RelayBriefInfoCache.RelayBriefInfo, val isSelected: Boolean ) data class RelayInfoDialog( - val relayBriefInfo: RelayBriefInfo, + val relayBriefInfo: RelayBriefInfoCache.RelayBriefInfo, val relayInfo: RelayInformation ) @@ -61,7 +61,7 @@ fun RelaySelectionDialog( accountViewModel.account.activeWriteRelays().map { RelayList( relay = it, - relayInfo = RelayBriefInfo(it.url), + relayInfo = RelayBriefInfoCache.RelayBriefInfo(it.url), isSelected = preSelectedList.any { relay -> it.url == relay.url } ) } @@ -167,7 +167,12 @@ fun RelaySelectionDialog( accountViewModel.retrieveRelayDocument( item.relay.url, onInfo = { - relayInfo = RelayInfoDialog(RelayBriefInfo(item.relay.url), it) + relayInfo = RelayInfoDialog( + RelayBriefInfoCache.RelayBriefInfo( + item.relay.url + ), + it + ) }, onError = { url, errorCode, exceptionMessage -> val msg = when (errorCode) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt index c1f68fbe0..c6f44569f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt @@ -56,7 +56,7 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter() .asSequence() .filter { it -> val noteEvent = it.event - val isGlobalRelay = it.relays.any { gRelays.contains(it) } + val isGlobalRelay = it.relays.any { gRelays.contains(it.url) } (noteEvent is TextNoteEvent || noteEvent is ClassifiedsEvent || noteEvent is RepostEvent || noteEvent is GenericRepostEvent || noteEvent is LongTextNoteEvent || noteEvent is PollNoteEvent || noteEvent is HighlightEvent || noteEvent is AudioTrackEvent || noteEvent is AudioHeaderEvent) && (!ignoreAddressables || noteEvent.kind() < 10000) && ((isGlobal && isGlobalRelay) || it.author?.pubkeyHex in followingKeySet || noteEvent.isTaggedHashes(followingTagSet) || noteEvent.isTaggedGeoHashes(followingGeohashSet) || noteEvent.isTaggedAddressableNotes(followingCommunities)) && diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt index d434777f0..ccb07e7ac 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt @@ -44,6 +44,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.NoteState import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.components.ImageUrlType import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer @@ -303,6 +304,31 @@ fun RenderBoostGallery( } } +@Composable +fun RenderBoostGallery( + noteToGetBoostEvents: NoteState, + nav: (String) -> Unit, + accountViewModel: AccountViewModel +) { + Row( + modifier = Modifier.fillMaxWidth() + ) { + Box( + modifier = NotificationIconModifierSmaller + ) { + RepostedIcon( + modifier = remember { + Modifier + .size(Size19dp) + .align(Alignment.TopEnd) + } + ) + } + + AuthorGallery(noteToGetBoostEvents, nav, accountViewModel) + } +} + @Composable fun MapZaps( zaps: ImmutableList, @@ -491,6 +517,22 @@ fun AuthorGallery( } } +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun AuthorGallery( + noteToGetBoostEvents: NoteState, + nav: (String) -> Unit, + accountViewModel: AccountViewModel +) { + Column(modifier = StdStartPadding) { + FlowRow() { + noteToGetBoostEvents.note.boosts.forEach { note -> + BoxedAuthor(note, nav, accountViewModel) + } + } + } +} + @Composable private fun BoxedAuthor( note: Note, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index f5e8efc77..30c8eef1e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -88,7 +88,7 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.Channel import com.vitorpamplona.amethyst.model.Note -import com.vitorpamplona.amethyst.model.RelayBriefInfo +import com.vitorpamplona.amethyst.model.RelayBriefInfoCache import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil import com.vitorpamplona.amethyst.ui.actions.NewRelayListView @@ -1746,7 +1746,7 @@ fun DisplayRelaySet( val relays by remember(baseNote) { mutableStateOf( - noteEvent.relays().map { RelayBriefInfo(it) }.toImmutableList() + noteEvent.relays().map { RelayBriefInfoCache.RelayBriefInfo(it) }.toImmutableList() ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index 0751acbb9..19bf2c423 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -406,10 +406,10 @@ private fun WatchBoostsAndRenderGallery( nav: (String) -> Unit, accountViewModel: AccountViewModel ) { - val boostsEvents by baseNote.live().boostList.observeAsState() + val boostsEvents by baseNote.live().boosts.observeAsState() boostsEvents?.let { - if (it.isNotEmpty()) { + if (it.note.boosts.isNotEmpty()) { RenderBoostGallery( it, nav, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListBox.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListBox.kt index 79a8e2cf2..eeebf309f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListBox.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListBox.kt @@ -11,8 +11,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf @@ -20,17 +18,15 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import com.vitorpamplona.amethyst.model.Note -import com.vitorpamplona.amethyst.model.RelayBriefInfo import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonBoxModifer import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonIconButtonModifier import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonIconModifier import com.vitorpamplona.amethyst.ui.theme.placeholderText -import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList +@OptIn(ExperimentalLayoutApi::class) @Composable fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) { var expanded by remember { mutableStateOf(false) } @@ -39,16 +35,23 @@ fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String Spacer(DoubleVertSpacer) - if (expanded) { - VerticalRelayPanelWithFlow(relayList, accountViewModel, nav) - } else { - val shortRelayList by remember { - derivedStateOf { - relayList.take(3).toImmutableList() + // FlowRow Seems to be a lot faster than LazyVerticalGrid + FlowRow() { + if (expanded) { + relayList?.forEach { + RenderRelay(it, accountViewModel, nav) + } + } else { + relayList?.getOrNull(0)?.let { + RenderRelay(it, accountViewModel, nav) + } + relayList?.getOrNull(1)?.let { + RenderRelay(it, accountViewModel, nav) + } + relayList?.getOrNull(2)?.let { + RenderRelay(it, accountViewModel, nav) } } - - VerticalRelayPanelWithFlow(shortRelayList, accountViewModel, nav) } if (relayList.size > 3 && !expanded) { @@ -58,22 +61,6 @@ fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String } } -@OptIn(ExperimentalLayoutApi::class) -@Composable -@Stable -private fun VerticalRelayPanelWithFlow( - relays: ImmutableList, - accountViewModel: AccountViewModel, - nav: (String) -> Unit -) { - // FlowRow Seems to be a lot faster than LazyVerticalGrid - FlowRow() { - relays.forEach { url -> - RenderRelay(url, accountViewModel, nav) - } - } -} - @Composable private fun ShowMoreRelaysButton(onClick: () -> Unit) { Row( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt index 51919dc91..4c4945d3e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt @@ -34,7 +34,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.map import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note -import com.vitorpamplona.amethyst.model.RelayBriefInfo +import com.vitorpamplona.amethyst.model.RelayBriefInfoCache import com.vitorpamplona.amethyst.model.RelayInformation import com.vitorpamplona.amethyst.service.Nip11Retriever import com.vitorpamplona.amethyst.ui.actions.RelayInformationDialog @@ -46,7 +46,6 @@ import com.vitorpamplona.amethyst.ui.theme.Size15Modifier import com.vitorpamplona.amethyst.ui.theme.Size15dp import com.vitorpamplona.amethyst.ui.theme.StdStartPadding import com.vitorpamplona.amethyst.ui.theme.placeholderText -import kotlinx.collections.immutable.toImmutableList @Composable public fun RelayBadgesHorizontal(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) { @@ -65,11 +64,20 @@ fun RenderRelayList(baseNote: Note, expanded: MutableState, accountView val noteRelays by baseNote.live().relayInfo.observeAsState() FlowRow(StdStartPadding, verticalArrangement = Arrangement.Center) { - val relaysToDisplay = remember(noteRelays, expanded.value) { - if (expanded.value) noteRelays else noteRelays?.take(3)?.toImmutableList() - } - relaysToDisplay?.forEach { - RenderRelay(it, accountViewModel, nav) + if (expanded.value) { + noteRelays?.forEach { + RenderRelay(it, accountViewModel, nav) + } + } else { + noteRelays?.getOrNull(0)?.let { + RenderRelay(it, accountViewModel, nav) + } + noteRelays?.getOrNull(1)?.let { + RenderRelay(it, accountViewModel, nav) + } + noteRelays?.getOrNull(2)?.let { + RenderRelay(it, accountViewModel, nav) + } } } } @@ -105,7 +113,7 @@ fun ChatRelayExpandButton(onClick: () -> Unit) { } @Composable -fun RenderRelay(relay: RelayBriefInfo, accountViewModel: AccountViewModel, nav: (String) -> Unit) { +fun RenderRelay(relay: RelayBriefInfoCache.RelayBriefInfo, accountViewModel: AccountViewModel, nav: (String) -> Unit) { var relayInfo: RelayInformation? by remember { mutableStateOf(null) } if (relayInfo != null) {