fix(sub2api): 修复柱状图不显示——接入按小时趋势
详情页「今天」窗口柱状图只有一根按天柱子看不出分布, 原因是 hourlyTrend / onLoadHourlyTrend 参数从未从 Screen 传入 Card。 修复: - VM 新增 hourlyTrend StateFlow + loadHourlyTrend(date) loader - Screen 传入 hourlyTrend + onLoadHourlyTrend 回调 - Card 的 onLoadHourlyTrend 改为 (LocalDate) -> Unit,今天窗口时拉取小时趋势 - detailTab 状态去掉 remember(detail) 依赖,避免后台刷新余额时重置 tab 选中
This commit is contained in:
parent
dc3027417e
commit
78f93588f8
@ -121,6 +121,7 @@ fun ServiceDetailScreen(
|
||||
// 详情页只保留用量明细所需的窗口统计状态。
|
||||
val panelWindowStats by viewModel.panelWindowStats.collectAsStateWithLifecycle()
|
||||
val panelKeyStats by viewModel.panelKeyStats.collectAsStateWithLifecycle()
|
||||
val hourlyTrend by viewModel.hourlyTrend.collectAsStateWithLifecycle()
|
||||
val config = ServiceConfigProvider.get(service)
|
||||
val isManualMode = config.method == FetchMethod.MANUAL
|
||||
|
||||
@ -207,6 +208,8 @@ fun ServiceDetailScreen(
|
||||
state = uiState.state,
|
||||
windowStatsState = panelWindowStats,
|
||||
onLoadWindowModelStats = { from, to -> viewModel.loadPanelWindowModelStats(from, to) },
|
||||
hourlyTrend = hourlyTrend,
|
||||
onLoadHourlyTrend = { date -> viewModel.loadHourlyTrend(date) },
|
||||
keyStatsState = panelKeyStats,
|
||||
onLoadKeyStats = { from, to -> viewModel.loadPanelKeyStats(from, to) }
|
||||
)
|
||||
|
||||
@ -12,6 +12,7 @@ import com.rainy.token.data.repository.TriggerErrorReason
|
||||
import com.rainy.token.domain.model.Credential
|
||||
import com.rainy.token.domain.model.CredentialStatus
|
||||
import com.rainy.token.domain.model.ServiceBalance
|
||||
import com.rainy.token.domain.model.Sub2DailyUsage
|
||||
import com.rainy.token.domain.model.TriggerSummary
|
||||
import com.rainy.token.R
|
||||
import com.rainy.token.domain.service.ServiceType
|
||||
@ -132,6 +133,21 @@ class ServiceDetailViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/** 「今天」按小时趋势(面板 snapshot-v2?granularity=hour)。 */
|
||||
private val _hourlyTrend = MutableStateFlow<List<Sub2DailyUsage>>(emptyList())
|
||||
val hourlyTrend: StateFlow<List<Sub2DailyUsage>> = _hourlyTrend.asStateFlow()
|
||||
|
||||
private var hourlyTrendGeneration = 0
|
||||
|
||||
fun loadHourlyTrend(date: LocalDate) {
|
||||
val generation = ++hourlyTrendGeneration
|
||||
viewModelScope.launch {
|
||||
val result = sub2ApiRepository.fetchHourlyTrend(date)
|
||||
if (generation != hourlyTrendGeneration) return@launch
|
||||
_hourlyTrend.value = result.getOrDefault(emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
/** 「按 Key」用量状态(面板使用记录口径,按当前时间周期)。 */
|
||||
private val _panelKeyStats = MutableStateFlow<PanelKeyStatsState>(PanelKeyStatsState.Idle)
|
||||
val panelKeyStats: StateFlow<PanelKeyStatsState> = _panelKeyStats.asStateFlow()
|
||||
|
||||
@ -1,14 +1,18 @@
|
||||
package com.rainy.token.ui.servicedetail
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
@ -20,6 +24,7 @@ import androidx.compose.material3.FilterChipDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@ -29,6 +34,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@ -66,7 +72,7 @@ internal fun Sub2ApiUsageCard(
|
||||
onLoadWindowModelStats: ((from: LocalDate, to: LocalDate) -> Unit)? = null,
|
||||
/** 「今天」窗口的按小时趋势;非空时用小时柱替代按天柱(按天只有一根柱没有信息量)。 */
|
||||
hourlyTrend: List<Sub2DailyUsage> = emptyList(),
|
||||
onLoadHourlyTrend: (() -> Unit)? = null,
|
||||
onLoadHourlyTrend: ((LocalDate) -> Unit)? = null,
|
||||
/** 「按 Key」tab 的数据状态(按当前时间周期查询面板使用记录)。 */
|
||||
keyStatsState: PanelKeyStatsState = PanelKeyStatsState.Idle,
|
||||
onLoadKeyStats: ((from: LocalDate, to: LocalDate) -> Unit)? = null
|
||||
@ -109,7 +115,7 @@ internal fun Sub2ApiUsageCard(
|
||||
// key=detail:刷新后模型列表变化时重置选中态,避免「每日区隐藏+无模型说明」的悬空状态
|
||||
var selectedModel by remember(detail) { mutableStateOf<String?>(null) }
|
||||
// 明细分区 tab:按模型 / 按 Key
|
||||
var detailTab by remember(detail) { mutableStateOf(DetailTab.MODEL) }
|
||||
var detailTab by remember { mutableStateOf(DetailTab.MODEL) }
|
||||
|
||||
// ── 时间窗口选择 ──
|
||||
LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
@ -131,7 +137,10 @@ internal fun Sub2ApiUsageCard(
|
||||
|
||||
// 「今天」改为按小时展示:只有一根按天柱子看不出分布,故拉当天小时趋势
|
||||
LaunchedEffect(window) {
|
||||
if (window == Sub2Window.TODAY) onLoadHourlyTrend?.invoke()
|
||||
if (window == Sub2Window.TODAY) {
|
||||
val (from, _) = window.range()
|
||||
onLoadHourlyTrend?.invoke(from)
|
||||
}
|
||||
}
|
||||
// 小时数据只在「今天」窗口且拿到 ≥2 个小时桶时才用。
|
||||
// 实测面板可能把整天聚合成单个 "00:00" 桶(时区/活动量原因),
|
||||
@ -292,27 +301,19 @@ internal fun Sub2ApiUsageCard(
|
||||
if (detail.modelStats.isNotEmpty() || window != Sub2Window.ALL) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f))
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
// tab 切换:按模型 / 按 Key
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
// tab 切换:按模型 / 按 Key(文字样式,非按钮)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
FilterChip(
|
||||
selected = detailTab == DetailTab.MODEL,
|
||||
onClick = { detailTab = DetailTab.MODEL },
|
||||
label = { Text(stringResource(R.string.sub2_tab_by_model)) },
|
||||
colors = FilterChipDefaults.filterChipColors(
|
||||
selectedContainerColor = MaterialTheme.colorScheme.primary,
|
||||
selectedLabelColor = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
label = { Text(stringResource(R.string.sub2_tab_by_model)) }
|
||||
)
|
||||
FilterChip(
|
||||
selected = detailTab == DetailTab.KEY,
|
||||
onClick = { detailTab = DetailTab.KEY },
|
||||
label = { Text(stringResource(R.string.sub2_tab_by_key)) },
|
||||
colors = FilterChipDefaults.filterChipColors(
|
||||
selectedContainerColor = MaterialTheme.colorScheme.primary,
|
||||
selectedLabelColor = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
label = { Text(stringResource(R.string.sub2_tab_by_key)) }
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user