From f86b5e1d39b2fe675ea9c9d9c5facc57a16f643a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E5=B8=86?= <3031465419@qq.com> Date: Fri, 7 Nov 2025 21:22:10 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E5=AE=89=E5=85=A8=E7=95=8C?= =?UTF-8?q?=E9=9D=A2ui=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ravenow/ui/account/AccountSetting.kt | 198 ++++++++++++------ .../ui/group/GroupChatInfoViewModel.kt | 28 +-- .../ui/group/GroupMemoryManageScreen.kt | 4 +- 3 files changed, 145 insertions(+), 85 deletions(-) diff --git a/app/src/main/java/com/aiosman/ravenow/ui/account/AccountSetting.kt b/app/src/main/java/com/aiosman/ravenow/ui/account/AccountSetting.kt index f6baafc..2b527ad 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/account/AccountSetting.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/account/AccountSetting.kt @@ -1,117 +1,177 @@ package com.aiosman.ravenow.ui.account -import android.widget.Toast +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.BasicTextField import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text -import androidx.compose.material3.rememberModalBottomSheetState -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.text.TextStyle import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.aiosman.ravenow.AppState -import com.aiosman.ravenow.AppStore import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.LocalNavController -import com.aiosman.ravenow.Messaging import com.aiosman.ravenow.R -import com.aiosman.ravenow.data.AccountServiceImpl import com.aiosman.ravenow.ui.NavigationRoute -import com.aiosman.ravenow.ui.comment.NoticeScreenHeader -import com.aiosman.ravenow.ui.composables.ActionButton import com.aiosman.ravenow.ui.composables.StatusBarSpacer -import com.aiosman.ravenow.ui.index.NavItem import com.aiosman.ravenow.ui.modifiers.noRippleClickable -import kotlinx.coroutines.launch + +private object AccountSettingConstants { + const val BACK_BUTTON_SIZE = 36 + const val BACK_BUTTON_ICON_SIZE = 24 + const val BACK_BUTTON_START_PADDING = 19 + const val OPTION_ITEM_HEIGHT = 56 + const val OPTION_ITEM_ICON_SIZE = 24 + const val OPTION_ITEM_HORIZONTAL_PADDING = 16 + const val OPTION_ITEM_ICON_TEXT_SPACING = 12 + const val OPTION_ITEM_TEXT_SIZE = 17 + const val HEADER_VERTICAL_PADDING = 16 + const val TITLE_OFFSET_X = 19 + const val CARD_CORNER_RADIUS = 16 +} + +@Composable +private fun CircularBackButton( + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + val appColors = LocalAppTheme.current + Box( + modifier = modifier + .size(AccountSettingConstants.BACK_BUTTON_SIZE.dp) + .background( + color = appColors.secondaryBackground, + shape = CircleShape + ) + .noRippleClickable { onClick() }, + contentAlignment = Alignment.Center + ) { + Image( + painter = painterResource(id = R.drawable.rider_pro_back_icon), + contentDescription = "返回", + modifier = Modifier.size(AccountSettingConstants.BACK_BUTTON_ICON_SIZE.dp), + colorFilter = ColorFilter.tint(appColors.text) + ) + } +} + +@Composable +private fun SecurityOptionItem( + iconRes: Int, + label: String, + onClick: () -> Unit, + applyColorFilter: Boolean = true +) { + val appColors = LocalAppTheme.current + Row( + modifier = Modifier + .fillMaxWidth() + .height(AccountSettingConstants.OPTION_ITEM_HEIGHT.dp) + .padding(horizontal = AccountSettingConstants.OPTION_ITEM_HORIZONTAL_PADDING.dp) + .noRippleClickable { onClick() }, + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(id = iconRes), + contentDescription = null, + modifier = Modifier.size(AccountSettingConstants.OPTION_ITEM_ICON_SIZE.dp), + colorFilter = if (applyColorFilter) ColorFilter.tint(appColors.text) else null + ) + Text( + text = label, + modifier = Modifier + .padding(start = AccountSettingConstants.OPTION_ITEM_ICON_TEXT_SPACING.dp) + .weight(1f), + color = appColors.text, + fontSize = AccountSettingConstants.OPTION_ITEM_TEXT_SIZE.sp, + fontWeight = FontWeight.Medium + ) + Image( + painter = painterResource(id = R.drawable.rave_now_nav_right), + contentDescription = null, + modifier = Modifier.size(AccountSettingConstants.OPTION_ITEM_ICON_SIZE.dp), + colorFilter = ColorFilter.tint(appColors.secondaryText) + ) + } +} @OptIn(ExperimentalMaterial3Api::class) @Composable fun AccountSetting() { val appColors = LocalAppTheme.current val navController = LocalNavController.current - val scope = rememberCoroutineScope() - val context = LocalContext.current Column( modifier = Modifier .fillMaxSize() .background(appColors.background), ) { StatusBarSpacer() + + // 顶部标题栏 Box( - modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp) + modifier = Modifier + .fillMaxWidth() + .padding(vertical = AccountSettingConstants.HEADER_VERTICAL_PADDING.dp) ) { - NoticeScreenHeader( - title = stringResource(R.string.account_and_security), - moreIcon = false + CircularBackButton( + onClick = { navController.navigateUp() }, + modifier = Modifier.padding(start = AccountSettingConstants.BACK_BUTTON_START_PADDING.dp) + ) + Text( + text = stringResource(R.string.account_and_security), + fontWeight = FontWeight.W800, + fontSize = AccountSettingConstants.OPTION_ITEM_TEXT_SIZE.sp, + color = appColors.text, + modifier = Modifier + .align(Alignment.Center) + .offset(x = AccountSettingConstants.TITLE_OFFSET_X.dp) ) } + + // 安全选项卡片 Column( - modifier = Modifier.padding(start = 24.dp) + modifier = Modifier + .fillMaxWidth() + .background( + color = appColors.background, + shape = RoundedCornerShape(AccountSettingConstants.CARD_CORNER_RADIUS.dp) + ) ) { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 8.dp) - ) { - NavItem( - iconRes = R.mipmap.rider_pro_change_password, - label = stringResource(R.string.change_password), - modifier = Modifier.noRippleClickable { - navController.navigate(NavigationRoute.ChangePasswordScreen.route) - } - ) - } - - // 分割线 - Box( - modifier = Modifier - .fillMaxWidth() - .height(1.dp) - .background(appColors.divider) + SecurityOptionItem( + iconRes = R.mipmap.icons_padlock, + label = stringResource(R.string.change_password), + onClick = { navController.navigate(NavigationRoute.ChangePasswordScreen.route) } ) - Box( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 8.dp) - ) { - NavItem( - iconRes = R.drawable.rider_pro_moment_delete, - label = stringResource(R.string.remove_account), - modifier = Modifier.noRippleClickable { - navController.navigate(NavigationRoute.RemoveAccountScreen.route) - } - ) - } - Box( - modifier = Modifier - .fillMaxWidth() - .height(1.dp) - .background(appColors.divider) + + SecurityOptionItem( + iconRes = R.mipmap.icons_block, + label = stringResource(R.string.blocked_users), + onClick = { + // TODO: 导航到屏蔽用户页面 + } + ) + + SecurityOptionItem( + iconRes = R.mipmap.icons_remove, + label = stringResource(R.string.remove_account), + onClick = { navController.navigate(NavigationRoute.RemoveAccountScreen.route) }, + applyColorFilter = false ) } - } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/ravenow/ui/group/GroupChatInfoViewModel.kt b/app/src/main/java/com/aiosman/ravenow/ui/group/GroupChatInfoViewModel.kt index 2dd62b2..a989194 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/group/GroupChatInfoViewModel.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/group/GroupChatInfoViewModel.kt @@ -9,9 +9,9 @@ import androidx.lifecycle.viewModelScope import com.aiosman.ravenow.AppStore import com.aiosman.ravenow.ChatState 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.api.CreateAgentRuleRequestBody +import com.aiosman.ravenow.data.api.AgentRule +import com.aiosman.ravenow.data.api.AgentRuleQuota import com.aiosman.ravenow.data.parseErrorResponse import com.aiosman.ravenow.entity.ChatNotification import com.aiosman.ravenow.entity.GroupInfo @@ -33,8 +33,8 @@ class GroupChatInfoViewModel( val notificationStrategy get() = chatNotification?.strategy ?: "default" // 记忆管理相关状态 - var memoryQuota by mutableStateOf(null) - var memoryList by mutableStateOf>(emptyList()) + var memoryQuota by mutableStateOf(null) + var memoryList by mutableStateOf>(emptyList()) var isLoadingMemory by mutableStateOf(false) var memoryError by mutableStateOf(null) var promptOpenId by mutableStateOf(null) @@ -137,12 +137,12 @@ class GroupChatInfoViewModel( } // 创建智能体规则(群记忆) - val requestBody = CreatePromptRuleRequestBody( + val requestBody = CreateAgentRuleRequestBody( rule = memoryText, openId = openId ) - val response = ApiClient.api.createPromptRule(requestBody) + val response = ApiClient.api.createAgentRule(requestBody) if (response.isSuccessful) { addMemorySuccess = true @@ -184,14 +184,14 @@ class GroupChatInfoViewModel( ?: throw Exception("无法获取智能体信息") promptOpenId = fetchedOpenId - val quotaResponse = ApiClient.api.getPromptRuleQuota(fetchedOpenId) + val quotaResponse = ApiClient.api.getAgentRuleQuota(fetchedOpenId) if (quotaResponse.isSuccessful) { memoryQuota = quotaResponse.body()?.data } else { throw Exception("获取配额信息失败: ${quotaResponse.code()}") } } else { - val quotaResponse = ApiClient.api.getPromptRuleQuota(targetOpenId) + val quotaResponse = ApiClient.api.getAgentRuleQuota(targetOpenId) if (quotaResponse.isSuccessful) { memoryQuota = quotaResponse.body()?.data } else { @@ -226,14 +226,14 @@ class GroupChatInfoViewModel( ?: throw Exception("无法获取智能体信息") promptOpenId = fetchedOpenId - val listResponse = ApiClient.api.getPromptRuleList(fetchedOpenId, page = page, pageSize = pageSize) + val listResponse = ApiClient.api.getAgentRuleList(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) + val listResponse = ApiClient.api.getAgentRuleList(targetOpenId, page = page, pageSize = pageSize) if (listResponse.isSuccessful) { memoryList = listResponse.body()?.data?.list ?: emptyList() } else { @@ -258,7 +258,7 @@ class GroupChatInfoViewModel( isLoadingMemory = true memoryError = null - val response = ApiClient.api.deletePromptRule(ruleId) + val response = ApiClient.api.deleteAgentRule(ruleId) if (response.isSuccessful) { // 刷新记忆列表和配额 promptOpenId?.let { openId -> @@ -292,13 +292,13 @@ class GroupChatInfoViewModel( val openId = targetOpenId ?: promptOpenId ?: throw Exception("无法获取智能体ID") - val requestBody = com.aiosman.ravenow.data.api.UpdatePromptRuleRequestBody( + val requestBody = com.aiosman.ravenow.data.api.UpdateAgentRuleRequestBody( id = ruleId, rule = newRuleText, openId = openId ) - val response = ApiClient.api.updatePromptRule(requestBody) + val response = ApiClient.api.updateAgentRule(requestBody) if (response.isSuccessful) { // 刷新记忆列表和配额 loadMemoryQuota(openId) diff --git a/app/src/main/java/com/aiosman/ravenow/ui/group/GroupMemoryManageScreen.kt b/app/src/main/java/com/aiosman/ravenow/ui/group/GroupMemoryManageScreen.kt index 47af95c..3c19423 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/group/GroupMemoryManageScreen.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/group/GroupMemoryManageScreen.kt @@ -257,7 +257,7 @@ fun GroupMemoryManageContent( @OptIn(ExperimentalMaterial3Api::class) @Composable fun EditGroupMemoryDialog( - memory: com.aiosman.ravenow.data.api.PromptRule, + memory: com.aiosman.ravenow.data.api.AgentRule, viewModel: GroupChatInfoViewModel, onDismiss: () -> Unit, onUpdateMemory: (String) -> Unit @@ -403,7 +403,7 @@ fun EditGroupMemoryDialog( */ @Composable fun MemoryItem( - memory: com.aiosman.ravenow.data.api.PromptRule, + memory: com.aiosman.ravenow.data.api.AgentRule, isEditing: Boolean = false, onEdit: () -> Unit = {}, onCancel: () -> Unit = {},