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)
This commit is contained in:
WaterRain 2026-06-19 07:38:02 +00:00
parent 05008ca20f
commit 5a18ffc02f
No known key found for this signature in database
20 changed files with 621 additions and 77 deletions

22
.commit_msg Normal file
View File

@ -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)

View File

@ -28,22 +28,44 @@
</intent-filter> </intent-filter>
</activity> </activity>
<!-- 桌面小组件 --> <!-- 桌面小组件:后台刷新接收器(主进程,需要 Hilt DI) -->
<receiver <receiver
android:name=".ui.widget.WidgetRefreshReceiver" android:name=".ui.widget.WidgetRefreshReceiver"
android:exported="false" /> android:exported="false" />
<!-- OpenCode Go 小组件(MIUI Widget) -->
<receiver <receiver
android:name=".ui.widget.OpenCodeGoWidgetProvider" android:name=".ui.widget.OpenCodeGoWidgetProvider"
android:exported="true"> android:exported="true"
android:label="@string/widget_name">
<intent-filter> <intent-filter>
<!-- 标准 Android 定时刷新 -->
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<!-- MIUI 曝光刷新(划到负一屏/桌面时触发) -->
<action android:name="miui.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.appwidget.provider" android:name="android.appwidget.provider"
android:resource="@xml/widget_opencode_go_info" /> android:resource="@xml/widget_opencode_go_info" />
<!-- MIUI Widget 核心标识 -->
<meta-data
android:name="miuiWidget"
android:value="true" />
<!-- MIUI 曝光刷新:用户滑动到小组件所在页面时自动触发 -->
<meta-data
android:name="miuiWidgetRefresh"
android:value="exposure" />
<!-- 曝光刷新最小间隔 20 秒(官方最短为 10 秒) -->
<meta-data
android:name="miuiWidgetRefreshMinInterval"
android:value="20000" />
</receiver> </receiver>
<!-- MIUI Widget 版本号(application 级,升级时递增) -->
<meta-data
android:name="miuiWidgetVersion"
android:value="1" />
</application> </application>
</manifest> </manifest>

View File

