会话分组及聊天室实现

This commit is contained in:
weber
2025-08-11 18:21:22 +08:00
parent 54ca1d3f1c
commit 697af504b7
23 changed files with 1049 additions and 115 deletions

1
.idea/misc.xml generated
View File

@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">

View File

@@ -144,7 +144,7 @@ class MainActivity : ComponentActivity() {
sender?.let { sender?.let {
scope.launch { scope.launch {
try { try {
val profile = userService.getUserProfileByTrtcUserId(it) val profile = userService.getUserProfileByTrtcUserId(it,0)
navController.navigate(NavigationRoute.Chat.route.replace( navController.navigate(NavigationRoute.Chat.route.replace(
"{id}", "{id}",
profile.id.toString() profile.id.toString()

View File

@@ -54,7 +54,7 @@ class TrtcService : Service() {
override fun onRecvNewMessage(msg: V2TIMMessage?) { override fun onRecvNewMessage(msg: V2TIMMessage?) {
super.onRecvNewMessage(msg) super.onRecvNewMessage(msg)
msg?.let { msg?.let {
MessageListViewModel.refreshConversation(context, it.sender) //MessageListViewModel.refreshConversation(context, it.sender)
if (MainActivityLifecycle.isForeground) { if (MainActivityLifecycle.isForeground) {
return return
} }

View File

@@ -66,13 +66,8 @@ data class AccountProfile(
followerCount = followerCount, followerCount = followerCount,
followingCount = followingCount, followingCount = followingCount,
nickName = nickname, nickName = nickname,
avatar = if (aiAccount) { avatar =
// 对于AI账户直接使用原始头像URL不添加服务器前缀 "${ApiClient.BASE_SERVER}$avatar",
"${ApiClient.BASE_API_URL+"/outside"}$avatar"+"?token="+"Bearer ${AppStore.token}"
} else {
// 对于普通用户,添加服务器前缀
"${ApiClient.BASE_SERVER}$avatar"
},
bio = bio, bio = bio,
country = "Worldwide", country = "Worldwide",
isFollowing = isFollowing, isFollowing = isFollowing,

View File

@@ -37,7 +37,7 @@ data class Agent(
desc = desc, desc = desc,
createdAt = createdAt, createdAt = createdAt,
updatedAt = updatedAt, updatedAt = updatedAt,
avatar = "${ApiClient.BASE_API_URL+"/outside"}$avatar"+"?token="+"Bearer ${AppStore.token}", avatar = "${ApiClient.BASE_API_URL+"/outside"}$avatar"+"?token="+"${AppStore.token}",
//author = author, //author = author,
isPublic = isPublic, isPublic = isPublic,
openId = openId, openId = openId,

View File

@@ -55,7 +55,7 @@ interface UserService {
* @return 用户信息 * @return 用户信息
*/ */
suspend fun getUserProfileByTrtcUserId(id: String):AccountProfileEntity suspend fun getUserProfileByTrtcUserId(id: String,includeAI: Int):AccountProfileEntity
/** /**
* 获取用户信息 * 获取用户信息
@@ -107,8 +107,8 @@ class UserServiceImpl : UserService {
) )
} }
override suspend fun getUserProfileByTrtcUserId(id: String): AccountProfileEntity { override suspend fun getUserProfileByTrtcUserId(id: String,includeAI: Int): AccountProfileEntity {
val resp = ApiClient.api.getAccountProfileByTrtcUserId(id) val resp = ApiClient.api.getAccountProfileByTrtcUserId(id,includeAI)
val body = resp.body() ?: throw ServiceException("Failed to get account") val body = resp.body() ?: throw ServiceException("Failed to get account")
return body.data.toAccountProfileEntity() return body.data.toAccountProfileEntity()
} }

View File

@@ -51,6 +51,9 @@ data class SendChatAiRequestBody(
val toTrtcUserId: String, val toTrtcUserId: String,
@SerializedName("message") @SerializedName("message")
val message: String, val message: String,
@SerializedName("skipTrtc")
val skipTrtc: Boolean,
) )
data class LoginUserRequestBody( data class LoginUserRequestBody(
@@ -380,7 +383,8 @@ interface RaveNowAPI {
@GET("profile/trtc/{id}") @GET("profile/trtc/{id}")
suspend fun getAccountProfileByTrtcUserId( suspend fun getAccountProfileByTrtcUserId(
@Path("id") id: String @Path("id") id: String,
@Query("includeAI") includeAI: Int
): Response<DataContainer<AccountProfile>> ): Response<DataContainer<AccountProfile>>

View File

@@ -423,6 +423,7 @@ fun NavigationController(
) { ) {
AddAgentScreen() AddAgentScreen()
} }
} }
} }

View File

@@ -186,7 +186,7 @@ fun ChatAiScreen(userId: String) {
CustomAsyncImage( CustomAsyncImage(
imageUrl = viewModel.userProfile?.avatar ?: "", imageUrl = viewModel.userProfile?.avatar ?: "",
modifier = Modifier modifier = Modifier
.size(40.dp) .size(32.dp)
.clip(RoundedCornerShape(40.dp)), .clip(RoundedCornerShape(40.dp)),
contentDescription = "avatar" contentDescription = "avatar"
) )
@@ -197,7 +197,7 @@ fun ChatAiScreen(userId: String) {
style = TextStyle( style = TextStyle(
color = AppColors.text, color = AppColors.text,
fontSize = 18.sp, fontSize = 18.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.Bold fontWeight = androidx.compose.ui.text.font.FontWeight.W700
) )
) )
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))

View File

@@ -260,7 +260,7 @@ class ChatAiViewModel(
message: String, message: String,
) { ) {
viewModelScope.launch { viewModelScope.launch {
val response = ApiClient.api.sendChatAiMessage(SendChatAiRequestBody(fromTrtcUserId = fromTrtcUserId,toTrtcUserId = toTrtcUserId,message = message)) val response = ApiClient.api.sendChatAiMessage(SendChatAiRequestBody(fromTrtcUserId = fromTrtcUserId,toTrtcUserId = toTrtcUserId,message = message,skipTrtc = true))
} }
} }

View File

@@ -57,7 +57,8 @@ fun CustomAsyncImage(
) { ) {
val localContext = LocalContext.current val localContext = LocalContext.current
val imageLoader = getImageLoader(context ?: localContext) // 使用remember来缓存ImageLoader避免重复创建
val imageLoader = remember { getImageLoader(context ?: localContext) }
// 处理 imageUrl 为 null 或空字符串的情况 // 处理 imageUrl 为 null 或空字符串的情况
if (imageUrl == null || imageUrl == "") { if (imageUrl == null || imageUrl == "") {
@@ -89,6 +90,8 @@ fun CustomAsyncImage(
model = ImageRequest.Builder(context ?: localContext) model = ImageRequest.Builder(context ?: localContext)
.data(imageUrl) .data(imageUrl)
.crossfade(200) .crossfade(200)
.memoryCachePolicy(coil.request.CachePolicy.ENABLED)
.diskCachePolicy(coil.request.CachePolicy.ENABLED)
.apply { .apply {
// 设置占位符图片 // 设置占位符图片
if (placeholderRes != null) { if (placeholderRes != null) {

View File

@@ -51,7 +51,7 @@ fun Agent() {
val navigationBarPaddings = val navigationBarPaddings =
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues() val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
var pagerState = rememberPagerState { 4 } var pagerState = rememberPagerState { 2 }
var scope = rememberCoroutineScope() var scope = rememberCoroutineScope()
Column( Column(
modifier = Modifier modifier = Modifier
@@ -142,7 +142,7 @@ fun Agent() {
} }
} }
) )
TabSpacer() /*TabSpacer()
TabItem( TabItem(
text = stringResource(R.string.agent_recommend), text = stringResource(R.string.agent_recommend),
isSelected = pagerState.currentPage == 2, isSelected = pagerState.currentPage == 2,
@@ -161,7 +161,7 @@ fun Agent() {
pagerState.animateScrollToPage(3) pagerState.animateScrollToPage(3)
} }
} }
) )*/
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
HorizontalPager( HorizontalPager(

View File

@@ -28,6 +28,7 @@ import androidx.compose.ui.text.font.FontWeight
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 com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.composables.AgentCard import com.aiosman.ravenow.ui.composables.AgentCard
@@ -37,6 +38,7 @@ fun HotAgent() {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val model = HotAgentViewModel val model = HotAgentViewModel
var agentList = model.agentList var agentList = model.agentList
val navController = LocalNavController.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val state = rememberPullRefreshState(model.refreshing, onRefresh = { val state = rememberPullRefreshState(model.refreshing, onRefresh = {
model.refreshPager(pullRefresh = true) model.refreshPager(pullRefresh = true)
@@ -108,7 +110,11 @@ fun HotAgent() {
key = { idx -> idx } key = { idx -> idx }
) { idx -> ) { idx ->
val agentItem = agentList[idx] val agentItem = agentList[idx]
AgentCard(agentEntity = agentItem) AgentCard(agentEntity = agentItem,
onClick = {
model.createSingleChat(agentItem.openId)
model.goToChatAi(agentItem.openId,navController)
})
} }
// 加载更多指示器 // 加载更多指示器

View File

@@ -5,9 +5,13 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.navigation.NavHostController
import com.aiosman.ravenow.data.api.ApiClient import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.data.ServiceException import com.aiosman.ravenow.data.ServiceException
import com.aiosman.ravenow.data.api.SingleChatRequestBody
import com.aiosman.ravenow.entity.AgentEntity import com.aiosman.ravenow.entity.AgentEntity
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.createGroup2ChatAi
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userService
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
object HotAgentViewModel : ViewModel() { object HotAgentViewModel : ViewModel() {
@@ -87,4 +91,21 @@ object HotAgentViewModel : ViewModel() {
} }
} }
} }
fun createSingleChat(
openId: String,
) {
viewModelScope.launch {
val response = ApiClient.api.createSingleChat(SingleChatRequestBody(generateText = openId))
}
}
fun goToChatAi(
openId: String,
navController: NavHostController
) {
viewModelScope.launch {
val profile = userService.getUserProfileByOpenId(openId)
createGroup2ChatAi(profile.trtcUserId,"ai_group",navController,profile.id)
}
}
} }

View File

@@ -8,22 +8,14 @@ import androidx.lifecycle.viewModelScope
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.aiosman.ravenow.data.api.ApiClient import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.data.ServiceException import com.aiosman.ravenow.data.ServiceException
import com.aiosman.ravenow.data.api.AgentMomentRequestBody
import com.aiosman.ravenow.data.api.SingleChatRequestBody import com.aiosman.ravenow.data.api.SingleChatRequestBody
import com.aiosman.ravenow.entity.AgentEntity import com.aiosman.ravenow.entity.AgentEntity
import com.aiosman.ravenow.ui.index.tabs.message.Conversation
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userService import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userService
import com.aiosman.ravenow.ui.navigateToChat
import com.aiosman.ravenow.ui.navigateToChatAi import com.aiosman.ravenow.ui.navigateToChatAi
import com.tencent.imsdk.v2.V2TIMConversation
import com.tencent.imsdk.v2.V2TIMConversationListFilter
import com.tencent.imsdk.v2.V2TIMConversationOperationResult import com.tencent.imsdk.v2.V2TIMConversationOperationResult
import com.tencent.imsdk.v2.V2TIMConversationResult
import com.tencent.imsdk.v2.V2TIMManager import com.tencent.imsdk.v2.V2TIMManager
import com.tencent.imsdk.v2.V2TIMValueCallback import com.tencent.imsdk.v2.V2TIMValueCallback
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.toRequestBody
object MineAgentViewModel : ViewModel() { object MineAgentViewModel : ViewModel() {
var agentList by mutableStateOf<List<AgentEntity>>(emptyList()) var agentList by mutableStateOf<List<AgentEntity>>(emptyList())
@@ -105,7 +97,9 @@ object MineAgentViewModel : ViewModel() {
fun createGroup2ChatAi( fun createGroup2ChatAi(
trtcUserId: String, trtcUserId: String,
groupName: String groupName: String,
navController: NavHostController,
id: Int
) { ) {
val conversationIDList = listOf("c2c_${trtcUserId}") val conversationIDList = listOf("c2c_${trtcUserId}")
@@ -115,6 +109,7 @@ object MineAgentViewModel : ViewModel() {
object : V2TIMValueCallback<List<V2TIMConversationOperationResult>> { object : V2TIMValueCallback<List<V2TIMConversationOperationResult>> {
override fun onSuccess(v2TIMConversationOperationResults: List<V2TIMConversationOperationResult>) { override fun onSuccess(v2TIMConversationOperationResults: List<V2TIMConversationOperationResult>) {
// 创建会话分组成功 // 创建会话分组成功
navController.navigateToChatAi(id.toString())
} }
override fun onError(code: Int, desc: String) { override fun onError(code: Int, desc: String) {
@@ -122,32 +117,7 @@ object MineAgentViewModel : ViewModel() {
} }
} }
) )
V2TIMManager.getConversationManager().getConversationGroupList(object : V2TIMValueCallback<List<String>> {
override fun onSuccess(v2TIMConversationOperationResults: List<String>) {
// 获取会话分组列表成功
}
override fun onError(code: Int, desc: String?) {
TODO("Not yet implemented")
}
})
val filter = V2TIMConversationListFilter()
filter.conversationGroup = "ai_group"
V2TIMManager.getConversationManager().getConversationListByFilter(
filter,
1000,
10000,
object : V2TIMValueCallback<V2TIMConversationResult> {
override fun onSuccess(v2TIMConversationResult: V2TIMConversationResult) {
// 获取会话列表成功
}
override fun onError(code: Int, desc: String) {
// 获取会话列表失败
}
}
)
} }
@@ -165,8 +135,7 @@ object MineAgentViewModel : ViewModel() {
) { ) {
viewModelScope.launch { viewModelScope.launch {
val profile = userService.getUserProfileByOpenId(openId) val profile = userService.getUserProfileByOpenId(openId)
createGroup2ChatAi(profile.trtcUserId,"ai_group") createGroup2ChatAi(profile.trtcUserId,"ai_group",navController,profile.id)
navController.navigateToChatAi(profile.id.toString())
} }
} }
} }

View File

@@ -9,11 +9,16 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize 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.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.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
@@ -25,7 +30,6 @@ import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -58,6 +62,8 @@ import com.aiosman.ravenow.ui.composables.StatusBarSpacer
import com.aiosman.ravenow.ui.composables.TabItem 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.FriendChatListScreen
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
@@ -81,12 +87,20 @@ fun NotificationsScreen() {
MessageListViewModel.initData(context, force = true, loadChat = AppState.enableChat) MessageListViewModel.initData(context, force = true, loadChat = AppState.enableChat)
} }
}) })
val navigationBarPaddings =
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
systemUiController.setNavigationBarColor(Color.Transparent) systemUiController.setNavigationBarColor(Color.Transparent)
MessageListViewModel.initData(context, loadChat = AppState.enableChat) MessageListViewModel.initData(context, loadChat = AppState.enableChat)
} }
Column( Column(
modifier = Modifier.fillMaxSize() modifier = Modifier
.fillMaxSize()
.padding(
bottom = navigationBarPaddings,
),
) { ) {
StatusBarSpacer() StatusBarSpacer()
Box( Box(
@@ -105,9 +119,10 @@ fun NotificationsScreen() {
.padding(horizontal = 15.dp, vertical = 8.dp), .padding(horizontal = 15.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Box(
Box(modifier = Modifier.size(24.dp)) modifier = Modifier
.size(24.dp)
)
Column( Column(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
@@ -193,6 +208,7 @@ fun NotificationsScreen() {
scope.launch { scope.launch {
pagerState.animateScrollToPage(0) pagerState.animateScrollToPage(0)
} }
} }
) )
TabSpacer() TabSpacer()
@@ -224,7 +240,7 @@ fun NotificationsScreen() {
) { ) {
when (it) { when (it) {
0 -> { 0 -> {
AgentChatListScreen()
} }
1 -> { 1 -> {
@@ -232,15 +248,14 @@ fun NotificationsScreen() {
} }
2 -> { 2 -> {
FriendChatListScreen()
} }
} }
} }
Box( /* Box(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.fillMaxWidth() .fillMaxWidth()
@@ -271,7 +286,8 @@ fun NotificationsScreen() {
MessageListViewModel.isLoading, MessageListViewModel.isLoading,
state, state,
Modifier.align(Alignment.TopCenter) Modifier.align(Alignment.TopCenter)
) )*/
}
} }
} }

View File

@@ -167,9 +167,9 @@ object MessageListViewModel : ViewModel() {
) )
} }
chatList = result?.conversationList?.map { msg: V2TIMConversation -> /* chatList = result?.conversationList?.map { msg: V2TIMConversation ->
Conversation.convertToConversation(msg, context) Conversation.convertToConversation(msg, context)
} ?: emptyList() } ?: emptyList()*/
} }
suspend fun loadUnreadCount() { suspend fun loadUnreadCount() {
@@ -181,36 +181,5 @@ object MessageListViewModel : ViewModel() {
} }
} }
fun goToChat(
conversation: Conversation,
navController: NavHostController
) {
viewModelScope.launch {
val profile = userService.getUserProfileByTrtcUserId(conversation.trtcUserId)
navController.navigateToChat(profile.id.toString())
}
}
fun goToUserDetail(
conversation: Conversation,
navController: NavController
) {
viewModelScope.launch {
val profile = userService.getUserProfileByTrtcUserId(conversation.trtcUserId)
navController.navigate(
NavigationRoute.AccountProfile.route.replace(
"{id}",
profile.id.toString()
)
)
}
}
fun refreshConversation(context: Context, userId: String) {
viewModelScope.launch {
loadChatList(context)
}
}
} }

View File

@@ -0,0 +1,267 @@
package com.aiosman.ravenow.ui.index.tabs.message.tab
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
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.res.stringResource
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.modifiers.noRippleClickable
/**
* 智能体聊天列表页面
*/
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun AgentChatListScreen() {
val context = LocalContext.current
val navController = LocalNavController.current
val AppColors = LocalAppTheme.current
val model = AgentChatListViewModel
val state = rememberPullRefreshState(
refreshing = AgentChatListViewModel.refreshing,
onRefresh = {
AgentChatListViewModel.refreshPager(pullRefresh = true, context = context)
}
)
// 初始化数据
LaunchedEffect(Unit) {
AgentChatListViewModel.refreshPager(context = context)
}
Column(
modifier = Modifier
.fillMaxSize()
.background(AppColors.background)
) {
// 聊天列表
Box(
modifier = Modifier
.fillMaxSize()
.pullRefresh(state)
) {
if (AgentChatListViewModel.agentChatList.isEmpty() && !AgentChatListViewModel.isLoading) {
// 空状态
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = stringResource(R.string.agent_chat_empty_title),
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.agent_chat_empty_subtitle),
color = AppColors.secondaryText,
fontSize = 14.sp
)
}
} else {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
itemsIndexed(
items = AgentChatListViewModel.agentChatList,
key = { _, item -> item.id }
) { index, item ->
AgentChatItem(
conversation = item,
onUserAvatarClick = { conv ->
AgentChatListViewModel.goToUserDetail(conv, navController)
},
onChatClick = { conv ->
model.createSingleChat(conv.trtcUserId)
model.goToChatAi(conv.trtcUserId,navController)
}
)
if (index < AgentChatListViewModel.agentChatList.size - 1) {
HorizontalDivider(
modifier = Modifier.padding(horizontal = 24.dp),
color = AppColors.divider
)
}
}
// 加载更多指示器
if (AgentChatListViewModel.isLoading && AgentChatListViewModel.agentChatList.isNotEmpty()) {
item {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = AppColors.main
)
}
}
}
}
}
// 下拉刷新指示器
PullRefreshIndicator(
refreshing = AgentChatListViewModel.refreshing,
state = state,
modifier = Modifier.align(Alignment.TopCenter)
)
}
// 错误信息显示
AgentChatListViewModel.error?.let { error ->
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Text(
text = error,
color = AppColors.error,
fontSize = 14.sp
)
}
}
}
}
@Composable
fun AgentChatItem(
conversation: AgentConversation,
onUserAvatarClick: (AgentConversation) -> Unit = {},
onChatClick: (AgentConversation) -> Unit = {}
) {
val AppColors = LocalAppTheme.current
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp, vertical = 12.dp)
.noRippleClickable {
onChatClick(conversation)
}
) {
// 头像
Box {
CustomAsyncImage(
context = LocalContext.current,
imageUrl = conversation.avatar,
contentDescription = conversation.nickname,
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.noRippleClickable {
onUserAvatarClick(conversation)
}
)
}
// 聊天信息
Column(
modifier = Modifier
.weight(1f)
.padding(start = 12.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = conversation.nickname,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = AppColors.text,
modifier = Modifier.weight(1f)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = conversation.lastMessageTime,
fontSize = 12.sp,
color = AppColors.secondaryText
)
}
Spacer(modifier = Modifier.height(4.dp))
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "${if (conversation.isSelf) stringResource(R.string.agent_chat_me_prefix) else ""}${conversation.displayText}",
fontSize = 14.sp,
color = AppColors.secondaryText,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
)
Spacer(modifier = Modifier.width(8.dp))
// 未读消息数量
if (conversation.unreadCount > 0) {
Box(
modifier = Modifier
.size(if (conversation.unreadCount > 99) 24.dp else 20.dp)
.background(
color = AppColors.main,
shape = CircleShape
),
contentAlignment = Alignment.Center
) {
Text(
text = if (conversation.unreadCount > 99) "99+" else conversation.unreadCount.toString(),
color = AppColors.mainText,
fontSize = if (conversation.unreadCount > 99) 9.sp else 10.sp,
fontWeight = FontWeight.Bold
)
}
}
}
}
}
}

