账号安全界面ui设置

This commit is contained in:
2025-11-07 21:22:10 +08:00
parent 75eb38b188
commit f86b5e1d39
3 changed files with 145 additions and 85 deletions

View File

@@ -1,117 +1,177 @@
package com.aiosman.ravenow.ui.account package com.aiosman.ravenow.ui.account
import android.widget.Toast import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding 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.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text 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.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.Alignment
import androidx.compose.ui.Modifier 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.res.stringResource
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.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp 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.LocalAppTheme
import com.aiosman.ravenow.LocalNavController import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.Messaging
import com.aiosman.ravenow.R import com.aiosman.ravenow.R
import com.aiosman.ravenow.data.AccountServiceImpl
import com.aiosman.ravenow.ui.NavigationRoute 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.composables.StatusBarSpacer
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
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) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AccountSetting() { fun AccountSetting() {
val appColors = LocalAppTheme.current val appColors = LocalAppTheme.current
val navController = LocalNavController.current val navController = LocalNavController.current
val scope = rememberCoroutineScope()
val context = LocalContext.current
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(appColors.background), .background(appColors.background),
) { ) {
StatusBarSpacer() StatusBarSpacer()
// 顶部标题栏
Box( Box(
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp) modifier = Modifier
.fillMaxWidth()
.padding(vertical = AccountSettingConstants.HEADER_VERTICAL_PADDING.dp)
) { ) {
NoticeScreenHeader( CircularBackButton(
title = stringResource(R.string.account_and_security), onClick = { navController.navigateUp() },
moreIcon = false 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( Column(
modifier = Modifier.padding(start = 24.dp)
) {
Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 8.dp) .background(
color = appColors.background,
shape = RoundedCornerShape(AccountSettingConstants.CARD_CORNER_RADIUS.dp)
)
) { ) {
NavItem( SecurityOptionItem(
iconRes = R.mipmap.rider_pro_change_password, iconRes = R.mipmap.icons_padlock,
label = stringResource(R.string.change_password), label = stringResource(R.string.change_password),
modifier = Modifier.noRippleClickable { onClick = { navController.navigate(NavigationRoute.ChangePasswordScreen.route) }
navController.navigate(NavigationRoute.ChangePasswordScreen.route)
}
) )
}
// 分割线 SecurityOptionItem(
Box( iconRes = R.mipmap.icons_block,
modifier = Modifier label = stringResource(R.string.blocked_users),
.fillMaxWidth() onClick = {
.height(1.dp) // TODO: 导航到屏蔽用户页面
.background(appColors.divider) }
) )
Box(
modifier = Modifier SecurityOptionItem(
.fillMaxWidth() iconRes = R.mipmap.icons_remove,
.padding(vertical = 8.dp)
) {
NavItem(
iconRes = R.drawable.rider_pro_moment_delete,
label = stringResource(R.string.remove_account), label = stringResource(R.string.remove_account),
modifier = Modifier.noRippleClickable { onClick = { navController.navigate(NavigationRoute.RemoveAccountScreen.route) },
navController.navigate(NavigationRoute.RemoveAccountScreen.route) applyColorFilter = false
}
) )
} }
Box(
modifier = Modifier
.fillMaxWidth()
.height(1.dp)
.background(appColors.divider)
)
}
} }
} }

View File

