This commit is contained in:
weber
2025-08-25 18:35:06 +08:00
parent 77033854f0
commit df75c710e5
20 changed files with 353 additions and 181 deletions

View File

@@ -61,7 +61,7 @@ object AgentViewModel: ViewModel() {
openId: String,
) {
viewModelScope.launch {
val response = ApiClient.api.createSingleChat(SingleChatRequestBody(generateText = openId))
val response = ApiClient.api.createSingleChat(SingleChatRequestBody(agentOpenId = openId))
}
}

View File

@@ -114,7 +114,7 @@ object HotAgentViewModel : ViewModel() {
openId: String,
) {
viewModelScope.launch {
val response = ApiClient.api.createSingleChat(SingleChatRequestBody(generateText = openId))
val response = ApiClient.api.createSingleChat(SingleChatRequestBody(agentOpenId = openId))
}
}

View File

@@ -28,6 +28,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R
@@ -39,13 +40,17 @@ fun MineAgent() {
val AppColors = LocalAppTheme.current
val navController = LocalNavController.current
val model = MineAgentViewModel
var agentList = model.agentList
val scope = rememberCoroutineScope()
val state = rememberPullRefreshState(model.refreshing, onRefresh = {
model.refreshPager(pullRefresh = true)
model.refreshPager(
//pullRefresh = true
)
})
val listState = rememberLazyListState()
var dataFlow = model.agentList
var agentList = dataFlow.collectAsLazyPagingItems()
// observe list scrolling
val reachedBottom by remember {
derivedStateOf {
@@ -56,33 +61,19 @@ fun MineAgent() {
// load more if scrolled to bottom
LaunchedEffect(reachedBottom) {
if (reachedBottom && !model.isLoading && model.hasNext) {
model.loadMore()
}
}
// 只在首次加载时刷新避免从AddAgent返回时重复刷新
LaunchedEffect(Unit) {
if (model.agentList.isEmpty() && !model.isLoading) {
if (reachedBottom) {
model.refreshPager()
}
}
val context = LocalContext.current
// 当智能体列表加载完成后,预加载图片
LaunchedEffect(agentList) {
if (agentList.isNotEmpty()) {
model.preloadImages(context)
}
LaunchedEffect(Unit) {
model.refreshPager()
}
Column(
modifier = Modifier
.fillMaxSize()
) {
if(agentList.isEmpty() && !model.isLoading) {
if(agentList.itemCount == 0 && !model.isLoading) {
Box(
modifier = Modifier
.fillMaxSize(),
@@ -119,23 +110,21 @@ fun MineAgent() {
modifier = Modifier.fillMaxSize(),
state = listState
) {
items(
agentList.size,
key = { idx -> agentList[idx].id } // 使用智能体ID作为key避免重新创建
) { idx ->
val agentItem = agentList[idx]
AgentCard(
agentEntity = agentItem,
onClick = {
model.createSingleChat(agentItem.openId)
model.goToChatAi(agentItem.openId,navController)
},
items(agentList.itemCount) { index ->
agentList[index]?.let { agent ->
AgentCard(
agentEntity = agent,
onClick = {
model.createSingleChat(agent.openId)
model.goToChatAi(agent.openId, navController)
},
)
)
}
}
// 加载更多指示器
if (model.isLoading && agentList.isNotEmpty()) {
if (model.isLoading && agentList.itemCount != 0) {
item {
Box(
modifier = Modifier

View File

@@ -6,19 +6,42 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavHostController
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.aiosman.ravenow.data.Agent
import com.aiosman.ravenow.data.AgentService
import com.aiosman.ravenow.data.MomentService
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.entity.AgentPagingSource
import com.aiosman.ravenow.entity.AgentRemoteDataSource
import com.aiosman.ravenow.entity.AgentServiceImpl
import com.aiosman.ravenow.entity.MomentEntity
import com.aiosman.ravenow.entity.MomentPagingSource
import com.aiosman.ravenow.entity.MomentRemoteDataSource
import com.aiosman.ravenow.entity.MomentServiceImpl
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userService
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.hot.HotMomentViewModel.firstLoad
import com.aiosman.ravenow.ui.navigateToChatAi
import com.tencent.imsdk.v2.V2TIMConversationOperationResult
import com.tencent.imsdk.v2.V2TIMManager
import com.tencent.imsdk.v2.V2TIMValueCallback
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
object MineAgentViewModel : ViewModel() {
var agentList by mutableStateOf<List<AgentEntity>>(emptyList())
private val agentService: AgentService = AgentServiceImpl()
private val _agentList =
MutableStateFlow<PagingData<AgentEntity>>(PagingData.empty())
val agentList = _agentList.asStateFlow()
var refreshing by mutableStateOf(false)
var isLoading by mutableStateOf(false)
var hasNext by mutableStateOf(true)
@@ -29,87 +52,25 @@ object MineAgentViewModel : ViewModel() {
private val preloadedImageIds = mutableSetOf<Int>()
private val pageSize = 20
init {
// 延迟初始化,避免在页面切换时立即加载
// refreshPager()
}
fun refreshPager(pullRefresh: Boolean = false) {
if (isLoading && !pullRefresh) return
viewModelScope.launch {
try {
isLoading = true
refreshing = pullRefresh
error = null
// 清除预加载记录,强制重新加载图片
if (pullRefresh) {
clearPreloadedImages()
}
val response = ApiClient.api.getMyAgent(
page = 1,
pageSize = pageSize
)
val body = response.body()
if (body != null) {
val newAgents = body.list.map { it.toAgentEntity() }
// 只有在列表为空或者是下拉刷新时才替换整个列表
if (agentList.isEmpty() || pullRefresh) {
agentList = newAgents
} else {
// 否则只添加新的智能体
val existingIds = agentList.map { it.id }.toSet()
val newAgentsToAdd = newAgents.filter { it.id !in existingIds }
if (newAgentsToAdd.isNotEmpty()) {
agentList = agentList + newAgentsToAdd
}
}
currentPage = 1
hasNext = newAgents.size == pageSize
} else {
throw ServiceException("Failed to load agents")
}
} catch (e: Exception) {
error = e.message ?: "加载失败"
e.printStackTrace()
} finally {
isLoading = false
refreshing = false
}
fun refreshPager() {
if (!firstLoad) {
return
}
}
fun loadMore() {
if (isLoading || !hasNext) return
firstLoad = false
viewModelScope.launch {
try {
isLoading = true
error = null
val response = ApiClient.api.getMyAgent(
page = currentPage + 1,
pageSize = pageSize
)
val body = response.body()
if (body != null) {
val newAgents = body.list.map { it.toAgentEntity() }
agentList = agentList + newAgents
currentPage += 1
hasNext = newAgents.size == pageSize
} else {
throw ServiceException("Failed to load more agents")
Pager(
config = PagingConfig(pageSize = 5, enablePlaceholders = false),
pagingSourceFactory = {
AgentPagingSource(
AgentRemoteDataSource(agentService),
//trend = true
)
}
} catch (e: Exception) {
error = e.message ?: "加载更多失败"
e.printStackTrace()
} finally {
isLoading = false
).flow.cachedIn(viewModelScope).collectLatest {
_agentList.value = it
}
}
}
@@ -144,7 +105,7 @@ object MineAgentViewModel : ViewModel() {
openId: String,
) {
viewModelScope.launch {
val response = ApiClient.api.createSingleChat(SingleChatRequestBody(generateText = openId))
val response = ApiClient.api.createSingleChat(SingleChatRequestBody(agentOpenId = openId))
}
}
@@ -158,36 +119,5 @@ object MineAgentViewModel : ViewModel() {
}
}
// 添加新创建的智能体到列表顶部
fun addAgentToList(agent: AgentEntity) {
agentList = listOf(agent) + agentList
}
// 预加载图片,避免滑动时重复加载
fun preloadImages(context: android.content.Context) {
viewModelScope.launch {
agentList.forEach { agent ->
if (agent.id !in preloadedImageIds && agent.avatar.isNotEmpty()) {
try {
// 预加载头像图片到缓存
com.aiosman.ravenow.utils.Utils.getImageLoader(context).enqueue(
coil.request.ImageRequest.Builder(context)
.data(agent.avatar)
.memoryCachePolicy(coil.request.CachePolicy.ENABLED)
.diskCachePolicy(coil.request.CachePolicy.ENABLED)
.build()
)
preloadedImageIds.add(agent.id)
} catch (e: Exception) {
// 忽略预加载错误
}
}
}
}
}
// 清除预加载记录(在刷新时调用)
fun clearPreloadedImages() {
preloadedImageIds.clear()
}
}

View File

@@ -175,7 +175,7 @@ fun FriendChatItem(
CustomAsyncImage(
context = LocalContext.current,
imageUrl = conversation.avatar,
contentDescription = conversation.nickname,
contentDescription = conversation.trtcUserId,
modifier = Modifier
.size(48.dp)
.clip(RoundedCornerShape(48.dp))

View File

@@ -1,6 +1,7 @@
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre
import android.annotation.SuppressLint
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
@@ -49,6 +50,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.draw.blur
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.res.stringResource
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import androidx.lifecycle.viewmodel.compose.viewModel
import com.aiosman.ravenow.AppStore
@@ -73,6 +75,8 @@ data class BannerItem(
val backgroundImageUrl: String,
val userCount: Int,
val agentName: String,
val trtcId: String,
val avatar: String
) {
companion object {
fun fromRoom(room: Room): BannerItem {
@@ -83,7 +87,18 @@ data class BannerItem(
imageUrl = "${ApiClient.RETROFIT_URL}${room.creator.profile.avatar}"+"?token="+"${AppStore.token}" ?: "",
backgroundImageUrl = "${ApiClient.BASE_API_URL+"/outside"}${room.recommendBanner}"+"?token="+"${AppStore.token}" ?: "",
userCount = room.userCount,
agentName = room.creator.profile.nickname
agentName = room.creator.profile.nickname,
trtcId = room.trtcRoomId,
avatar = if (room.avatar.isNullOrEmpty()) {
// 将 groupId 转换为 Base64
val groupIdBase64 = android.util.Base64.encodeToString(
room.trtcType.toByteArray(),
android.util.Base64.NO_WRAP
)
"${ApiClient.RETROFIT_URL+"group/avatar?groupIdBase64="}${groupIdBase64}"+"&token="+"${AppStore.token}"
} else {
"${ApiClient.BASE_API_URL+"/outside/"}${room.avatar}"+"?token="+"${AppStore.token}"
}
)
}
}
@@ -96,7 +111,7 @@ data class AgentItem(
val desc: String,
val avatar: String,
val useCount: Int,
//val trtcId: String
val openId: String
) {
companion object {
fun fromAgent(agent: Agent): AgentItem {
@@ -106,7 +121,7 @@ data class AgentItem(
desc = agent.desc,
avatar = "${ApiClient.BASE_API_URL+"/outside"}${agent.avatar}"+"?token="+"${AppStore.token}",
useCount = agent.useCount,
// trtcId = agent.
openId = agent.openId,
)
}
}
@@ -126,6 +141,9 @@ fun Explore() {
// 模拟刷新状态
var isRefreshing by remember { mutableStateOf(false) }
val enterSuccessText = stringResource(R.string.group_room_enter_success)
val enterFailText = stringResource(R.string.group_room_enter_fail) // 假设有这个资源
// 监听ViewModel的刷新状态
LaunchedEffect(viewModel.isRefreshing) {
isRefreshing = viewModel.isRefreshing
@@ -218,12 +236,13 @@ fun Explore() {
shape = RoundedCornerShape(8.dp)
)
.clickable {
// 聊天逻辑
viewModel.createSingleChat(agentItem.openId)
viewModel.goToChatAi(agentItem.openId, navController = navController)
},
contentAlignment = Alignment.Center
) {
Text(
text = "聊天",
text = stringResource(R.string.chat),
fontSize = 12.sp,
color = AppColors.text,
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
@@ -320,7 +339,7 @@ fun Explore() {
@Composable
fun HotChatRoomGridItem(roomItem: BannerItem) {
val AppColors = LocalAppTheme.current
val context = LocalContext.current
Row(
modifier = Modifier
.padding(horizontal = 2.dp, vertical = 4.dp)
@@ -332,7 +351,22 @@ fun Explore() {
4 -> Color(0x28af52de)
else -> Color(0x28ffcc00)
},
shape = RoundedCornerShape(12.dp)),
shape = RoundedCornerShape(12.dp))
.clickable {
// 调用加入房间接口
viewModel.joinRoom(
trtcId = roomItem.trtcId.toString(),
name = roomItem.title,
avatar = roomItem.avatar,
navController = navController,
onSuccess = {
Toast.makeText(context, enterSuccessText, Toast.LENGTH_SHORT).show()
},
onError = { errorMessage ->
Toast.makeText(context, enterFailText, Toast.LENGTH_SHORT).show()
}
)
},
verticalAlignment = Alignment.CenterVertically
) {
@@ -427,9 +461,10 @@ fun Explore() {
@Composable
fun BannerCard(bannerItem: BannerItem, modifier: Modifier = Modifier) {
fun BannerCard(bannerItem: BannerItem, viewModel: ExploreViewModel, modifier: Modifier = Modifier) {
val AppColors = LocalAppTheme.current
val context = LocalContext.current
val navController = LocalNavController.current
Card(
modifier = modifier
@@ -580,12 +615,24 @@ fun Explore() {
shape = RoundedCornerShape(8.dp)
)
.clickable {
// 入房间逻辑
// 调用加入房间接口
viewModel.joinRoom(
trtcId = bannerItem.trtcId.toString(),
name = bannerItem.title,
avatar = bannerItem.avatar,
navController = navController,
onSuccess = {
Toast.makeText(context, enterSuccessText, Toast.LENGTH_SHORT).show()
},
onError = { errorMessage ->
Toast.makeText(context, enterFailText, Toast.LENGTH_SHORT).show()
}
)
},
contentAlignment = Alignment.Center
) {
Text(
text = "进入",
text = stringResource(R.string.group_room_enter),
fontSize = 14.sp,
color = Color.White,
fontWeight = androidx.compose.ui.text.font.FontWeight.W600
@@ -806,6 +853,7 @@ fun Explore() {
BannerCard(
bannerItem = bannerItem,
viewModel = viewModel,
modifier = Modifier
.graphicsLayer {
scaleX = scale

View File

@@ -5,10 +5,18 @@ 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.Room
import com.aiosman.ravenow.data.Agent
import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.data.api.RaveNowAPI
import com.aiosman.ravenow.data.api.SingleChatRequestBody
import com.aiosman.ravenow.data.api.JoinGroupChatRequestBody
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.createGroup2ChatAi
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userService
import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListViewModel
import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListViewModel.createGroupChat
import com.aiosman.ravenow.ui.navigateToGroupChat
import kotlinx.coroutines.launch
class ExploreViewModel : ViewModel() {
@@ -122,5 +130,57 @@ class ExploreViewModel : ViewModel() {
}
}
}
fun createSingleChat(
openId: String,
) {
viewModelScope.launch {
val response = ApiClient.api.createSingleChat(SingleChatRequestBody(agentOpenId = openId))
}
}
fun goToChatAi(
openId: String,
navController: NavHostController
) {
viewModelScope.launch {
val profile = userService.getUserProfileByOpenId(openId)
createGroup2ChatAi(profile.trtcUserId,"ai_group",navController,profile.id)
}
}
fun joinRoom(
trtcId: String,
name: String,
avatar: String,
navController: NavHostController,
onSuccess: () -> Unit,
onError: (String) -> Unit
) {
viewModelScope.launch {
try {
val response = apiClient.joinRoom(JoinGroupChatRequestBody(trtcId = trtcId))
if (response.isSuccessful) {
viewModelScope.launch {
try {
createGroupChat(trtcGroupId = trtcId)
// 群聊直接使用群ID进行导航
navController.navigateToGroupChat( id = trtcId,
name = name,
avatar = avatar)
} catch (e: Exception) {
onError("加入房间失败")
e.printStackTrace()
}
}
onSuccess()
} else {
onError("加入房间失败")
}
} catch (e: Exception) {
onError("网络请求失败:${e.message}")
}
}
}
}

View File

@@ -58,7 +58,7 @@ fun HotMomentsList() {
val navigationBarPaddings =
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp
LaunchedEffect(Unit) {
HotMomentViewModel.refreshPager()
model.refreshPager()
}
var refreshing by remember { mutableStateOf(false) }
val state = rememberPullRefreshState(refreshing, onRefresh = {