View File

@@ -0,0 +1,219 @@
package com.aiosman.ravenow.ui.index.tabs.message.tab
import android.content.Context
import android.icu.util.Calendar
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavHostController
import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.AppStore
import com.aiosman.ravenow.ConstVars
import com.aiosman.ravenow.R
import com.aiosman.ravenow.data.UserService
import com.aiosman.ravenow.data.UserServiceImpl
import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.data.api.SingleChatRequestBody
import com.aiosman.ravenow.exp.formatChatTime
import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.createGroup2ChatAi
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel
import com.aiosman.ravenow.ui.navigateToChatAi
import com.tencent.imsdk.v2.V2TIMConversation
import com.tencent.imsdk.v2.V2TIMConversationListFilter
import com.tencent.imsdk.v2.V2TIMConversationResult
import com.tencent.imsdk.v2.V2TIMManager
import com.tencent.imsdk.v2.V2TIMMessage
import com.tencent.imsdk.v2.V2TIMValueCallback
import kotlinx.coroutines.launch
import kotlin.coroutines.suspendCoroutine
data class AgentConversation(
val id: String,
val trtcUserId: String,
val nickname: String,
val lastMessage: String,
val lastMessageTime: String,
val avatar: String = "",
val unreadCount: Int = 0,
val displayText: String,
val isSelf: Boolean
) {
companion object {
fun convertToAgentConversation(msg: V2TIMConversation, context: Context): AgentConversation {
val lastMessage = Calendar.getInstance().apply {
timeInMillis = msg.lastMessage?.timestamp ?: 0
timeInMillis *= 1000
}
var displayText = ""
when (msg.lastMessage?.elemType) {
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
displayText = msg.lastMessage?.textElem?.text ?: ""
}
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> {
displayText = "[图片]"
}
V2TIMMessage.V2TIM_ELEM_TYPE_SOUND -> {
displayText = "[语音]"
}
V2TIMMessage.V2TIM_ELEM_TYPE_VIDEO -> {
displayText = "[视频]"
}
V2TIMMessage.V2TIM_ELEM_TYPE_FILE -> {
displayText = "[文件]"
}
else -> {
displayText = "[消息]"
}
}
return AgentConversation(
id = msg.conversationID,
nickname = msg.showName,
lastMessage = msg.lastMessage?.textElem?.text ?: "",
lastMessageTime = lastMessage.time.formatChatTime(context),
avatar = "${ApiClient.BASE_API_URL+"/"}${msg.faceUrl}"+"?token="+"${AppStore.token}".replace("storage/avatars/", "/avatar/"),
unreadCount = msg.unreadCount,
trtcUserId = msg.userID,
displayText = displayText,
isSelf = msg.lastMessage?.sender == AppState.profile?.trtcUserId
)
}
}
}
object AgentChatListViewModel : ViewModel() {
val userService: UserService = UserServiceImpl()
var agentChatList by mutableStateOf<List<AgentConversation>>(emptyList())
var isLoading by mutableStateOf(false)
var refreshing by mutableStateOf(false)
var hasNext by mutableStateOf(true)
var currentPage by mutableStateOf(1)
var error by mutableStateOf<String?>(null)
private val pageSize = 20
fun refreshPager(pullRefresh: Boolean = false, context: Context? = null) {
if (isLoading && !pullRefresh) return
viewModelScope.launch {
try {
isLoading = true
refreshing = pullRefresh
error = null
context?.let { loadAgentChatList(it) }
currentPage = 1
} catch (e: Exception) {
error = e.message ?: context?.getString(R.string.agent_chat_load_failed)
e.printStackTrace()
} finally {
isLoading = false
refreshing = false
}
}
}
fun loadMore() {
if (isLoading || !hasNext) return
viewModelScope.launch {
try {
isLoading = true
error = null
// 腾讯IM的会话列表是一次性获取的这里模拟分页
// 实际项目中可能需要根据时间戳或其他方式实现真正的分页
hasNext = false
} catch (e: Exception) {
error = ""
e.printStackTrace()
} finally {
isLoading = false
}
}
}
private suspend fun loadAgentChatList(context: Context) {
val result = suspendCoroutine { continuation ->
val filter = V2TIMConversationListFilter()
filter.conversationGroup = "ai_group"
V2TIMManager.getConversationManager().getConversationListByFilter(
filter,
0,
Int.MAX_VALUE,
object : V2TIMValueCallback<V2TIMConversationResult> {
override fun onSuccess(t: V2TIMConversationResult?) {
continuation.resumeWith(Result.success(t))
}
override fun onError(code: Int, desc: String?) {
continuation.resumeWith(Result.failure(Exception("Error $code: $desc")))
}
}
)
}
agentChatList = result?.conversationList?.map { msg: V2TIMConversation ->
val conversation = AgentConversation.convertToAgentConversation(msg, context)
println("AgentChatList: Conversation ${conversation.nickname} has ${conversation.unreadCount} unread messages")
conversation
} ?: emptyList()
}
fun goToChatAi(
conversation: AgentConversation,
navController: NavHostController
) {
viewModelScope.launch {
try {
val profile = userService.getUserProfileByTrtcUserId(conversation.trtcUserId,1)
navController.navigateToChatAi(profile.id.toString())
} catch (e: Exception) {
error = ""
e.printStackTrace()
}
}
}
fun goToUserDetail(
conversation: AgentConversation,
navController: NavHostController
) {
viewModelScope.launch {
try {
val profile = userService.getUserProfileByTrtcUserId(conversation.trtcUserId,1)
navController.navigate(
NavigationRoute.AccountProfile.route.replace(
"{id}",
profile.id.toString()
)
)
} catch (e: Exception) {
error = ""
e.printStackTrace()
}
}
}
fun refreshConversation(context: Context, userId: String) {
viewModelScope.launch {
loadAgentChatList(context)
}
}
fun createSingleChat(
openId: String,
) {
viewModelScope.launch {
val response = ApiClient.api.createSingleChat(SingleChatRequestBody(generateText = openId))
}
}
fun goToChatAi(
openId: String,
navController: NavHostController
) {
viewModelScope.launch {
val profile = MessageListViewModel.userService.getUserProfileByTrtcUserId(openId,1)
navController.navigateToChatAi(profile.id.toString())
}
}
}

