diff --git a/.commit_msg b/.commit_msg
new file mode 100644
index 0000000..4210909
--- /dev/null
+++ b/.commit_msg
@@ -0,0 +1,22 @@
+feat: MIUI Widget 完整适配 + 深色模式全局修复
+
+MIUI Widget 接入(负一屏 + 曝光刷新)
+- AndroidManifest 新增 miuiWidget 全套标识
+- intent-filter 新增 miui.appwidget.action.APPWIDGET_UPDATE
+- 根布局改为 FrameLayout + @android:id/background
+- minWidth 250→300dp(4x2 标准)
+- onReceive() 处理 MIUI 曝光刷新
+- 5min 冷却自动刷新
+
+深色模式全面适配
+- drawable-night/widget_bg.xml
+- drawable-night/ic_opencode_go_logo.xml
+- drawable-night/ic_deepseek_logo.png + wordmark
+- layout-night/widget_opencode_go.xml
+- DarkInkWarm/DarkInkMuted + inkWarm()/inkMuted()
+- 全页面 51 处 InkMuted → inkMuted()
+- ServiceIcon 白底 → 主题自适应
+
+用户体验
+- 顶部栏 + 按钮:一键添桌面小组件
+- 负一屏底部遮挡修复(间距压缩 28dp)
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b238319..0bda2f5 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -28,22 +28,44 @@
-
+
+
+ android:exported="true"
+ android:label="@string/widget_name">
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/rainy/token/ui/components/ServiceIcon.kt b/app/src/main/java/com/rainy/token/ui/components/ServiceIcon.kt
index 3cdcd4c..1ac174f 100644
--- a/app/src/main/java/com/rainy/token/ui/components/ServiceIcon.kt
+++ b/app/src/main/java/com/rainy/token/ui/components/ServiceIcon.kt
@@ -2,6 +2,7 @@ package com.rainy.token.ui.components
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
+import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -40,7 +41,7 @@ fun ServiceIcon(
modifier = modifier
.size(size.dp)
.clip(RoundedCornerShape(8.dp))
- .background(Color.White),
+ .background(if (isSystemInDarkTheme()) Color(0xFF352329) else Color.White),
contentAlignment = Alignment.Center
) {
Image(
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 817d122..5c7d997 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
@@ -22,6 +22,7 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Card
@@ -59,8 +60,14 @@ 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.theme.InkMuted
+import com.rainy.token.ui.theme.inkMuted
import com.rainy.token.ui.theme.StrawberryPink
+import android.appwidget.AppWidgetManager
+import android.content.ComponentName
+import android.content.Intent
+import androidx.compose.ui.platform.LocalContext
+import android.widget.Toast
+import com.rainy.token.ui.widget.OpenCodeGoWidgetProvider
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@@ -110,11 +117,36 @@ fun DashboardScreen(
Text(
text = "AI 余额一览",
style = MaterialTheme.typography.bodySmall,
- color = InkMuted
+ color = inkMuted()
)
}
},
actions = {
+ // 添加小组件:优先 requestPinAppWidget,不支持则打开系统小组件选择器
+ val context = LocalContext.current
+ IconButton(onClick = {
+ val appWidgetManager = AppWidgetManager.getInstance(context)
+ val component = ComponentName(context, OpenCodeGoWidgetProvider::class.java)
+ if (appWidgetManager.isRequestPinAppWidgetSupported) {
+ appWidgetManager.requestPinAppWidget(component, null, null)
+ } else {
+ // MIUI 等桌面回退:直接打开小组件选择器
+ try {
+ val pickerIntent = Intent(AppWidgetManager.ACTION_APPWIDGET_PICK).apply {
+ putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
+ }
+ context.startActivity(pickerIntent)
+ } catch (e: Exception) {
+ Toast.makeText(context, "请在桌面长按 → 小组件 → 查找雨晴Token", Toast.LENGTH_LONG).show()
+ }
+ }
+ }) {
+ Icon(
+ imageVector = Icons.Filled.Add,
+ contentDescription = "添加小组件到桌面",
+ tint = StrawberryPink
+ )
+ }
IconButton(
onClick = { viewModel.refresh() },
enabled = !uiState.refreshing
@@ -165,7 +197,7 @@ fun DashboardScreen(
Text(
text = "服务余额",
style = MaterialTheme.typography.labelMedium,
- color = InkMuted,
+ color = inkMuted(),
modifier = Modifier.padding(top = 8.dp)
)
}
@@ -204,7 +236,7 @@ private fun DashboardCard(card: DashboardCardUi, onClick: () -> Unit) {
Text(
text = secondaryLine(card),
style = MaterialTheme.typography.bodySmall,
- color = InkMuted
+ color = inkMuted()
)
}
StatusChip(style = card.statusBadgeStyle())
@@ -223,7 +255,7 @@ private fun DashboardCard(card: DashboardCardUi, onClick: () -> Unit) {
color = if (card.lastFetchError != null)
MaterialTheme.colorScheme.error
else
- InkMuted
+ inkMuted()
)
}
}
@@ -237,24 +269,24 @@ private fun BalanceMainArea(card: DashboardCardUi) {
Text(
text = "—",
style = MaterialTheme.typography.displayMedium,
- color = InkMuted
+ color = inkMuted()
)
Text(
text = "点击配置凭据",
style = MaterialTheme.typography.bodySmall,
- color = InkMuted
+ color = inkMuted()
)
}
balance == null -> {
Text(
text = "—",
style = MaterialTheme.typography.displayMedium,
- color = InkMuted
+ color = inkMuted()
)
Text(
text = "下拉刷新",
style = MaterialTheme.typography.bodySmall,
- color = InkMuted
+ color = inkMuted()
)
}
card.service == ServiceType.OPENCODE_GO -> {
@@ -278,7 +310,7 @@ private fun BalanceMainArea(card: DashboardCardUi) {
Text(
text = balance.unit,
style = MaterialTheme.typography.titleMedium,
- color = InkMuted,
+ color = inkMuted(),
modifier = Modifier.padding(bottom = 6.dp)
)
}
@@ -293,7 +325,7 @@ private fun BalanceMainArea(card: DashboardCardUi) {
Text(
text = "本月已用 ${formatAmount(spent)}${balance.unit}",
style = MaterialTheme.typography.bodySmall,
- color = InkMuted
+ color = inkMuted()
)
}
}
@@ -313,7 +345,7 @@ private fun OpenCodeGoMainBalance(balance: com.rainy.token.domain.model.ServiceB
Text(
text = "%",
style = MaterialTheme.typography.titleLarge,
- color = InkMuted,
+ color = inkMuted(),
fontWeight = FontWeight.SemiBold,
modifier = Modifier.padding(bottom = 6.dp, start = 2.dp)
)
@@ -321,7 +353,7 @@ private fun OpenCodeGoMainBalance(balance: com.rainy.token.domain.model.ServiceB
Text(
text = "5h 用量",
style = MaterialTheme.typography.titleMedium,
- color = InkMuted,
+ color = inkMuted(),
modifier = Modifier.padding(bottom = 6.dp)
)
}
@@ -356,13 +388,13 @@ private fun CompactUsageRowEmpty(label: String, resetInSec: Long?) {
Text(
text = label,
style = MaterialTheme.typography.bodySmall,
- color = InkMuted,
+ color = inkMuted(),
modifier = Modifier.weight(1f)
)
Text(
text = "—",
style = MaterialTheme.typography.bodyMedium,
- color = InkMuted
+ color = inkMuted()
)
}
Spacer(modifier = Modifier.height(4.dp))
@@ -372,7 +404,7 @@ private fun CompactUsageRowEmpty(label: String, resetInSec: Long?) {
.fillMaxWidth()
.height(4.dp)
.clip(RoundedCornerShape(2.dp)),
- color = InkMuted.copy(alpha = 0.3f),
+ color = inkMuted().copy(alpha = 0.3f),
trackColor = MaterialTheme.colorScheme.surfaceVariant,
strokeCap = androidx.compose.ui.graphics.StrokeCap.Butt
)
@@ -381,7 +413,7 @@ private fun CompactUsageRowEmpty(label: String, resetInSec: Long?) {
Text(
text = "${formatResetInSec(resetInSec)}后重置",
style = MaterialTheme.typography.bodySmall,
- color = InkMuted
+ color = inkMuted()
)
}
}
@@ -398,7 +430,7 @@ private fun CompactUsageRow(label: String, pct: Int, resetInSec: Long?) {
Text(
text = label,
style = MaterialTheme.typography.bodySmall,
- color = InkMuted,
+ color = inkMuted(),
modifier = Modifier.weight(1f)
)
Text(
@@ -433,7 +465,7 @@ private fun CompactUsageRow(label: String, pct: Int, resetInSec: Long?) {
Text(
text = "${formatResetInSec(resetInSec)}后重置",
style = MaterialTheme.typography.bodySmall,
- color = InkMuted
+ color = inkMuted()
)
}
}
@@ -511,13 +543,13 @@ private fun DashboardFooter() {
Text(
text = "RainyToken · v1.0",
style = MaterialTheme.typography.bodySmall,
- color = InkMuted
+ color = inkMuted()
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = "本地加密存储,不上传任何数据",
style = MaterialTheme.typography.bodySmall,
- color = InkMuted
+ color = inkMuted()
)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/rainy/token/ui/dashboard/UsageDataScreen.kt b/app/src/main/java/com/rainy/token/ui/dashboard/UsageDataScreen.kt
index b481d03..db7f3a2 100644
--- a/app/src/main/java/com/rainy/token/ui/dashboard/UsageDataScreen.kt
+++ b/app/src/main/java/com/rainy/token/ui/dashboard/UsageDataScreen.kt
@@ -52,7 +52,7 @@ import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.rainy.token.data.local.UsageRecord
-import com.rainy.token.ui.theme.InkMuted
+import com.rainy.token.ui.theme.inkMuted
import com.rainy.token.ui.theme.StrawberryPink
import java.text.SimpleDateFormat
import java.time.Instant
@@ -121,7 +121,7 @@ fun UsageDataScreen(
})
}
}
- Text("UTC+0", style = MaterialTheme.typography.bodySmall, color = InkMuted)
+ Text("UTC+0", style = MaterialTheme.typography.bodySmall, color = inkMuted())
Spacer(Modifier.weight(1f))
// 模型筛选
Box {
@@ -150,17 +150,17 @@ fun UsageDataScreen(
CustomTimeRangeRow(customStartMs, customEndMs,
{ showStartPicker = true }, { showEndPicker = true },
{ viewModel.setTimeFilter(TimeFilter.Custom(customStartMs, customEndMs)) })
- Text("所有时间按 UTC+0 计算", style = MaterialTheme.typography.bodySmall, color = InkMuted)
+ Text("所有时间按 UTC+0 计算", style = MaterialTheme.typography.bodySmall, color = inkMuted())
}
// ─── 记录数 + 分页 ───
Row(Modifier.fillMaxWidth().padding(vertical = 4.dp), Arrangement.SpaceBetween, Alignment.CenterVertically) {
- Text("${state.totalRecords} 条记录", style = MaterialTheme.typography.bodySmall, color = InkMuted)
+ Text("${state.totalRecords} 条记录", style = MaterialTheme.typography.bodySmall, color = inkMuted())
if (state.totalPages > 1) {
Row(verticalAlignment = Alignment.CenterVertically) {
TextButton(onClick = { viewModel.prevPage() }, enabled = state.currentPage > 1) {
Text("◀", color = StrawberryPink, fontSize = 13.sp) }
- Text("${state.currentPage}/${state.totalPages}", style = MaterialTheme.typography.bodySmall, color = InkMuted)
+ Text("${state.currentPage}/${state.totalPages}", style = MaterialTheme.typography.bodySmall, color = inkMuted())
TextButton(onClick = { viewModel.nextPage() }, enabled = state.currentPage < state.totalPages) {
Text("▶", color = StrawberryPink, fontSize = 13.sp) }
Spacer(Modifier.width(6.dp))
@@ -173,7 +173,7 @@ fun UsageDataScreen(
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = androidx.compose.ui.text.input.KeyboardType.Number),
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = StrawberryPink,
- unfocusedBorderColor = InkMuted.copy(alpha = 0.4f)
+ unfocusedBorderColor = inkMuted().copy(alpha = 0.4f)
)
)
TextButton(onClick = {
@@ -201,11 +201,11 @@ fun UsageDataScreen(
// ─── 记录列表 ───
if (state.loading) {
Box(Modifier.fillMaxWidth().height(200.dp), contentAlignment = Alignment.Center) {
- Text("加载中…", color = InkMuted)
+ Text("加载中…", color = inkMuted())
}
} else if (state.records.isEmpty()) {
Box(Modifier.fillMaxWidth().height(200.dp), contentAlignment = Alignment.Center) {
- Text("暂无数据", color = InkMuted)
+ Text("暂无数据", color = inkMuted())
}
} else {
LazyColumn(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(1.dp)) {
@@ -265,7 +265,7 @@ fun UsageDataScreen(
@Composable
private fun HeaderCell(text: String, modifier: Modifier) {
Text(text, modifier = modifier, fontSize = 12.sp, fontWeight = FontWeight.Bold,
- color = InkMuted, textAlign = TextAlign.Center)
+ color = inkMuted(), textAlign = TextAlign.Center)
}
@Composable
@@ -293,7 +293,7 @@ private fun DataCell(text: String, modifier: Modifier) {
@Composable
private fun RawField(label: String, value: String) {
Row(Modifier.fillMaxWidth().padding(vertical = 1.dp)) {
- Text("$label: ", fontSize = 12.sp, fontWeight = FontWeight.Medium, color = InkMuted)
+ Text("$label: ", fontSize = 12.sp, fontWeight = FontWeight.Medium, color = inkMuted())
Text(value, fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurface)
}
}
diff --git a/app/src/main/java/com/rainy/token/ui/dashboard/UsageOverviewScreen.kt b/app/src/main/java/com/rainy/token/ui/dashboard/UsageOverviewScreen.kt
index 785d90f..efb2c93 100644
--- a/app/src/main/java/com/rainy/token/ui/dashboard/UsageOverviewScreen.kt
+++ b/app/src/main/java/com/rainy/token/ui/dashboard/UsageOverviewScreen.kt
@@ -43,7 +43,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.rainy.token.data.local.ModelStats
import com.rainy.token.data.local.OverviewStats
-import com.rainy.token.ui.theme.InkMuted
+import com.rainy.token.ui.theme.inkMuted
import com.rainy.token.ui.theme.StrawberryPink
import java.text.SimpleDateFormat
import java.util.Date
@@ -89,7 +89,7 @@ fun UsageOverviewScreen(
Text(uiState.timeFilter.label, color = StrawberryPink, fontWeight = FontWeight.SemiBold)
Icon(Icons.Filled.ArrowDropDown, null, tint = StrawberryPink)
}
- Text("UTC+0", style = MaterialTheme.typography.bodySmall, color = InkMuted, modifier = Modifier.padding(start = 4.dp))
+ Text("UTC+0", style = MaterialTheme.typography.bodySmall, color = inkMuted(), modifier = Modifier.padding(start = 4.dp))
DropdownMenu(menuExpanded, { menuExpanded = false }) {
listOf(TimeFilter.All, TimeFilter.Last5h, TimeFilter.Last24h,
TimeFilter.Today, TimeFilter.Yesterday,
@@ -112,7 +112,7 @@ fun UsageOverviewScreen(
CustomTimeRangeRow(customStartMs, customEndMs,
{ showStartPicker = true }, { showEndPicker = true },
{ viewModel.setTimeFilter(TimeFilter.Custom(customStartMs, customEndMs)) })
- Text("所有时间按 UTC+0 计算", style = MaterialTheme.typography.bodySmall, color = InkMuted)
+ Text("所有时间按 UTC+0 计算", style = MaterialTheme.typography.bodySmall, color = inkMuted())
}
}
// 总览
@@ -157,7 +157,7 @@ fun UsageOverviewScreen(
Row(Modifier.fillMaxWidth(), Arrangement.SpaceBetween, Alignment.CenterVertically) {
TextButton(onClick = { viewModel.prevDailyPage() }, enabled = uiState.dailyPage > 1) {
Text("◀ 上一页", color = StrawberryPink, style = MaterialTheme.typography.bodySmall) }
- Text("${uiState.dailyPage} / $totalPages", style = MaterialTheme.typography.bodySmall, color = InkMuted)
+ Text("${uiState.dailyPage} / $totalPages", style = MaterialTheme.typography.bodySmall, color = inkMuted())
TextButton(onClick = { viewModel.nextDailyPage() }, enabled = uiState.dailyPage < totalPages) {
Text("下一页 ▶", color = StrawberryPink, style = MaterialTheme.typography.bodySmall) }
}
@@ -193,7 +193,7 @@ fun UsageOverviewScreen(
@Composable private fun CacheBreakdownCard(overview: OverviewStats) {
Card(Modifier.fillMaxWidth(), RoundedCornerShape(20.dp), CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)) {
Column(Modifier.padding(16.dp)) {
- Text("缓存 Token 细分", style = MaterialTheme.typography.labelMedium, color = InkMuted)
+ Text("缓存 Token 细分", style = MaterialTheme.typography.labelMedium, color = inkMuted())
Spacer(Modifier.height(8.dp))
Row(Modifier.fillMaxWidth(), Arrangement.SpaceEvenly) {
StatItem("缓存读取", formatTokenCount(overview.cacheReadTokens))
@@ -201,7 +201,7 @@ fun UsageOverviewScreen(
}
if (overview.cacheReadTokens > 0) {
Spacer(Modifier.height(4.dp))
- Text("※ 缓存读取已计入「输入 Token」", style = MaterialTheme.typography.bodySmall, color = InkMuted)
+ Text("※ 缓存读取已计入「输入 Token」", style = MaterialTheme.typography.bodySmall, color = inkMuted())
}
}
}
@@ -216,11 +216,11 @@ fun UsageOverviewScreen(
Row(Modifier.fillMaxWidth().padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
Column(Modifier.weight(1f)) {
Text(stat.model, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium)
- Text("${stat.count} 次调用", style = MaterialTheme.typography.bodySmall, color = InkMuted)
+ Text("${stat.count} 次调用", style = MaterialTheme.typography.bodySmall, color = inkMuted())
}
Column(horizontalAlignment = Alignment.End) {
Text(formatTokenCount(stat.totalTokens), style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold, color = StrawberryPink)
- Text("$${String.format(Locale.US, "%.4f", stat.totalCost / 100_000_000.0)}", style = MaterialTheme.typography.bodySmall, color = InkMuted)
+ Text("$${String.format(Locale.US, "%.4f", stat.totalCost / 100_000_000.0)}", style = MaterialTheme.typography.bodySmall, color = inkMuted())
}
}
}
@@ -233,7 +233,7 @@ fun UsageOverviewScreen(
Text(utcFmt.format(Date(day.dayTs)), style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium, modifier = Modifier.weight(1f))
Column(horizontalAlignment = Alignment.End) {
Text(formatTokenCount(day.totalTokens), style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold, color = StrawberryPink)
- Text("${day.count} 次 · $${String.format(Locale.US, "%.4f", day.totalCost / 100_000_000.0)}", style = MaterialTheme.typography.bodySmall, color = InkMuted)
+ Text("${day.count} 次 · $${String.format(Locale.US, "%.4f", day.totalCost / 100_000_000.0)}", style = MaterialTheme.typography.bodySmall, color = inkMuted())
}
}
}
@@ -242,7 +242,7 @@ fun UsageOverviewScreen(
@Composable private fun StatItem(label: String, value: String) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(value, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = StrawberryPink)
- Text(label, style = MaterialTheme.typography.bodySmall, color = InkMuted)
+ Text(label, style = MaterialTheme.typography.bodySmall, color = inkMuted())
}
}
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 7229173..a36b713 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
@@ -51,7 +51,7 @@ 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.theme.InkMuted
+import com.rainy.token.ui.theme.inkMuted
import com.rainy.token.ui.theme.StrawberryPink
import java.text.SimpleDateFormat
import java.util.Date
@@ -187,7 +187,7 @@ private fun OpenCodeGoWindowsCard(state: State) {
Text(
text = "用量窗口",
style = MaterialTheme.typography.labelLarge,
- color = InkMuted
+ color = inkMuted()
)
Spacer(modifier = Modifier.height(12.dp))
UsageWindowRow(
@@ -226,7 +226,7 @@ private fun UsageWindowRow(label: String, pct: Int?, resetInSec: Long?) {
Text(
text = label,
style = MaterialTheme.typography.bodyMedium,
- color = InkMuted,
+ color = inkMuted(),
modifier = Modifier.weight(1f)
)
Row(verticalAlignment = Alignment.Bottom) {
@@ -238,7 +238,7 @@ private fun UsageWindowRow(label: String, pct: Int?, resetInSec: Long?) {
Text(
text = "%",
style = MaterialTheme.typography.bodyMedium,
- color = InkMuted,
+ color = inkMuted(),
modifier = Modifier.padding(bottom = 2.dp, start = 2.dp)
)
}
@@ -262,7 +262,7 @@ private fun UsageWindowRow(label: String, pct: Int?, resetInSec: Long?) {
Text(
text = "${formatResetInSec(resetInSec)}后重置",
style = MaterialTheme.typography.bodySmall,
- color = InkMuted
+ color = inkMuted()
)
}
}
@@ -293,7 +293,7 @@ private fun MainBalanceCard(state: State, service: ServiceType) {
Text(
text = mainCardLabel(service),
style = MaterialTheme.typography.labelLarge,
- color = InkMuted
+ color = inkMuted()
)
Spacer(modifier = Modifier.weight(1f))
StatusChip(style = stateToChip(state))
@@ -322,14 +322,14 @@ private fun MainBalanceCard(state: State, service: ServiceType) {
Text(
text = "显示的是上次成功获取的余额",
style = MaterialTheme.typography.bodySmall,
- color = InkMuted
+ color = inkMuted()
)
}
is State.ManualModeHint -> {
Text(
text = "—",
style = MaterialTheme.typography.displayMedium,
- color = InkMuted
+ color = inkMuted()
)
}
}
@@ -343,7 +343,7 @@ private fun BalanceBigNumber(balance: ServiceBalance?) {
Text(
text = "—",
style = MaterialTheme.typography.displayLarge,
- color = InkMuted
+ color = inkMuted()
)
return
}
@@ -358,7 +358,7 @@ private fun BalanceBigNumber(balance: ServiceBalance?) {
Text(
text = balance.unit,
style = MaterialTheme.typography.titleLarge,
- color = InkMuted,
+ color = inkMuted(),
modifier = Modifier.padding(bottom = 8.dp)
)
}
@@ -375,7 +375,7 @@ private fun BalanceBigNumber(balance: ServiceBalance?) {
Text(
text = "本月消费 ${formatAmount(spent)}${balance.unit}",
style = MaterialTheme.typography.bodyMedium,
- color = InkMuted
+ color = inkMuted()
)
}
}
@@ -406,7 +406,7 @@ private fun DeepSeekBreakdownCard(state: State) {
Text(
text = "余额构成",
style = MaterialTheme.typography.labelLarge,
- color = InkMuted
+ color = inkMuted()
)
Spacer(modifier = Modifier.height(12.dp))
BreakdownRow("赠送余额", granted, balance!!.unit)
@@ -427,7 +427,7 @@ private fun BreakdownRow(label: String, value: Double, unit: String) {
Text(
text = label,
style = MaterialTheme.typography.bodyMedium,
- color = InkMuted,
+ color = inkMuted(),
modifier = Modifier.weight(1f)
)
Row(verticalAlignment = Alignment.Bottom) {
@@ -440,7 +440,7 @@ private fun BreakdownRow(label: String, value: Double, unit: String) {
Text(
text = unit,
style = MaterialTheme.typography.bodyMedium,
- color = InkMuted,
+ color = inkMuted(),
modifier = Modifier.padding(bottom = 2.dp)
)
}
diff --git a/app/src/main/java/com/rainy/token/ui/theme/Color.kt b/app/src/main/java/com/rainy/token/ui/theme/Color.kt
index ba89c31..467e195 100644
--- a/app/src/main/java/com/rainy/token/ui/theme/Color.kt
+++ b/app/src/main/java/com/rainy/token/ui/theme/Color.kt
@@ -37,3 +37,6 @@ val DarkBackground = Color(0xFF1F1419) // 暖深背景
val DarkSurface = Color(0xFF2A1F25) // 卡片
val DarkOnSurface = Color(0xFFEFE0E5) // 卡片文本
val DarkPrimary = Color(0xFFFFB3C6) // 暗色下主色更亮
+val DarkInkWarm = Color(0xFFEFE0E5) // 暗色下暖黑→浅粉白
+val DarkInkMuted = Color(0xFFC9B8BE) // 暗色下暖灰→浅灰粉
+val DarkInkOutline = Color(0xFF4D3A42) // 暗色下描边
diff --git a/app/src/main/java/com/rainy/token/ui/theme/Theme.kt b/app/src/main/java/com/rainy/token/ui/theme/Theme.kt
index 19fad57..c375f84 100644
--- a/app/src/main/java/com/rainy/token/ui/theme/Theme.kt
+++ b/app/src/main/java/com/rainy/token/ui/theme/Theme.kt
@@ -88,4 +88,14 @@ fun RainyTokenTheme(
typography = RainyTypography,
content = content
)
-}
\ No newline at end of file
+}
+
+/**
+ * 暗色模式自适应的文本颜色。
+ * 浅色模式下返回暖黑/暖灰,深色模式下自动切换到浅色。
+ */
+@Composable
+fun inkWarm(): Color = if (isSystemInDarkTheme()) DarkInkWarm else InkWarm
+
+@Composable
+fun inkMuted(): Color = if (isSystemInDarkTheme()) DarkInkMuted else InkMuted
\ No newline at end of file
diff --git a/app/src/main/java/com/rainy/token/ui/widget/OpenCodeGoWidgetProvider.kt b/app/src/main/java/com/rainy/token/ui/widget/OpenCodeGoWidgetProvider.kt
index fa2c64c..507380c 100644
--- a/app/src/main/java/com/rainy/token/ui/widget/OpenCodeGoWidgetProvider.kt
+++ b/app/src/main/java/com/rainy/token/ui/widget/OpenCodeGoWidgetProvider.kt
@@ -19,13 +19,30 @@ import java.util.Date
import java.util.Locale
/**
- * OpenCode Go 桌面小组件。
+ * OpenCode Go 桌面小组件(MIUI Widget)。
*
- * 显示 3 个用量窗口(5h / 本周 / 本月)的百分比 + 进度条 + 重置倒计时。
- * 优先展示缓存数据;缓存为空或超过 5 分钟冷却时间时,自动触发后台网络刷新。
+ * 显示 3 个用量窗口(5h / 本周 / 本月)的百分比 + 进度条 + 重置倒计时 + DeepSeek 余额。
+ *
+ * 刷新路径:
+ * - MIUI 曝光刷新:用户划到负一屏 → miui.appwidget.action.APPWIDGET_UPDATE → onReceive → onUpdate
+ * - 标准定时刷新:系统 30min 定时 → android.appwidget.action.APPWIDGET_UPDATE → onUpdate
+ * - 手动 ↻ 按钮:PendingIntent 直通 WidgetRefreshReceiver → 网络请求 → notifyDataChanged
+ * - APP 内刷新:notifyDataChanged() → 广播 ACTION_APPWIDGET_UPDATE → onUpdate
+ * - 缓存为空/过期时 onUpdate 自动触发后台刷新
*/
class OpenCodeGoWidgetProvider : AppWidgetProvider() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if ("miui.appwidget.action.APPWIDGET_UPDATE" == intent.action) {
+ val appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS)
+ if (appWidgetIds != null) {
+ onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds)
+ }
+ } else {
+ super.onReceive(context, intent)
+ }
+ }
+
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
@@ -44,7 +61,7 @@ class OpenCodeGoWidgetProvider : AppWidgetProvider() {
context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
- views.setOnClickPendingIntent(R.id.widget_root, pendingIntent)
+ views.setOnClickPendingIntent(R.id.widget_content, pendingIntent)
// 刷新按钮 → 后台广播刷新
val refreshPendingIntent = PendingIntent.getBroadcast(
diff --git a/app/src/main/res/drawable-night/ic_deepseek_logo.png b/app/src/main/res/drawable-night/ic_deepseek_logo.png
new file mode 100644
index 0000000..85ca0b5
Binary files /dev/null and b/app/src/main/res/drawable-night/ic_deepseek_logo.png differ
diff --git a/app/src/main/res/drawable-night/ic_opencode_go_logo.xml b/app/src/main/res/drawable-night/ic_opencode_go_logo.xml
new file mode 100644
index 0000000..8874b6a
--- /dev/null
+++ b/app/src/main/res/drawable-night/ic_opencode_go_logo.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-night/rainytoken_wordmark.png b/app/src/main/res/drawable-night/rainytoken_wordmark.png
new file mode 100644
index 0000000..b4d65ba
Binary files /dev/null and b/app/src/main/res/drawable-night/rainytoken_wordmark.png differ
diff --git a/app/src/main/res/drawable-night/widget_bg.xml b/app/src/main/res/drawable-night/widget_bg.xml
new file mode 100644
index 0000000..f99fb09
--- /dev/null
+++ b/app/src/main/res/drawable-night/widget_bg.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable-nodpi/widget_preview.png b/app/src/main/res/drawable-nodpi/widget_preview.png
new file mode 100644
index 0000000..187a472
Binary files /dev/null and b/app/src/main/res/drawable-nodpi/widget_preview.png differ
diff --git a/app/src/main/res/layout-night/widget_opencode_go.xml b/app/src/main/res/layout-night/widget_opencode_go.xml
new file mode 100644
index 0000000..7353014
--- /dev/null
+++ b/app/src/main/res/layout-night/widget_opencode_go.xml
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/widget_opencode_go.xml b/app/src/main/res/layout/widget_opencode_go.xml
index 6adf53b..b4a1722 100644
--- a/app/src/main/res/layout/widget_opencode_go.xml
+++ b/app/src/main/res/layout/widget_opencode_go.xml
@@ -1,11 +1,19 @@
-
+
+ android:background="@drawable/widget_bg">
+
+
+
+ android:layout_marginBottom="6dp">
+ android:layout_marginBottom="3dp">
+ android:layout_marginBottom="3dp">
+ android:layout_marginBottom="3dp">
+ android:layout_marginBottom="2dp">
-
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/widget_opencode_go_preview.xml b/app/src/main/res/layout/widget_opencode_go_preview.xml
new file mode 100644
index 0000000..c6927ff
--- /dev/null
+++ b/app/src/main/res/layout/widget_opencode_go_preview.xml
@@ -0,0 +1,180 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 08ab72d..a787889 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,5 @@
雨晴Token
+ AI用量面板
+OpenCode Go 用量 + DeepSeek 余额,自动刷新
\ No newline at end of file
diff --git a/app/src/main/res/xml/widget_opencode_go_info.xml b/app/src/main/res/xml/widget_opencode_go_info.xml
index 537cdaa..924714c 100644
--- a/app/src/main/res/xml/widget_opencode_go_info.xml
+++ b/app/src/main/res/xml/widget_opencode_go_info.xml
@@ -1,9 +1,12 @@