消息列表和聊天时调整

This commit is contained in:
weber
2025-08-21 17:08:18 +08:00
parent edcab76fdb
commit 5ee1897739
22 changed files with 906 additions and 182 deletions

View File

@@ -41,7 +41,9 @@ data class AgentMomentRequestBody(
data class SingleChatRequestBody( data class SingleChatRequestBody(
@SerializedName("agentOpenId") @SerializedName("agentOpenId")
val generateText: String, val generateText: String? = null,
@SerializedName("agentTrtcId")
val agentTrtcId: String? = null,
) )
data class GroupChatRequestBody( data class GroupChatRequestBody(
@@ -561,6 +563,11 @@ interface RaveNowAPI {
@Query("isRecommended") isRecommended: Int = 1, @Query("isRecommended") isRecommended: Int = 1,
): Response<ListContainer<Room>> ): Response<ListContainer<Room>>
@GET("rooms/detail")
suspend fun getRoomDetail(@Query("trtcId") trtcId: String,
): Response<DataContainer<Room>>
} }

View File

@@ -0,0 +1,16 @@
package com.aiosman.ravenow.entity
data class GroupMember(
val userId: String,
val nickname: String,
val avatar: String,
val isOwner: Boolean = false
)
data class GroupInfo(
val groupId: String,
val groupName: String,
val groupAvatar: String,
val memberCount: Int,
val members: List<GroupMember>
)

View File

