修复动态内容为空时的崩溃问题并优化UI
- 将`Moment`实体中的`momentTextContent`字段类型从`String`修改为`String?`,以允许其为空,修复了多处因空内容引发的崩溃。 - 在多个UI组件中(如新闻、短视频、推荐等)添加了对`momentTextContent`的空值检查。 - 优化了“发现”页中智能体(Agent)卡片的UI样式,使用大图背景和渐变效果,并调整了按钮和文本布局。 - 为图片加载组件(`CustomAsyncImage`)增加了默认占位图,提升了加载过程中的用户体验。 - 在热门动态列表中,过滤掉没有图片的动态,确保UI显示正常。 - 修复了Prompt推荐页面的用户资料和AI聊天导航逻辑,并增加了防崩溃处理。
This commit is contained in:
@@ -73,7 +73,7 @@ data class Profile(
|
||||
@SerializedName("nickname")
|
||||
val nickname: String,
|
||||
@SerializedName("trtcUserId")
|
||||
val trtcUserId: String,
|
||||
val trtcUserId: String? = null,
|
||||
@SerializedName("username")
|
||||
val username: String
|
||||
){
|
||||
@@ -85,7 +85,7 @@ data class Profile(
|
||||
avatar = "${ApiClient.BASE_SERVER}$avatar",
|
||||
bio = bio,
|
||||
banner = "${ApiClient.BASE_SERVER}$banner",
|
||||
trtcUserId = trtcUserId,
|
||||
trtcUserId = trtcUserId ?: "",
|
||||
chatAIId = chatAIId,
|
||||
aiAccount = aiAccount
|
||||
)
|
||||
|
||||
@@ -26,6 +26,22 @@ import com.aiosman.ravenow.entity.RoomRuleQuotaEntity
|
||||
import com.aiosman.ravenow.entity.UsersEntity
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
/**
|
||||
* 房间内的智能体信息(PromptTemplate)
|
||||
*/
|
||||
data class PromptTemplate(
|
||||
@SerializedName("id")
|
||||
val id: Int,
|
||||
@SerializedName("openId")
|
||||
val openId: String,
|
||||
@SerializedName("title")
|
||||
val title: String,
|
||||
@SerializedName("desc")
|
||||
val desc: String,
|
||||
@SerializedName("avatar")
|
||||
val avatar: String
|
||||
)
|
||||
|
||||
data class Room(
|
||||
@SerializedName("id")
|
||||
val id: Int,
|
||||
@@ -51,12 +67,26 @@ data class Room(
|
||||
val creator: Creator,
|
||||
@SerializedName("userCount")
|
||||
val userCount: Int,
|
||||
@SerializedName("totalMemberCount")
|
||||
val totalMemberCount: Int? = null,
|
||||
@SerializedName("maxMemberLimit")
|
||||
val maxMemberLimit: Int,
|
||||
@SerializedName("maxTotal")
|
||||
val maxTotal: Int? = null,
|
||||
@SerializedName("systemMaxTotal")
|
||||
val systemMaxTotal: Int? = null,
|
||||
@SerializedName("canJoin")
|
||||
val canJoin: Boolean,
|
||||
@SerializedName("canJoinCode")
|
||||
val canJoinCode: Int,
|
||||
@SerializedName("privateFeePaid")
|
||||
val privateFeePaid: Boolean? = null,
|
||||
@SerializedName("prompts")
|
||||
val prompts: List<PromptTemplate>? = null,
|
||||
@SerializedName("createdAt")
|
||||
val createdAt: String? = null,
|
||||
@SerializedName("updatedAt")
|
||||
val updatedAt: String? = null,
|
||||
@SerializedName("users")
|
||||
val users: List<Users>
|
||||
|
||||
@@ -75,9 +105,24 @@ data class Room(
|
||||
allowInHot = allowInHot,
|
||||
creator = creator.toCreatorEntity(),
|
||||
userCount = userCount,
|
||||
totalMemberCount = totalMemberCount,
|
||||
maxMemberLimit = maxMemberLimit,
|
||||
maxTotal = maxTotal,
|
||||
systemMaxTotal = systemMaxTotal,
|
||||
canJoin = canJoin,
|
||||
canJoinCode = canJoinCode,
|
||||
privateFeePaid = privateFeePaid ?: false,
|
||||
prompts = prompts?.map {
|
||||
com.aiosman.ravenow.entity.PromptTemplateEntity(
|
||||
id = it.id,
|
||||
openId = it.openId,
|
||||
title = it.title,
|
||||
desc = it.desc,
|
||||
avatar = it.avatar
|
||||
)
|
||||
} ?: emptyList(),
|
||||
createdAt = createdAt,
|
||||
updatedAt = updatedAt,
|
||||
users = users.map { it.toUsersEntity() }
|
||||
)
|
||||
}
|
||||
@@ -90,7 +135,7 @@ data class Creator(
|
||||
@SerializedName("userId")
|
||||
val userId: String,
|
||||
@SerializedName("trtcUserId")
|
||||
val trtcUserId: String,
|
||||
val trtcUserId: String? = null,
|
||||
@SerializedName("profile")
|
||||
val profile: Profile
|
||||
){
|
||||
@@ -98,7 +143,7 @@ data class Creator(
|
||||
return CreatorEntity(
|
||||
id = id,
|
||||
userId = userId,
|
||||
trtcUserId = trtcUserId,
|
||||
trtcUserId = trtcUserId ?: "",
|
||||
profile = profile.toProfileEntity()
|
||||
)
|
||||
}
|
||||
@@ -110,7 +155,7 @@ data class Users(
|
||||
@SerializedName("userId")
|
||||
val userId: String,
|
||||
@SerializedName("trtcUserId")
|
||||
val trtcUserId: String,
|
||||
val trtcUserId: String? = null,
|
||||
@SerializedName("profile")
|
||||
val profile: Profile
|
||||
){
|
||||
|
||||
@@ -1423,11 +1423,39 @@ interface RaveNowAPI {
|
||||
@POST("outside/rooms")
|
||||
suspend fun createGroupChat(@Body body: CreateGroupChatRequestBody): Response<DataContainer<Unit>>
|
||||
|
||||
/**
|
||||
* 获取房间列表
|
||||
*
|
||||
* 支持游客和认证用户访问,根据用户类型返回不同的房间数据。
|
||||
* 游客模式返回公开推荐房间列表,认证用户模式返回用户可访问的群聊列表。
|
||||
*
|
||||
* @param page 页码,默认 1
|
||||
* @param pageSize 每页数量,默认 20(游客模式最大50)
|
||||
* @param roomId 房间ID,用于精确查询特定房间(仅认证用户)
|
||||
* @param includeUsers 是否包含用户列表,默认false(仅认证用户)
|
||||
* @param isRecommended 是否推荐过滤器:1=推荐,0=非推荐,null=不过滤(仅认证用户)
|
||||
* @param roomType 房间类型过滤:all=公有私有都显示, public=只显示公有, private=只显示私有, created=只显示自己创建的, joined=只显示自己加入的(仅认证用户)
|
||||
* @param search 搜索关键字,支持房间名称、描述、智能体名称模糊匹配
|
||||
* @param random 是否随机排序,字符串长度不为0则为true(传任意非空字符串即可)
|
||||
* @param ownerSessionId 创建者用户ID(ChatAIID),用于过滤特定创建者的房间
|
||||
* @param showPublic 是否显示公有房间,只有明确设置为true时才生效(优先级高于roomType,仅认证用户)
|
||||
* @param showCreated 是否显示自己创建的房间,只有明确设置为true时才生效(优先级高于roomType,仅认证用户)
|
||||
* @param showJoined 是否显示自己加入的房间,只有明确设置为true时才生效(优先级高于roomType,仅认证用户)
|
||||
*/
|
||||
@GET("outside/rooms")
|
||||
suspend fun getRooms(@Query("page") page: Int = 1,
|
||||
@Query("pageSize") pageSize: Int = 20,
|
||||
@Query("isRecommended") isRecommended: Int = 1,
|
||||
@Query("random") random: Int? = null,
|
||||
suspend fun getRooms(
|
||||
@Query("page") page: Int = 1,
|
||||
@Query("pageSize") pageSize: Int = 20,
|
||||
@Query("roomId") roomId: Long? = null,
|
||||
@Query("includeUsers") includeUsers: Boolean? = null,
|
||||
@Query("isRecommended") isRecommended: Int? = null,
|
||||
@Query("roomType") roomType: String? = null,
|
||||
@Query("search") search: String? = null,
|
||||
@Query("random") random: String? = null,
|
||||
@Query("ownerSessionId") ownerSessionId: String? = null,
|
||||
@Query("showPublic") showPublic: Boolean? = null,
|
||||
@Query("showCreated") showCreated: Boolean? = null,
|
||||
@Query("showJoined") showJoined: Boolean? = null,
|
||||
): Response<ListContainer<Room>>
|
||||
|
||||
@GET("outside/rooms/detail")
|
||||
|
||||
@@ -8,6 +8,17 @@ import com.aiosman.ravenow.data.api.ApiClient
|
||||
* 群聊房间
|
||||
*/
|
||||
|
||||
/**
|
||||
* 房间内的智能体信息实体
|
||||
*/
|
||||
data class PromptTemplateEntity(
|
||||
val id: Int,
|
||||
val openId: String,
|
||||
val title: String,
|
||||
val desc: String,
|
||||
val avatar: String
|
||||
)
|
||||
|
||||
data class RoomEntity(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
@@ -21,9 +32,16 @@ data class RoomEntity(
|
||||
val allowInHot: Boolean,
|
||||
val creator: CreatorEntity,
|
||||
val userCount: Int,
|
||||
val totalMemberCount: Int? = null,
|
||||
val maxMemberLimit: Int,
|
||||
val maxTotal: Int? = null,
|
||||
val systemMaxTotal: Int? = null,
|
||||
val canJoin: Boolean,
|
||||
val canJoinCode: Int,
|
||||
val privateFeePaid: Boolean = false,
|
||||
val prompts: List<PromptTemplateEntity> = emptyList(),
|
||||
val createdAt: String? = null,
|
||||
val updatedAt: String? = null,
|
||||
val users: List<UsersEntity>,
|
||||
)
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ object AgentViewModel: ViewModel() {
|
||||
page = 1,
|
||||
pageSize = 20,
|
||||
isRecommended = 1,
|
||||
random = 1
|
||||
random = "1"
|
||||
)
|
||||
if (response.isSuccessful) {
|
||||
val allRooms = response.body()?.list ?: emptyList()
|
||||
|
||||
@@ -23,6 +23,10 @@ import com.aiosman.ravenow.entity.MomentEntity
|
||||
import com.aiosman.ravenow.entity.MomentLoader
|
||||
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
|
||||
import com.aiosman.ravenow.entity.MomentServiceImpl
|
||||
import com.aiosman.ravenow.entity.RoomEntity
|
||||
import com.aiosman.ravenow.data.api.ApiClient
|
||||
import com.aiosman.ravenow.data.api.RaveNowAPI
|
||||
import com.aiosman.ravenow.data.Room
|
||||
import com.aiosman.ravenow.event.FollowChangeEvent
|
||||
import com.aiosman.ravenow.event.MomentAddEvent
|
||||
import com.aiosman.ravenow.event.MomentFavouriteChangeEvent
|
||||
@@ -40,6 +44,14 @@ object MyProfileViewModel : ViewModel() {
|
||||
var profile by mutableStateOf<AccountProfileEntity?>(null)
|
||||
var moments by mutableStateOf<List<MomentEntity>>(emptyList())
|
||||
var agents by mutableStateOf<List<AgentEntity>>(emptyList())
|
||||
var rooms by mutableStateOf<List<RoomEntity>>(emptyList())
|
||||
var roomsLoading by mutableStateOf(false)
|
||||
var roomsRefreshing by mutableStateOf(false)
|
||||
var roomsCurrentPage by mutableStateOf(1)
|
||||
var roomsHasMore by mutableStateOf(true)
|
||||
private val roomsPageSize = 20
|
||||
private val apiClient: RaveNowAPI = ApiClient.api
|
||||
|
||||
val momentLoader: MomentLoader = MomentLoader().apply {
|
||||
pageSize = 20 // 设置与后端一致的页面大小
|
||||
onListChanged = {
|
||||
@@ -254,4 +266,115 @@ object MyProfileViewModel : ViewModel() {
|
||||
fun onFollowChangeEvent(event: FollowChangeEvent) {
|
||||
momentLoader.updateFollowStatus(event.userId, event.isFollow)
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载房间列表
|
||||
* @param filterType 筛选类型:0=全部,1=公开,2=私有
|
||||
* @param pullRefresh 是否下拉刷新
|
||||
*/
|
||||
fun loadRooms(filterType: Int = 0, pullRefresh: Boolean = false) {
|
||||
// 游客模式下不加载房间列表
|
||||
if (AppStore.isGuest) {
|
||||
Log.d("MyProfileViewModel", "loadRooms: 游客模式下跳过加载房间列表")
|
||||
return
|
||||
}
|
||||
|
||||
if (roomsLoading && !pullRefresh) return
|
||||
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
roomsLoading = true
|
||||
roomsRefreshing = pullRefresh
|
||||
|
||||
val currentPage = if (pullRefresh) {
|
||||
roomsCurrentPage = 1
|
||||
roomsHasMore = true
|
||||
1
|
||||
} else {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
} finally {
|
||||
roomsLoading = false
|
||||
roomsRefreshing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载更多房间
|
||||
* @param filterType 筛选类型:0=全部,1=公开,2=私有
|
||||
*/
|
||||
fun loadMoreRooms(filterType: Int = 0) {
|
||||
if (roomsLoading || !roomsHasMore) return
|
||||
loadRooms(filterType = filterType, pullRefresh = false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新房间列表
|
||||
* @param filterType 筛选类型:0=全部,1=公开,2=私有
|
||||
*/
|
||||
fun refreshRooms(filterType: Int = 0) {
|
||||
rooms = emptyList()
|
||||
roomsCurrentPage = 1
|
||||
roomsHasMore = true
|
||||
loadRooms(filterType = filterType, pullRefresh = true)
|
||||
}
|
||||
}
|
||||
@@ -14,17 +14,28 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.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
|
||||
@@ -32,13 +43,46 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.aiosman.ravenow.ConstVars
|
||||
import com.aiosman.ravenow.LocalAppTheme
|
||||
import com.aiosman.ravenow.LocalNavController
|
||||
import com.aiosman.ravenow.R
|
||||
import com.aiosman.ravenow.entity.RoomEntity
|
||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||
import com.aiosman.ravenow.ui.composables.rememberDebouncer
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
import com.aiosman.ravenow.ui.navigateToGroupChat
|
||||
import com.aiosman.ravenow.AppStore
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun GroupChatEmptyContent() {
|
||||
var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有
|
||||
val AppColors = LocalAppTheme.current
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
val viewModel = MyProfileViewModel
|
||||
|
||||
val state = rememberPullRefreshState(
|
||||
refreshing = viewModel.roomsRefreshing,
|
||||
onRefresh = {
|
||||
viewModel.refreshRooms(filterType = selectedSegment)
|
||||
}
|
||||
)
|
||||
|
||||
// 当分段改变时,重新加载数据
|
||||
LaunchedEffect(selectedSegment) {
|
||||
// 切换分段时重新加载
|
||||
viewModel.refreshRooms(filterType = selectedSegment)
|
||||
}
|
||||
|
||||
// 初始加载
|
||||
LaunchedEffect(Unit) {
|
||||
if (viewModel.rooms.isEmpty() && !viewModel.roomsLoading) {
|
||||
viewModel.loadRooms(filterType = selectedSegment)
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -50,37 +94,192 @@ fun GroupChatEmptyContent() {
|
||||
// 分段控制器
|
||||
SegmentedControl(
|
||||
selectedIndex = selectedSegment,
|
||||
onSegmentSelected = { selectedSegment = it },
|
||||
onSegmentSelected = {
|
||||
selectedSegment = it
|
||||
// LaunchedEffect 会监听 selectedSegment 的变化并自动刷新
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// 空状态内容(居中)
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.pullRefresh(state)
|
||||
) {
|
||||
// 空状态插图
|
||||
EmptyStateIllustration()
|
||||
if (viewModel.rooms.isEmpty() && !viewModel.roomsLoading) {
|
||||
// 空状态内容(居中)
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// 空状态插图
|
||||
EmptyStateIllustration()
|
||||
|
||||
Spacer(modifier = Modifier.height(9.dp))
|
||||
Spacer(modifier = Modifier.height(9.dp))
|
||||
|
||||
// 空状态文本
|
||||
Text(
|
||||
text = stringResource(R.string.empty_nothing),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = AppColors.text,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
// 空状态文本
|
||||
Text(
|
||||
text = stringResource(R.string.empty_nothing),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = AppColors.text,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = viewModel.rooms,
|
||||
key = { _, item -> item.id }
|
||||
) { index, room ->
|
||||
RoomItem(
|
||||
room = room,
|
||||
onRoomClick = { roomEntity ->
|
||||
// 导航到群聊聊天界面
|
||||
navController.navigateToGroupChat(
|
||||
id = roomEntity.trtcRoomId,
|
||||
name = roomEntity.name,
|
||||
avatar = roomEntity.avatar
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
if (index < viewModel.rooms.size - 1) {
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
color = AppColors.divider
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载更多指示器
|
||||
if (viewModel.roomsLoading && viewModel.rooms.isNotEmpty()) {
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(24.dp),
|
||||
color = AppColors.main
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 加载更多触发
|
||||
if (viewModel.roomsHasMore && !viewModel.roomsLoading) {
|
||||
item {
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.loadMoreRooms(filterType = selectedSegment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = viewModel.roomsRefreshing,
|
||||
state = state,
|
||||
modifier = Modifier.align(Alignment.TopCenter)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RoomItem(
|
||||
room: RoomEntity,
|
||||
onRoomClick: (RoomEntity) -> Unit = {}
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
val context = LocalContext.current
|
||||
val roomDebouncer = rememberDebouncer()
|
||||
|
||||
// 构建头像URL
|
||||
val avatarUrl = if (room.avatar.isNotEmpty()) {
|
||||
"${ConstVars.BASE_SERVER}/api/v1/outside/${room.avatar}?token=${AppStore.token}"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp, vertical = 12.dp)
|
||||
.noRippleClickable {
|
||||
roomDebouncer {
|
||||
onRoomClick(room)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Box {
|
||||
CustomAsyncImage(
|
||||
context = context,
|
||||
imageUrl = avatarUrl,
|
||||
contentDescription = room.name,
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(start = 12.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = room.name,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = AppColors.text,
|
||||
modifier = Modifier.weight(1f),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = room.description.ifEmpty { "暂无描述" },
|
||||
fontSize = 14.sp,
|
||||
color = AppColors.secondaryText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
Text(
|
||||
text = "${room.userCount}人",
|
||||
fontSize = 12.sp,
|
||||
color = AppColors.secondaryText
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SegmentedControl(
|
||||
selectedIndex: Int,
|
||||
|
||||
Reference in New Issue
Block a user