新增创建群聊的费用和人数上限功能

- **创建群聊费用:**
  - 创建群聊现在会根据后台配置的积分规则扣除相应费用(派币)。
  - 在创建页面会显示当前余额和所需费用。
  - 创建时会弹出确认弹窗,显示费用、当前余额和扣除后余额。
  - 如果余额不足,将无法创建。

- **群聊人数上限:**
  - 新增创建群聊时的初始成员人数上限,该上限从后台动态获取。
  - 在选择成员界面会显示当前已选人数和上限(例如 `5/10`)。
  - 如果选择的成员超过上限,会提示错误并且无法创建。

- **后台数据加载:**
  - 新增了从外部字典表 (`/outside/dict`) 获取配置的接口和逻辑,用于加载积分规则和群聊人数限制。
  - App启动时会预加载这些配置,以确保创建群聊时能正确显示费用和人数限制。
This commit is contained in:
2025-11-12 17:23:20 +08:00
parent 4135583758
commit ca16d54823
9 changed files with 580 additions and 33 deletions

View File

@@ -32,6 +32,7 @@ import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel
import com.aiosman.ravenow.ui.like.LikeNoticeViewModel
import com.aiosman.ravenow.utils.Utils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlin.coroutines.suspendCoroutine
import com.aiosman.ravenow.im.OpenIMManager
import io.openim.android.sdk.OpenIMClient
@@ -51,7 +52,7 @@ object AppState {
suspend fun initWithAccount(scope: CoroutineScope, context: Context) {
// 如果是游客模式,使用简化的初始化流程
if (AppStore.isGuest) {
initWithGuestAccount()
initWithGuestAccount(scope)
return
}
@@ -90,18 +91,50 @@ object AppState {
} catch (e: Exception) {
Log.e("AppState", "刷新积分失败: ${e.message}")
}
// 并行加载积分规则和房间规则配置(不阻塞主流程)
scope.launch {
try {
PointService.refreshPointsRules()
} catch (e: Exception) {
Log.e("AppState", "加载积分规则失败: ${e.message}")
}
}
scope.launch {
try {
PointService.refreshRoomMaxMembers()
} catch (e: Exception) {
Log.e("AppState", "加载房间规则失败: ${e.message}")
}
}
}
/**
* 游客模式的简化初始化
*/
private fun initWithGuestAccount() {
private fun initWithGuestAccount(scope: CoroutineScope) {
// 游客模式下不初始化推送和TRTC
// 设置默认的用户信息
UserId = 0
profile = null
enableChat = false
Log.d("AppState", "Guest mode initialized without push notifications and TRTC")
// 游客模式下也加载规则配置(用于查看费用信息)
scope.launch {
try {
PointService.refreshPointsRules()
} catch (e: Exception) {
Log.e("AppState", "加载积分规则失败: ${e.message}")
}
}
scope.launch {
try {
PointService.refreshRoomMaxMembers()
} catch (e: Exception) {
Log.e("AppState", "加载房间规则失败: ${e.message}")
}
}
}
private suspend fun initChat(context: Context){

View File

@@ -14,6 +14,16 @@ interface DictService {
* 获取字典列表
*/
suspend fun getDistList(keys: List<String>): List<DictItem>
/**
* 获取外部字典项
*/
suspend fun getOutsideDictByKey(key: String): DictItem
/**
* 获取外部字典列表
*/
suspend fun getOutsideDistList(keys: List<String>): List<DictItem>
}
class DictServiceImpl : DictService {
@@ -26,4 +36,13 @@ class DictServiceImpl : DictService {
val resp = ApiClient.api.getDicts(keys.joinToString(","))
return resp.body()?.list ?: throw Exception("failed to get dict list")
}
override suspend fun getOutsideDictByKey(key: String): DictItem {
val resp = ApiClient.api.getOutsideDict(key)
return resp.body()?.data ?: throw Exception("failed to get outside dict")
}
override suspend fun getOutsideDistList(keys: List<String>): List<DictItem> {
val resp = ApiClient.api.getOutsideDicts(keys.joinToString(","))
return resp.body()?.list ?: throw Exception("failed to get outside dict list")
}
}

View File

@@ -26,6 +26,36 @@ object PointService {
private val dictService: DictService = DictServiceImpl()
private val gson = Gson()
/**
* 积分规则key常量
* 对应积分规则JSON中的key值
*/
object PointsRuleKey {
// 获得积分类型add
/** 每日登录奖励 */
const val DAILY_LOGIN = "daily_login"
/** 用户注册奖励 */
const val USER_REGISTER = "user_register"
// 消费积分类型sub
/** 添加Agent记忆 */
const val ADD_AGENT_MEMORY = "add_agent_memory"
/** 增加房间容量 */
const val ADD_ROOM_CAP = "add_room_cap"
/** 创建房间 */
const val CREATE_ROOM = "create_room"
/** 创建定时事件 */
const val CREATE_SCHEDULE_EVENT = "create_schedule_event"
/** 房间私密模式 */
const val ROOM_PRIVATE = "room_private"
/** Agent私密模式 */
const val SPEND_AGENT_PRIVATE = "spend_agent_private"
/** 自定义聊天背景 */
const val SPEND_CHAT_BACKGROUND = "spend_chat_background"
/** 房间记忆添加 */
const val SPEND_ROOM_MEMORY = "spend_room_memory"
}
sealed class RuleAmount {
data class Fixed(val value: Int) : RuleAmount()
data class Range(val min: Int, val max: Int) : RuleAmount()
@@ -50,6 +80,64 @@ object PointService {
}
}
// ========== 群聊人数限制(字典 points-rule相关 ==========
/**
* 群聊人数限制配置
* @param defaultMaxTotal 初始最大人数(默认值)
* @param maxTotal 最大人数(上限)
*/
data class RoomMaxMembers(
val defaultMaxTotal: Int,
val maxTotal: Int
)
private val _roomMaxMembers = MutableStateFlow<RoomMaxMembers?>(null)
val roomMaxMembers: StateFlow<RoomMaxMembers?> = _roomMaxMembers.asStateFlow()
/**
* 刷新群聊人数限制配置(从外部字典表加载 points-rule
* 加载时机与 refreshPointsRules 一致
*/
suspend fun refreshRoomMaxMembers(key: String = "points-rule") {
withContext(Dispatchers.IO) {
try {
val dict = dictService.getOutsideDictByKey(key)
val config = parseRoomMaxMembers(dict)
_roomMaxMembers.value = config
} catch (_: Exception) {
_roomMaxMembers.value = null
}
}
}
/**
* 解析群聊人数限制配置
* 解析格式:{"room":{"default":{"max-total":5},"max":{"max-total":200}}}
*/
private fun parseRoomMaxMembers(dict: DictItem): RoomMaxMembers? {
val raw = dict.value
val jsonStr = when (raw) {
is String -> raw
else -> gson.toJson(raw)
}
return try {
val root = JsonParser.parseString(jsonStr).asJsonObject
val roomObj = root.getAsJsonObject("room")
val defaultObj = roomObj?.getAsJsonObject("default")
val maxObj = roomObj?.getAsJsonObject("max")
val defaultMaxTotal = defaultObj?.get("max-total")?.takeIf { it.isJsonPrimitive }?.asInt ?: 5
val maxTotal = maxObj?.get("max-total")?.takeIf { it.isJsonPrimitive }?.asInt ?: 200
RoomMaxMembers(
defaultMaxTotal = defaultMaxTotal,
maxTotal = maxTotal
)
} catch (_: Exception) {
null
}
}
private fun parsePointsRules(dict: DictItem): PointsRules? {
val raw = dict.value
val jsonStr = when (raw) {

View File

@@ -1335,6 +1335,16 @@ interface RaveNowAPI {
@Query("keys") keys: String
): Response<ListContainer<DictItem>>
@GET("/outside/dict")
suspend fun getOutsideDict(
@Query("key") key: String
): Response<DataContainer<DictItem>>
@GET("/outside/dicts")
suspend fun getOutsideDicts(
@Query("keys") keys: String
): Response<ListContainer<DictItem>>
@POST("captcha/generate")
suspend fun generateCaptcha(
@Body body: CaptchaRequestBody

View File

@@ -3,6 +3,7 @@ package com.aiosman.ravenow.ui.group
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
@@ -14,11 +15,15 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.SolidColor
@@ -27,11 +32,15 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
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.LocalNavController
import com.aiosman.ravenow.R
import com.aiosman.ravenow.data.PointService
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
import com.aiosman.ravenow.ui.composables.TabItem
@@ -91,6 +100,19 @@ fun CreateGroupChatScreen() {
val navigationBarPadding = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
// 获取费用和余额信息
val pointsRules by PointService.pointsRules.collectAsState(initial = null)
val pointsBalance by PointService.pointsBalance.collectAsState(initial = null)
val roomMaxMembers by PointService.roomMaxMembers.collectAsState(initial = null)
val cost = CreateGroupChatViewModel.getCreateRoomCost()
val currentBalance = CreateGroupChatViewModel.getCurrentBalance()
val balanceAfterCost = CreateGroupChatViewModel.calculateBalanceAfterCost(cost)
val isBalanceSufficient = CreateGroupChatViewModel.isBalanceSufficient(cost)
// 获取群聊初始上限
val maxMemberLimit = roomMaxMembers?.defaultMaxTotal ?: 5
LaunchedEffect(Unit) {
systemUiController.setNavigationBarColor(Color.Transparent)
}
@@ -367,14 +389,19 @@ fun CreateGroupChatScreen() {
}
}
// Tab切换
// Tab切换和成员数量显示
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(horizontal = 16.dp, vertical = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
// Tab左对齐
Row(
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.Bottom
verticalAlignment = Alignment.CenterVertically
) {
TabItem(
text = stringResource(R.string.chat_ai),
@@ -397,6 +424,15 @@ fun CreateGroupChatScreen() {
)
}
// 成员数量显示右对齐x/x格式
Text(
text = "${selectedMembers.size}/$maxMemberLimit",
fontSize = 14.sp,
color = if (selectedMembers.size > maxMemberLimit) AppColors.error else AppColors.secondaryText,
fontWeight = FontWeight.W500
)
}
// 内容区域 - 自适应填满剩余高度
HorizontalPager(
state = pagerState,
@@ -436,11 +472,44 @@ fun CreateGroupChatScreen() {
}
}
// 余额和扣减积分显示(创建按钮上方)
val buttonTopPadding = if (cost > 0) 4.dp else 16.dp
if (cost > 0) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 4.dp),
horizontalArrangement = Arrangement.Center
) {
Text(
text = "${stringResource(R.string.create_group_chat_current_balance)}: ${currentBalance.formatNumber()} ${stringResource(R.string.pai_coin)}",
fontSize = 12.sp,
color = AppColors.secondaryText
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = "${stringResource(R.string.create_group_chat_required_cost)} ${cost.formatNumber()} ${stringResource(R.string.pai_coin)}",
fontSize = 12.sp,
color = AppColors.secondaryText
)
}
}
// 创建群聊按钮 - 固定在底部
Button(
onClick = {
// 创建群聊逻辑
if (selectedMembers.isNotEmpty()) {
// 检查是否超过上限
if (selectedMembers.size > maxMemberLimit) {
CreateGroupChatViewModel.showError(context.getString(R.string.create_group_chat_exceed_limit, maxMemberLimit))
return@Button
}
// 如果费用大于0显示确认弹窗
if (cost > 0) {
CreateGroupChatViewModel.showConfirmDialog()
} else {
// 费用为0直接创建
scope.launch {
val success = CreateGroupChatViewModel.createGroupChat(
groupName = groupName.text,
@@ -452,10 +521,11 @@ fun CreateGroupChatScreen() {
}
}
}
}
},
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = navigationBarPadding + 16.dp),
.padding(start = 16.dp, end = 16.dp, top = buttonTopPadding, bottom = navigationBarPadding + 16.dp),
colors = ButtonDefaults.buttonColors(
containerColor = AppColors.main,
contentColor = AppColors.mainText,
@@ -482,6 +552,38 @@ fun CreateGroupChatScreen() {
}
// 消费确认弹窗
if (CreateGroupChatViewModel.showConfirmDialog) {
CreateGroupChatConfirmDialog(
cost = cost,
currentBalance = currentBalance,
balanceAfterCost = balanceAfterCost,
isBalanceSufficient = isBalanceSufficient,
onConfirm = {
// 检查是否超过上限
if (selectedMembers.size > maxMemberLimit) {
CreateGroupChatViewModel.hideConfirmDialog()
CreateGroupChatViewModel.showError(context.getString(R.string.create_group_chat_exceed_limit, maxMemberLimit))
} else {
CreateGroupChatViewModel.hideConfirmDialog()
scope.launch {
val success = CreateGroupChatViewModel.createGroupChat(
groupName = groupName.text,
selectedMembers = selectedMembers,
context = context
)
if (success) {
navController.popBackStack()
}
}
}
},
onCancel = {
CreateGroupChatViewModel.hideConfirmDialog()
}
)
}
// 居中显示的错误提示弹窗
CreateGroupChatViewModel.errorMessage?.let { error ->
Box(
@@ -496,7 +598,7 @@ fun CreateGroupChatScreen() {
horizontalAlignment = Alignment.CenterHorizontally, // 水平居中
verticalArrangement = Arrangement.Center // 垂直居中
) {
androidx.compose.material3.Card(
Card(
modifier = Modifier
.fillMaxWidth(0.8f),
shape = RoundedCornerShape(8.dp)
@@ -508,7 +610,7 @@ fun CreateGroupChatScreen() {
.fillMaxWidth(),
color = Color.Red,
fontSize = 14.sp,
textAlign = androidx.compose.ui.text.style.TextAlign.Center
textAlign = TextAlign.Center
)
}
}
@@ -516,3 +618,219 @@ fun CreateGroupChatScreen() {
}
}
}
/**
* 创建群聊消费确认弹窗
*/
@Composable
fun CreateGroupChatConfirmDialog(
cost: Int,
currentBalance: Int,
balanceAfterCost: Int,
isBalanceSufficient: Boolean,
onConfirm: () -> Unit,
onCancel: () -> Unit
) {
val AppColors = LocalAppTheme.current
Dialog(
onDismissRequest = onCancel,
properties = DialogProperties(
dismissOnBackPress = true,
dismissOnClickOutside = true
)
) {
Card(
modifier = Modifier
.fillMaxWidth(0.9f)
.padding(16.dp),
shape = RoundedCornerShape(16.dp)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
// 硬币图标(使用文本代替,实际项目中可以使用图片资源)
Box(
modifier = Modifier
.size(64.dp)
.background(
brush = Brush.linearGradient(
colors = listOf(
Color(0xFFFFD700), // 金色
Color(0xFFFFA500) // 橙色
)
),
shape = CircleShape
),
contentAlignment = Alignment.Center
) {
Text(
text = "pai",
color = Color.White,
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
}
Spacer(modifier = Modifier.height(16.dp))
// 标题
Text(
text = stringResource(R.string.create_group_chat_confirm_title),
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
color = AppColors.text
)
Spacer(modifier = Modifier.height(24.dp))
// 需要消耗
CostInfoRow(
label = stringResource(R.string.create_group_chat_required_cost),
amount = cost,
AppColors = AppColors
)
Spacer(modifier = Modifier.height(12.dp))
// 当前余额
CostInfoRow(
label = stringResource(R.string.create_group_chat_current_balance),
amount = currentBalance,
AppColors = AppColors
)
Spacer(modifier = Modifier.height(12.dp))
// 消耗后余额
CostInfoRow(
label = stringResource(R.string.create_group_chat_balance_after),
amount = balanceAfterCost,
AppColors = AppColors
)
// 余额不足提示
if (!isBalanceSufficient) {
Spacer(modifier = Modifier.height(12.dp))
Text(
text = stringResource(R.string.create_group_chat_insufficient_balance),
color = Color.Red,
fontSize = 14.sp
)
}
Spacer(modifier = Modifier.height(24.dp))
// 按钮行
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
// 取消按钮
OutlinedButton(
onClick = onCancel,
modifier = Modifier
.weight(1f)
.height(48.dp)
.border(
width = 1.dp,
color = AppColors.secondaryText.copy(alpha = 0.3f),
shape = RoundedCornerShape(24.dp)
),
colors = ButtonDefaults.outlinedButtonColors(
contentColor = AppColors.text
),
shape = RoundedCornerShape(24.dp)
) {
Text(
text = stringResource(R.string.cancel),
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
}
// 确认消耗按钮
Button(
onClick = onConfirm,
modifier = Modifier
.weight(1f)
.height(48.dp),
enabled = isBalanceSufficient,
colors = ButtonDefaults.buttonColors(
containerColor = AppColors.main,
contentColor = AppColors.mainText,
disabledContainerColor = AppColors.disabledBackground,
disabledContentColor = AppColors.text
),
shape = RoundedCornerShape(24.dp)
) {
Text(
text = stringResource(R.string.create_group_chat_confirm_consume),
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
}
}
}
}
}
}
/**
* 费用信息行组件
*/
@Composable
fun CostInfoRow(
label: String,
amount: Int,
AppColors: com.aiosman.ravenow.AppThemeData
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = label,
fontSize = 14.sp,
color = AppColors.text
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
// 小硬币图标(使用简单的圆形)
Box(
modifier = Modifier
.size(16.dp)
.background(
brush = Brush.linearGradient(
colors = listOf(
Color(0xFFFFD700),
Color(0xFFFFA500)
)
),
shape = CircleShape
)
)
Text(
text = "${amount.formatNumber()} ${stringResource(R.string.pai_coin)}",
fontSize = 14.sp,
fontWeight = FontWeight.W600,
color = AppColors.text
)
}
}
}
/**
* 格式化数字,添加千位分隔符
*/
fun Int.formatNumber(): String {
return this.toString().reversed().chunked(3).joinToString(",").reversed()
}

View File

@@ -16,6 +16,7 @@ import com.aiosman.ravenow.ConstVars
import com.aiosman.ravenow.data.AccountNotice
import com.aiosman.ravenow.data.AccountService
import com.aiosman.ravenow.data.AccountServiceImpl
import com.aiosman.ravenow.data.PointService
import com.aiosman.ravenow.data.UserService
import com.aiosman.ravenow.data.UserServiceImpl
import com.aiosman.ravenow.R
@@ -36,6 +37,7 @@ object CreateGroupChatViewModel : ViewModel() {
// 状态管理
var isLoading by mutableStateOf(false)
var errorMessage by mutableStateOf<String?>(null)
var showConfirmDialog by mutableStateOf(false)
// 创建群聊
suspend fun createGroupChat(
@@ -76,6 +78,11 @@ object CreateGroupChatViewModel : ViewModel() {
}
}
// 显示错误信息(公开方法)
fun showError(message: String) {
showToast(message)
}
// 清除错误信息
fun clearError() {
errorMessage = null
@@ -107,4 +114,59 @@ object CreateGroupChatViewModel : ViewModel() {
addSelectedMember(member, selectedMemberIds, selectedMembers)
}
}
/**
* 获取创建群聊的费用
* @return 费用金额,如果无法获取则返回 0
*/
fun getCreateRoomCost(): Int {
val rules = PointService.pointsRules.value
val costRule = rules?.sub?.get(PointService.PointsRuleKey.CREATE_ROOM)
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
}
/**
* 显示确认弹窗
*/
fun showConfirmDialog() {
showConfirmDialog = true
}
/**
* 隐藏确认弹窗
*/
fun hideConfirmDialog() {
showConfirmDialog = false
}
}

View File

@@ -23,10 +23,11 @@ object PointsViewModel : ViewModel() {
viewModelScope.launch {
try {
loading = true
// 并行预加载积分定价表不影响UI
// 并行预加载积分定价表和群聊人数限制不影响UI
launch {
try {
PointService.refreshPointsRules()
PointService.refreshRoomMaxMembers()
} catch (e: Exception) {
Log.e("PointsViewModel", "refresh rules error", e)
}

View File

@@ -368,6 +368,14 @@
<string name="recharge_pai_coin">充值派币</string>
<string name="recharge_pai_coin_desc">多种套餐可选,立即充值</string>
<string name="create_group_chat_failed">创建群聊失败: %1$s</string>
<string name="create_group_chat_confirm_title">创建群聊确认</string>
<string name="create_group_chat_required_cost">需要消耗:</string>
<string name="create_group_chat_current_balance">当前余额</string>
<string name="create_group_chat_balance_after">消耗后余额:</string>
<string name="create_group_chat_confirm_consume">确认消耗</string>
<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="connect_world_start_following">连接世界,从关注开始</string>
<string name="why_not_start_with_agent">不如从一个 Agent 开始认识这世界?</string>
<string name="explore">去探索</string>

View File

@@ -361,6 +361,14 @@
<string name="recharge_pai_coin">Recharge Pai Coin</string>
<string name="recharge_pai_coin_desc">Multiple packages available, recharge now</string>
<string name="create_group_chat_failed">Failed to create group chat: %1$s</string>
<string name="create_group_chat_confirm_title">Create Group Chat</string>
<string name="create_group_chat_required_cost">Required consumption:</string>
<string name="create_group_chat_current_balance">Current balance</string>
<string name="create_group_chat_balance_after">Balance after consumption:</string>
<string name="create_group_chat_confirm_consume">Confirm consumption</string>
<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="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>