@@ -35,6 +35,7 @@ import com.aiosman.ravenow.ui.agent.AddAgentScreen
import com.aiosman.ravenow.ui.group.CreateGroupChatScreen import com.aiosman.ravenow.ui.group.CreateGroupChatScreen
import com.aiosman.ravenow.ui.chat.ChatAiScreen import com.aiosman.ravenow.ui.chat.ChatAiScreen
import com.aiosman.ravenow.ui.chat.ChatScreen import com.aiosman.ravenow.ui.chat.ChatScreen
import com.aiosman.ravenow.ui.group.GroupChatInfoScreen
import com.aiosman.ravenow.ui.chat.GroupChatScreen import com.aiosman.ravenow.ui.chat.GroupChatScreen
import com.aiosman.ravenow.ui.comment.CommentsScreen import com.aiosman.ravenow.ui.comment.CommentsScreen
import com.aiosman.ravenow.ui.comment.notice.CommentNoticeScreen import com.aiosman.ravenow.ui.comment.notice.CommentNoticeScreen
@@ -96,6 +97,7 @@ sealed class NavigationRoute(
data object Chat : NavigationRoute("Chat/{id}") data object Chat : NavigationRoute("Chat/{id}")
data object ChatAi : NavigationRoute("ChatAi/{id}") data object ChatAi : NavigationRoute("ChatAi/{id}")
data object ChatGroup : NavigationRoute("ChatGroup/{id}/{name}/{avatar}") data object ChatGroup : NavigationRoute("ChatGroup/{id}/{name}/{avatar}")
data object GroupChatInfo : NavigationRoute("GroupChatInfo/{groupId}")
data object CommentNoticeScreen : NavigationRoute("CommentNoticeScreen") data object CommentNoticeScreen : NavigationRoute("CommentNoticeScreen")
data object ImageCrop : NavigationRoute("ImageCrop") data object ImageCrop : NavigationRoute("ImageCrop")
data object AccountSetting : NavigationRoute("AccountSetting") data object AccountSetting : NavigationRoute("AccountSetting")
@@ -404,6 +406,18 @@ fun NavigationController(
} }
} }
composable(
route = NavigationRoute.GroupChatInfo.route,
arguments = listOf(navArgument("groupId") { type = NavType.StringType })
) {
val groupId = it.arguments?.getString("groupId") ?: ""
CompositionLocalProvider(
LocalAnimatedContentScope provides this,
) {
GroupChatInfoScreen(groupId)
}
}
composable(route = NavigationRoute.CommentNoticeScreen.route) { composable(route = NavigationRoute.CommentNoticeScreen.route) {
CompositionLocalProvider( CompositionLocalProvider(

View File

@@ -8,6 +8,7 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
@@ -49,6 +50,7 @@ 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
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.focus.onFocusChanged
@@ -229,7 +231,8 @@ fun GroupChatScreen(groupId: String,name: String,avatar: String,) {
modifier = Modifier modifier = Modifier
.size(28.dp) .size(28.dp)
.noRippleClickable { .noRippleClickable {
isMenuExpanded = true // 跳转到群聊信息页面
navController.navigate("GroupChatInfo/$groupId")
}, },
contentDescription = null, contentDescription = null,
colorFilter = ColorFilter.tint(AppColors.text) colorFilter = ColorFilter.tint(AppColors.text)
@@ -276,9 +279,8 @@ fun GroupChatScreen(groupId: String,name: String,avatar: String,) {
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(1.dp) .height(1.dp)
.background(AppColors.decentBackground)
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(12.dp))
GroupChatInput( GroupChatInput(
onSendImage = { uri -> onSendImage = { uri ->
uri?.let { uri?.let {
@@ -294,7 +296,7 @@ fun GroupChatScreen(groupId: String,name: String,avatar: String,) {
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(AppColors.decentBackground) .background(AppColors.background)
.padding(paddingValues) .padding(paddingValues)
) { ) {
LazyColumn( LazyColumn(
@@ -306,23 +308,31 @@ fun GroupChatScreen(groupId: String,name: String,avatar: String,) {
val chatList = groupMessagesByTime(viewModel.getDisplayChatList(), viewModel) val chatList = groupMessagesByTime(viewModel.getDisplayChatList(), viewModel)
items(chatList.size, key = { index -> chatList[index].msgId + UUID.randomUUID().toString()}) { index -> items(chatList.size, key = { index -> chatList[index].msgId + UUID.randomUUID().toString()}) { index ->
val item = chatList[index] val item = chatList[index]
if (item.showTimeDivider) { Column {
val calendar = java.util.Calendar.getInstance() if (item.showTimeDivider) {
calendar.timeInMillis = item.timestamp val calendar = java.util.Calendar.getInstance()
Text( calendar.timeInMillis = item.timestamp
text = calendar.time.formatChatTime(context), Text(
style = TextStyle( text = calendar.time.formatChatTime(context),
color = AppColors.secondaryText, style = TextStyle(
fontSize = 14.sp, color = AppColors.secondaryText,
textAlign = TextAlign.Center fontSize = 11.sp,
), textAlign = TextAlign.Center
modifier = Modifier ),
.fillMaxWidth() modifier = Modifier
.padding(vertical = 8.dp) .fillMaxWidth()
.padding(vertical = 8.dp)
)
}
// 获取上一个item的userId用于判断是否显示头像和昵称
val previousItem = if (index < chatList.size - 1) chatList[index + 1] else null
val showAvatarAndNickname = previousItem?.userId != item.userId
GroupChatItem(
item = item,
currentUserId = viewModel.myProfile?.trtcUserId!!,
showAvatarAndNickname = showAvatarAndNickname
) )
} }
GroupChatItem(item = item, viewModel.myProfile?.trtcUserId!!)
} }
} }
@@ -345,7 +355,7 @@ fun GroupChatScreen(groupId: String,name: String,avatar: String,) {
text = "${goToNewCount} 条新消息", text = "${goToNewCount} 条新消息",
style = TextStyle( style = TextStyle(
color = AppColors.text, color = AppColors.text,
fontSize = 16.sp, fontSize = 12.sp,
), ),
) )
} }
@@ -390,7 +400,7 @@ fun GroupChatSelfItem(item: ChatItem) {
vertical = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 8.dp else 0.dp), vertical = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 8.dp else 0.dp),
horizontal = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 16.dp else 0.dp) horizontal = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 16.dp else 0.dp)
) )
.padding(bottom = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 3.dp else 0.dp))
) { ) {
when (item.messageType) { when (item.messageType) {
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> { V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
@@ -441,7 +451,7 @@ fun GroupChatSelfItem(item: ChatItem) {
} }
@Composable @Composable
fun GroupChatOtherItem(item: ChatItem) { fun GroupChatOtherItem(item: ChatItem, showAvatarAndNickname: Boolean = true) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
Column( Column(
@@ -451,30 +461,27 @@ fun GroupChatOtherItem(item: ChatItem) {
) { ) {
Row( Row(
horizontalArrangement = Arrangement.Start, horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.Bottom,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Box( if (showAvatarAndNickname) {
modifier = Modifier Box(
.size(24.dp) modifier = Modifier
.clip(RoundedCornerShape(24.dp)) .size(24.dp)
) { .clip(RoundedCornerShape(24.dp))
CustomAsyncImage( ) {
imageUrl = item.avatar.replace("storage/avatars/", "/avatar/"), CustomAsyncImage(
modifier = Modifier.fillMaxSize(), imageUrl = item.avatar.replace("storage/avatars/", "/avatar/"),
contentDescription = "avatar" modifier = Modifier.fillMaxSize(),
) contentDescription = "avatar"
)
}
Spacer(modifier = Modifier.width(12.dp))
} else {
// 当不显示头像时,添加左边距以保持消息对齐
Spacer(modifier = Modifier.width(36.dp))
} }
Spacer(modifier = Modifier.width(12.dp))
Column { Column {
Text(
text = item.nickname,
style = TextStyle(
color = AppColors.secondaryText,
fontSize = 12.sp,
),
modifier = Modifier.padding(bottom = 2.dp)
)
Box( Box(
modifier = Modifier modifier = Modifier
.widthIn( .widthIn(
@@ -487,7 +494,6 @@ fun GroupChatOtherItem(item: ChatItem) {
vertical = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 8.dp else 0.dp), vertical = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 8.dp else 0.dp),
horizontal = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 16.dp else 0.dp) horizontal = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 16.dp else 0.dp)
) )
.padding(bottom = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 3.dp else 0.dp))
) { ) {
when (item.messageType) { when (item.messageType) {
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> { V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
@@ -520,13 +526,24 @@ fun GroupChatOtherItem(item: ChatItem) {
} }
} }
} }
if (showAvatarAndNickname) {
Text(
text = item.nickname,
style = TextStyle(
color = AppColors.secondaryText,
fontSize = 12.sp,
),
modifier = Modifier.padding(top = 2.dp)
)
}
} }
} }
} }
} }
@Composable @Composable
fun GroupChatItem(item: ChatItem, currentUserId: String) { fun GroupChatItem(item: ChatItem, currentUserId: String, showAvatarAndNickname: Boolean = true) {
val isCurrentUser = item.userId == currentUserId val isCurrentUser = item.userId == currentUserId
// 管理员消息显示特殊布局 // 管理员消息显示特殊布局
@@ -538,7 +555,7 @@ fun GroupChatItem(item: ChatItem, currentUserId: String) {
// 根据是否是当前用户显示不同样式 // 根据是否是当前用户显示不同样式
when (item.userId) { when (item.userId) {
currentUserId -> GroupChatSelfItem(item) currentUserId -> GroupChatSelfItem(item)
else -> GroupChatOtherItem(item) else -> GroupChatOtherItem(item, showAvatarAndNickname)
} }
} }
@@ -615,20 +632,21 @@ fun GroupChatInput(
onSendImage(uri) onSendImage(uri)
} }
} }
Box( modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp),){
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp) .clip(RoundedCornerShape(20.dp))
.padding(bottom = inputBarHeight) .background(appColors.decentBackground)
.padding(start = 16.dp, end = 8.dp, top = 2.dp, bottom = 2.dp),
verticalAlignment = Alignment.CenterVertically,
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.clip(RoundedCornerShape(16.dp))
.background(appColors.background)
.padding(horizontal = 16.dp),
contentAlignment = Alignment.CenterStart,
) { ) {
BasicTextField( BasicTextField(
value = text, value = text,
@@ -640,6 +658,7 @@ fun GroupChatInput(
fontSize = 16.sp fontSize = 16.sp
), ),
cursorBrush = SolidColor(appColors.text), cursorBrush = SolidColor(appColors.text),
singleLine = true,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 8.dp) .padding(vertical = 8.dp)
@@ -662,44 +681,32 @@ fun GroupChatInput(
) )
) )
} }
Spacer(modifier = Modifier.width(16.dp))
Icon(
painter = painterResource(id = R.drawable.rider_pro_camera),
contentDescription = "发送图片",
modifier = Modifier
.size(30.dp)
.noRippleClickable {
imagePickUpLauncher.launch(
Intent.createChooser(
Intent(Intent.ACTION_GET_CONTENT).apply {
type = "image/*"
},
"选择图片"
)
)
},
tint = appColors.chatActionColor
)
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Crossfade( Crossfade(
targetState = text.isNotEmpty(), animationSpec = tween(500), targetState = text.isNotEmpty(), animationSpec = tween(500),
label = "" label = ""
) { isNotEmpty -> ) { isNotEmpty ->
Icon( val alpha by animateFloatAsState(
painter = painterResource(id = R.drawable.rider_pro_video_share), targetValue = if (isNotEmpty) 1f else 0.5f,
contentDescription = "发送消息", animationSpec = tween(300)
)
Image(
painter = painterResource(R.mipmap.rider_pro_im_send),
modifier = Modifier modifier = Modifier
.size(32.dp) .size(24.dp)
.alpha(alpha)
.noRippleClickable { .noRippleClickable {
if (text.isNotEmpty()) { if (text.isNotEmpty()) {
onSend(text) onSend(text)
text = "" text = ""
} }
}, },
tint = if (isNotEmpty) appColors.main else appColors.chatActionColor contentDescription = null,
) )
} }
} }
}
} }
fun groupMessagesByTime(chatList: List<ChatItem>, viewModel: GroupChatViewModel): List<ChatItem> { fun groupMessagesByTime(chatList: List<ChatItem>, viewModel: GroupChatViewModel): List<ChatItem> {
@@ -711,7 +718,7 @@ fun groupMessagesByTime(chatList: List<ChatItem>, viewModel: GroupChatViewModel)
} }
val currentMessage = chatList[i] val currentMessage = chatList[i]
val timeDiff = currentMessage.timestamp - chatList[i - 1].timestamp val timeDiff = currentMessage.timestamp - chatList[i - 1].timestamp
if (-timeDiff > 30 * 60 * 1000) { if (-timeDiff > 10 * 60 * 1000) {
viewModel.showTimestampMap[currentMessage.msgId] = true viewModel.showTimestampMap[currentMessage.msgId] = true
currentMessage.showTimeDivider = true currentMessage.showTimeDivider = true
} }

