From 76faf7ae35e1c1957ceb18dddf3a686a910b5aee Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 24 Aug 2023 12:53:07 -0400 Subject: [PATCH] Moving livedata creation to long-lived objects --- .../com/vitorpamplona/amethyst/model/Note.kt | 25 ++++++++++++++++ .../com/vitorpamplona/amethyst/model/User.kt | 11 +++++++ .../amethyst/ui/components/RichTextViewer.kt | 14 ++------- .../amethyst/ui/navigation/DrawerContent.kt | 3 +- .../amethyst/ui/note/ChannelCardCompose.kt | 30 +++++++++---------- .../amethyst/ui/note/ChatroomHeaderCompose.kt | 10 +++---- .../ui/note/ChatroomMessageCompose.kt | 26 ++++++++-------- .../amethyst/ui/note/MultiSetCompose.kt | 10 ++----- .../amethyst/ui/note/NoteCompose.kt | 30 +++++++++---------- .../amethyst/ui/note/ReactionsRow.kt | 17 ++--------- .../amethyst/ui/note/UserReactionsRow.kt | 9 +++--- .../amethyst/ui/screen/RelayPoolViewModel.kt | 2 +- 12 files changed, 95 insertions(+), 92 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 e9237ab6e..3a4ce8925 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,8 @@ package com.vitorpamplona.amethyst.model import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.lifecycle.LiveData +import androidx.lifecycle.distinctUntilChanged +import androidx.lifecycle.map import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.firstFullCharOrEmoji @@ -10,6 +12,7 @@ import com.vitorpamplona.amethyst.service.relays.EOSETime import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.ui.actions.updated import com.vitorpamplona.amethyst.ui.components.BundledUpdate +import com.vitorpamplona.amethyst.ui.note.combineWith import com.vitorpamplona.amethyst.ui.note.toShortenHex import com.vitorpamplona.quartz.encoders.ATag import com.vitorpamplona.quartz.encoders.Hex @@ -651,10 +654,18 @@ open class Note(val idHex: String) { } } +@Stable class NoteLiveSet(u: Note) { // Observers line up here. val metadata: NoteLiveData = NoteLiveData(u) + val authorChanges = metadata.map { + it.note.author + } + val hasEvent = metadata.map { + it.note.event != null + }.distinctUntilChanged() + val reactions: NoteLiveData = NoteLiveData(u) val boosts: NoteLiveData = NoteLiveData(u) val replies: NoteLiveData = NoteLiveData(u) @@ -662,6 +673,20 @@ class NoteLiveSet(u: Note) { val relays: NoteLiveData = NoteLiveData(u) val zaps: NoteLiveData = NoteLiveData(u) + val hasReactions = zaps.combineWith(boosts, reactions) { zapState, boostState, reactionState -> + zapState?.note?.zaps?.isNotEmpty() ?: false || + boostState?.note?.boosts?.isNotEmpty() ?: false || + reactionState?.note?.reactions?.isNotEmpty() ?: false + }.distinctUntilChanged() + + val replyCount = replies.map { + it.note.replies.size + }.distinctUntilChanged() + + val boostCount = boosts.map { + it.note.boosts.size + }.distinctUntilChanged() + fun isInUse(): Boolean { return metadata.hasObservers() || reactions.hasObservers() || 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 f2ee77c99..67d89769a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -3,6 +3,8 @@ package com.vitorpamplona.amethyst.model import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.lifecycle.LiveData +import androidx.lifecycle.distinctUntilChanged +import androidx.lifecycle.map import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.relays.EOSETime @@ -378,6 +380,7 @@ class User(val pubkeyHex: String) { } } +@Stable class UserLiveSet(u: User) { // UI Observers line up here. val follows: UserLiveData = UserLiveData(u) @@ -390,6 +393,14 @@ class UserLiveSet(u: User) { val zaps: UserLiveData = UserLiveData(u) val bookmarks: UserLiveData = UserLiveData(u) + val profilePictureChanges = metadata.map { + it.user.profilePicture() + }.distinctUntilChanged() + + val userMetadataInfo = metadata.map { + it.user.info + }.distinctUntilChanged() + fun isInUse(): Boolean { return follows.hasObservers() || followers.hasObservers() || 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 95450735e..df9a345e7 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 @@ -30,8 +30,6 @@ import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.em -import androidx.lifecycle.distinctUntilChanged -import androidx.lifecycle.map import com.halilibo.richtext.markdown.Markdown import com.halilibo.richtext.markdown.MarkdownParseOptions import com.halilibo.richtext.ui.material.MaterialRichText @@ -421,9 +419,7 @@ private fun ObserveNIP19Event( @Composable fun ObserveNote(note: Note, onRefresh: () -> Unit) { - val loadedNoteId by note.live().metadata.map { - it.note.event?.id() - }.distinctUntilChanged().observeAsState(note.event?.id()) + val loadedNoteId by note.live().metadata.observeAsState() LaunchedEffect(key1 = loadedNoteId) { if (loadedNoteId != null) { @@ -460,9 +456,7 @@ private fun ObserveNIP19User( @Composable private fun ObserveUser(user: User, onRefresh: () -> Unit) { - val loadedUserMetaId by user.live().metadata.map { - it.user.info?.latestMetadata?.id - }.distinctUntilChanged().observeAsState(user.info?.latestMetadata?.id) + val loadedUserMetaId by user.live().metadata.observeAsState() LaunchedEffect(key1 = loadedUserMetaId) { if (loadedUserMetaId != null) { @@ -854,9 +848,7 @@ private fun DisplayUserFromTag( val route = remember { "User/${baseUser.pubkeyHex}" } val hex = remember { baseUser.pubkeyDisplayHex() } - val meta by baseUser.live().metadata.map { - it.user.info - }.distinctUntilChanged().observeAsState(baseUser.info) + val meta by baseUser.live().userMetadataInfo.observeAsState(baseUser.info) Crossfade(targetState = meta) { Row() { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt index 184092bab..973fad4f5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt @@ -52,7 +52,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.lifecycle.distinctUntilChanged import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage import com.vitorpamplona.amethyst.BuildConfig @@ -484,7 +483,7 @@ private fun RelayStatus( relayViewModel: RelayPoolViewModel ) { val connectedRelaysText by relayViewModel.connectionStatus.observeAsState("--/--") - val isConnected by relayViewModel.isConnected.distinctUntilChanged().observeAsState(false) + val isConnected by relayViewModel.isConnected.observeAsState(false) RenderRelayStatus(connectedRelaysText, isConnected) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt index c6aef6fad..5df050bf0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt @@ -93,24 +93,10 @@ fun ChannelCardCompose( accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - val isBlank by baseNote.live().metadata.map { - it.note.event == null - }.distinctUntilChanged().observeAsState(baseNote.event == null) + val hasEvent by baseNote.live().hasEvent.observeAsState(baseNote.event != null) - Crossfade(targetState = isBlank) { + Crossfade(targetState = hasEvent) { if (it) { - LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> - BlankNote( - remember { - modifier.combinedClickable( - onClick = { }, - onLongClick = showPopup - ) - }, - false - ) - } - } else { if (forceEventKind == null || baseNote.event?.kind() == forceEventKind) { CheckHiddenChannelCardCompose( baseNote, @@ -122,6 +108,18 @@ fun ChannelCardCompose( nav ) } + } else { + LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> + BlankNote( + remember { + modifier.combinedClickable( + onClick = { }, + onLongClick = showPopup + ) + }, + false + ) + } } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomHeaderCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomHeaderCompose.kt index 2979f033b..f0a2c050f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomHeaderCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomHeaderCompose.kt @@ -77,14 +77,12 @@ fun ChatroomHeaderCompose( accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - val isBlank by baseNote.live().metadata.map { - it.note.event == null - }.observeAsState(baseNote.event == null) + val hasEvent by baseNote.live().hasEvent.observeAsState(baseNote.event != null) - if (isBlank) { - BlankNote(Modifier) - } else { + if (hasEvent) { ChatroomComposeChannelOrUser(baseNote, accountViewModel, nav) + } else { + BlankNote(Modifier) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt index 0a3fcd919..ab7081152 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt @@ -85,12 +85,20 @@ fun ChatroomMessageCompose( nav: (String) -> Unit, onWantsToReply: (Note) -> Unit ) { - val isBlank by baseNote.live().metadata.map { - it.note.event == null - }.observeAsState(baseNote.event == null) + val hasEvent by baseNote.live().hasEvent.observeAsState(baseNote.event != null) - Crossfade(targetState = isBlank) { + Crossfade(targetState = hasEvent) { if (it) { + CheckHiddenChatMessage( + baseNote, + routeForLastRead, + innerQuote, + parentBackgroundColor, + accountViewModel, + nav, + onWantsToReply + ) + } else { LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> BlankNote( remember { @@ -101,16 +109,6 @@ fun ChatroomMessageCompose( } ) } - } else { - CheckHiddenChatMessage( - baseNote, - routeForLastRead, - innerQuote, - parentBackgroundColor, - accountViewModel, - nav, - onWantsToReply - ) } } } 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 05c679f99..618e052bf 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 @@ -41,8 +41,6 @@ import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.lifecycle.distinctUntilChanged -import androidx.lifecycle.map import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note @@ -579,9 +577,7 @@ private fun WatchNoteAuthor( baseNote: Note, onContent: @Composable (User?) -> Unit ) { - val author by baseNote.live().metadata.map { - it.note.author - }.observeAsState(baseNote.author) + val author by baseNote.live().authorChanges.observeAsState(baseNote.author) onContent(author) } @@ -591,9 +587,7 @@ private fun WatchUserMetadata( author: User, onNewMetadata: @Composable (String?) -> Unit ) { - val userProfile by author.live().metadata.map { - it.user.profilePicture() - }.distinctUntilChanged().observeAsState(author.profilePicture()) + val userProfile by author.live().profilePictureChanges.observeAsState(author.profilePicture()) onNewMetadata(userProfile) } 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 4138bca19..b5b118725 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 @@ -223,24 +223,10 @@ fun NoteCompose( accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - val isBlank by baseNote.live().metadata.map { - it.note.event == null - }.distinctUntilChanged().observeAsState(baseNote.event == null) + val hasEvent by baseNote.live().hasEvent.observeAsState(baseNote.event != null) - Crossfade(targetState = isBlank) { + Crossfade(targetState = hasEvent) { if (it) { - LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> - BlankNote( - remember { - modifier.combinedClickable( - onClick = { }, - onLongClick = showPopup - ) - }, - isBoostedNote || isQuotedNote - ) - } - } else { CheckHiddenNoteCompose( note = baseNote, routeForLastRead = routeForLastRead, @@ -255,6 +241,18 @@ fun NoteCompose( accountViewModel = accountViewModel, nav = nav ) + } else { + LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> + BlankNote( + remember { + modifier.combinedClickable( + onClick = { }, + onLongClick = showPopup + ) + }, + isBoostedNote || isQuotedNote + ) + } } } } 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 6d1cad664..8a2426021 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 @@ -414,14 +414,7 @@ private fun ReactionDetailGallery( val defaultBackgroundColor = MaterialTheme.colors.background val backgroundColor = remember { mutableStateOf(defaultBackgroundColor) } - val hasReactions by baseNote.live().zaps.combineWith( - baseNote.live().boosts, - baseNote.live().reactions - ) { zapState, boostState, reactionState -> - zapState?.note?.zaps?.isNotEmpty() ?: false || - boostState?.note?.boosts?.isNotEmpty() ?: false || - reactionState?.note?.reactions?.isNotEmpty() ?: false - }.distinctUntilChanged().observeAsState( + val hasReactions by baseNote.live().hasReactions.observeAsState( baseNote.zaps.isNotEmpty() || baseNote.boosts.isNotEmpty() || baseNote.reactions.isNotEmpty() ) @@ -594,9 +587,7 @@ fun ReplyReaction( @Composable fun ReplyCounter(baseNote: Note, textColor: Color) { - val repliesState by baseNote.live().replies.map { - it.note.replies.size - }.observeAsState(baseNote.replies.size) + val repliesState by baseNote.live().replyCount.observeAsState(baseNote.replies.size) SlidingAnimationCount(repliesState, textColor) } @@ -731,9 +722,7 @@ fun BoostIcon(baseNote: Note, iconSize: Dp = Size20dp, grayTint: Color, accountV @Composable fun BoostText(baseNote: Note, grayTint: Color) { - val boostState by baseNote.live().boosts.map { - it.note.boosts.size - }.distinctUntilChanged().observeAsState(baseNote.boosts.size) + val boostState by baseNote.live().boostCount.observeAsState(baseNote.boosts.size) SlidingAnimationCount(boostState, grayTint) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt index 8ae51ed24..45418fadd 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt @@ -57,6 +57,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import java.math.BigDecimal @@ -187,10 +188,10 @@ class UserReactionsViewModel(val account: Account) : ViewModel() { private var takenIntoAccount = setOf() private val sdf = DateTimeFormatter.ofPattern("yyyy-MM-dd") // SimpleDateFormat() - val todaysReplyCount = _replies.map { showCount(it[today()]) } - val todaysBoostCount = _boosts.map { showCount(it[today()]) } - val todaysReactionCount = _reactions.map { showCount(it[today()]) } - val todaysZapAmount = _zaps.map { showAmountAxis(it[today()]) } + val todaysReplyCount = _replies.map { showCount(it[today()]) }.distinctUntilChanged() + val todaysBoostCount = _boosts.map { showCount(it[today()]) }.distinctUntilChanged() + val todaysReactionCount = _reactions.map { showCount(it[today()]) }.distinctUntilChanged() + val todaysZapAmount = _zaps.map { showAmountAxis(it[today()]) }.distinctUntilChanged() fun formatDate(createAt: Long): String { return sdf.format( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayPoolViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayPoolViewModel.kt index e9f91be1c..a57974b6c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayPoolViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayPoolViewModel.kt @@ -16,5 +16,5 @@ class RelayPoolViewModel : ViewModel() { val isConnected = RelayPool.live.map { it.relays.connectedRelays() > 0 - } + }.distinctUntilChanged() }