记忆管理功能实现

This commit is contained in:
2025-11-04 18:28:24 +08:00
parent 9262288bc9
commit cff6b78c30
8 changed files with 1121 additions and 95 deletions

View File

@@ -7,19 +7,27 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.BottomSheetDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchDefaults import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.* import androidx.compose.runtime.*
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.draw.clip
import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalContext 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.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -37,6 +45,7 @@ import com.aiosman.ravenow.ui.index.NavItem
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun GroupChatInfoScreen(groupId: String) { fun GroupChatInfoScreen(groupId: String) {
val navController = LocalNavController.current val navController = LocalNavController.current
@@ -52,6 +61,11 @@ fun GroupChatInfoScreen(groupId: String) {
} }
) )
var showAddMemoryDialog by remember { mutableStateOf(false) }
var showMemoryManageDialog by remember { mutableStateOf(false) }
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val memoryManageSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -67,7 +81,7 @@ fun GroupChatInfoScreen(groupId: String) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 16.dp, horizontal = 16.dp), .padding(vertical = 12.dp, horizontal = 12.dp),
horizontalArrangement = Arrangement.Start, horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
@@ -81,26 +95,25 @@ fun GroupChatInfoScreen(groupId: String) {
contentDescription = null, contentDescription = null,
colorFilter = ColorFilter.tint(AppColors.text) colorFilter = ColorFilter.tint(AppColors.text)
) )
Spacer(modifier = Modifier.width(138.dp)) Spacer(modifier = Modifier.width(8.dp))
Text( Text(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Start, textAlign = TextAlign.Start,
text = stringResource(R.string.group_info), text = "群聊信息",
style = androidx.compose.ui.text.TextStyle( style = androidx.compose.ui.text.TextStyle(
color = AppColors.text, color = AppColors.text,
fontSize = 17.sp, fontSize = 16.sp,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
) )
Spacer(modifier = Modifier.width(40.dp))
} }
} }
// 内容区域 // 内容区域
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp) contentPadding = PaddingValues(12.dp)
) { ) {
// 群聊头像和名称 // 群聊头像和名称
item { item {
@@ -112,7 +125,7 @@ fun GroupChatInfoScreen(groupId: String) {
CustomAsyncImage( CustomAsyncImage(
imageUrl = viewModel.groupInfo!!.groupAvatar, imageUrl = viewModel.groupInfo!!.groupAvatar,
modifier = Modifier modifier = Modifier
.size(80.dp) .size(64.dp)
.clip(RoundedCornerShape(12.dp)), .clip(RoundedCornerShape(12.dp)),
contentDescription = "群聊头像" contentDescription = "群聊头像"
) )
@@ -120,7 +133,7 @@ fun GroupChatInfoScreen(groupId: String) {
Box( Box(
modifier = Modifier modifier = Modifier
.size(80.dp) .size(80.dp)
.clip(CircleShape) .clip(RoundedCornerShape(12.dp))
.background(AppColors.decentBackground), .background(AppColors.decentBackground),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
@@ -135,22 +148,39 @@ fun GroupChatInfoScreen(groupId: String) {
} }
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = viewModel.groupInfo?.groupName ?: "群聊", text = viewModel.groupInfo?.groupName ?: "_feiye,Rita 米小离儿",
style = androidx.compose.ui.text.TextStyle( style = androidx.compose.ui.text.TextStyle(
color = AppColors.text, color = AppColors.text,
fontSize = 18.sp, fontSize = 16.sp,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
) )
Spacer(modifier = Modifier.height(4.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
painter = painterResource(R.drawable.group_info_users),
modifier = Modifier.size(12.dp),
contentDescription = null,
colorFilter = ColorFilter.tint(AppColors.text.copy(alpha = 0.7f))
)
Spacer(modifier = Modifier.width(4.dp))
Text(
text = "4人",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text.copy(alpha = 0.7f),
fontSize = 11.sp
)
)
}
} }
} }
// 操作按钮 // 操作按钮
item { item {
Spacer(modifier = Modifier.height(32.dp)) Spacer(modifier = Modifier.height(10.dp))
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly horizontalArrangement = Arrangement.SpaceEvenly
@@ -164,24 +194,24 @@ fun GroupChatInfoScreen(groupId: String) {
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.size(48.dp) .size(30.dp)
.clip(CircleShape), .clip(CircleShape),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Image( Image(
painter = painterResource(R.drawable.rider_pro_add_other), painter = painterResource(R.drawable.rider_pro_add_other),
modifier = Modifier.size(24.dp), modifier = Modifier.size(20.dp),
contentDescription = null, contentDescription = null,
colorFilter = ColorFilter.tint( colorFilter = ColorFilter.tint(
AppColors.text) AppColors.text)
) )
} }
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(5.dp))
Text( Text(
text = stringResource(R.string.group_info_add_other), text = "添加成员",
style = androidx.compose.ui.text.TextStyle( style = androidx.compose.ui.text.TextStyle(
color = AppColors.text, color = AppColors.text,
fontSize = 12.sp fontSize = 11.sp
) )
) )
} }
@@ -201,24 +231,24 @@ fun GroupChatInfoScreen(groupId: String) {
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.size(48.dp) .size(30.dp)
.clip(CircleShape), .clip(CircleShape),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Image( Image(
painter = painterResource(if (viewModel.notificationStrategy == "mute") R.drawable.rider_pro_notice_mute else R.drawable.rider_pro_notice_active,), painter = painterResource(if (viewModel.notificationStrategy == "mute") R.drawable.rider_pro_notice_mute else R.drawable.rider_pro_notice_active,),
modifier = Modifier.size(24.dp), modifier = Modifier.size(20.dp),
contentDescription = null, contentDescription = null,
colorFilter = ColorFilter.tint( colorFilter = ColorFilter.tint(
AppColors.text) AppColors.text)
) )
} }
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(5.dp))
Text( Text(
text = stringResource(R.string.group_info_notice_setting), text = "通知",
style = androidx.compose.ui.text.TextStyle( style = androidx.compose.ui.text.TextStyle(
color = AppColors.text, color = AppColors.text,
fontSize = 12.sp fontSize = 11.sp
) )
) )
} }
@@ -232,41 +262,145 @@ fun GroupChatInfoScreen(groupId: String) {
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.size(48.dp) .size(30.dp)
.clip(CircleShape), .clip(CircleShape),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Image( Image(
painter = painterResource(R.drawable.group_info_exit painter = painterResource(R.drawable.group_info_edit),
), modifier = Modifier.size(20.dp),
modifier = Modifier.size(24.dp),
contentDescription = null, contentDescription = null,
colorFilter = ColorFilter.tint( colorFilter = ColorFilter.tint(AppColors.text)
AppColors.text)
) )
} }
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(5.dp))
Text( Text(
text = stringResource(R.string.group_info_exit), text = "分享",
style = androidx.compose.ui.text.TextStyle( style = androidx.compose.ui.text.TextStyle(
color = AppColors.text, color = AppColors.text,
fontSize = 12.sp fontSize = 11.sp
) )
) )
} }
} }
} }
// 解锁群扩展 横幅
item {
Spacer(modifier = Modifier.height(12.dp))
Box(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(10.dp))
.background(AppColors.decentBackground.copy(alpha = 0.35f))
.padding(horizontal = 10.dp, vertical = 10.dp),
contentAlignment = Alignment.CenterStart
) {
Text(
text = "解锁群扩展",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.main,
fontSize = 12.sp,
fontWeight = FontWeight.Medium
)
)
}
}
// 群记忆 卡片
item {
Spacer(modifier = Modifier.height(12.dp))
Column(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.background(AppColors.decentBackground.copy(alpha = 0.28f))
.padding(12.dp)
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
painter = painterResource(R.drawable.group_info_edit),
modifier = Modifier.size(16.dp),
contentDescription = null,
colorFilter = ColorFilter.tint(AppColors.text)
)
Spacer(modifier = Modifier.width(6.dp))
Column(modifier = Modifier.weight(1f)) {
Text(
text = "群记忆",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text,
fontSize = 15.sp,
fontWeight = FontWeight.Bold
)
)
Spacer(modifier = Modifier.height(2.dp))
Text(
text = "AI 会根据记忆在群聊里更懂你",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text.copy(alpha = 0.6f),
fontSize = 11.sp
)
)
}
}
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Box(
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(20.dp))
.background(AppColors.background)
.padding(vertical = 8.dp)
.noRippleClickable {
showAddMemoryDialog = true
},
contentAlignment = Alignment.Center
) {
Text(
text = "添加记忆",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text,
fontSize = 13.sp
)
)
}
Box(
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(20.dp))
.background(AppColors.background)
.padding(vertical = 8.dp)
.noRippleClickable {
showMemoryManageDialog = true
},
contentAlignment = Alignment.Center
) {
Text(
text = "记忆管理",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text,
fontSize = 13.sp
)
)
}
}
}
}
// 设置选项 // 设置选项
item { item {
Spacer(modifier = Modifier.height(32.dp)) Spacer(modifier = Modifier.height(13.dp))
// 设置聊天主题 // 设置聊天主题
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clip(RoundedCornerShape(8.dp)) .clip(RoundedCornerShape(8.dp))
.padding(16.dp) .padding(12.dp)
.noRippleClickable { .noRippleClickable {
// TODO: 实现设置聊天主题功能 // TODO: 实现设置聊天主题功能
}, },
@@ -274,104 +408,521 @@ fun GroupChatInfoScreen(groupId: String) {
) { ) {
Image( Image(
painter = painterResource(R.drawable.group_info_edit), painter = painterResource(R.drawable.group_info_edit),
modifier = Modifier.size(24.dp), modifier = Modifier.size(20.dp),
contentDescription = null, contentDescription = null,
colorFilter = ColorFilter.tint( colorFilter = ColorFilter.tint(
AppColors.text) AppColors.text)
) )
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(10.dp))
Text( Text(
text = stringResource(R.string.group_info_edit), text = "群资料设置",
style = androidx.compose.ui.text.TextStyle( style = androidx.compose.ui.text.TextStyle(
color = AppColors.text, color = AppColors.text,
fontSize = 16.sp fontSize = 15.sp
), ),
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
Image( Image(
painter = painterResource(R.drawable.rave_now_nav_right), painter = painterResource(R.drawable.rave_now_nav_right),
modifier = Modifier.size(18.dp), modifier = Modifier.size(16.dp),
contentDescription = null, contentDescription = null,
) )
} }
Spacer(modifier = Modifier.height(1.dp)) // 群可见性
// 群聊成员
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.clip(RoundedCornerShape(8.dp)) .clip(RoundedCornerShape(8.dp))
.padding(16.dp) .padding(12.dp)
.noRippleClickable { .noRippleClickable {
// TODO: 实现查看群聊成员功能
},
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(R.drawable.group_info_users),
modifier = Modifier.size(24.dp),
contentDescription = null,
colorFilter = ColorFilter.tint(
AppColors.text)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = "群聊成员 (${viewModel.groupInfo?.memberCount ?: 0})",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text,
fontSize = 16.sp
),
modifier = Modifier.weight(1f)
)
Image(
painter = painterResource(R.drawable.rave_now_nav_right),
modifier = Modifier.size(18.dp),
contentDescription = null,
)
}
Spacer(modifier = Modifier.height(1.dp))
//仅自己可见
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.padding(16.dp)
.noRippleClickable {
// TODO: 实现仅自己可见功能
}, },
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Image( Image(
painter = painterResource(R.mipmap.rider_pro_change_password), painter = painterResource(R.mipmap.rider_pro_change_password),
modifier = Modifier.size(24.dp), modifier = Modifier.size(20.dp),
contentDescription = null,
colorFilter = ColorFilter.tint(AppColors.text)
)
Spacer(modifier = Modifier.width(10.dp))
Text(
text = "群可见性",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text,
fontSize = 15.sp
),
modifier = Modifier.weight(1f)
)
Text(
text = "待解锁",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text.copy(alpha = 0.5f),
fontSize = 11.sp
)
)
Image(
painter = painterResource(R.drawable.rave_now_nav_right),
modifier = Modifier.size(16.dp),
contentDescription = null,
)
}
// 成员管理
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.padding(12.dp)
.noRippleClickable {
// 静态占位
},
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(R.drawable.group_info_users),
modifier = Modifier.size(20.dp),
contentDescription = null, contentDescription = null,
colorFilter = ColorFilter.tint( colorFilter = ColorFilter.tint(
AppColors.text) AppColors.text)
) )
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(10.dp))
Text( Text(
text = "仅自己可见", text = "成员管理",
style = androidx.compose.ui.text.TextStyle( style = androidx.compose.ui.text.TextStyle(
color = AppColors.text, color = AppColors.text,
fontSize = 16.sp fontSize = 15.sp
), ),
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
Switch( Image(
checked = true, painter = painterResource(R.drawable.rave_now_nav_right),
onCheckedChange = { modifier = Modifier.size(16.dp),
// TODO: 实现群聊仅自己可见功能 contentDescription = null,
}, )
colors = SwitchDefaults.colors( }
checkedThumbColor = Color.White, // 群聊壁纸
checkedTrackColor = Color.Green, Row(
uncheckedThumbColor = Color.White, modifier = Modifier
uncheckedTrackColor = AppColors.main.copy(alpha = 0.5f), .fillMaxWidth()
uncheckedBorderColor = Color.Transparent .clip(RoundedCornerShape(8.dp))
.padding(12.dp)
.noRippleClickable { },
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(R.drawable.group_info_edit),
modifier = Modifier.size(20.dp),
contentDescription = null,
colorFilter = ColorFilter.tint(
AppColors.text)
)
Spacer(modifier = Modifier.width(10.dp))
Text(
text = "群聊壁纸",
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text,
fontSize = 15.sp
), ),
modifier = Modifier.scale(1f).height(18.dp) modifier = Modifier.weight(1f)
)
Image(
painter = painterResource(R.drawable.rave_now_nav_right),
modifier = Modifier.size(18.dp),
contentDescription = null,
)
}
// 解散群聊
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.padding(12.dp)
.noRippleClickable { },
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(R.drawable.group_info_exit),
modifier = Modifier.size(20.dp),
contentDescription = null,
colorFilter = ColorFilter.tint(Color(0xFFFF3B30))
)
Spacer(modifier = Modifier.width(10.dp))
Text(
text = "解散群聊",
style = androidx.compose.ui.text.TextStyle(
color = Color(0xFFFF3B30),
fontSize = 15.sp
),
modifier = Modifier.weight(1f)
)
Image(
painter = painterResource(R.drawable.rave_now_nav_right),
modifier = Modifier.size(18.dp),
contentDescription = null,
colorFilter = ColorFilter.tint(Color(0xFFFF3B30))
)
}
}
}
}
// 添加群记忆弹窗
if (showAddMemoryDialog) {
ModalBottomSheet(
onDismissRequest = { showAddMemoryDialog = false },
sheetState = sheetState,
containerColor = Color(0xFFFAF9FB),
dragHandle = {
Box(
modifier = Modifier
.width(36.dp)
.height(5.dp)
.padding(top = 5.dp)
.background(
Color(0xFFCCCCCC),
RoundedCornerShape(100.dp)
)
)
},
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)
) {
AddGroupMemoryDialog(
groupInfo = viewModel.groupInfo,
viewModel = viewModel,
onDismiss = { showAddMemoryDialog = false },
onAddMemory = { memoryText ->
viewModel.addGroupMemory(memoryText)
}
)
}
}
// 记忆管理弹窗
if (showMemoryManageDialog) {
ModalBottomSheet(
onDismissRequest = { showMemoryManageDialog = false },
sheetState = memoryManageSheetState,
containerColor = Color(0xFFFAF9FB),
dragHandle = {
Box(
modifier = Modifier
.width(36.dp)
.height(5.dp)
.padding(top = 5.dp)
.background(
Color(0xFFCCCCCC),
RoundedCornerShape(100.dp)
)
)
},
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)
) {
GroupMemoryManageContent(
groupId = groupId,
viewModel = viewModel,
onAddMemoryClick = {
showMemoryManageDialog = false
showAddMemoryDialog = true
}
)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AddGroupMemoryDialog(
groupInfo: com.aiosman.ravenow.entity.GroupInfo?,
viewModel: GroupChatInfoViewModel,
onDismiss: () -> Unit,
onAddMemory: (String) -> Unit
) {
val AppColors = LocalAppTheme.current
val context = LocalContext.current
var memoryText by remember { mutableStateOf("") }
val maxLength = 500
// 监听添加记忆的结果
LaunchedEffect(viewModel.addMemorySuccess) {
if (viewModel.addMemorySuccess) {
android.widget.Toast.makeText(context, "群记忆添加成功", android.widget.Toast.LENGTH_SHORT).show()
memoryText = "" // 清空输入框
onDismiss()
viewModel.addMemorySuccess = false
}
}
LaunchedEffect(viewModel.addMemoryError) {
viewModel.addMemoryError?.let { error ->
android.widget.Toast.makeText(context, error, android.widget.Toast.LENGTH_SHORT).show()
viewModel.addMemoryError = null
}
}
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.padding(bottom = 40.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
// 顶部标题栏
Row(
modifier = Modifier
.fillMaxWidth()
.height(44.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Spacer(modifier = Modifier.width(24.dp))
Text(
text = "添加群记忆",
style = TextStyle(
fontSize = 17.sp,
fontWeight = FontWeight.SemiBold,
color = Color.Black
),
modifier = Modifier.weight(1f),
textAlign = TextAlign.Center
)
Image(
painter = painterResource(R.drawable.rider_pro_close),
contentDescription = "关闭",
modifier = Modifier
.size(24.dp)
.noRippleClickable { onDismiss() },
colorFilter = ColorFilter.tint(Color.Black)
)
}
Spacer(modifier = Modifier.height(10.dp))
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// 群信息卡片
Row(
modifier = Modifier
.fillMaxWidth()
.height(80.dp)
.clip(RoundedCornerShape(16.dp))
.background(Color.White)
.padding(horizontal = 12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
if (groupInfo?.groupAvatar?.isNotEmpty() == true) {
CustomAsyncImage(
imageUrl = groupInfo.groupAvatar,
modifier = Modifier
.size(48.dp)
.clip(RoundedCornerShape(12.dp)),
contentDescription = "群头像"
)
} else {
Box(
modifier = Modifier
.size(48.dp)
.clip(RoundedCornerShape(12.dp))
.background(AppColors.decentBackground),
contentAlignment = Alignment.Center
) {
Text(
text = groupInfo?.groupName?.firstOrNull()?.toString() ?: "",
style = TextStyle(
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = AppColors.text
)
)
}
}
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = groupInfo?.groupName ?: "创意项目讨论组",
style = TextStyle(
fontSize = 15.sp,
fontWeight = FontWeight.SemiBold,
color = Color.Black
)
)
Text(
text = "${groupInfo?.memberCount ?: 32} 位成员",
style = TextStyle(
fontSize = 12.sp,
color = Color(0xFF3C3C43).copy(alpha = 0.6f)
)
)
}
}
// 输入框卡片
Box(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp))
.background(Color.White)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(12.dp)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(84.dp),
contentAlignment = Alignment.TopStart
) {
BasicTextField(
value = memoryText,
onValueChange = { newText ->
if (newText.length <= maxLength) {
memoryText = newText
}
},
cursorBrush = SolidColor(Color.Black),
modifier = Modifier.fillMaxWidth(),
maxLines = 6,
textStyle = TextStyle(
fontSize = 15.sp,
color = Color.Black,
lineHeight = 20.sp
),
decorationBox = { innerTextField ->
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.TopStart
) {
innerTextField()
if (memoryText.isEmpty()) {
Text(
text = "这里的内容超过输入框自动换行",
style = TextStyle(
fontSize = 15.sp,
color = Color.Black.copy(alpha = 0.3f)
)
)
}
}
}
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
Text(
text = "${memoryText.length}/$maxLength",
style = TextStyle(
fontSize = 12.sp,
color = Color(0xFF3C3C43).copy(alpha = 0.3f)
)
)
}
}
}
// 提示信息卡片
Column(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp))
.background(Color(0xFFFBF8EF))
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "",
fontSize = 13.sp
)
Text(
text = "添加记忆需消耗 20 派币",
style = TextStyle(
fontSize = 13.sp,
color = Color.Black
)
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "🤖",
fontSize = 13.sp
)
Text(
text = "AI 将基于记忆优化回复",
style = TextStyle(
fontSize = 13.sp,
color = Color.Black
)
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "✏️",
fontSize = 13.sp
)
Text(
text = "可随时编辑或删除",
style = TextStyle(
fontSize = 13.sp,
color = Color.Black
)
)
}
}
// 添加记忆按钮
Box(
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.clip(RoundedCornerShape(1000.dp))
.background(
brush = Brush.horizontalGradient(
colors = listOf(
Color(0xFF7C45ED), // 紫色
Color(0xFF7C57EE), // 浅紫色
Color(0xFF7BD8F8) // 蓝色
)
)
)
.noRippleClickable {
if (memoryText.isNotEmpty() && !viewModel.isAddingMemory) {
onAddMemory(memoryText)
}
},
contentAlignment = Alignment.Center
) {
if (viewModel.isAddingMemory) {
androidx.compose.material3.CircularProgressIndicator(
modifier = Modifier.size(20.dp),
color = Color.White
)
} else {
Text(
text = "添加记忆",
style = TextStyle(
fontSize = 17.sp,
color = Color.White
)
) )
} }
} }

