From 239f973e897929dc17b5b71e96557e48a7e9886c Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 16 Aug 2024 16:42:00 -0400 Subject: [PATCH] Moves lastread routes to mutableStateFlow --- .../amethyst/LocalPreferences.kt | 17 +++-- .../vitorpamplona/amethyst/model/Account.kt | 25 ++++++-- .../ui/actions/JoinUserOrChannelView.kt | 5 +- .../amethyst/ui/note/ChatroomHeaderCompose.kt | 63 +++++-------------- .../ui/screen/loggedIn/AccountViewModel.kt | 3 - .../ui/screen/loggedIn/SearchScreen.kt | 4 +- 6 files changed, 51 insertions(+), 66 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt index 39d36c3d0..5da5f2229 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt @@ -349,9 +349,15 @@ object LocalPreferences { putInt(PrefKeys.PROXY_PORT, account.proxyPort) putBoolean(PrefKeys.WARN_ABOUT_REPORTS, account.warnAboutPostsWithReports) putBoolean(PrefKeys.FILTER_SPAM_FROM_STRANGERS, account.filterSpamFromStrangers) + + val regularMap = + account.lastReadPerRoute.value.mapValues { + it.value.value + } + putString( PrefKeys.LAST_READ_PER_ROUTE, - Event.mapper.writeValueAsString(account.lastReadPerRoute), + Event.mapper.writeValueAsString(regularMap), ) putStringSet(PrefKeys.HAS_DONATED_IN_VERSION, account.hasDonatedInVersion) @@ -611,9 +617,10 @@ object LocalPreferences { val lastReadPerRoute = try { getString(PrefKeys.LAST_READ_PER_ROUTE, null)?.let { - Event.mapper.readValue?>(it) - } - ?: mapOf() + Event.mapper.readValue?>(it)?.mapValues { + MutableStateFlow(it.value) + } + } ?: mapOf() } catch (e: Throwable) { if (e is CancellationException) throw e Log.w( @@ -669,7 +676,7 @@ object LocalPreferences { showSensitiveContent = MutableStateFlow(showSensitiveContent), warnAboutPostsWithReports = warnAboutReports, filterSpamFromStrangers = filterSpam, - lastReadPerRoute = lastReadPerRoute, + lastReadPerRoute = MutableStateFlow(lastReadPerRoute), hasDonatedInVersion = hasDonatedInVersion, pendingAttestations = MutableStateFlow(pendingAttestations ?: emptyMap()), ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 211a07b70..7c6bd9542 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -202,7 +202,7 @@ class Account( var showSensitiveContent: MutableStateFlow = MutableStateFlow(null), var warnAboutPostsWithReports: Boolean = true, var filterSpamFromStrangers: Boolean = true, - var lastReadPerRoute: Map = mapOf(), + var lastReadPerRoute: MutableStateFlow>> = MutableStateFlow(mapOf()), var hasDonatedInVersion: Set = setOf(), var pendingAttestations: MutableStateFlow> = MutableStateFlow>(mapOf()), val scope: CoroutineScope = Amethyst.instance.applicationIOScope, @@ -3213,9 +3213,15 @@ class Account( route: String, timestampInSecs: Long, ): Boolean { - val lastTime = lastReadPerRoute[route] - return if (lastTime == null || timestampInSecs > lastTime) { - lastReadPerRoute = lastReadPerRoute + Pair(route, timestampInSecs) + val lastTime = lastReadPerRoute.value[route] + return if (lastTime == null) { + lastReadPerRoute.update { + it + Pair(route, MutableStateFlow(timestampInSecs)) + } + saveable.invalidateData() + true + } else if (timestampInSecs > lastTime.value) { + lastTime.tryEmit(timestampInSecs) saveable.invalidateData() true } else { @@ -3223,7 +3229,16 @@ class Account( } } - fun loadLastRead(route: String): Long = lastReadPerRoute[route] ?: 0 + fun loadLastRead(route: String): Long = lastReadPerRoute.value[route]?.value ?: 0 + + fun loadLastReadFlow(route: String): StateFlow = + lastReadPerRoute.value[route] ?: run { + val newFlow = MutableStateFlow(0) + lastReadPerRoute.update { + it + Pair(route, newFlow) + } + newFlow + } fun hasDonatedInThisVersion(): Boolean = hasDonatedInVersion.contains(BuildConfig.VERSION_NAME) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt index 57b596c40..45b1cde5b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt @@ -50,7 +50,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment @@ -387,8 +386,6 @@ private fun RenderChannel( loadRobohash: Boolean, onClick: () -> Unit, ) { - val hasNewMessages = remember { mutableStateOf(false) } - ChannelName( channelIdHex = item.idHex, channelPicture = item.profilePicture(), @@ -400,7 +397,7 @@ private fun RenderChannel( }, channelLastTime = null, channelLastContent = item.summary(), - hasNewMessages, + hasNewMessages = false, onClick = onClick, loadProfilePicture = loadProfilePicture, loadRobohash = loadRobohash, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomHeaderCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomHeaderCompose.kt index 5952348bc..87e46ec58 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomHeaderCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomHeaderCompose.kt @@ -33,7 +33,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState @@ -54,6 +53,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.distinctUntilChanged import androidx.lifecycle.map import com.patrykandpatrick.vico.core.extension.forEachIndexedExtended @@ -171,15 +171,14 @@ private fun ChannelRoomCompose( .observeAsState() val authorName = remember(note, authorState) { authorState?.user?.toBestDisplayName() } - val chanHex = remember { channel.idHex } - val channelState by channel.live.observeAsState() - val channelPicture by remember(note, channelState) { derivedStateOf { channel.profilePicture() } } - val channelName by remember(note, channelState) { derivedStateOf { channel.toBestDisplayName() } } + + val channelPicture = channelState?.channel?.profilePicture() ?: channel.profilePicture() + val channelName = channelState?.channel?.toBestDisplayName() ?: channel.toBestDisplayName() val noteEvent = note.event - val route = remember(note) { "Channel/$chanHex" } + val route = "Channel/${channel.idHex}" val description = if (noteEvent is ChannelCreateEvent) { @@ -190,21 +189,15 @@ private fun ChannelRoomCompose( noteEvent?.content()?.take(200) } - val hasNewMessages = remember { mutableStateOf(false) } - - WatchNotificationChanges(note, route, accountViewModel) { newHasNewMessages -> - if (hasNewMessages.value != newHasNewMessages) { - hasNewMessages.value = newHasNewMessages - } - } + val lastReadTime by accountViewModel.account.loadLastReadFlow(route).collectAsStateWithLifecycle() ChannelName( - channelIdHex = chanHex, + channelIdHex = channel.idHex, channelPicture = channelPicture, channelTitle = { modifier -> ChannelTitleWithLabelInfo(channelName, modifier) }, - channelLastTime = remember(note) { note.createdAt() }, - channelLastContent = remember(note, authorState) { "$authorName: $description" }, - hasNewMessages = hasNewMessages, + channelLastTime = note.createdAt(), + channelLastContent = "$authorName: $description", + hasNewMessages = (noteEvent?.createdAt() ?: Long.MIN_VALUE) > lastReadTime, loadProfilePicture = accountViewModel.settings.showProfilePictures.value, loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, onClick = { nav(route) }, @@ -257,17 +250,9 @@ private fun UserRoomCompose( accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - val hasNewMessages = remember { mutableStateOf(false) } + val route = "Room/${room.hashCode()}" - val route = remember(room) { "Room/${room.hashCode()}" } - - val createAt by remember(note) { derivedStateOf { note.createdAt() } } - - WatchNotificationChanges(note, route, accountViewModel) { newHasNewMessages -> - if (hasNewMessages.value != newHasNewMessages) { - hasNewMessages.value = newHasNewMessages - } - } + val lastReadTime by accountViewModel.account.loadLastReadFlow(route).collectAsStateWithLifecycle() LoadDecryptedContentOrNull(note, accountViewModel) { content -> ChannelName( @@ -279,9 +264,9 @@ private fun UserRoomCompose( ) }, channelTitle = { RoomNameDisplay(room, it, accountViewModel) }, - channelLastTime = createAt, + channelLastTime = note.createdAt(), channelLastContent = content, - hasNewMessages = hasNewMessages, + hasNewMessages = (note.createdAt() ?: Long.MIN_VALUE) > lastReadTime, onClick = { nav(route) }, ) } @@ -412,20 +397,6 @@ fun ShortUsernameDisplay( } } -@Composable -private fun WatchNotificationChanges( - note: Note, - route: String, - accountViewModel: AccountViewModel, - onNewStatus: (Boolean) -> Unit, -) { - LaunchedEffect(key1 = note, accountViewModel.accountMarkAsReadUpdates.intValue) { - note.event?.createdAt()?.let { - onNewStatus(it > accountViewModel.account.loadLastRead(route)) - } - } -} - @Composable fun LoadUser( baseUserHex: String, @@ -455,7 +426,7 @@ fun ChannelName( channelTitle: @Composable (Modifier) -> Unit, channelLastTime: Long?, channelLastContent: String?, - hasNewMessages: MutableState, + hasNewMessages: Boolean, loadProfilePicture: Boolean, loadRobohash: Boolean, onClick: () -> Unit, @@ -485,7 +456,7 @@ fun ChannelName( channelTitle: @Composable (Modifier) -> Unit, channelLastTime: Long?, channelLastContent: String?, - hasNewMessages: MutableState, + hasNewMessages: Boolean, onClick: () -> Unit, ) { ChatHeaderLayout( @@ -514,7 +485,7 @@ fun ChannelName( ) } - if (hasNewMessages.value) { + if (hasNewMessages) { NewItemsBubble() } }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index d254d8876..24d3eca53 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -27,7 +27,6 @@ import android.util.LruCache import androidx.compose.runtime.Immutable import androidx.compose.runtime.MutableState import androidx.compose.runtime.Stable -import androidx.compose.runtime.mutableIntStateOf import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider @@ -150,7 +149,6 @@ class AccountViewModel( Dao { val accountLiveData: LiveData = account.live.map { it } val accountLanguagesLiveData: LiveData = account.liveLanguages.map { it } - val accountMarkAsReadUpdates = mutableIntStateOf(0) // TODO: contact lists are not notes yet // val kind3Relays: StateFlow = observeByAuthor(ContactListEvent.KIND, account.signer.pubKey) @@ -1121,7 +1119,6 @@ class AccountViewModel( suspend fun refreshMarkAsReadObservers() { updateNotificationDots() - accountMarkAsReadUpdates.value++ } fun loadAndMarkAsRead( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt index b2f8e7b60..2a9f5a8c7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt @@ -380,8 +380,6 @@ private fun DisplaySearchResults( val channels by searchBarViewModel.searchResultsChannels.collectAsStateWithLifecycle() val notes by searchBarViewModel.searchResultsNotes.collectAsStateWithLifecycle() - val hasNewMessages = remember { mutableStateOf(false) } - LazyColumn( modifier = Modifier.fillMaxHeight(), contentPadding = FeedPadding, @@ -425,7 +423,7 @@ private fun DisplaySearchResults( }, channelLastTime = null, channelLastContent = item.summary(), - hasNewMessages = hasNewMessages, + hasNewMessages = false, loadProfilePicture = accountViewModel.settings.showProfilePictures.value, loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, onClick = { nav("Channel/${item.idHex}") },