账号安全界面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
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
)
}
}
}

View File

@@ -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<PromptRuleQuota?>(null)
var memoryList by mutableStateOf<List<PromptRule>>(emptyList())
var memoryQuota by mutableStateOf<AgentRuleQuota?>(null)
var memoryList by mutableStateOf<List<AgentRule>>(emptyList())
var isLoadingMemory by mutableStateOf(false)
var memoryError by mutableStateOf<String?>(null)
var promptOpenId by mutableStateOf<String?>(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)

View File

@@ -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 = {},