From eebe9f69879705e739d492af0cd64b6b1ecf4f23 Mon Sep 17 00:00:00 2001
From: Liuxinyu176 <1041316040@qq.com>
Date: Sat, 12 Sep 2026 09:40:59 +0800
Subject: [PATCH] =?UTF-8?q?fix(sub2api):=20=E9=9D=A2=E6=9D=BF=E7=AE=A1?=
=?UTF-8?q?=E7=90=86=E5=8F=AA=E4=BF=9D=E7=95=99=E9=A6=96=E9=A1=B5=E5=85=A5?=
=?UTF-8?q?=E5=8F=A3=EF=BC=8C=E5=BC=B9=E7=AA=97=E6=94=AF=E6=8C=81=E6=BB=9A?=
=?UTF-8?q?=E5=8A=A8=E3=80=81=E6=93=8D=E4=BD=9C=E5=89=8D=E7=BD=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
按使用反馈调整(真机验证):
- 详情页不再重复展示面板管理卡: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 的本地路径开关仍未纳入提交。
---
.../token/ui/dashboard/DashboardScreen.kt | 45 +++++++++-----
.../ui/servicedetail/ServiceDetailScreen.kt | 26 ++------
.../servicedetail/Sub2ApiPanelManagement.kt | 62 +++++++++++++------
app/src/main/res/values-b+zh+Hans/strings.xml | 1 +
app/src/main/res/values-b+zh+Hant/strings.xml | 1 +
app/src/main/res/values/strings.xml | 1 +
6 files changed, 79 insertions(+), 57 deletions(-)
diff --git a/app/src/main/java/com/rainy/token/ui/dashboard/DashboardScreen.kt b/app/src/main/java/com/rainy/token/ui/dashboard/DashboardScreen.kt
index dec5ae2..5c90572 100644
--- a/app/src/main/java/com/rainy/token/ui/dashboard/DashboardScreen.kt
+++ b/app/src/main/java/com/rainy/token/ui/dashboard/DashboardScreen.kt
@@ -19,12 +19,14 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
+import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
@@ -373,26 +375,39 @@ fun DashboardScreen(
}
}
- // Sub2API 面板管理弹窗:与详情页共用同一个面板管理组件
+ // Sub2API 面板管理弹窗
+ // 滚动说明:AlertDialog 的 text 槽是固定高度且**不自带滚动容器**,内容超过上限会被直接裁掉
+ // (表现为「打开后滚不动、下面的 Key 看不到」)。这里给 text 槽加 verticalScroll + 高度上限,
+ // 让整个面板管理内容(Key 列表/新建/分组/兑换)可以正常上下滚动。
if (showSub2PanelManagement) {
+ val scrollState = rememberScrollState()
+ val maxDialogHeight = (LocalConfiguration.current.screenHeightDp * 0.72f).dp
AlertDialog(
onDismissRequest = { showSub2PanelManagement = false },
title = { Text(stringResource(R.string.sub2_panel_manage_title), fontWeight = FontWeight.Bold) },
text = {
- com.rainy.token.ui.servicedetail.Sub2ApiPanelManagement(
- balance = uiState.cards.firstOrNull { it.service == ServiceType.SUB2API }?.cachedBalance?.balance,
- 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
- )
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .heightIn(max = maxDialogHeight)
+ .verticalScroll(scrollState)
+ ) {
+ com.rainy.token.ui.servicedetail.Sub2ApiPanelManagement(
+ balance = uiState.cards.firstOrNull { it.service == ServiceType.SUB2API }?.cachedBalance?.balance,
+ 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,
+ showHeader = false
+ )
+ }
},
confirmButton = {
TextButton(onClick = { showSub2PanelManagement = false }) {
diff --git a/app/src/main/java/com/rainy/token/ui/servicedetail/ServiceDetailScreen.kt b/app/src/main/java/com/rainy/token/ui/servicedetail/ServiceDetailScreen.kt
index a7ee86c..ce32db1 100644
--- a/app/src/main/java/com/rainy/token/ui/servicedetail/ServiceDetailScreen.kt
+++ b/app/src/main/java/com/rainy/token/ui/servicedetail/ServiceDetailScreen.kt
@@ -117,10 +117,8 @@ fun ServiceDetailScreen(
val selectedModel by viewModel.selectedModel.collectAsStateWithLifecycle()
val modelsLoading by viewModel.modelsLoading.collectAsStateWithLifecycle()
val checkinState by viewModel.checkinState.collectAsStateWithLifecycle()
- val panelKeysState by viewModel.panelKeysState.collectAsStateWithLifecycle()
- val panelActionMessage by viewModel.panelActionMessage.collectAsStateWithLifecycle()
- val panelGroups by viewModel.panelGroups.collectAsStateWithLifecycle()
- val recentlyCreatedKey by viewModel.recentlyCreatedKey.collectAsStateWithLifecycle()
+ // 面板管理(Key/分组/兑换)已移到首页 Sub2API 卡片的「面板管理」入口,
+ // 详情页只保留用量明细所需的窗口统计状态。
val panelWindowStats by viewModel.panelWindowStats.collectAsStateWithLifecycle()
val config = ServiceConfigProvider.get(service)
val isManualMode = config.method == FetchMethod.MANUAL
@@ -210,24 +208,8 @@ fun ServiceDetailScreen(
onLoadWindowModelStats = { from, to -> viewModel.loadPanelWindowModelStats(from, to) }
)
}
- // 面板管理:Key 列表/新建/分组/兑换/低余额提醒(有面板 Token 才加载)
- item {
- 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
- )
- }
+ // 面板管理(Key 列表/新建/分组/兑换)不再在详情页展示:
+ // 入口统一放在首页 Sub2API 卡片的「面板管理」按钮,避免同页重复且详情页过长。
}
}
diff --git a/app/src/main/java/com/rainy/token/ui/servicedetail/Sub2ApiPanelManagement.kt b/app/src/main/java/com/rainy/token/ui/servicedetail/Sub2ApiPanelManagement.kt
index 59d884b..9aeea5c 100644
--- a/app/src/main/java/com/rainy/token/ui/servicedetail/Sub2ApiPanelManagement.kt
+++ b/app/src/main/java/com/rainy/token/ui/servicedetail/Sub2ApiPanelManagement.kt
@@ -75,7 +75,9 @@ internal fun Sub2ApiPanelManagement(
onSwitchGroup: (Long, Long) -> Unit,
onRedeem: (String) -> Unit,
onConsumeMessage: () -> Unit,
- onConsumeCreatedKey: () -> Unit = {}
+ onConsumeCreatedKey: () -> Unit = {},
+ /** 弹窗场景由 AlertDialog 的 title 槽展示标题,可关闭内部标题避免重复 */
+ showHeader: Boolean = true
) {
val context = LocalContext.current
var showCreate by remember { mutableStateOf(false) }
@@ -110,15 +112,25 @@ internal fun Sub2ApiPanelManagement(
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f))
) {
Column(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
- Row(verticalAlignment = Alignment.CenterVertically) {
- Text(
- text = stringResource(R.string.sub2_panel_manage_title),
- style = MaterialTheme.typography.titleMedium,
- fontWeight = FontWeight.Bold
- )
- Spacer(modifier = Modifier.weight(1f))
- IconButton(onClick = { onLoadKeys(); onLoadGroups() }) {
- Icon(Icons.Filled.Refresh, contentDescription = stringResource(R.string.action_refresh))
+ if (showHeader) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text(
+ text = stringResource(R.string.sub2_panel_manage_title),
+ style = MaterialTheme.typography.titleMedium,
+ fontWeight = FontWeight.Bold
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ 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))
+ // 主要操作(新建 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) {
is PanelKeysState.Idle -> {
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))
- }
- }
}
}
diff --git a/app/src/main/res/values-b+zh+Hans/strings.xml b/app/src/main/res/values-b+zh+Hans/strings.xml
index b76d11d..a659cd8 100644
--- a/app/src/main/res/values-b+zh+Hans/strings.xml
+++ b/app/src/main/res/values-b+zh+Hans/strings.xml
@@ -672,4 +672,5 @@
API Key 已复制
⚠ 余额不足:剩余 $%1$s,请及时充值或兑换卡密
(累计口径,未按所选周期过滤)
+ API Key 列表
\ No newline at end of file
diff --git a/app/src/main/res/values-b+zh+Hant/strings.xml b/app/src/main/res/values-b+zh+Hant/strings.xml
index 91a47db..a7a200c 100644
--- a/app/src/main/res/values-b+zh+Hant/strings.xml
+++ b/app/src/main/res/values-b+zh+Hant/strings.xml
@@ -672,4 +672,5 @@
API Key 已複製
⚠ 餘額不足:剩餘 $%1$s,請及時儲值或兌換卡密
(累計口徑,未按所選週期過濾)
+ API Key 列表
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 8329ab0..beb8339 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -677,4 +677,5 @@
API Key copied
⚠ Low balance: $%1$s left — top up or redeem a code
(all-time, not limited to the selected period)
+ API keys
\ No newline at end of file