mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-12 01:07:46 +00:00
Merge pull request #3735 from vitorpamplona/fix/community-tab-first-frame
fix(chats): render the community tab correct on its first frame
This commit is contained in:
@@ -94,10 +94,18 @@ class Nav(
|
||||
// Clear sibling bottom-nav entries but keep Home (the start
|
||||
// destination) below, so back-swipe from any tab returns to
|
||||
// Home and back-swipe from Home leaves the app.
|
||||
//
|
||||
// saveState/restoreState is what makes a tab survive being left. Without them the
|
||||
// popped entry is DESTROYED, taking its ViewModelStore with it — so every return to
|
||||
// a tab rebuilt its screen-scoped ViewModels from nothing and re-fetched. On the
|
||||
// Buzz community tab that is a visible ~1s of empty Direct Messages plus a channel
|
||||
// list that reshuffles as data lands; other tabs pay it as lost scroll position.
|
||||
popUpTo(Route.Home) {
|
||||
inclusive = false
|
||||
saveState = true
|
||||
}
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
// Mark this entry as a tab root: hides the back arrow in canPop
|
||||
// and skips the horizontal slide in composableFromEnd.
|
||||
|
||||
+30
@@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.commons.model.buzz.BuzzDmChannels
|
||||
import com.vitorpamplona.amethyst.commons.model.buzz.BuzzDmRegistry
|
||||
import com.vitorpamplona.amethyst.commons.model.buzz.BuzzRelayDialect
|
||||
import com.vitorpamplona.amethyst.commons.model.buzz.BuzzWorkspaces
|
||||
@@ -126,10 +127,39 @@ class BuzzDmListViewModel : ViewModel() {
|
||||
// challenge was spent unauthenticated, so reconnect to re-challenge and authenticate.
|
||||
if (newlyJoined) account.client.reconnect(onlyIfChanged = false, ignoreRetryDelays = true)
|
||||
|
||||
// Paint from cache BEFORE any network work. [discoverMemberChannels] learns the channel ids
|
||||
// from a relay round-trip, so waiting on it left the Direct Messages section visibly empty
|
||||
// for about a second on every visit — even though the always-on [BuzzDmDiscovery] already
|
||||
// recorded those ids process-wide and [rebuildRows] reads nothing but caches. Seeding from
|
||||
// that registry makes the first frame the right frame; the refresh below still runs and
|
||||
// corrects anything stale.
|
||||
seedFromDiscovery(account, relay)
|
||||
|
||||
refresh()
|
||||
startLive()
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills [memberChannels] from the app-wide [BuzzDmChannels] registry (scoped to this community's
|
||||
* relay) and projects the rows straight away, so the inbox renders from cache instead of after a
|
||||
* fetch. A no-op the first time a viewer ever opens a Buzz relay, when discovery genuinely has
|
||||
* nothing yet.
|
||||
*/
|
||||
private fun seedFromDiscovery(
|
||||
account: Account,
|
||||
relay: NormalizedRelayUrl,
|
||||
) {
|
||||
val known = BuzzDmChannels.channelsFor(account.userProfile().pubkeyHex)
|
||||
var seeded = false
|
||||
known.forEach { (channelId, discoveredOn) ->
|
||||
if (discoveredOn == relay) {
|
||||
memberChannels[channelId] = discoveredOn
|
||||
seeded = true
|
||||
}
|
||||
}
|
||||
if (seeded) rebuildRows(account)
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
val account = account ?: return
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
|
||||
+14
-4
@@ -216,20 +216,30 @@ fun RelayGroupChannelListScreen(
|
||||
}
|
||||
|
||||
fun buzzTypeOf(groupId: GroupId): String? = channelsById[groupId.id]?.event?.buzzChannelType()
|
||||
// Starred channels float to the top of their section (stable sort keeps the alphabetical order
|
||||
// within the starred and unstarred buckets).
|
||||
|
||||
// Starred channels float to the top of their section, then alphabetical.
|
||||
//
|
||||
// The name is the tie-break on purpose: [buzzGroupIds] is in *arrival* order (membership ids as
|
||||
// the ViewModel emitted them, then directory ids), so sorting on `starred` alone — a stable sort
|
||||
// over a boolean — left the underlying order at the mercy of whatever landed first. The list
|
||||
// visibly reshuffled in the second after opening, and came back differently each visit. Ordering
|
||||
// by a property of the channel instead makes the first frame the final order; a channel whose
|
||||
// 39000 hasn't arrived sorts by its id until the name lands.
|
||||
val starred by BuzzChannelStars.flow.collectAsStateWithLifecycle()
|
||||
|
||||
fun buzzSortKey(groupId: GroupId): String = channelsById[groupId.id]?.toBestDisplayName()?.lowercase() ?: groupId.id
|
||||
|
||||
val buzzChatChannels =
|
||||
remember(buzzGroupIds, channelsById, starred) {
|
||||
buzzGroupIds
|
||||
.filter { buzzTypeOf(it).let { t -> t != BUZZ_CHANNEL_TYPE_FORUM && t != BUZZ_CHANNEL_TYPE_DM } }
|
||||
.sortedByDescending { it.id in starred }
|
||||
.sortedWith(compareByDescending<GroupId> { it.id in starred }.thenBy { buzzSortKey(it) })
|
||||
}
|
||||
val buzzForumChannels =
|
||||
remember(buzzGroupIds, channelsById, starred) {
|
||||
buzzGroupIds
|
||||
.filter { buzzTypeOf(it) == BUZZ_CHANNEL_TYPE_FORUM }
|
||||
.sortedByDescending { it.id in starred }
|
||||
.sortedWith(compareByDescending<GroupId> { it.id in starred }.thenBy { buzzSortKey(it) })
|
||||
}
|
||||
|
||||
// Which sections the user has collapsed (session-scoped). Keyed by section id below.
|
||||
|
||||
Reference in New Issue
Block a user