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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 - + - - - + + + - + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml index 6a60b13..1347412 100644 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -7,14 +7,14 @@ android:viewportHeight="108"> - + + android:fillColor="#C9436C" /> - + + android:fillColor="#FFC9D8" /> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_rainy_wordmark.xml b/app/src/main/res/drawable/ic_rainy_wordmark.xml index 2a110f1..1c4eaf3 100644 --- a/app/src/main/res/drawable/ic_rainy_wordmark.xml +++ b/app/src/main/res/drawable/ic_rainy_wordmark.xml @@ -1,9 +1,9 @@ - + - + \ No newline at end of file diff --git a/app/src/main/res/drawable/widget_bg.xml b/app/src/main/res/drawable/widget_bg.xml index 8e73ed4..67925d2 100644 --- a/app/src/main/res/drawable/widget_bg.xml +++ b/app/src/main/res/drawable/widget_bg.xml @@ -2,8 +2,8 @@ - + + android:color="#F2DCE4" /> \ No newline at end of file diff --git a/app/src/main/res/layout-night/widget_opencode_go.xml b/app/src/main/res/layout-night/widget_opencode_go.xml index 06e632d..b551c7e 100644 --- a/app/src/main/res/layout-night/widget_opencode_go.xml +++ b/app/src/main/res/layout-night/widget_opencode_go.xml @@ -1,5 +1,5 @@ - + @@ -44,7 +44,7 @@ android:layout_width="48dp" android:layout_height="24dp" android:text="OCGO" - android:textColor="#FF85A2" + android:textColor="#FFA9BF" android:textSize="10sp" android:textStyle="bold" android:layout_marginStart="6dp" @@ -75,7 +75,7 @@ android:layout_width="40dp" android:layout_height="24dp" android:text="↻" - android:textColor="#FF85A2" + android:textColor="#FFA9BF" android:textSize="20sp" android:layout_marginStart="6dp" android:gravity="center" /> @@ -99,7 +99,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/widget_title_opencode" - android:textColor="#C4A0AB" + android:textColor="#CBB8C0" android:textSize="11sp" /> @@ -113,10 +113,10 @@ + android:text="5h" android:textColor="#CBB8C0" android:textSize="11sp" /> + android:text="--" android:textColor="#CBB8C0" android:textSize="10sp" /> @@ -139,10 +139,10 @@ + android:text="@string/window_weekly" android:textColor="#CBB8C0" android:textSize="11sp" /> + android:text="--" android:textColor="#CBB8C0" android:textSize="10sp" /> @@ -165,10 +165,10 @@ + android:text="@string/window_monthly" android:textColor="#CBB8C0" android:textSize="11sp" /> + android:text="--" android:textColor="#CBB8C0" android:textSize="10sp" /> @@ -200,14 +200,14 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/widget_deepseek_balance" - android:textColor="#C4A0AB" + android:textColor="#CBB8C0" android:textSize="11sp" /> @@ -233,7 +233,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/widget_brand" - android:textColor="#8A7A82" + android:textColor="#CBB8C0" android:textSize="9sp" /> diff --git a/app/src/main/res/layout/widget_opencode_go.xml b/app/src/main/res/layout/widget_opencode_go.xml index 6f121ef..73f9f0e 100644 --- a/app/src/main/res/layout/widget_opencode_go.xml +++ b/app/src/main/res/layout/widget_opencode_go.xml @@ -36,7 +36,7 @@ android:layout_width="10dp" android:layout_height="24dp" android:text=">" - android:textColor="#C4A0AB" + android:textColor="#7A6870" android:textSize="14sp" android:gravity="center" /> @@ -45,7 +45,7 @@ android:layout_width="48dp" android:layout_height="24dp" android:text="OCGO" - android:textColor="#FF85A2" + android:textColor="#C9436C" android:textSize="10sp" android:textStyle="bold" android:layout_marginStart="6dp" @@ -69,7 +69,7 @@ android:maxLines="1" android:ellipsize="end" android:text="--" - android:textColor="#C4A0AB" + android:textColor="#7A6870" android:textSize="10sp" /> @@ -105,7 +105,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/widget_title_opencode" - android:textColor="#8A7A82" + android:textColor="#7A6870" android:textSize="11sp" /> @@ -120,11 +120,11 @@ + android:text="5h" android:textColor="#7A6870" android:textSize="11sp" /> + android:text="--" android:textColor="#7A6870" android:textSize="10sp" /> @@ -150,11 +150,11 @@ + android:text="@string/window_weekly" android:textColor="#7A6870" android:textSize="11sp" /> + android:text="--" android:textColor="#7A6870" android:textSize="10sp" /> @@ -180,11 +180,11 @@ + android:text="@string/window_monthly" android:textColor="#7A6870" android:textSize="11sp" /> + android:text="--" android:textColor="#7A6870" android:textSize="10sp" /> @@ -221,7 +221,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/widget_deepseek_balance" - android:textColor="#8A7A82" + android:textColor="#7A6870" android:textSize="11sp" /> @@ -258,7 +258,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/widget_brand" - android:textColor="#D4C4CC" + android:textColor="#7A6870" android:textSize="9sp" /> diff --git a/app/src/main/res/layout/widget_opencode_go_preview.xml b/app/src/main/res/layout/widget_opencode_go_preview.xml index a33984a..95d0c15 100644 --- a/app/src/main/res/layout/widget_opencode_go_preview.xml +++ b/app/src/main/res/layout/widget_opencode_go_preview.xml @@ -42,7 +42,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/widget_auto_refresh" - android:textColor="#C4A0AB" + android:textColor="#7A6870" android:textSize="10sp" /> @@ -64,7 +64,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/widget_title_opencode" - android:textColor="#8A7A82" + android:textColor="#7A6870" android:textSize="11sp" /> @@ -79,11 +79,11 @@ + android:text="5h" android:textColor="#7A6870" android:textSize="11sp" /> + android:progressTint="#C9436C" /> @@ -105,11 +105,11 @@ + android:text="@string/window_weekly" android:textColor="#7A6870" android:textSize="11sp" /> + android:text="@string/window_monthly" android:textColor="#7A6870" android:textSize="11sp" /> + android:progressTint="#C9436C" /> @@ -165,14 +165,14 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/widget_deepseek_balance" - android:textColor="#8A7A82" + android:textColor="#7A6870" android:textSize="11sp" /> diff --git a/app/src/main/res/values/widget_colors.xml b/app/src/main/res/values/widget_colors.xml index 0559e10..45d3fa4 100644 --- a/app/src/main/res/values/widget_colors.xml +++ b/app/src/main/res/values/widget_colors.xml @@ -1,7 +1,7 @@ - - #FF85A2 + + #C9436C #FFA726 - #E91E63 + #D64A6E diff --git a/docs/签到核心请求.md b/docs/签到核心请求.md new file mode 100644 index 0000000..9209f4d --- /dev/null +++ b/docs/签到核心请求.md @@ -0,0 +1,136 @@ +# WB(WorkBuddy)与 Trae 每日签到核心请求 + +> 本文档整理自仓库现有实现(源码为准): +> - `app/src/main/java/com/rainy/token/data/repository/TraeRepository.kt` +> - `app/src/main/java/com/rainy/token/data/repository/WorkBuddyRepository.kt` +> +> 其中 **WB = 腾讯 WorkBuddy**(非微博)。所有请求均为 `POST`,body 默认为 `{}`。 + +## 一、端点汇总 + +| 服务 | 用途 | URL | Body | +| --- | --- | --- | --- | +| Trae(CN) | 查询签到状态 | `https://api.trae.cn/trae/api/v2/ug/checkin_credits/status` | `{}` | +| Trae(CN) | 执行签到 | `https://api.trae.cn/trae/api/v2/ug/checkin_credits/claim` | `{}`(9074 时换 `{"req_source":2}` → `{"req_source":1}`) | +| WorkBuddy | 查询签到状态(优先) | `https://www.codebuddy.cn/v2/billing/meter/checkin-activity-status` | `{}` | +| WorkBuddy | 查询签到状态(兜底) | `https://www.codebuddy.cn/v2/billing/meter/checkin-status` | `{}` | +| WorkBuddy | 执行签到 | `https://www.codebuddy.cn/v2/billing/meter/daily-checkin` | `{}` | +| WorkBuddy | accessToken 刷新 | `https://copilot.tencent.com/v2/plugin/auth/token/refresh` | 头 `X-Refresh-Token` | + +## 二、Trae 核心请求源码 + +认证头:`Authorization: Cloud-IDE-JWT `,`x-device-id` 必须是账号绑定的官方客户端设备 ID。 + +```kotlin +// 官方签到接口路径 +const val CHECKIN_STATUS_PATH = "/trae/api/v2/ug/checkin_credits/status" +const val CHECKIN_CLAIM_PATH = "/trae/api/v2/ug/checkin_credits/claim" + +private fun checkinRequest( + jwt: String, + deviceId: String, + deviceSrc: String, + path: String, + bodyJson: String = "{}" +): Result { + val requestId = java.util.UUID.randomUUID().toString() + val request = Request.Builder() + .url("https://api.trae.cn" + path) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "*/*") + .addHeader("User-Agent", "VSCode 1.107.1 (TRAE SOLO CN)") + .addHeader("Authorization", "Cloud-IDE-JWT " + jwt) + .addHeader("X-User-Region", "CN") + .addHeader("Accept-Language", "zh-CN") + .addHeader("Package-Type", "stable_cn") + .addHeader("X-Lgw-Req-Sdk-Type", "3") + .addHeader("X-Market-Client-Id", "VSCode 1.107.1") + .addHeader("X-Device-Brand", "83DG") + .addHeader("X-Device-Type", "windows") + .addHeader("X-OS-Version", "Windows 11 Pro") + .addHeader("App-Version", "0.1.61") + .addHeader("X-Request-Id", requestId) + .addHeader("X-TT-Trace-Id", ttTraceId(requestId)) + .addHeader("X-Device-Id", deviceId) + .addHeader("Sec-Fetch-Dest", "empty") + .addHeader("Sec-Fetch-Mode", "no-cors") + .addHeader("Sec-Fetch-Site", "none") + .post(bodyJson.toRequestBody("application/json".toMediaType())) + .build() + // ... +} +``` + +### Trae 签到结果判定 + +- `code = 0 / 200` → **SUCCESS**(成功) +- `code = 1001 / 10001`,或文案含 `已签到 / 已经签到 / 明日再来 / 今日已完成 / 已领取 / already / checked / claimed` → **ALREADY**(今日已签) +- 文案含 `设备 / device / machine` → **DEVICE_BLOCKED**(设备级拦截) +- 文案含 `参与 / 稍后重试 / 火爆 / 繁忙 / 限流 / 排队 / too many / retry / busy / popular / later` → **RETRYABLE**(临时限流) +- 其余 → **FAILED** + +### Trae 注意事项 + +- UA 必须是 `VSCode 1.107.1 (TRAE SOLO CN)`,这是签到走的 VSCode 插件进程身份;与 IDE 主进程 / Web 混用会触发风控。 +- `X-Device-Id` 不可用固定 ClientID `en1oxy7wnw8j9n`(所有用户相同),会按设备风控拒绝。 +- 仓库内置默认设备 ID:`ACCOUNT_BOUND_DEVICE_ID = "895482260673817"`,账号级,换设备需更新。 +- INTL 区签到接口未实测,仓库实现直接不支持。 + +## 三、WorkBuddy(WB)核心请求源码 + +认证头:`Authorization: Bearer `。签到域名是 `www.codebuddy.cn`(余额主查询才是 `www.workbuddy.cn`)。 + +```kotlin +// 查询今日签到状态(两条路径依次 fallback,共享 token 轮换状态) +suspend fun fetchCheckinStatus(accountId: String? = null): Result { + val body = authedPostFirstSuccessful( + credential, + accountId, + "/v2/billing/meter/checkin-activity-status", + "/v2/billing/meter/checkin-status" + ) ?: return Result.failure(RepositoryError.Unknown()) + return runCatching { parseCheckin(body) }... +} + +// 执行签到 +suspend fun checkIn(accountId: String? = null): Result { + val body = when (val r = authedPost(credential, "/v2/billing/meter/daily-checkin")) { + is AuthedPostResult.Ok -> r.body + is AuthedPostResult.Failed -> r.body + // ... + } + // code=0 成功;code=10001 或文案含「已签/今日/already」→ 已签 +} + +// 带自动 refresh 一次的 POST 核心 +private fun authedPost(credential: Credential.WorkBuddyCredential, path: String): AuthedPostResult { + var access = credential.accessToken + var refresh = credential.refreshToken + repeat(2) { attempt -> + val request = Request.Builder() + .url("https://www.codebuddy.cn" + path) + .addHeader("Authorization", "Bearer " + access) + .addHeader("Accept", "application/json") + .addHeader("Content-Type", "application/json") + .post("{}".toRequestBody(jsonMedia)) + .build() + // 2xx → Ok + // 401/403 且第一次尝试 → tryRefesh(refresh) 换新 token 后重试 + // refresh 请求头携带 X-Refresh-Token + // 轮换出的新凭据必须先落库再用,否则旧 refreshToken 单次消费后失效 + } +} +``` + +### WorkBuddy 签到结果判定 + +- `code = 0` → **SUCCESS** +- `code = 10001`,或文案含 `已签 / 已经签到 / 今日 / already / checked / claimed` → **ALREADY**(今日已签) +- 其余 → **FAILED**(HTTP 400 body 也可能携带签到业务语义,需透传解析,不能直接当网络错误丢弃) + +## 四、通用约定 + +1. Trae 用 `Cloud-IDE-JWT`,WorkBuddy 用 `Bearer`,两者凭据类型不同,不能混用。 +2. 两者签到都幂等:今日已签时服务端返回已签语义,客户端本地也会记录 `lastCheckinDate` 短路,避免重复请求。 +3. 401/403:Trae 直接视为凭据失效;WorkBuddy 会尝试用 refreshToken 轮换一次。 +4. 不要随机生成设备 ID(Trae)或篡改客户端头,否则会被风控(Trae 9074「参与用户太多」)。