View File

@@ -0,0 +1,244 @@
package com.aiosman.ravenow.ui.index.tabs.message.tab
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
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 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.modifiers.noRippleClickable
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun FriendChatListScreen() {
val context = LocalContext.current
val navController = LocalNavController.current
val AppColors = LocalAppTheme.current
val model = FriendChatListViewModel
val state = rememberPullRefreshState(
refreshing = FriendChatListViewModel.refreshing,
onRefresh = {
FriendChatListViewModel.refreshPager(pullRefresh = true, context = context)
}
)
LaunchedEffect(Unit) {
FriendChatListViewModel.refreshPager(context = context)
}
Column(
modifier = Modifier
.fillMaxSize()
.background(AppColors.background)
) {
Box(
modifier = Modifier
.fillMaxSize()
.pullRefresh(state)
) {
if (FriendChatListViewModel.friendChatList.isEmpty() && !FriendChatListViewModel.isLoading) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = stringResource(R.string.friend_chat_empty_title),
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.friend_chat_empty_subtitle),
color = AppColors.secondaryText,
fontSize = 14.sp
)
}
} else {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
itemsIndexed(
items = FriendChatListViewModel.friendChatList,
key = { _, item -> item.id }
) { index, item ->
FriendChatItem(
conversation = item,
onUserAvatarClick = { conv ->
FriendChatListViewModel.goToUserDetail(conv, navController)
},
onChatClick = { conv ->
FriendChatListViewModel.goToChat(conv, navController)
}
)
if (index < FriendChatListViewModel.friendChatList.size - 1) {
HorizontalDivider(
modifier = Modifier.padding(horizontal = 24.dp),
color = AppColors.divider
)
}
}
if (FriendChatListViewModel.isLoading && FriendChatListViewModel.friendChatList.isNotEmpty()) {
item {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = AppColors.main
)
}
}
}
}
}
PullRefreshIndicator(
refreshing = FriendChatListViewModel.refreshing,
state = state,
modifier = Modifier.align(Alignment.TopCenter)
)
}
FriendChatListViewModel.error?.let { error ->
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Text(
text = error,
color = AppColors.error,
fontSize = 14.sp
)
}
}
}
}
@Composable
fun FriendChatItem(
conversation: FriendConversation,
onUserAvatarClick: (FriendConversation) -> Unit = {},
onChatClick: (FriendConversation) -> Unit = {}
) {
val AppColors = LocalAppTheme.current
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp, vertical = 12.dp)
.noRippleClickable {
onChatClick(conversation)
}
) {
Box {
CustomAsyncImage(
context = LocalContext.current,
imageUrl = conversation.avatar,
contentDescription = conversation.nickname,
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.noRippleClickable {
onUserAvatarClick(conversation)
}
)
}
Column(
modifier = Modifier
.weight(1f)
.padding(start = 12.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = conversation.nickname,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = AppColors.text,
modifier = Modifier.weight(1f)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = conversation.lastMessageTime,
fontSize = 12.sp,
color = AppColors.secondaryText
)
}
Spacer(modifier = Modifier.height(4.dp))
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "${if (conversation.isSelf) stringResource(R.string.friend_chat_me_prefix) else ""}${conversation.displayText}",
fontSize = 14.sp,
color = AppColors.secondaryText,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
)
Spacer(modifier = Modifier.width(8.dp))
if (conversation.unreadCount > 0) {
Box(
modifier = Modifier
.size(if (conversation.unreadCount > 99) 24.dp else 20.dp)
.background(
color = AppColors.main,
shape = CircleShape
),
contentAlignment = Alignment.Center
) {
Text(
text = if (conversation.unreadCount > 99) "99+" else conversation.unreadCount.toString(),
color = AppColors.mainText,
fontSize = if (conversation.unreadCount > 99) 9.sp else 10.sp,
fontWeight = FontWeight.Bold
)
}
}
}
}
}
}

