From 778c06342a182f56062d36c42d5cb1187dd697fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E5=B8=86?= <3031465419@qq.com> Date: Wed, 19 Nov 2025 18:30:07 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=A4=9A=E4=B8=AA=E7=95=8C?= =?UTF-8?q?=E9=9D=A2=E5=92=8C=E6=9D=83=E9=99=90=E7=9B=B8=E5=85=B3=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复其他用户个人主页群聊列表显示问题 - 移除不必要的全部、公开、私有标签栏 - 修复搜索框切换类型闪退 - 修复摄像机权限被拒绝时的闪退问题 - 修复视频动态显示不全问题 - 移除标签栏触摸反馈 --- .../java/com/aiosman/ravenow/entity/Room.kt | 77 ++++++--- .../index/tabs/profile/MyProfileViewModel.kt | 155 ++++++++++++------ .../ui/index/tabs/profile/ProfileV3.kt | 21 ++- .../tabs/profile/composable/GalleryItem.kt | 96 ++++++----- .../composable/GroupChatEmptyContent.kt | 51 +++--- .../tabs/profile/composable/UserAgentsList.kt | 30 ++-- .../com/aiosman/ravenow/ui/post/NewPost.kt | 60 ++++++- 7 files changed, 328 insertions(+), 162 deletions(-) diff --git a/app/src/main/java/com/aiosman/ravenow/entity/Room.kt b/app/src/main/java/com/aiosman/ravenow/entity/Room.kt index 6f932b0..44967b3 100644 --- a/app/src/main/java/com/aiosman/ravenow/entity/Room.kt +++ b/app/src/main/java/com/aiosman/ravenow/entity/Room.kt @@ -1,5 +1,6 @@ package com.aiosman.ravenow.entity +import android.util.Log import androidx.paging.PagingSource import androidx.paging.PagingState import com.aiosman.ravenow.data.ListContainer @@ -272,19 +273,41 @@ class RoomRemoteDataSource { pageSize: Int = 20, search: String ): ListContainer? { - val resp = ApiClient.api.getRooms( - page = pageNumber, - pageSize = pageSize, - search = search, - roomType = "public" // 搜索时只显示公有房间 - ) - val body = resp.body() ?: return null - return ListContainer( - total = body.total, - page = pageNumber, - pageSize = pageSize, - list = body.list.map { it.toRoomtEntity() } - ) + return try { + val resp = ApiClient.api.getRooms( + page = pageNumber, + pageSize = pageSize, + search = search, + roomType = "public" // 搜索时只显示公有房间 + ) + if (!resp.isSuccessful) { + // API 调用失败,返回 null + return null + } + val body = resp.body() ?: return null + + // 安全地转换数据,过滤掉转换失败的项目 + val roomList = body.list.mapNotNull { room -> + try { + room.toRoomtEntity() + } catch (e: Exception) { + // 如果某个房间数据转换失败,记录错误但继续处理其他房间 + Log.e("RoomRemoteDataSource", "Failed to convert room: ${room.id}", e) + null + } + } + + ListContainer( + total = body.total, + page = pageNumber, + pageSize = pageSize, + list = roomList + ) + } catch (e: Exception) { + // 捕获所有异常,返回 null 让 PagingSource 处理 + Log.e("RoomRemoteDataSource", "searchRooms error", e) + null + } } } @@ -303,17 +326,31 @@ class RoomSearchPagingSource( pageSize = params.loadSize, search = keyword ) - LoadResult.Page( - data = rooms?.list ?: listOf(), - prevKey = if (currentPage == 1) null else currentPage - 1, - nextKey = if (rooms?.list?.isNotEmpty() == true) currentPage + 1 else null - ) - } catch (exception: IOException) { + if (rooms == null) { + // API 调用失败,返回空列表 + LoadResult.Page( + data = emptyList(), + prevKey = if (currentPage == 1) null else currentPage - 1, + nextKey = null + ) + } else { + LoadResult.Page( + data = rooms.list, + prevKey = if (currentPage == 1) null else currentPage - 1, + nextKey = if (rooms.list.isNotEmpty() && rooms.list.size >= params.loadSize) currentPage + 1 else null + ) + } + } catch (exception: Exception) { + // 捕获所有异常,包括 IOException、ServiceException 等 LoadResult.Error(exception) } } override fun getRefreshKey(state: PagingState): Int? { - return state.anchorPosition + // 更健壮的实现:根据 anchorPosition 计算刷新键 + return state.anchorPosition?.let { anchorPosition -> + state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1) + ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/MyProfileViewModel.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/MyProfileViewModel.kt index 22cfa69..1466d60 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/MyProfileViewModel.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/MyProfileViewModel.kt @@ -33,6 +33,8 @@ import com.aiosman.ravenow.event.MomentFavouriteChangeEvent import com.aiosman.ravenow.event.MomentLikeChangeEvent import com.aiosman.ravenow.event.MomentRemoveEvent import com.aiosman.ravenow.data.PointService +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe @@ -308,8 +310,9 @@ object MyProfileViewModel : ViewModel() { * 加载房间列表 * @param filterType 筛选类型:0=全部,1=公开,2=私有 * @param pullRefresh 是否下拉刷新 + * @param ownerSessionId 创建者用户ID(ChatAIID),用于过滤特定创建者的房间。如果为null,则显示当前用户创建或加入的房间 */ - fun loadRooms(filterType: Int = 0, pullRefresh: Boolean = false) { + fun loadRooms(filterType: Int = 0, pullRefresh: Boolean = false, ownerSessionId: String? = null) { // 游客模式下不加载房间列表 if (AppStore.isGuest) { Log.d("MyProfileViewModel", "loadRooms: 游客模式下跳过加载房间列表") @@ -331,60 +334,114 @@ object MyProfileViewModel : ViewModel() { roomsCurrentPage } - val response = when (filterType) { - 0 -> { - // 全部:显示自己创建或加入的所有房间 - apiClient.getRooms( - page = currentPage, - pageSize = roomsPageSize, - showCreated = true, - showJoined = true - ) - } - 1 -> { - // 公开:显示公开房间中自己创建或加入的 - apiClient.getRooms( - page = currentPage, - pageSize = roomsPageSize, - roomType = "public", - showCreated = true, - showJoined = true - ) - } - 2 -> { - // 私有:显示自己创建或加入的私有房间 - apiClient.getRooms( - page = currentPage, - pageSize = roomsPageSize, - roomType = "private" - ) - } - else -> { - apiClient.getRooms( - page = currentPage, - pageSize = roomsPageSize, - showCreated = true, - showJoined = true - ) - } + // 根据filterType确定roomType + val roomType = when (filterType) { + 1 -> "public" + 2 -> "private" + else -> null } - if (response.isSuccessful) { - val roomList = response.body()?.list ?: emptyList() - val total = response.body()?.total ?: 0L + // 构建API调用参数 + if (ownerSessionId != null) { + // 查看其他用户的房间:显示该用户创建和加入的房间 + // 1. 先快速获取该用户创建的房间(不需要 includeUsers,减少数据量) + val createdResponse = apiClient.getRooms( + page = currentPage, + pageSize = roomsPageSize, + roomType = roomType, + ownerSessionId = ownerSessionId, + includeUsers = false + ) - if (pullRefresh || currentPage == 1) { - rooms = roomList.map { it.toRoomtEntity() } + val createdRooms = if (createdResponse.isSuccessful) { + createdResponse.body()?.list?.map { it.toRoomtEntity() } ?: emptyList() } else { - rooms = rooms + roomList.map { it.toRoomtEntity() } + emptyList() } + // 先快速显示创建的房间,提升用户体验 + if (pullRefresh || currentPage == 1) { + rooms = createdRooms + } else { + rooms = rooms + createdRooms + } + + // 处理分页(基于创建的房间) + val total = createdResponse.body()?.total ?: 0L roomsHasMore = rooms.size < total if (roomsHasMore && !pullRefresh) { roomsCurrentPage++ } + + // 2. 后台异步获取该用户加入的房间(仅在非私有房间时获取,且只在第一页时获取以提升性能) + if (filterType != 2 && currentPage == 1) { + launch { + try { + // 获取公开房间,但限制数量以减少数据量 + val joinedResponse = apiClient.getRooms( + page = 1, + pageSize = roomsPageSize, + roomType = if (filterType == 1) "public" else null, + includeUsers = true // 需要成员信息来判断 + ) + + if (joinedResponse.isSuccessful) { + val joinedRooms = joinedResponse.body()?.list?.mapNotNull { room -> + try { + val entity = room.toRoomtEntity() + // 检查房间的创建者是否是该用户 + val isCreatedByUser = entity.creator.profile.chatAIId == ownerSessionId + // 检查房间的成员是否包含该用户 + val isMember = entity.users.any { it.profile.chatAIId == ownerSessionId } + + // 如果用户是成员但不是创建者,则认为是加入的房间 + if (isMember && !isCreatedByUser) { + entity + } else { + null + } + } catch (e: Exception) { + Log.e("MyProfileViewModel", "处理房间失败: ${e.message}") + null + } + } ?: emptyList() + + // 合并并去重(基于房间ID),然后更新列表 + val allRooms = (rooms + joinedRooms).distinctBy { it.id } + rooms = allRooms + } + } catch (e: Exception) { + Log.e("MyProfileViewModel", "获取加入的房间失败: ${e.message}") + } + } + } } else { - Log.e("MyProfileViewModel", "loadRooms failed: ${response.code()}") + // 查看自己的房间:显示创建或加入的房间 + val response = apiClient.getRooms( + page = currentPage, + pageSize = roomsPageSize, + roomType = roomType, + showCreated = true, + showJoined = if (filterType == 2) null else true // 私有房间不显示加入的 + ) + + if (response.isSuccessful) { + val roomList = response.body()?.list ?: emptyList() + val total = response.body()?.total ?: 0L + + if (pullRefresh || currentPage == 1) { + rooms = roomList.map { it.toRoomtEntity() } + } else { + rooms = rooms + roomList.map { it.toRoomtEntity() } + } + + roomsHasMore = rooms.size < total + if (roomsHasMore && !pullRefresh) { + roomsCurrentPage++ + } + } else { + Log.e("MyProfileViewModel", "loadRooms failed: ${response.code()}") + } } } catch (e: Exception) { Log.e("MyProfileViewModel", "loadRooms error: ", e) @@ -398,20 +455,22 @@ object MyProfileViewModel : ViewModel() { /** * 加载更多房间 * @param filterType 筛选类型:0=全部,1=公开,2=私有 + * @param ownerSessionId 创建者用户ID(ChatAIID),用于过滤特定创建者的房间 */ - fun loadMoreRooms(filterType: Int = 0) { + fun loadMoreRooms(filterType: Int = 0, ownerSessionId: String? = null) { if (roomsLoading || !roomsHasMore) return - loadRooms(filterType = filterType, pullRefresh = false) + loadRooms(filterType = filterType, pullRefresh = false, ownerSessionId = ownerSessionId) } /** * 刷新房间列表 * @param filterType 筛选类型:0=全部,1=公开,2=私有 + * @param ownerSessionId 创建者用户ID(ChatAIID),用于过滤特定创建者的房间 */ - fun refreshRooms(filterType: Int = 0) { + fun refreshRooms(filterType: Int = 0, ownerSessionId: String? = null) { rooms = emptyList() roomsCurrentPage = 1 roomsHasMore = true - loadRooms(filterType = filterType, pullRefresh = true) + loadRooms(filterType = filterType, pullRefresh = true, ownerSessionId = ownerSessionId) } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/ProfileV3.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/ProfileV3.kt index 46ab4ea..77623ba 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/ProfileV3.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/ProfileV3.kt @@ -533,22 +533,29 @@ fun ProfileV3( showNoMoreText = isSelf, modifier = Modifier.fillMaxSize(), state = listState, - nestedScrollConnection = nestedScrollConnection + nestedScrollConnection = nestedScrollConnection, + showSegments = isSelf // 只有查看自己的主页时才显示分段控制器 ) } else { + // 查看其他用户的主页时,传递该用户的chatAIId以显示其创建的群聊;查看自己的主页时传递null GroupChatPlaceholder( modifier = Modifier.fillMaxSize(), listState = groupChatListState, - nestedScrollConnection = nestedScrollConnection + nestedScrollConnection = nestedScrollConnection, + ownerSessionId = if (!isSelf) profile?.chatAIId else null, + showSegments = isSelf // 只有查看自己的主页时才显示分段控制器 ) } } 2 -> { if (!isAiAccount) { + // 查看其他用户的主页时,传递该用户的chatAIId以显示其创建的群聊;查看自己的主页时传递null GroupChatPlaceholder( modifier = Modifier.fillMaxSize(), listState = groupChatListState, - nestedScrollConnection = nestedScrollConnection + nestedScrollConnection = nestedScrollConnection, + ownerSessionId = if (!isSelf) profile?.chatAIId else null, + showSegments = isSelf // 只有查看自己的主页时才显示分段控制器 ) } } @@ -656,12 +663,16 @@ fun ProfileV3( private fun GroupChatPlaceholder( modifier: Modifier = Modifier, listState: androidx.compose.foundation.lazy.LazyListState, - nestedScrollConnection: NestedScrollConnection? = null + nestedScrollConnection: NestedScrollConnection? = null, + ownerSessionId: String? = null, // 创建者用户ID(ChatAIID),用于过滤特定创建者的房间。如果为null,则显示当前用户创建或加入的房间 + showSegments: Boolean = true // 是否显示分段控制器(全部、公开、私有) ) { GroupChatEmptyContent( modifier = modifier, listState = listState, - nestedScrollConnection = nestedScrollConnection + nestedScrollConnection = nestedScrollConnection, + ownerSessionId = ownerSessionId, + showSegments = showSegments ) } diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/GalleryItem.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/GalleryItem.kt index 9c4bb7a..27e8dd9 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/GalleryItem.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/GalleryItem.kt @@ -1,47 +1,47 @@ package com.aiosman.ravenow.ui.index.tabs.profile.composable +import androidx.compose.foundation.Image +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.unit.dp -import com.aiosman.ravenow.LocalNavController -import com.aiosman.ravenow.entity.MomentEntity -import com.aiosman.ravenow.ui.composables.CustomAsyncImage -import com.aiosman.ravenow.ui.modifiers.noRippleClickable -import com.aiosman.ravenow.ui.navigateToPost -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.ui.Alignment -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.sp -import com.aiosman.ravenow.LocalAppTheme -import com.aiosman.ravenow.R -import androidx.compose.material3.Text +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.aiosman.ravenow.LocalAppTheme +import com.aiosman.ravenow.LocalNavController +import com.aiosman.ravenow.R +import com.aiosman.ravenow.entity.MomentEntity +import com.aiosman.ravenow.ui.composables.CustomAsyncImage +import com.aiosman.ravenow.ui.modifiers.noRippleClickable +import com.aiosman.ravenow.ui.navigateToPost import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import com.aiosman.ravenow.AppState @@ -227,8 +227,20 @@ fun GalleryGrid( .padding(bottom = 8.dp), ) { itemsIndexed(moments) { idx, moment -> - if (moment != null && moment.images.isNotEmpty()) { + moment?.let { momentItem -> val itemDebouncer = rememberDebouncer() + val isVideoMoment = momentItem.images.isEmpty() && !momentItem.videos.isNullOrEmpty() + val previewUrl = when { + momentItem.images.isNotEmpty() -> momentItem.images[0].thumbnail + isVideoMoment -> { + val firstVideo = momentItem.videos!!.first() + firstVideo.thumbnailDirectUrl + ?: firstVideo.thumbnailUrl + ?: firstVideo.directUrl + ?: firstVideo.url + } + else -> null + } Box( modifier = Modifier .fillMaxWidth() @@ -237,31 +249,29 @@ fun GalleryGrid( .noRippleClickable { itemDebouncer { navController.navigateToPost( - id = moment.id, + id = momentItem.id, highlightCommentId = 0, initImagePagerIndex = 0 ) } } ) { - CustomAsyncImage( - imageUrl = moment.images[0].thumbnail, - contentDescription = "", - modifier = Modifier.fillMaxSize(), - context = LocalContext.current - ) - if (moment.images.size > 1) { + if (previewUrl != null) { + CustomAsyncImage( + imageUrl = previewUrl, + contentDescription = "", + modifier = Modifier.fillMaxSize(), + context = LocalContext.current + ) + } else { Box( modifier = Modifier - .padding(top = 8.dp, end = 8.dp) - .align(Alignment.TopEnd) - ) { - Image( - modifier = Modifier.size(24.dp), - painter = painterResource(R.drawable.rider_pro_picture_more), - contentDescription = "", - ) - } + .fillMaxSize() + .background( + color = AppColors.basicMain.copy(alpha = 0.2f), + shape = RoundedCornerShape(10.dp) + ) + ) } } } diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/GroupChatEmptyContent.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/GroupChatEmptyContent.kt index 2098d8b..a04ed77 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/GroupChatEmptyContent.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/GroupChatEmptyContent.kt @@ -69,7 +69,9 @@ import android.util.Base64 fun GroupChatEmptyContent( modifier: Modifier = Modifier, listState: LazyListState, - nestedScrollConnection: NestedScrollConnection? = null + nestedScrollConnection: NestedScrollConnection? = null, + ownerSessionId: String? = null, // 创建者用户ID(ChatAIID),用于过滤特定创建者的房间。如果为null,则显示当前用户创建或加入的房间 + showSegments: Boolean = true // 是否显示分段控制器(全部、公开、私有) ) { var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有 val AppColors = LocalAppTheme.current @@ -77,24 +79,19 @@ fun GroupChatEmptyContent( val navController = LocalNavController.current val viewModel = MyProfileViewModel + // 如果查看其他用户的房间,固定使用全部类型(filterType = 0) + val filterType = if (showSegments) selectedSegment else 0 + val state = rememberPullRefreshState( refreshing = viewModel.roomsRefreshing, onRefresh = { - viewModel.refreshRooms(filterType = selectedSegment) + viewModel.refreshRooms(filterType = filterType, ownerSessionId = ownerSessionId) } ) - // 当分段改变时,重新加载数据 - LaunchedEffect(selectedSegment) { - // 切换分段时重新加载 - viewModel.refreshRooms(filterType = selectedSegment) - } - - // 初始加载 - LaunchedEffect(Unit) { - if (viewModel.rooms.isEmpty() && !viewModel.roomsLoading) { - viewModel.loadRooms(filterType = selectedSegment) - } + // 当分段或用户ID改变时,重新加载数据 + LaunchedEffect(selectedSegment, ownerSessionId, showSegments) { + viewModel.refreshRooms(filterType = filterType, ownerSessionId = ownerSessionId) } val nestedScrollModifier = if (nestedScrollConnection != null) { @@ -110,17 +107,19 @@ fun GroupChatEmptyContent( ) { Spacer(modifier = Modifier.height(16.dp)) - // 分段控制器 - SegmentedControl( - selectedIndex = selectedSegment, - onSegmentSelected = { - selectedSegment = it - // LaunchedEffect 会监听 selectedSegment 的变化并自动刷新 - }, - modifier = Modifier.fillMaxWidth() - ) - - Spacer(modifier = Modifier.height(8.dp)) + // 只在查看自己的房间时显示分段控制器 + if (showSegments) { + SegmentedControl( + selectedIndex = selectedSegment, + onSegmentSelected = { + selectedSegment = it + // LaunchedEffect 会监听 selectedSegment 的变化并自动刷新 + }, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(8.dp)) + } Box( modifier = nestedScrollModifier @@ -201,7 +200,7 @@ fun GroupChatEmptyContent( if (viewModel.roomsHasMore && !viewModel.roomsLoading) { item { LaunchedEffect(Unit) { - viewModel.loadMoreRooms(filterType = selectedSegment) + viewModel.loadMoreRooms(filterType = filterType, ownerSessionId = ownerSessionId) } } } @@ -372,7 +371,7 @@ private fun SegmentButton( }, shape = RoundedCornerShape(1000.dp) ) - .clickable(onClick = onClick), + .noRippleClickable { onClick() }, contentAlignment = Alignment.Center ) { Text( diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/UserAgentsList.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/UserAgentsList.kt index 8811eb8..9e8fb97 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/UserAgentsList.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/profile/composable/UserAgentsList.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import com.aiosman.ravenow.ui.modifiers.noRippleClickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -66,7 +67,8 @@ fun UserAgentsList( showNoMoreText: Boolean = false, modifier: Modifier = Modifier, state: LazyListState, - nestedScrollConnection: NestedScrollConnection? = null + nestedScrollConnection: NestedScrollConnection? = null, + showSegments: Boolean = true // 是否显示分段控制器(全部、公开、私有) ) { val AppColors = LocalAppTheme.current val listModifier = if (nestedScrollConnection != null) { @@ -80,7 +82,7 @@ fun UserAgentsList( Box( modifier = listModifier.fillMaxSize() ) { - AgentEmptyContentWithSegments() + AgentEmptyContentWithSegments(showSegments = showSegments) } } else { LazyColumn( @@ -248,7 +250,9 @@ fun UserAgentCard( } @Composable -fun AgentEmptyContentWithSegments() { +fun AgentEmptyContentWithSegments( + showSegments: Boolean = true // 是否显示分段控制器(全部、公开、私有) +) { var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有 val AppColors = LocalAppTheme.current val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current) @@ -260,14 +264,16 @@ fun AgentEmptyContentWithSegments() { ) { Spacer(modifier = Modifier.height(16.dp)) - // 分段控制器 - AgentSegmentedControl( - selectedIndex = selectedSegment, - onSegmentSelected = { selectedSegment = it }, - modifier = Modifier.fillMaxWidth() - ) - - Spacer(modifier = Modifier.height(8.dp)) + // 只在查看自己的智能体时显示分段控制器 + if (showSegments) { + AgentSegmentedControl( + selectedIndex = selectedSegment, + onSegmentSelected = { selectedSegment = it }, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(8.dp)) + } // 空状态内容(与动态、群聊保持一致) Column( @@ -397,7 +403,7 @@ private fun AgentSegmentButton( }, shape = RoundedCornerShape(1000.dp) ) - .clickable(onClick = onClick), + .noRippleClickable { onClick() }, contentAlignment = Alignment.Center ) { Text( diff --git a/app/src/main/java/com/aiosman/ravenow/ui/post/NewPost.kt b/app/src/main/java/com/aiosman/ravenow/ui/post/NewPost.kt index d380a99..1871d8b 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/post/NewPost.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/post/NewPost.kt @@ -1,9 +1,12 @@ package com.aiosman.ravenow.ui.post +import android.Manifest +import android.content.pm.PackageManager import android.net.Uri import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.ContextCompat import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateFloatAsState @@ -588,6 +591,34 @@ fun AddImageGrid() { } } + // 摄像头权限请求 + val requestCameraPermissionLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.RequestPermission() + ) { isGranted -> + if (isGranted) { + // 权限已授予,打开相机 + if (model.imageList.size < 9) { + val photoFile = File(context.cacheDir, "photo.jpg") + val photoUri: Uri = FileProvider.getUriForFile( + context, + "${context.packageName}.fileprovider", + photoFile + ) + model.currentPhotoUri = photoUri + takePictureLauncher.launch(photoUri) + } else { + Toast.makeText(context, "最多只能选择9张图片", Toast.LENGTH_SHORT).show() + } + } else { + // 权限被拒绝,提示用户 + Toast.makeText( + context, + "需要摄像头权限才能拍摄照片,请在设置中开启", + Toast.LENGTH_LONG + ).show() + } + } + val addImageDebouncer = rememberDebouncer() val canAddMoreImages = model.imageList.size < 9 @@ -642,14 +673,27 @@ fun AddImageGrid() { .background(Color(0xFFFAF9FB)) .noRippleClickable { if (model.imageList.size < 9) { - val photoFile = File(context.cacheDir, "photo.jpg") - val photoUri: Uri = FileProvider.getUriForFile( - context, - "${context.packageName}.fileprovider", - photoFile - ) - model.currentPhotoUri = photoUri - takePictureLauncher.launch(photoUri) + // 检查摄像头权限 + when { + ContextCompat.checkSelfPermission( + context, + Manifest.permission.CAMERA + ) == PackageManager.PERMISSION_GRANTED -> { + // 已有权限,直接打开相机 + val photoFile = File(context.cacheDir, "photo.jpg") + val photoUri: Uri = FileProvider.getUriForFile( + context, + "${context.packageName}.fileprovider", + photoFile + ) + model.currentPhotoUri = photoUri + takePictureLauncher.launch(photoUri) + } + else -> { + // 没有权限,请求权限 + requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA) + } + } } else { Toast.makeText(context, "最多只能选择9张图片", Toast.LENGTH_SHORT).show() }