会话分组及聊天室实现
This commit is contained in:
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@@ -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">
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -423,6 +423,7 @@ fun NavigationController(
|
|||||||
) {
|
) {
|
||||||
AddAgentScreen()
|
AddAgentScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载更多指示器
|
// 加载更多指示器
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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,9 +97,11 @@ 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}")
|
||||||
|
|
||||||
V2TIMManager.getConversationManager().createConversationGroup(
|
V2TIMManager.getConversationManager().createConversationGroup(
|
||||||
groupName,
|
groupName,
|
||||||
@@ -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())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
@@ -159,7 +174,7 @@ fun NotificationsScreen() {
|
|||||||
MessageListViewModel.followNoticeCount,
|
MessageListViewModel.followNoticeCount,
|
||||||
R.mipmap.rider_pro_followers,
|
R.mipmap.rider_pro_followers,
|
||||||
stringResource(R.string.followers_upper),
|
stringResource(R.string.followers_upper),
|
||||||
Color(0xFFF470FE)
|
Color(0xFFF470FE)
|
||||||
) {
|
) {
|
||||||
if (MessageListViewModel.followNoticeCount > 0) {
|
if (MessageListViewModel.followNoticeCount > 0) {
|
||||||
// 刷新关注消息列表
|
// 刷新关注消息列表
|
||||||
@@ -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)
|
||||||
)
|
)*/
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
Reference in New Issue
Block a user