更新聊天的滚动逻辑

This commit is contained in:
2024-10-12 16:29:53 +08:00
parent 198a815b32
commit dc363122c9
2 changed files with 102 additions and 29 deletions

View File

@@ -48,8 +48,10 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
@@ -78,6 +80,7 @@ import com.aiosman.riderpro.ui.composables.MenuItem
import com.aiosman.riderpro.ui.composables.StatusBarSpacer import com.aiosman.riderpro.ui.composables.StatusBarSpacer
import com.aiosman.riderpro.ui.modifiers.noRippleClickable import com.aiosman.riderpro.ui.modifiers.noRippleClickable
import com.tencent.imsdk.v2.V2TIMMessage import com.tencent.imsdk.v2.V2TIMMessage
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -86,6 +89,7 @@ fun ChatScreen(userId: String) {
var isMenuExpanded by remember { mutableStateOf(false) } var isMenuExpanded by remember { mutableStateOf(false) }
val navController = LocalNavController.current val navController = LocalNavController.current
val context = LocalNavController.current.context val context = LocalNavController.current.context
var goToNewCount by remember { mutableStateOf(0) }
val viewModel = viewModel<ChatViewModel>( val viewModel = viewModel<ChatViewModel>(
key = "ChatViewModel_$userId", key = "ChatViewModel_$userId",
factory = object : ViewModelProvider.Factory { factory = object : ViewModelProvider.Factory {
@@ -109,17 +113,44 @@ fun ChatScreen(userId: String) {
val navigationBarHeight = with(LocalDensity.current) { val navigationBarHeight = with(LocalDensity.current) {
WindowInsets.navigationBars.getBottom(this).toDp() WindowInsets.navigationBars.getBottom(this).toDp()
} }
var inBottom by remember { mutableStateOf(true) }
// 监听滚动状态,触发加载更多 // 监听滚动状态,触发加载更多
LaunchedEffect(listState) { LaunchedEffect(listState) {
snapshotFlow { listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index } snapshotFlow { listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index }
.collect { index -> .collect { index ->
Log.d("ChatScreen", "lastVisibleItemIndex: ${index}")
if (index == listState.layoutInfo.totalItemsCount - 1) { if (index == listState.layoutInfo.totalItemsCount - 1) {
coroutineScope.launch { coroutineScope.launch {
viewModel.onLoadMore(context) viewModel.onLoadMore(context)
} }
} }
} }
} }
// 监听滚动状态,触发滚动到底部
LaunchedEffect(listState) {
snapshotFlow { listState.layoutInfo.visibleItemsInfo.firstOrNull()?.index }
.collect { index ->
inBottom = index == 0
if (index == 0) {
goToNewCount = 0
}
}
}
// 监听是否需要滚动到最新消息
LaunchedEffect(viewModel.goToNew) {
if (viewModel.goToNew) {
if (inBottom) {
listState.scrollToItem(0)
} else {
goToNewCount++
}
viewModel.goToNew = false
}
}
Scaffold( Scaffold(
modifier = Modifier modifier = Modifier
@@ -128,7 +159,6 @@ fun ChatScreen(userId: String) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.border(1.dp, Color(0xffe5e5e5))
.background(Color.White) .background(Color.White)
) { ) {
StatusBarSpacer() StatusBarSpacer()
@@ -137,7 +167,7 @@ fun ChatScreen(userId: String) {
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 16.dp, horizontal = 16.dp), .padding(vertical = 16.dp, horizontal = 16.dp),
horizontalArrangement = Arrangement.Start, horizontalArrangement = Arrangement.Start,
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Image( Image(
painter = painterResource(R.drawable.rider_pro_nav_back), painter = painterResource(R.drawable.rider_pro_nav_back),
@@ -178,7 +208,7 @@ fun ChatScreen(userId: String) {
MenuItem( MenuItem(
title = if (viewModel.notificationStrategy == "mute") "Unmute" else "Mute", title = if (viewModel.notificationStrategy == "mute") "Unmute" else "Mute",
icon = if (viewModel.notificationStrategy == "mute") R.drawable.rider_pro_notice_mute else R.drawable.rider_pro_notice_active, icon = if (viewModel.notificationStrategy == "mute") R.drawable.rider_pro_notice_mute else R.drawable.rider_pro_notice_active,
){ ) {
isMenuExpanded = false isMenuExpanded = false
viewModel.viewModelScope.launch { viewModel.viewModelScope.launch {
@@ -191,7 +221,7 @@ fun ChatScreen(userId: String) {
} }
), ),
) )
} }
} }
} }
@@ -221,36 +251,74 @@ fun ChatScreen(userId: String) {
} }
} }
) { paddingValues -> ) { paddingValues ->
LazyColumn( Box(
state = listState,
modifier = Modifier modifier = Modifier
.padding(paddingValues) .fillMaxSize()
.background(Color(0xfff7f7f7)) .background(Color(0xfff7f7f7))
.fillMaxSize(), .padding(paddingValues)
reverseLayout = true,
verticalArrangement = Arrangement.Top
) { ) {
val chatList = groupMessagesByTime(viewModel.getDisplayChatList(), viewModel) LazyColumn(
items(chatList.size, key = { index -> chatList[index].msgId }) { index -> state = listState,
val item = chatList[index] modifier = Modifier
if (item.showTimeDivider) { .fillMaxSize(),
val calendar = java.util.Calendar.getInstance() reverseLayout = true,
calendar.timeInMillis = item.timestamp verticalArrangement = Arrangement.Top
) {
val chatList = groupMessagesByTime(viewModel.getDisplayChatList(), viewModel)
items(chatList.size, key = { index -> chatList[index].msgId }) { index ->
val item = chatList[index]
if (item.showTimeDivider) {
val calendar = java.util.Calendar.getInstance()
calendar.timeInMillis = item.timestamp
Text(
text = calendar.time.formatChatTime(context), // Format the timestamp
style = TextStyle(
color = Color.Gray,
fontSize = 14.sp,
textAlign = TextAlign.Center
),
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
)
}
ChatItem(item = item, viewModel.myProfile?.trtcUserId!!)
}
// item {
// Spacer(modifier = Modifier.height(72.dp))
// }
}
if (goToNewCount > 0) {
Box(
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(bottom = 16.dp, end = 16.dp)
.shadow(4.dp, shape = RoundedCornerShape(16.dp))
.clip(RoundedCornerShape(16.dp))
.background(Color.White)
.padding(8.dp)
.noRippleClickable {
coroutineScope.launch {
listState.scrollToItem(0)
}
},
) {
Text( Text(
text = calendar.time.formatChatTime(context), // Format the timestamp text = "${goToNewCount} New Message",
style = TextStyle( style = TextStyle(
color = Color.Gray, color = Color.Black,
fontSize = 14.sp, fontSize = 16.sp,
textAlign = TextAlign.Center
), ),
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
) )
} }
ChatItem(item = item, viewModel.myProfile?.trtcUserId!!)
} }
} }
} }
} }