View File

@@ -0,0 +1,198 @@
package com.aiosman.ravenow.ui.index.tabs.message.tab
import android.content.Context
import android.icu.util.Calendar
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavHostController
import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.ConstVars
import com.aiosman.ravenow.data.UserService
import com.aiosman.ravenow.data.UserServiceImpl
import com.aiosman.ravenow.exp.formatChatTime
import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.navigateToChat
import com.tencent.imsdk.v2.V2TIMConversation
import com.tencent.imsdk.v2.V2TIMConversationResult
import com.tencent.imsdk.v2.V2TIMManager
import com.tencent.imsdk.v2.V2TIMMessage
import com.tencent.imsdk.v2.V2TIMValueCallback
import kotlinx.coroutines.launch
import kotlin.coroutines.suspendCoroutine
data class FriendConversation(
val id: String,
val trtcUserId: String,
val nickname: String,
val lastMessage: String,
val lastMessageTime: String,
val avatar: String = "",
val unreadCount: Int = 0,
val displayText: String,
val isSelf: Boolean
) {
companion object {
fun convertToFriendConversation(msg: V2TIMConversation, context: Context): FriendConversation {
val lastMessage = Calendar.getInstance().apply {
timeInMillis = msg.lastMessage?.timestamp ?: 0
timeInMillis *= 1000
}
var displayText = ""
when (msg.lastMessage?.elemType) {
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
displayText = msg.lastMessage?.textElem?.text ?: ""
}
V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE -> {
displayText = "[图片]"
}
V2TIMMessage.V2TIM_ELEM_TYPE_SOUND -> {
displayText = "[语音]"
}
V2TIMMessage.V2TIM_ELEM_TYPE_VIDEO -> {
displayText = "[视频]"
}
V2TIMMessage.V2TIM_ELEM_TYPE_FILE -> {
displayText = "[文件]"
}
else -> {
displayText = "[消息]"
}
}
return FriendConversation(
id = msg.conversationID,
nickname = msg.showName,
lastMessage = msg.lastMessage?.textElem?.text ?: "",
lastMessageTime = lastMessage.time.formatChatTime(context),
avatar = "${ConstVars.BASE_SERVER}${msg.faceUrl}",
unreadCount = msg.unreadCount,
trtcUserId = msg.userID,
displayText = displayText,
isSelf = msg.lastMessage?.sender == AppState.profile?.trtcUserId
)
}
}
}
object FriendChatListViewModel : ViewModel() {
val userService: UserService = UserServiceImpl()
var friendChatList by mutableStateOf<List<FriendConversation>>(emptyList())
var isLoading by mutableStateOf(false)
var refreshing by mutableStateOf(false)
var hasNext by mutableStateOf(true)
var currentPage by mutableStateOf(1)
var error by mutableStateOf<String?>(null)
private val pageSize = 20
fun refreshPager(pullRefresh: Boolean = false, context: Context? = null) {
if (isLoading && !pullRefresh) return
viewModelScope.launch {
try {
isLoading = true
refreshing = pullRefresh
error = null
context?.let { loadFriendChatList(it) }
currentPage = 1
} catch (e: Exception) {
error = ""
e.printStackTrace()
} finally {
isLoading = false
refreshing = false
}
}
}
fun loadMore() {
if (isLoading || !hasNext) return
viewModelScope.launch {
try {
isLoading = true
error = null
// 腾讯IM的会话列表是一次性获取的这里模拟分页
// 实际项目中可能需要根据时间戳或其他方式实现真正的分页
hasNext = false
} catch (e: Exception) {
error = ""
e.printStackTrace()
} finally {
isLoading = false
}
}
}
private suspend fun loadFriendChatList(context: Context) {
val result = suspendCoroutine { continuation ->
// 获取全部会话列表
V2TIMManager.getConversationManager().getConversationList(
0,
Int.MAX_VALUE,
object : V2TIMValueCallback<V2TIMConversationResult> {
override fun onSuccess(t: V2TIMConversationResult?) {
continuation.resumeWith(Result.success(t))
}
override fun onError(code: Int, desc: String?) {
continuation.resumeWith(Result.failure(Exception("Error $code: $desc")))
}
}
)
}
// 过滤掉ai_group的会话只保留朋友聊天
val filteredConversations = result?.conversationList?.filter { conversation ->
// 排除ai_group的会话
!conversation.conversationGroupList.contains("ai_group")
} ?: emptyList()
friendChatList = filteredConversations.map { msg: V2TIMConversation ->
val conversation = FriendConversation.convertToFriendConversation(msg, context)
println("FriendChatList: Conversation ${conversation.nickname} has ${conversation.unreadCount} unread messages")
conversation
}
}
fun goToChat(
conversation: FriendConversation,
navController: NavHostController
) {
viewModelScope.launch {
try {
val profile = userService.getUserProfileByTrtcUserId(conversation.trtcUserId,0)
navController.navigateToChat(profile.id.toString())
} catch (e: Exception) {
error = ""
e.printStackTrace()
}
}
}
fun goToUserDetail(
conversation: FriendConversation,
navController: NavHostController
) {
viewModelScope.launch {
try {
val profile = userService.getUserProfileByTrtcUserId(conversation.trtcUserId,0)
navController.navigate(
NavigationRoute.AccountProfile.route.replace(
"{id}",
profile.id.toString()
)
)
} catch (e: Exception) {
error = ""
e.printStackTrace()
}
}
}
fun refreshConversation(context: Context, userId: String) {
viewModelScope.launch {
loadFriendChatList(context)
}
}
}

