Merge pull request #85 from Kevinlinpr/nagisa

修复多个界面和权限相关问题
This commit is contained in:
2025-11-19 23:50:17 +08:00
committed by GitHub
7 changed files with 328 additions and 162 deletions

View File

@@ -1,5 +1,6 @@
package com.aiosman.ravenow.entity package com.aiosman.ravenow.entity
import android.util.Log
import androidx.paging.PagingSource import androidx.paging.PagingSource
import androidx.paging.PagingState import androidx.paging.PagingState
import com.aiosman.ravenow.data.ListContainer import com.aiosman.ravenow.data.ListContainer
@@ -272,19 +273,41 @@ class RoomRemoteDataSource {
pageSize: Int = 20, pageSize: Int = 20,
search: String search: String
): ListContainer<RoomEntity>? { ): ListContainer<RoomEntity>? {
return try {
val resp = ApiClient.api.getRooms( val resp = ApiClient.api.getRooms(
page = pageNumber, page = pageNumber,
pageSize = pageSize, pageSize = pageSize,
search = search, search = search,
roomType = "public" // 搜索时只显示公有房间 roomType = "public" // 搜索时只显示公有房间
) )
if (!resp.isSuccessful) {
// API 调用失败,返回 null
return null
}
val body = resp.body() ?: return null val body = resp.body() ?: return null
return ListContainer(
// 安全地转换数据,过滤掉转换失败的项目
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, total = body.total,
page = pageNumber, page = pageNumber,
pageSize = pageSize, pageSize = pageSize,
list = body.list.map { it.toRoomtEntity() } list = roomList
) )
} catch (e: Exception) {
// 捕获所有异常,返回 null 让 PagingSource 处理
Log.e("RoomRemoteDataSource", "searchRooms error", e)
null
}
} }
} }
@@ -303,17 +326,31 @@ class RoomSearchPagingSource(
pageSize = params.loadSize, pageSize = params.loadSize,
search = keyword search = keyword
) )
if (rooms == null) {
// API 调用失败,返回空列表
LoadResult.Page( LoadResult.Page(
data = rooms?.list ?: listOf(), data = emptyList(),
prevKey = if (currentPage == 1) null else currentPage - 1, prevKey = if (currentPage == 1) null else currentPage - 1,
nextKey = if (rooms?.list?.isNotEmpty() == true) currentPage + 1 else null nextKey = null
) )
} catch (exception: IOException) { } 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) LoadResult.Error(exception)
} }
} }
override fun getRefreshKey(state: PagingState<Int, RoomEntity>): Int? { override fun getRefreshKey(state: PagingState<Int, RoomEntity>): Int? {
return state.anchorPosition // 更健壮的实现:根据 anchorPosition 计算刷新键
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
} }
} }

View File