View File

@@ -1,5 +1,6 @@
package com.aiosman.ravenow.ui.group package com.aiosman.ravenow.ui.group
import android.util.Log
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@@ -8,6 +9,10 @@ import androidx.lifecycle.viewModelScope
import com.aiosman.ravenow.AppStore import com.aiosman.ravenow.AppStore
import com.aiosman.ravenow.ChatState import com.aiosman.ravenow.ChatState
import com.aiosman.ravenow.data.api.ApiClient import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.data.api.CreatePromptRuleRequestBody
import com.aiosman.ravenow.data.api.PromptRule
import com.aiosman.ravenow.data.api.PromptRuleQuota
import com.aiosman.ravenow.data.parseErrorResponse
import com.aiosman.ravenow.entity.ChatNotification import com.aiosman.ravenow.entity.ChatNotification
import com.aiosman.ravenow.entity.GroupInfo import com.aiosman.ravenow.entity.GroupInfo
import com.aiosman.ravenow.entity.GroupMember import com.aiosman.ravenow.entity.GroupMember
@@ -22,9 +27,36 @@ class GroupChatInfoViewModel(
var isLoading by mutableStateOf(false) var isLoading by mutableStateOf(false)
var error by mutableStateOf<String?>(null) var error by mutableStateOf<String?>(null)
var chatNotification by mutableStateOf<ChatNotification?>(null) var chatNotification by mutableStateOf<ChatNotification?>(null)
var isAddingMemory by mutableStateOf(false)
var addMemoryError by mutableStateOf<String?>(null)
var addMemorySuccess by mutableStateOf(false)
val notificationStrategy get() = chatNotification?.strategy ?: "default" val notificationStrategy get() = chatNotification?.strategy ?: "default"
// 记忆管理相关状态
var memoryQuota by mutableStateOf<PromptRuleQuota?>(null)
var memoryList by mutableStateOf<List<PromptRule>>(emptyList())
var isLoadingMemory by mutableStateOf(false)
var memoryError by mutableStateOf<String?>(null)
var promptOpenId by mutableStateOf<String?>(null)
init { init {
loadGroupInfo() loadGroupInfo()
loadPromptOpenId()
}
/**
* 获取群聊中智能体的 OpenID
*/
private fun loadPromptOpenId() {
viewModelScope.launch {
try {
val response = ApiClient.api.createGroupChatAi(trtcGroupId = groupId)
val groupChatResponse = response.body()?.data
val prompts = groupChatResponse?.prompts
promptOpenId = prompts?.firstOrNull()?.openId
} catch (e: Exception) {
Log.e("GroupChatInfoViewModel", "获取智能体OpenID失败: ${e.message}", e)
}
}
} }
suspend fun updateNotificationStrategy(strategy: String) { suspend fun updateNotificationStrategy(strategy: String) {
val result = ChatState.updateChatNotification(groupId.hashCode(), strategy) val result = ChatState.updateChatNotification(groupId.hashCode(), strategy)
@@ -71,4 +103,180 @@ class GroupChatInfoViewModel(
} }
} }
} }
/**
* 添加群记忆
* @param memoryText 记忆内容
* @param promptOpenId 智能体的 OpenID可选如果不提供则从群聊信息中获取
*/
fun addGroupMemory(memoryText: String, promptOpenId: String? = null) {
viewModelScope.launch {
try {
isAddingMemory = true
addMemoryError = null
addMemorySuccess = false
// 如果没有提供 promptOpenId需要先获取群聊的智能体信息
val openId = promptOpenId ?: run {
// 通过 createGroupChatAi 接口获取群聊详细信息(包含 prompts
val response = ApiClient.api.createGroupChatAi(trtcGroupId = groupId)
val groupChatResponse = response.body()?.data
val prompts = groupChatResponse?.prompts
if (prompts.isNullOrEmpty()) {
throw Exception("群聊中没有找到智能体,无法添加记忆")
}
// 使用第一个智能体的 openId
prompts.firstOrNull()?.openId
?: throw Exception("无法获取智能体信息")
}
if (openId.isBlank()) {
throw Exception("智能体ID不能为空")
}
// 创建智能体规则(群记忆)
val requestBody = CreatePromptRuleRequestBody(
rule = memoryText,
openId = openId
)
val response = ApiClient.api.createPromptRule(requestBody)
if (response.isSuccessful) {
addMemorySuccess = true
Log.d("GroupChatInfoViewModel", "群记忆添加成功")
// 刷新记忆列表和配额
loadMemoryQuota(openId)
loadMemoryList(openId)
} else {
val errorResponse = parseErrorResponse(response.errorBody())
val errorMessage = errorResponse?.toServiceException()?.message
?: "添加群记忆失败: ${response.code()}"
throw Exception(errorMessage)
}
} catch (e: Exception) {
addMemoryError = e.message ?: "添加群记忆失败"
Log.e("GroupChatInfoViewModel", "添加群记忆失败: ${e.message}", e)
} finally {
isAddingMemory = false
}
}
}
/**
* 获取记忆配额信息
*/
fun loadMemoryQuota(openId: String? = null) {
viewModelScope.launch {
try {
isLoadingMemory = true
memoryError = null
val targetOpenId = openId ?: promptOpenId
if (targetOpenId.isNullOrBlank()) {
// 如果还没有获取到 openId先获取
val response = ApiClient.api.createGroupChatAi(trtcGroupId = groupId)
val groupChatResponse = response.body()?.data
val prompts = groupChatResponse?.prompts
val fetchedOpenId = prompts?.firstOrNull()?.openId
?: throw Exception("无法获取智能体信息")
promptOpenId = fetchedOpenId
val quotaResponse = ApiClient.api.getPromptRuleQuota(fetchedOpenId)
if (quotaResponse.isSuccessful) {
memoryQuota = quotaResponse.body()?.data
} else {
throw Exception("获取配额信息失败: ${quotaResponse.code()}")
}
} else {
val quotaResponse = ApiClient.api.getPromptRuleQuota(targetOpenId)
if (quotaResponse.isSuccessful) {
memoryQuota = quotaResponse.body()?.data
} else {
throw Exception("获取配额信息失败: ${quotaResponse.code()}")
}
}
} catch (e: Exception) {
memoryError = e.message ?: "获取配额信息失败"
Log.e("GroupChatInfoViewModel", "获取配额信息失败: ${e.message}", e)
} finally {
isLoadingMemory = false
}
}
}
/**
* 获取记忆列表
*/
fun loadMemoryList(openId: String? = null, page: Int = 1, pageSize: Int = 20) {
viewModelScope.launch {
try {
isLoadingMemory = true
memoryError = null
val targetOpenId = openId ?: promptOpenId
if (targetOpenId.isNullOrBlank()) {
// 如果还没有获取到 openId先获取
val response = ApiClient.api.createGroupChatAi(trtcGroupId = groupId)
val groupChatResponse = response.body()?.data
val prompts = groupChatResponse?.prompts
val fetchedOpenId = prompts?.firstOrNull()?.openId
?: throw Exception("无法获取智能体信息")
promptOpenId = fetchedOpenId
val listResponse = ApiClient.api.getPromptRuleList(fetchedOpenId, page = page, pageSize = pageSize)
if (listResponse.isSuccessful) {
memoryList = listResponse.body()?.data?.list ?: emptyList()
} else {
throw Exception("获取记忆列表失败: ${listResponse.code()}")
}
} else {
val listResponse = ApiClient.api.getPromptRuleList(targetOpenId, page = page, pageSize = pageSize)
if (listResponse.isSuccessful) {
memoryList = listResponse.body()?.data?.list ?: emptyList()
} else {
throw Exception("获取记忆列表失败: ${listResponse.code()}")
}
}
} catch (e: Exception) {
memoryError = e.message ?: "获取记忆列表失败"
Log.e("GroupChatInfoViewModel", "获取记忆列表失败: ${e.message}", e)
} finally {
isLoadingMemory = false
}
}
}
/**
* 删除记忆
*/
fun deleteMemory(ruleId: Int) {
viewModelScope.launch {
try {
isLoadingMemory = true
memoryError = null
val response = ApiClient.api.deletePromptRule(ruleId)
if (response.isSuccessful) {
// 刷新记忆列表和配额
promptOpenId?.let { openId ->
loadMemoryQuota(openId)
loadMemoryList(openId)
}
} else {
val errorResponse = parseErrorResponse(response.errorBody())
val errorMessage = errorResponse?.toServiceException()?.message
?: "删除记忆失败: ${response.code()}"
throw Exception(errorMessage)
}
} catch (e: Exception) {
memoryError = e.message ?: "删除记忆失败"
Log.e("GroupChatInfoViewModel", "删除记忆失败: ${e.message}", e)
} finally {
isLoadingMemory = false
}
}
}
} }

