更新聊天的滚动逻辑
This commit is contained in:
@@ -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!!)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user