@ -2,6 +2,7 @@ package com.rainy.token.ui.components
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
@ -40,7 +41,7 @@ fun ServiceIcon(
modifier = modifier modifier = modifier
.size(size.dp) .size(size.dp)
.clip(RoundedCornerShape(8.dp)) .clip(RoundedCornerShape(8.dp))
.background(Color.White), .background(if (isSystemInDarkTheme()) Color(0xFF352329) else Color.White),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Image( Image(

View File

@ -22,6 +22,7 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons 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.Refresh
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Card 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.StatusChip
import com.rainy.token.ui.components.StatusLevel import com.rainy.token.ui.components.StatusLevel
import com.rainy.token.ui.components.StatusStyle 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 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.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
@ -110,11 +117,36 @@ fun DashboardScreen(
Text( Text(
text = "AI 余额一览", text = "AI 余额一览",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = InkMuted color = inkMuted()
) )
} }
}, },
actions = { 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( IconButton(
onClick = { viewModel.refresh() }, onClick = { viewModel.refresh() },
enabled = !uiState.refreshing enabled = !uiState.refreshing
@ -165,7 +197,7 @@ fun DashboardScreen(
Text( Text(
text = "服务余额", text = "服务余额",
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
color = InkMuted, color = inkMuted(),
modifier = Modifier.padding(top = 8.dp) modifier = Modifier.padding(top = 8.dp)
) )
} }
@ -204,7 +236,7 @@ private fun DashboardCard(card: DashboardCardUi, onClick: () -> Unit) {
Text( Text(
text = secondaryLine(card), text = secondaryLine(card),
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = InkMuted color = inkMuted()
) )
} }
StatusChip(style = card.statusBadgeStyle()) StatusChip(style = card.statusBadgeStyle())
@ -223,7 +255,7 @@ private fun DashboardCard(card: DashboardCardUi, onClick: () -> Unit) {
color = if (card.lastFetchError != null) color = if (card.lastFetchError != null)
MaterialTheme.colorScheme.error MaterialTheme.colorScheme.error
else else
InkMuted inkMuted()
) )
} }
} }
@ -237,24 +269,24 @@ private fun BalanceMainArea(card: DashboardCardUi) {
Text( Text(
text = "—", text = "—",
style = MaterialTheme.typography.displayMedium, style = MaterialTheme.typography.displayMedium,
color = InkMuted color = inkMuted()
) )
Text( Text(
text = "点击配置凭据", text = "点击配置凭据",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = InkMuted color = inkMuted()
) )
} }
balance == null -> { balance == null -> {
Text( Text(
text = "—", text = "—",
style = MaterialTheme.typography.displayMedium, style = MaterialTheme.typography.displayMedium,
color = InkMuted color = inkMuted()
) )
Text( Text(
text = "下拉刷新", text = "下拉刷新",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = InkMuted color = inkMuted()
) )
} }
card.service == ServiceType.OPENCODE_GO -> { card.service == ServiceType.OPENCODE_GO -> {
@ -278,7 +310,7 @@ private fun BalanceMainArea(card: DashboardCardUi) {
Text( Text(
text = balance.unit, text = balance.unit,
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
color = InkMuted, color = inkMuted(),
modifier = Modifier.padding(bottom = 6.dp) modifier = Modifier.padding(bottom = 6.dp)
) )
} }
@ -293,7 +325,7 @@ private fun BalanceMainArea(card: DashboardCardUi) {
Text( Text(
text = "本月已用 ${formatAmount(spent)}${balance.unit}", text = "本月已用 ${formatAmount(spent)}${balance.unit}",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = InkMuted color = inkMuted()
) )
} }
} }
@ -313,7 +345,7 @@ private fun OpenCodeGoMainBalance(balance: com.rainy.token.domain.model.ServiceB
Text( Text(
text = "%", text = "%",
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge,
color = InkMuted, color = inkMuted(),
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
modifier = Modifier.padding(bottom = 6.dp, start = 2.dp) 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(
text = "5h 用量", text = "5h 用量",
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
color = InkMuted, color = inkMuted(),
modifier = Modifier.padding(bottom = 6.dp) modifier = Modifier.padding(bottom = 6.dp)
) )
} }
@ -356,13 +388,13 @@ private fun CompactUsageRowEmpty(label: String, resetInSec: Long?) {
Text( Text(
text = label, text = label,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = InkMuted, color = inkMuted(),
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
Text( Text(
text = "—", text = "—",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = InkMuted color = inkMuted()
) )
} }
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
@ -372,7 +404,7 @@ private fun CompactUsageRowEmpty(label: String, resetInSec: Long?) {
.fillMaxWidth() .fillMaxWidth()
.height(4.dp) .height(4.dp)
.clip(RoundedCornerShape(2.dp)), .clip(RoundedCornerShape(2.dp)),
color = InkMuted.copy(alpha = 0.3f), color = inkMuted().copy(alpha = 0.3f),
trackColor = MaterialTheme.colorScheme.surfaceVariant, trackColor = MaterialTheme.colorScheme.surfaceVariant,
strokeCap = androidx.compose.ui.graphics.StrokeCap.Butt strokeCap = androidx.compose.ui.graphics.StrokeCap.Butt
) )
@ -381,7 +413,7 @@ private fun CompactUsageRowEmpty(label: String, resetInSec: Long?) {
Text( Text(
text = "${formatResetInSec(resetInSec)}后重置", text = "${formatResetInSec(resetInSec)}后重置",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = InkMuted color = inkMuted()
) )
} }
} }
@ -398,7 +430,7 @@ private fun CompactUsageRow(label: String, pct: Int, resetInSec: Long?) {
Text( Text(
text = label, text = label,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = InkMuted, color = inkMuted(),
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
Text( Text(
@ -433,7 +465,7 @@ private fun CompactUsageRow(label: String, pct: Int, resetInSec: Long?) {
Text( Text(
text = "${formatResetInSec(resetInSec)}后重置", text = "${formatResetInSec(resetInSec)}后重置",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = InkMuted color = inkMuted()
) )
} }
} }
@ -511,13 +543,13 @@ private fun DashboardFooter() {
Text( Text(
text = "RainyToken · v1.0", text = "RainyToken · v1.0",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = InkMuted color = inkMuted()
) )
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
Text( Text(
text = "本地加密存储,不上传任何数据", text = "本地加密存储,不上传任何数据",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = InkMuted color = inkMuted()
) )
} }
} }

View File

