refactor: extract duplicate format functions to FormatUtils

- New: ui/components/FormatUtils.kt — formatAmount/formatResetInSec/formatResetForWidget/normalizeWindowLabel
- ServiceBalanceCards.kt: remove internal definitions, import from FormatUtils
- ServiceDetailScreen.kt: remove private formatResetInSec/formatAmount, import from FormatUtils
- OpenCodeGoWidgetProvider.kt: remove private normalizeWindowLabel, import from FormatUtils
  (formatReset kept — widget uses short d/h/m format, not Chinese)
This commit is contained in:
WaterRain 2026-07-06 23:47:20 +00:00
parent ec5f2dcb5c
commit f2e5eedaa4
No known key found for this signature in database
4 changed files with 53 additions and 44 deletions

View File

@ -0,0 +1,47 @@
package com.rainy.token.ui.components
import java.util.Locale
/**
* 共享格式化工具函数。
*
* 从 DashboardScreen / ServiceDetailScreen / WidgetProvider 提取,消除三处重复。
*/
/** 格式化金额:整数无小数点,带小数保留 2 位 */
fun formatAmount(value: Double): String {
return if (value % 1.0 == 0.0) value.toInt().toString()
else String.format(Locale.US, "%.2f", value)
}
/** 格式化重置倒计时(秒 → "X 天 Y 小时" / "X 小时 Y 分" / "Y 分"),无效返回 "—" */
fun formatResetInSec(sec: Long): String {
if (sec <= 0) return "—"
val days = sec / 86400
val hours = (sec % 86400) / 3600
val minutes = (sec % 3600) / 60
return when {
days > 0 -> "$days 天 $hours 小时"
hours > 0 -> "$hours 小时 $minutes 分"
else -> "$minutes 分"
}
}
/** Widget 版本:逻辑相同但空值返回空串(非 "—"),适配 RemoteViews 布局 */
fun formatResetForWidget(sec: Long): String {
if (sec <= 0) return ""
val days = sec / 86400
val hours = (sec % 86400) / 3600
val minutes = (sec % 3600) / 60
return when {
days > 0 -> "$days 天 $hours 小时"
hours > 0 -> "$hours 小时 $minutes 分"
else -> "$minutes 分"
}
}
/** 标准化窗口标签:weekly → 每周,其他原样返回 */
fun normalizeWindowLabel(label: String): String = when (label.lowercase()) {
"weekly" -> "每周"
else -> label
}

View File

@ -24,6 +24,9 @@ import com.rainy.token.domain.model.ServiceBalance
import com.rainy.token.domain.service.ServiceType
import com.rainy.token.ui.components.StatusStyle
import com.rainy.token.ui.components.StatusLevel
import com.rainy.token.ui.components.formatAmount
import com.rainy.token.ui.components.formatResetInSec
import com.rainy.token.ui.components.normalizeWindowLabel
import com.rainy.token.ui.theme.StrawberryPink
import com.rainy.token.ui.theme.StatusOrange
import com.rainy.token.ui.theme.inkMuted
@ -353,11 +356,6 @@ internal fun OllamaUsageWindows(balance: ServiceBalance) {
// ── Shared UI components ──
internal fun normalizeWindowLabel(label: String): String = when (label.lowercase()) {
"weekly" -> "每周"
else -> label
}
@Composable
internal fun CompactUsageRowEmpty(label: String, resetInSec: Long?) {
Column {
@ -452,18 +450,6 @@ internal fun CompactUsageRow(label: String, pct: Int, resetInSec: Long?) {
// ── Dashboard utility functions ──
internal fun formatResetInSec(sec: Long): String {
if (sec <= 0) return "—"
val days = sec / 86400
val hours = (sec % 86400) / 3600
val minutes = (sec % 3600) / 60
return when {
days > 0 -> "$days 天 $hours 小时"
hours > 0 -> "$hours 小时 $minutes 分"
else -> "$minutes 分"
}
}
internal fun DashboardCardUi.statusBadgeStyle(): StatusStyle = when {
credentialState == CredentialStatus.State.NOT_CONFIGURED ->
StatusStyle("未配置", StatusLevel.WARNING)
@ -503,9 +489,4 @@ internal fun footerText(card: DashboardCardUi): String {
diffMin < 1440 -> "${diffMin / 60} 小时前更新"
else -> "$timeStr 更新"
}
}
internal fun formatAmount(value: Double): String {
return if (value % 1.0 == 0.0) value.toInt().toString()
else String.format(Locale.US, "%.2f", value)
}

View File

@ -51,6 +51,8 @@ import com.rainy.token.ui.components.ServiceIcon
import com.rainy.token.ui.components.StatusChip
import com.rainy.token.ui.components.StatusLevel
import com.rainy.token.ui.components.StatusStyle
import com.rainy.token.ui.components.formatAmount
import com.rainy.token.ui.components.formatResetInSec
import com.rainy.token.ui.theme.inkMuted
import com.rainy.token.ui.theme.StrawberryPink
import java.text.SimpleDateFormat
@ -388,18 +390,6 @@ private fun UsageWindowRow(label: String, pct: Int?, resetInSec: Long?) {
}
}
private fun formatResetInSec(sec: Long): String {
if (sec <= 0) return "—"
val days = sec / 86400
val hours = (sec % 86400) / 3600
val minutes = (sec % 3600) / 60
return when {
days > 0 -> "$days 天 $hours 小时"
hours > 0 -> "$hours 小时 $minutes 分"
else -> "$minutes 分"
}
}
/**
* Ollama Pro 专属:5h + 每周用量窗口卡。
*/
@ -721,9 +711,4 @@ private fun stateToChip(state: State): StatusStyle = when (state) {
private fun formatTime(epochMillis: Long): String {
val sdf = SimpleDateFormat("MM-dd HH:mm", Locale.getDefault())
return sdf.format(Date(epochMillis))
}
private fun formatAmount(value: Double): String {
return if (value % 1.0 == 0.0) value.toInt().toString()
else String.format(Locale.US, "%.2f", value)
}

View File

@ -14,6 +14,7 @@ import com.rainy.token.R
import com.rainy.token.data.cache.BalanceCache
import com.rainy.token.data.cache.balanceCacheDataStore
import com.rainy.token.domain.service.ServiceType
import com.rainy.token.ui.components.normalizeWindowLabel
import kotlinx.coroutines.runBlocking
import java.text.SimpleDateFormat
import java.util.Date
@ -218,11 +219,6 @@ class OpenCodeGoWidgetProvider : AppWidgetProvider() {
views.setTextViewText(R.id.row3_label, third)
}
private fun normalizeWindowLabel(label: String): String = when (label.lowercase()) {
"weekly" -> "每周"
else -> label
}
private fun populateRow(
views: RemoteViews,
pctViewId: Int,