fix(sub2api): 面板管理只保留首页入口,弹窗支持滚动、操作前置
按使用反馈调整(真机验证): - 详情页不再重复展示面板管理卡:Key 列表/新建/分组/兑换统一收到首页 Sub2API 卡片的 「面板管理」入口,详情页只保留用量明细(周期切换 + 柱状图 + 按模型)。 随之移除详情页已不再使用的 panelKeysState / panelGroups / panelActionMessage 状态收集。 - 修复「面板管理弹窗打开后不能滚动」:AlertDialog 的 text 槽是固定高度且不自带滚动容器, 内容超过上限会被直接裁剪。改为给 text 槽加 verticalScroll + 高度上限(屏幕高度 72%), 现在可以正常上下滚动浏览全部 Key。 - 主要操作前置:新建 Key / 兑换卡密 从列表底部移到顶部(原位置在 20+ 条 Key 之后, 需要滚到底才能看到);并新增「API Key 列表」小标题区分列表区。 - 弹窗内不再重复标题:组件新增 showHeader 参数,首页弹窗由 AlertDialog title 槽显示标题, 组件内部标题隐藏(刷新按钮保留)。 验证:compileDebugKotlin、lintDebug 通过;真机(V2172A / Android 15)实测 首页仍只显示今日与近7天、弹窗可滚动、新建/兑换在首屏、详情页无面板管理卡。 注:gradle.properties 的本地路径开关仍未纳入提交。
This commit is contained in:
parent
58d8b0e077
commit
eebe9f6987
@ -19,12 +19,14 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
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.heightIn
|
||||||
import androidx.compose.foundation.layout.offset
|
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.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@ -373,26 +375,39 @@ fun DashboardScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sub2API 面板管理弹窗:与详情页共用同一个面板管理组件
|
// Sub2API 面板管理弹窗
|
||||||
|
// 滚动说明:AlertDialog 的 text 槽是固定高度且**不自带滚动容器**,内容超过上限会被直接裁掉
|
||||||
|
// (表现为「打开后滚不动、下面的 Key 看不到」)。这里给 text 槽加 verticalScroll + 高度上限,
|
||||||
|
// 让整个面板管理内容(Key 列表/新建/分组/兑换)可以正常上下滚动。
|
||||||
if (showSub2PanelManagement) {
|
if (showSub2PanelManagement) {
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
val maxDialogHeight = (LocalConfiguration.current.screenHeightDp * 0.72f).dp
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { showSub2PanelManagement = false },
|
onDismissRequest = { showSub2PanelManagement = false },
|
||||||
title = { Text(stringResource(R.string.sub2_panel_manage_title), fontWeight = FontWeight.Bold) },
|
title = { Text(stringResource(R.string.sub2_panel_manage_title), fontWeight = FontWeight.Bold) },
|
||||||
text = {
|
text = {
|
||||||
com.rainy.token.ui.servicedetail.Sub2ApiPanelManagement(
|
Box(
|
||||||
balance = uiState.cards.firstOrNull { it.service == ServiceType.SUB2API }?.cachedBalance?.balance,
|
modifier = Modifier
|
||||||
keysState = panelKeysState,
|
.fillMaxWidth()
|
||||||
groups = panelGroups,
|
.heightIn(max = maxDialogHeight)
|
||||||
actionMessage = panelActionMessage,
|
.verticalScroll(scrollState)
|
||||||
createdKey = recentlyCreatedKey,
|
) {
|
||||||
onLoadKeys = { viewModel.loadPanelKeys() },
|
com.rainy.token.ui.servicedetail.Sub2ApiPanelManagement(
|
||||||
onLoadGroups = { viewModel.loadPanelGroups() },
|
balance = uiState.cards.firstOrNull { it.service == ServiceType.SUB2API }?.cachedBalance?.balance,
|
||||||
onCreateKey = viewModel::createPanelKey,
|
keysState = panelKeysState,
|
||||||
onSwitchGroup = viewModel::switchPanelGroup,
|
groups = panelGroups,
|
||||||
onRedeem = viewModel::redeemPanelCode,
|
actionMessage = panelActionMessage,
|
||||||
onConsumeMessage = viewModel::consumePanelActionMessage,
|
createdKey = recentlyCreatedKey,
|
||||||
onConsumeCreatedKey = viewModel::consumeRecentlyCreatedKey
|
onLoadKeys = { viewModel.loadPanelKeys() },
|
||||||
)
|
onLoadGroups = { viewModel.loadPanelGroups() },
|
||||||
|
onCreateKey = viewModel::createPanelKey,
|
||||||
|
onSwitchGroup = viewModel::switchPanelGroup,
|
||||||
|
onRedeem = viewModel::redeemPanelCode,
|
||||||
|
onConsumeMessage = viewModel::consumePanelActionMessage,
|
||||||
|
onConsumeCreatedKey = viewModel::consumeRecentlyCreatedKey,
|
||||||
|
showHeader = false
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = { showSub2PanelManagement = false }) {
|
TextButton(onClick = { showSub2PanelManagement = false }) {
|
||||||
|
|||||||
@ -117,10 +117,8 @@ fun ServiceDetailScreen(
|
|||||||
val selectedModel by viewModel.selectedModel.collectAsStateWithLifecycle()
|
val selectedModel by viewModel.selectedModel.collectAsStateWithLifecycle()
|
||||||
val modelsLoading by viewModel.modelsLoading.collectAsStateWithLifecycle()
|
val modelsLoading by viewModel.modelsLoading.collectAsStateWithLifecycle()
|
||||||
val checkinState by viewModel.checkinState.collectAsStateWithLifecycle()
|
val checkinState by viewModel.checkinState.collectAsStateWithLifecycle()
|
||||||
val panelKeysState by viewModel.panelKeysState.collectAsStateWithLifecycle()
|
// 面板管理(Key/分组/兑换)已移到首页 Sub2API 卡片的「面板管理」入口,
|
||||||
val panelActionMessage by viewModel.panelActionMessage.collectAsStateWithLifecycle()
|
// 详情页只保留用量明细所需的窗口统计状态。
|
||||||
val panelGroups by viewModel.panelGroups.collectAsStateWithLifecycle()
|
|
||||||
val recentlyCreatedKey by viewModel.recentlyCreatedKey.collectAsStateWithLifecycle()
|
|
||||||
val panelWindowStats by viewModel.panelWindowStats.collectAsStateWithLifecycle()
|
val panelWindowStats by viewModel.panelWindowStats.collectAsStateWithLifecycle()
|
||||||
val config = ServiceConfigProvider.get(service)
|
val config = ServiceConfigProvider.get(service)
|
||||||
val isManualMode = config.method == FetchMethod.MANUAL
|
val isManualMode = config.method == FetchMethod.MANUAL
|
||||||
@ -210,24 +208,8 @@ fun ServiceDetailScreen(
|
|||||||
onLoadWindowModelStats = { from, to -> viewModel.loadPanelWindowModelStats(from, to) }
|
onLoadWindowModelStats = { from, to -> viewModel.loadPanelWindowModelStats(from, to) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// 面板管理:Key 列表/新建/分组/兑换/低余额提醒(有面板 Token 才加载)
|
// 面板管理(Key 列表/新建/分组/兑换)不再在详情页展示:
|
||||||
item {
|
// 入口统一放在首页 Sub2API 卡片的「面板管理」按钮,避免同页重复且详情页过长。
|
||||||
Sub2ApiPanelManagement(
|
|
||||||
balance = (uiState.state as? State.Fresh)?.data
|
|
||||||
?: (uiState.state as? State.Stale)?.data,
|
|
||||||
keysState = panelKeysState,
|
|
||||||
groups = panelGroups,
|
|
||||||
actionMessage = panelActionMessage,
|
|
||||||
createdKey = recentlyCreatedKey,
|
|
||||||
onLoadKeys = { viewModel.loadPanelKeys() },
|
|
||||||
onLoadGroups = { viewModel.loadPanelGroups() },
|
|
||||||
onCreateKey = viewModel::createPanelKey,
|
|
||||||
onSwitchGroup = viewModel::switchPanelGroup,
|
|
||||||
onRedeem = viewModel::redeemPanelCode,
|
|
||||||
onConsumeMessage = viewModel::consumePanelActionMessage,
|
|
||||||
onConsumeCreatedKey = viewModel::consumeRecentlyCreatedKey
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -75,7 +75,9 @@ internal fun Sub2ApiPanelManagement(
|
|||||||
onSwitchGroup: (Long, Long) -> Unit,
|
onSwitchGroup: (Long, Long) -> Unit,
|
||||||
onRedeem: (String) -> Unit,
|
onRedeem: (String) -> Unit,
|
||||||
onConsumeMessage: () -> Unit,
|
onConsumeMessage: () -> Unit,
|
||||||
onConsumeCreatedKey: () -> Unit = {}
|
onConsumeCreatedKey: () -> Unit = {},
|
||||||
|
/** 弹窗场景由 AlertDialog 的 title 槽展示标题,可关闭内部标题避免重复 */
|
||||||
|
showHeader: Boolean = true
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var showCreate by remember { mutableStateOf(false) }
|
var showCreate by remember { mutableStateOf(false) }
|
||||||
@ -110,15 +112,25 @@ internal fun Sub2ApiPanelManagement(
|
|||||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f))
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f))
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
|
Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
if (showHeader) {
|
||||||
Text(
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
text = stringResource(R.string.sub2_panel_manage_title),
|
Text(
|
||||||
style = MaterialTheme.typography.titleMedium,
|
text = stringResource(R.string.sub2_panel_manage_title),
|
||||||
fontWeight = FontWeight.Bold
|
style = MaterialTheme.typography.titleMedium,
|
||||||
)
|
fontWeight = FontWeight.Bold
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
)
|
||||||
IconButton(onClick = { onLoadKeys(); onLoadGroups() }) {
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
Icon(Icons.Filled.Refresh, contentDescription = stringResource(R.string.action_refresh))
|
IconButton(onClick = { onLoadKeys(); onLoadGroups() }) {
|
||||||
|
Icon(Icons.Filled.Refresh, contentDescription = stringResource(R.string.action_refresh))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 标题由外部弹窗提供时,这里保留一个刷新按钮,方便手动重新拉取 Key/分组
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
IconButton(onClick = { onLoadKeys(); onLoadGroups() }) {
|
||||||
|
Icon(Icons.Filled.Refresh, contentDescription = stringResource(R.string.action_refresh))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,6 +166,26 @@ internal fun Sub2ApiPanelManagement(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
// 主要操作(新建 Key / 兑换卡密)固定在列表上方:
|
||||||
|
// Key 数量多时列表很长,放在底部会被埋在滚动区尽头,不易发现。
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
Button(onClick = { onLoadGroups(); showCreate = true }, modifier = Modifier.weight(1f)) {
|
||||||
|
Text(stringResource(R.string.sub2_panel_create))
|
||||||
|
}
|
||||||
|
OutlinedButton(onClick = { showRedeem = true }, modifier = Modifier.weight(1f)) {
|
||||||
|
Text(stringResource(R.string.sub2_panel_redeem))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.sub2_panel_keys_header),
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
color = inkMuted()
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
when (keysState) {
|
when (keysState) {
|
||||||
is PanelKeysState.Idle -> {
|
is PanelKeysState.Idle -> {
|
||||||
OutlinedButton(onClick = onLoadKeys, modifier = Modifier.fillMaxWidth()) {
|
OutlinedButton(onClick = onLoadKeys, modifier = Modifier.fillMaxWidth()) {
|
||||||
@ -225,16 +257,6 @@ internal fun Sub2ApiPanelManagement(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
|
||||||
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
|
||||||
OutlinedButton(onClick = { onLoadGroups(); showCreate = true }, modifier = Modifier.weight(1f)) {
|
|
||||||
Text(stringResource(R.string.sub2_panel_create))
|
|
||||||
}
|
|
||||||
OutlinedButton(onClick = { showRedeem = true }, modifier = Modifier.weight(1f)) {
|
|
||||||
Text(stringResource(R.string.sub2_panel_redeem))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -672,4 +672,5 @@
|
|||||||
<string name="sub2_panel_copied">API Key 已复制</string>
|
<string name="sub2_panel_copied">API Key 已复制</string>
|
||||||
<string name="sub2_panel_low_balance">⚠ 余额不足:剩余 $%1$s,请及时充值或兑换卡密</string>
|
<string name="sub2_panel_low_balance">⚠ 余额不足:剩余 $%1$s,请及时充值或兑换卡密</string>
|
||||||
<string name="sub2_models_alltime_suffix">(累计口径,未按所选周期过滤)</string>
|
<string name="sub2_models_alltime_suffix">(累计口径,未按所选周期过滤)</string>
|
||||||
|
<string name="sub2_panel_keys_header">API Key 列表</string>
|
||||||
</resources>
|
</resources>
|
||||||
@ -672,4 +672,5 @@
|
|||||||
<string name="sub2_panel_copied">API Key 已複製</string>
|
<string name="sub2_panel_copied">API Key 已複製</string>
|
||||||
<string name="sub2_panel_low_balance">⚠ 餘額不足:剩餘 $%1$s,請及時儲值或兌換卡密</string>
|
<string name="sub2_panel_low_balance">⚠ 餘額不足:剩餘 $%1$s,請及時儲值或兌換卡密</string>
|
||||||
<string name="sub2_models_alltime_suffix">(累計口徑,未按所選週期過濾)</string>
|
<string name="sub2_models_alltime_suffix">(累計口徑,未按所選週期過濾)</string>
|
||||||
|
<string name="sub2_panel_keys_header">API Key 列表</string>
|
||||||
</resources>
|
</resources>
|
||||||
@ -677,4 +677,5 @@
|
|||||||
<string name="sub2_panel_copied">API Key copied</string>
|
<string name="sub2_panel_copied">API Key copied</string>
|
||||||
<string name="sub2_panel_low_balance">⚠ Low balance: $%1$s left — top up or redeem a code</string>
|
<string name="sub2_panel_low_balance">⚠ Low balance: $%1$s left — top up or redeem a code</string>
|
||||||
<string name="sub2_models_alltime_suffix">(all-time, not limited to the selected period)</string>
|
<string name="sub2_models_alltime_suffix">(all-time, not limited to the selected period)</string>
|
||||||
|
<string name="sub2_panel_keys_header">API keys</string>
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
Reference in New Issue
Block a user