Memory improvements on relay lists and LiveData objects

This commit is contained in:
Vitor Pamplona 2023-12-13 18:51:30 -05:00
parent d4f060d509
commit c7be0e4a95
12 changed files with 133 additions and 82 deletions

View File

@ -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<Note, Note?>()
private set
var relays = listOf<String>()
var relays = listOf<RelayBriefInfoCache.RelayBriefInfo>()
private set
var lastReactionsDownloadTime: Map<String, EOSETime> = 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<Note, Note?>()
zapPayments = mapOf<Note, Note?>()
zapsAmount = BigDecimal.ZERO
relays = listOf<String>()
relays = listOf<RelayBriefInfoCache.RelayBriefInfo>()
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<Y>(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<String, RelayBriefInfo?>(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
}
}

View File

@ -15,5 +15,5 @@ data class RelaySetupInfo(
val feedTypes: Set<FeedType>,
val paidRelay: Boolean = false
) {
val briefInfo: RelayBriefInfo = RelayBriefInfo(url)
val briefInfo: RelayBriefInfoCache.RelayBriefInfo = RelayBriefInfoCache.RelayBriefInfo(url)
}

View File

@ -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> = FeedType.values().toSet()
val url: String,
val read: Boolean = true,
val write: Boolean = true,
val activeTypes: Set<FeedType> = 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

View File

@ -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) {

View File

@ -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

View File

@ -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) {

View File

@ -56,7 +56,7 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
.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)) &&

View File

@ -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<CombinedZap>,
@ -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,

View File

@ -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()
)
}

View File

@ -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,

View File

@ -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<RelayBriefInfo>,
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(

View File

@ -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<Boolean>, 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) {