mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-09-19 04:59:59 +00:00
fix(ui): prevent animation flicker when backfilling message history
- Remove AnimatedVisibility wrapper from navigation bar and replace with conditional if statement - Add comment explaining why animations are deliberately avoided on screen swaps to prevent layout thrashing - Update MessageArrivalTracker.arrivals() to filter out backfilled messages from arrival detection - Add lastKnownIndex tracking to distinguish between backfilled history and new messages - Update filter logic to only report messages newer than the last known message as arrivals - Add comprehensive test coverage for backfill scenarios: messages loaded behind latest and new arrivals alongside backfill - Prevents transcript flicker on short conversations by not animating backfilled history rows
This commit is contained in:
parent
b9f87eba9e
commit
357e7f65ee
@ -1,11 +1,6 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@ -105,11 +100,11 @@ fun MainScaffold(viewModel: ChatViewModel, modifier: Modifier = Modifier) {
|
||||
// so the Scaffold must not pad the top. The bottom bar still reports its own height.
|
||||
contentWindowInsets = WindowInsets(0),
|
||||
bottomBar = {
|
||||
AnimatedVisibility(
|
||||
visible = !inConversation,
|
||||
enter = slideInVertically { it } + fadeIn(),
|
||||
exit = slideOutVertically { it } + fadeOut()
|
||||
) {
|
||||
// Shown or hidden in the same frame the screen swaps, deliberately not animated. A
|
||||
// slide/fade AnimatedVisibility keeps its full layout slot for the whole exit and only
|
||||
// then collapses to zero, so the conversation opened above a blank strip and lurched
|
||||
// down by the bar's height a few hundred milliseconds later.
|
||||
if (!inConversation) {
|
||||
BitchatNavigationBar(
|
||||
viewModel = viewModel,
|
||||
selected = tab,
|
||||
|
||||
@ -183,8 +183,18 @@ internal fun MessageArrivalTracker.arrivals(messages: List<BitchatMessage>): Set
|
||||
val isWholesaleReplacement =
|
||||
messages.isNotEmpty() && known.isNotEmpty() && messages.none { it.id in known }
|
||||
|
||||
// History backfill inserts messages *older* than ones already on screen: opening a DM shows its
|
||||
// compacted summary (the latest message only), then loads the stored history in behind it.
|
||||
// Those rows are not arrivals - only messages after the newest already-known one are. Without
|
||||
// this, a short conversation's whole history (anything under the burst cap) slid in on every
|
||||
// open, which read as the transcript flickering.
|
||||
val lastKnownIndex = messages.indexOfLast { it.id in known }
|
||||
|
||||
// `HashSet.add` reports whether the id was new, so this both diffs and updates in one pass.
|
||||
val added = messages.filter { known.add(it.id) }
|
||||
// The add runs first so backfilled ids are still recorded as known.
|
||||
val added = messages.filterIndexed { index, message ->
|
||||
known.add(message.id) && index > lastKnownIndex
|
||||
}
|
||||
|
||||
if (known.size > messages.size) {
|
||||
// Messages disappeared (/clear, channel switch). Drop the stale ids so the set cannot
|
||||
|
||||
@ -171,6 +171,32 @@ class MessageArrivalTrackerTest {
|
||||
assertEquals(setOf("y"), tracker.arrivals(switched))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `history backfilled behind the latest message is adopted silently`() {
|
||||
// Opening a DM shows its compacted summary (just the latest message), then the stored
|
||||
// history loads in behind it. Those older rows are not arrivals; animating them made the
|
||||
// whole transcript flicker on every open of a short conversation.
|
||||
val tracker = MessageArrivalTracker()
|
||||
val latest = msg("latest")
|
||||
tracker.arrivals(listOf(latest))
|
||||
|
||||
val backfilled = listOf(msg("h1"), msg("h2"), msg("h3"), latest)
|
||||
assertTrue(tracker.arrivals(backfilled).isEmpty())
|
||||
assertEquals(setOf("h1", "h2", "h3", "latest"), tracker.known)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a new message arriving alongside a backfill still animates`() {
|
||||
val tracker = MessageArrivalTracker()
|
||||
val latest = msg("latest")
|
||||
tracker.arrivals(listOf(latest))
|
||||
|
||||
assertEquals(
|
||||
setOf("new"),
|
||||
tracker.arrivals(listOf(msg("h1"), latest, msg("new")))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the known set never outgrows the conversation`() {
|
||||
val tracker = MessageArrivalTracker()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user