@ -52,7 +52,7 @@ import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.rainy.token.data.local.UsageRecord 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 com.rainy.token.ui.theme.StrawberryPink
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.time.Instant 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)) Spacer(Modifier.weight(1f))
// 模型筛选 // 模型筛选
Box { Box {
@ -150,17 +150,17 @@ fun UsageDataScreen(
CustomTimeRangeRow(customStartMs, customEndMs, CustomTimeRangeRow(customStartMs, customEndMs,
{ showStartPicker = true }, { showEndPicker = true }, { showStartPicker = true }, { showEndPicker = true },
{ viewModel.setTimeFilter(TimeFilter.Custom(customStartMs, customEndMs)) }) { 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) { 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) { if (state.totalPages > 1) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
TextButton(onClick = { viewModel.prevPage() }, enabled = state.currentPage > 1) { TextButton(onClick = { viewModel.prevPage() }, enabled = state.currentPage > 1) {
Text("◀", color = StrawberryPink, fontSize = 13.sp) } 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) { TextButton(onClick = { viewModel.nextPage() }, enabled = state.currentPage < state.totalPages) {
Text("▶", color = StrawberryPink, fontSize = 13.sp) } Text("▶", color = StrawberryPink, fontSize = 13.sp) }
Spacer(Modifier.width(6.dp)) Spacer(Modifier.width(6.dp))
@ -173,7 +173,7 @@ fun UsageDataScreen(
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = androidx.compose.ui.text.input.KeyboardType.Number), keyboardOptions = KeyboardOptions.Default.copy(keyboardType = androidx.compose.ui.text.input.KeyboardType.Number),
colors = OutlinedTextFieldDefaults.colors( colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = StrawberryPink, focusedBorderColor = StrawberryPink,
unfocusedBorderColor = InkMuted.copy(alpha = 0.4f) unfocusedBorderColor = inkMuted().copy(alpha = 0.4f)
) )
) )
TextButton(onClick = { TextButton(onClick = {
@ -201,11 +201,11 @@ fun UsageDataScreen(
// ─── 记录列表 ─── // ─── 记录列表 ───
if (state.loading) { if (state.loading) {
Box(Modifier.fillMaxWidth().height(200.dp), contentAlignment = Alignment.Center) { Box(Modifier.fillMaxWidth().height(200.dp), contentAlignment = Alignment.Center) {
Text("加载中…", color = InkMuted) Text("加载中…", color = inkMuted())
} }
} else if (state.records.isEmpty()) { } else if (state.records.isEmpty()) {
Box(Modifier.fillMaxWidth().height(200.dp), contentAlignment = Alignment.Center) { Box(Modifier.fillMaxWidth().height(200.dp), contentAlignment = Alignment.Center) {
Text("暂无数据", color = InkMuted) Text("暂无数据", color = inkMuted())
} }
} else { } else {
LazyColumn(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(1.dp)) { LazyColumn(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(1.dp)) {
@ -265,7 +265,7 @@ fun UsageDataScreen(
@Composable @Composable
private fun HeaderCell(text: String, modifier: Modifier) { private fun HeaderCell(text: String, modifier: Modifier) {
Text(text, modifier = modifier, fontSize = 12.sp, fontWeight = FontWeight.Bold, Text(text, modifier = modifier, fontSize = 12.sp, fontWeight = FontWeight.Bold,
color = InkMuted, textAlign = TextAlign.Center) color = inkMuted(), textAlign = TextAlign.Center)
} }
@Composable @Composable
@ -293,7 +293,7 @@ private fun DataCell(text: String, modifier: Modifier) {
@Composable @Composable
private fun RawField(label: String, value: String) { private fun RawField(label: String, value: String) {
Row(Modifier.fillMaxWidth().padding(vertical = 1.dp)) { 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) Text(value, fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurface)
} }
} }

View File

@ -43,7 +43,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.rainy.token.data.local.ModelStats import com.rainy.token.data.local.ModelStats
import com.rainy.token.data.local.OverviewStats 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 com.rainy.token.ui.theme.StrawberryPink
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
@ -89,7 +89,7 @@ fun UsageOverviewScreen(
Text(uiState.timeFilter.label, color = StrawberryPink, fontWeight = FontWeight.SemiBold) Text(uiState.timeFilter.label, color = StrawberryPink, fontWeight = FontWeight.SemiBold)
Icon(Icons.Filled.ArrowDropDown, null, tint = StrawberryPink) 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 }) { DropdownMenu(menuExpanded, { menuExpanded = false }) {
listOf(TimeFilter.All, TimeFilter.Last5h, TimeFilter.Last24h, listOf(TimeFilter.All, TimeFilter.Last5h, TimeFilter.Last24h,
TimeFilter.Today, TimeFilter.Yesterday, TimeFilter.Today, TimeFilter.Yesterday,
@ -112,7 +112,7 @@ fun UsageOverviewScreen(
CustomTimeRangeRow(customStartMs, customEndMs, CustomTimeRangeRow(customStartMs, customEndMs,
{ showStartPicker = true }, { showEndPicker = true }, { showStartPicker = true }, { showEndPicker = true },
{ viewModel.setTimeFilter(TimeFilter.Custom(customStartMs, customEndMs)) }) { 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) { Row(Modifier.fillMaxWidth(), Arrangement.SpaceBetween, Alignment.CenterVertically) {
TextButton(onClick = { viewModel.prevDailyPage() }, enabled = uiState.dailyPage > 1) { TextButton(onClick = { viewModel.prevDailyPage() }, enabled = uiState.dailyPage > 1) {
Text("◀ 上一页", color = StrawberryPink, style = MaterialTheme.typography.bodySmall) } 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) { TextButton(onClick = { viewModel.nextDailyPage() }, enabled = uiState.dailyPage < totalPages) {
Text("下一页 ▶", color = StrawberryPink, style = MaterialTheme.typography.bodySmall) } Text("下一页 ▶", color = StrawberryPink, style = MaterialTheme.typography.bodySmall) }
} }
@ -193,7 +193,7 @@ fun UsageOverviewScreen(
@Composable private fun CacheBreakdownCard(overview: OverviewStats) { @Composable private fun CacheBreakdownCard(overview: OverviewStats) {
Card(Modifier.fillMaxWidth(), RoundedCornerShape(20.dp), CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)) { Card(Modifier.fillMaxWidth(), RoundedCornerShape(20.dp), CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)) {
Column(Modifier.padding(16.dp)) { 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)) Spacer(Modifier.height(8.dp))
Row(Modifier.fillMaxWidth(), Arrangement.SpaceEvenly) { Row(Modifier.fillMaxWidth(), Arrangement.SpaceEvenly) {
StatItem("缓存读取", formatTokenCount(overview.cacheReadTokens)) StatItem("缓存读取", formatTokenCount(overview.cacheReadTokens))
@ -201,7 +201,7 @@ fun UsageOverviewScreen(
} }
if (overview.cacheReadTokens > 0) { if (overview.cacheReadTokens > 0) {
Spacer(Modifier.height(4.dp)) 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) { Row(Modifier.fillMaxWidth().padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
Text(stat.model, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium) 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) { Column(horizontalAlignment = Alignment.End) {
Text(formatTokenCount(stat.totalTokens), style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold, color = StrawberryPink) 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)) Text(utcFmt.format(Date(day.dayTs)), style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium, modifier = Modifier.weight(1f))
Column(horizontalAlignment = Alignment.End) { Column(horizontalAlignment = Alignment.End) {
Text(formatTokenCount(day.totalTokens), style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold, color = StrawberryPink) 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) { @Composable private fun StatItem(label: String, value: String) {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(value, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = StrawberryPink) 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())
} }
} }

View File

@ -51,7 +51,7 @@ import com.rainy.token.ui.components.ServiceIcon
import com.rainy.token.ui.components.StatusChip import com.rainy.token.ui.components.StatusChip
import com.rainy.token.ui.components.StatusLevel import com.rainy.token.ui.components.StatusLevel
import com.rainy.token.ui.components.StatusStyle 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 com.rainy.token.ui.theme.StrawberryPink
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
@ -187,7 +187,7 @@ private fun OpenCodeGoWindowsCard(state: State) {
Text( Text(
text = "用量窗口", text = "用量窗口",
style = MaterialTheme.typography.labelLarge, style = MaterialTheme.typography.labelLarge,
color = InkMuted color = inkMuted()
) )
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
UsageWindowRow( UsageWindowRow(
@ -226,7 +226,7 @@ private fun UsageWindowRow(label: String, pct: Int?, resetInSec: Long?) {
Text( Text(
text = label, text = label,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = InkMuted, color = inkMuted(),
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
Row(verticalAlignment = Alignment.Bottom) { Row(verticalAlignment = Alignment.Bottom) {
@ -238,7 +238,7 @@ private fun UsageWindowRow(label: String, pct: Int?, resetInSec: Long?) {
Text( Text(
text = "%", text = "%",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = InkMuted, color = inkMuted(),
modifier = Modifier.padding(bottom = 2.dp, start = 2.dp) modifier = Modifier.padding(bottom = 2.dp, start = 2.dp)
) )
} }
@ -262,7 +262,7 @@ private fun UsageWindowRow(label: String, pct: Int?, resetInSec: Long?) {
Text( Text(
text = "${formatResetInSec(resetInSec)}后重置", text = "${formatResetInSec(resetInSec)}后重置",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = InkMuted color = inkMuted()
) )
} }
} }
@ -293,7 +293,7 @@ private fun MainBalanceCard(state: State, service: ServiceType) {
Text( Text(
text = mainCardLabel(service), text = mainCardLabel(service),
style = MaterialTheme.typography.labelLarge, style = MaterialTheme.typography.labelLarge,
color = InkMuted color = inkMuted()
) )
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
StatusChip(style = stateToChip(state)) StatusChip(style = stateToChip(state))
@ -322,14 +322,14 @@ private fun MainBalanceCard(state: State, service: ServiceType) {
Text( Text(
text = "显示的是上次成功获取的余额", text = "显示的是上次成功获取的余额",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
color = InkMuted color = inkMuted()
) )
} }
is State.ManualModeHint -> { is State.ManualModeHint -> {
Text( Text(
text = "—", text = "—",
style = MaterialTheme.typography.displayMedium, style = MaterialTheme.typography.displayMedium,
color = InkMuted color = inkMuted()
) )
} }
} }
@ -343,7 +343,7 @@ private fun BalanceBigNumber(balance: ServiceBalance?) {
Text( Text(
text = "—", text = "—",
style = MaterialTheme.typography.displayLarge, style = MaterialTheme.typography.displayLarge,
color = InkMuted color = inkMuted()
) )
return return
} }
@ -358,7 +358,7 @@ private fun BalanceBigNumber(balance: ServiceBalance?) {
Text( Text(
text = balance.unit, text = balance.unit,
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge,
color = InkMuted, color = inkMuted(),
modifier = Modifier.padding(bottom = 8.dp) modifier = Modifier.padding(bottom = 8.dp)
) )
} }
@ -375,7 +375,7 @@ private fun BalanceBigNumber(balance: ServiceBalance?) {
Text( Text(
text = "本月消费 ${formatAmount(spent)}${balance.unit}", text = "本月消费 ${formatAmount(spent)}${balance.unit}",
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = InkMuted color = inkMuted()
) )
} }
} }
@ -406,7 +406,7 @@ private fun DeepSeekBreakdownCard(state: State) {
Text( Text(
text = "余额构成", text = "余额构成",
style = MaterialTheme.typography.labelLarge, style = MaterialTheme.typography.labelLarge,
color = InkMuted color = inkMuted()
) )
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
BreakdownRow("赠送余额", granted, balance!!.unit) BreakdownRow("赠送余额", granted, balance!!.unit)
@ -427,7 +427,7 @@ private fun BreakdownRow(label: String, value: Double, unit: String) {
Text( Text(
text = label, text = label,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = InkMuted, color = inkMuted(),
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
Row(verticalAlignment = Alignment.Bottom) { Row(verticalAlignment = Alignment.Bottom) {
@ -440,7 +440,7 @@ private fun BreakdownRow(label: String, value: Double, unit: String) {
Text( Text(
text = unit, text = unit,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = InkMuted, color = inkMuted(),
modifier = Modifier.padding(bottom = 2.dp) modifier = Modifier.padding(bottom = 2.dp)
) )
} }

View File

@ -37,3 +37,6 @@ val DarkBackground = Color(0xFF1F1419) // 暖深背景
val DarkSurface = Color(0xFF2A1F25) // 卡片 val DarkSurface = Color(0xFF2A1F25) // 卡片
val DarkOnSurface = Color(0xFFEFE0E5) // 卡片文本 val DarkOnSurface = Color(0xFFEFE0E5) // 卡片文本
val DarkPrimary = Color(0xFFFFB3C6) // 暗色下主色更亮 val DarkPrimary = Color(0xFFFFB3C6) // 暗色下主色更亮
val DarkInkWarm = Color(0xFFEFE0E5) // 暗色下暖黑→浅粉白
val DarkInkMuted = Color(0xFFC9B8BE) // 暗色下暖灰→浅灰粉
val DarkInkOutline = Color(0xFF4D3A42) // 暗色下描边

View File

@ -88,4 +88,14 @@ fun RainyTokenTheme(
typography = RainyTypography, typography = RainyTypography,
content = content content = content
) )
} }
/**
* 暗色模式自适应的文本颜色。
* 浅色模式下返回暖黑/暖灰,深色模式下自动切换到浅色。
*/
@Composable
fun inkWarm(): Color = if (isSystemInDarkTheme()) DarkInkWarm else InkWarm
@Composable
fun inkMuted(): Color = if (isSystemInDarkTheme()) DarkInkMuted else InkMuted

View File

@ -19,13 +19,30 @@ import java.util.Date
import java.util.Locale import java.util.Locale
/** /**
* OpenCode Go 桌面小组件。 * OpenCode Go 桌面小组件(MIUI Widget)。
* *
* 显示 3 个用量窗口(5h / 本周 / 本月)的百分比 + 进度条 + 重置倒计时。 * 显示 3 个用量窗口(5h / 本周 / 本月)的百分比 + 进度条 + 重置倒计时 + DeepSeek 余额。
* 优先展示缓存数据;缓存为空或超过 5 分钟冷却时间时,自动触发后台网络刷新。 *
* 刷新路径:
* - 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() { 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( override fun onUpdate(
context: Context, context: Context,
appWidgetManager: AppWidgetManager, appWidgetManager: AppWidgetManager,
@ -44,7 +61,7 @@ class OpenCodeGoWidgetProvider : AppWidgetProvider() {
context, 0, intent, context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE 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( val refreshPendingIntent = PendingIntent.getBroadcast(

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- OpenCode Go logo 深色模式版:深色填充 → 浅色,保证在 #2D1825 背景上可见 -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="54dp"
android:height="30dp"
android:viewportWidth="54"
android:viewportHeight="30">
<path
android:fillColor="#E8D8D5"
android:pathData="M24 30H0V0H24V6H6V24H18V18H12V12H24V30Z"/>
<path
android:fillColor="#A09098"
android:pathData="M12 18H18V24H6V12H12V18Z"/>
<path
android:fillColor="#A09098"
android:pathData="M48 12V24H36V12H48Z"/>
<path
android:fillColor="#E8D8D5"
android:pathData="M54 30H30V0H54V30ZM36 24H48V6H36V24Z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- MIUI Widget 深色模式背景 -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="16dp" />
<solid android:color="#2D1825" />
<stroke
android:width="1dp"
android:color="#4D2D3D" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@ -0,0 +1,212 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- MIUI Widget 深色模式布局 — 文字颜色适配暗色背景 #2D1825 -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/widget_bg">
<LinearLayout
android:id="@+id/widget_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="8dp"
android:gravity="center_vertical">
<!-- 第一行:wordmark + 更新时间 + 刷新按钮 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="2dp">
<ImageView
android:id="@+id/widget_wordmark"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:src="@drawable/rainytoken_wordmark"
android:contentDescription="RainyToken"
android:adjustViewBounds="true" />
<ProgressBar
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"
android:max="100" android:progress="0"
android:progressTint="#00000000"
android:progressBackgroundTint="#00000000"
android:indeterminate="false" />
<TextView
android:id="@+id/widget_updated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="--"
android:textColor="#D4C4CC"
android:textSize="10sp" />
<TextView
android:id="@+id/widget_refresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="↻"
android:textColor="#FF85A2"
android:textSize="20sp"
android:layout_marginStart="6dp" />
</LinearLayout>
<!-- OpenCode Go 标识 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="6dp">
<ImageView
android:id="@+id/widget_logo"
android:layout_width="22dp" android:layout_height="12dp"
android:src="@drawable/ic_opencode_go_logo"
android:contentDescription="OpenCode Go"
android:layout_marginEnd="5dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="OpenCode Go · 用量"
android:textColor="#C4A0AB"
android:textSize="11sp" />
</LinearLayout>
<!-- 5h -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="3dp">
<TextView android:id="@+id/row1_label"
android:layout_width="36dp" android:layout_height="wrap_content"
android:text="5h" android:textColor="#C4A0AB" android:textSize="11sp" />
<TextView android:id="@+id/row1_pct"
android:layout_width="38dp" android:layout_height="wrap_content"
android:text="--" android:textColor="#FFD1DC" android:textSize="12sp"
android:textStyle="bold" android:gravity="end" />
<ProgressBar android:id="@+id/row1_bar"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="0dp" android:layout_height="6dp"
android:layout_weight="1" android:layout_marginStart="8dp"
android:layout_marginEnd="8dp" android:max="100" android:progress="0"
android:progressTint="@color/widget_bar_5h" />
<TextView android:id="@+id/row1_reset"
android:layout_width="48dp" android:layout_height="wrap_content"
android:text="--" android:textColor="#8A7A82" android:textSize="10sp" />
</LinearLayout>
<!-- 本周 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="3dp">
<TextView android:id="@+id/row2_label"
android:layout_width="36dp" android:layout_height="wrap_content"
android:text="本周" android:textColor="#C4A0AB" android:textSize="11sp" />
<TextView android:id="@+id/row2_pct"
android:layout_width="38dp" android:layout_height="wrap_content"
android:text="--" android:textColor="#FFD1DC" android:textSize="12sp"
android:textStyle="bold" android:gravity="end" />
<ProgressBar android:id="@+id/row2_bar"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="0dp" android:layout_height="6dp"
android:layout_weight="1" android:layout_marginStart="8dp"
android:layout_marginEnd="8dp" android:max="100" android:progress="0"
android:progressTint="@color/widget_bar_weekly" />
<TextView android:id="@+id/row2_reset"
android:layout_width="48dp" android:layout_height="wrap_content"
android:text="--" android:textColor="#8A7A82" android:textSize="10sp" />
</LinearLayout>
<!-- 本月 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="3dp">
<TextView android:id="@+id/row3_label"
android:layout_width="36dp" android:layout_height="wrap_content"
android:text="本月" android:textColor="#C4A0AB" android:textSize="11sp" />
<TextView android:id="@+id/row3_pct"
android:layout_width="38dp" android:layout_height="wrap_content"
android:text="--" android:textColor="#FFD1DC" android:textSize="12sp"
android:textStyle="bold" android:gravity="end" />
<ProgressBar android:id="@+id/row3_bar"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="0dp" android:layout_height="6dp"
android:layout_weight="1" android:layout_marginStart="8dp"
android:layout_marginEnd="8dp" android:max="100" android:progress="0"
android:progressTint="@color/widget_bar_monthly" />
<TextView android:id="@+id/row3_reset"
android:layout_width="48dp" android:layout_height="wrap_content"
android:text="--" android:textColor="#8A7A82" android:textSize="10sp" />
</LinearLayout>
<!-- DeepSeek 余额 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="2dp">
<ImageView
android:layout_width="wrap_content" android:layout_height="12dp"
android:src="@drawable/ic_deepseek_logo"
android:contentDescription="DeepSeek"
android:adjustViewBounds="true"
android:layout_marginEnd="5dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DeepSeek 余额"
android:textColor="#C4A0AB"
android:textSize="11sp" />
<TextView
android:id="@+id/widget_ds_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="--"
android:textColor="#FFD1DC"
android:textSize="11sp"
android:textStyle="bold"
android:layout_marginStart="4dp" />
</LinearLayout>
<!-- 署名 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<ProgressBar
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"
android:max="100" android:progress="0"
android:progressTint="#00000000"
android:progressBackgroundTint="#00000000"
android:indeterminate="false" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="雨晴喵"
android:textColor="#8A7A82"
android:textSize="9sp" />
</LinearLayout>
</LinearLayout>
</FrameLayout>

View File

@ -1,11 +1,19 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <!-- MIUI Widget 布局规范:根布局必须为 FrameLayout + @android:id/background -->
android:id="@+id/widget_root" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/background"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@drawable/widget_bg" android:background="@drawable/widget_bg">
android:orientation="vertical"
android:padding="12dp"> <LinearLayout
android:id="@+id/widget_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="8dp"
android:gravity="center_vertical">
<!-- 注意:保持和原来一样的内部结构,只是套了一层 FrameLayout -->
<!-- 第一行 --> <!-- 第一行 -->
<LinearLayout <LinearLayout
@ -59,7 +67,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center_vertical" android:gravity="center_vertical"
android:layout_marginBottom="10dp"> android:layout_marginBottom="6dp">
<ImageView <ImageView
android:id="@+id/widget_logo" android:id="@+id/widget_logo"
@ -83,7 +91,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center_vertical" android:gravity="center_vertical"
android:layout_marginBottom="6dp"> android:layout_marginBottom="3dp">
<TextView android:id="@+id/row1_label" <TextView android:id="@+id/row1_label"
android:layout_width="36dp" android:layout_height="wrap_content" android:layout_width="36dp" android:layout_height="wrap_content"
@ -112,7 +120,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center_vertical" android:gravity="center_vertical"
android:layout_marginBottom="6dp"> android:layout_marginBottom="3dp">
<TextView android:id="@+id/row2_label" <TextView android:id="@+id/row2_label"
android:layout_width="36dp" android:layout_height="wrap_content" android:layout_width="36dp" android:layout_height="wrap_content"
@ -141,7 +149,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center_vertical" android:gravity="center_vertical"
android:layout_marginBottom="8dp"> android:layout_marginBottom="3dp">
<TextView android:id="@+id/row3_label" <TextView android:id="@+id/row3_label"
android:layout_width="36dp" android:layout_height="wrap_content" android:layout_width="36dp" android:layout_height="wrap_content"
@ -170,7 +178,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center_vertical" android:gravity="center_vertical"
android:layout_marginBottom="4dp"> android:layout_marginBottom="2dp">
<ImageView <ImageView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -224,4 +232,6 @@
android:textSize="9sp" /> android:textSize="9sp" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</FrameLayout>

View File

@ -0,0 +1,180 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 小组件预览布局 — 小米小部件中心选择器展示用 -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/widget_bg">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="12dp"
android:gravity="center_vertical">
<!-- 标题行 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="2dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="24dp"
android:src="@drawable/rainytoken_wordmark"
android:adjustViewBounds="true" />
<ProgressBar
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"
android:max="100"
android:progress="0"
android:progressTint="#00000000"
android:progressBackgroundTint="#00000000"
android:indeterminate="false" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="自动刷新"
android:textColor="#C4A0AB"
android:textSize="10sp" />
</LinearLayout>
<!-- OpenCode Go 标识 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="10dp">
<ImageView
android:layout_width="22dp"
android:layout_height="12dp"
android:src="@drawable/ic_opencode_go_logo"
android:layout_marginEnd="5dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="OpenCode Go · 用量"
android:textColor="#8A7A82"
android:textSize="11sp" />
</LinearLayout>
<!-- 5h -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="6dp">
<TextView
android:layout_width="36dp" android:layout_height="wrap_content"
android:text="5h" android:textColor="#8A7A82" android:textSize="11sp" />
<TextView
android:layout_width="38dp" android:layout_height="wrap_content"
android:text="42%" android:textColor="#3D2C35" android:textSize="12sp"
android:textStyle="bold" android:gravity="end" />
<ProgressBar
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="0dp" android:layout_height="6dp"
android:layout_weight="1" android:layout_marginStart="8dp"
android:layout_marginEnd="8dp" android:max="100" android:progress="42"
android:progressTint="#FF85A2" />
</LinearLayout>
<!-- 本周 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="6dp">
<TextView
android:layout_width="36dp" android:layout_height="wrap_content"
android:text="本周" android:textColor="#8A7A82" android:textSize="11sp" />
<TextView
android:layout_width="38dp" android:layout_height="wrap_content"
android:text="68%" android:textColor="#3D2C35" android:textSize="12sp"
android:textStyle="bold" android:gravity="end" />
<ProgressBar
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="0dp" android:layout_height="6dp"
android:layout_weight="1" android:layout_marginStart="8dp"
android:layout_marginEnd="8dp" android:max="100" android:progress="68"
android:progressTint="#FFA726" />
</LinearLayout>
<!-- 本月 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="8dp">
<TextView
android:layout_width="36dp" android:layout_height="wrap_content"
android:text="本月" android:textColor="#8A7A82" android:textSize="11sp" />
<TextView
android:layout_width="38dp" android:layout_height="wrap_content"
android:text="35%" android:textColor="#3D2C35" android:textSize="12sp"
android:textStyle="bold" android:gravity="end" />
<ProgressBar
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="0dp" android:layout_height="6dp"
android:layout_weight="1" android:layout_marginStart="8dp"
android:layout_marginEnd="8dp" android:max="100" android:progress="35"
android:progressTint="#FF85A2" />
</LinearLayout>
<!-- DeepSeek 余额 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="4dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="12dp"
android:src="@drawable/ic_deepseek_logo"
android:adjustViewBounds="true"
android:layout_marginEnd="5dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DeepSeek 余额"
android:textColor="#8A7A82"
android:textSize="11sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="¥18.00"
android:textColor="#3D2C35"
android:textSize="11sp"
android:textStyle="bold"
android:layout_marginStart="4dp" />
</LinearLayout>
</LinearLayout>
</FrameLayout>

View File

@ -1,3 +1,5 @@
<resources> <resources>
<string name="app_name">雨晴Token</string> <string name="app_name">雨晴Token</string>
<string name="widget_name">AI用量面板</string>
<string name="widget_description">OpenCode Go 用量 + DeepSeek 余额,自动刷新</string>
</resources> </resources>

View File

@ -1,9 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="250dp" android:minWidth="300dp"
android:minHeight="110dp" android:minHeight="110dp"
android:initialLayout="@layout/widget_opencode_go" android:initialLayout="@layout/widget_opencode_go"
android:previewImage="@mipmap/ic_launcher" android:previewImage="@drawable/widget_preview"
android:previewLayout="@layout/widget_opencode_go_preview"
android:description="@string/widget_description"
android:resizeMode="vertical|horizontal" android:resizeMode="vertical|horizontal"
android:widgetCategory="home_screen" android:widgetCategory="home_screen|keyguard"
android:widgetFeatures="reconfigurable"
android:updatePeriodMillis="1800000" /> android:updatePeriodMillis="1800000" />