自定义NavigationItem,新增群组创建页面
This commit is contained in:
@@ -17,6 +17,7 @@ open class AppThemeData(
|
|||||||
var loadingText: Color,
|
var loadingText: Color,
|
||||||
var disabledBackground: Color,
|
var disabledBackground: Color,
|
||||||
var background: Color,
|
var background: Color,
|
||||||
|
var secondaryBackground: Color,
|
||||||
var decentBackground: Color,
|
var decentBackground: Color,
|
||||||
var divider: Color,
|
var divider: Color,
|
||||||
var inputBackground: Color,
|
var inputBackground: Color,
|
||||||
@@ -46,6 +47,7 @@ class LightThemeColors : AppThemeData(
|
|||||||
loadingText = Color(0xffffffff),
|
loadingText = Color(0xffffffff),
|
||||||
disabledBackground = Color(0xFFD0D0D0),
|
disabledBackground = Color(0xFFD0D0D0),
|
||||||
background = Color(0xFFFFFFFF),
|
background = Color(0xFFFFFFFF),
|
||||||
|
secondaryBackground = Color(0xFFF7f7f7),
|
||||||
divider = Color(0xFFEbEbEb),
|
divider = Color(0xFFEbEbEb),
|
||||||
inputBackground = Color(0xFFF7f7f7),
|
inputBackground = Color(0xFFF7f7f7),
|
||||||
inputBackground2 = Color(0xFFFFFFFF),
|
inputBackground2 = Color(0xFFFFFFFF),
|
||||||
@@ -76,6 +78,7 @@ class DarkThemeColors : AppThemeData(
|
|||||||
loadingText = Color(0xff000000),
|
loadingText = Color(0xff000000),
|
||||||
disabledBackground = Color(0xFF3A3A3A),
|
disabledBackground = Color(0xFF3A3A3A),
|
||||||
background = Color(0xFF121212),
|
background = Color(0xFF121212),
|
||||||
|
secondaryBackground = Color(0xFF1C1C1C),
|
||||||
divider = Color(0xFF282828),
|
divider = Color(0xFF282828),
|
||||||
inputBackground = Color(0xFF1C1C1C),
|
inputBackground = Color(0xFF1C1C1C),
|
||||||
inputBackground2 = Color(0xFF1C1C1C),
|
inputBackground2 = Color(0xFF1C1C1C),
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import com.aiosman.ravenow.ui.account.AccountEditScreen2
|
|||||||
import com.aiosman.ravenow.ui.account.AccountSetting
|
import com.aiosman.ravenow.ui.account.AccountSetting
|
||||||
import com.aiosman.ravenow.ui.account.ResetPasswordScreen
|
import com.aiosman.ravenow.ui.account.ResetPasswordScreen
|
||||||
import com.aiosman.ravenow.ui.agent.AddAgentScreen
|
import com.aiosman.ravenow.ui.agent.AddAgentScreen
|
||||||
|
import com.aiosman.ravenow.ui.group.CreateGroupChatScreen
|
||||||
import com.aiosman.ravenow.ui.chat.ChatAiScreen
|
import com.aiosman.ravenow.ui.chat.ChatAiScreen
|
||||||
import com.aiosman.ravenow.ui.chat.ChatScreen
|
import com.aiosman.ravenow.ui.chat.ChatScreen
|
||||||
import com.aiosman.ravenow.ui.comment.CommentsScreen
|
import com.aiosman.ravenow.ui.comment.CommentsScreen
|
||||||
@@ -98,6 +99,7 @@ sealed class NavigationRoute(
|
|||||||
data object AccountSetting : NavigationRoute("AccountSetting")
|
data object AccountSetting : NavigationRoute("AccountSetting")
|
||||||
data object AboutScreen : NavigationRoute("AboutScreen")
|
data object AboutScreen : NavigationRoute("AboutScreen")
|
||||||
data object AddAgent : NavigationRoute("AddAgent")
|
data object AddAgent : NavigationRoute("AddAgent")
|
||||||
|
data object CreateGroupChat : NavigationRoute("CreateGroupChat")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -423,6 +425,18 @@ fun NavigationController(
|
|||||||
) {
|
) {
|
||||||
AddAgentScreen()
|
AddAgentScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
composable(
|
||||||
|
route = NavigationRoute.CreateGroupChat.route,
|
||||||
|
enterTransition = {
|
||||||
|
fadeIn(animationSpec = tween(durationMillis = 0))
|
||||||
|
},
|
||||||
|
exitTransition = {
|
||||||
|
fadeOut(animationSpec = tween(durationMillis = 0))
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
CreateGroupChatScreen()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,12 +30,14 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.aiosman.ravenow.AppState
|
||||||
import com.aiosman.ravenow.LocalAppTheme
|
import com.aiosman.ravenow.LocalAppTheme
|
||||||
import com.aiosman.ravenow.LocalNavController
|
import com.aiosman.ravenow.LocalNavController
|
||||||
import com.aiosman.ravenow.R
|
import com.aiosman.ravenow.R
|
||||||
import com.aiosman.ravenow.ui.NavigationRoute
|
import com.aiosman.ravenow.ui.NavigationRoute
|
||||||
import com.aiosman.ravenow.ui.comment.NoticeScreenHeader
|
import com.aiosman.ravenow.ui.comment.NoticeScreenHeader
|
||||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||||
|
import com.aiosman.ravenow.ui.composables.StatusBarMaskLayout
|
||||||
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
||||||
import com.aiosman.ravenow.ui.composables.form.FormTextInput
|
import com.aiosman.ravenow.ui.composables.form.FormTextInput
|
||||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||||
@@ -82,13 +84,18 @@ fun AccountEditScreen2() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
StatusBarMaskLayout(
|
||||||
|
modifier = Modifier.background(color = appColors.background).padding(horizontal = 16.dp),
|
||||||
|
darkIcons = !AppState.darkMode,
|
||||||
|
maskBoxBackgroundColor = appColors.background
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(color = appColors.background),
|
.background(color = appColors.background),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
StatusBarSpacer()
|
//StatusBarSpacer()
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
|
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
|
||||||
) {
|
) {
|
||||||
@@ -184,7 +191,7 @@ fun AccountEditScreen2() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package com.aiosman.ravenow.ui.group
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.aiosman.ravenow.LocalAppTheme
|
||||||
|
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||||
|
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AiAgentListScreen(
|
||||||
|
searchText: String,
|
||||||
|
selectedMemberIds: Set<String> = emptySet(),
|
||||||
|
onMemberSelect: (GroupMember) -> Unit
|
||||||
|
) {
|
||||||
|
val AppColors = LocalAppTheme.current
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
var aiAgents by remember { mutableStateOf<List<GroupMember>>(emptyList()) }
|
||||||
|
var isLoading by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// 加载AI智能体数据
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
isLoading = true
|
||||||
|
aiAgents = CreateGroupChatViewModel.getAiAgents()
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
val filteredAgents = if (searchText.isEmpty()) {
|
||||||
|
aiAgents
|
||||||
|
} else {
|
||||||
|
aiAgents.filter { it.name.contains(searchText, ignoreCase = true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "加载中...",
|
||||||
|
color = AppColors.secondaryText,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentPadding = PaddingValues(16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
items(filteredAgents) { agent ->
|
||||||
|
MemberItem(
|
||||||
|
member = agent,
|
||||||
|
isSelected = selectedMemberIds.contains(agent.id),
|
||||||
|
onSelect = { onMemberSelect(agent) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,397 @@
|
|||||||
|
package com.aiosman.ravenow.ui.group
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
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.graphics.ColorFilter
|
||||||
|
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.text.input.TextFieldValue
|
||||||
|
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.ui.composables.CustomAsyncImage
|
||||||
|
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
||||||
|
import com.aiosman.ravenow.ui.composables.TabItem
|
||||||
|
import com.aiosman.ravenow.ui.composables.TabSpacer
|
||||||
|
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||||
|
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
// 成员数据类
|
||||||
|
data class GroupMember(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val avatar: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
@Composable
|
||||||
|
fun CreateGroupChatScreen() {
|
||||||
|
val AppColors = LocalAppTheme.current
|
||||||
|
val navController = LocalNavController.current
|
||||||
|
val systemUiController = rememberSystemUiController()
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
// 状态管理
|
||||||
|
var groupName by remember { mutableStateOf(TextFieldValue("")) }
|
||||||
|
var searchText by remember { mutableStateOf(TextFieldValue("")) }
|
||||||
|
var selectedMembers by remember { mutableStateOf(listOf<GroupMember>()) }
|
||||||
|
var selectedMemberIds by remember { mutableStateOf<Set<String>>(emptySet()) }
|
||||||
|
var pagerState = rememberPagerState(pageCount = { 2 })
|
||||||
|
var scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
// 清除错误信息
|
||||||
|
LaunchedEffect(groupName.text, searchText.text) {
|
||||||
|
if (CreateGroupChatViewModel.errorMessage != null) {
|
||||||
|
CreateGroupChatViewModel.clearError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听页面切换,清除当前tab的选中状态(但保留已选成员列表)
|
||||||
|
LaunchedEffect(pagerState.currentPage) {
|
||||||
|
// 不清除selectedMemberIds,因为我们需要保持跨tab的选中状态
|
||||||
|
// 这样用户可以在AI智能体和朋友之间切换,选中的状态会保持
|
||||||
|
}
|
||||||
|
|
||||||
|
val navigationBarPaddings = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
systemUiController.setNavigationBarColor(Color.Transparent)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(bottom = navigationBarPaddings)
|
||||||
|
) {
|
||||||
|
// 错误提示
|
||||||
|
CreateGroupChatViewModel.errorMessage?.let { error ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(AppColors.error.copy(alpha = 0.1f))
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = error,
|
||||||
|
color = AppColors.error,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StatusBarSpacer()
|
||||||
|
|
||||||
|
// 头部
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
// 返回按钮
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.rider_pro_back_icon),
|
||||||
|
contentDescription = "back",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(24.dp)
|
||||||
|
.noRippleClickable {
|
||||||
|
navController.popBackStack()
|
||||||
|
},
|
||||||
|
colorFilter = ColorFilter.tint(AppColors.text)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.create_group_chat),
|
||||||
|
fontSize = 17.sp,
|
||||||
|
fontWeight = FontWeight.W700,
|
||||||
|
color = AppColors.text,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(start = 16.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 一键创建按钮
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.noRippleClickable {
|
||||||
|
// 一键创建逻辑
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.rider_pro_new_post_add_pic),
|
||||||
|
contentDescription = "quick create",
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
colorFilter = ColorFilter.tint(AppColors.main)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.quick_create),
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = AppColors.main
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索栏
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
.background(
|
||||||
|
color = AppColors.inputBackground,
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
)
|
||||||
|
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||||
|
) {
|
||||||
|
BasicTextField(
|
||||||
|
value = searchText,
|
||||||
|
onValueChange = { searchText = it },
|
||||||
|
textStyle = androidx.compose.ui.text.TextStyle(
|
||||||
|
color = AppColors.text,
|
||||||
|
fontSize = 14.sp
|
||||||
|
),
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
decorationBox = { innerTextField ->
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.rider_pro_nav_search),
|
||||||
|
contentDescription = "search",
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
colorFilter = ColorFilter.tint(AppColors.secondaryText)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
if (searchText.text.isEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = "搜索",
|
||||||
|
color = AppColors.secondaryText,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
innerTextField()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 群聊名称输入框
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "群聊名称",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = AppColors.text,
|
||||||
|
modifier = Modifier.width(80.dp)
|
||||||
|
)
|
||||||
|
BasicTextField(
|
||||||
|
value = groupName,
|
||||||
|
onValueChange = { groupName = it },
|
||||||
|
textStyle = androidx.compose.ui.text.TextStyle(
|
||||||
|
color = AppColors.text,
|
||||||
|
fontSize = 14.sp
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.background(
|
||||||
|
color = AppColors.inputBackground,
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
)
|
||||||
|
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已选成员列表
|
||||||
|
if (selectedMembers.isNotEmpty()) {
|
||||||
|
// 显示选中成员数量
|
||||||
|
Text(
|
||||||
|
text = "已选择 ${selectedMembers.size} 个成员",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = AppColors.secondaryText,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp)
|
||||||
|
)
|
||||||
|
LazyRow(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
items(selectedMembers) { member ->
|
||||||
|
Box {
|
||||||
|
CustomAsyncImage(
|
||||||
|
context = context,
|
||||||
|
imageUrl = member.avatar,
|
||||||
|
contentDescription = member.name,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 删除按钮
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(20.dp)
|
||||||
|
.background(AppColors.error, CircleShape)
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
.noRippleClickable {
|
||||||
|
// 删除成员时同时更新选中状态
|
||||||
|
val (newSelectedMemberIds, newSelectedMembers) = CreateGroupChatViewModel.removeSelectedMember(
|
||||||
|
member, selectedMemberIds, selectedMembers
|
||||||
|
)
|
||||||
|
selectedMemberIds = newSelectedMemberIds
|
||||||
|
selectedMembers = newSelectedMembers
|
||||||
|
},
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "×",
|
||||||
|
color = Color.White,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab切换
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
horizontalArrangement = Arrangement.Start,
|
||||||
|
verticalAlignment = Alignment.Bottom
|
||||||
|
) {
|
||||||
|
TabItem(
|
||||||
|
text = stringResource(R.string.chat_ai),
|
||||||
|
isSelected = pagerState.currentPage == 0,
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
pagerState.animateScrollToPage(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
TabSpacer()
|
||||||
|
TabItem(
|
||||||
|
text = stringResource(R.string.chat_friend),
|
||||||
|
isSelected = pagerState.currentPage == 1,
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
pagerState.animateScrollToPage(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内容区域
|
||||||
|
HorizontalPager(
|
||||||
|
state = pagerState,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f)
|
||||||
|
) {
|
||||||
|
when (it) {
|
||||||
|
0 -> {
|
||||||
|
// AI智能体列表
|
||||||
|
AiAgentListScreen(
|
||||||
|
searchText = searchText.text,
|
||||||
|
selectedMemberIds = selectedMemberIds,
|
||||||
|
onMemberSelect = { member ->
|
||||||
|
val (newSelectedMemberIds, newSelectedMembers) = CreateGroupChatViewModel.toggleMemberSelection(
|
||||||
|
member, selectedMemberIds, selectedMembers
|
||||||
|
)
|
||||||
|
selectedMemberIds = newSelectedMemberIds
|
||||||
|
selectedMembers = newSelectedMembers
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
|
// 朋友列表
|
||||||
|
FriendListScreen(
|
||||||
|
searchText = searchText.text,
|
||||||
|
selectedMemberIds = selectedMemberIds,
|
||||||
|
onMemberSelect = { member ->
|
||||||
|
val (newSelectedMemberIds, newSelectedMembers) = CreateGroupChatViewModel.toggleMemberSelection(
|
||||||
|
member, selectedMemberIds, selectedMembers
|
||||||
|
)
|
||||||
|
selectedMemberIds = newSelectedMemberIds
|
||||||
|
selectedMembers = newSelectedMembers
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建群聊按钮
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
// 创建群聊逻辑
|
||||||
|
if (groupName.text.isNotEmpty() && selectedMembers.isNotEmpty()) {
|
||||||
|
scope.launch {
|
||||||
|
val success = CreateGroupChatViewModel.createGroupChat(
|
||||||
|
groupName = groupName.text,
|
||||||
|
memberIds = selectedMembers.map { it.id },
|
||||||
|
context = context
|
||||||
|
)
|
||||||
|
if (success) {
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 16.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = AppColors.main,
|
||||||
|
contentColor = AppColors.mainText
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
enabled = groupName.text.isNotEmpty() && selectedMembers.isNotEmpty() && !CreateGroupChatViewModel.isLoading
|
||||||
|
) {
|
||||||
|
if (CreateGroupChatViewModel.isLoading) {
|
||||||
|
Text(
|
||||||
|
text = "创建中...",
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.W600
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.create_group_chat),
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.W600
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package com.aiosman.ravenow.ui.group
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.icu.util.Calendar
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.paging.PagingData
|
||||||
|
import androidx.paging.map
|
||||||
|
import com.aiosman.ravenow.AppState
|
||||||
|
import com.aiosman.ravenow.ConstVars
|
||||||
|
import com.aiosman.ravenow.data.AccountNotice
|
||||||
|
import com.aiosman.ravenow.data.AccountService
|
||||||
|
import com.aiosman.ravenow.data.AccountServiceImpl
|
||||||
|
import com.aiosman.ravenow.data.UserService
|
||||||
|
import com.aiosman.ravenow.data.UserServiceImpl
|
||||||
|
import com.aiosman.ravenow.entity.CommentEntity
|
||||||
|
import com.aiosman.ravenow.exp.formatChatTime
|
||||||
|
import com.aiosman.ravenow.ui.NavigationRoute
|
||||||
|
import com.aiosman.ravenow.ui.navigateToChat
|
||||||
|
import com.aiosman.ravenow.utils.TrtcHelper
|
||||||
|
import com.tencent.imsdk.v2.V2TIMConversation
|
||||||
|
import com.tencent.imsdk.v2.V2TIMConversationResult
|
||||||
|
import com.tencent.imsdk.v2.V2TIMManager
|
||||||
|
import com.tencent.imsdk.v2.V2TIMMessage
|
||||||
|
import com.tencent.imsdk.v2.V2TIMValueCallback
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
object CreateGroupChatViewModel : ViewModel() {
|
||||||
|
val accountService: AccountService = AccountServiceImpl()
|
||||||
|
val userService: UserService = UserServiceImpl()
|
||||||
|
|
||||||
|
// 状态管理
|
||||||
|
var isLoading by mutableStateOf(false)
|
||||||
|
var errorMessage by mutableStateOf<String?>(null)
|
||||||
|
|
||||||
|
// 获取AI智能体列表
|
||||||
|
suspend fun getAiAgents(): List<GroupMember> {
|
||||||
|
return try {
|
||||||
|
// TODO: 从API获取AI智能体列表
|
||||||
|
listOf(
|
||||||
|
GroupMember("1", "AI助手", "https://example.com/avatar1.jpg"),
|
||||||
|
GroupMember("2", "智能客服", "https://example.com/avatar2.jpg"),
|
||||||
|
GroupMember("3", "翻译助手", "https://example.com/avatar3.jpg"),
|
||||||
|
GroupMember("4", "写作助手", "https://example.com/avatar4.jpg"),
|
||||||
|
GroupMember("5", "编程助手", "https://example.com/avatar5.jpg"),
|
||||||
|
GroupMember("6", "设计助手", "https://example.com/avatar6.jpg")
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
errorMessage = "获取AI智能体列表失败: ${e.message}"
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取朋友列表
|
||||||
|
suspend fun getFriends(): List<GroupMember> {
|
||||||
|
return try {
|
||||||
|
// TODO: 从API获取朋友列表
|
||||||
|
listOf(
|
||||||
|
GroupMember("7", "张三", "https://example.com/avatar7.jpg"),
|
||||||
|
GroupMember("8", "李四", "https://example.com/avatar8.jpg"),
|
||||||
|
GroupMember("9", "王五", "https://example.com/avatar9.jpg"),
|
||||||
|
GroupMember("10", "赵六", "https://example.com/avatar10.jpg"),
|
||||||
|
GroupMember("11", "钱七", "https://example.com/avatar11.jpg"),
|
||||||
|
GroupMember("12", "孙八", "https://example.com/avatar12.jpg")
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
errorMessage = "获取朋友列表失败: ${e.message}"
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建群聊
|
||||||
|
suspend fun createGroupChat(
|
||||||
|
groupName: String,
|
||||||
|
memberIds: List<String>,
|
||||||
|
context: Context
|
||||||
|
): Boolean {
|
||||||
|
return try {
|
||||||
|
isLoading = true
|
||||||
|
// TODO: 实现创建群聊的API调用
|
||||||
|
// 这里应该调用实际的API来创建群聊
|
||||||
|
|
||||||
|
// 模拟API调用延迟
|
||||||
|
kotlinx.coroutines.delay(1000)
|
||||||
|
|
||||||
|
isLoading = false
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
isLoading = false
|
||||||
|
errorMessage = "创建群聊失败: ${e.message}"
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除错误信息
|
||||||
|
fun clearError() {
|
||||||
|
errorMessage = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加成员到选中列表
|
||||||
|
fun addSelectedMember(member: GroupMember, selectedMemberIds: Set<String>, selectedMembers: List<GroupMember>): Pair<Set<String>, List<GroupMember>> {
|
||||||
|
val newSelectedMemberIds = selectedMemberIds + member.id
|
||||||
|
val newSelectedMembers = if (selectedMembers.none { it.id == member.id }) {
|
||||||
|
selectedMembers + member
|
||||||
|
} else {
|
||||||
|
selectedMembers
|
||||||
|
}
|
||||||
|
return Pair(newSelectedMemberIds, newSelectedMembers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从选中列表移除成员
|
||||||
|
fun removeSelectedMember(member: GroupMember, selectedMemberIds: Set<String>, selectedMembers: List<GroupMember>): Pair<Set<String>, List<GroupMember>> {
|
||||||
|
val newSelectedMemberIds = selectedMemberIds - member.id
|
||||||
|
val newSelectedMembers = selectedMembers.filter { it.id != member.id }
|
||||||
|
return Pair(newSelectedMemberIds, newSelectedMembers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换成员选中状态
|
||||||
|
fun toggleMemberSelection(member: GroupMember, selectedMemberIds: Set<String>, selectedMembers: List<GroupMember>): Pair<Set<String>, List<GroupMember>> {
|
||||||
|
return if (selectedMemberIds.contains(member.id)) {
|
||||||
|
removeSelectedMember(member, selectedMemberIds, selectedMembers)
|
||||||
|
} else {
|
||||||
|
addSelectedMember(member, selectedMemberIds, selectedMembers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package com.aiosman.ravenow.ui.group
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.aiosman.ravenow.LocalAppTheme
|
||||||
|
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||||
|
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FriendListScreen(
|
||||||
|
searchText: String,
|
||||||
|
selectedMemberIds: Set<String> = emptySet(),
|
||||||
|
onMemberSelect: (GroupMember) -> Unit
|
||||||
|
) {
|
||||||
|
val AppColors = LocalAppTheme.current
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
var friends by remember { mutableStateOf<List<GroupMember>>(emptyList()) }
|
||||||
|
var isLoading by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// 加载朋友数据
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
isLoading = true
|
||||||
|
friends = CreateGroupChatViewModel.getFriends()
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
val filteredFriends = if (searchText.isEmpty()) {
|
||||||
|
friends
|
||||||
|
} else {
|
||||||
|
friends.filter { it.name.contains(searchText, ignoreCase = true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "加载中...",
|
||||||
|
color = AppColors.secondaryText,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentPadding = PaddingValues(16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
items(filteredFriends) { friend ->
|
||||||
|
MemberItem(
|
||||||
|
member = friend,
|
||||||
|
isSelected = selectedMemberIds.contains(friend.id),
|
||||||
|
onSelect = { onMemberSelect(friend) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
65
app/src/main/java/com/aiosman/ravenow/ui/group/MemberItem.kt
Normal file
65
app/src/main/java/com/aiosman/ravenow/ui/group/MemberItem.kt
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package com.aiosman.ravenow.ui.group
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material3.Checkbox
|
||||||
|
import androidx.compose.material3.CheckboxDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
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.ui.composables.CustomAsyncImage
|
||||||
|
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MemberItem(
|
||||||
|
member: GroupMember,
|
||||||
|
isSelected: Boolean = false,
|
||||||
|
onSelect: () -> Unit
|
||||||
|
) {
|
||||||
|
val AppColors = LocalAppTheme.current
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.noRippleClickable { onSelect() }
|
||||||
|
.padding(vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
CustomAsyncImage(
|
||||||
|
context = context,
|
||||||
|
imageUrl = member.avatar,
|
||||||
|
contentDescription = member.name,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(40.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = member.name,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
color = AppColors.text,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
|
Checkbox(
|
||||||
|
checked = isSelected,
|
||||||
|
onCheckedChange = { onSelect() },
|
||||||
|
colors = CheckboxDefaults.colors(
|
||||||
|
checkedColor = AppColors.main,
|
||||||
|
uncheckedColor = AppColors.secondaryText
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,6 +52,7 @@ import androidx.compose.ui.platform.LocalDensity
|
|||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
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.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
@@ -259,7 +260,7 @@ fun IndexScreen() {
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
NavigationBar(
|
NavigationBar(
|
||||||
modifier = Modifier.height(56.dp + navigationBarHeight),
|
modifier = Modifier.height(72.dp + navigationBarHeight),
|
||||||
containerColor = AppColors.background
|
containerColor = AppColors.background
|
||||||
) {
|
) {
|
||||||
item.forEachIndexed { idx, it ->
|
item.forEachIndexed { idx, it ->
|
||||||
@@ -268,6 +269,7 @@ fun IndexScreen() {
|
|||||||
targetValue = if (isSelected) AppColors.brandColorsColor else AppColors.text,
|
targetValue = if (isSelected) AppColors.brandColorsColor else AppColors.text,
|
||||||
animationSpec = tween(durationMillis = 250), label = ""
|
animationSpec = tween(durationMillis = 250), label = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
modifier = Modifier.padding(top = 6.dp),
|
modifier = Modifier.padding(top = 6.dp),
|
||||||
selected = isSelected,
|
selected = isSelected,
|
||||||
@@ -282,58 +284,65 @@ fun IndexScreen() {
|
|||||||
}
|
}
|
||||||
model.tabIndex = idx
|
model.tabIndex = idx
|
||||||
},
|
},
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
|
||||||
colors = NavigationBarItemColors(
|
colors = NavigationBarItemColors(
|
||||||
selectedTextColor = Color.Red,
|
selectedIconColor = Color.Transparent,
|
||||||
|
selectedTextColor = Color.Transparent,
|
||||||
selectedIndicatorColor = Color.Transparent,
|
selectedIndicatorColor = Color.Transparent,
|
||||||
unselectedTextColor = Color.Red,
|
unselectedIconColor = Color.Transparent,
|
||||||
disabledIconColor = Color.Red,
|
unselectedTextColor = Color.Transparent,
|
||||||
disabledTextColor = Color.Red,
|
disabledIconColor = Color.Transparent,
|
||||||
selectedIconColor = iconTint,
|
disabledTextColor = Color.Transparent
|
||||||
unselectedIconColor = iconTint,
|
|
||||||
),
|
),
|
||||||
icon = {
|
icon = {
|
||||||
// 特殊处理中间的Add按钮,只显示图标并放大
|
Column(
|
||||||
if (it.route == NavigationItem.Add.route) {
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
Icon(
|
verticalArrangement = Arrangement.Center
|
||||||
modifier = Modifier.size(32.dp),
|
) {
|
||||||
imageVector = if (isSelected) it.selectedIcon() else it.icon(),
|
if (it.route == NavigationItem.Add.route) {
|
||||||
contentDescription = null,
|
// Add按钮:只显示大图标
|
||||||
tint = AppColors.text
|
Image(
|
||||||
)
|
painter = painterResource(if (isSelected) it.selectedIcon() else it.icon()),
|
||||||
} else {
|
contentDescription = it.label(),
|
||||||
Box(
|
modifier = Modifier.size(32.dp),
|
||||||
modifier = Modifier
|
colorFilter = if (!isSelected) ColorFilter.tint(AppColors.text) else null
|
||||||
.width(46.dp)
|
)
|
||||||
.height(30.dp)
|
} else {
|
||||||
.background(
|
// 其他按钮:图标+文字
|
||||||
color = if (isSelected) AppColors.brandColorsColor.copy(alpha = 0.1f) else Color.Transparent ,
|
Box(
|
||||||
shape = RoundedCornerShape(10.dp)
|
modifier = Modifier
|
||||||
),
|
.width(48.dp)
|
||||||
contentAlignment = Alignment.Center
|
.height(32.dp)
|
||||||
) {
|
.background(
|
||||||
Icon(
|
color = if (isSelected) AppColors.brandColorsColor.copy(alpha = 0.15f) else Color.Transparent,
|
||||||
modifier = Modifier.size(24.dp),
|
shape = RoundedCornerShape(12.dp)
|
||||||
imageVector = if (isSelected) it.selectedIcon() else it.icon(),
|
),
|
||||||
contentDescription = null,
|
contentAlignment = Alignment.Center
|
||||||
tint = iconTint
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(if (isSelected) it.selectedIcon() else it.icon()),
|
||||||
|
contentDescription = it.label(),
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
colorFilter = if (!isSelected) ColorFilter.tint(AppColors.text) else null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文字标签,可控制间距
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = it.label(),
|
||||||
|
fontSize = 10.sp,
|
||||||
|
color = if (isSelected) AppColors.brandColorsColor else AppColors.text,
|
||||||
|
fontWeight = if (isSelected) FontWeight.W600 else FontWeight.Normal
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
label = {
|
label = {
|
||||||
// Add按钮不显示文字标签
|
// 不显示默认标签
|
||||||
if (it.route != NavigationItem.Add.route) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.padding(0.dp),
|
|
||||||
text = it.label(),
|
|
||||||
fontSize = 9.sp,
|
|
||||||
color = if (isSelected) AppColors.brandColorsColor else AppColors.text,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -344,7 +353,7 @@ fun IndexScreen() {
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(AppColors.background)
|
.background(AppColors.background)
|
||||||
.padding(0.dp),
|
.padding(0.dp),
|
||||||
beyondBoundsPageCount = 5,
|
beyondBoundsPageCount = 4,
|
||||||
userScrollEnabled = false
|
userScrollEnabled = false
|
||||||
) { page ->
|
) { page ->
|
||||||
when (page) {
|
when (page) {
|
||||||
|
|||||||
@@ -3,57 +3,46 @@ package com.aiosman.ravenow.ui.index
|
|||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.res.vectorResource
|
import androidx.compose.ui.res.vectorResource
|
||||||
import com.aiosman.ravenow.R
|
import com.aiosman.ravenow.R
|
||||||
|
|
||||||
sealed class NavigationItem(
|
sealed class NavigationItem(
|
||||||
val route: String,
|
val route: String,
|
||||||
val icon: @Composable () -> ImageVector,
|
val icon: @Composable () -> Int,
|
||||||
val selectedIcon: @Composable () -> ImageVector = icon,
|
val selectedIcon: @Composable () -> Int = icon,
|
||||||
val label: @Composable () -> String
|
val label: @Composable () -> String
|
||||||
) {
|
) {
|
||||||
|
|
||||||
data object Home : NavigationItem("Home",
|
data object Home : NavigationItem("Home",
|
||||||
icon = { ImageVector.vectorResource(R.drawable.rider_pro_nav_home) },
|
icon = { R.drawable.rider_pro_nav_home },
|
||||||
selectedIcon = { ImageVector.vectorResource(R.drawable.rider_pro_nav_home_hl) },
|
selectedIcon = { R.mipmap.rider_pro_nav_home_hl },
|
||||||
label = { stringResource(R.string.main_home) }
|
label = { stringResource(R.string.main_home) }
|
||||||
)
|
)
|
||||||
|
|
||||||
data object Street : NavigationItem("Street",
|
data object Ai : NavigationItem("Ai",
|
||||||
icon = { ImageVector.vectorResource(R.drawable.rider_pro_location) },
|
icon = { R.drawable.rider_pro_nav_ai },
|
||||||
selectedIcon = { ImageVector.vectorResource(R.drawable.rider_pro_location_filed) },
|
selectedIcon = { R.mipmap.rider_pro_nav_ai_hl },
|
||||||
label = { stringResource(R.string.main_home) }
|
label = { stringResource(R.string.main_home) }
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
data object Add : NavigationItem("Add",
|
data object Add : NavigationItem("Add",
|
||||||
icon = { ImageVector.vectorResource(R.drawable.ic_nav_add) },
|
icon = { R.drawable.ic_nav_add },
|
||||||
selectedIcon = { ImageVector.vectorResource(R.drawable.ic_nav_add) },
|
selectedIcon = { R.drawable.ic_nav_add },
|
||||||
label = { "" }
|
|
||||||
)
|
|
||||||
|
|
||||||
data object Message : NavigationItem("Message",
|
|
||||||
icon = { ImageVector.vectorResource(R.drawable.rider_pro_video_outline) },
|
|
||||||
selectedIcon = { ImageVector.vectorResource(R.drawable.rider_pro_video) },
|
|
||||||
label = { stringResource(R.string.main_home) }
|
label = { stringResource(R.string.main_home) }
|
||||||
)
|
)
|
||||||
|
|
||||||
data object Notification : NavigationItem("Notification",
|
data object Notification : NavigationItem("Notification",
|
||||||
icon = { ImageVector.vectorResource(R.drawable.rider_pro_nav_notification)},
|
icon = { R.drawable.rider_pro_nav_notification },
|
||||||
selectedIcon = { ImageVector.vectorResource(R.drawable.rider_pro_nav_notification) },
|
selectedIcon = { R.mipmap.rider_pro_nav_message_hl },
|
||||||
label = { stringResource(R.string.main_message) }
|
label = { stringResource(R.string.main_message) }
|
||||||
)
|
)
|
||||||
|
|
||||||
data object Profile : NavigationItem("Profile",
|
data object Profile : NavigationItem("Profile",
|
||||||
icon = { ImageVector.vectorResource(R.drawable.rider_pro_nav_profile) },
|
icon = { R.drawable.rider_pro_nav_profile },
|
||||||
selectedIcon = { ImageVector.vectorResource(R.drawable.rider_pro_nav_profile_hl) },
|
selectedIcon = { R.mipmap.rider_pro_nav_profile_hl },
|
||||||
label = { stringResource(R.string.main_profile) }
|
label = { stringResource(R.string.main_profile) }
|
||||||
)
|
)
|
||||||
|
|
||||||
data object Ai : NavigationItem("Ai",
|
|
||||||
icon = { ImageVector.vectorResource(R.drawable.rider_pro_nav_search) },
|
|
||||||
selectedIcon = { ImageVector.vectorResource(R.drawable.rider_pro_nav_search_hl) },
|
|
||||||
label = { stringResource(R.string.main_ai) }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ fun NotificationsScreen() {
|
|||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val systemUiController = rememberSystemUiController()
|
val systemUiController = rememberSystemUiController()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var pagerState = rememberPagerState (pageCount = { 4 })
|
var pagerState = rememberPagerState (pageCount = { 3 })
|
||||||
var scope = rememberCoroutineScope()
|
var scope = rememberCoroutineScope()
|
||||||
val state = rememberPullRefreshState(MessageListViewModel.isLoading, onRefresh = {
|
val state = rememberPullRefreshState(MessageListViewModel.isLoading, onRefresh = {
|
||||||
MessageListViewModel.viewModelScope.launch {
|
MessageListViewModel.viewModelScope.launch {
|
||||||
@@ -143,7 +143,7 @@ fun NotificationsScreen() {
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(24.dp)
|
.size(24.dp)
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
|
navController.navigate(NavigationRoute.CreateGroupChat.route)
|
||||||
},
|
},
|
||||||
colorFilter = ColorFilter.tint(AppColors.text)
|
colorFilter = ColorFilter.tint(AppColors.text)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.aiosman.ravenow.ui.index.tabs.moment
|
package com.aiosman.ravenow.ui.index.tabs.moment
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -31,6 +32,7 @@ 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.draw.clip
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.res.vectorResource
|
import androidx.compose.ui.res.vectorResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
@@ -95,13 +97,16 @@ fun MomentsList() {
|
|||||||
fontWeight = FontWeight.W600)
|
fontWeight = FontWeight.W600)
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
Box(
|
Image(
|
||||||
modifier = Modifier
|
painter = painterResource(
|
||||||
.width(34.dp)
|
if (pagerState.currentPage == 0) R.mipmap.tab_indicator_selected
|
||||||
.height(4.dp)
|
else R.drawable.tab_indicator_unselected
|
||||||
.clip(RoundedCornerShape(16.dp))
|
),
|
||||||
.background(if (pagerState.currentPage == 0) AppColors.brandColorsColor else AppColors.background)
|
contentDescription = "tab indicator",
|
||||||
)
|
modifier = Modifier
|
||||||
|
.width(34.dp)
|
||||||
|
.height(4.dp)
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
@@ -122,13 +127,16 @@ fun MomentsList() {
|
|||||||
fontWeight = FontWeight.W600)
|
fontWeight = FontWeight.W600)
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
Box(
|
Image(
|
||||||
modifier = Modifier
|
painter = painterResource(
|
||||||
.width(34.dp)
|
if (pagerState.currentPage == 1) R.mipmap.tab_indicator_selected
|
||||||
.height(4.dp)
|
else R.drawable.tab_indicator_unselected
|
||||||
.clip(RoundedCornerShape(16.dp))
|
),
|
||||||
.background(if (pagerState.currentPage == 1) AppColors.brandColorsColor else AppColors.background)
|
contentDescription = "tab indicator",
|
||||||
)
|
modifier = Modifier
|
||||||
|
.width(34.dp)
|
||||||
|
.height(4.dp)
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
//热门tab
|
//热门tab
|
||||||
@@ -150,12 +158,15 @@ fun MomentsList() {
|
|||||||
fontWeight = FontWeight.W600)
|
fontWeight = FontWeight.W600)
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
Box(
|
Image(
|
||||||
|
painter = painterResource(
|
||||||
|
if (pagerState.currentPage == 2) R.mipmap.tab_indicator_selected
|
||||||
|
else R.drawable.tab_indicator_unselected
|
||||||
|
),
|
||||||
|
contentDescription = "tab indicator",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(34.dp)
|
.width(34.dp)
|
||||||
.height(4.dp)
|
.height(4.dp)
|
||||||
.clip(RoundedCornerShape(16.dp))
|
|
||||||
.background(if (pagerState.currentPage == 2) AppColors.brandColorsColor else AppColors.background)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
35
app/src/main/res/drawable/rider_pro_nav_message.xml
Normal file
35
app/src/main/res/drawable/rider_pro_nav_message.xml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<group
|
||||||
|
android:translateX="-351"
|
||||||
|
android:translateY="-797">
|
||||||
|
<group
|
||||||
|
android:translateY="791">
|
||||||
|
<group
|
||||||
|
android:translateX="351"
|
||||||
|
android:translateY="6">
|
||||||
|
<path
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:pathData="M 0 0 L 24 0 L 24 24 L 0 24 Z" />
|
||||||
|
<path
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeWidth="1.93476923"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:pathData="M 12 3 C 14.2091389993 3 16 4.79086100068 16 7 C 16 9.20913899932 14.2091389993 11 12 11 C 9.79086100068 11 8 9.20913899932 8 7 C 8 4.79086100068 9.79086100068 3 12 3 Z" />
|
||||||
|
<path
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeWidth="1.93476923"
|
||||||
|
android:pathData="M12,14 C16.418278,14 20,16.0147186 20,18.5 C20,19.3079805 19.6214332,20.0662253 18.9586285,20.7216744 C18.5467837,20.900999 18.0920303,21 17.6141538,21 L6.38584615,21 C5.90796974,21 5.45321625,20.900999 5.04097677,20.7223879 C4.37856678,20.0662253 4,19.3079805 4,18.5 C4,16.0147186 7.581722,14 12,14 Z" />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</vector>
|
||||||
@@ -1,20 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path
|
|
||||||
android:pathData="M12,9m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"
|
<group
|
||||||
android:strokeWidth="1.93476923"
|
android:translateX="-351"
|
||||||
android:fillColor="#00000000"
|
android:translateY="-797">
|
||||||
android:strokeColor="#FFFFFF"
|
<group
|
||||||
android:fillType="evenOdd"/>
|
android:translateY="791">
|
||||||
<path
|
<group
|
||||||
android:pathData="M2.906,20.25C4.782,17.001 8.248,14.999 12,14.999C15.752,14.999 19.218,17.001 21.094,20.25"
|
android:translateX="351"
|
||||||
android:strokeLineJoin="round"
|
android:translateY="6">
|
||||||
android:strokeWidth="1.93476923"
|
<path
|
||||||
android:fillColor="#00000000"
|
android:fillType="evenOdd"
|
||||||
android:strokeColor="#FFFFFF"
|
android:strokeWidth="1"
|
||||||
android:fillType="evenOdd"
|
android:pathData="M 0 0 L 24 0 L 24 24 L 0 24 Z" />
|
||||||
android:strokeLineCap="round"/>
|
<path
|
||||||
</vector>
|
android:fillType="evenOdd"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeWidth="1.93476923"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:pathData="M 12 3 C 14.2091389993 3 16 4.79086100068 16 7 C 16 9.20913899932 14.2091389993 11 12 11 C 9.79086100068 11 8 9.20913899932 8 7 C 8 4.79086100068 9.79086100068 3 12 3 Z" />
|
||||||
|
<path
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:strokeColor="#000000"
|
||||||
|
android:strokeWidth="1.93476923"
|
||||||
|
android:pathData="M12,14 C16.418278,14 20,16.0147186 20,18.5 C20,19.3079805 19.6214332,20.0662253 18.9586285,20.7216744 C18.5467837,20.900999 18.0920303,21 17.6141538,21 L6.38584615,21 C5.90796974,21 5.45321625,20.900999 5.04097677,20.7223879 C4.37856678,20.0662253 4,19.3079805 4,18.5 C4,16.0147186 7.581722,14 12,14 Z" />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</vector>
|
||||||
11
app/src/main/res/drawable/tab_indicator_unselected.xml
Normal file
11
app/src/main/res/drawable/tab_indicator_unselected.xml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="34dp"
|
||||||
|
android:height="4dp"
|
||||||
|
android:viewportWidth="34"
|
||||||
|
android:viewportHeight="4">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,2C0,0.895 0.895,0 2,0L32,0C33.105,0 34,0.895 34,2C34,3.105 33.105,4 32,4L2,4C0.895,4 0,3.105 0,2Z"/>
|
||||||
|
</vector>
|
||||||
BIN
app/src/main/res/mipmap-xhdpi/tab_indicator_selected.png
Normal file
BIN
app/src/main/res/mipmap-xhdpi/tab_indicator_selected.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 650 B |
@@ -162,5 +162,9 @@
|
|||||||
<string name="friend_chat_empty_subtitle">开始与朋友对话吧</string>
|
<string name="friend_chat_empty_subtitle">开始与朋友对话吧</string>
|
||||||
<string name="friend_chat_me_prefix">我: </string>
|
<string name="friend_chat_me_prefix">我: </string>
|
||||||
<string name="friend_chat_load_failed">加载失败</string>
|
<string name="friend_chat_load_failed">加载失败</string>
|
||||||
|
<string name="create_group_chat">创建群聊</string>
|
||||||
|
<string name="quick_create">一键创建</string>
|
||||||
|
<string name="group_name">群聊名称</string>
|
||||||
|
<string name="search_placeholder">搜索</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -158,5 +158,9 @@
|
|||||||
<string name="friend_chat_empty_subtitle">开始与朋友对话吧</string>
|
<string name="friend_chat_empty_subtitle">开始与朋友对话吧</string>
|
||||||
<string name="friend_chat_me_prefix">我: </string>
|
<string name="friend_chat_me_prefix">我: </string>
|
||||||
<string name="friend_chat_load_failed">加载失败</string>
|
<string name="friend_chat_load_failed">加载失败</string>
|
||||||
|
<string name="create_group_chat">Create Group Chat</string>
|
||||||
|
<string name="quick_create">Quick Create</string>
|
||||||
|
<string name="group_name">Group Name</string>
|
||||||
|
<string name="search_placeholder">Search</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user