@@ -33,6 +33,8 @@ import com.aiosman.ravenow.event.MomentFavouriteChangeEvent
import com.aiosman.ravenow.event.MomentLikeChangeEvent import com.aiosman.ravenow.event.MomentLikeChangeEvent
import com.aiosman.ravenow.event.MomentRemoveEvent import com.aiosman.ravenow.event.MomentRemoveEvent
import com.aiosman.ravenow.data.PointService import com.aiosman.ravenow.data.PointService
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
@@ -308,8 +310,9 @@ object MyProfileViewModel : ViewModel() {
* 加载房间列表 * 加载房间列表
* @param filterType 筛选类型0=全部1=公开2=私有 * @param filterType 筛选类型0=全部1=公开2=私有
* @param pullRefresh 是否下拉刷新 * @param pullRefresh 是否下拉刷新
* @param ownerSessionId 创建者用户IDChatAIID用于过滤特定创建者的房间。如果为null则显示当前用户创建或加入的房间
*/ */
fun loadRooms(filterType: Int = 0, pullRefresh: Boolean = false) { fun loadRooms(filterType: Int = 0, pullRefresh: Boolean = false, ownerSessionId: String? = null) {
// 游客模式下不加载房间列表 // 游客模式下不加载房间列表
if (AppStore.isGuest) { if (AppStore.isGuest) {
Log.d("MyProfileViewModel", "loadRooms: 游客模式下跳过加载房间列表") Log.d("MyProfileViewModel", "loadRooms: 游客模式下跳过加载房间列表")
@@ -331,43 +334,96 @@ object MyProfileViewModel : ViewModel() {
roomsCurrentPage roomsCurrentPage
} }
val response = when (filterType) { // 根据filterType确定roomType
0 -> { val roomType = when (filterType) {
// 全部:显示自己创建或加入的所有房间 1 -> "public"
apiClient.getRooms( 2 -> "private"
else -> null
}
// 构建API调用参数
if (ownerSessionId != null) {
// 查看其他用户的房间:显示该用户创建和加入的房间
// 1. 先快速获取该用户创建的房间(不需要 includeUsers减少数据量
val createdResponse = apiClient.getRooms(
page = currentPage, page = currentPage,
pageSize = roomsPageSize, pageSize = roomsPageSize,
roomType = roomType,
ownerSessionId = ownerSessionId,
includeUsers = false
)
val createdRooms = if (createdResponse.isSuccessful) {
createdResponse.body()?.list?.map { it.toRoomtEntity() } ?: emptyList()
} else {
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 {
// 查看自己的房间:显示创建或加入的房间
val response = apiClient.getRooms(
page = currentPage,
pageSize = roomsPageSize,
roomType = roomType,
showCreated = true, showCreated = true,
showJoined = true showJoined = if (filterType == 2) null else 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
)
}
}
if (response.isSuccessful) { if (response.isSuccessful) {
val roomList = response.body()?.list ?: emptyList() val roomList = response.body()?.list ?: emptyList()
@@ -386,6 +442,7 @@ object MyProfileViewModel : ViewModel() {
} else { } else {
Log.e("MyProfileViewModel", "loadRooms failed: ${response.code()}") Log.e("MyProfileViewModel", "loadRooms failed: ${response.code()}")
} }
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e("MyProfileViewModel", "loadRooms error: ", e) Log.e("MyProfileViewModel", "loadRooms error: ", e)
} finally { } finally {
@@ -398,20 +455,22 @@ object MyProfileViewModel : ViewModel() {
/** /**
* 加载更多房间 * 加载更多房间
* @param filterType 筛选类型0=全部1=公开2=私有 * @param filterType 筛选类型0=全部1=公开2=私有
* @param ownerSessionId 创建者用户IDChatAIID用于过滤特定创建者的房间
*/ */
fun loadMoreRooms(filterType: Int = 0) { fun loadMoreRooms(filterType: Int = 0, ownerSessionId: String? = null) {
if (roomsLoading || !roomsHasMore) return if (roomsLoading || !roomsHasMore) return
loadRooms(filterType = filterType, pullRefresh = false) loadRooms(filterType = filterType, pullRefresh = false, ownerSessionId = ownerSessionId)
} }
/** /**
* 刷新房间列表 * 刷新房间列表
* @param filterType 筛选类型0=全部1=公开2=私有 * @param filterType 筛选类型0=全部1=公开2=私有
* @param ownerSessionId 创建者用户IDChatAIID用于过滤特定创建者的房间
*/ */
fun refreshRooms(filterType: Int = 0) { fun refreshRooms(filterType: Int = 0, ownerSessionId: String? = null) {
rooms = emptyList() rooms = emptyList()
roomsCurrentPage = 1 roomsCurrentPage = 1
roomsHasMore = true roomsHasMore = true
loadRooms(filterType = filterType, pullRefresh = true) loadRooms(filterType = filterType, pullRefresh = true, ownerSessionId = ownerSessionId)
} }
} }

View File

@@ -533,22 +533,29 @@ fun ProfileV3(
showNoMoreText = isSelf, showNoMoreText = isSelf,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
state = listState, state = listState,
nestedScrollConnection = nestedScrollConnection nestedScrollConnection = nestedScrollConnection,
showSegments = isSelf // 只有查看自己的主页时才显示分段控制器
) )
} else { } else {
// 查看其他用户的主页时传递该用户的chatAIId以显示其创建的群聊查看自己的主页时传递null
GroupChatPlaceholder( GroupChatPlaceholder(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
listState = groupChatListState, listState = groupChatListState,
nestedScrollConnection = nestedScrollConnection nestedScrollConnection = nestedScrollConnection,
ownerSessionId = if (!isSelf) profile?.chatAIId else null,
showSegments = isSelf // 只有查看自己的主页时才显示分段控制器
) )
} }
} }
2 -> { 2 -> {
if (!isAiAccount) { if (!isAiAccount) {
// 查看其他用户的主页时传递该用户的chatAIId以显示其创建的群聊查看自己的主页时传递null
GroupChatPlaceholder( GroupChatPlaceholder(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
listState = groupChatListState, listState = groupChatListState,
nestedScrollConnection = nestedScrollConnection nestedScrollConnection = nestedScrollConnection,
ownerSessionId = if (!isSelf) profile?.chatAIId else null,
showSegments = isSelf // 只有查看自己的主页时才显示分段控制器
) )
} }
} }
@@ -656,12 +663,16 @@ fun ProfileV3(
private fun GroupChatPlaceholder( private fun GroupChatPlaceholder(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
listState: androidx.compose.foundation.lazy.LazyListState, listState: androidx.compose.foundation.lazy.LazyListState,
nestedScrollConnection: NestedScrollConnection? = null nestedScrollConnection: NestedScrollConnection? = null,
ownerSessionId: String? = null, // 创建者用户IDChatAIID用于过滤特定创建者的房间。如果为null则显示当前用户创建或加入的房间
showSegments: Boolean = true // 是否显示分段控制器(全部、公开、私有)
) { ) {
GroupChatEmptyContent( GroupChatEmptyContent(
modifier = modifier, modifier = modifier,
listState = listState, listState = listState,
nestedScrollConnection = nestedScrollConnection nestedScrollConnection = nestedScrollConnection,
ownerSessionId = ownerSessionId,
showSegments = showSegments
) )
} }

View File

@@ -1,47 +1,47 @@
package com.aiosman.ravenow.ui.index.tabs.profile.composable 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.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
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.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.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.ui.Alignment import androidx.compose.foundation.layout.size
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.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.foundation.lazy.grid.itemsIndexed
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue 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.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.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import com.aiosman.ravenow.AppState import com.aiosman.ravenow.AppState
@@ -227,8 +227,20 @@ fun GalleryGrid(
.padding(bottom = 8.dp), .padding(bottom = 8.dp),
) { ) {
itemsIndexed(moments) { idx, moment -> itemsIndexed(moments) { idx, moment ->
if (moment != null && moment.images.isNotEmpty()) { moment?.let { momentItem ->
val itemDebouncer = rememberDebouncer() 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( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -237,31 +249,29 @@ fun GalleryGrid(
.noRippleClickable { .noRippleClickable {
itemDebouncer { itemDebouncer {
navController.navigateToPost( navController.navigateToPost(
id = moment.id, id = momentItem.id,
highlightCommentId = 0, highlightCommentId = 0,
initImagePagerIndex = 0 initImagePagerIndex = 0
) )
} }
} }
) { ) {
if (previewUrl != null) {
CustomAsyncImage( CustomAsyncImage(
imageUrl = moment.images[0].thumbnail, imageUrl = previewUrl,
contentDescription = "", contentDescription = "",
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
context = LocalContext.current context = LocalContext.current
) )
if (moment.images.size > 1) { } else {
Box( Box(
modifier = Modifier modifier = Modifier
.padding(top = 8.dp, end = 8.dp) .fillMaxSize()
.align(Alignment.TopEnd) .background(
) { color = AppColors.basicMain.copy(alpha = 0.2f),
Image( shape = RoundedCornerShape(10.dp)
modifier = Modifier.size(24.dp), )
painter = painterResource(R.drawable.rider_pro_picture_more),
contentDescription = "",
) )
}
} }
} }
} }

View File

@@ -69,7 +69,9 @@ import android.util.Base64
fun GroupChatEmptyContent( fun GroupChatEmptyContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
listState: LazyListState, listState: LazyListState,
nestedScrollConnection: NestedScrollConnection? = null nestedScrollConnection: NestedScrollConnection? = null,
ownerSessionId: String? = null, // 创建者用户IDChatAIID用于过滤特定创建者的房间。如果为null则显示当前用户创建或加入的房间
showSegments: Boolean = true // 是否显示分段控制器(全部、公开、私有)
) { ) {
var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有 var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
@@ -77,24 +79,19 @@ fun GroupChatEmptyContent(
val navController = LocalNavController.current val navController = LocalNavController.current
val viewModel = MyProfileViewModel val viewModel = MyProfileViewModel
// 如果查看其他用户的房间固定使用全部类型filterType = 0
val filterType = if (showSegments) selectedSegment else 0
val state = rememberPullRefreshState( val state = rememberPullRefreshState(
refreshing = viewModel.roomsRefreshing, refreshing = viewModel.roomsRefreshing,
onRefresh = { onRefresh = {
viewModel.refreshRooms(filterType = selectedSegment) viewModel.refreshRooms(filterType = filterType, ownerSessionId = ownerSessionId)
} }
) )
// 当分段改变时,重新加载数据 // 当分段或用户ID改变时,重新加载数据
LaunchedEffect(selectedSegment) { LaunchedEffect(selectedSegment, ownerSessionId, showSegments) {
// 切换分段时重新加载 viewModel.refreshRooms(filterType = filterType, ownerSessionId = ownerSessionId)
viewModel.refreshRooms(filterType = selectedSegment)
}
// 初始加载
LaunchedEffect(Unit) {
if (viewModel.rooms.isEmpty() && !viewModel.roomsLoading) {
viewModel.loadRooms(filterType = selectedSegment)
}
} }
val nestedScrollModifier = if (nestedScrollConnection != null) { val nestedScrollModifier = if (nestedScrollConnection != null) {
@@ -110,7 +107,8 @@ fun GroupChatEmptyContent(
) { ) {
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// 分段控制器 // 只在查看自己的房间时显示分段控制器
if (showSegments) {
SegmentedControl( SegmentedControl(
selectedIndex = selectedSegment, selectedIndex = selectedSegment,
onSegmentSelected = { onSegmentSelected = {
@@ -121,6 +119,7 @@ fun GroupChatEmptyContent(
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
}
Box( Box(
modifier = nestedScrollModifier modifier = nestedScrollModifier
@@ -201,7 +200,7 @@ fun GroupChatEmptyContent(
if (viewModel.roomsHasMore && !viewModel.roomsLoading) { if (viewModel.roomsHasMore && !viewModel.roomsLoading) {
item { item {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
viewModel.loadMoreRooms(filterType = selectedSegment) viewModel.loadMoreRooms(filterType = filterType, ownerSessionId = ownerSessionId)
} }
} }
} }
@@ -372,7 +371,7 @@ private fun SegmentButton(
}, },
shape = RoundedCornerShape(1000.dp) shape = RoundedCornerShape(1000.dp)
) )
.clickable(onClick = onClick), .noRippleClickable { onClick() },
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Text( Text(

View File

@@ -4,6 +4,7 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import androidx.compose.foundation.layout.Box 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
@@ -66,7 +67,8 @@ fun UserAgentsList(
showNoMoreText: Boolean = false, showNoMoreText: Boolean = false,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
state: LazyListState, state: LazyListState,
nestedScrollConnection: NestedScrollConnection? = null nestedScrollConnection: NestedScrollConnection? = null,
showSegments: Boolean = true // 是否显示分段控制器(全部、公开、私有)
) { ) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val listModifier = if (nestedScrollConnection != null) { val listModifier = if (nestedScrollConnection != null) {
@@ -80,7 +82,7 @@ fun UserAgentsList(
Box( Box(
modifier = listModifier.fillMaxSize() modifier = listModifier.fillMaxSize()
) { ) {
AgentEmptyContentWithSegments() AgentEmptyContentWithSegments(showSegments = showSegments)
} }
} else { } else {
LazyColumn( LazyColumn(
@@ -248,7 +250,9 @@ fun UserAgentCard(
} }
@Composable @Composable
fun AgentEmptyContentWithSegments() { fun AgentEmptyContentWithSegments(
showSegments: Boolean = true // 是否显示分段控制器(全部、公开、私有)
) {
var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有 var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current) val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current)
@@ -260,7 +264,8 @@ fun AgentEmptyContentWithSegments() {
) { ) {
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// 分段控制器 // 只在查看自己的智能体时显示分段控制器
if (showSegments) {
AgentSegmentedControl( AgentSegmentedControl(
selectedIndex = selectedSegment, selectedIndex = selectedSegment,
onSegmentSelected = { selectedSegment = it }, onSegmentSelected = { selectedSegment = it },
@@ -268,6 +273,7 @@ fun AgentEmptyContentWithSegments() {
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
}
// 空状态内容(与动态、群聊保持一致) // 空状态内容(与动态、群聊保持一致)
Column( Column(
@@ -397,7 +403,7 @@ private fun AgentSegmentButton(
}, },
shape = RoundedCornerShape(1000.dp) shape = RoundedCornerShape(1000.dp)
) )
.clickable(onClick = onClick), .noRippleClickable { onClick() },
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Text( Text(

View File

@@ -1,9 +1,12 @@
package com.aiosman.ravenow.ui.post package com.aiosman.ravenow.ui.post
import android.Manifest
import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloatAsState 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 addImageDebouncer = rememberDebouncer()
val canAddMoreImages = model.imageList.size < 9 val canAddMoreImages = model.imageList.size < 9
@@ -642,6 +673,13 @@ fun AddImageGrid() {
.background(Color(0xFFFAF9FB)) .background(Color(0xFFFAF9FB))
.noRippleClickable { .noRippleClickable {
if (model.imageList.size < 9) { if (model.imageList.size < 9) {
// 检查摄像头权限
when {
ContextCompat.checkSelfPermission(
context,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED -> {
// 已有权限,直接打开相机
val photoFile = File(context.cacheDir, "photo.jpg") val photoFile = File(context.cacheDir, "photo.jpg")
val photoUri: Uri = FileProvider.getUriForFile( val photoUri: Uri = FileProvider.getUriForFile(
context, context,
@@ -650,6 +688,12 @@ fun AddImageGrid() {
) )
model.currentPhotoUri = photoUri model.currentPhotoUri = photoUri
takePictureLauncher.launch(photoUri) takePictureLauncher.launch(photoUri)
}
else -> {
// 没有权限,请求权限
requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
} else { } else {
Toast.makeText(context, "最多只能选择9张图片", Toast.LENGTH_SHORT).show() Toast.makeText(context, "最多只能选择9张图片", Toast.LENGTH_SHORT).show()
} }