fix(chat): 新消息自动跟随底部(微信/QQ 式)
reverseLayout 会锚定原可见项,导致新消息插到视口外。改为:滚动结束时记录是否贴底(容差 64px),最新一项变化且处于跟随状态时 animateScrollToItem(0);用户上滑查看历史时不再强制滚动。
This commit is contained in:
1 parent
67ed0479e4
commit
e04995a84d
1 file changed
+28
@@ -18,6 +18,9 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.snapshotFlow
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -40,6 +43,9 @@ import top.yukonga.miuix.kmp.icon.MiuixIcons
|
|||||||
import top.yukonga.miuix.kmp.icon.basic.Sidebar
|
import top.yukonga.miuix.kmp.icon.basic.Sidebar
|
||||||
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
import top.yukonga.miuix.kmp.theme.MiuixTheme
|
||||||
|
|
||||||
|
/** 判定「贴着底部」的容差(px)。 */
|
||||||
|
private const val BOTTOM_TOLERANCE = 64
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatScreen(
|
fun ChatScreen(
|
||||||
container: AppContainer,
|
container: AppContainer,
|
||||||
@@ -52,6 +58,28 @@ fun ChatScreen(
|
|||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
|
// 是否跟随底部(微信/QQ 式):只有用户主动滚到别处时才停止跟随。
|
||||||
|
// 只在「滚动结束」时更新,避免新消息插入导致的锚点位移把状态带偏。
|
||||||
|
var followBottom by remember { mutableStateOf(true) }
|
||||||
|
LaunchedEffect(listState) {
|
||||||
|
snapshotFlow { listState.isScrollInProgress }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.collect { scrolling ->
|
||||||
|
if (!scrolling) {
|
||||||
|
followBottom = listState.firstVisibleItemIndex == 0 &&
|
||||||
|
listState.firstVisibleItemScrollOffset < BOTTOM_TOLERANCE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最新一项变化(新消息 / 乐观气泡 / 流式气泡 / 落库归一)时,跟随则滚到底。
|
||||||
|
val newestKey = state.items.lastOrNull()?.key
|
||||||
|
LaunchedEffect(newestKey) {
|
||||||
|
if (newestKey != null && followBottom) {
|
||||||
|
listState.animateScrollToItem(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// reverseLayout 下「列表末尾」就是屏幕顶部;接近末尾时加载更早的一页。
|
// reverseLayout 下「列表末尾」就是屏幕顶部;接近末尾时加载更早的一页。
|
||||||
LaunchedEffect(listState) {
|
LaunchedEffect(listState) {
|
||||||
snapshotFlow {
|
snapshotFlow {
|
||||||
|
|||||||
Reference in new issue
Block a user