mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-10-25 07:40:36 +00:00
Fix slow Wear scroll intent detection
This commit is contained in:
parent
a125da48aa
commit
7cf5286bfb
@ -50,6 +50,7 @@ import com.bitchat.watch.ui.theme.ChatVisualTokens
|
||||
import com.bitchat.watch.ui.theme.LocalBitchatPalette
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlin.math.sign
|
||||
|
||||
/**
|
||||
* The shared chat body for global chat and DM threads, following the classic messenger
|
||||
@ -95,6 +96,7 @@ fun ChatScaffold(
|
||||
val controlsVisible = remember { mutableStateOf(true) }
|
||||
LaunchedEffect(columnState) {
|
||||
var lastPosition = -1
|
||||
var scrollIntent = ChatScrollIntentState()
|
||||
snapshotFlow {
|
||||
val first = columnState.layoutInfo.visibleItems.firstOrNull()
|
||||
ChatScrollSnapshot(
|
||||
@ -103,21 +105,13 @@ fun ChatScaffold(
|
||||
position = (first?.index ?: 0) * 100_000 + (first?.offset ?: 0)
|
||||
)
|
||||
}.collect { snapshot ->
|
||||
followNewest = updatedFollowNewest(
|
||||
current = followNewest,
|
||||
scrollIntent = updatedChatScrollIntent(
|
||||
current = scrollIntent,
|
||||
snapshot = snapshot,
|
||||
previousPosition = lastPosition
|
||||
)
|
||||
if (!snapshot.canScrollForward) {
|
||||
controlsVisible.value = true
|
||||
} else if (lastPosition >= 0) {
|
||||
when {
|
||||
snapshot.position > lastPosition + CHAT_SCROLL_DIRECTION_THRESHOLD_PX ->
|
||||
controlsVisible.value = true
|
||||
snapshot.position < lastPosition - CHAT_SCROLL_DIRECTION_THRESHOLD_PX ->
|
||||
controlsVisible.value = false
|
||||
}
|
||||
}
|
||||
followNewest = scrollIntent.followsNewest
|
||||
controlsVisible.value = scrollIntent.controlsVisible
|
||||
lastPosition = snapshot.position
|
||||
}
|
||||
}
|
||||
@ -173,16 +167,42 @@ internal data class ChatScrollSnapshot(
|
||||
val position: Int
|
||||
)
|
||||
|
||||
internal fun updatedFollowNewest(
|
||||
current: Boolean,
|
||||
internal data class ChatScrollIntentState(
|
||||
val followsNewest: Boolean = true,
|
||||
val controlsVisible: Boolean = true,
|
||||
val accumulatedDeltaPx: Int = 0
|
||||
)
|
||||
|
||||
internal fun updatedChatScrollIntent(
|
||||
current: ChatScrollIntentState,
|
||||
snapshot: ChatScrollSnapshot,
|
||||
previousPosition: Int
|
||||
): Boolean = when {
|
||||
!snapshot.canScrollForward -> true
|
||||
snapshot.isScrollInProgress &&
|
||||
previousPosition >= 0 &&
|
||||
snapshot.position < previousPosition - CHAT_SCROLL_DIRECTION_THRESHOLD_PX -> false
|
||||
else -> current
|
||||
): ChatScrollIntentState {
|
||||
if (!snapshot.canScrollForward) return ChatScrollIntentState()
|
||||
if (!snapshot.isScrollInProgress || previousPosition < 0) return current
|
||||
|
||||
val delta = snapshot.position - previousPosition
|
||||
val accumulatedDelta = when {
|
||||
delta == 0 -> current.accumulatedDeltaPx
|
||||
current.accumulatedDeltaPx == 0 ||
|
||||
current.accumulatedDeltaPx.sign == delta.sign ->
|
||||
current.accumulatedDeltaPx + delta
|
||||
else -> delta
|
||||
}
|
||||
val movedAway = accumulatedDelta <= -CHAT_SCROLL_DIRECTION_THRESHOLD_PX
|
||||
val movedTowardNewest = accumulatedDelta >= CHAT_SCROLL_DIRECTION_THRESHOLD_PX
|
||||
|
||||
return current.copy(
|
||||
followsNewest = current.followsNewest && !movedAway,
|
||||
controlsVisible = when {
|
||||
movedAway -> false
|
||||
movedTowardNewest -> true
|
||||
else -> current.controlsVisible
|
||||
},
|
||||
// Keep sub-threshold movement across discrete rotary events. Once intent is clear,
|
||||
// start a fresh accumulator so reversing direction gets the same threshold treatment.
|
||||
accumulatedDeltaPx = if (movedAway || movedTowardNewest) 0 else accumulatedDelta
|
||||
)
|
||||
}
|
||||
|
||||
internal suspend fun scrollToNewestAfterItemsMeasured(
|
||||
|
||||
@ -12,8 +12,8 @@ class ChatAutoScrollTest {
|
||||
|
||||
@Test
|
||||
fun `new scroll range from appended message keeps follow intent`() {
|
||||
val updated = updatedFollowNewest(
|
||||
current = true,
|
||||
val updated = updatedChatScrollIntent(
|
||||
current = ChatScrollIntentState(),
|
||||
snapshot = ChatScrollSnapshot(
|
||||
canScrollForward = true,
|
||||
isScrollInProgress = false,
|
||||
@ -22,13 +22,13 @@ class ChatAutoScrollTest {
|
||||
previousPosition = 100
|
||||
)
|
||||
|
||||
assertEquals(true, updated)
|
||||
assertEquals(ChatScrollIntentState(), updated)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `user scroll away disables follow until list reaches newest again`() {
|
||||
val browsingHistory = updatedFollowNewest(
|
||||
current = true,
|
||||
val browsingHistory = updatedChatScrollIntent(
|
||||
current = ChatScrollIntentState(),
|
||||
snapshot = ChatScrollSnapshot(
|
||||
canScrollForward = true,
|
||||
isScrollInProgress = true,
|
||||
@ -36,9 +36,10 @@ class ChatAutoScrollTest {
|
||||
),
|
||||
previousPosition = 100
|
||||
)
|
||||
assertFalse(browsingHistory)
|
||||
assertFalse(browsingHistory.followsNewest)
|
||||
assertFalse(browsingHistory.controlsVisible)
|
||||
|
||||
val dockedAgain = updatedFollowNewest(
|
||||
val dockedAgain = updatedChatScrollIntent(
|
||||
current = browsingHistory,
|
||||
snapshot = ChatScrollSnapshot(
|
||||
canScrollForward = false,
|
||||
@ -47,7 +48,62 @@ class ChatAutoScrollTest {
|
||||
),
|
||||
previousPosition = 60
|
||||
)
|
||||
assertEquals(true, dockedAgain)
|
||||
assertEquals(ChatScrollIntentState(), dockedAgain)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `slow scroll away accumulates intent across sub-threshold updates`() {
|
||||
var state = ChatScrollIntentState()
|
||||
var previousPosition = 100
|
||||
|
||||
listOf(94, 88, 82, 76).forEach { position ->
|
||||
state = updatedChatScrollIntent(
|
||||
current = state,
|
||||
snapshot = ChatScrollSnapshot(
|
||||
canScrollForward = true,
|
||||
isScrollInProgress = true,
|
||||
position = position
|
||||
),
|
||||
previousPosition = previousPosition
|
||||
)
|
||||
previousPosition = position
|
||||
state = updatedChatScrollIntent(
|
||||
current = state,
|
||||
snapshot = ChatScrollSnapshot(
|
||||
canScrollForward = true,
|
||||
isScrollInProgress = false,
|
||||
position = position
|
||||
),
|
||||
previousPosition = previousPosition
|
||||
)
|
||||
}
|
||||
|
||||
assertFalse(state.followsNewest)
|
||||
assertFalse(state.controlsVisible)
|
||||
assertEquals(0, state.accumulatedDeltaPx)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `slow scroll toward newest reveals controls without restoring follow early`() {
|
||||
var state = ChatScrollIntentState(followsNewest = false, controlsVisible = false)
|
||||
var previousPosition = 60
|
||||
|
||||
listOf(66, 72, 78, 84).forEach { position ->
|
||||
state = updatedChatScrollIntent(
|
||||
current = state,
|
||||
snapshot = ChatScrollSnapshot(
|
||||
canScrollForward = true,
|
||||
isScrollInProgress = true,
|
||||
position = position
|
||||
),
|
||||
previousPosition = previousPosition
|
||||
)
|
||||
previousPosition = position
|
||||
}
|
||||
|
||||
assertFalse(state.followsNewest)
|
||||
assertEquals(true, state.controlsVisible)
|
||||
assertEquals(0, state.accumulatedDeltaPx)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user