更新聊天的滚动逻辑

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.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
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.modifiers.noRippleClickable
import com.tencent.imsdk.v2.V2TIMMessage
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
@@ -86,6 +89,7 @@ fun ChatScreen(userId: String) {
var isMenuExpanded by remember { mutableStateOf(false) }
val navController = LocalNavController.current
val context = LocalNavController.current.context
var goToNewCount by remember { mutableStateOf(0) }
val viewModel = viewModel<ChatViewModel>(
key = "ChatViewModel_$userId",
factory = object : ViewModelProvider.Factory {
@@ -109,17 +113,44 @@ fun ChatScreen(userId: String) {
val navigationBarHeight = with(LocalDensity.current) {
WindowInsets.navigationBars.getBottom(this).toDp()
}
var inBottom by remember { mutableStateOf(true) }
// 监听滚动状态,触发加载更多
LaunchedEffect(listState) {
snapshotFlow { listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index }
.collect { index ->
Log.d("ChatScreen", "lastVisibleItemIndex: ${index}")
if (index == listState.layoutInfo.totalItemsCount - 1) {
coroutineScope.launch {
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(
modifier = Modifier
@@ -128,7 +159,6 @@ fun ChatScreen(userId: String) {
Column(
modifier = Modifier
.fillMaxWidth()
.border(1.dp, Color(0xffe5e5e5))
.background(Color.White)
) {
StatusBarSpacer()
@@ -137,7 +167,7 @@ fun ChatScreen(userId: String) {
.fillMaxWidth()
.padding(vertical = 16.dp, horizontal = 16.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(R.drawable.rider_pro_nav_back),
@@ -178,7 +208,7 @@ fun ChatScreen(userId: String) {
MenuItem(
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,
){
) {
isMenuExpanded = false
viewModel.viewModelScope.launch {
@@ -191,7 +221,7 @@ fun ChatScreen(userId: String) {
}
),
)
)
}
}
}
@@ -221,36 +251,74 @@ fun ChatScreen(userId: String) {
}
}
) { paddingValues ->
LazyColumn(
state = listState,
Box(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
.background(Color(0xfff7f7f7))
.fillMaxSize(),
reverseLayout = true,
verticalArrangement = Arrangement.Top
.padding(paddingValues)
) {
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
LazyColumn(
state = listState,
modifier = Modifier
.fillMaxSize(),
reverseLayout = true,
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 = calendar.time.formatChatTime(context), // Format the timestamp
text = "${goToNewCount} New Message",
style = TextStyle(
color = Color.Gray,
fontSize = 14.sp,
textAlign = TextAlign.Center
color = Color.Black,
fontSize = 16.sp,
),
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
val showTimestampMap = mutableMapOf<String, Boolean>() // Add this map
var chatNotification by mutableStateOf<ChatNotification?>(null)
var goToNew by mutableStateOf(false)
fun init(context: Context) {
// 获取用户信息
viewModelScope.launch {
@@ -68,6 +69,7 @@ class ChatViewModel(
val chatItem = ChatItem.convertToChatItem(msg, context)
chatItem?.let {
chatData = listOf(it) + chatData
goToNew = true
}
}
}
@@ -94,7 +96,6 @@ class ChatViewModel(
}
fun onLoadMore(context: Context) {
if (!hasMore || isLoading) {
return
@@ -145,13 +146,14 @@ class ChatViewModel(
val chatItem = ChatItem.convertToChatItem(p0!!, context)
chatItem?.let {
chatData = listOf(it) + chatData
goToNew = true
}
}
}
)
}
fun sendImageMessage(imageUri:Uri, context: Context) {
fun sendImageMessage(imageUri: Uri, context: Context) {
val tempFile = createTempFile(context, imageUri)
val imagePath = tempFile?.path
if (imagePath != null) {
@@ -177,13 +179,15 @@ class ChatViewModel(
val chatItem = ChatItem.convertToChatItem(p0!!, context)
chatItem?.let {
chatData = listOf(it) + chatData
}
goToNew = true
}
}
}
)
}
}
fun createTempFile(context: Context, uri: Uri): File? {
return try {
val projection = arrayOf(MediaStore.Images.Media.DATA)
@@ -195,7 +199,8 @@ class ChatViewModel(
val inputStream: InputStream? = context.contentResolver.openInputStream(uri)
val mimeType = context.contentResolver.getType(uri)
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)
inputStream?.use { input ->
@@ -247,7 +252,7 @@ class ChatViewModel(
return list
}
suspend fun updateNotificationStrategy(strategy: String) {
suspend fun updateNotificationStrategy(strategy: String) {
userProfile?.let {
val result = ChatState.updateChatNotification(it.id, strategy)
chatNotification = result