View File

@@ -44,6 +44,7 @@ class ChatViewModel(
var lastMessage: V2TIMMessage? = null var lastMessage: V2TIMMessage? = null
val showTimestampMap = mutableMapOf<String, Boolean>() // Add this map val showTimestampMap = mutableMapOf<String, Boolean>() // Add this map
var chatNotification by mutableStateOf<ChatNotification?>(null) var chatNotification by mutableStateOf<ChatNotification?>(null)
var goToNew by mutableStateOf(false)
fun init(context: Context) { fun init(context: Context) {
// 获取用户信息 // 获取用户信息
viewModelScope.launch { viewModelScope.launch {
@@ -68,6 +69,7 @@ class ChatViewModel(
val chatItem = ChatItem.convertToChatItem(msg, context) val chatItem = ChatItem.convertToChatItem(msg, context)
chatItem?.let { chatItem?.let {
chatData = listOf(it) + chatData chatData = listOf(it) + chatData
goToNew = true
} }
} }
} }
@@ -94,7 +96,6 @@ class ChatViewModel(
} }
fun onLoadMore(context: Context) { fun onLoadMore(context: Context) {
if (!hasMore || isLoading) { if (!hasMore || isLoading) {
return return
@@ -145,13 +146,14 @@ class ChatViewModel(
val chatItem = ChatItem.convertToChatItem(p0!!, context) val chatItem = ChatItem.convertToChatItem(p0!!, context)
chatItem?.let { chatItem?.let {
chatData = listOf(it) + chatData chatData = listOf(it) + chatData
goToNew = true
} }
} }
} }
) )
} }
fun sendImageMessage(imageUri:Uri, context: Context) { fun sendImageMessage(imageUri: Uri, context: Context) {
val tempFile = createTempFile(context, imageUri) val tempFile = createTempFile(context, imageUri)
val imagePath = tempFile?.path val imagePath = tempFile?.path
if (imagePath != null) { if (imagePath != null) {
@@ -177,13 +179,15 @@ class ChatViewModel(
val chatItem = ChatItem.convertToChatItem(p0!!, context) val chatItem = ChatItem.convertToChatItem(p0!!, context)
chatItem?.let { chatItem?.let {
chatData = listOf(it) + chatData chatData = listOf(it) + chatData
} goToNew = true
}
} }
} }
) )
} }
} }
fun createTempFile(context: Context, uri: Uri): File? { fun createTempFile(context: Context, uri: Uri): File? {
return try { return try {
val projection = arrayOf(MediaStore.Images.Media.DATA) val projection = arrayOf(MediaStore.Images.Media.DATA)
@@ -195,7 +199,8 @@ class ChatViewModel(
val inputStream: InputStream? = context.contentResolver.openInputStream(uri) val inputStream: InputStream? = context.contentResolver.openInputStream(uri)
val mimeType = context.contentResolver.getType(uri) val mimeType = context.contentResolver.getType(uri)
val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)
val tempFile = File.createTempFile("temp_image", ".$extension", context.cacheDir) val tempFile =
File.createTempFile("temp_image", ".$extension", context.cacheDir)
val outputStream = FileOutputStream(tempFile) val outputStream = FileOutputStream(tempFile)
inputStream?.use { input -> inputStream?.use { input ->
@@ -247,7 +252,7 @@ class ChatViewModel(
return list return list
} }
suspend fun updateNotificationStrategy(strategy: String) { suspend fun updateNotificationStrategy(strategy: String) {
userProfile?.let { userProfile?.let {
val result = ChatState.updateChatNotification(it.id, strategy) val result = ChatState.updateChatNotification(it.id, strategy)
chatNotification = result chatNotification = result