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 3bad499..61b4ecc 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 @@ -41,7 +41,7 @@ data class AgentMomentRequestBody( data class SingleChatRequestBody( @SerializedName("agentOpenId") - val generateText: String? = null, + val agentOpenId: String? = null, @SerializedName("agentTrtcId") val agentTrtcId: String? = null, ) diff --git a/app/src/main/java/com/aiosman/ravenow/entity/Agent.kt b/app/src/main/java/com/aiosman/ravenow/entity/Agent.kt index 3c1e49a..9a6bb95 100644 --- a/app/src/main/java/com/aiosman/ravenow/entity/Agent.kt +++ b/app/src/main/java/com/aiosman/ravenow/entity/Agent.kt @@ -4,8 +4,10 @@ import androidx.paging.PagingSource import androidx.paging.PagingState import com.aiosman.ravenow.data.ListContainer import com.aiosman.ravenow.data.AgentService +import com.aiosman.ravenow.data.MomentService import com.aiosman.ravenow.data.ServiceException import com.aiosman.ravenow.data.UploadImage +import com.aiosman.ravenow.data.UserService import com.aiosman.ravenow.data.api.ApiClient import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaTypeOrNull @@ -47,6 +49,79 @@ suspend fun createAgent( } +/** + * 智能体信息分页加载器 + */ +class AgentPagingSource( + private val agentRemoteDataSource: AgentRemoteDataSource, +) : PagingSource() { + override suspend fun load(params: LoadParams): LoadResult { + return try { + val currentPage = params.key ?: 1 + val users = agentRemoteDataSource.getAgent( + pageNumber = currentPage + ) + LoadResult.Page( + data = users.list, + prevKey = if (currentPage == 1) null else currentPage - 1, + nextKey = if (users.list.isEmpty()) null else users.page + 1 + ) + } catch (exception: IOException) { + return LoadResult.Error(exception) + } + } + + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition + } + +} + + +class AgentRemoteDataSource( + private val agentService: AgentService, +) { + suspend fun getAgent( + pageNumber: Int, + ): ListContainer { + return agentService.getAgent( + pageNumber = pageNumber + ) + } +} + +class AgentServiceImpl() : AgentService { + val agentBackend = AgentBackend() + + override suspend fun getAgent(pageNumber: Int, pageSize: Int): ListContainer { + return agentBackend.getAgent( + pageNumber = pageNumber, + + ) + } +} + + class AgentBackend { + val DataBatchSize = 20 + suspend fun getAgent( + pageNumber: Int, + + ): ListContainer { + val resp = ApiClient.api.getMyAgent( + pageSize = DataBatchSize, + page = pageNumber, + + ) + val body = resp.body() ?: throw ServiceException("Failed to get moments") + return ListContainer( + total = body.total, + page = pageNumber, + pageSize = DataBatchSize, + list = body.list.map { it.toAgentEntity() } + ) + } + } + data class AgentEntity( //val author: String, val avatar: String, 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 e7d35ca..bab04a1 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/Navi.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/Navi.kt @@ -534,7 +534,7 @@ fun NavHostController.navigateToGroupInfo(id: String) { val encodedId = java.net.URLEncoder.encode(id, "UTF-8") navigate( - route = NavigationRoute.ChatGroup.route + route = NavigationRoute.GroupInfo.route .replace("{id}", encodedId) ) diff --git a/app/src/main/java/com/aiosman/ravenow/ui/agent/AddAgentViewModel.kt b/app/src/main/java/com/aiosman/ravenow/ui/agent/AddAgentViewModel.kt index 8972500..1efc27f 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/agent/AddAgentViewModel.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/agent/AddAgentViewModel.kt @@ -58,9 +58,7 @@ object AddAgentViewModel : ViewModel() { ) println("AddAgentViewModel: Agent created successfully with ID: ${result.id}") - - // 通知相关ViewModel更新列表 - notifyAgentCreated(result) + return result } catch (e: Exception) { @@ -71,10 +69,7 @@ object AddAgentViewModel : ViewModel() { } } - private fun notifyAgentCreated(agent: AgentEntity) { - // 通知我的智能体列表更新 - com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.addAgentToList(agent) - } + fun validate(): String? { return when { 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 c199eb2..bb7d91e 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 @@ -199,9 +199,11 @@ fun ChatAiScreen(userId: String) { color = AppColors.text, fontSize = 18.sp, fontWeight = androidx.compose.ui.text.font.FontWeight.W700 - ) + ), + maxLines = 1, + overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis, ) - Spacer(modifier = Modifier.weight(1f)) + Spacer(modifier = Modifier.width(8.dp)) Box { Image( painter = painterResource(R.drawable.rider_pro_more_horizon), diff --git a/app/src/main/java/com/aiosman/ravenow/ui/chat/ChatScreen.kt b/app/src/main/java/com/aiosman/ravenow/ui/chat/ChatScreen.kt index 9914ba6..cf691b8 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/chat/ChatScreen.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/chat/ChatScreen.kt @@ -199,6 +199,7 @@ fun ChatScreen(userId: String) { modifier = Modifier.weight(1f) , maxLines = 1, + overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis, style = TextStyle( color = AppColors.text, fontSize = 18.sp, diff --git a/app/src/main/java/com/aiosman/ravenow/ui/chat/GroupChatScreen.kt b/app/src/main/java/com/aiosman/ravenow/ui/chat/GroupChatScreen.kt index eb25d67..7fd6c89 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/chat/GroupChatScreen.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/chat/GroupChatScreen.kt @@ -206,7 +206,7 @@ fun GroupChatScreen(groupId: String,name: String,avatar: String,) { fontWeight = androidx.compose.ui.text.font.FontWeight.W700 ), maxLines = 1, - overflow = TextOverflow.Ellipsis + overflow =TextOverflow.Ellipsis, ) } @@ -220,11 +220,13 @@ fun GroupChatScreen(groupId: String,name: String,avatar: String,) { color = AppColors.text, fontSize = 18.sp, fontWeight = androidx.compose.ui.text.font.FontWeight.Bold - ) + ), + maxLines = 1, + overflow =TextOverflow.Ellipsis, ) } - Spacer(modifier = Modifier.weight(1f)) + Spacer(modifier = Modifier.width(8.dp)) Box { Image( painter = painterResource(R.drawable.rider_pro_more_horizon), diff --git a/app/src/main/java/com/aiosman/ravenow/ui/composables/Agent.kt b/app/src/main/java/com/aiosman/ravenow/ui/composables/Agent.kt index 1400f1a..733a998 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/composables/Agent.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/composables/Agent.kt @@ -59,16 +59,15 @@ fun AgentCard( Row( modifier = Modifier ) { - // 使用remember基于agentEntity.id来缓存图片,避免滑动时重复加载 Box( modifier = Modifier .size(40.dp) .clip(RoundedCornerShape(40.dp)) ) { CustomAsyncImage( - LocalContext.current, + context, agentEntity.avatar, - contentDescription = "", + contentDescription = agentEntity.openId, modifier = Modifier.size(40.dp), contentScale = ContentScale.Crop ) 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 f2734d5..422a86a 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 @@ -23,6 +23,28 @@ import com.aiosman.ravenow.utils.Utils.getImageLoader import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +/** + * CustomAsyncImage 组件使用说明: + * + * @param context 上下文,可选 + * @param imageUrl 图片URL或Bitmap对象 + * @param contentDescription 图片描述 + * @param modifier 修饰符 + * @param blurHash 模糊哈希值(暂未使用) + * @param placeholderRes 加载时显示的占位符图片资源ID + * @param errorRes 加载失败时显示的错误图片资源ID + * @param defaultRes 当imageUrl为空或null时显示的默认图片资源ID(优先级最高) + * @param contentScale 图片缩放模式 + * + * 使用示例: + * CustomAsyncImage( + * imageUrl = "https://example.com/image.jpg", + * contentDescription = "用户头像", + * defaultRes = R.mipmap.default_avatar, + * placeholderRes = R.mipmap.loading_placeholder, + * errorRes = R.mipmap.error_image + * ) + */ @Composable fun rememberImageBitmap(imageUrl: String, imageLoader: ImageLoader): Bitmap? { val context = LocalContext.current @@ -53,6 +75,10 @@ fun CustomAsyncImage( blurHash: String? = null, @DrawableRes placeholderRes: Int? = null, + @DrawableRes + errorRes: Int? = null, + @DrawableRes + defaultRes: Int? = null, contentScale: ContentScale = ContentScale.Crop ) { val localContext = LocalContext.current @@ -62,10 +88,11 @@ fun CustomAsyncImage( // 处理 imageUrl 为 null 或空字符串的情况 if (imageUrl == null || imageUrl == "") { - // 如果 imageUrl 为 null 且有占位符,则直接显示占位符 - if (placeholderRes != null) { + // 优先使用 defaultRes,然后是 placeholderRes + val fallbackRes = defaultRes ?: placeholderRes + if (fallbackRes != null) { Image( - painter = androidx.compose.ui.res.painterResource(placeholderRes), + painter = androidx.compose.ui.res.painterResource(fallbackRes), contentDescription = contentDescription, modifier = modifier, contentScale = contentScale @@ -97,6 +124,10 @@ fun CustomAsyncImage( if (placeholderRes != null) { placeholder(placeholderRes) } + // 设置错误时显示的图片 + if (errorRes != null) { + error(errorRes) + } } .build(), contentDescription = contentDescription, @@ -104,4 +135,38 @@ fun CustomAsyncImage( contentScale = contentScale, imageLoader = imageLoader ) -} \ No newline at end of file +} + +/* +使用示例: + +1. 基本使用(带默认图片): +CustomAsyncImage( + imageUrl = user.avatar, + contentDescription = "用户头像", + defaultRes = R.mipmap.default_avatar +) + +2. 完整配置: +CustomAsyncImage( + imageUrl = "https://example.com/image.jpg", + contentDescription = "产品图片", + defaultRes = R.mipmap.default_product, + placeholderRes = R.mipmap.loading_placeholder, + errorRes = R.mipmap.error_image, + contentScale = ContentScale.Crop +) + +3. 处理空URL: +CustomAsyncImage( + imageUrl = "", // 空字符串会显示默认图片 + contentDescription = "头像", + defaultRes = R.mipmap.default_avatar +) + +4. 处理Bitmap: +CustomAsyncImage( + imageUrl = bitmapObject, // Bitmap对象会直接显示 + contentDescription = "裁剪后的图片" +) +*/ \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/ravenow/ui/group/GroupChatInfoScreen.kt b/app/src/main/java/com/aiosman/ravenow/ui/group/GroupChatInfoScreen.kt index f91fe64..f95093a 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/group/GroupChatInfoScreen.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/group/GroupChatInfoScreen.kt @@ -187,11 +187,11 @@ fun GroupChatInfoScreen(groupId: String) { horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.noRippleClickable { viewModel.viewModelScope.launch { - if (viewModel.notificationStrategy == "mute") { + /*if (viewModel.notificationStrategy == "mute") { viewModel.updateNotificationStrategy("active") } else { viewModel.updateNotificationStrategy("mute") - } + }*/ } } ) { diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/AgentViewModel.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/AgentViewModel.kt index 8ea2e8d..a8730fd 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/AgentViewModel.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/AgentViewModel.kt @@ -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)) } } 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 2d012ff..5fe59cd 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 @@ -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)) } } diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/mine/MineAgent.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/mine/MineAgent.kt index 7ffc4c5..974a860 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/mine/MineAgent.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/ai/tabs/mine/MineAgent.kt @@ -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 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 2b89dbc..3d1fd7e 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 @@ -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>(emptyList()) + private val agentService: AgentService = AgentServiceImpl() + + private val _agentList = + MutableStateFlow>(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() 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() - } + } \ No newline at end of file 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 index b05c113..974e440 100644 --- 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 @@ -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)) diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/expolre/Explore.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/expolre/Explore.kt index 7eaedc6..3d46e36 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/expolre/Explore.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/expolre/Explore.kt @@ -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 diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/expolre/ExploreViewModel.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/expolre/ExploreViewModel.kt index 3f28fc7..fa0fdbd 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/expolre/ExploreViewModel.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/expolre/ExploreViewModel.kt @@ -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}") + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/hot/Moment.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/hot/Moment.kt index ad15e3d..01118fb 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/hot/Moment.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/hot/Moment.kt @@ -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 = { diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 6c05312..c403d80 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -182,5 +182,8 @@ 创建智能体 发布动态 热门智能体 + 进入 + 成功加入房间 + 加入房间失败 \ 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 acf6655..33e648c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -177,5 +177,8 @@ 创建智能体 发布动态 热门智能体 + 进入 + 成功加入房间 + 加入房间失败 \ No newline at end of file