From ceb5e6a26845cf0062a0ad63a1da292cdd100767 Mon Sep 17 00:00:00 2001
From: Liuxinyu176 <1041316040@qq.com>
Date: Thu, 8 Oct 2026 21:08:22 +0800
Subject: [PATCH] =?UTF-8?q?feat(checkin):=20=E7=AD=BE=E5=88=B0=E8=AE=B0?=
=?UTF-8?q?=E5=BD=95=E5=90=AB=E6=97=A5=E6=9C=9F/=E7=8A=B6=E6=80=81/id/?=
=?UTF-8?q?=E6=95=B0=E9=87=8F=EF=BC=8C=E5=A4=B1=E8=B4=A5=E5=8F=AF=E5=BD=93?=
=?UTF-8?q?=E5=A4=A9=E9=87=8D=E8=AF=95=EF=BC=9B=E5=90=8C=E6=AD=A5=E6=9C=AC?=
=?UTF-8?q?=E5=9C=B0=E4=B8=BB=E9=A2=98=E4=B8=8E=E7=BB=84=E4=BB=B6=E6=94=B9?=
=?UTF-8?q?=E5=8A=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.en.md | 2 +-
README.md | 2 +-
app/src/main/AndroidManifest.xml | 10 ++
.../main/java/com/rainy/token/MainActivity.kt | 2 +-
.../data/repository/CheckinStatusStore.kt | 132 ++++++++++++---
.../domain/usecase/RefreshBalanceUseCase.kt | 40 ++++-
.../token/ui/account/ApiManagementScreen.kt | 3 +-
.../token/ui/components/DebugLogScreen.kt | 6 +-
.../token/ui/components/RainyBackground.kt | 6 +-
.../rainy/token/ui/components/ServiceIcon.kt | 4 +-
.../rainy/token/ui/components/StatusChip.kt | 12 +-
.../token/ui/dashboard/DashboardViewModel.kt | 6 +-
.../token/ui/dashboard/UsageChartScreen.kt | 10 +-
.../autocheckin/AutoCheckinEntryPoint.kt | 16 ++
.../autocheckin/AutoCheckinManager.kt | 160 +++++++++++++-----
.../autocheckin/AutoCheckinReceiver.kt | 60 +++++++
.../rainy/token/ui/heatmap/HeatmapCanvas.kt | 28 +--
.../java/com/rainy/token/ui/theme/Color.kt | 45 ++---
.../java/com/rainy/token/ui/theme/Theme.kt | 37 ++--
.../token/ui/webview/CodexOAuthScreen.kt | 4 +-
.../ui/widget/OpenCodeGoWidgetProvider.kt | 4 +-
.../drawable-night/rainytoken_wordmark.png | Bin 17411 -> 11287 bytes
app/src/main/res/drawable-night/widget_bg.xml | 4 +-
.../drawable-nodpi/rainytoken_wordmark.png | Bin 18904 -> 11244 bytes
.../res/drawable-nodpi/widget_preview.png | Bin 8887 -> 5417 bytes
.../res/drawable/ic_launcher_background.xml | 10 +-
.../res/drawable/ic_launcher_foreground.xml | 24 +--
.../main/res/drawable/ic_rainy_wordmark.xml | 18 +-
app/src/main/res/drawable/widget_bg.xml | 4 +-
.../res/layout-night/widget_opencode_go.xml | 34 ++--
.../main/res/layout/widget_opencode_go.xml | 34 ++--
.../res/layout/widget_opencode_go_preview.xml | 24 +--
app/src/main/res/values/widget_colors.xml | 6 +-
docs/签到核心请求.md | 136 +++++++++++++++
34 files changed, 653 insertions(+), 230 deletions(-)
create mode 100644 app/src/main/java/com/rainy/token/ui/dashboard/autocheckin/AutoCheckinEntryPoint.kt
create mode 100644 app/src/main/java/com/rainy/token/ui/dashboard/autocheckin/AutoCheckinReceiver.kt
create mode 100644 docs/签到核心请求.md
diff --git a/README.en.md b/README.en.md
index 4721367..4dd2140 100644
--- a/README.en.md
+++ b/README.en.md
@@ -59,7 +59,7 @@ RainyToken (雨晴Token) — AI Balance & Usage Quota Query · the Rainy Family
| ➕ **One-tap add to home screen** | Tap + inside the app to add the widget directly, no need to browse the system list; double confirmation + permission check |
| 💡 **Usage tips** | Home page shows a random operation tip (refreshed on each launch); settings page lists all 13 hidden tips |
| ⚡ **Room database** | Usage records stored in Room (indexed on workspaceId+timeCreated); DAO queries replace full JSON serialization; auto-migration from the legacy DataStore on first launch |
-| 🎀 **Rainy pink theme** | Material Design 3 · Strawberry Pink #FF85A2 · Sakura Pink #FFD1DC |
+| 🎀 **Rainy pink theme** | Material Design 3 · Refined Rose primary #C9436C · Blush white background #FDEFF4 |
| 🔐 **Codex OAuth sign-in** | Headless OAuth PKCE: the app generates an authorization link → sign in in an external browser → paste the callback URL to complete authorization; no manual auth.json export needed |
| ⚡ **Codex one-tap usage activation** | Codex detail page can send a short request to the ChatGPT API to trigger usage tracking; model list fetched dynamically from models.dev and persisted, with manual refresh; response dialog is copyable |
| ⚡ **OCGO / Ollama one-tap usage activation** | OCGO detail page can send a request to `opencode.ai/zen/v1`, Ollama detail page to `ollama.com/v1` to trigger usage tracking; API keys are entered manually in settings; model lists fetched dynamically from models.dev |
diff --git a/README.md b/README.md
index 00b1515..cf9a4c8 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@ RainyToken(雨晴Token)— AI Balance & Usage Quota Query · the Rainy Famil
| ➕ **一键添桌面** | APP 内点 + 直接添加小组件,不用去系统列表翻;二次确认 + 权限检测 |
| 💡 **使用小技巧** | 首页随机展示一条操作提示(每次启动刷新);设置页可查看全部 13 条隐藏操作技巧 |
| ⚡ **Room 数据库** | 用量记录存 Room(indexed on workspaceId+timeCreated),DAO 查询替代全量 JSON 序列化;首次启动自动从旧 DataStore 迁移 |
-| 🎀 **雨晴粉主题** | Material Design 3 · 草莓粉 #FF85A2 · 樱粉 #FFD1DC |
+| 🎀 **雨晴粉主题** | Material Design 3 · 精致玫瑰粉 主色 #C9436C · 粉白背景 #FDEFF4 |
| 🔐 **Codex OAuth 登录** | 无头模式 OAuth PKCE:APP 生成授权链接 → 外部浏览器登录 → 粘贴回调 URL 完成授权,无需手动导出 auth.json |
| ⚡ **Codex 一键激活用量** | Codex 详情页可向 ChatGPT API 发送简短请求触发用量统计;模型列表从 models.dev 动态获取并持久化,支持手动刷新;响应弹窗可复制 |
| ⚡ **OCGO / Ollama 一键激活用量** | OCGO 详情页可向 `opencode.ai/zen/v1` 发送请求,Ollama 详情页可向 `ollama.com/v1` 发送请求触发用量统计;API Key 在设置页手动填写,模型列表从 models.dev 动态获取 |
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fc63127..18632ca 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,6 +4,7 @@
+
+
+
+
+
+
+
+
result.getOrNull()?.let { s ->
- if (s.outcome == WorkBuddyRepository.CheckinOutcome.SUCCESS ||
- s.outcome == WorkBuddyRepository.CheckinOutcome.ALREADY
- ) {
- checkinStatusStore.markChecked(ServiceType.WORKBUDDY, accountId)
+ val isChecked = s.outcome == WorkBuddyRepository.CheckinOutcome.SUCCESS ||
+ s.outcome == WorkBuddyRepository.CheckinOutcome.ALREADY ||
+ s.outcome == WorkBuddyRepository.CheckinOutcome.QUERY
+ if (isChecked) {
+ checkinStatusStore.record(
+ service = ServiceType.WORKBUDDY,
+ accountId = accountId,
+ status = if (s.outcome == WorkBuddyRepository.CheckinOutcome.QUERY) {
+ CheckinStatusStore.STATUS_ALREADY
+ } else {
+ s.outcome.name
+ },
+ checkinId = if (s.outcome == WorkBuddyRepository.CheckinOutcome.SUCCESS) {
+ java.util.UUID.randomUUID().toString()
+ } else {
+ null
+ },
+ credits = if (s.outcome == WorkBuddyRepository.CheckinOutcome.SUCCESS) {
+ s.todayCredit.takeIf { it > 0 } ?: s.dailyCredit
+ } else {
+ 0
+ }
+ )
}
}
}
@@ -123,7 +143,17 @@ class RefreshBalanceUseCase @Inject constructor(
if (r.outcome == TraeRepository.CheckinOutcome.SUCCESS ||
r.outcome == TraeRepository.CheckinOutcome.ALREADY
) {
- checkinStatusStore.markChecked(ServiceType.TRAE, accountId)
+ checkinStatusStore.record(
+ service = ServiceType.TRAE,
+ accountId = accountId,
+ status = r.outcome.name,
+ checkinId = if (r.outcome == TraeRepository.CheckinOutcome.SUCCESS) {
+ java.util.UUID.randomUUID().toString()
+ } else {
+ null
+ },
+ credits = if (r.outcome == TraeRepository.CheckinOutcome.SUCCESS) r.points else 0
+ )
}
}
}
diff --git a/app/src/main/java/com/rainy/token/ui/account/ApiManagementScreen.kt b/app/src/main/java/com/rainy/token/ui/account/ApiManagementScreen.kt
index 277c7ad..903d253 100644
--- a/app/src/main/java/com/rainy/token/ui/account/ApiManagementScreen.kt
+++ b/app/src/main/java/com/rainy/token/ui/account/ApiManagementScreen.kt
@@ -66,6 +66,7 @@ import com.rainy.token.ui.components.ServiceIcon
import com.rainy.token.ui.components.formatAmount
import com.rainy.token.ui.components.resolve
import com.rainy.token.ui.theme.StrawberryPink
+import com.rainy.token.ui.theme.StatusGreen
import com.rainy.token.ui.theme.StatusOrange
import com.rainy.token.ui.theme.inkMuted
import java.text.SimpleDateFormat
@@ -420,7 +421,7 @@ private fun ApiRowCard(
@Composable
private fun StatusDot(state: CredentialStatus.State) {
val color = when (state) {
- CredentialStatus.State.OK -> Color(0xFF3BB273)
+ CredentialStatus.State.OK -> StatusGreen
CredentialStatus.State.WARNING -> StatusOrange
CredentialStatus.State.EXPIRED -> MaterialTheme.colorScheme.error
CredentialStatus.State.NOT_CONFIGURED -> inkMuted().copy(alpha = 0.5f)
diff --git a/app/src/main/java/com/rainy/token/ui/components/DebugLogScreen.kt b/app/src/main/java/com/rainy/token/ui/components/DebugLogScreen.kt
index 15d7cb8..ee4ec33 100644
--- a/app/src/main/java/com/rainy/token/ui/components/DebugLogScreen.kt
+++ b/app/src/main/java/com/rainy/token/ui/components/DebugLogScreen.kt
@@ -39,6 +39,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.rainy.token.R
import com.rainy.token.data.debug.DebugLog
import com.rainy.token.ui.theme.InkMuted
+import com.rainy.token.ui.theme.StatusOrange
+import com.rainy.token.ui.theme.StatusRed
import com.rainy.token.ui.theme.StrawberryPink
@OptIn(ExperimentalMaterial3Api::class)
@@ -103,8 +105,8 @@ fun DebugLogScreen(onBack: () -> Unit) {
items(entries, key = { it.timestamp.toString() + it.tag + it.message }) { entry ->
val levelColor = when (entry.level) {
DebugLog.Level.INFO -> InkMuted
- DebugLog.Level.WARN -> Color(0xFFFFA726)
- DebugLog.Level.ERROR -> Color(0xFFE91E63)
+ DebugLog.Level.WARN -> StatusOrange
+ DebugLog.Level.ERROR -> StatusRed
}
Card(
modifier = Modifier.fillMaxWidth(),
diff --git a/app/src/main/java/com/rainy/token/ui/components/RainyBackground.kt b/app/src/main/java/com/rainy/token/ui/components/RainyBackground.kt
index f2c7057..103b960 100644
--- a/app/src/main/java/com/rainy/token/ui/components/RainyBackground.kt
+++ b/app/src/main/java/com/rainy/token/ui/components/RainyBackground.kt
@@ -13,10 +13,10 @@ import com.rainy.token.ui.theme.DarkSurface
import androidx.compose.foundation.isSystemInDarkTheme
/**
- * 雨晴风格全局背景。
+ * 雨晴风格全局背景(精致粉调)。
*
- * Light 模式:樱粉渐变(#FFF0F5 → #FFD1DC,从左上到右下)
- * Dark 模式:暖深渐变(#1F1419 → #2A1F25)
+ * Light 模式:低饱和粉白渐变(#FFF9FB → #FDEFF4,从左上到右下)
+ * Dark 模式:暖深渐变(#1A1417 → #251C21)
*
* 放在 Scaffold 容器层,所有页面共享同一层渐变。
*/
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 0072828..13941d3 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
@@ -44,7 +44,7 @@ fun ServiceIcon(
modifier = modifier
.size(size.dp)
.clip(RoundedCornerShape(8.dp))
- .background(if (isSystemInDarkTheme()) Color(0xFF352329) else Color.White),
+ .background(if (isSystemInDarkTheme()) Color(0xFF32262C) else Color.White),
contentAlignment = Alignment.Center
) {
Image(
@@ -59,7 +59,7 @@ fun ServiceIcon(
modifier = modifier
.size(size.dp)
.clip(RoundedCornerShape(8.dp))
- .background(if (isSystemInDarkTheme()) Color(0xFF352329) else Color.White),
+ .background(if (isSystemInDarkTheme()) Color(0xFF32262C) else Color.White),
contentAlignment = Alignment.Center
) {
Image(
diff --git a/app/src/main/java/com/rainy/token/ui/components/StatusChip.kt b/app/src/main/java/com/rainy/token/ui/components/StatusChip.kt
index 4ae9658..cab238d 100644
--- a/app/src/main/java/com/rainy/token/ui/components/StatusChip.kt
+++ b/app/src/main/java/com/rainy/token/ui/components/StatusChip.kt
@@ -1,6 +1,7 @@
package com.rainy.token.ui.components
import androidx.compose.foundation.background
+import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -44,11 +45,14 @@ fun StatusChip(
style: StatusStyle,
modifier: Modifier = Modifier
) {
+ // 暗色模式下胶囊底色是状态色 12% alpha 叠加暗色卡片,
+ // 深色文字几乎不可读,故暗色使用同色系亮色文字。
+ val dark = isSystemInDarkTheme()
val (dotColor, textColor) = when (style.level) {
- StatusLevel.OK -> StatusGreen to Color(0xFF1B5E20)
- StatusLevel.WARNING -> StatusOrange to Color(0xFF8A4A00)
- StatusLevel.ERROR -> StatusRed to Color(0xFF8B0033)
- StatusLevel.INFO -> StatusBlue to Color(0xFF0D47A1)
+ StatusLevel.OK -> StatusGreen to if (dark) Color(0xFF9CE8BC) else Color(0xFF1B5E20)
+ StatusLevel.WARNING -> StatusOrange to if (dark) Color(0xFFFFDBA6) else Color(0xFF8A4A00)
+ StatusLevel.ERROR -> StatusRed to if (dark) Color(0xFFFFB6CA) else Color(0xFF8A1F40)
+ StatusLevel.INFO -> StatusBlue to if (dark) Color(0xFFAED7FF) else Color(0xFF0D47A1)
}
val bgColor = dotColor.copy(alpha = 0.12f)
Row(
diff --git a/app/src/main/java/com/rainy/token/ui/dashboard/DashboardViewModel.kt b/app/src/main/java/com/rainy/token/ui/dashboard/DashboardViewModel.kt
index fa035fe..c9fe07f 100644
--- a/app/src/main/java/com/rainy/token/ui/dashboard/DashboardViewModel.kt
+++ b/app/src/main/java/com/rainy/token/ui/dashboard/DashboardViewModel.kt
@@ -13,6 +13,7 @@ import com.rainy.token.domain.service.ServiceType
import com.rainy.token.domain.usecase.RefreshBalanceUseCase
import com.rainy.token.R
import com.rainy.token.ui.components.UiText
+import com.rainy.token.ui.dashboard.autocheckin.CHECKIN_SERVICES
import com.rainy.token.ui.widget.OpenCodeGoWidgetProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
@@ -62,6 +63,8 @@ class DashboardViewModel @Inject constructor(
// 展示自相矛盾。串行后签到先落库,refresh 再拉到含新积分的余额。
// 用 try/catch 而非 runCatching:后者会吞掉 CancellationException,
// 导致 ViewModel 销毁取消后协程仍继续往下跑。
+ // 2.5) 打开 App 时把零点后台闹钟重排到下次零点(开机/升级/后台被杀后的兜底)
+ autoCheckinManager.refreshSchedule()
try {
autoCheckinManager.runDailyIfDue()
} catch (cancelled: CancellationException) {
@@ -475,9 +478,6 @@ private data class AccountKey(
val accountId: String? = null
)
-/** 支持每日签到的服务(首页角标与自动签到的统一口径)。 */
-internal val CHECKIN_SERVICES = listOf(ServiceType.TRAE, ServiceType.WORKBUDDY)
-
/** 把 Repository 错误映射为本地化文案(ParseError 按 reason 映射资源,detail 仅作日志,不流向 UI)。 */
private fun errorToUiText(error: Throwable): UiText = when (error) {
is RepositoryError.SessionExpired ->
diff --git a/app/src/main/java/com/rainy/token/ui/dashboard/UsageChartScreen.kt b/app/src/main/java/com/rainy/token/ui/dashboard/UsageChartScreen.kt
index 64f5a28..1c365fa 100644
--- a/app/src/main/java/com/rainy/token/ui/dashboard/UsageChartScreen.kt
+++ b/app/src/main/java/com/rainy/token/ui/dashboard/UsageChartScreen.kt
@@ -76,7 +76,7 @@ import java.util.Locale
// 图表用色板(每个模型一个颜色,12 色 Material Design 500 色阶,足够区分 12 个模型)
internal val modelColors = listOf(
Color(0xFFF44336), // Red
- Color(0xFFE91E63), // Pink
+ Color(0xFFC9436C), // Pink(玫瑰主色)
Color(0xFF9C27B0), // Purple
Color(0xFF673AB7), // Deep Purple
Color(0xFF3F51B5), // Indigo
@@ -89,11 +89,11 @@ internal val modelColors = listOf(
Color(0xFF607D8B) // Blue Grey
)
-// Token 堆叠色板:命中缓存(浅粉·顶)、未命中输入(粉·中)、输出(深粉·底)
+// Token 堆叠色板:命中缓存(浅樱粉·顶)、未命中输入(玫瑰粉·中)、输出(深玫瑰·底)
internal val tokenColors = listOf(
- Color(0xFFFFD1DC), // cache hit — 浅粉(上面)
- Color(0xFFFF85A2), // cache miss input — 粉色(中间)
- Color(0xFFE91E63) // output — 深粉(下面)
+ Color(0xFFFFE3EC), // cache hit — 浅樱粉(上面)
+ Color(0xFFFFA3BC), // cache miss input — 玫瑰粉(中间)
+ Color(0xFFC9436C) // output — 深玫瑰(主色,下面)
)
@OptIn(ExperimentalMaterial3Api::class)
diff --git a/app/src/main/java/com/rainy/token/ui/dashboard/autocheckin/AutoCheckinEntryPoint.kt b/app/src/main/java/com/rainy/token/ui/dashboard/autocheckin/AutoCheckinEntryPoint.kt
new file mode 100644
index 0000000..21f2e68
--- /dev/null
+++ b/app/src/main/java/com/rainy/token/ui/dashboard/autocheckin/AutoCheckinEntryPoint.kt
@@ -0,0 +1,16 @@
+package com.rainy.token.ui.dashboard.autocheckin
+
+import dagger.hilt.EntryPoint
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+
+/**
+ * 后台广播(自动签到闹钟 / 开机重排)获取 Hilt 单例的入口。
+ * 与 ui.widget.WidgetRefreshEntryPoint 同一模式:广播接收器不依赖
+ * @AndroidEntryPoint,通过 EntryPointAccessors 拿 SingletonComponent 里的依赖。
+ */
+@EntryPoint
+@InstallIn(SingletonComponent::class)
+interface AutoCheckinEntryPoint {
+ fun autoCheckinManager(): AutoCheckinManager
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/rainy/token/ui/dashboard/autocheckin/AutoCheckinManager.kt b/app/src/main/java/com/rainy/token/ui/dashboard/autocheckin/AutoCheckinManager.kt
index 8ca1919..515d0ae 100644
--- a/app/src/main/java/com/rainy/token/ui/dashboard/autocheckin/AutoCheckinManager.kt
+++ b/app/src/main/java/com/rainy/token/ui/dashboard/autocheckin/AutoCheckinManager.kt
@@ -1,6 +1,9 @@
package com.rainy.token.ui.dashboard.autocheckin
+import android.app.AlarmManager
+import android.app.PendingIntent
import android.content.Context
+import android.content.Intent
import com.rainy.token.data.repository.CheckinStatusStore
import com.rainy.token.data.repository.CredentialRepository
import com.rainy.token.data.repository.RepositoryError
@@ -11,23 +14,32 @@ import com.rainy.token.domain.model.ServiceAccountInfo
import com.rainy.token.domain.service.ServiceType
import com.rainy.token.domain.usecase.RefreshBalanceUseCase
import dagger.hilt.android.qualifiers.ApplicationContext
-import java.time.LocalDate
+import java.time.ZonedDateTime
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
/**
* 每日自动签到编排器(小组件开关控制启用)。
*
- * 行为契约(用户需求 2026-09-13 / 多账号版 2026-10):
- * - 开启后,每天首次打开 App 自动为所有「已配置凭据」的可签服务的【默认账号】各签一次;
- * - 当日只签一次:[CheckinStatusStore] 有当日记录的账号直接跳过(手动签过也算);
+ * 行为契约(用户需求 2026-09-13 / 多账号版 2026-10 / 零点后台版 2026-11):
+ * - 开启后,每天零点后由 AlarmManager 自动触发([AutoCheckinReceiver]),
+ * 为所有「已配置凭据」的可签服务的【全部账号】各签一次;
+ * - 兜底:每天首次打开 App(DashboardViewModel)也会补签一次(错过闹钟/被杀进程场景);
+ * - 当日每账号只签一次:本地有「今日已成功签到」记录的账号直接跳过(手动签过也算);
+ * 失败/限流/设备拦截等非成功记录不占位,下次打开 App 会再次唤起签到流程;
* - 结果汇报:签完把每个账号的结果暴露为 [report],首页据此弹「xx 已签到,积分增加 xx」;
* - 开关持久化在 SharedPreferences "auto_checkin"(小组件与 App 内共用同一份)。
*
* 仅负责编排与记录,不做 UI:签到复用 RefreshBalanceUseCase(带凭据快照会话)。
+ *
+ * 调度说明:使用框架自带的 AlarmManager(非精确 setAndAllowWhileIdle)实现「每日零点后
+ * 后台签到」,零新增依赖;重启设备后闹钟由 BOOT_COMPLETED 广播重排。国产 ROM(如 vivo/
+ * iQOO)可能因电池优化延迟或抑制该闹钟,届时由「首次打开 App」兜底补签。
*/
@Singleton
class AutoCheckinManager @Inject constructor(
@@ -54,52 +66,54 @@ class AutoCheckinManager @Inject constructor(
)
/**
- * 自动签到汇总结果。null=没有可展示的内容(未开启/无默认账号/全部跳过)。
+ * 自动签到汇总结果。null=没有可展示的内容(未开启/无候选账号/全部账号已签或静默跳过)。
* 弹窗消费后由 [consumeReport] 置 null。
*/
private val _report = MutableStateFlow?>(null)
val report: StateFlow?> = _report.asStateFlow()
+ /** 防同日并发重复签到:闹钟广播(IO 线程)与 Dashboard init(viewModelScope)可能同时进入。 */
+ private val runMutex = Mutex()
+
private fun prefs() =
context.getSharedPreferences("auto_checkin", Context.MODE_PRIVATE)
- /** 自动签到开关(小组件 CheckBox 与 App 内共用)。 */
+ /** 自动签到开关(小组件 CheckBox 与 App 内共用)。开关变更时同步排/撤零点闹钟。 */
var enabled: Boolean
get() = prefs().getBoolean(KEY_ENABLED, false)
set(value) {
prefs().edit().putBoolean(KEY_ENABLED, value).apply()
- }
-
- /** 已执行过「今日自动签到」的日期(防止同日多次开 App 重复跑)。 */
- private var lastRunDate: String?
- get() = prefs().getString(KEY_LAST_RUN, null)
- set(value) {
- prefs().edit().putString(KEY_LAST_RUN, value).apply()
+ refreshSchedule()
}
/**
- * 每日首次打开 App 时调用:开启且今日未跑过 → 为已配置凭据的可签服务默认账号各签一次。
+ * 每日签到执行入口(后台闹钟与首次打开 App 共用):
+ * 开启时 → 为已配置凭据的可签服务中【今日尚未成功签到】的账号各签一次。
* 返回 true 表示本次实际执行了签到(无论结果)。
*/
- suspend fun runDailyIfDue(): Boolean {
+ suspend fun runDailyIfDue(): Boolean = runMutex.withLock {
if (!enabled) return false
- val today = LocalDate.now().toString()
- if (lastRunDate == today) return false
- // 先占位再执行:即使执行中崩溃/杀进程,同日再开也不会重跑(宁可少签不重复打扰)
- lastRunDate = today
-
+ // 不再使用全局 lastRunDate 占位:只有真正「今日签到成功」的账号才跳过,
+ // 失败/限流/设备拦截等不会消费当天机会,重开 App 可再次唤起签到流程。
val results = mutableListOf()
for (service in CHECKIN_SERVICES) {
- val defaultAccount = credentialRepository.getDefaultAccount(service) ?: continue
- if (credentialRepository.statusFor(service, defaultAccount.id).state ==
- CredentialStatus.State.NOT_CONFIGURED
- ) {
- continue
+ // 多账号服务遍历全部账号;无账号文档时 getDefaultAccount 同样返回 null,
+ // 候选为空 = 不签,与旧版「只签默认账号」的边界行为一致,不会误签旧凭据
+ val accounts = credentialRepository.accountsFor(service)
+ val candidates = accounts.ifEmpty {
+ listOfNotNull(credentialRepository.getDefaultAccount(service))
+ }
+ for (account in candidates) {
+ if (credentialRepository.statusFor(service, account.id).state ==
+ CredentialStatus.State.NOT_CONFIGURED
+ ) {
+ continue
+ }
+ // 今日已有成功签到记录(手动签过也算)→ 跳过,不再发网络请求
+ if (checkinStatusStore.isCheckedToday(service, account.id)) continue
+ // runOne 返回 null = 无需汇报(如 INTL 不支持签到),不弹误导性条目
+ runOne(service, account)?.let { results += it }
}
- // 当日已有签到记录(手动签过)→ 跳过,不再发网络请求
- if (checkinStatusStore.isCheckedToday(service, defaultAccount.id)) continue
- // runOne 返回 null = 无需汇报(如 INTL 不支持签到),不弹误导性条目
- runOne(service, defaultAccount)?.let { results += it }
}
if (results.isNotEmpty()) {
_report.value = results
@@ -107,6 +121,39 @@ class AutoCheckinManager @Inject constructor(
return true
}
+ /**
+ * 注册/取消每日零点后台闹钟(非精确 setAndAllowWhileIdle,零点后几分钟内触发)。
+ * 设备重启后闹钟丢失,由 BOOT_COMPLETED 广播再次调用本方法重排。
+ */
+ fun refreshSchedule() {
+ val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+ val intent = Intent(context, AutoCheckinReceiver::class.java)
+ .setAction(ACTION_DAILY_CHECKIN)
+ val pendingIntent = PendingIntent.getBroadcast(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ if (!enabled) {
+ alarmManager.cancel(pendingIntent)
+ return
+ }
+ alarmManager.setAndAllowWhileIdle(
+ AlarmManager.RTC_WAKEUP,
+ nextRunTimeMillis(),
+ pendingIntent
+ )
+ }
+
+ /** 下一次零点(00:05,本地时区)的运行时间戳。 */
+ private fun nextRunTimeMillis(): Long {
+ val now = ZonedDateTime.now()
+ var next = now.toLocalDate().atTime(CHECKIN_HOUR, CHECKIN_MINUTE).atZone(now.zone)
+ if (!next.isAfter(now)) next = next.plusDays(1)
+ return next.toInstant().toEpochMilli()
+ }
+
/**
* 返回 null 表示该账号无需出现在汇报弹窗里(如 INTL 不支持签到、静默跳过)。
*/
@@ -132,15 +179,24 @@ class AutoCheckinManager @Inject constructor(
ServiceResult(service, label, false, true, 0, "", false)
// INTL 不支持签到:静默跳过,不进弹窗(避免误报「已签到」)
TraeRepository.CheckinOutcome.INTL_UNSUPPORTED -> null
- TraeRepository.CheckinOutcome.DEVICE_BLOCKED ->
+ TraeRepository.CheckinOutcome.DEVICE_BLOCKED -> {
+ recordFailure(service, account.id, CheckinStatusStore.STATUS_DEVICE_BLOCKED)
ServiceResult(service, label, false, false, 0, r.message, failed = true)
- TraeRepository.CheckinOutcome.RETRYABLE ->
+ }
+ TraeRepository.CheckinOutcome.RETRYABLE -> {
+ recordFailure(service, account.id, CheckinStatusStore.STATUS_RETRYABLE)
ServiceResult(service, label, false, false, 0, r.message, failed = true)
- TraeRepository.CheckinOutcome.FAILED ->
+ }
+ TraeRepository.CheckinOutcome.FAILED -> {
+ recordFailure(service, account.id, CheckinStatusStore.STATUS_FAILED)
ServiceResult(service, label, false, false, 0, r.message, failed = true)
+ }
}
},
- onFailure = { e -> failureResult(service, label, account.id, e) }
+ onFailure = { e ->
+ recordFailure(service, account.id, CheckinStatusStore.STATUS_FAILED)
+ failureResult(service, label, account.id, e)
+ }
)
ServiceType.WORKBUDDY -> refreshBalanceUseCase.checkInWorkBuddy(account.id).fold(
onSuccess = { s ->
@@ -158,11 +214,16 @@ class AutoCheckinManager @Inject constructor(
ServiceResult(service, label, false, true, 0, "", false)
WorkBuddyRepository.CheckinOutcome.QUERY ->
ServiceResult(service, label, false, true, 0, "", false)
- WorkBuddyRepository.CheckinOutcome.FAILED ->
+ WorkBuddyRepository.CheckinOutcome.FAILED -> {
+ recordFailure(service, account.id, CheckinStatusStore.STATUS_FAILED)
ServiceResult(service, label, false, false, 0, s.message, failed = true)
+ }
}
},
- onFailure = { e -> failureResult(service, label, account.id, e) }
+ onFailure = { e ->
+ recordFailure(service, account.id, CheckinStatusStore.STATUS_FAILED)
+ failureResult(service, label, account.id, e)
+ }
)
// CHECKIN_SERVICES 只含上述两者;穷尽兜底(未来加服务忘了实现时安全返回失败)
else -> null
@@ -173,7 +234,7 @@ class AutoCheckinManager @Inject constructor(
* 签到请求失败的汇报结果。
*
* 竞态兜底:签到请求在服务端已成功([RefreshBalanceUseCase.checkInTrae] 的 also 块
- * 已 markChecked),但与并发余额刷新争抢凭据快照导致结果被 [RepositoryError.CredentialChanged]
+ * 已写 CheckinStatusStore 成功记录),但与并发余额刷新争抢凭据快照导致结果被 [RepositoryError.CredentialChanged]
* 丢弃时——若本地记录确认今日已签,按「今日已签到」汇报,而不是报「凭据」错误
* (用户会看到积分实际到账却提示失败的矛盾)。
*/
@@ -199,16 +260,37 @@ class AutoCheckinManager @Inject constructor(
)
}
+ /**
+ * 落一条非成功签到记录(仅当今天还没有成功记录时写入)。
+ * 失败/限流/设备拦截不消费当天签到机会,重开 App 仍会重试;
+ * 但若在一次 CredentialChanged 竞态中服务端实际已成功,保留成功记录不被覆盖。
+ */
+ private fun recordFailure(
+ service: ServiceType,
+ accountId: String,
+ status: String
+ ) {
+ if (!checkinStatusStore.isCheckedToday(service, accountId)) {
+ checkinStatusStore.record(service, accountId, status)
+ }
+ }
+
/** 弹窗关闭时调用,清除汇报状态(下次执行前不再重复弹)。 */
fun consumeReport() {
_report.value = null
}
- private companion object {
+ companion object {
const val KEY_ENABLED = "enabled"
- const val KEY_LAST_RUN = "last_run_date"
+
+ /** 每日自动签到闹钟的广播 action(供 [AutoCheckinReceiver] 分发)。 */
+ const val ACTION_DAILY_CHECKIN = "com.rainy.token.action.DAILY_AUTO_CHECKIN"
+
+ /** 每日签到目标时间:零点后 5 分钟(避开日切边界),本地时区。 */
+ const val CHECKIN_HOUR = 0
+ const val CHECKIN_MINUTE = 5
}
}
-/** 支持每日签到的服务(与 Dashboard 的 CHECKIN_SERVICES 同一口径,避免两处漂移)。 */
+/** 支持每日签到的服务(DashboardViewModel 与自动签到共用的唯一来源,避免两处漂移)。 */
internal val CHECKIN_SERVICES = listOf(ServiceType.TRAE, ServiceType.WORKBUDDY)
\ No newline at end of file
diff --git a/app/src/main/java/com/rainy/token/ui/dashboard/autocheckin/AutoCheckinReceiver.kt b/app/src/main/java/com/rainy/token/ui/dashboard/autocheckin/AutoCheckinReceiver.kt
new file mode 100644
index 0000000..dc7f5a7
--- /dev/null
+++ b/app/src/main/java/com/rainy/token/ui/dashboard/autocheckin/AutoCheckinReceiver.kt
@@ -0,0 +1,60 @@
+package com.rainy.token.ui.dashboard.autocheckin
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import dagger.hilt.android.EntryPointAccessors
+import kotlin.coroutines.cancellation.CancellationException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.launch
+
+/**
+ * 每日自动签到后台触发点。
+ *
+ * 两种入口共用同一个接收器:
+ * - [AutoCheckinManager.ACTION_DAILY_CHECKIN]:AlarmManager 每日零点后发出的闹钟广播,
+ * 到点就为该服务的全部已配置账号签到;
+ * - ACTION_BOOT_COMPLETED:设备重启后闹钟会被系统清空,这里只负责重新排程,不抢签。
+ *
+ * 通过 EntryPointAccessors 获取 Hilt 单例,不依赖 @AndroidEntryPoint。
+ */
+class AutoCheckinReceiver : BroadcastReceiver() {
+
+ override fun onReceive(context: Context, intent: Intent) {
+ val action = intent.action
+ val appContext = context.applicationContext
+ val pendingResult = goAsync()
+
+ CoroutineScope(SupervisorJob() + Dispatchers.IO).launch {
+ try {
+ val manager = EntryPointAccessors.fromApplication(
+ appContext, AutoCheckinEntryPoint::class.java
+ ).autoCheckinManager()
+
+ when (action) {
+ AutoCheckinManager.ACTION_DAILY_CHECKIN -> {
+ // 到点签到(关闭时 runDailyIfDue 自身会短路返回);
+ // 无论本次签到是否抛异常,都必须把一次性闹钟重排到明天
+ try {
+ manager.runDailyIfDue()
+ } finally {
+ manager.refreshSchedule()
+ }
+ }
+ Intent.ACTION_BOOT_COMPLETED -> {
+ // 重启后闹钟丢失,重新排程(今天是否签到由首次打开 App 兜底)
+ manager.refreshSchedule()
+ }
+ }
+ } catch (cancelled: CancellationException) {
+ throw cancelled
+ } catch (_: Exception) {
+ // 后台静默:签到/排程失败不阻塞下次闹钟,等下次打开 App 再补救
+ } finally {
+ pendingResult.finish()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/rainy/token/ui/heatmap/HeatmapCanvas.kt b/app/src/main/java/com/rainy/token/ui/heatmap/HeatmapCanvas.kt
index 8bc860e..0069700 100644
--- a/app/src/main/java/com/rainy/token/ui/heatmap/HeatmapCanvas.kt
+++ b/app/src/main/java/com/rainy/token/ui/heatmap/HeatmapCanvas.kt
@@ -66,24 +66,24 @@ import kotlinx.coroutines.flow.first
// ── 热力图颜色 ──────────────────────────────────────────────
-/** 亮色模式 6 级粉色梯度 */
+/** 亮色模式 6 级玫瑰粉梯度(精致粉调,最高级与主色一致) */
private val LightHeatmapColors = listOf(
- Color(0xFFEFE0E5), // Level 0: 极浅暖灰
- Color(0xFFFFD6E0), // Level 1: 浅樱粉
- Color(0xFFFFB3C6), // Level 2: 草莓粉柔
- Color(0xFFFF85A2), // Level 3: 草莓粉
- Color(0xFFFF6B8E), // Level 4: 草莓粉深
- Color(0xFFE84973), // Level 5: 玫红
+ Color(0xFFF3E9EE), // Level 0: 极浅暖灰
+ Color(0xFFFFE3EC), // Level 1: 浅樱粉
+ Color(0xFFFFC9D9), // Level 2: 浅玫瑰柔
+ Color(0xFFFFA3BC), // Level 3: 玫瑰粉
+ Color(0xFFF27E9F), // Level 4: 玫瑰粉深
+ Color(0xFFC9436C), // Level 5: 深玫瑰(主色)
)
-/** 暗色模式 6 级粉色梯度 */
+/** 暗色模式 6 级玫瑰粉梯度 */
private val DarkHeatmapColors = listOf(
- Color(0xFF3A2A30), // Level 0: 暖深灰
- Color(0xFF4A2E3A), // Level 1
- Color(0xFF6B3D4A), // Level 2
- Color(0xFF8B4D5A), // Level 3
- Color(0xFFB86880), // Level 4
- Color(0xFFD67F9A), // Level 5
+ Color(0xFF3A2B33), // Level 0: 暖深灰
+ Color(0xFF4A3239), // Level 1
+ Color(0xFF643D4A), // Level 2
+ Color(0xFF855062), // Level 3
+ Color(0xFFA9627A), // Level 4
+ Color(0xFFE07C9A), // Level 5
)
/** 月份名称资源(按月索引 1~12,locale-aware) */
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 467e195..8502a18 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
@@ -3,40 +3,41 @@ package com.rainy.token.ui.theme
import androidx.compose.ui.graphics.Color
/**
- * 雨晴风格配色(RainyStyle Palette)。
+ * 雨晴风格配色(RainyStyle Palette)— 精致粉调。
*
- * 视觉基调:元气樱粉 / 草莓粉主调,搭配纯白卡片与暖灰文本。
+ * 视觉基调:低饱和樱粉白背景 + 纯白卡片 + 深玫瑰主色点缀,
+ * 通过克制用色与留白呈现精致感,保留"雨晴粉"品牌识别度。
* 状态色:草绿(正常)、暖橙(注意)、玫红(异常)。
*
- * 背景使用樱粉渐变(#FFF0F5 → #FFD1DC),主元素采用草莓粉
- * (#FF85A2 / #FF6B8E),与白色形成层次。
+ * 背景使用柔和粉白渐变(#FFF9FB → #FDEFF4),主元素采用深玫瑰
+ * (#C9436C / #B3305B),与白色形成优雅层次。
*/
// ─── 品牌主色 ───
-val CherryPinkLight = Color(0xFFFFF0F5) // 樱粉浅(背景起始)
-val CherryPinkDeep = Color(0xFFFFD1DC) // 樱粉深(背景结束)
-val StrawberryPink = Color(0xFFFF85A2) // 草莓粉(主色 / 强调)
-val StrawberryPinkDark = Color(0xFFFF6B8E)// 草莓粉深(按下态 / 主按钮)
-val StrawberryPinkSoft = Color(0xFFFFB3C6)// 草莓粉柔(hover / 副按钮)
+val CherryPinkLight = Color(0xFFFFF9FB) // 粉白(背景起始)
+val CherryPinkDeep = Color(0xFFFDEFF4) // 浅樱粉(背景结束)
+val StrawberryPink = Color(0xFFC9436C) // 深玫瑰(主色 / 强调,白底对比度 ≥ 4.5:1)
+val StrawberryPinkDark = Color(0xFFB3305B)// 深玫瑰按下态(主按钮)
+val StrawberryPinkSoft = Color(0xFFFFC9D8)// 浅玫瑰柔(容器 / 选中背景)
// ─── 中性色 ───
val PureWhite = Color(0xFFFFFFFF)
-val SnowWhite = Color(0xFFFAF6F8) // 卡片次级背景
-val InkWarm = Color(0xFF3D2C35) // 暖黑(主文本)
-val InkMuted = Color(0xFF7A6B72) // 暖灰(次要文本)
-val InkOutline = Color(0xFFD9CFD3) // 暖灰(分隔线 / 描边)
+val SnowWhite = Color(0xFFFAF5F7) // 卡片次级背景
+val InkWarm = Color(0xFF3A2B31) // 暖黑(主文本)
+val InkMuted = Color(0xFF7A6870) // 暖灰(次要文本,白底 ≥ 4.5:1)
+val InkOutline = Color(0xFFE8DCE1) // 暖灰(分隔线 / 描边)
// ─── 状态色 ───
-val StatusGreen = Color(0xFF66BB6A) // 正常
+val StatusGreen = Color(0xFF4CAF50) // 正常
val StatusOrange = Color(0xFFFFA726) // 注意 / 需重登
-val StatusRed = Color(0xFFE91E63) // 错误 / 异常
+val StatusRed = Color(0xFFD64A6E) // 错误 / 异常(玫红,与粉调统一)
val StatusBlue = Color(0xFF64B5F6) // 信息(Stale 缓存)
// ─── 暗色模式 ───
-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) // 暗色下描边
+val DarkBackground = Color(0xFF1A1417) // 暖深背景
+val DarkSurface = Color(0xFF251C21) // 卡片
+val DarkOnSurface = Color(0xFFF2E6EB) // 卡片文本
+val DarkPrimary = Color(0xFFFFA9BF) // 暗色下主色更亮
+val DarkInkWarm = Color(0xFFF2E6EB) // 暗色下暖黑→浅粉白
+val DarkInkMuted = Color(0xFFCBB8C0) // 暗色下暖灰→浅灰粉
+val DarkInkOutline = Color(0xFF4C3B44) // 暗色下描边
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 c375f84..e297063 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
@@ -13,21 +13,22 @@ import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
/**
- * 雨晴风格主题。
+ * 雨晴风格主题(精致粉调)。
*
- * - **不使用** dynamicColor(系统主题色),固定使用雨晴樱粉品牌色。
+ * - **不使用** dynamicColor(系统主题色),固定使用雨晴玫瑰粉品牌色。
* 理由:dynamicColor 会让 APP 视觉随系统变化,破坏品牌一致性;
* 玩家一眼看到 APP icon + 粉色调就知道是 RainyToken 系列。
- * - 暗色模式:暖深背景 + 亮草莓粉强调,跟 Light 模式同色系不同明度。
+ * - 主色:深玫瑰 #C9436C;容器色:浅玫瑰粉;背景:低饱和粉白渐变。
+ * - 暗色模式:暖深背景 + 亮玫瑰粉强调,跟 Light 模式同色系不同明度。
*/
private val LightColors = lightColorScheme(
primary = StrawberryPink,
onPrimary = PureWhite,
primaryContainer = StrawberryPinkSoft,
- onPrimaryContainer = InkWarm,
- secondary = StrawberryPinkDark,
+ onPrimaryContainer = Color(0xFF5C112C),
+ secondary = Color(0xFFB7698B),
onSecondary = PureWhite,
- secondaryContainer = Color(0xFFFFE4EC),
+ secondaryContainer = Color(0xFFFFE7EE),
onSecondaryContainer = InkWarm,
tertiary = StatusGreen,
onTertiary = PureWhite,
@@ -38,32 +39,32 @@ private val LightColors = lightColorScheme(
surfaceVariant = SnowWhite,
onSurfaceVariant = InkMuted,
outline = InkOutline,
- outlineVariant = Color(0xFFEFE0E5),
+ outlineVariant = Color(0xFFF2E7EB),
error = StatusRed,
onError = PureWhite
)
private val DarkColors = darkColorScheme(
primary = DarkPrimary,
- onPrimary = DarkBackground,
- primaryContainer = StrawberryPinkDark,
- onPrimaryContainer = PureWhite,
+ onPrimary = Color(0xFF4A1025),
+ primaryContainer = Color(0xFF6B2C42),
+ onPrimaryContainer = DarkOnSurface,
secondary = StrawberryPink,
onSecondary = DarkBackground,
secondaryContainer = Color(0xFF4A2E3A),
onSecondaryContainer = DarkOnSurface,
- tertiary = StatusGreen,
- onTertiary = DarkBackground,
+ tertiary = Color(0xFF7BCF9E),
+ onTertiary = Color(0xFF0D2B1C),
background = DarkBackground,
onBackground = DarkOnSurface,
surface = DarkSurface,
onSurface = DarkOnSurface,
- surfaceVariant = Color(0xFF352329),
- onSurfaceVariant = Color(0xFFC9B8BE),
- outline = Color(0xFF4D3A42),
- outlineVariant = Color(0xFF3A2A30),
- error = Color(0xFFFF6B8E),
- onError = PureWhite
+ surfaceVariant = Color(0xFF32262C),
+ onSurfaceVariant = Color(0xFFCBB8C0),
+ outline = Color(0xFF4C3B44),
+ outlineVariant = Color(0xFF3B2C33),
+ error = Color(0xFFFF8194),
+ onError = Color(0xFF4A1025)
)
@Composable
diff --git a/app/src/main/java/com/rainy/token/ui/webview/CodexOAuthScreen.kt b/app/src/main/java/com/rainy/token/ui/webview/CodexOAuthScreen.kt
index 2d9d026..6a52d99 100644
--- a/app/src/main/java/com/rainy/token/ui/webview/CodexOAuthScreen.kt
+++ b/app/src/main/java/com/rainy/token/ui/webview/CodexOAuthScreen.kt
@@ -122,7 +122,7 @@ fun CodexOAuthScreen(
Text(
uiState.error!!.asString(),
style = MaterialTheme.typography.bodyLarge,
- color = androidx.compose.ui.graphics.Color(0xFFE91E63)
+ color = MaterialTheme.colorScheme.error
)
TextButton(onClick = { viewModel.start(CodexOAuthMode.WEBVIEW) }) {
Text(stringResource(R.string.action_retry), color = StrawberryPink)
@@ -286,7 +286,7 @@ private fun HeadlessOAuthContent(
Text(
text = error.asString(),
style = MaterialTheme.typography.bodySmall,
- color = androidx.compose.ui.graphics.Color(0xFFE91E63),
+ color = MaterialTheme.colorScheme.error,
modifier = Modifier.padding(top = 4.dp)
)
TextButton(onClick = onRetry) {
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 56e54d1..f3d94cd 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
@@ -339,9 +339,9 @@ class OpenCodeGoWidgetProvider : AppWidgetProvider() {
/** 根据用量百分比动态改进度条颜色 */
private fun setProgressColor(views: RemoteViews, barViewId: Int, pct: Int) {
val color = when {
- pct >= 80 -> 0xFFE91E63.toInt() // 玫红
+ pct >= 80 -> 0xFFD64A6E.toInt() // 玫红
pct >= 50 -> 0xFFFFA726.toInt() // 暖橙
- else -> 0xFFFF85A2.toInt() // 草莓粉
+ else -> 0xFFC9436C.toInt() // 深玫瑰(主色)
}
views.setColorStateList(barViewId, "setProgressTintList", ColorStateList.valueOf(color))
}
diff --git a/app/src/main/res/drawable-night/rainytoken_wordmark.png b/app/src/main/res/drawable-night/rainytoken_wordmark.png
index b4d65baa199009c3cb551c89afed727f4c4e5a87..974ef9f22ed68c5c6144d624b25bcb53abcc99ef 100644
GIT binary patch
literal 11287
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
literal 17411
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
T^!5QB?!n;c>gTe~DWM4fAlk=B
diff --git a/app/src/main/res/drawable-night/widget_bg.xml b/app/src/main/res/drawable-night/widget_bg.xml
index f99fb09..73f621a 100644
--- a/app/src/main/res/drawable-night/widget_bg.xml
+++ b/app/src/main/res/drawable-night/widget_bg.xml
@@ -3,8 +3,8 @@
-
+
+ android:color="#4C3B44" />
\ No newline at end of file
diff --git a/app/src/main/res/drawable-nodpi/rainytoken_wordmark.png b/app/src/main/res/drawable-nodpi/rainytoken_wordmark.png
index d8435c8ec68f57df64e1b78b571672a6efef9cf1..0990a3bb90be8d1231e5016b2c55be435dfa7910 100644
GIT binary patch
literal 11244
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
UsN;b;a*7uPtf;O~BWD)&KMu9v<^TWy
literal 18904
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