mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-09-19 04:59:59 +00:00
Fix first Wear message auto-scroll
This commit is contained in:
parent
efeb9b54b5
commit
44b2b8cb79
@ -113,9 +113,21 @@ fun ChatScaffold(
|
|||||||
val followNewest = remember(messages.size) { atNewest }
|
val followNewest = remember(messages.size) { atNewest }
|
||||||
LaunchedEffect(columnState, messages.size) {
|
LaunchedEffect(columnState, messages.size) {
|
||||||
if (messages.isNotEmpty() && followNewest) {
|
if (messages.isNotEmpty() && followNewest) {
|
||||||
|
val expectedSingleMessageKey = messages.singleOrNull()?.id
|
||||||
scrollToNewestAfterItemsMeasured(
|
scrollToNewestAfterItemsMeasured(
|
||||||
expectedItemCount = messages.size,
|
expectedItemCount = messages.size,
|
||||||
measuredItemCounts = snapshotFlow { columnState.layoutInfo.totalItemsCount }
|
expectedSingleMessageKey = expectedSingleMessageKey,
|
||||||
|
measuredLayouts = snapshotFlow {
|
||||||
|
val layoutInfo = columnState.layoutInfo
|
||||||
|
MeasuredChatLayout(
|
||||||
|
itemCount = layoutInfo.totalItemsCount,
|
||||||
|
singleVisibleItemKey = if (expectedSingleMessageKey != null) {
|
||||||
|
layoutInfo.visibleItems.singleOrNull()?.key
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
// scrollBy to the end of the range: animateScrollToItem stops as soon as the
|
// scrollBy to the end of the range: animateScrollToItem stops as soon as the
|
||||||
// item is partially visible, which left the last message cropped.
|
// item is partially visible, which left the last message cropped.
|
||||||
@ -138,12 +150,22 @@ fun ChatScaffold(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal data class MeasuredChatLayout(
|
||||||
|
val itemCount: Int,
|
||||||
|
val singleVisibleItemKey: Any?
|
||||||
|
)
|
||||||
|
|
||||||
internal suspend fun scrollToNewestAfterItemsMeasured(
|
internal suspend fun scrollToNewestAfterItemsMeasured(
|
||||||
expectedItemCount: Int,
|
expectedItemCount: Int,
|
||||||
measuredItemCounts: Flow<Int>,
|
expectedSingleMessageKey: Any?,
|
||||||
|
measuredLayouts: Flow<MeasuredChatLayout>,
|
||||||
scrollToEnd: suspend () -> Unit
|
scrollToEnd: suspend () -> Unit
|
||||||
) {
|
) {
|
||||||
measuredItemCounts.first { it >= expectedItemCount }
|
measuredLayouts.first { layout ->
|
||||||
|
layout.itemCount >= expectedItemCount &&
|
||||||
|
(expectedSingleMessageKey == null ||
|
||||||
|
layout.singleVisibleItemKey == expectedSingleMessageKey)
|
||||||
|
}
|
||||||
scrollToEnd()
|
scrollToEnd()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,13 +12,14 @@ class ChatAutoScrollTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `newest scroll waits until appended message is measured`() = runTest {
|
fun `newest scroll waits until appended message is measured`() = runTest {
|
||||||
val measuredItemCount = MutableStateFlow(3)
|
val measuredLayouts = MutableStateFlow(MeasuredChatLayout(3, null))
|
||||||
var scrollCount = 0
|
var scrollCount = 0
|
||||||
|
|
||||||
val scrollJob = launch(start = CoroutineStart.UNDISPATCHED) {
|
val scrollJob = launch(start = CoroutineStart.UNDISPATCHED) {
|
||||||
scrollToNewestAfterItemsMeasured(
|
scrollToNewestAfterItemsMeasured(
|
||||||
expectedItemCount = 4,
|
expectedItemCount = 4,
|
||||||
measuredItemCounts = measuredItemCount
|
expectedSingleMessageKey = null,
|
||||||
|
measuredLayouts = measuredLayouts
|
||||||
) {
|
) {
|
||||||
scrollCount += 1
|
scrollCount += 1
|
||||||
}
|
}
|
||||||
@ -27,7 +28,7 @@ class ChatAutoScrollTest {
|
|||||||
assertFalse(scrollJob.isCompleted)
|
assertFalse(scrollJob.isCompleted)
|
||||||
assertEquals(0, scrollCount)
|
assertEquals(0, scrollCount)
|
||||||
|
|
||||||
measuredItemCount.value = 4
|
measuredLayouts.value = MeasuredChatLayout(4, null)
|
||||||
scrollJob.join()
|
scrollJob.join()
|
||||||
|
|
||||||
assertEquals(1, scrollCount)
|
assertEquals(1, scrollCount)
|
||||||
@ -35,16 +36,47 @@ class ChatAutoScrollTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `newest scroll runs immediately when messages are already measured`() = runTest {
|
fun `newest scroll runs immediately when messages are already measured`() = runTest {
|
||||||
val measuredItemCount = MutableStateFlow(4)
|
val measuredLayouts = MutableStateFlow(MeasuredChatLayout(4, null))
|
||||||
var scrollCount = 0
|
var scrollCount = 0
|
||||||
|
|
||||||
scrollToNewestAfterItemsMeasured(
|
scrollToNewestAfterItemsMeasured(
|
||||||
expectedItemCount = 4,
|
expectedItemCount = 4,
|
||||||
measuredItemCounts = measuredItemCount
|
expectedSingleMessageKey = null,
|
||||||
|
measuredLayouts = measuredLayouts
|
||||||
) {
|
) {
|
||||||
scrollCount += 1
|
scrollCount += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(1, scrollCount)
|
assertEquals(1, scrollCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `first message waits past stale empty placeholder layout`() = runTest {
|
||||||
|
val messageKey = "first-message"
|
||||||
|
val measuredLayouts = MutableStateFlow(
|
||||||
|
MeasuredChatLayout(itemCount = 1, singleVisibleItemKey = "empty-placeholder")
|
||||||
|
)
|
||||||
|
var scrollCount = 0
|
||||||
|
|
||||||
|
val scrollJob = launch(start = CoroutineStart.UNDISPATCHED) {
|
||||||
|
scrollToNewestAfterItemsMeasured(
|
||||||
|
expectedItemCount = 1,
|
||||||
|
expectedSingleMessageKey = messageKey,
|
||||||
|
measuredLayouts = measuredLayouts
|
||||||
|
) {
|
||||||
|
scrollCount += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertFalse(scrollJob.isCompleted)
|
||||||
|
assertEquals(0, scrollCount)
|
||||||
|
|
||||||
|
measuredLayouts.value = MeasuredChatLayout(
|
||||||
|
itemCount = 1,
|
||||||
|
singleVisibleItemKey = messageKey
|
||||||
|
)
|
||||||
|
scrollJob.join()
|
||||||
|
|
||||||
|
assertEquals(1, scrollCount)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user