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