@@ -9,9 +9,9 @@ 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.CreateAgentRuleRequestBody
import com.aiosman.ravenow.data.api.PromptRule import com.aiosman.ravenow.data.api.AgentRule
import com.aiosman.ravenow.data.api.PromptRuleQuota import com.aiosman.ravenow.data.api.AgentRuleQuota
import com.aiosman.ravenow.data.parseErrorResponse 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
@@ -33,8 +33,8 @@ class GroupChatInfoViewModel(
val notificationStrategy get() = chatNotification?.strategy ?: "default" val notificationStrategy get() = chatNotification?.strategy ?: "default"
// 记忆管理相关状态 // 记忆管理相关状态
var memoryQuota by mutableStateOf<PromptRuleQuota?>(null) var memoryQuota by mutableStateOf<AgentRuleQuota?>(null)
var memoryList by mutableStateOf<List<PromptRule>>(emptyList()) var memoryList by mutableStateOf<List<AgentRule>>(emptyList())
var isLoadingMemory by mutableStateOf(false) var isLoadingMemory by mutableStateOf(false)
var memoryError by mutableStateOf<String?>(null) var memoryError by mutableStateOf<String?>(null)
var promptOpenId by mutableStateOf<String?>(null) var promptOpenId by mutableStateOf<String?>(null)
@@ -137,12 +137,12 @@ class GroupChatInfoViewModel(
} }
// 创建智能体规则(群记忆) // 创建智能体规则(群记忆)
val requestBody = CreatePromptRuleRequestBody( val requestBody = CreateAgentRuleRequestBody(
rule = memoryText, rule = memoryText,
openId = openId openId = openId
) )
val response = ApiClient.api.createPromptRule(requestBody) val response = ApiClient.api.createAgentRule(requestBody)
if (response.isSuccessful) { if (response.isSuccessful) {
addMemorySuccess = true addMemorySuccess = true
@@ -184,14 +184,14 @@ class GroupChatInfoViewModel(
?: throw Exception("无法获取智能体信息") ?: throw Exception("无法获取智能体信息")
promptOpenId = fetchedOpenId promptOpenId = fetchedOpenId
val quotaResponse = ApiClient.api.getPromptRuleQuota(fetchedOpenId) val quotaResponse = ApiClient.api.getAgentRuleQuota(fetchedOpenId)
if (quotaResponse.isSuccessful) { if (quotaResponse.isSuccessful) {
memoryQuota = quotaResponse.body()?.data memoryQuota = quotaResponse.body()?.data
} else { } else {
throw Exception("获取配额信息失败: ${quotaResponse.code()}") throw Exception("获取配额信息失败: ${quotaResponse.code()}")
} }
} else { } else {
val quotaResponse = ApiClient.api.getPromptRuleQuota(targetOpenId) val quotaResponse = ApiClient.api.getAgentRuleQuota(targetOpenId)
if (quotaResponse.isSuccessful) { if (quotaResponse.isSuccessful) {
memoryQuota = quotaResponse.body()?.data memoryQuota = quotaResponse.body()?.data
} else { } else {
@@ -226,14 +226,14 @@ class GroupChatInfoViewModel(
?: throw Exception("无法获取智能体信息") ?: throw Exception("无法获取智能体信息")
promptOpenId = fetchedOpenId 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) { if (listResponse.isSuccessful) {
memoryList = listResponse.body()?.data?.list ?: emptyList() memoryList = listResponse.body()?.data?.list ?: emptyList()
} else { } else {
throw Exception("获取记忆列表失败: ${listResponse.code()}") throw Exception("获取记忆列表失败: ${listResponse.code()}")
} }
} else { } else {
val listResponse = ApiClient.api.getPromptRuleList(targetOpenId, page = page, pageSize = pageSize) val listResponse = ApiClient.api.getAgentRuleList(targetOpenId, page = page, pageSize = pageSize)
if (listResponse.isSuccessful) { if (listResponse.isSuccessful) {
memoryList = listResponse.body()?.data?.list ?: emptyList() memoryList = listResponse.body()?.data?.list ?: emptyList()
} else { } else {
@@ -258,7 +258,7 @@ class GroupChatInfoViewModel(
isLoadingMemory = true isLoadingMemory = true
memoryError = null memoryError = null
val response = ApiClient.api.deletePromptRule(ruleId) val response = ApiClient.api.deleteAgentRule(ruleId)
if (response.isSuccessful) { if (response.isSuccessful) {
// 刷新记忆列表和配额 // 刷新记忆列表和配额
promptOpenId?.let { openId -> promptOpenId?.let { openId ->
@@ -292,13 +292,13 @@ class GroupChatInfoViewModel(
val openId = targetOpenId ?: promptOpenId val openId = targetOpenId ?: promptOpenId
?: throw Exception("无法获取智能体ID") ?: throw Exception("无法获取智能体ID")
val requestBody = com.aiosman.ravenow.data.api.UpdatePromptRuleRequestBody( val requestBody = com.aiosman.ravenow.data.api.UpdateAgentRuleRequestBody(
id = ruleId, id = ruleId,
rule = newRuleText, rule = newRuleText,
openId = openId openId = openId
) )
val response = ApiClient.api.updatePromptRule(requestBody) val response = ApiClient.api.updateAgentRule(requestBody)
if (response.isSuccessful) { if (response.isSuccessful) {
// 刷新记忆列表和配额 // 刷新记忆列表和配额
loadMemoryQuota(openId) loadMemoryQuota(openId)

View File

@@ -257,7 +257,7 @@ fun GroupMemoryManageContent(
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun EditGroupMemoryDialog( fun EditGroupMemoryDialog(
memory: com.aiosman.ravenow.data.api.PromptRule, memory: com.aiosman.ravenow.data.api.AgentRule,
viewModel: GroupChatInfoViewModel, viewModel: GroupChatInfoViewModel,
onDismiss: () -> Unit, onDismiss: () -> Unit,
onUpdateMemory: (String) -> Unit onUpdateMemory: (String) -> Unit
@@ -403,7 +403,7 @@ fun EditGroupMemoryDialog(
*/ */
@Composable @Composable
fun MemoryItem( fun MemoryItem(
memory: com.aiosman.ravenow.data.api.PromptRule, memory: com.aiosman.ravenow.data.api.AgentRule,
isEditing: Boolean = false, isEditing: Boolean = false,
onEdit: () -> Unit = {}, onEdit: () -> Unit = {},
onCancel: () -> Unit = {}, onCancel: () -> Unit = {},