@@ -33,10 +33,13 @@ import com.aiosman.ravenow.R
|
||||
import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.aiosman.ravenow.ui.composables.ActionButton
|
||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||
import com.aiosman.ravenow.ui.composables.PointsPaymentDialog
|
||||
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
||||
import com.aiosman.ravenow.ui.composables.form.FormTextInput
|
||||
import com.aiosman.ravenow.ui.composables.form.FormTextInput2
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
import com.aiosman.ravenow.data.PointService
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
@@ -71,23 +74,30 @@ fun AiPromptEditScreen(
|
||||
var errorMessage by remember { mutableStateOf<String?>(null) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
// 获取积分规则和余额
|
||||
val pointsRules by PointService.pointsRules.collectAsState(initial = null)
|
||||
val pointsBalance by PointService.pointsBalance.collectAsState(initial = null)
|
||||
|
||||
// 计算是否需要付费
|
||||
val needsPayment = viewModel.needsPrivacyPayment()
|
||||
val privacyCost = 100 // 默认100钥匙,后续可以从PointService获取
|
||||
val privacyCost = viewModel.getPrivacyCost()
|
||||
val currentBalance = viewModel.getCurrentBalance()
|
||||
val balanceAfterCost = viewModel.calculateBalanceAfterCost(privacyCost)
|
||||
val isBalanceSufficient = viewModel.isBalanceSufficient(privacyCost)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = appColors.background),
|
||||
.background(color = Color(0xFFFAFAFB)),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
StatusBarSpacer()
|
||||
|
||||
|
||||
// 顶部导航栏
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(color = appColors.background)
|
||||
.background(color = Color(0xFFFAFAFB))
|
||||
.padding(horizontal = 14.dp, vertical = 16.dp)
|
||||
) {
|
||||
Row(
|
||||
@@ -117,15 +127,15 @@ fun AiPromptEditScreen(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.height(1.dp))
|
||||
|
||||
|
||||
// 内容区域
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
.background(appColors.background)
|
||||
.background(Color(0xFFFAFAFB))
|
||||
) {
|
||||
// 头像选择
|
||||
Column(
|
||||
@@ -195,9 +205,9 @@ fun AiPromptEditScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.height(18.dp))
|
||||
|
||||
|
||||
// 名称输入
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -214,7 +224,7 @@ fun AiPromptEditScreen(
|
||||
FormTextInput(
|
||||
value = viewModel.title,
|
||||
hint = stringResource(R.string.agent_name_hint_1),
|
||||
background = appColors.inputBackground2,
|
||||
background = Color.White,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) { value ->
|
||||
viewModel.title = value
|
||||
@@ -222,7 +232,7 @@ fun AiPromptEditScreen(
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(18.dp))
|
||||
|
||||
|
||||
// 描述输入
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -239,15 +249,15 @@ fun AiPromptEditScreen(
|
||||
FormTextInput2(
|
||||
value = viewModel.desc,
|
||||
hint = stringResource(R.string.agent_desc_hint),
|
||||
background = appColors.inputBackground2,
|
||||
background = Color.White,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) { value ->
|
||||
viewModel.desc = value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Spacer(modifier = Modifier.height(18.dp))
|
||||
|
||||
|
||||
// 设定权限区域
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -261,13 +271,18 @@ fun AiPromptEditScreen(
|
||||
fontWeight = FontWeight.W600
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
|
||||
// 公开/私有切换
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(appColors.inputBackground2)
|
||||
.clip(RoundedCornerShape(25.dp))
|
||||
.background(Color.White)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = Color(red = 124f / 255f, green = 116f / 255f, blue = 128f / 255f, alpha = 0.08f),
|
||||
shape = RoundedCornerShape(25.dp)
|
||||
)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
@@ -298,58 +313,89 @@ fun AiPromptEditScreen(
|
||||
modifier = Modifier.size(width = 64.dp, height = 28.dp)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// 首次解锁AI权限提示
|
||||
if (needsPayment && !viewModel.paidForPrivacyEdit) {
|
||||
if (needsPayment && !viewModel.paidForPrivacyEdit && privacyCost > 0) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
// 主要内容容器(去掉阴影)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(appColors.inputBackground2.copy(alpha = 0.9f))
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(
|
||||
color = Color(red = 251f / 255f, green = 248f / 255f, blue = 239f / 255f)
|
||||
)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = Color(red = 243f / 255f, green = 234f / 255f, blue = 206f / 255f),
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
)
|
||||
.padding(12.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
// 锁图标容器
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(32.dp)
|
||||
.background(
|
||||
color = Color(red = 1f, green = 204f / 255f, blue = 0f, alpha = 0.12f),
|
||||
shape = RoundedCornerShape(10.7.dp)
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
// 锁图标(使用文本代替,实际项目中可以使用图片资源)
|
||||
Text(
|
||||
text = "🔒",
|
||||
fontSize = 18.sp
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
text = "首次解锁 AI 权限",
|
||||
text = "首次解锁Ai权限",
|
||||
fontSize = 13.sp,
|
||||
color = appColors.text,
|
||||
color = Color(red = 172f / 255f, green = 127f / 255f, blue = 94f / 255f),
|
||||
fontWeight = FontWeight.W500
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "将消耗",
|
||||
fontSize = 12.sp,
|
||||
color = appColors.text.copy(alpha = 0.6f)
|
||||
color = Color(red = 172f / 255f, green = 127f / 255f, blue = 94f / 255f)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = "$privacyCost",
|
||||
fontSize = 12.sp,
|
||||
color = appColors.brandColorsColor,
|
||||
fontWeight = FontWeight.W500
|
||||
color = Color(red = 1f, green = 141f / 255f, blue = 40f / 255f)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(2.dp))
|
||||
Text(
|
||||
text = "钥匙",
|
||||
fontSize = 12.sp,
|
||||
color = appColors.brandColorsColor,
|
||||
fontWeight = FontWeight.W500
|
||||
// 小硬币图标
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(16.dp)
|
||||
.background(
|
||||
brush = Brush.linearGradient(
|
||||
colors = listOf(
|
||||
Color(0xFFFFD700),
|
||||
Color(0xFFFFA500)
|
||||
)
|
||||
),
|
||||
shape = CircleShape
|
||||
)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
text = "解锁后可随时切换",
|
||||
fontSize = 12.sp,
|
||||
color = appColors.text.copy(alpha = 0.6f)
|
||||
color = Color(red = 172f / 255f, green = 127f / 255f, blue = 94f / 255f)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -357,23 +403,13 @@ fun AiPromptEditScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(18.dp))
|
||||
|
||||
// 添加智能体记忆按钮
|
||||
AddAgentMemoryButton(
|
||||
onAddMemoryClick = {
|
||||
// TODO: 导航到记忆管理页面
|
||||
// navController.navigate(NavigationRoute.AgentMemoryManage.route.replace("{chatAIId}", chatAIId))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// 底部保存按钮
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(color = appColors.background)
|
||||
.background(color = Color(0xFFFAFAFB))
|
||||
.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||
) {
|
||||
ActionButton(
|
||||
@@ -388,13 +424,13 @@ fun AiPromptEditScreen(
|
||||
errorMessage = validationError
|
||||
return@ActionButton
|
||||
}
|
||||
|
||||
|
||||
// 检查是否需要付费确认
|
||||
if (needsPayment && !viewModel.paidForPrivacyEdit) {
|
||||
showPrivacyConfirmDialog = true
|
||||
return@ActionButton
|
||||
}
|
||||
|
||||
|
||||
// 执行保存
|
||||
scope.launch {
|
||||
try {
|
||||
@@ -407,44 +443,35 @@ fun AiPromptEditScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 隐私权限付费确认对话框
|
||||
if (showPrivacyConfirmDialog) {
|
||||
// TODO: 实现付费确认对话框
|
||||
// 暂时直接切换,后续可以添加积分检查和扣减逻辑
|
||||
androidx.compose.material3.AlertDialog(
|
||||
onDismissRequest = { showPrivacyConfirmDialog = false },
|
||||
title = { Text("升级隐私权限") },
|
||||
text = { Text("首次切换智能体的公开/私有状态需要支付一次性费用。支付后可自由在公有/私有之间切换,后续不再扣费。") },
|
||||
confirmButton = {
|
||||
androidx.compose.material3.TextButton(
|
||||
onClick = {
|
||||
showPrivacyConfirmDialog = false
|
||||
scope.launch {
|
||||
try {
|
||||
viewModel.isPublic = false
|
||||
viewModel.updatePrompt(context)
|
||||
viewModel.paidForPrivacyEdit = true
|
||||
navController.navigateUp()
|
||||
} catch (e: Exception) {
|
||||
errorMessage = e.message ?: "保存失败"
|
||||
}
|
||||
}
|
||||
PointsPaymentDialog(
|
||||
cost = privacyCost,
|
||||
currentBalance = currentBalance,
|
||||
balanceAfterCost = balanceAfterCost,
|
||||
isBalanceSufficient = isBalanceSufficient,
|
||||
onConfirm = {
|
||||
showPrivacyConfirmDialog = false
|
||||
scope.launch {
|
||||
try {
|
||||
viewModel.isPublic = false
|
||||
viewModel.updatePrompt(context)
|
||||
viewModel.paidForPrivacyEdit = true
|
||||
navController.navigateUp()
|
||||
} catch (e: Exception) {
|
||||
errorMessage = e.message ?: "保存失败"
|
||||
}
|
||||
) {
|
||||
Text("确认支付")
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
androidx.compose.material3.TextButton(
|
||||
onClick = { showPrivacyConfirmDialog = false }
|
||||
) {
|
||||
Text("取消")
|
||||
}
|
||||
}
|
||||
onCancel = {
|
||||
showPrivacyConfirmDialog = false
|
||||
},
|
||||
title = "首次解锁AI权限",
|
||||
description = "将消耗 $privacyCost 派币解锁后可随时切换"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// 错误提示
|
||||
errorMessage?.let { error ->
|
||||
LaunchedEffect(error) {
|
||||
@@ -454,72 +481,3 @@ fun AiPromptEditScreen(
|
||||
// TODO: 显示Toast或Snackbar
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddAgentMemoryButton(
|
||||
onAddMemoryClick: () -> Unit
|
||||
) {
|
||||
val appColors = LocalAppTheme.current
|
||||
|
||||
// 定义渐变边框颜色:紫色到蓝色
|
||||
val borderGradient = Brush.horizontalGradient(
|
||||
colors = listOf(
|
||||
Color(0xFF7C45ED), // 紫色
|
||||
Color(0xFF4A90E2) // 蓝色
|
||||
)
|
||||
)
|
||||
|
||||
// 浅紫色背景
|
||||
val lightPurpleBackground = Color(0xFFF5F0FF)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
// 使用两层Box来实现渐变边框效果
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(brush = borderGradient)
|
||||
.padding(1.5.dp) // 边框宽度
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(lightPurpleBackground)
|
||||
.padding(horizontal = 16.dp, vertical = 14.dp)
|
||||
.noRippleClickable {
|
||||
onAddMemoryClick()
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
// 脑部图标
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_brain_add),
|
||||
contentDescription = "添加智能体记忆",
|
||||
modifier = Modifier.size(20.dp),
|
||||
colorFilter = ColorFilter.tint(Color(0xFF7C45ED))
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
// 文字
|
||||
Text(
|
||||
text = "添加智能体记忆",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.W600,
|
||||
color = Color(0xFF7C45ED)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aiosman.ravenow.data.UploadImage
|
||||
import com.aiosman.ravenow.data.ServiceException
|
||||
import com.aiosman.ravenow.data.PointService
|
||||
import com.aiosman.ravenow.data.api.ApiClient
|
||||
import com.aiosman.ravenow.entity.AgentEntity
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -145,6 +146,47 @@ class AiPromptEditViewModel : ViewModel() {
|
||||
return originalIsPublic == true && isPublic == false
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取解锁隐私权限的费用
|
||||
* @return 费用金额,如果无法获取则返回 0
|
||||
*/
|
||||
fun getPrivacyCost(): Int {
|
||||
val rules = PointService.pointsRules.value
|
||||
val costRule = rules?.sub?.get(PointService.PointsRuleKey.SPEND_AGENT_PRIVATE)
|
||||
return when (costRule) {
|
||||
is PointService.RuleAmount.Fixed -> costRule.value
|
||||
is PointService.RuleAmount.Range -> costRule.min // 使用最小值作为默认费用
|
||||
null -> 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前余额
|
||||
* @return 当前余额,如果无法获取则返回 0
|
||||
*/
|
||||
fun getCurrentBalance(): Int {
|
||||
return PointService.pointsBalance.value?.balance ?: 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算消耗后余额
|
||||
* @param cost 费用
|
||||
* @return 消耗后余额
|
||||
*/
|
||||
fun calculateBalanceAfterCost(cost: Int): Int {
|
||||
val currentBalance = getCurrentBalance()
|
||||
return (currentBalance - cost).coerceAtLeast(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查余额是否充足
|
||||
* @param cost 费用
|
||||
* @return 是否充足
|
||||
*/
|
||||
fun isBalanceSufficient(cost: Int): Boolean {
|
||||
return getCurrentBalance() >= cost
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空数据
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
package com.aiosman.ravenow.ui.composables
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Star
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
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 androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.aiosman.ravenow.LocalAppTheme
|
||||
import com.aiosman.ravenow.R
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
|
||||
/**
|
||||
* 全局付费确认对话框组件
|
||||
* 参考 iOS 版本的 PointsConfirmDialog
|
||||
*
|
||||
* @param cost 需要支付的费用
|
||||
* @param currentBalance 当前余额
|
||||
* @param balanceAfterCost 支付后余额
|
||||
* @param isBalanceSufficient 余额是否充足
|
||||
* @param onConfirm 确认支付回调
|
||||
* @param onCancel 取消回调
|
||||
* @param title 对话框标题
|
||||
* @param description 对话框描述
|
||||
* @param isProcessing 是否正在处理中
|
||||
*/
|
||||
@Composable
|
||||
fun PointsPaymentDialog(
|
||||
cost: Int,
|
||||
currentBalance: Int,
|
||||
balanceAfterCost: Int,
|
||||
isBalanceSufficient: Boolean,
|
||||
onConfirm: () -> Unit,
|
||||
onCancel: () -> Unit,
|
||||
title: String,
|
||||
description: String,
|
||||
isProcessing: Boolean = false
|
||||
) {
|
||||
val appColors = LocalAppTheme.current
|
||||
val configuration = LocalConfiguration.current
|
||||
val screenWidth = configuration.screenWidthDp.dp
|
||||
val dialogWidth = (screenWidth - 48.dp).coerceAtMost(360.dp)
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = {
|
||||
if (!isProcessing) {
|
||||
onCancel()
|
||||
}
|
||||
},
|
||||
properties = DialogProperties(
|
||||
dismissOnBackPress = !isProcessing,
|
||||
dismissOnClickOutside = !isProcessing
|
||||
)
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.width(dialogWidth)
|
||||
.shadow(
|
||||
elevation = 20.dp,
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
spotColor = Color.Black.copy(alpha = 0.2f)
|
||||
),
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = appColors.background
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// 顶部图标 - 使用 paip_coin_img
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.paip_coin_img),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(80.dp),
|
||||
contentScale = ContentScale.Fit
|
||||
)
|
||||
|
||||
// 标题
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = title,
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = appColors.text,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
|
||||
// 描述
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = description,
|
||||
fontSize = 14.sp,
|
||||
color = appColors.secondaryText,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(horizontal = 20.dp)
|
||||
)
|
||||
|
||||
// 积分消耗信息区域
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
color = appColors.inputBackground.copy(alpha = 0.5f),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
// 需要消耗
|
||||
CostInfoRow(
|
||||
label = stringResource(R.string.cost_required),
|
||||
amount = cost,
|
||||
appColors = appColors,
|
||||
amountColor = Color(0xFFFF8C00) // 橙色
|
||||
)
|
||||
|
||||
HorizontalDivider(color = appColors.divider)
|
||||
|
||||
// 当前余额
|
||||
CostInfoRow(
|
||||
label = stringResource(R.string.current_balance),
|
||||
amount = currentBalance,
|
||||
appColors = appColors,
|
||||
amountColor = if (isBalanceSufficient) appColors.text else Color.Red
|
||||
)
|
||||
|
||||
HorizontalDivider(color = appColors.divider)
|
||||
|
||||
// 支付后余额
|
||||
CostInfoRow(
|
||||
label = stringResource(R.string.balance_after),
|
||||
amount = balanceAfterCost,
|
||||
appColors = appColors,
|
||||
amountColor = appColors.text
|
||||
)
|
||||
}
|
||||
|
||||
// 余额不足提示
|
||||
if (!isBalanceSufficient) {
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Warning,
|
||||
contentDescription = null,
|
||||
tint = Color(0xFFFF8C00), // 橙色
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.insufficient_pai_coin_balance),
|
||||
fontSize = 13.sp,
|
||||
color = Color(0xFFFF8C00), // 橙色
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 按钮
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
// 取消按钮
|
||||
Button(
|
||||
onClick = {
|
||||
if (!isProcessing) {
|
||||
onCancel()
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(50.dp),
|
||||
enabled = !isProcessing,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = appColors.inputBackground,
|
||||
contentColor = appColors.text,
|
||||
disabledContainerColor = appColors.inputBackground,
|
||||
disabledContentColor = appColors.text.copy(alpha = 0.5f)
|
||||
),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.cancel),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.W500
|
||||
)
|
||||
}
|
||||
|
||||
// 确认按钮
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(50.dp)
|
||||
.background(
|
||||
brush = if (isBalanceSufficient) {
|
||||
Brush.horizontalGradient(
|
||||
colors = listOf(
|
||||
appColors.main,
|
||||
appColors.main
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Brush.horizontalGradient(
|
||||
colors = listOf(
|
||||
Color(0xFFFF8C00), // 橙色
|
||||
Color.Red
|
||||
)
|
||||
)
|
||||
},
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
.then(
|
||||
if (!isProcessing) {
|
||||
Modifier.noRippleClickable {
|
||||
if (!isBalanceSufficient) {
|
||||
// 积分不足,跳转充值页面
|
||||
onCancel()
|
||||
// 这里可以发送通知或回调来跳转充值页面
|
||||
} else {
|
||||
// 积分充足,确认消费
|
||||
onConfirm()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
if (isProcessing) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(20.dp),
|
||||
color = Color.White,
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = if (isBalanceSufficient) {
|
||||
stringResource(R.string.confirm_consumption)
|
||||
} else {
|
||||
stringResource(R.string.go_recharge)
|
||||
},
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.W600,
|
||||
color = Color.White,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 费用信息行组件
|
||||
*/
|
||||
@Composable
|
||||
private fun CostInfoRow(
|
||||
label: String,
|
||||
amount: Int,
|
||||
appColors: com.aiosman.ravenow.AppThemeData,
|
||||
amountColor: Color? = null
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
fontSize = 14.sp,
|
||||
color = appColors.secondaryText
|
||||
)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
// 星形图标(参考 iOS 版本)
|
||||
Icon(
|
||||
imageVector = Icons.Default.Star,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(14.dp),
|
||||
tint = Color(0xFFFFD700) // 黄色
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "${amount.formatNumber()}",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.W600,
|
||||
color = amountColor ?: appColors.text
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.pai_coin),
|
||||
fontSize = 14.sp,
|
||||
color = appColors.secondaryText
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数字,添加千位分隔符
|
||||
*/
|
||||
private fun Int.formatNumber(): String {
|
||||
return this.toString().reversed().chunked(3).joinToString(",").reversed()
|
||||
}
|
||||
|
||||
@@ -67,6 +67,13 @@ fun FormTextInput(
|
||||
.let {
|
||||
if (error != null) {
|
||||
it.border(1.dp, AppColors.error, RoundedCornerShape(24.dp))
|
||||
} else if (background != null && background == Color.White) {
|
||||
// 如果传入白色背景,添加灰色边框
|
||||
it.border(
|
||||
1.dp,
|
||||
Color(red = 124f / 255f, green = 116f / 255f, blue = 128f / 255f, alpha = 0.08f),
|
||||
RoundedCornerShape(25.dp)
|
||||
)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
|
||||
@@ -68,6 +68,13 @@ fun FormTextInput2(
|
||||
.let {
|
||||
if (error != null) {
|
||||
it.border(1.dp, AppColors.error, RoundedCornerShape(24.dp))
|
||||
} else if (background != null && background == Color.White) {
|
||||
// 如果传入白色背景,添加灰色边框
|
||||
it.border(
|
||||
1.dp,
|
||||
Color(red = 124f / 255f, green = 116f / 255f, blue = 128f / 255f, alpha = 0.08f),
|
||||
RoundedCornerShape(25.dp)
|
||||
)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
|
||||
@@ -376,6 +376,11 @@
|
||||
<string name="create_group_chat_insufficient_balance">余额不足</string>
|
||||
<string name="create_group_chat_exceed_limit">成员数量超过上限(%1$d)</string>
|
||||
<string name="pai_coin">派币</string>
|
||||
<string name="cost_required">需要消耗</string>
|
||||
<string name="balance_after">支付后余额</string>
|
||||
<string name="insufficient_pai_coin_balance">派币余额不足</string>
|
||||
<string name="go_recharge">去充值</string>
|
||||
<string name="confirm_consumption">确认消费</string>
|
||||
<string name="connect_world_start_following">连接世界,从关注开始</string>
|
||||
<string name="why_not_start_with_agent">不如从一个 Agent 开始认识这世界?</string>
|
||||
<string name="explore">去探索</string>
|
||||
|
||||
@@ -369,6 +369,11 @@
|
||||
<string name="create_group_chat_insufficient_balance">Insufficient balance</string>
|
||||
<string name="create_group_chat_exceed_limit">Member count exceeds the limit (%1$d)</string>
|
||||
<string name="pai_coin">Pai Coin</string>
|
||||
<string name="cost_required">Cost Required</string>
|
||||
<string name="balance_after">Balance After</string>
|
||||
<string name="insufficient_pai_coin_balance">Insufficient Pai Coin Balance</string>
|
||||
<string name="go_recharge">Go Recharge</string>
|
||||
<string name="confirm_consumption">Confirm Consumption</string>
|
||||
<string name="connect_world_start_following">Connect the world, start by following</string>
|
||||
<string name="why_not_start_with_agent">Why not start exploring the world with an Agent?</string>
|
||||
<string name="explore">Explore</string>
|
||||
|
||||
Reference in New Issue
Block a user