Merge pull request #69 from Kevinlinpr/zhong_1

Zhong 1
This commit is contained in:
2025-11-12 10:32:33 +08:00
committed by GitHub
8 changed files with 462 additions and 184 deletions

View File

@@ -84,6 +84,8 @@ fun AddGroupMemberScreen(groupId: String, groupName: String?) {
LaunchedEffect(Unit) {
systemUiController.setNavigationBarColor(Color.Transparent)
AddGroupMemberViewModel.groupName = groupName
AddGroupMemberViewModel.trtcId = groupId
AddGroupMemberViewModel.roomId = null
}
Box(

View File

@@ -9,6 +9,8 @@ import androidx.lifecycle.viewModelScope
import com.aiosman.ravenow.AppStore
import com.aiosman.ravenow.data.AccountService
import com.aiosman.ravenow.data.AccountServiceImpl
import com.aiosman.ravenow.data.RoomService
import com.aiosman.ravenow.data.RoomServiceImpl
import com.aiosman.ravenow.data.UserService
import com.aiosman.ravenow.data.UserServiceImpl
import com.aiosman.ravenow.data.api.ApiClient
@@ -17,11 +19,14 @@ import kotlinx.coroutines.launch
object AddGroupMemberViewModel : ViewModel() {
val accountService: AccountService = AccountServiceImpl()
val userService: UserService = UserServiceImpl()
val roomService: RoomService = RoomServiceImpl()
// 状态管理
var isLoading by mutableStateOf(false)
var errorMessage by mutableStateOf<String?>(null)
var groupName: String? = null
var trtcId: String? = null
var roomId: Int? = null
// 添加成员到群聊
suspend fun addMembersToGroup(
@@ -30,26 +35,84 @@ object AddGroupMemberViewModel : ViewModel() {
return try {
isLoading = true
if (groupName == null) {
// 验证房间标识
if (trtcId == null && roomId == null) {
isLoading = false
val errorMsg = "群聊名称不能为空"
val errorMsg = "房间标识不能为空"
showToast(errorMsg)
return false
}
// 根据isAi属性分别获取userIds和promptIds
val userIds = selectedMembers.filter { !it.isAi }.map { it.id }
val promptIds = selectedMembers.filter { it.isAi }.map { it.id }
// 根据isAi属性分别获取用户和智能体的OpenID列表
val userOpenIds = selectedMembers.filter { !it.isAi }.map { it.id }
val agentOpenIds = selectedMembers.filter { it.isAi }.map { it.id }
var allSuccess = true
var errorMessages = mutableListOf<String>()
// 添加用户到房间
if (userOpenIds.isNotEmpty()) {
try {
val userResult = roomService.addUserToRoom(
roomId = roomId,
trtcId = trtcId,
openIds = userOpenIds
)
// 检查添加结果
if (userResult.failedCount > 0) {
val failedUsers = userResult.failedItems.joinToString(", ") { it.userId }
errorMessages.add("部分用户添加失败: $failedUsers")
}
if (userResult.successCount == 0 && userResult.failedCount > 0) {
allSuccess = false
}
} catch (e: Exception) {
allSuccess = false
errorMessages.add("添加用户失败: ${e.message}")
}
}
// 添加智能体到房间
if (agentOpenIds.isNotEmpty()) {
try {
val agentResult = roomService.addAgentToRoom(
roomId = roomId,
trtcId = trtcId,
agentOpenIds = agentOpenIds
)
// 检查添加结果
if (agentResult.failedCount > 0) {
val failedAgents = agentResult.failedItems.joinToString(", ") { it.agentOpenId }
errorMessages.add("部分智能体添加失败: $failedAgents")
}
if (agentResult.successCount == 0 && agentResult.failedCount > 0) {
allSuccess = false
}
} catch (e: Exception) {
allSuccess = false
errorMessages.add("添加智能体失败: ${e.message}")
}
}
// 使用创建群聊的API传入群聊名称来添加成员到现有群聊
// 与创建群聊使用相同的方法,通过群聊名称标识目标群聊
val response = accountService.createGroupChat(groupName!!, userIds, promptIds, roomId = null)
if (response.isSuccessful && response.body() != null) {
isLoading = false
if (allSuccess) {
if (errorMessages.isNotEmpty()) {
// 有部分失败,但至少有一些成功
showToast(errorMessages.joinToString("\n"))
}
true
} else {
isLoading = false
val errorMsg = "添加成员失败: ${response.message()}"
// 全部失败
val errorMsg = if (errorMessages.isNotEmpty()) {
errorMessages.joinToString("\n")
} else {
"添加成员失败"
}
showToast(errorMsg)
false
}
@@ -62,11 +125,7 @@ object AddGroupMemberViewModel : ViewModel() {
}
private fun showToast(message: String) {
errorMessage = message
viewModelScope.launch {
kotlinx.coroutines.delay(3000)
errorMessage = null
}
Log.w("AddGroupMemberViewModel", message)
}
// 清除错误信息

View File

@@ -113,10 +113,5 @@ class GroupMembersViewModel(
}
}
}
fun toggleRequireApproval() {
requireApproval = !requireApproval
// TODO: 实现更新设置的API调用
}
}

View File

@@ -323,6 +323,8 @@ fun Agent() {
}
}
// 只有当热门聊天室有数据时,才展示“发现更多”区域
if (viewModel.chatRooms.isNotEmpty()) {
item { Spacer(modifier = Modifier.height(20.dp)) }
// "发现更多" 标题 - 吸顶
@@ -379,7 +381,7 @@ fun Agent() {
}
}
// 加载更多指示器
// 加载更多指示器(仅在展示“发现更多”时显示)
if (viewModel.isLoadingMore) {
item {
Row(
@@ -404,6 +406,7 @@ fun Agent() {
}
}
}
}
}
@Composable

View File

@@ -163,7 +163,7 @@ fun NotificationsScreen() {
modifier = Modifier
.size(24.dp)
.noRippleClickable {
// TODO: 实现搜索功能
navController.navigate(NavigationRoute.Search.route)
},
colorFilter = ColorFilter.tint(AppColors.text)
)

View File

@@ -29,9 +29,12 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
@@ -57,6 +60,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.positionInParent
import com.aiosman.ravenow.ui.composables.TabItem
import com.aiosman.ravenow.ui.composables.UnderlineTabItem
import com.aiosman.ravenow.ui.composables.rememberDebouncer
@@ -77,6 +81,14 @@ fun MomentsList() {
val tabCount = if (AppStore.isGuest) 5 else 6
var pagerState = rememberPagerState { tabCount }
var scope = rememberCoroutineScope()
val scrollState = rememberScrollState()
val density = LocalDensity.current
// 存储每个标签的位置和宽度信息 (页面索引, 位置, 宽度)
val tabPositions = remember { mutableStateOf<List<Triple<Int, Float, Float>>>(emptyList()) }
// 存储容器宽度
val containerWidth = remember { mutableStateOf(0f) }
// 记录上一次的页面索引
val previousPage = remember { mutableStateOf(pagerState.currentPage) }
Column(
modifier = Modifier
.fillMaxSize()
@@ -96,16 +108,38 @@ fun MomentsList() {
verticalAlignment = Alignment.CenterVertically
) {
// 可滚动的标签页行
Row(
Box(
modifier = Modifier
.weight(1f)
.horizontalScroll(rememberScrollState()),
.onGloballyPositioned { coordinates ->
containerWidth.value = with(density) { coordinates.size.width.toDp().toPx() }
}
) {
Row(
modifier = Modifier
.fillMaxWidth()
.horizontalScroll(scrollState),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically
) {
val tabDebouncer = rememberDebouncer()
// 推荐标签
Box(
modifier = Modifier.onGloballyPositioned { coordinates ->
val position = with(density) { coordinates.positionInParent().x.toDp().toPx() }
val width = with(density) { coordinates.size.width.toDp().toPx() }
val currentPositions = tabPositions.value.toMutableList()
val existingIndex = currentPositions.indexOfFirst { it.first == 0 }
if (existingIndex == -1) {
currentPositions.add(Triple(0, position, width))
tabPositions.value = currentPositions.sortedBy { it.first }
} else {
currentPositions[existingIndex] = Triple(0, position, width)
tabPositions.value = currentPositions
}
}
) {
UnderlineTabItem(
text = stringResource(R.string.tab_recommend),
isSelected = pagerState.currentPage == 0,
@@ -117,9 +151,25 @@ fun MomentsList() {
}
}
)
}
// 短视频标签
Box(
modifier = Modifier.onGloballyPositioned { coordinates ->
val position = with(density) { coordinates.positionInParent().x.toDp().toPx() }
val width = with(density) { coordinates.size.width.toDp().toPx() }
val currentPositions = tabPositions.value.toMutableList()
val existingIndex = currentPositions.indexOfFirst { it.first == 1 }
if (existingIndex == -1) {
currentPositions.add(Triple(1, position, width))
tabPositions.value = currentPositions.sortedBy { it.first }
} else {
currentPositions[existingIndex] = Triple(1, position, width)
tabPositions.value = currentPositions
}
}
) {
UnderlineTabItem(
text = stringResource(R.string.tab_short_video),
isSelected = pagerState.currentPage == 1,
@@ -131,38 +181,110 @@ fun MomentsList() {
}
}
)
}
// 动态标签
Box(
modifier = Modifier.onGloballyPositioned { coordinates ->
val position = with(density) { coordinates.positionInParent().x.toDp().toPx() }
val width = with(density) { coordinates.size.width.toDp().toPx() }
val currentPositions = tabPositions.value.toMutableList()
val existingIndex = currentPositions.indexOfFirst { it.first == 2 }
if (existingIndex == -1) {
currentPositions.add(Triple(2, position, width))
tabPositions.value = currentPositions.sortedBy { it.first }
} else {
currentPositions[existingIndex] = Triple(2, position, width)
tabPositions.value = currentPositions
}
}
) {
UnderlineTabItem(
text = stringResource(R.string.moment),
isSelected = pagerState.currentPage == 2,
onClick = {
tabDebouncer {
scope.launch {
// 如果当前在动态标签右边的标签页,立即向左滚动显示推荐标签
if (pagerState.currentPage > 2 && containerWidth.value > 0) {
val recommendTab = tabPositions.value.find { it.first == 0 }
if (recommendTab != null) {
val (_, recommendPosition, _) = recommendTab
val currentScroll = scrollState.value.toFloat()
val scrollToPosition = recommendPosition.coerceAtLeast(0f)
if (kotlin.math.abs(scrollToPosition - currentScroll) > 1f) {
scrollState.animateScrollTo(scrollToPosition.toInt())
}
}
}
pagerState.animateScrollToPage(2)
}
}
}
)
}
// 只有非游客用户才显示"关注"tab
if (!AppStore.isGuest) {
Box(
modifier = Modifier.onGloballyPositioned { coordinates ->
val position = with(density) { coordinates.positionInParent().x.toDp().toPx() }
val width = with(density) { coordinates.size.width.toDp().toPx() }
val currentPositions = tabPositions.value.toMutableList()
val existingIndex = currentPositions.indexOfFirst { it.first == 3 }
if (existingIndex == -1) {
currentPositions.add(Triple(3, position, width))
tabPositions.value = currentPositions.sortedBy { it.first }
} else {
currentPositions[existingIndex] = Triple(3, position, width)
tabPositions.value = currentPositions
}
}
) {
UnderlineTabItem(
text = stringResource(R.string.index_following),
isSelected = pagerState.currentPage == 3,
onClick = {
tabDebouncer {
scope.launch {
// 如果当前在关注标签左边的标签页,立即向右滚动显示新闻标签
if (pagerState.currentPage < 3 && containerWidth.value > 0) {
val newsTab = tabPositions.value.find { it.first == 5 }
if (newsTab != null) {
val (_, newsPosition, newsWidth) = newsTab
val currentScroll = scrollState.value.toFloat()
val scrollToPosition = (newsPosition + newsWidth - containerWidth.value).coerceAtLeast(0f)
if (kotlin.math.abs(scrollToPosition - currentScroll) > 1f) {
scrollState.animateScrollTo(scrollToPosition.toInt())
}
}
}
pagerState.animateScrollToPage(3)
}
}
}
)
}
// 热门标签
Box(
modifier = Modifier.onGloballyPositioned { coordinates ->
val position = with(density) { coordinates.positionInParent().x.toDp().toPx() }
val width = with(density) { coordinates.size.width.toDp().toPx() }
val currentPositions = tabPositions.value.toMutableList()
val existingIndex = currentPositions.indexOfFirst { it.first == 4 }
if (existingIndex == -1) {
currentPositions.add(Triple(4, position, width))
tabPositions.value = currentPositions.sortedBy { it.first }
} else {
currentPositions[existingIndex] = Triple(4, position, width)
tabPositions.value = currentPositions
}
}
) {
UnderlineTabItem(
text = stringResource(R.string.index_hot),
isSelected = pagerState.currentPage == 4,
@@ -174,8 +296,24 @@ fun MomentsList() {
}
}
)
}
} else {
// 热门标签 (游客模式) - 在游客模式下热门标签对应第3页
Box(
modifier = Modifier.onGloballyPositioned { coordinates ->
val position = with(density) { coordinates.positionInParent().x.toDp().toPx() }
val width = with(density) { coordinates.size.width.toDp().toPx() }
val currentPositions = tabPositions.value.toMutableList()
val existingIndex = currentPositions.indexOfFirst { it.first == 3 }
if (existingIndex == -1) {
currentPositions.add(Triple(3, position, width))
tabPositions.value = currentPositions.sortedBy { it.first }
} else {
currentPositions[existingIndex] = Triple(3, position, width)
tabPositions.value = currentPositions
}
}
) {
UnderlineTabItem(
text = stringResource(R.string.index_hot),
isSelected = pagerState.currentPage == 3,
@@ -188,10 +326,26 @@ fun MomentsList() {
}
)
}
}
// 新闻标签 - 在游客模式下对应第4页非游客模式下对应第5页
val newsPageIndex = if (AppStore.isGuest) 4 else 5
Box(
modifier = Modifier.onGloballyPositioned { coordinates ->
val position = with(density) { coordinates.positionInParent().x.toDp().toPx() }
val width = with(density) { coordinates.size.width.toDp().toPx() }
val currentPositions = tabPositions.value.toMutableList()
val existingIndex = currentPositions.indexOfFirst { it.first == newsPageIndex }
if (existingIndex == -1) {
currentPositions.add(Triple(newsPageIndex, position, width))
tabPositions.value = currentPositions.sortedBy { it.first }
} else {
currentPositions[existingIndex] = Triple(newsPageIndex, position, width)
tabPositions.value = currentPositions
}
}
) {
UnderlineTabItem(
text = stringResource(R.string.tab_news),
isSelected = pagerState.currentPage == newsPageIndex,
@@ -204,6 +358,8 @@ fun MomentsList() {
}
)
}
}
}
// 搜索按钮
val lastClickTime = remember { mutableStateOf(0L) }
@@ -224,6 +380,50 @@ fun MomentsList() {
)
}
// 监听页面变化,实现自动滚动
LaunchedEffect(pagerState.currentPage, pagerState.currentPageOffsetFraction) {
val currentPage = pagerState.currentPage
val offsetFraction = pagerState.currentPageOffsetFraction
val prevPage = previousPage.value
// 只在页面切换接近完成时执行(避免滑动过程中频繁触发)
val absOffset = kotlin.math.abs(offsetFraction)
if (absOffset < 0.1f && containerWidth.value > 0) {
// 情况1从关注标签页左边的标签页0,1,2滑动到关注标签页3向右滚动显示新闻标签5
if (currentPage == 3 && prevPage < 3 && !AppStore.isGuest) {
val newsTab = tabPositions.value.find { it.first == 5 }
if (newsTab != null) {
val (_, newsPosition, newsWidth) = newsTab
val currentScroll = scrollState.value.toFloat()
// 计算滚动位置,使新闻标签可见(确保新闻标签的结束位置在可见区域内)
val scrollToPosition = (newsPosition + newsWidth - containerWidth.value).coerceAtLeast(0f)
if (kotlin.math.abs(scrollToPosition - currentScroll) > 1f) {
scrollState.animateScrollTo(scrollToPosition.toInt())
}
}
}
// 情况2从动态标签页右边的标签页3,4,5滑动到动态标签页2向左滚动显示推荐标签0
if (currentPage == 2 && prevPage > 2) {
val recommendTab = tabPositions.value.find { it.first == 0 }
if (recommendTab != null) {
val (_, recommendPosition, _) = recommendTab
val currentScroll = scrollState.value.toFloat()
// 计算滚动位置,使推荐标签可见(确保推荐标签的起始位置在可见区域内)
val scrollToPosition = recommendPosition.coerceAtLeast(0f)
if (kotlin.math.abs(scrollToPosition - currentScroll) > 1f) {
scrollState.animateScrollTo(scrollToPosition.toInt())
}
}
}
// 更新上一次的页面索引
if (currentPage != prevPage) {
previousPage.value = currentPage
}
}
}
HorizontalPager(
state = pagerState,
modifier = Modifier

View File

@@ -310,9 +310,28 @@
<!-- Edit Profile Extras -->
<string name="mbti_type">MBTI</string>
<string name="zodiac">星座</string>
<string name="change_cover">カバーを変更</string>
<string name="personal_intro">自己紹介</string>
<string name="error_nickname_empty">ニックネームは空にできません</string>
<string name="error_nickname_too_short">ニックネームの長さは3文字未満にできません</string>
<string name="error_nickname_too_long">ニックネームの長さは20文字を超えることはできません</string>
<string name="error_bio_too_long">自己紹介の長さは100文字を超えることはできません</string>
<string name="error_load_profile_failed">ユーザープロフィールの読み込みに失敗しました。もう一度お試しください</string>
<string name="save">保存</string>
<string name="choose_mbti">MBTIを選択</string>
<string name="choose_zodiac">星座を選択</string>
<string name="zodiac_aries">牡羊座</string>
<string name="zodiac_taurus">牡牛座</string>
<string name="zodiac_gemini">双子座</string>
<string name="zodiac_cancer">蟹座</string>
<string name="zodiac_leo">獅子座</string>
<string name="zodiac_virgo">乙女座</string>
<string name="zodiac_libra">天秤座</string>
<string name="zodiac_scorpio">蠍座</string>
<string name="zodiac_sagittarius">射手座</string>
<string name="zodiac_capricorn">山羊座</string>
<string name="zodiac_aquarius">水瓶座</string>
<string name="zodiac_pisces">魚座</string>
<!-- Side Menu -->
<string name="scan_qr">QRコードをスキャン</string>

View File

@@ -15,11 +15,11 @@
<string name="posts">帖子</string>
<string name="favourites_upper">收藏</string>
<string name="notifications_upper">消息</string>
<string name="following_upper">关注11</string>
<string name="following_upper">关注</string>
<string name="unfollow_upper">取消关注</string>
<string name="comment_count">%d条评论</string>
<string name="post_comment_hint">快来互动吧...</string>
<string name="follow_upper">关注3</string>
<string name="follow_upper">关注</string>
<string name="follow_upper_had">已关注</string>
<string name="login_upper">登录</string>
<string name="lets_ride_upper">确认</string>
@@ -334,7 +334,7 @@
<string name="group_members_admin">管理员</string>
<string name="group_members_list">群成员(%d</string>
<string name="group_members_send_message">发消息</string>
<string name="group_members_delete_member">Delete Member</string>
<string name="group_members_delete_member">删除成员</string>
<!-- Side Menu -->
<string name="scan_qr">扫一扫</string>