View File

@@ -0,0 +1,386 @@
package com.aiosman.ravenow.ui.group
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.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.compose.viewModel
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.StatusBarSpacer
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
@Composable
fun GroupChatInfoScreen(groupId: String) {
val navController = LocalNavController.current
val context = LocalContext.current
val AppColors = LocalAppTheme.current
val viewModel = viewModel<GroupChatInfoViewModel>(
key = "GroupChatInfoViewModel_$groupId",
factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return GroupChatInfoViewModel(groupId) as T
}
}
)
Column(
modifier = Modifier
.fillMaxSize()
.background(AppColors.background)
) {
// 顶部导航栏
Column(
modifier = Modifier
.fillMaxWidth()
.background(AppColors.background)
) {
StatusBarSpacer()
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp, horizontal = 16.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(R.drawable.rider_pro_back_icon),
modifier = Modifier
.size(24.dp)
.noRippleClickable {
navController.navigateUp()
},
contentDescription = null,
colorFilter = ColorFilter.tint(AppColors.text)
)
Spacer(modifier = Modifier.width(16.dp))
Text(
modifier = Modifier.fillMaxWidth(),
text = "群聊信息",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text,
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
)
Spacer(modifier = Modifier.width(40.dp))
}
}
// 内容区域
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp)
) {
// 群聊头像和名称
item {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
if (viewModel.groupInfo?.groupAvatar?.isNotEmpty() == true) {
CustomAsyncImage(
imageUrl = viewModel.groupInfo!!.groupAvatar,
modifier = Modifier
.size(80.dp)
.clip(CircleShape),
contentDescription = "群聊头像"
)
} else {
Box(
modifier = Modifier
.size(80.dp)
.clip(CircleShape)
.background(AppColors.decentBackground),
contentAlignment = Alignment.Center
) {
Text(
text = viewModel.groupInfo?.groupName?.firstOrNull()?.toString() ?: "",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text,
fontSize = 24.sp,
fontWeight = FontWeight.Bold
)
)
}
}
Spacer(modifier = Modifier.height(16.dp))
Text(
text = viewModel.groupInfo?.groupName ?: "群聊",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text,
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "更改名称和图片",
style = androidx.compose.ui.text.TextStyle(
color = Color(0xFF007AFF),
fontSize = 14.sp
),
modifier = Modifier.noRippleClickable {
// TODO: 实现更改群聊名称和图片功能
}
)
}
}
// 操作按钮
item {
Spacer(modifier = Modifier.height(32.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
// 添加其他人
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.noRippleClickable {
// TODO: 实现添加其他人功能
}
) {
Box(
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.background(Color(0xFF007AFF)),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(R.drawable.rider_pro_back_icon),
modifier = Modifier.size(24.dp),
contentDescription = null,
colorFilter = ColorFilter.tint(Color.White)
)
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "添加其他人",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text,
fontSize = 12.sp
)
)
}
// 通知设置
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.noRippleClickable {
// TODO: 实现通知设置功能
}
) {
Box(
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.background(Color(0xFF007AFF)),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(R.drawable.rider_pro_notice_active),
modifier = Modifier.size(24.dp),
contentDescription = null,
colorFilter = ColorFilter.tint(Color.White)
)
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "通知",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text,
fontSize = 12.sp
)
)
}
// 退出群聊
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.noRippleClickable {
// TODO: 实现退出群聊功能
}
) {
Box(
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.background(Color(0xFFFF3B30)),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(R.drawable.rider_pro_back_icon),
modifier = Modifier.size(24.dp),
contentDescription = null,
colorFilter = ColorFilter.tint(Color.White)
)
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "退出",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text,
fontSize = 12.sp
)
)
}
}
}
// 设置选项
item {
Spacer(modifier = Modifier.height(32.dp))
// 设置聊天主题
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.background(AppColors.decentBackground)
.padding(16.dp)
.noRippleClickable {
// TODO: 实现设置聊天主题功能
},
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(R.drawable.rider_pro_back_icon),
modifier = Modifier.size(24.dp),
contentDescription = null,
colorFilter = ColorFilter.tint(AppColors.text)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = "设置聊天主题",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text,
fontSize = 16.sp
),
modifier = Modifier.weight(1f)
)
Image(
painter = painterResource(R.drawable.rider_pro_back_icon),
modifier = Modifier.size(16.dp),
contentDescription = null,
colorFilter = ColorFilter.tint(AppColors.secondaryText)
)
}
Spacer(modifier = Modifier.height(1.dp))
// 群聊成员
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.background(AppColors.decentBackground)
.padding(16.dp)
.noRippleClickable {
// TODO: 实现查看群聊成员功能
},
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(R.drawable.rider_pro_back_icon),
modifier = Modifier.size(24.dp),
contentDescription = null,
colorFilter = ColorFilter.tint(AppColors.text)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = "群聊成员 (${viewModel.groupInfo?.memberCount ?: 0})",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text,
fontSize = 16.sp
),
modifier = Modifier.weight(1f)
)
Image(
painter = painterResource(R.drawable.rider_pro_back_icon),
modifier = Modifier.size(16.dp),
contentDescription = null,
colorFilter = ColorFilter.tint(AppColors.secondaryText)
)
}
}
// 成员列表
if (viewModel.groupInfo?.members?.isNotEmpty() == true) {
item {
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "群成员",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.Bold
)
)
}
items(viewModel.groupInfo!!.members) { member ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
CustomAsyncImage(
imageUrl = member.avatar,
modifier = Modifier
.size(40.dp)
.clip(CircleShape),
contentDescription = "成员头像"
)
Spacer(modifier = Modifier.width(12.dp))
Column {
Text(
text = member.nickname,
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text,
fontSize = 16.sp
)
)
if (member.isOwner) {
Text(
text = "群主",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.secondaryText,
fontSize = 12.sp
)
)
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,60 @@
package com.aiosman.ravenow.ui.group
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.entity.GroupInfo
import com.aiosman.ravenow.entity.GroupMember
import kotlinx.coroutines.launch
class GroupChatInfoViewModel(
private val groupId: String
) : ViewModel() {
var groupInfo by mutableStateOf<GroupInfo?>(null)
var isLoading by mutableStateOf(false)
var error by mutableStateOf<String?>(null)
init {
loadGroupInfo()
}
private fun loadGroupInfo() {
viewModelScope.launch {
try {
isLoading = true
error = null
// 调用接口获取群聊详情
val response = ApiClient.api.getRoomDetail(trtcId = groupId)
// 使用接口返回的数据
val room = response.body()?.data
groupInfo = room?.let {
GroupInfo(
groupId = groupId,
groupName = it.name,
groupAvatar = room.avatar,
memberCount = room.userCount,
members = listOf(
GroupMember(
userId = room.creator.userId,
nickname = room.creator.profile.nickname,
avatar = room.creator.profile.avatar,
isOwner = true
)
)
)
}
} catch (e: Exception) {
error = e.message ?: "加载失败"
} finally {
isLoading = false
}
}
}
}

View File

@@ -21,8 +21,10 @@ import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.DrawerValue import androidx.compose.material3.DrawerValue
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@@ -55,6 +57,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.offset
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.AppState import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.AppStore import com.aiosman.ravenow.AppStore
@@ -66,6 +69,9 @@ import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.index.tabs.add.AddPage import com.aiosman.ravenow.ui.index.tabs.add.AddPage
import com.aiosman.ravenow.ui.index.tabs.ai.Agent import com.aiosman.ravenow.ui.index.tabs.ai.Agent
import com.aiosman.ravenow.ui.index.tabs.message.NotificationsScreen import com.aiosman.ravenow.ui.index.tabs.message.NotificationsScreen
import com.aiosman.ravenow.ui.index.tabs.message.tab.AgentChatListViewModel
import com.aiosman.ravenow.ui.index.tabs.message.tab.FriendChatListViewModel
import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListViewModel
import com.aiosman.ravenow.ui.index.tabs.moment.MomentsList import com.aiosman.ravenow.ui.index.tabs.moment.MomentsList
import com.aiosman.ravenow.ui.index.tabs.profile.ProfileWrap import com.aiosman.ravenow.ui.index.tabs.profile.ProfileWrap
import com.aiosman.ravenow.ui.index.tabs.search.DiscoverScreen import com.aiosman.ravenow.ui.index.tabs.search.DiscoverScreen
@@ -99,6 +105,10 @@ fun IndexScreen() {
val context = LocalContext.current val context = LocalContext.current
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
systemUiController.setNavigationBarColor(Color.Transparent) systemUiController.setNavigationBarColor(Color.Transparent)
// 初始化聊天列表以获取未读消息数
AgentChatListViewModel.refreshPager(context = context)
GroupChatListViewModel.refreshPager(context = context)
FriendChatListViewModel.refreshPager(context = context)
} }
LaunchedEffect(model.openDrawer) { LaunchedEffect(model.openDrawer) {
if (model.openDrawer) { if (model.openDrawer) {
@@ -311,12 +321,40 @@ fun IndexScreen() {
), ),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Box(
modifier = Modifier
.width(24.dp)
.height(24.dp)
,
contentAlignment = Alignment.Center
) {
Image( Image(
painter = painterResource(if (isSelected) it.selectedIcon() else it.icon()), painter = painterResource(if (isSelected) it.selectedIcon() else it.icon()),
contentDescription = it.label(), contentDescription = it.label(),
modifier = Modifier.size(24.dp), modifier = Modifier.size(24.dp),
colorFilter = if (!isSelected) ColorFilter.tint(AppColors.text) else null colorFilter = if (!isSelected) ColorFilter.tint(AppColors.text) else null
) )
// 消息按钮红点显示在图片右上角
if (it.route == NavigationItem.Notification.route) {
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 = 8.dp)
)
}
}
}
} }
// 文字标签,可控制间距 // 文字标签,可控制间距

