修复动态内容为空时的崩溃问题并优化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")
|
@SerializedName("nickname")
|
||||||
val nickname: String,
|
val nickname: String,
|
||||||
@SerializedName("trtcUserId")
|
@SerializedName("trtcUserId")
|
||||||
val trtcUserId: String,
|
val trtcUserId: String? = null,
|
||||||
@SerializedName("username")
|
@SerializedName("username")
|
||||||
val username: String
|
val username: String
|
||||||
){
|
){
|
||||||
@@ -85,7 +85,7 @@ data class Profile(
|
|||||||
avatar = "${ApiClient.BASE_SERVER}$avatar",
|
avatar = "${ApiClient.BASE_SERVER}$avatar",
|
||||||
bio = bio,
|
bio = bio,
|
||||||
banner = "${ApiClient.BASE_SERVER}$banner",
|
banner = "${ApiClient.BASE_SERVER}$banner",
|
||||||
trtcUserId = trtcUserId,
|
trtcUserId = trtcUserId ?: "",
|
||||||
chatAIId = chatAIId,
|
chatAIId = chatAIId,
|
||||||
aiAccount = aiAccount
|
aiAccount = aiAccount
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,6 +26,22 @@ import com.aiosman.ravenow.entity.RoomRuleQuotaEntity
|
|||||||
import com.aiosman.ravenow.entity.UsersEntity
|
import com.aiosman.ravenow.entity.UsersEntity
|
||||||
import com.google.gson.annotations.SerializedName
|
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(
|
data class Room(
|
||||||
@SerializedName("id")
|
@SerializedName("id")
|
||||||
val id: Int,
|
val id: Int,
|
||||||
@@ -51,12 +67,26 @@ data class Room(
|
|||||||
val creator: Creator,
|
val creator: Creator,
|
||||||
@SerializedName("userCount")
|
@SerializedName("userCount")
|
||||||
val userCount: Int,
|
val userCount: Int,
|
||||||
|
@SerializedName("totalMemberCount")
|
||||||
|
val totalMemberCount: Int? = null,
|
||||||
@SerializedName("maxMemberLimit")
|
@SerializedName("maxMemberLimit")
|
||||||
val maxMemberLimit: Int,
|
val maxMemberLimit: Int,
|
||||||
|
@SerializedName("maxTotal")
|
||||||
|
val maxTotal: Int? = null,
|
||||||
|
@SerializedName("systemMaxTotal")
|
||||||
|
val systemMaxTotal: Int? = null,
|
||||||
@SerializedName("canJoin")
|
@SerializedName("canJoin")
|
||||||
val canJoin: Boolean,
|
val canJoin: Boolean,
|
||||||
@SerializedName("canJoinCode")
|
@SerializedName("canJoinCode")
|
||||||
val canJoinCode: Int,
|
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")
|
@SerializedName("users")
|
||||||
val users: List<Users>
|
val users: List<Users>
|
||||||
|
|
||||||
@@ -75,9 +105,24 @@ data class Room(
|
|||||||
allowInHot = allowInHot,
|
allowInHot = allowInHot,
|
||||||
creator = creator.toCreatorEntity(),
|
creator = creator.toCreatorEntity(),
|
||||||
userCount = userCount,
|
userCount = userCount,
|
||||||
|
totalMemberCount = totalMemberCount,
|
||||||
maxMemberLimit = maxMemberLimit,
|
maxMemberLimit = maxMemberLimit,
|
||||||
|
maxTotal = maxTotal,
|
||||||
|
systemMaxTotal = systemMaxTotal,
|
||||||
canJoin = canJoin,
|
canJoin = canJoin,
|
||||||
canJoinCode = canJoinCode,
|
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() }
|
users = users.map { it.toUsersEntity() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -90,7 +135,7 @@ data class Creator(
|
|||||||
@SerializedName("userId")
|
@SerializedName("userId")
|
||||||
val userId: String,
|
val userId: String,
|
||||||
@SerializedName("trtcUserId")
|
@SerializedName("trtcUserId")
|
||||||
val trtcUserId: String,
|
val trtcUserId: String? = null,
|
||||||
@SerializedName("profile")
|
@SerializedName("profile")
|
||||||
val profile: Profile
|
val profile: Profile
|
||||||
){
|
){
|
||||||
@@ -98,7 +143,7 @@ data class Creator(
|
|||||||
return CreatorEntity(
|
return CreatorEntity(
|
||||||
id = id,
|
id = id,
|
||||||
userId = userId,
|
userId = userId,
|
||||||
trtcUserId = trtcUserId,
|
trtcUserId = trtcUserId ?: "",
|
||||||
profile = profile.toProfileEntity()
|
profile = profile.toProfileEntity()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -110,7 +155,7 @@ data class Users(
|
|||||||
@SerializedName("userId")
|
@SerializedName("userId")
|
||||||
val userId: String,
|
val userId: String,
|
||||||
@SerializedName("trtcUserId")
|
@SerializedName("trtcUserId")
|
||||||
val trtcUserId: String,
|
val trtcUserId: String? = null,
|
||||||
@SerializedName("profile")
|
@SerializedName("profile")
|
||||||
val profile: Profile
|
val profile: Profile
|
||||||
){
|
){
|
||||||
|
|||||||
@@ -1423,11 +1423,39 @@ interface RaveNowAPI {
|
|||||||
@POST("outside/rooms")
|
@POST("outside/rooms")
|
||||||
suspend fun createGroupChat(@Body body: CreateGroupChatRequestBody): Response<DataContainer<Unit>>
|
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")
|
@GET("outside/rooms")
|
||||||
suspend fun getRooms(@Query("page") page: Int = 1,
|
suspend fun getRooms(
|
||||||
@Query("pageSize") pageSize: Int = 20,
|
@Query("page") page: Int = 1,
|
||||||
@Query("isRecommended") isRecommended: Int = 1,
|
@Query("pageSize") pageSize: Int = 20,
|
||||||
@Query("random") random: Int? = null,
|
@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>>
|
): Response<ListContainer<Room>>
|
||||||
|
|
||||||
@GET("outside/rooms/detail")
|
@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(
|
data class RoomEntity(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val name: String,
|
val name: String,
|
||||||
@@ -21,9 +32,16 @@ data class RoomEntity(
|
|||||||
val allowInHot: Boolean,
|
val allowInHot: Boolean,
|
||||||
val creator: CreatorEntity,
|
val creator: CreatorEntity,
|
||||||
val userCount: Int,
|
val userCount: Int,
|
||||||
|
val totalMemberCount: Int? = null,
|
||||||
val maxMemberLimit: Int,
|
val maxMemberLimit: Int,
|
||||||
|
val maxTotal: Int? = null,
|
||||||
|
val systemMaxTotal: Int? = null,
|
||||||
val canJoin: Boolean,
|
val canJoin: Boolean,
|
||||||
val canJoinCode: Int,
|
val canJoinCode: Int,
|
||||||
|
val privateFeePaid: Boolean = false,
|
||||||
|
val prompts: List<PromptTemplateEntity> = emptyList(),
|
||||||
|
val createdAt: String? = null,
|
||||||
|
val updatedAt: String? = null,
|
||||||
val users: List<UsersEntity>,
|
val users: List<UsersEntity>,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ object AgentViewModel: ViewModel() {
|
|||||||
page = 1,
|
page = 1,
|
||||||
pageSize = 20,
|
pageSize = 20,
|
||||||
isRecommended = 1,
|
isRecommended = 1,
|
||||||
random = 1
|
random = "1"
|
||||||
)
|
)
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
val allRooms = response.body()?.list ?: emptyList()
|
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.MomentLoader
|
||||||
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
|
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
|
||||||
import com.aiosman.ravenow.entity.MomentServiceImpl
|
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.FollowChangeEvent
|
||||||
import com.aiosman.ravenow.event.MomentAddEvent
|
import com.aiosman.ravenow.event.MomentAddEvent
|
||||||
import com.aiosman.ravenow.event.MomentFavouriteChangeEvent
|
import com.aiosman.ravenow.event.MomentFavouriteChangeEvent
|
||||||
@@ -40,6 +44,14 @@ object MyProfileViewModel : ViewModel() {
|
|||||||
var profile by mutableStateOf<AccountProfileEntity?>(null)
|
var profile by mutableStateOf<AccountProfileEntity?>(null)
|
||||||
var moments by mutableStateOf<List<MomentEntity>>(emptyList())
|
var moments by mutableStateOf<List<MomentEntity>>(emptyList())
|
||||||
var agents by mutableStateOf<List<AgentEntity>>(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 {
|
val momentLoader: MomentLoader = MomentLoader().apply {
|
||||||
pageSize = 20 // 设置与后端一致的页面大小
|
pageSize = 20 // 设置与后端一致的页面大小
|
||||||
onListChanged = {
|
onListChanged = {
|
||||||
@@ -254,4 +266,115 @@ object MyProfileViewModel : ViewModel() {
|
|||||||
fun onFollowChangeEvent(event: FollowChangeEvent) {
|
fun onFollowChangeEvent(event: FollowChangeEvent) {
|
||||||
momentLoader.updateFollowStatus(event.userId, event.isFollow)
|
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.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
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.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.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
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.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.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.aiosman.ravenow.ConstVars
|
||||||
import com.aiosman.ravenow.LocalAppTheme
|
import com.aiosman.ravenow.LocalAppTheme
|
||||||
|
import com.aiosman.ravenow.LocalNavController
|
||||||
import com.aiosman.ravenow.R
|
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
|
@Composable
|
||||||
fun GroupChatEmptyContent() {
|
fun GroupChatEmptyContent() {
|
||||||
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 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(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -50,37 +94,192 @@ fun GroupChatEmptyContent() {
|
|||||||
// 分段控制器
|
// 分段控制器
|
||||||
SegmentedControl(
|
SegmentedControl(
|
||||||
selectedIndex = selectedSegment,
|
selectedIndex = selectedSegment,
|
||||||
onSegmentSelected = { selectedSegment = it },
|
onSegmentSelected = {
|
||||||
|
selectedSegment = it
|
||||||
|
// LaunchedEffect 会监听 selectedSegment 的变化并自动刷新
|
||||||
|
},
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
// 空状态内容(居中)
|
Box(
|
||||||
Column(
|
modifier = Modifier
|
||||||
modifier = Modifier.fillMaxWidth(),
|
.fillMaxSize()
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
.pullRefresh(state)
|
||||||
) {
|
) {
|
||||||
// 空状态插图
|
if (viewModel.rooms.isEmpty() && !viewModel.roomsLoading) {
|
||||||
EmptyStateIllustration()
|
// 空状态内容(居中)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
// 空状态插图
|
||||||
|
EmptyStateIllustration()
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(9.dp))
|
PullRefreshIndicator(
|
||||||
|
refreshing = viewModel.roomsRefreshing,
|
||||||
// 空状态文本
|
state = state,
|
||||||
Text(
|
modifier = Modifier.align(Alignment.TopCenter)
|
||||||
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
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
@Composable
|
||||||
private fun SegmentedControl(
|
private fun SegmentedControl(
|
||||||
selectedIndex: Int,
|
selectedIndex: Int,
|
||||||
|
|||||||
Reference in New Issue
Block a user