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