View File

@@ -16,6 +16,8 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBars
@@ -50,6 +52,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.offset
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.aiosman.ravenow.AppState import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.AppStore import com.aiosman.ravenow.AppStore
@@ -63,8 +66,11 @@ import com.aiosman.ravenow.ui.composables.TabItem
import com.aiosman.ravenow.ui.composables.TabSpacer import com.aiosman.ravenow.ui.composables.TabSpacer
import com.aiosman.ravenow.ui.follower.FollowerNoticeViewModel import com.aiosman.ravenow.ui.follower.FollowerNoticeViewModel
import com.aiosman.ravenow.ui.index.tabs.message.tab.AgentChatListScreen import com.aiosman.ravenow.ui.index.tabs.message.tab.AgentChatListScreen
import com.aiosman.ravenow.ui.index.tabs.message.tab.AgentChatListViewModel
import com.aiosman.ravenow.ui.index.tabs.message.tab.FriendChatListScreen import com.aiosman.ravenow.ui.index.tabs.message.tab.FriendChatListScreen
import com.aiosman.ravenow.ui.index.tabs.message.tab.FriendChatListViewModel
import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListScreen import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListScreen
import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListViewModel
import com.aiosman.ravenow.ui.like.LikeNoticeViewModel import com.aiosman.ravenow.ui.like.LikeNoticeViewModel
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.accompanist.systemuicontroller.rememberSystemUiController
@@ -77,6 +83,10 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class) @OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
@Composable @Composable
fun NotificationsScreen() { fun NotificationsScreen() {
// 计算总未读消息数
val totalUnreadCount = AgentChatListViewModel.totalUnreadCount +
GroupChatListViewModel.totalUnreadCount +
FriendChatListViewModel.totalUnreadCount
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val navController = LocalNavController.current val navController = LocalNavController.current
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
@@ -87,6 +97,12 @@ fun NotificationsScreen() {
MessageListViewModel.viewModelScope.launch { MessageListViewModel.viewModelScope.launch {
MessageListViewModel.initData(context, force = true, loadChat = AppState.enableChat) MessageListViewModel.initData(context, force = true, loadChat = AppState.enableChat)
} }
// 刷新群聊列表以更新未读消息数
GroupChatListViewModel.refreshPager(context = context)
// 刷新智能体列表以更新未读消息数
AgentChatListViewModel.refreshPager(context = context)
// 刷新朋友列表以更新未读消息数
FriendChatListViewModel.refreshPager(context = context)
}) })
val navigationBarPaddings = val navigationBarPaddings =
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp
@@ -94,6 +110,12 @@ fun NotificationsScreen() {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
systemUiController.setNavigationBarColor(Color.Transparent) systemUiController.setNavigationBarColor(Color.Transparent)
MessageListViewModel.initData(context, loadChat = AppState.enableChat) MessageListViewModel.initData(context, loadChat = AppState.enableChat)
// 初始化群聊列表以获取未读消息数
GroupChatListViewModel.refreshPager(context = context)
// 初始化智能体列表以获取未读消息数
AgentChatListViewModel.refreshPager(context = context)
// 初始化朋友列表以获取未读消息数
FriendChatListViewModel.refreshPager(context = context)
} }
Column( Column(
modifier = Modifier modifier = Modifier
@@ -202,36 +224,83 @@ fun NotificationsScreen() {
horizontalArrangement = Arrangement.Start, horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.Bottom verticalAlignment = Alignment.Bottom
) { ) {
TabItem( Box {
text = stringResource(R.string.chat_ai), TabItem(
isSelected = pagerState.currentPage == 0, text = stringResource(R.string.chat_ai),
onClick = { isSelected = pagerState.currentPage == 0,
scope.launch { onClick = {
pagerState.animateScrollToPage(0) scope.launch {
pagerState.animateScrollToPage(0)
}
} }
)
// 智能体未读消息红点
if (AgentChatListViewModel.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() TabSpacer()
TabItem( Box {
text = stringResource(R.string.chat_group), TabItem(
isSelected = pagerState.currentPage == 1, text = stringResource(R.string.chat_group),
onClick = { isSelected = pagerState.currentPage == 1,
scope.launch { onClick = {
pagerState.animateScrollToPage(1) scope.launch {
pagerState.animateScrollToPage(1)
}
} }
)
// 群聊未读消息红点
if (GroupChatListViewModel.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() TabSpacer()
TabItem( Box {
text = stringResource(R.string.chat_friend), TabItem(
isSelected = pagerState.currentPage == 2, text = stringResource(R.string.chat_friend),
onClick = { isSelected = pagerState.currentPage == 2,
scope.launch { onClick = {
pagerState.animateScrollToPage(2) scope.launch {
pagerState.animateScrollToPage(2)
}
} }
)
// 朋友未读消息红点
if (FriendChatListViewModel.totalUnreadCount > 0) {
Box(
modifier = Modifier
.size(8.dp)
.background(
color = Color(0xFFFF3B30),
shape = CircleShape
)
.align(Alignment.TopEnd)
.offset(x = 8.dp, y = (-4).dp)
)
} }
) }
} }
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,

View File

@@ -1,5 +1,6 @@
package com.aiosman.ravenow.ui.index.tabs.message.tab package com.aiosman.ravenow.ui.index.tabs.message.tab
import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -29,7 +30,9 @@ 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.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -38,6 +41,7 @@ import androidx.compose.ui.res.stringResource
import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.composables.CustomAsyncImage import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
@@ -82,8 +86,16 @@ fun AgentChatListScreen() {
.fillMaxSize() .fillMaxSize()
.padding(16.dp), .padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) { ) {
Spacer(modifier = Modifier.height(80.dp))
Image(
painter = painterResource(id = R.mipmap.icon_agent_chat_empty),
contentDescription = "null data",
modifier = Modifier
.size(140.dp)
)
Spacer(modifier = Modifier.height(24.dp))
Text( Text(
text = stringResource(R.string.agent_chat_empty_title), text = stringResource(R.string.agent_chat_empty_title),
color = AppColors.text, color = AppColors.text,
@@ -93,7 +105,7 @@ fun AgentChatListScreen() {
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = stringResource(R.string.agent_chat_empty_subtitle), text = stringResource(R.string.agent_chat_empty_subtitle),
color = AppColors.secondaryText, color = AppColors.text,
fontSize = 14.sp fontSize = 14.sp
) )
} }

View File

@@ -92,6 +92,10 @@ object AgentChatListViewModel : ViewModel() {
var currentPage by mutableStateOf(1) var currentPage by mutableStateOf(1)
var error by mutableStateOf<String?>(null) var error by mutableStateOf<String?>(null)
// 计算智能体总未读消息数
val totalUnreadCount: Int
get() = agentChatList.sumOf { it.unreadCount }
private val pageSize = 20 private val pageSize = 20
fun refreshPager(pullRefresh: Boolean = false, context: Context? = null) { fun refreshPager(pullRefresh: Boolean = false, context: Context? = null) {
@@ -200,10 +204,10 @@ object AgentChatListViewModel : ViewModel() {
} }
} }
fun createSingleChat( fun createSingleChat(
openId: String, trtcId: String,
) { ) {
viewModelScope.launch { viewModelScope.launch {
val response = ApiClient.api.createSingleChat(SingleChatRequestBody(generateText = openId)) val response = ApiClient.api.createSingleChat(SingleChatRequestBody(agentTrtcId = trtcId))
} }
} }

View File

@@ -1,5 +1,6 @@
package com.aiosman.ravenow.ui.index.tabs.message.tab package com.aiosman.ravenow.ui.index.tabs.message.tab
import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
@@ -20,6 +21,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@@ -68,6 +70,15 @@ fun FriendChatListScreen() {
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
Spacer(modifier = Modifier.height(80.dp))
Image(
painter = painterResource(id = R.mipmap.icon_friend_chat_empty),
contentDescription = "null data",
modifier = Modifier
.size(140.dp)
)
Spacer(modifier = Modifier.height(24.dp))
Text( Text(
text = stringResource(R.string.friend_chat_empty_title), text = stringResource(R.string.friend_chat_empty_title),
color = AppColors.text, color = AppColors.text,
@@ -77,7 +88,7 @@ fun FriendChatListScreen() {
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = stringResource(R.string.friend_chat_empty_subtitle), text = stringResource(R.string.friend_chat_empty_subtitle),
color = AppColors.secondaryText, color = AppColors.text,
fontSize = 14.sp fontSize = 14.sp
) )
} }

View File

@@ -86,6 +86,10 @@ object FriendChatListViewModel : ViewModel() {
var currentPage by mutableStateOf(1) var currentPage by mutableStateOf(1)
var error by mutableStateOf<String?>(null) var error by mutableStateOf<String?>(null)
// 计算朋友总未读消息数
val totalUnreadCount: Int
get() = friendChatList.sumOf { it.unreadCount }
private val pageSize = 20 private val pageSize = 20
fun refreshPager(pullRefresh: Boolean = false, context: Context? = null) { fun refreshPager(pullRefresh: Boolean = false, context: Context? = null) {

View File

@@ -1,6 +1,8 @@
package com.aiosman.ravenow.ui.index.tabs.message.tab package com.aiosman.ravenow.ui.index.tabs.message.tab
import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
@@ -21,6 +23,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@@ -78,16 +81,29 @@ fun GroupChatListScreen() {
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
Spacer(modifier = Modifier.height(80.dp))
Image(
painter = painterResource(id = if (isSystemInDarkTheme()) {
R.mipmap.icon_group_chat_empty // 深色模式图片
} else {
R.mipmap.icon_group_chat_empty // 浅色模式图片
}),
contentDescription = "null data",
modifier = Modifier
.size(140.dp)
)
Spacer(modifier = Modifier.height(24.dp))
Text( Text(
text = "暂无群聊", text = stringResource(R.string.group_chat_empty_title),
color = AppColors.text, color = AppColors.text,
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.W600 fontWeight = FontWeight.W600
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = "您还没有加入任何群聊", text = stringResource(R.string.group_chat_empty_subtitle),
color = AppColors.secondaryText, color = AppColors.text,
fontSize = 14.sp fontSize = 14.sp
) )
} }

View File

@@ -103,6 +103,10 @@ object GroupChatListViewModel : ViewModel() {
var currentPage by mutableStateOf(1) var currentPage by mutableStateOf(1)
var error by mutableStateOf<String?>(null) var error by mutableStateOf<String?>(null)
// 计算群聊总未读消息数
val totalUnreadCount: Int
get() = groupChatList.sumOf { it.unreadCount }
private val pageSize = 20 private val pageSize = 20
// 消息监听器 // 消息监听器

View File

@@ -79,7 +79,7 @@ fun MomentsList() {
// center the tabs // center the tabs
horizontalArrangement = Arrangement.Start, horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) {//探索tab ) {
Column( Column(
modifier = Modifier modifier = Modifier
.noRippleClickable { .noRippleClickable {
@@ -108,8 +108,38 @@ fun MomentsList() {
.width(34.dp) .width(34.dp)
.height(4.dp) .height(4.dp)
) )
}
Spacer(modifier = Modifier.width(16.dp))
Column(
modifier = Modifier
.noRippleClickable {
scope.launch {
pagerState.animateScrollToPage(1)
}
},
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(R.string.index_dynamic),
fontSize = 16.sp,
color = if (pagerState.currentPage == 1) AppColors.text else AppColors.nonActiveText,
fontWeight = FontWeight.W600)
Spacer(modifier = Modifier.height(4.dp))
Image(
painter = painterResource(
if (pagerState.currentPage == 1) R.mipmap.tab_indicator_selected
else R.drawable.tab_indicator_unselected
),
contentDescription = "tab indicator",
modifier = Modifier
.width(34.dp)
.height(4.dp)
)
} }
//“关注”tab
Spacer(modifier = Modifier.width(16.dp)) Spacer(modifier = Modifier.width(16.dp))
Column( Column(
modifier = Modifier modifier = Modifier

View File

@@ -48,6 +48,7 @@ import androidx.compose.ui.unit.sp
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.draw.blur import androidx.compose.ui.draw.blur
import androidx.compose.ui.graphics.graphicsLayer
import com.aiosman.ravenow.ui.composables.CustomAsyncImage import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.aiosman.ravenow.AppStore import com.aiosman.ravenow.AppStore
@@ -232,9 +233,9 @@ fun Explore() {
} }
@Composable @Composable
fun AgentPage(agentItems: List<AgentItem>, page: Int) { fun AgentPage(agentItems: List<AgentItem>, page: Int, modifier: Modifier = Modifier) {
Column( Column(
modifier = Modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
.padding(horizontal = 0.dp) .padding(horizontal = 0.dp)
) { ) {
@@ -268,11 +269,27 @@ fun Explore() {
) { ) {
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize(),
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 4.dp),
pageSpacing = 0.dp
) { page -> ) { page ->
// 计算当前页面的偏移量
val pageOffset = (
(pagerState.currentPage - page) + pagerState
.currentPageOffsetFraction
).coerceIn(-1f, 1f)
// 根据偏移量计算缩放比例
val scale = 1f - (0.1f * kotlin.math.abs(pageOffset))
AgentPage( AgentPage(
agentItems = agentItems.drop(page * itemsPerPage).take(itemsPerPage), agentItems = agentItems.drop(page * itemsPerPage).take(itemsPerPage),
page = page page = page,
modifier = Modifier
.graphicsLayer {
scaleX = scale
scaleY = scale
}
) )
} }
} }
@@ -410,12 +427,12 @@ fun Explore() {
@Composable @Composable
fun BannerCard(bannerItem: BannerItem) { fun BannerCard(bannerItem: BannerItem, modifier: Modifier = Modifier) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val context = LocalContext.current val context = LocalContext.current
Card( Card(
modifier = Modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
.padding(horizontal = 0.dp), .padding(horizontal = 0.dp),
shape = RoundedCornerShape(20.dp), shape = RoundedCornerShape(20.dp),
@@ -595,15 +612,15 @@ fun Explore() {
) { ) {
// 可以添加更多不同高度的内容项 // 第一块区域
item { item {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 6.dp), .padding(vertical = 6.dp),
horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceEvenly horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceBetween
) { ) {
// 第一个 // 第一个 - 靠左显示
Column( Column(
modifier = Modifier modifier = Modifier
.clickable { .clickable {
@@ -633,69 +650,75 @@ fun Explore() {
) )
} }
// 第二个 // 中间两个 - 平均分布
Column( Row(
modifier = Modifier modifier = Modifier.weight(1f),
.clickable { horizontalArrangement = androidx.compose.foundation.layout.Arrangement.SpaceEvenly
navController.navigate(
NavigationRoute.AddAgent.route)
},
horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Box( // 第二个
Column(
modifier = Modifier modifier = Modifier
.size(64.dp) .clickable {
.background(Color(0xFF94f9f2), RoundedCornerShape(24.dp)), navController.navigate(
contentAlignment = Alignment.Center NavigationRoute.AddAgent.route)
},
horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Image( Box(
painter = painterResource(R.mipmap.rider_pro_agent), modifier = Modifier
contentDescription = "创建智能体", .size(64.dp)
modifier = Modifier.size(24.dp), .background(Color(0xFF94f9f2), RoundedCornerShape(24.dp)),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(R.mipmap.rider_pro_agent),
contentDescription = "创建智能体",
modifier = Modifier.size(24.dp),
) )
}
Spacer(modifier = Modifier.size(8.dp))
Text(
text = "创建Agent",
fontSize = 12.sp,
color = AppColors.text,
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
)
}
// 第三个
Column(
modifier = Modifier
.clickable {
NewPostViewModel.asNewPost()
navController.navigate("NewPost")
},
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier
.size(64.dp)
.background(Color(0xFFfafd5d), RoundedCornerShape(24.dp)),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(R.mipmap.rider_pro_release),
contentDescription = "发布动态",
modifier = Modifier.size(24.dp),
)
}
Spacer(modifier = Modifier.size(8.dp))
Text(
text = "发布动态",
fontSize = 12.sp,
color = AppColors.text,
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
)
} }
Spacer(modifier = Modifier.size(8.dp))
Text(
text = "创建Agent",
fontSize = 12.sp,
color = AppColors.text,
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
)
} }
// 第三个 // 第四个 - 靠右显示
Column(
modifier = Modifier
.clickable {
NewPostViewModel.asNewPost()
navController.navigate("NewPost")
},
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier
.size(64.dp)
.background(Color(0xFFfafd5d), RoundedCornerShape(24.dp)),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(R.mipmap.rider_pro_release),
contentDescription = "发布动态",
modifier = Modifier.size(24.dp),
)
}
Spacer(modifier = Modifier.size(8.dp))
Text(
text = "发布动态",
fontSize = 12.sp,
color = AppColors.text,
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
)
}
// 第四个
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
@@ -767,10 +790,28 @@ fun Explore() {
) { ) {
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize(),
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 4.dp),
) { page -> ) { page ->
val bannerItem = bannerItems[page] val bannerItem = bannerItems[page]
BannerCard(bannerItem = bannerItem)
// 计算当前页面的偏移量
val pageOffset = (
(pagerState.currentPage - page) + pagerState
.currentPageOffsetFraction
).coerceIn(-1f, 1f)
// 根据偏移量计算缩放比例
val scale = 1f - (0.1f * kotlin.math.abs(pageOffset))
BannerCard(
bannerItem = bannerItem,
modifier = Modifier
.graphicsLayer {
scaleX = scale
scaleY = scale
}
)
} }
} }
@@ -807,7 +848,7 @@ fun Explore() {
// 标题 // 标题
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(bottom = 30.dp) modifier = Modifier.padding(bottom = 12.dp)
) { ) {
Image( Image(
painter = painterResource(R.mipmap.rider_pro_fire2), painter = painterResource(R.mipmap.rider_pro_fire2),
@@ -848,9 +889,9 @@ fun Explore() {
) )
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(4.dp))
Text( Text(
text = "推荐给你的Agent", text = "推荐给你的智能体",
fontSize = 20.sp, fontSize = 16.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.W900, fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
color = AppColors.text color = AppColors.text
) )
} }
@@ -875,7 +916,7 @@ fun Explore() {
Image( Image(
painter = painterResource(R.mipmap.rider_pro_hot_room), painter = painterResource(R.mipmap.rider_pro_hot_room),
contentDescription = "chat room", contentDescription = "chat room",
modifier = Modifier.size(28.dp), modifier = Modifier.size(24.dp),
) )
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(4.dp))
Text( Text(

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -147,8 +147,8 @@
<string name="chat_friend">朋友</string> <string name="chat_friend">朋友</string>
<string name="agent_chat_list_title">智能体聊天</string> <string name="agent_chat_list_title">智能体聊天</string>
<string name="agent_chat_empty_title">暂无智能体聊天</string> <string name="agent_chat_empty_title">AI 们在等你开启第一句对话</string>
<string name="agent_chat_empty_subtitle">开始与智能体对话</string> <string name="agent_chat_empty_subtitle">去首页探索一下,主动发起一场对话</string>
<string name="agent_chat_me_prefix">我: </string> <string name="agent_chat_me_prefix">我: </string>
<string name="agent_chat_image">[图片]</string> <string name="agent_chat_image">[图片]</string>
<string name="agent_chat_voice">[语音]</string> <string name="agent_chat_voice">[语音]</string>
@@ -159,6 +159,9 @@
<string name="agent_chat_load_more_failed">加载更多失败</string> <string name="agent_chat_load_more_failed">加载更多失败</string>
<string name="agent_chat_user_info_failed">获取用户信息失败: %s</string> <string name="agent_chat_user_info_failed">获取用户信息失败: %s</string>
<string name="group_chat_empty_title">没有群聊消息的宇宙太安静了</string>
<string name="group_chat_empty_subtitle">在首页探索感兴趣的主题房间</string>
<string name="friend_chat_empty_title">暂无朋友聊天</string> <string name="friend_chat_empty_title">暂无朋友聊天</string>
<string name="friend_chat_empty_subtitle">开始与朋友对话吧</string> <string name="friend_chat_empty_subtitle">开始与朋友对话吧</string>
<string name="friend_chat_me_prefix">我: </string> <string name="friend_chat_me_prefix">我: </string>

View File

@@ -155,6 +155,8 @@
<string name="agent_chat_load_failed">加载失败</string> <string name="agent_chat_load_failed">加载失败</string>
<string name="agent_chat_load_more_failed">加载更多失败</string> <string name="agent_chat_load_more_failed">加载更多失败</string>
<string name="agent_chat_user_info_failed">获取用户信息失败: %s</string> <string name="agent_chat_user_info_failed">获取用户信息失败: %s</string>
<string name="group_chat_empty_title">没有群聊消息的宇宙太安静了</string>
<string name="group_chat_empty_subtitle">在首页探索感兴趣的主题房间</string>
<string name="friend_chat_empty_title">暂无朋友聊天</string> <string name="friend_chat_empty_title">暂无朋友聊天</string>
<string name="friend_chat_empty_subtitle">开始与朋友对话吧</string> <string name="friend_chat_empty_subtitle">开始与朋友对话吧</string>
<string name="friend_chat_me_prefix">我: </string> <string name="friend_chat_me_prefix">我: </string>