View File

@@ -62,17 +62,6 @@
<string name="confirm_new_password">确认新密码</string> <string name="confirm_new_password">确认新密码</string>
<string name="confirm_new_password_tip1">请确认新密码</string> <string name="confirm_new_password_tip1">请确认新密码</string>
<string name="change_password">修改密码</string>
<string name="please_enter_current_password">请输入当前密码</string>
<string name="password_length_tip">密码长度至少8位</string>
<string name="password_digit_tip">密码必须包含至少一个数字</string>
<string name="password_uppercase_tip">密码必须包含至少一个大写字母</string>
<string name="password_lowercase_tip">密码必须包含至少一个小写字母</string>
<string name="password_not_match">两次输入的密码不一致</string>
<string name="enter_current_password">请输入当前密码</string>
<string name="enter_new_password">请输入新密码</string>
<string name="enter_new_password_again">请再次输入新密码</string>
<string name="confirm_change">确认修改</string>
<string name="cancel">取消</string> <string name="cancel">取消</string>
<string name="bio">个性签名</string> <string name="bio">个性签名</string>
@@ -156,5 +145,22 @@
<string name="chat_group">群聊</string> <string name="chat_group">群聊</string>
<string name="chat_friend">朋友</string> <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_me_prefix">我: </string>
<string name="agent_chat_image">[图片]</string>
<string name="agent_chat_voice">[语音]</string>
<string name="agent_chat_video">[视频]</string>
<string name="agent_chat_file">[文件]</string>
<string name="agent_chat_message">[消息]</string>
<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="friend_chat_empty_title">暂无朋友聊天</string>
<string name="friend_chat_empty_subtitle">开始与朋友对话吧</string>
<string name="friend_chat_me_prefix">我: </string>
<string name="friend_chat_load_failed">加载失败</string>
</resources> </resources>

View File

@@ -142,4 +142,21 @@
<string name="chat_ai">Ai</string> <string name="chat_ai">Ai</string>
<string name="chat_group">Group</string> <string name="chat_group">Group</string>
<string name="chat_friend">Friends</string> <string name="chat_friend">Friends</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_me_prefix">我: </string>
<string name="agent_chat_image">[图片]</string>
<string name="agent_chat_voice">[语音]</string>
<string name="agent_chat_video">[视频]</string>
<string name="agent_chat_file">[文件]</string>
<string name="agent_chat_message">[消息]</string>
<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="friend_chat_empty_title">暂无朋友聊天</string>
<string name="friend_chat_empty_subtitle">开始与朋友对话吧</string>
<string name="friend_chat_me_prefix">我: </string>
<string name="friend_chat_load_failed">加载失败</string>
</resources> </resources>