账号安全界面ui设置
This commit is contained in:
@@ -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)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 = {},
|
||||||
|
|||||||
Reference in New Issue
Block a user