新增消息界面“全部”标签页

This commit is contained in:
2025-10-14 17:40:25 +08:00
parent f0a9704e2d
commit 9d3d13a22d
6 changed files with 463 additions and 14 deletions

View File

@@ -78,7 +78,7 @@ import com.aiosman.ravenow.ui.like.LikeNoticeViewModel
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import kotlinx.coroutines.launch
import com.aiosman.ravenow.ui.index.tabs.message.tab.AllChatListScreen
/**
* 消息列表界面
@@ -95,7 +95,7 @@ fun NotificationsScreen() {
val navController = LocalNavController.current
val systemUiController = rememberSystemUiController()
val context = LocalContext.current
var pagerState = rememberPagerState (pageCount = { 3 })
var pagerState = rememberPagerState (pageCount = { 4 })
var scope = rememberCoroutineScope()
val state = rememberPullRefreshState(MessageListViewModel.isLoading, onRefresh = {
MessageListViewModel.viewModelScope.launch {
@@ -324,7 +324,7 @@ fun NotificationsScreen() {
Box {
TabItem(
text = stringResource(R.string.chat_ai),
text = stringResource(R.string.chat_all),
isSelected = pagerState.currentPage == 0,
onClick = {
tabDebouncer {
@@ -335,6 +335,38 @@ fun NotificationsScreen() {
}
)
// 全部未读消息红点
val totalUnreadCount = AgentChatListViewModel.totalUnreadCount +
GroupChatListViewModel.totalUnreadCount +
FriendChatListViewModel.totalUnreadCount
if (totalUnreadCount > 0) {
Box(
modifier = Modifier
.size(8.dp)
.background(
color = Color(0xFFFF3B30),
shape = CircleShape
)
.align(Alignment.TopEnd)
.offset(x = 8.dp, y = (-4).dp)
)
}
}
TabSpacer()
Box {
TabItem(
text = stringResource(R.string.chat_ai),
isSelected = pagerState.currentPage == 1,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(1)
}
}
}
)
// 智能体未读消息红点
if (AgentChatListViewModel.totalUnreadCount > 0) {
Box(
@@ -353,11 +385,11 @@ fun NotificationsScreen() {
Box {
TabItem(
text = stringResource(R.string.chat_group),
isSelected = pagerState.currentPage == 1,
isSelected = pagerState.currentPage == 2,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(1)
pagerState.animateScrollToPage(2)
}
}
}
@@ -378,14 +410,15 @@ fun NotificationsScreen() {
}
}
TabSpacer()
Box {
TabItem(
text = stringResource(R.string.chat_friend),
isSelected = pagerState.currentPage == 2,
isSelected = pagerState.currentPage == 3,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(2)
pagerState.animateScrollToPage(3)
}
}
}
@@ -414,14 +447,17 @@ fun NotificationsScreen() {
) {
when (it) {
0 -> {
AllChatListScreen()
}
1 -> {
AgentChatListScreen()
}
1 -> {
2 -> {
GroupChatListScreen()
}
2 -> {
3 -> {
FriendChatListScreen()
}
}

View File

@@ -0,0 +1,390 @@
package com.aiosman.ravenow.ui.index.tabs.message.tab
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.composables.rememberDebouncer
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.aiosman.ravenow.utils.NetworkUtils
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.ui.text.font.FontFamily
data class CombinedConversation(
val type: String, // "agent", "group", or "friend"
val agentConversation: AgentConversation? = null,
val groupConversation: GroupConversation? = null,
val friendConversation: FriendConversation? = null
) {
val id: String
get() = when (type) {
"agent" -> "agent_${agentConversation?.id ?: 0}"
"group" -> "group_${groupConversation?.id ?: 0}"
"friend" -> "friend_${friendConversation?.id ?: 0}"
else -> ""
}
val avatar: String
get() = when (type) {
"agent" -> agentConversation?.avatar ?: ""
"group" -> groupConversation?.avatar ?: ""
"friend" -> friendConversation?.avatar ?: ""
else -> ""
}
val name: String
get() = when (type) {
"agent" -> agentConversation?.nickname ?: ""
"group" -> groupConversation?.groupName ?: ""
"friend" -> friendConversation?.nickname ?: ""
else -> ""
}
val lastMessageTime: String
get() = when (type) {
"agent" -> agentConversation?.lastMessageTime ?: ""
"group" -> groupConversation?.lastMessageTime ?: ""
"friend" -> friendConversation?.lastMessageTime ?: ""
else -> ""
}
val displayText: String
get() = when (type) {
"agent" -> agentConversation?.displayText ?: ""
"group" -> groupConversation?.displayText ?: ""
"friend" -> friendConversation?.displayText ?: ""
else -> ""
}
val unreadCount: Int
get() = when (type) {
"agent" -> agentConversation?.unreadCount ?: 0
"group" -> groupConversation?.unreadCount ?: 0
"friend" -> friendConversation?.unreadCount ?: 0
else -> 0
}
val isSelf: Boolean
get() = when (type) {
"agent" -> agentConversation?.isSelf ?: false
"group" -> groupConversation?.isSelf ?: false
"friend" -> friendConversation?.isSelf ?: false
else -> false
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun AllChatListScreen() {
val context = LocalContext.current
val navController = LocalNavController.current
val AppColors = LocalAppTheme.current
var allConversations by remember { mutableStateOf<List<CombinedConversation>>(emptyList()) }
var refreshing by remember { mutableStateOf(false) }
var isLoading by remember { mutableStateOf(false) }
var error by remember { mutableStateOf<String?>(null) }
val state = rememberPullRefreshState(
refreshing = refreshing,
onRefresh = {
refreshing = true
refreshAllData(context,
onSuccess = { conversations ->
allConversations = conversations
refreshing = false
},
onError = { errorMsg ->
error = errorMsg
refreshing = false
}
)
}
)
LaunchedEffect(Unit) {
isLoading = true
refreshAllData(context,
onSuccess = { conversations ->
allConversations = conversations
isLoading = false
},
onError = { errorMsg ->
error = errorMsg
isLoading = false
}
)
}
Column(
modifier = Modifier
.fillMaxSize()
.background(AppColors.background)
) {
Box(
modifier = Modifier
.fillMaxSize()
.pullRefresh(state)
) {
if (allConversations.isEmpty() && !isLoading) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(context)
if (isNetworkAvailable) {
Spacer(modifier = Modifier.height(39.dp))
Image(
painter = painterResource(
id = if(AppState.darkMode) R.mipmap.qs_py_qs_as_img
else R.mipmap.invalid_name_2),
contentDescription = "null data",
modifier = Modifier
.size(181.dp)
)
Spacer(modifier = Modifier.height(24.dp))
Text(
text = stringResource(R.string.friend_chat_empty_title),
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.friend_chat_empty_subtitle),
color = AppColors.secondaryText,
fontSize = 14.sp
)
} else {
Spacer(modifier = Modifier.height(39.dp))
Image(
painter = painterResource(id = R.mipmap.invalid_name_10),
contentDescription = "network error",
modifier = Modifier
.size(181.dp)
)
Spacer(modifier = Modifier.height(24.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_title),
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_subtitle),
color = AppColors.secondaryText,
fontSize = 14.sp
)
Spacer(modifier = Modifier.height(16.dp))
ReloadButton(
onClick = {
isLoading = true
refreshAllData(context,
onSuccess = { conversations ->
allConversations = conversations
isLoading = false
},
onError = { errorMsg ->
error = errorMsg
isLoading = false
}
)
}
)
}
}
} else {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
itemsIndexed(
items = allConversations,
key = { _, item -> item.id }
) { index, item ->
when (item.type) {
"agent" -> {
item.agentConversation?.let { agent ->
AgentChatItem(
conversation = agent,
onUserAvatarClick = { conv ->
AgentChatListViewModel.goToUserDetail(conv, navController)
},
onChatClick = { conv ->
if (NetworkUtils.isNetworkAvailable(context)) {
AgentChatListViewModel.createSingleChat(conv.trtcUserId)
AgentChatListViewModel.goToChatAi(conv.trtcUserId, navController)
} else {
Toast.makeText(context, "网络连接异常,请检查网络设置", Toast.LENGTH_SHORT).show()
}
}
)
}
}
"group" -> {
item.groupConversation?.let { group ->
GroupChatItem(
conversation = group,
onGroupAvatarClick = { conv ->
GroupChatListViewModel.goToGroupDetail(conv, navController)
},
onChatClick = { conv ->
if (NetworkUtils.isNetworkAvailable(context)) {
GroupChatListViewModel.goToChat(conv, navController)
} else {
Toast.makeText(context, "网络连接异常,请检查网络设置", Toast.LENGTH_SHORT).show()
}
}
)
}
}
"friend" -> {
item.friendConversation?.let { friend ->
FriendChatItem(
conversation = friend,
onUserAvatarClick = { conv ->
FriendChatListViewModel.goToUserDetail(conv, navController)
},
onChatClick = { conv ->
if (NetworkUtils.isNetworkAvailable(context)) {
FriendChatListViewModel.goToChat(conv, navController)
} else {
Toast.makeText(context, "网络连接异常,请检查网络设置", Toast.LENGTH_SHORT).show()
}
}
)
}
}
}
// 分隔线
// if (index < allConversations.size - 1) {
// HorizontalDivider(
// modifier = Modifier.padding(horizontal = 24.dp),
// color = AppColors.divider
// )
// }
}
if (isLoading && allConversations.isNotEmpty()) {
item {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = AppColors.main
)
}
}
}
}
}
PullRefreshIndicator(
refreshing = refreshing,
state = state,
modifier = Modifier.align(Alignment.TopCenter)
)
}
error?.let { errorMsg ->
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Text(
text = errorMsg,
color = AppColors.error,
fontSize = 14.sp
)
}
}
}
}
fun refreshAllData(
context: android.content.Context,
onSuccess: (List<CombinedConversation>) -> Unit,
onError: (String) -> Unit
) {
try {
// 同时刷新所有类型的数据
AgentChatListViewModel.refreshPager(context = context)
GroupChatListViewModel.refreshPager(context = context)
FriendChatListViewModel.refreshPager(context = context)
val combinedList = mutableListOf<CombinedConversation>()
AgentChatListViewModel.agentChatList.forEach { agent ->
combinedList.add(CombinedConversation(type = "agent", agentConversation = agent))
}
GroupChatListViewModel.groupChatList.forEach { group ->
combinedList.add(CombinedConversation(type = "group", groupConversation = group))
}
FriendChatListViewModel.friendChatList.forEach { friend ->
val isDuplicate = combinedList.any {//判断重复
it.type == "agent" && it.agentConversation?.trtcUserId == friend.trtcUserId
}
if (!isDuplicate) {
combinedList.add(CombinedConversation(type = "friend", friendConversation = friend))
}
}
// 按最后消息时间排序
val sortedList = combinedList.sortedByDescending {
it.lastMessageTime
}
onSuccess(sortedList)
} catch (e: Exception) {
onError("刷新数据失败: ${e.message}")
}
}

View File

@@ -48,7 +48,7 @@ fun SelfProfileAction(
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(18.dp))
.clip(RoundedCornerShape(10.dp))
.background(AppColors.nonActive)
.padding(horizontal = 16.dp, vertical = 12.dp)
.noRippleClickable {
@@ -60,7 +60,27 @@ fun SelfProfileAction(
Text(
text = stringResource(R.string.edit_profile),
fontSize = 14.sp,
fontWeight = FontWeight.W600,
fontWeight = FontWeight.W900,
color = AppColors.text,
)
}
// 预留按钮位置
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(10.dp))
.padding(horizontal = 16.dp, vertical = 12.dp)
.noRippleClickable {
}
) {
Text(
text = "",
fontSize = 14.sp,
fontWeight = FontWeight.W900,
color = AppColors.text,
)
}
@@ -71,7 +91,7 @@ fun SelfProfileAction(
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(18.dp))
.clip(RoundedCornerShape(10.dp))
.background(AppColors.nonActive)
.padding(horizontal = 16.dp, vertical = 12.dp)
.noRippleClickable {
@@ -83,7 +103,7 @@ fun SelfProfileAction(
Text(
text = stringResource(R.string.share),
fontSize = 14.sp,
fontWeight = FontWeight.W600,
fontWeight = FontWeight.W900,
color = AppColors.text,
)
}