View File

@@ -0,0 +1,267 @@
package com.aiosman.ravenow.ui.group
import androidx.compose.foundation.background
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.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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.LocalConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import androidx.compose.foundation.Image
@Composable
fun GroupMemoryManageContent(
groupId: String,
viewModel: GroupChatInfoViewModel,
onAddMemoryClick: () -> Unit = {}
) {
val AppColors = LocalAppTheme.current
val configuration = LocalConfiguration.current
val screenHeight = configuration.screenHeightDp.dp
val sheetHeight = screenHeight * 0.9f
// 加载配额和列表数据
LaunchedEffect(Unit) {
viewModel.loadMemoryQuota()
viewModel.loadMemoryList()
}
val quota = viewModel.memoryQuota
val memoryList = viewModel.memoryList
val isLoading = viewModel.isLoadingMemory
Column(
modifier = Modifier
.fillMaxWidth()
.height(sheetHeight)
.background(Color(0xFFFAF9FB))
) {
// 顶部栏:标题 + 加号(适配弹窗)
Row(
modifier = Modifier
.fillMaxWidth()
.height(44.dp)
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.Center) {
Text(
text = "记忆管理",
style = TextStyle(color = Color.Black, fontSize = 17.sp, fontWeight = FontWeight.SemiBold),
textAlign = TextAlign.Center
)
}
// 右上角 + 按钮
Row(
modifier = Modifier
.clip(RoundedCornerShape(296.dp))
.noRippleClickable { onAddMemoryClick() }
.padding(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = "+", style = TextStyle(color = Color.Black, fontSize = 20.sp, fontWeight = FontWeight.Medium))
}
}
// 浅黄色提示栏 - 显示真实的配额数据
Row(
modifier = Modifier
.fillMaxWidth()
.background(Color(0xFFFBF8EF))
.padding(horizontal = 16.dp, vertical = 12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text("已付费:", style = TextStyle(color = Color(0x993C3C43), fontSize = 13.sp))
Spacer(Modifier.width(3.dp))
Text(
"${quota?.purchasedCount ?: 0}",
style = TextStyle(color = Color(0xFFFF8D28), fontSize = 13.sp)
)
}
Row(verticalAlignment = Alignment.CenterVertically) {
Text("已使用:", style = TextStyle(color = Color(0x993C3C43), fontSize = 13.sp))
Spacer(Modifier.width(3.dp))
Text(
"${quota?.currentCount ?: 0}",
style = TextStyle(color = Color(0xFFFF8D28), fontSize = 13.sp)
)
}
}
Row(verticalAlignment = Alignment.CenterVertically) {
Text("可用上限:", style = TextStyle(color = Color(0x993C3C43), fontSize = 13.sp))
Spacer(Modifier.width(3.dp))
Text(
"${quota?.totalMaxCount ?: 0}",
style = TextStyle(color = Color(0xFFFF8D28), fontSize = 13.sp)
)
}
}
// 记忆列表或空状态
Box(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
) {
if (isLoading) {
// 加载中状态
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
modifier = Modifier.size(40.dp),
color = Color(0xFFFF8D28)
)
}
} else if (memoryList.isNotEmpty()) {
// 显示记忆列表
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(memoryList) { memory ->
MemoryItem(
memory = memory,
onDelete = {
viewModel.deleteMemory(memory.id)
}
)
}
}
} else {
// 空状态
Column(
modifier = Modifier
.fillMaxSize()
.padding(vertical = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
// 简化的图形占位:橙色与紫色对话气泡
Box(contentAlignment = Alignment.Center) {
Box(
modifier = Modifier
.size(74.dp)
.clip(RoundedCornerShape(20.dp))
.background(Color(0xFFD4D4FC))
.offset(x = 26.dp, y = (-18).dp)
)
Row(
modifier = Modifier
.height(72.dp)
.clip(RoundedCornerShape(20.dp))
.background(Color(0xFFFA8334))
.padding(horizontal = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(22.dp)
.clip(CircleShape)
.background(Color.White)
)
Spacer(Modifier.width(12.dp))
Box(
modifier = Modifier
.size(22.dp)
.clip(CircleShape)
.background(Color.White)
)
}
}
Spacer(Modifier.height(24.dp))
Text(
text = "暂无记忆",
style = TextStyle(color = Color.Black, fontSize = 16.sp, fontWeight = FontWeight.SemiBold)
)
Spacer(Modifier.height(6.dp))
Text(
text = "点击上方按钮添加群记忆",
style = TextStyle(color = Color.Black, fontSize = 14.sp, fontWeight = FontWeight.Normal)
)
}
}
}
}
}
/**
* 记忆项组件
*/
@Composable
fun MemoryItem(
memory: com.aiosman.ravenow.data.api.PromptRule,
onDelete: () -> Unit
) {
val AppColors = LocalAppTheme.current
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.background(Color.White)
.padding(16.dp),
verticalAlignment = Alignment.Top
) {
// 记忆内容
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = memory.rule,
style = TextStyle(
color = Color.Black,
fontSize = 14.sp,
lineHeight = 20.sp
),
maxLines = 3,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "创建于 ${if (memory.createdAt.length >= 10) memory.createdAt.substring(0, 10) else memory.createdAt}",
style = TextStyle(
color = Color(0x993C3C43),
fontSize = 12.sp
)
)
}
// 删除按钮
Image(
painter = painterResource(R.drawable.rider_pro_close),
contentDescription = "删除",
modifier = Modifier
.size(20.dp)
.noRippleClickable { onDelete() },
colorFilter = ColorFilter.tint(Color(0x993C3C43))
)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB