Merge upstream/main and resolve conflicts

This commit is contained in:
2025-08-22 10:41:30 +08:00
31 changed files with 1056 additions and 257 deletions

2
.idea/kotlinc.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.0" />
<option name="version" value="1.9.10" />
</component>
</project>

View File

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

View File

@@ -0,0 +1,15 @@
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,
)

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.chat.ChatAiScreen
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.comment.CommentsScreen
import com.aiosman.ravenow.ui.comment.notice.CommentNoticeScreen
@@ -96,6 +97,7 @@ sealed class NavigationRoute(
data object Chat : NavigationRoute("Chat/{id}")
data object ChatAi : NavigationRoute("ChatAi/{id}")
data object ChatGroup : NavigationRoute("ChatGroup/{id}/{name}/{avatar}")
data object GroupChatInfo : NavigationRoute("GroupChatInfo/{groupId}")
data object CommentNoticeScreen : NavigationRoute("CommentNoticeScreen")
data object ImageCrop : NavigationRoute("ImageCrop")
data object AccountSetting : NavigationRoute("AccountSetting")
@@ -404,6 +406,19 @@ fun NavigationController(
}
}
composable(
route = NavigationRoute.GroupChatInfo.route,
arguments = listOf(navArgument("groupId") { type = NavType.StringType })
) {
val encodedId = it.arguments?.getString("groupId")
val decodedId = encodedId?.let { java.net.URLDecoder.decode(it, "UTF-8") }
CompositionLocalProvider(
LocalAnimatedContentScope provides this,
) {
GroupChatInfoScreen(decodedId?:"")
}
}
composable(route = NavigationRoute.CommentNoticeScreen.route) {
CompositionLocalProvider(
@@ -514,6 +529,15 @@ fun NavHostController.navigateToGroupChat(id: String,name:String,avatar:String)
)
}
fun NavHostController.navigateToGroupInfo(id: String) {
val encodedId = java.net.URLEncoder.encode(id, "UTF-8")
navigate(
route = NavigationRoute.GroupChatInfo.route
.replace("{groupId}", encodedId)
)
}
fun NavHostController.goTo(

View File

@@ -8,6 +8,7 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@@ -49,6 +50,7 @@ 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.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.focus.onFocusChanged
@@ -175,7 +177,7 @@ fun ChatScreen(userId: String) {
Image(
painter = painterResource(R.drawable.rider_pro_back_icon),
modifier = Modifier
.size(28.dp)
.size(24.dp)
.noRippleClickable {
navController.navigateUp()
},
@@ -187,8 +189,8 @@ fun ChatScreen(userId: String) {
CustomAsyncImage(
imageUrl = viewModel.userProfile?.avatar ?: "",
modifier = Modifier
.size(40.dp)
.clip(RoundedCornerShape(40.dp)),
.size(32.dp)
.clip(RoundedCornerShape(32.dp)),
contentDescription = "avatar"
)
Spacer(modifier = Modifier.width(8.dp))
@@ -221,7 +223,7 @@ fun ChatScreen(userId: String) {
},
menuItems = listOf(
MenuItem(
title = if (viewModel.notificationStrategy == "mute") "Unmute" else "Mute",
title = if (viewModel.notificationStrategy == "mute") "" else "",
icon = if (viewModel.notificationStrategy == "mute") R.drawable.rider_pro_notice_mute else R.drawable.rider_pro_notice_active,
) {
@@ -251,8 +253,7 @@ fun ChatScreen(userId: String) {
modifier = Modifier
.fillMaxWidth()
.height(1.dp)
.background(
AppColors.decentBackground)
)
Spacer(modifier = Modifier.height(8.dp))
ChatInput(
@@ -270,13 +271,12 @@ fun ChatScreen(userId: String) {
Box(
modifier = Modifier
.fillMaxSize()
.background(AppColors.decentBackground)
.background(AppColors.background)
.padding(paddingValues)
) {
LazyColumn(
state = listState,
modifier = Modifier
.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
reverseLayout = true,
verticalArrangement = Arrangement.Top
) {
@@ -340,6 +340,7 @@ fun ChatScreen(userId: String) {
@Composable
fun ChatSelfItem(item: ChatItem) {
val context = LocalContext.current
Column(
modifier = Modifier
@@ -353,19 +354,28 @@ fun ChatSelfItem(item: ChatItem) {
Column(
horizontalAlignment = androidx.compose.ui.Alignment.End,
) {
/* Text(
text = item.nickname,
style = TextStyle(
color = Color.Gray,
fontSize = 12.sp,
),
modifier = Modifier.padding(bottom = 2.dp)
)
*/
Box(
modifier = Modifier
.widthIn(
min = 20.dp,
max = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 250.dp else 150.dp)
)
.clip(RoundedCornerShape(8.dp))
.background(Color(0xFF000000))
.clip(RoundedCornerShape(20.dp))
.background(Color(0xFF6246FF))
.padding(
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)
)
.padding(bottom = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 3.dp else 0.dp))
) {
when (item.messageType) {
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
@@ -373,7 +383,7 @@ fun ChatSelfItem(item: ChatItem) {
text = item.message,
style = TextStyle(
color = Color.White,
fontSize = 16.sp,
fontSize = 14.sp,
),
textAlign = TextAlign.Start
)
@@ -389,28 +399,28 @@ fun ChatSelfItem(item: ChatItem) {
else -> {
Text(
text = "Unsupported message type",
text = "不支持的消息类型",
style = TextStyle(
color = Color.White,
fontSize = 16.sp,
fontSize = 14.sp,
)
)
}
}
}
}
Spacer(modifier = Modifier.width(12.dp))
/*Spacer(modifier = Modifier.width(12.dp))
Box(
modifier = Modifier
.size(40.dp)
.clip(RoundedCornerShape(40.dp))
.size(24.dp)
.clip(RoundedCornerShape(24.dp))
) {
CustomAsyncImage(
imageUrl = item.avatar,
modifier = Modifier.fillMaxSize(),
contentDescription = "avatar"
)
}
}*/
}
}
}
@@ -430,11 +440,11 @@ fun ChatOtherItem(item: ChatItem) {
) {
Box(
modifier = Modifier
.size(40.dp)
.clip(RoundedCornerShape(40.dp))
.size(24.dp)
.clip(RoundedCornerShape(24.dp))
) {
CustomAsyncImage(
imageUrl = item.avatar,
imageUrl = item.avatar.replace("storage/avatars/", "/avatar/"),
modifier = Modifier.fillMaxSize(),
contentDescription = "avatar"
)
@@ -447,13 +457,12 @@ fun ChatOtherItem(item: ChatItem) {
min = 20.dp,
max = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 250.dp else 150.dp)
)
.clip(RoundedCornerShape(8.dp))
.background(AppColors.background)
.clip(RoundedCornerShape(20.dp))
.background(AppColors.bubbleBackground)
.padding(
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)
)
.padding(bottom = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 3.dp else 0.dp))
) {
when (item.messageType) {
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
@@ -461,7 +470,7 @@ fun ChatOtherItem(item: ChatItem) {
text = item.message,
style = TextStyle(
color = AppColors.text,
fontSize = 16.sp,
fontSize = 14.sp,
),
textAlign = TextAlign.Start
)
@@ -477,7 +486,7 @@ fun ChatOtherItem(item: ChatItem) {
else -> {
Text(
text = "Unsupported message type",
text = "不支持的消息类型",
style = TextStyle(
color = AppColors.text,
fontSize = 16.sp,
@@ -486,7 +495,6 @@ fun ChatOtherItem(item: ChatItem) {
}
}
}
}
}
@@ -547,89 +555,96 @@ fun ChatInput(
onSendImage(uri)
}
}
Box( modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp),){
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.padding(bottom = inputBarHeight)
) {
Box(
Row(
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(16.dp))
.background(appColors.background)
.padding(horizontal = 16.dp),
contentAlignment = Alignment.CenterStart,
.fillMaxWidth()
.clip(RoundedCornerShape(20.dp))
.background(appColors.decentBackground)
.padding(start = 16.dp, end = 8.dp, top = 2.dp, bottom = 2.dp),
verticalAlignment = Alignment.CenterVertically,
) {
BasicTextField(
value = text,
onValueChange = {
text = it
},
textStyle = TextStyle(
color = appColors.text,
fontSize = 16.sp
),
cursorBrush = SolidColor(appColors.text),
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
.onFocusChanged { focusState ->
isKeyboardOpen = focusState.isFocused
}
.pointerInput(Unit) {
awaitPointerEventScope {
keyboardController = softwareKeyboardController
awaitFirstDown().also {
keyboardController?.show()
.weight(1f)
) {
BasicTextField(
value = text,
onValueChange = {
text = it
},
textStyle = TextStyle(
color = appColors.text,
fontSize = 16.sp
),
cursorBrush = SolidColor(appColors.text),
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
.onFocusChanged { focusState ->
isKeyboardOpen = focusState.isFocused
}
.pointerInput(Unit) {
awaitPointerEventScope {
keyboardController = softwareKeyboardController
awaitFirstDown().also {
keyboardController?.show()
}
}
},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(
onDone = {
keyboardController?.hide()
}
},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(
onDone = {
keyboardController?.hide()
}
)
)
}
Spacer(modifier = Modifier.width(16.dp))
Icon(
painter = painterResource(id = R.drawable.rider_pro_camera),
contentDescription = "Emoji",
modifier = Modifier
.size(30.dp)
.noRippleClickable {
imagePickUpLauncher.launch(
Intent.createChooser(
Intent(Intent.ACTION_GET_CONTENT).apply {
type = "image/*"
},
"Select Image"
)
)
},
tint = appColors.chatActionColor
)
Spacer(modifier = Modifier.width(8.dp))
Crossfade(
targetState = text.isNotEmpty(), animationSpec = tween(500),
label = ""
) { isNotEmpty ->
Icon(
painter = painterResource(id = R.drawable.rider_pro_video_share),
contentDescription = "Emoji",
)
}
Spacer(modifier = Modifier.width(8.dp))
Image(
painter = painterResource(R.mipmap.rider_pro_im_image),
contentDescription = "Image",
modifier = Modifier
.size(32.dp)
.size(30.dp)
.noRippleClickable {
if (text.isNotEmpty()) {
onSend(text)
text = ""
}
imagePickUpLauncher.launch(
Intent.createChooser(
Intent(Intent.ACTION_GET_CONTENT).apply {
type = "image/*"
},
"Select Image"
)
)
},
tint = if (isNotEmpty) appColors.main else appColors.chatActionColor
)
Spacer(modifier = Modifier.width(8.dp))
Crossfade(
targetState = text.isNotEmpty(), animationSpec = tween(500),
label = ""
) { isNotEmpty ->
val alpha by animateFloatAsState(
targetValue = if (isNotEmpty) 1f else 0.5f,
animationSpec = tween(300)
)
Image(
painter = painterResource(R.mipmap.rider_pro_im_send),
modifier = Modifier
.size(24.dp)
.alpha(alpha)
.noRippleClickable {
if (text.isNotEmpty()) {
onSend(text)
text = ""
}
},
contentDescription = null,
)
}
}
}
}

View File

@@ -8,6 +8,7 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@@ -49,6 +50,7 @@ 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.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.focus.onFocusChanged
@@ -189,7 +191,7 @@ fun GroupChatScreen(groupId: String,name: String,avatar: String,) {
} else {
Box(
modifier = Modifier
.size(40.dp)
.size(24.dp)
.clip(RoundedCornerShape(8.dp))
.background(AppColors.decentBackground),
contentAlignment = Alignment.Center
@@ -234,34 +236,6 @@ fun GroupChatScreen(groupId: String,name: String,avatar: String,) {
contentDescription = null,
colorFilter = ColorFilter.tint(AppColors.text)
)
DropdownMenu(
expanded = isMenuExpanded,
onDismissRequest = {
isMenuExpanded = false
},
menuItems = listOf(
MenuItem(
title = if (viewModel.notificationStrategy == "mute") "取消静音" else "静音",
icon = if (viewModel.notificationStrategy == "mute") R.drawable.rider_pro_notice_mute else R.drawable.rider_pro_notice_active,
) {
isMenuExpanded = false
viewModel.viewModelScope.launch {
if (viewModel.notificationStrategy == "mute") {
viewModel.updateNotificationStrategy("active")
} else {
viewModel.updateNotificationStrategy("mute")
}
}
},
MenuItem(
title = "群成员",
icon = R.drawable.rider_pro_more_horizon,
) {
isMenuExpanded = false
viewModel.getGroupMembers()
}
),
)
}
}
}
@@ -276,9 +250,8 @@ fun GroupChatScreen(groupId: String,name: String,avatar: String,) {
modifier = Modifier
.fillMaxWidth()
.height(1.dp)
.background(AppColors.decentBackground)
)
Spacer(modifier = Modifier.height(8.dp))
Spacer(modifier = Modifier.height(12.dp))
GroupChatInput(
onSendImage = { uri ->
uri?.let {
@@ -294,7 +267,7 @@ fun GroupChatScreen(groupId: String,name: String,avatar: String,) {
Box(
modifier = Modifier
.fillMaxSize()
.background(AppColors.decentBackground)
.background(AppColors.background)
.padding(paddingValues)
) {
LazyColumn(
@@ -306,23 +279,31 @@ fun GroupChatScreen(groupId: String,name: String,avatar: String,) {
val chatList = groupMessagesByTime(viewModel.getDisplayChatList(), viewModel)
items(chatList.size, key = { index -> chatList[index].msgId + UUID.randomUUID().toString()}) { index ->
val item = chatList[index]
if (item.showTimeDivider) {
val calendar = java.util.Calendar.getInstance()
calendar.timeInMillis = item.timestamp
Text(
text = calendar.time.formatChatTime(context),
style = TextStyle(
color = AppColors.secondaryText,
fontSize = 14.sp,
textAlign = TextAlign.Center
),
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
Column {
if (item.showTimeDivider) {
val calendar = java.util.Calendar.getInstance()
calendar.timeInMillis = item.timestamp
Text(
text = calendar.time.formatChatTime(context),
style = TextStyle(
color = AppColors.secondaryText,
fontSize = 11.sp,
textAlign = TextAlign.Center
),
modifier = Modifier
.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 +326,7 @@ fun GroupChatScreen(groupId: String,name: String,avatar: String,) {
text = "${goToNewCount} 条新消息",
style = TextStyle(
color = AppColors.text,
fontSize = 16.sp,
fontSize = 12.sp,
),
)
}
@@ -390,7 +371,7 @@ fun GroupChatSelfItem(item: ChatItem) {
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)
)
.padding(bottom = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 3.dp else 0.dp))
) {
when (item.messageType) {
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
@@ -441,7 +422,7 @@ fun GroupChatSelfItem(item: ChatItem) {
}
@Composable
fun GroupChatOtherItem(item: ChatItem) {
fun GroupChatOtherItem(item: ChatItem, showAvatarAndNickname: Boolean = true) {
val AppColors = LocalAppTheme.current
Column(
@@ -453,28 +434,24 @@ fun GroupChatOtherItem(item: ChatItem) {
horizontalArrangement = Arrangement.Start,
modifier = Modifier.fillMaxWidth()
) {
Box(
modifier = Modifier
.size(24.dp)
.clip(RoundedCornerShape(24.dp))
) {
CustomAsyncImage(
imageUrl = item.avatar.replace("storage/avatars/", "/avatar/"),
modifier = Modifier.fillMaxSize(),
contentDescription = "avatar"
)
if (showAvatarAndNickname) {
Box(
modifier = Modifier
.size(24.dp)
.clip(RoundedCornerShape(24.dp))
) {
CustomAsyncImage(
imageUrl = item.avatar.replace("storage/avatars/", "/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 {
Text(
text = item.nickname,
style = TextStyle(
color = AppColors.secondaryText,
fontSize = 12.sp,
),
modifier = Modifier.padding(bottom = 2.dp)
)
Box(
modifier = Modifier
.widthIn(
@@ -487,7 +464,6 @@ fun GroupChatOtherItem(item: ChatItem) {
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)
)
.padding(bottom = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 3.dp else 0.dp))
) {
when (item.messageType) {
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
@@ -520,13 +496,24 @@ fun GroupChatOtherItem(item: ChatItem) {
}
}
}
if (showAvatarAndNickname) {
Text(
text = item.nickname,
style = TextStyle(
color = AppColors.secondaryText,
fontSize = 12.sp,
),
modifier = Modifier.padding(bottom = 2.dp)
)
}
}
}
}
}
@Composable
fun GroupChatItem(item: ChatItem, currentUserId: String) {
fun GroupChatItem(item: ChatItem, currentUserId: String, showAvatarAndNickname: Boolean = true) {
val isCurrentUser = item.userId == currentUserId
// 管理员消息显示特殊布局
@@ -538,7 +525,7 @@ fun GroupChatItem(item: ChatItem, currentUserId: String) {
// 根据是否是当前用户显示不同样式
when (item.userId) {
currentUserId -> GroupChatSelfItem(item)
else -> GroupChatOtherItem(item)
else -> GroupChatOtherItem(item, showAvatarAndNickname)
}
}
@@ -615,20 +602,21 @@ fun GroupChatInput(
onSendImage(uri)
}
}
Box( modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp),){
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.padding(bottom = inputBarHeight)
.clip(RoundedCornerShape(20.dp))
.background(appColors.decentBackground)
.padding(start = 16.dp, end = 8.dp, top = 2.dp, bottom = 2.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(16.dp))
.background(appColors.background)
.padding(horizontal = 16.dp),
contentAlignment = Alignment.CenterStart,
) {
BasicTextField(
value = text,
@@ -640,6 +628,7 @@ fun GroupChatInput(
fontSize = 16.sp
),
cursorBrush = SolidColor(appColors.text),
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
@@ -662,44 +651,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))
Crossfade(
targetState = text.isNotEmpty(), animationSpec = tween(500),
label = ""
) { isNotEmpty ->
Icon(
painter = painterResource(id = R.drawable.rider_pro_video_share),
contentDescription = "发送消息",
val alpha by animateFloatAsState(
targetValue = if (isNotEmpty) 1f else 0.5f,
animationSpec = tween(300)
)
Image(
painter = painterResource(R.mipmap.rider_pro_im_send),
modifier = Modifier
.size(32.dp)
.size(24.dp)
.alpha(alpha)
.noRippleClickable {
if (text.isNotEmpty()) {
onSend(text)
text = ""
}
},
tint = if (isNotEmpty) appColors.main else appColors.chatActionColor
contentDescription = null,
)
}
}
}
}
fun groupMessagesByTime(chatList: List<ChatItem>, viewModel: GroupChatViewModel): List<ChatItem> {
@@ -711,7 +688,7 @@ fun groupMessagesByTime(chatList: List<ChatItem>, viewModel: GroupChatViewModel)
}
val currentMessage = chatList[i]
val timeDiff = currentMessage.timestamp - chatList[i - 1].timestamp
if (-timeDiff > 30 * 60 * 1000) {
if (-timeDiff > 10 * 60 * 1000) {
viewModel.showTimestampMap[currentMessage.msgId] = true
currentMessage.showTimeDivider = true
}

View File

@@ -48,7 +48,6 @@ class GroupChatViewModel(
var isLoading by mutableStateOf(false)
var lastMessage: V2TIMMessage? = null
val showTimestampMap = mutableMapOf<String, Boolean>()
var chatNotification by mutableStateOf<ChatNotification?>(null)
var goToNew by mutableStateOf(false)
// 群聊特有属性
@@ -71,8 +70,6 @@ class GroupChatViewModel(
myProfile = accountService.getMyAccountProfile()
RegistListener(context)
fetchHistoryMessage(context)
val notiStrategy = ChatState.getStrategyByTargetTrtcId(groupId)
chatNotification = notiStrategy
} catch (e: Exception) {
Log.e("GroupChatViewModel", "初始化失败: ${e.message}")
}
@@ -287,16 +284,4 @@ class GroupChatViewModel(
return list
}
suspend fun updateNotificationStrategy(strategy: String) {
val result = ChatState.updateChatNotification(groupId.hashCode(), strategy)
chatNotification = result
}
val notificationStrategy get() = chatNotification?.strategy ?: "default"
// 群聊特有功能
fun getGroupMembers() {
// 简化群成员获取,暂时只记录日志
Log.d("GroupChatViewModel", "获取群成员功能待实现")
}
}

View File

@@ -0,0 +1,328 @@
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.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
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(),
textAlign = TextAlign.Start,
text = "群聊信息",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text,
fontSize = 17.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(RoundedCornerShape(12.dp)),
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
)
)
}
}
// 操作按钮
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),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(R.drawable.rider_pro_add_other),
modifier = Modifier.size(24.dp),
contentDescription = null,
colorFilter = ColorFilter.tint(
AppColors.text)
)
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.group_info_add_other),
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text,
fontSize = 12.sp
)
)
}
// 通知设置
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.noRippleClickable {
/* if (viewModel.notificationStrategy == "mute") {
viewModel.updateNotificationStrategy("active")
} else {
viewModel.updateNotificationStrategy("mute")
}*/
}
) {
Box(
modifier = Modifier
.size(48.dp)
.clip(CircleShape),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(if (viewModel.notificationStrategy == "mute") R.drawable.rider_pro_notice_mute else R.drawable.rider_pro_notice_active,),
modifier = Modifier.size(24.dp),
contentDescription = null,
colorFilter = ColorFilter.tint(
AppColors.text)
)
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.group_info_notice_setting),
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),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(R.drawable.group_info_exit
),
modifier = Modifier.size(24.dp),
contentDescription = null,
colorFilter = ColorFilter.tint(
AppColors.text)
)
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.group_info_exit),
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))
.padding(16.dp)
.noRippleClickable {
// TODO: 实现设置聊天主题功能
},
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(R.drawable.group_info_edit),
modifier = Modifier.size(24.dp),
contentDescription = null,
colorFilter = ColorFilter.tint(
AppColors.text)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = stringResource(R.string.group_info_edit),
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text,
fontSize = 16.sp
),
modifier = Modifier.weight(1f)
)
Image(
painter = painterResource(R.drawable.rave_now_nav_right),
modifier = Modifier.size(18.dp),
contentDescription = null,
)
}
Spacer(modifier = Modifier.height(1.dp))
// 群聊成员
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.padding(16.dp)
.noRippleClickable {
// TODO: 实现查看群聊成员功能
},
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(R.drawable.group_info_users),
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.rave_now_nav_right),
modifier = Modifier.size(18.dp),
contentDescription = null,
)
}
}
}
}
}

