diff --git a/.idea/misc.xml b/.idea/misc.xml index 74dd639..b2c751a 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/app/src/main/java/com/aiosman/ravenow/MainActivity.kt b/app/src/main/java/com/aiosman/ravenow/MainActivity.kt index e43a0bd..ca0250f 100644 --- a/app/src/main/java/com/aiosman/ravenow/MainActivity.kt +++ b/app/src/main/java/com/aiosman/ravenow/MainActivity.kt @@ -144,7 +144,7 @@ class MainActivity : ComponentActivity() { sender?.let { scope.launch { try { - val profile = userService.getUserProfileByTrtcUserId(it) + val profile = userService.getUserProfileByTrtcUserId(it,0) navController.navigate(NavigationRoute.Chat.route.replace( "{id}", profile.id.toString() diff --git a/app/src/main/java/com/aiosman/ravenow/TrtcService.kt b/app/src/main/java/com/aiosman/ravenow/TrtcService.kt index 4740ead..e3708e7 100644 --- a/app/src/main/java/com/aiosman/ravenow/TrtcService.kt +++ b/app/src/main/java/com/aiosman/ravenow/TrtcService.kt @@ -54,7 +54,7 @@ class TrtcService : Service() { override fun onRecvNewMessage(msg: V2TIMMessage?) { super.onRecvNewMessage(msg) msg?.let { - MessageListViewModel.refreshConversation(context, it.sender) + //MessageListViewModel.refreshConversation(context, it.sender) if (MainActivityLifecycle.isForeground) { return } diff --git a/app/src/main/java/com/aiosman/ravenow/data/AccountService.kt b/app/src/main/java/com/aiosman/ravenow/data/AccountService.kt index 79ddaba..0ca29ac 100644 --- a/app/src/main/java/com/aiosman/ravenow/data/AccountService.kt +++ b/app/src/main/java/com/aiosman/ravenow/data/AccountService.kt @@ -66,13 +66,8 @@ data class AccountProfile( followerCount = followerCount, followingCount = followingCount, nickName = nickname, - avatar = if (aiAccount) { - // 对于AI账户,直接使用原始头像URL,不添加服务器前缀 - "${ApiClient.BASE_API_URL+"/outside"}$avatar"+"?token="+"Bearer ${AppStore.token}" - } else { - // 对于普通用户,添加服务器前缀 - "${ApiClient.BASE_SERVER}$avatar" - }, + avatar = + "${ApiClient.BASE_SERVER}$avatar", bio = bio, country = "Worldwide", isFollowing = isFollowing, diff --git a/app/src/main/java/com/aiosman/ravenow/data/AgentService.kt b/app/src/main/java/com/aiosman/ravenow/data/AgentService.kt index 1c70212..d600147 100644 --- a/app/src/main/java/com/aiosman/ravenow/data/AgentService.kt +++ b/app/src/main/java/com/aiosman/ravenow/data/AgentService.kt @@ -37,7 +37,7 @@ data class Agent( desc = desc, createdAt = createdAt, 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, isPublic = isPublic, openId = openId, diff --git a/app/src/main/java/com/aiosman/ravenow/data/UserService.kt b/app/src/main/java/com/aiosman/ravenow/data/UserService.kt index e878265..ad24ac0 100644 --- a/app/src/main/java/com/aiosman/ravenow/data/UserService.kt +++ b/app/src/main/java/com/aiosman/ravenow/data/UserService.kt @@ -55,7 +55,7 @@ interface UserService { * @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 { - val resp = ApiClient.api.getAccountProfileByTrtcUserId(id) + override suspend fun getUserProfileByTrtcUserId(id: String,includeAI: Int): AccountProfileEntity { + val resp = ApiClient.api.getAccountProfileByTrtcUserId(id,includeAI) val body = resp.body() ?: throw ServiceException("Failed to get account") return body.data.toAccountProfileEntity() } diff --git a/app/src/main/java/com/aiosman/ravenow/data/api/RiderProAPI.kt b/app/src/main/java/com/aiosman/ravenow/data/api/RiderProAPI.kt index 30d4a97..be98f05 100644 --- a/app/src/main/java/com/aiosman/ravenow/data/api/RiderProAPI.kt +++ b/app/src/main/java/com/aiosman/ravenow/data/api/RiderProAPI.kt @@ -51,6 +51,9 @@ data class SendChatAiRequestBody( val toTrtcUserId: String, @SerializedName("message") val message: String, + @SerializedName("skipTrtc") + val skipTrtc: Boolean, + ) data class LoginUserRequestBody( @@ -380,7 +383,8 @@ interface RaveNowAPI { @GET("profile/trtc/{id}") suspend fun getAccountProfileByTrtcUserId( - @Path("id") id: String + @Path("id") id: String, + @Query("includeAI") includeAI: Int ): Response> diff --git a/app/src/main/java/com/aiosman/ravenow/ui/Navi.kt b/app/src/main/java/com/aiosman/ravenow/ui/Navi.kt index 9512b90..8cd3df0 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/Navi.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/Navi.kt @@ -423,6 +423,7 @@ fun NavigationController( ) { AddAgentScreen() } + } } diff --git a/app/src/main/java/com/aiosman/ravenow/ui/chat/ChatAiScreen.kt b/app/src/main/java/com/aiosman/ravenow/ui/chat/ChatAiScreen.kt index d388991..4ea2590 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/chat/ChatAiScreen.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/chat/ChatAiScreen.kt @@ -186,7 +186,7 @@ fun ChatAiScreen(userId: String) { CustomAsyncImage( imageUrl = viewModel.userProfile?.avatar ?: "", modifier = Modifier - .size(40.dp) + .size(32.dp) .clip(RoundedCornerShape(40.dp)), contentDescription = "avatar" ) @@ -197,7 +197,7 @@ fun ChatAiScreen(userId: String) { style = TextStyle( color = AppColors.text, fontSize = 18.sp, - fontWeight = androidx.compose.ui.text.font.FontWeight.Bold + fontWeight = androidx.compose.ui.text.font.FontWeight.W700 ) ) Spacer(modifier = Modifier.weight(1f)) diff --git a/app/src/main/java/com/aiosman/ravenow/ui/chat/ChatAiViewModel.kt b/app/src/main/java/com/aiosman/ravenow/ui/chat/ChatAiViewModel.kt index 4bde778..6c234b2 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/chat/ChatAiViewModel.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/chat/ChatAiViewModel.kt @@ -260,7 +260,7 @@ class ChatAiViewModel( message: String, ) { 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)) } } diff --git a/app/src/main/java/com/aiosman/ravenow/ui/composables/Image.kt b/app/src/main/java/com/aiosman/ravenow/ui/composables/Image.kt index 66c85da..f2734d5 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/composables/Image.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/composables/Image.kt @@ -57,7 +57,8 @@ fun CustomAsyncImage( ) { val localContext = LocalContext.current - val imageLoader = getImageLoader(context ?: localContext) + // 使用remember来缓存ImageLoader,避免重复创建 + val imageLoader = remember { getImageLoader(context ?: localContext) } // 处理 imageUrl 为 null 或空字符串的情况 if (imageUrl == null || imageUrl == "") { @@ -89,6 +90,8 @@ fun CustomAsyncImage( model = ImageRequest.Builder(context ?: localContext) .data(imageUrl) .crossfade(200) + .memoryCachePolicy(coil.request.CachePolicy.ENABLED) + .diskCachePolicy(coil.request.CachePolicy.ENABLED) .apply { // 设置占位符图片 if (placeholderRes != null) { diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/Agent.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/Agent.kt index cb3d958..6c6e04e 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/Agent.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/Agent.kt @@ -51,7 +51,7 @@ fun Agent() { val navigationBarPaddings = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues() - var pagerState = rememberPagerState { 4 } + var pagerState = rememberPagerState { 2 } var scope = rememberCoroutineScope() Column( modifier = Modifier @@ -142,7 +142,7 @@ fun Agent() { } } ) - TabSpacer() + /*TabSpacer() TabItem( text = stringResource(R.string.agent_recommend), isSelected = pagerState.currentPage == 2, @@ -161,7 +161,7 @@ fun Agent() { pagerState.animateScrollToPage(3) } } - ) + )*/ } Spacer(modifier = Modifier.height(16.dp)) HorizontalPager( diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/hot/HotAgent.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/hot/HotAgent.kt index 2d58eee..a1e31a3 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/hot/HotAgent.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/hot/HotAgent.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.text.font.FontWeight 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.AgentCard @@ -37,6 +38,7 @@ fun HotAgent() { val AppColors = LocalAppTheme.current val model = HotAgentViewModel var agentList = model.agentList + val navController = LocalNavController.current val scope = rememberCoroutineScope() val state = rememberPullRefreshState(model.refreshing, onRefresh = { model.refreshPager(pullRefresh = true) @@ -108,7 +110,11 @@ fun HotAgent() { key = { idx -> idx } ) { idx -> val agentItem = agentList[idx] - AgentCard(agentEntity = agentItem) + AgentCard(agentEntity = agentItem, + onClick = { + model.createSingleChat(agentItem.openId) + model.goToChatAi(agentItem.openId,navController) + }) } // 加载更多指示器 diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/hot/HotAgentViewModel.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/hot/HotAgentViewModel.kt index 36c167b..b79083a 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/hot/HotAgentViewModel.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/hot/HotAgentViewModel.kt @@ -5,9 +5,13 @@ 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.data.api.ApiClient import com.aiosman.ravenow.data.ServiceException +import com.aiosman.ravenow.data.api.SingleChatRequestBody 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 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) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/mine/MineAgentViewModel.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/mine/MineAgentViewModel.kt index 00d86b2..a486ebf 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/mine/MineAgentViewModel.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/mine/MineAgentViewModel.kt @@ -8,22 +8,14 @@ import androidx.lifecycle.viewModelScope import androidx.navigation.NavHostController import com.aiosman.ravenow.data.api.ApiClient import com.aiosman.ravenow.data.ServiceException -import com.aiosman.ravenow.data.api.AgentMomentRequestBody import com.aiosman.ravenow.data.api.SingleChatRequestBody 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.navigateToChat 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.V2TIMConversationResult import com.tencent.imsdk.v2.V2TIMManager import com.tencent.imsdk.v2.V2TIMValueCallback import kotlinx.coroutines.launch -import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.RequestBody.Companion.toRequestBody object MineAgentViewModel : ViewModel() { var agentList by mutableStateOf>(emptyList()) @@ -105,9 +97,11 @@ object MineAgentViewModel : ViewModel() { fun createGroup2ChatAi( trtcUserId: String, - groupName: String + groupName: String, + navController: NavHostController, + id: Int ) { - val conversationIDList = listOf("c2c_${trtcUserId}") + val conversationIDList = listOf("c2c_${trtcUserId}") V2TIMManager.getConversationManager().createConversationGroup( groupName, @@ -115,6 +109,7 @@ object MineAgentViewModel : ViewModel() { object : V2TIMValueCallback> { override fun onSuccess(v2TIMConversationOperationResults: List) { // 创建会话分组成功 + navController.navigateToChatAi(id.toString()) } override fun onError(code: Int, desc: String) { @@ -122,32 +117,7 @@ object MineAgentViewModel : ViewModel() { } } ) - V2TIMManager.getConversationManager().getConversationGroupList(object : V2TIMValueCallback> { - override fun onSuccess(v2TIMConversationOperationResults: List) { - // 获取会话分组列表成功 - } - 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 { - override fun onSuccess(v2TIMConversationResult: V2TIMConversationResult) { - // 获取会话列表成功 - } - - override fun onError(code: Int, desc: String) { - // 获取会话列表失败 - } - } - ) } @@ -165,8 +135,7 @@ object MineAgentViewModel : ViewModel() { ) { viewModelScope.launch { val profile = userService.getUserProfileByOpenId(openId) - createGroup2ChatAi(profile.trtcUserId,"ai_group") - navController.navigateToChatAi(profile.id.toString()) + createGroup2ChatAi(profile.trtcUserId,"ai_group",navController,profile.id) } } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/MessageList.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/MessageList.kt index 2aaa6c0..f294898 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/MessageList.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/MessageList.kt @@ -9,11 +9,16 @@ 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.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxHeight 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.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight 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.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable 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.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.FriendChatListScreen import com.aiosman.ravenow.ui.like.LikeNoticeViewModel import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.google.accompanist.systemuicontroller.rememberSystemUiController @@ -81,12 +87,20 @@ fun NotificationsScreen() { MessageListViewModel.initData(context, force = true, loadChat = AppState.enableChat) } }) + val navigationBarPaddings = + WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp + val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues() LaunchedEffect(Unit) { systemUiController.setNavigationBarColor(Color.Transparent) MessageListViewModel.initData(context, loadChat = AppState.enableChat) } Column( - modifier = Modifier.fillMaxSize() + modifier = Modifier + .fillMaxSize() + .padding( + bottom = navigationBarPaddings, + + ), ) { StatusBarSpacer() Box( @@ -105,9 +119,10 @@ fun NotificationsScreen() { .padding(horizontal = 15.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { - - Box(modifier = Modifier.size(24.dp)) - + Box( + modifier = Modifier + .size(24.dp) + ) Column( modifier = Modifier .weight(1f) @@ -159,7 +174,7 @@ fun NotificationsScreen() { MessageListViewModel.followNoticeCount, R.mipmap.rider_pro_followers, stringResource(R.string.followers_upper), - Color(0xFFF470FE) + Color(0xFFF470FE) ) { if (MessageListViewModel.followNoticeCount > 0) { // 刷新关注消息列表 @@ -193,6 +208,7 @@ fun NotificationsScreen() { scope.launch { pagerState.animateScrollToPage(0) } + } ) TabSpacer() @@ -224,7 +240,7 @@ fun NotificationsScreen() { ) { when (it) { 0 -> { - + AgentChatListScreen() } 1 -> { @@ -232,15 +248,14 @@ fun NotificationsScreen() { } 2 -> { - + FriendChatListScreen() } - } } - Box( + /* Box( modifier = Modifier .weight(1f) .fillMaxWidth() @@ -271,7 +286,8 @@ fun NotificationsScreen() { MessageListViewModel.isLoading, state, Modifier.align(Alignment.TopCenter) - ) + )*/ + } } } diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/MessageListViewModel.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/MessageListViewModel.kt index a9bbcd8..690381b 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/MessageListViewModel.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/MessageListViewModel.kt @@ -167,9 +167,9 @@ object MessageListViewModel : ViewModel() { ) } - chatList = result?.conversationList?.map { msg: V2TIMConversation -> + /* chatList = result?.conversationList?.map { msg: V2TIMConversation -> Conversation.convertToConversation(msg, context) - } ?: emptyList() + } ?: emptyList()*/ } 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) - } - - } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/tab/AgentChatListScreen.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/tab/AgentChatListScreen.kt new file mode 100644 index 0000000..98d1759 --- /dev/null +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/tab/AgentChatListScreen.kt @@ -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 + ) + } + } + } + } + } +} diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/tab/AgentChatListViewModel.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/tab/AgentChatListViewModel.kt new file mode 100644 index 0000000..561c231 --- /dev/null +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/tab/AgentChatListViewModel.kt @@ -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>(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(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 { + 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()) + } + } +} diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/tab/FriendChatListScreen.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/tab/FriendChatListScreen.kt new file mode 100644 index 0000000..37478a8 --- /dev/null +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/tab/FriendChatListScreen.kt @@ -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 + ) + } + } + } + } + } +} diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/tab/FriendChatListViewModel.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/tab/FriendChatListViewModel.kt new file mode 100644 index 0000000..52c51c7 --- /dev/null +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/message/tab/FriendChatListViewModel.kt @@ -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>(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(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 { + 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) + } + } +} diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 66f5dd5..423b50c 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -62,17 +62,6 @@ 确认新密码 请确认新密码 - 修改密码 - 请输入当前密码 - 密码长度至少8位 - 密码必须包含至少一个数字 - 密码必须包含至少一个大写字母 - 密码必须包含至少一个小写字母 - 两次输入的密码不一致 - 请输入当前密码 - 请输入新密码 - 请再次输入新密码 - 确认修改 取消 个性签名 @@ -156,5 +145,22 @@ 群聊 朋友 + 智能体聊天 + 暂无智能体聊天 + 开始与智能体对话吧 + 我: + [图片] + [语音] + [视频] + [文件] + [消息] + 加载失败 + 加载更多失败 + 获取用户信息失败: %s + + 暂无朋友聊天 + 开始与朋友对话吧 + 我: + 加载失败 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 23ef407..7b83809 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -142,4 +142,21 @@ Ai Group Friends + 智能体聊天 + 暂无智能体聊天 + 开始与智能体对话吧 + 我: + [图片] + [语音] + [视频] + [文件] + [消息] + 加载失败 + 加载更多失败 + 获取用户信息失败: %s + 暂无朋友聊天 + 开始与朋友对话吧 + 我: + 加载失败 + \ No newline at end of file