View File

@@ -0,0 +1,72 @@
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.AppStore
import com.aiosman.ravenow.ChatState
import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.entity.ChatNotification
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)
var chatNotification by mutableStateOf<ChatNotification?>(null)
val notificationStrategy get() = chatNotification?.strategy ?: "default"
init {
loadGroupInfo()
}
suspend fun updateNotificationStrategy(strategy: String) {
val result = ChatState.updateChatNotification(groupId.hashCode(), strategy)
chatNotification = result
}
private fun loadGroupInfo() {
viewModelScope.launch {
val notiStrategy = ChatState.getStrategyByTargetTrtcId(groupId)
chatNotification = notiStrategy
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 = if (it.avatar.isNullOrEmpty()) {
// 将 groupId 转换为 Base64
val groupIdBase64 = android.util.Base64.encodeToString(
groupId.toByteArray(),
android.util.Base64.NO_WRAP
)
"${ApiClient.RETROFIT_URL+"group/avatar?groupIdBase64="}${groupIdBase64}"+"&token="+"${AppStore.token}"
} else {
"${ApiClient.BASE_API_URL+"/outside/rooms/avatar/"}${it.avatar}"+"?token="+"${AppStore.token}"
},
memberCount = room.userCount,
)
}
} 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.width
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.DrawerValue
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.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.offset
import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.AppState
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.ai.Agent
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.profile.ProfileWrap
import com.aiosman.ravenow.ui.index.tabs.search.DiscoverScreen
@@ -99,6 +105,10 @@ fun IndexScreen() {
val context = LocalContext.current
LaunchedEffect(Unit) {
systemUiController.setNavigationBarColor(Color.Transparent)
// 初始化聊天列表以获取未读消息数
AgentChatListViewModel.refreshPager(context = context)
GroupChatListViewModel.refreshPager(context = context)
FriendChatListViewModel.refreshPager(context = context)
}
LaunchedEffect(model.openDrawer) {
if (model.openDrawer) {
@@ -311,12 +321,40 @@ fun IndexScreen() {
),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.width(24.dp)
.height(24.dp)
,
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(if (isSelected) it.selectedIcon() else it.icon()),
contentDescription = it.label(),
modifier = Modifier.size(24.dp),
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.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
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.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.offset
import androidx.lifecycle.viewModelScope
import com.aiosman.ravenow.AppState
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.follower.FollowerNoticeViewModel
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.FriendChatListViewModel
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.modifiers.noRippleClickable
import com.google.accompanist.systemuicontroller.rememberSystemUiController
@@ -77,6 +83,10 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
@Composable
fun NotificationsScreen() {
// 计算总未读消息数
val totalUnreadCount = AgentChatListViewModel.totalUnreadCount +
GroupChatListViewModel.totalUnreadCount +
FriendChatListViewModel.totalUnreadCount
val AppColors = LocalAppTheme.current
val navController = LocalNavController.current
val systemUiController = rememberSystemUiController()
@@ -87,6 +97,12 @@ fun NotificationsScreen() {
MessageListViewModel.viewModelScope.launch {
MessageListViewModel.initData(context, force = true, loadChat = AppState.enableChat)
}
// 刷新群聊列表以更新未读消息数
GroupChatListViewModel.refreshPager(context = context)
// 刷新智能体列表以更新未读消息数
AgentChatListViewModel.refreshPager(context = context)
// 刷新朋友列表以更新未读消息数
FriendChatListViewModel.refreshPager(context = context)
})
val navigationBarPaddings =
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp
@@ -94,6 +110,12 @@ fun NotificationsScreen() {
LaunchedEffect(Unit) {
systemUiController.setNavigationBarColor(Color.Transparent)
MessageListViewModel.initData(context, loadChat = AppState.enableChat)
// 初始化群聊列表以获取未读消息数
GroupChatListViewModel.refreshPager(context = context)
// 初始化智能体列表以获取未读消息数
AgentChatListViewModel.refreshPager(context = context)
// 初始化朋友列表以获取未读消息数
FriendChatListViewModel.refreshPager(context = context)
}
Column(
modifier = Modifier
@@ -202,36 +224,83 @@ fun NotificationsScreen() {
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.Bottom
) {
TabItem(
text = stringResource(R.string.chat_ai),
isSelected = pagerState.currentPage == 0,
onClick = {
scope.launch {
pagerState.animateScrollToPage(0)
Box {
TabItem(
text = stringResource(R.string.chat_ai),
isSelected = pagerState.currentPage == 0,
onClick = {
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()
TabItem(
text = stringResource(R.string.chat_group),
isSelected = pagerState.currentPage == 1,
onClick = {
scope.launch {
pagerState.animateScrollToPage(1)
Box {
TabItem(
text = stringResource(R.string.chat_group),
isSelected = pagerState.currentPage == 1,
onClick = {
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()
TabItem(
text = stringResource(R.string.chat_friend),
isSelected = pagerState.currentPage == 2,
onClick = {
scope.launch {
pagerState.animateScrollToPage(2)
Box {
TabItem(
text = stringResource(R.string.chat_friend),
isSelected = pagerState.currentPage == 2,
onClick = {
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(
state = pagerState,

View File

@@ -1,5 +1,6 @@
package com.aiosman.ravenow.ui.index.tabs.message.tab
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -29,7 +30,9 @@ 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.text.style.TextOverflow
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.LocalNavController
import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
@@ -82,8 +86,16 @@ fun AgentChatListScreen() {
.fillMaxSize()
.padding(16.dp),
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 = stringResource(R.string.agent_chat_empty_title),
color = AppColors.text,
@@ -93,7 +105,7 @@ fun AgentChatListScreen() {
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.agent_chat_empty_subtitle),
color = AppColors.secondaryText,
color = AppColors.text,
fontSize = 14.sp
)
}

View File

@@ -92,6 +92,10 @@ object AgentChatListViewModel : ViewModel() {
var currentPage by mutableStateOf(1)
var error by mutableStateOf<String?>(null)
// 计算智能体总未读消息数
val totalUnreadCount: Int
get() = agentChatList.sumOf { it.unreadCount }
private val pageSize = 20
fun refreshPager(pullRefresh: Boolean = false, context: Context? = null) {
@@ -200,10 +204,10 @@ object AgentChatListViewModel : ViewModel() {
}
}
fun createSingleChat(
openId: String,
trtcId: String,
) {
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
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
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.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.TextOverflow
@@ -68,6 +70,15 @@ fun FriendChatListScreen() {
horizontalAlignment = Alignment.CenterHorizontally,
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 = stringResource(R.string.friend_chat_empty_title),
color = AppColors.text,
@@ -77,7 +88,7 @@ fun FriendChatListScreen() {
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.friend_chat_empty_subtitle),
color = AppColors.secondaryText,
color = AppColors.text,
fontSize = 14.sp
)
}

View File

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

View File

@@ -1,6 +1,8 @@
package com.aiosman.ravenow.ui.index.tabs.message.tab
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
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.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.TextOverflow
@@ -78,16 +81,29 @@ fun GroupChatListScreen() {
horizontalAlignment = Alignment.CenterHorizontally,
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 = stringResource(R.string.group_chat_empty_title),
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "您还没有加入任何群聊",
color = AppColors.secondaryText,
text = stringResource(R.string.group_chat_empty_subtitle),
color = AppColors.text,
fontSize = 14.sp
)
}

View File

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

View File

@@ -270,6 +270,15 @@ fun Explore() {
state = pagerState,
modifier = Modifier.fillMaxSize()
) { page ->
// 计算当前页面的偏移量
val pageOffset = (
(pagerState.currentPage - page) + pagerState
.currentPageOffsetFraction
).coerceIn(-1f, 1f)
// 根据偏移量计算缩放比例
val scale = 1f - (0.1f * kotlin.math.abs(pageOffset))
AgentPage(
agentItems = agentItems.drop(page * itemsPerPage).take(itemsPerPage),
page = page
@@ -653,7 +662,19 @@ fun Explore() {
contentDescription = "创建智能体",
modifier = Modifier.size(24.dp),
<<<<<<< HEAD
)
=======
)
}
Spacer(modifier = Modifier.size(8.dp))
Text(
text = "发布动态",
fontSize = 12.sp,
color = AppColors.text,
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
)
>>>>>>> upstream/main
}
Spacer(modifier = Modifier.size(8.dp))
Text(
@@ -767,10 +788,35 @@ fun Explore() {
) {
HorizontalPager(
state = pagerState,
<<<<<<< HEAD
modifier = Modifier.fillMaxSize()
) { page ->
val bannerItem = bannerItems[page]
BannerCard(bannerItem = bannerItem)
=======
modifier = Modifier.fillMaxSize(),
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 4.dp),
) { page ->
val bannerItem = bannerItems[page]
// 计算当前页面的偏移量
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
}
)
>>>>>>> upstream/main
}
}

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:strokeWidth="1"
android:pathData="M 0 0 L 24 0 L 24 24 L 0 24 Z" />
<path
android:strokeColor="#000000"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeLineCap="round"
android:pathData="M 15 8 L 15.01 8" />
<path
android:strokeColor="#000000"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeLineCap="round"
android:pathData="M 7 4 L 17 4 Q 20 4 20 7 L 20 17 Q 20 20 17 20 L 7 20 Q 4 20 4 17 L 4 7 Q 4 4 7 4 Z" />
<path
android:strokeColor="#000000"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeLineCap="round"
android:pathData="M4,15 L8,11 C8.92820323,10.106836 10.0717968,10.106836 11,11 L16,16" />
<path
android:strokeColor="#000000"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeLineCap="round"
android:pathData="M14,14 L15,13 C15.9282032,12.106836 17.0717968,12.106836 18,13 L20,15" />
</vector>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillType="evenOdd"
android:pathData="M0 0h24v24H0z" />
<path
android:fillType="evenOdd"
android:strokeColor="#000"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeLineCap="round"
android:pathData="M14 8V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2v-2" />
<path
android:fillType="evenOdd"
android:strokeColor="#000"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeLineCap="round"
android:pathData="M7 12h14l-3-3m0 6 3-3" />
</vector>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillType="evenOdd"
android:strokeColor="#000"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeLineCap="round"
android:pathData="M21 16.5H3a2.7 2.7 0 0 0 2.7-2.7V9.3a6.3 6.3 0 0 1 12.6 0v4.5a2.7 2.7 0 0 0 2.7 2.7h0zm-7.443 3.6a1.8 1.8 0 0 1-3.114 0" />
</vector>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group
android:translateX="2"
android:translateY="4">
<path
android:fillType="evenOdd"
android:strokeColor="#000"
android:strokeWidth="2"
android:pathData="M7.273 10.777c4.016 0 7.272 1.791 7.272 4 0 0.706-0.331 1.368-0.914 1.944-0.392 0.179 -0.828 0.278 -1.288 0.278 H2.202a3.1 3.1 0 0 1-1.288-0.278C0.332 16.145 0 15.483 0 14.777c0-2.209 3.256-4 7.273-4z" />
<path
android:fillType="evenOdd"
android:strokeColor="#000"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeLineCap="round"
android:pathData="M16.491 17h1.236c0.5 0 2.273-0.398 2.273-2.223 0-1.49-1.482-2.79-3.68-3.478" />
<path
android:fillType="evenOdd"
android:strokeColor="#000"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeLineCap="round"
android:pathData="M 7.273 0 C 9.28110735038 0 10.909 1.59162771435 10.909 3.555 C 10.909 5.51837228565 9.28110735038 7.11 7.273 7.11 C 5.26489264962 7.11 3.637 5.51837228565 3.637 3.555 C 3.637 1.59162771435 5.26489264962 0 7.273 0 Z" />
<path
android:fillType="evenOdd"
android:strokeColor="#000"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeLineCap="round"
android:pathData="M13.636 0.116 c1.61 0.402 2.735 1.82 2.735 3.444 0 1.624-1.126 3.041-2.735 3.444" />
</group>
</vector>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<group
android:translateX="2"
android:translateY="4">
<path
android:fillType="evenOdd"
android:strokeColor="#110C13"
android:strokeWidth="1.778"
android:pathData="M7.273 10.777c4.016 0 7.272 1.791 7.272 4 0 0.706-0.331 1.368-0.914 1.944-0.392 0.179 -0.828 0.278 -1.288 0.278 H2.202a3.1 3.1 0 0 1-1.288-0.278C0.332 16.145 0 15.483 0 14.777c0-2.209 3.256-4 7.273-4z" />
<path
android:fillType="evenOdd"
android:strokeColor="#110C13"
android:strokeWidth="1.778"
android:strokeLineJoin="round"
android:strokeLineCap="round"
android:pathData="M 7.273 0 C 9.28110735038 0 10.909 1.59162771435 10.909 3.555 C 10.909 5.51837228565 9.28110735038 7.11 7.273 7.11 C 5.26489264962 7.11 3.637 5.51837228565 3.637 3.555 C 3.637 1.59162771435 5.26489264962 0 7.273 0 Z" />
</group>
<path
android:fillType="evenOdd"
android:strokeColor="#110C13"
android:strokeWidth="2"
android:strokeLineCap="round"
android:pathData="M16.364 10.945h6M19.364 7.945v6" />
</vector>

View File

@@ -1,5 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:alpha="0.77" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zM12,6.5c2.49,0 4,2.02 4,4.5v0.1l2,2L18,11c0,-3.07 -1.63,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68c-0.24,0.06 -0.47,0.15 -0.69,0.23l1.64,1.64c0.18,-0.02 0.36,-0.05 0.55,-0.05zM5.41,3.35L4,4.76l2.81,2.81C6.29,8.57 6,9.74 6,11v5l-2,2v1h14.24l1.74,1.74 1.41,-1.41L5.41,3.35zM16,17L8,17v-6c0,-0.68 0.12,-1.32 0.34,-1.9L16,16.76L16,17z"/>
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillType="evenOdd"
android:strokeColor="#000"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeLineCap="round"
android:pathData="M16.06 16.5H3a2.7 2.7 0 0 0 2.7-2.7V9.3c0-0.913 0.194 -1.78 0.543 -2.563m2.564-2.87A6.3 6.3 0 0 1 18.3 9.3v4.5m-4.743 6.3a1.8 1.8 0 0 1-3.114 0" />
<path
android:strokeColor="#000"
android:strokeWidth="2"
android:strokeLineJoin="round"
android:strokeLineCap="round"
android:pathData="M3 3 18.75 18.75" />
</vector>

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: 1.7 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="agent_chat_list_title">智能体聊天</string>
<string name="agent_chat_empty_title">暂无智能体聊天</string>
<string name="agent_chat_empty_subtitle">开始与智能体对话</string>
<string name="agent_chat_empty_title">AI 们在等你开启第一句对话</string>
<string name="agent_chat_empty_subtitle">去首页探索一下,主动发起一场对话</string>
<string name="agent_chat_me_prefix">我: </string>
<string name="agent_chat_image">[图片]</string>
<string name="agent_chat_voice">[语音]</string>
@@ -159,6 +159,9 @@
<string name="agent_chat_load_more_failed">加载更多失败</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_subtitle">开始与朋友对话吧</string>
<string name="friend_chat_me_prefix">我: </string>
@@ -167,5 +170,9 @@
<string name="quick_create">一键创建</string>
<string name="group_name">群聊名称</string>
<string name="search_placeholder">搜索</string>
<string name="group_info_add_other">添加其他人</string>
<string name="group_info_notice_setting">通知</string>
<string name="group_info_exit">退出</string>
<string name="group_info_edit">编辑资料</string>
</resources>

View File

@@ -155,6 +155,8 @@
<string name="agent_chat_load_failed">加载失败</string>
<string name="agent_chat_load_more_failed">加载更多失败</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_subtitle">开始与朋友对话吧</string>
<string name="friend_chat_me_prefix">我: </string>
@@ -163,5 +165,9 @@
<string name="quick_create">Quick Create</string>
<string name="group_name">Group Name</string>
<string name="search_placeholder">Search</string>
<string name="group_info_add_other">添加其他人</string>
<string name="group_info_notice_setting">通知</string>
<string name="group_info_exit">退出</string>
<string name="group_info_edit">编辑资料</string>
</resources>