feat: 使用小技巧系统 + 小组件点击提示 + 图表默认tooltip + 添加小组件权限检测
- 新增 AppTips.kt:13条操作技巧,Dashboard随机展示一条hint,设置页TipsScreen查看全部 - 小组件左上角加 › 箭头提示可点击进APP,其他区域点切换服务 - 图表默认选中最后一根柱子,tooltip首次可见 - 添加小组件二次确认弹窗 + Manifest权限 + MIUI AppOps检测 - Dashboard首次进入显示长按拖拽排序提示横幅 - NavHost新增Tips路由 - 同步AGENTS.md和README.md
This commit is contained in:
parent
86aaef9a60
commit
814ae61f01
21
AGENTS.md
21
AGENTS.md
@ -92,6 +92,7 @@ Compact(手机):
|
|||||||
↘ UsageData(原始数据)
|
↘ UsageData(原始数据)
|
||||||
→ CCGO: CCGO_USAGE_DETAIL(图表) → CCGO_USAGE_OVERVIEW(总统计)
|
→ CCGO: CCGO_USAGE_DETAIL(图表) → CCGO_USAGE_OVERVIEW(总统计)
|
||||||
↘ CCGO_USAGE_DATA(原始数据)
|
↘ CCGO_USAGE_DATA(原始数据)
|
||||||
|
→ Settings → Tips(使用小技巧)
|
||||||
|
|
||||||
返回用 guardedPop()(200ms 时间戳围栏,PopGuard 非 State 对象)+ Android predictive back。
|
返回用 guardedPop()(200ms 时间戳围栏,PopGuard 非 State 对象)+ Android predictive back。
|
||||||
Manifest 开启 `android:enableOnBackInvokedCallback="true"`;`navigation-compose` 保持 2.9.x 以上,使用后续 predictive back 修复。
|
Manifest 开启 `android:enableOnBackInvokedCallback="true"`;`navigation-compose` 保持 2.9.x 以上,使用后续 predictive back 修复。
|
||||||
@ -103,7 +104,7 @@ PopGuard 额外检查 previousBackStackEntry != null,且 popBackStack() 返回
|
|||||||
Expanded(平板,≥840dp):
|
Expanded(平板,≥840dp):
|
||||||
┌─ 左侧 35%: Dashboard(固定) ─┐ ┌─ 右侧 65%: when(pane) 原子切换 ─────┐
|
┌─ 左侧 35%: Dashboard(固定) ─┐ ┌─ 右侧 65%: when(pane) 原子切换 ─────┐
|
||||||
│ │ │ ServiceDetail / OCGOUsage / CCGOUsage │
|
│ │ │ ServiceDetail / OCGOUsage / CCGOUsage │
|
||||||
│ │ │ Settings(内嵌 NavHost) │
|
│ │ │ Settings(内嵌 NavHost → Tips) │
|
||||||
└────────────────────────────────┘ └────────────────────────────────────────┘
|
└────────────────────────────────┘ └────────────────────────────────────────┘
|
||||||
右侧用量详情内部子路由:图表 → 总览 / 原始数据(OCGO/CCGO/Settings 各自用局部 NavHost)
|
右侧用量详情内部子路由:图表 → 总览 / 原始数据(OCGO/CCGO/Settings 各自用局部 NavHost)
|
||||||
面板切换用 when(pane) 分支(同一帧原子重组,零穿透),子路由由局部 NavHost 的 popBackStack() 内置防护。
|
面板切换用 when(pane) 分支(同一帧原子重组,零穿透),子路由由局部 NavHost 的 popBackStack() 内置防护。
|
||||||
@ -118,9 +119,25 @@ Expanded(平板,≥840dp):
|
|||||||
- 进度条颜色按百分比动态变化(<50% 草莓粉 / 50-80% 暖橙 / >80% 玫红)
|
- 进度条颜色按百分比动态变化(<50% 草莓粉 / 50-80% 暖橙 / >80% 玫红)
|
||||||
- **MIUI Widget 适配**:`miuiWidget` 标识 → 可拖入负一屏;`miui.appwidget.action.APPWIDGET_UPDATE` 曝光刷新(划到即触发,20s 冷却);`@android:id/background` 根布局 ID(系统统一裁切圆角)
|
- **MIUI Widget 适配**:`miuiWidget` 标识 → 可拖入负一屏;`miui.appwidget.action.APPWIDGET_UPDATE` 曝光刷新(划到即触发,20s 冷却);`@android:id/background` 根布局 ID(系统统一裁切圆角)
|
||||||
- **自动刷新**:`onUpdate()` 内缓存为空或超过 5 分钟冷却时自动发送 `WidgetRefreshReceiver` 广播
|
- **自动刷新**:`onUpdate()` 内缓存为空或超过 5 分钟冷却时自动发送 `WidgetRefreshReceiver` 广播
|
||||||
- **一键添桌面**:Dashboard 顶部栏 + 按钮 → `requestPinAppWidget`(有 fallback 到 `ACTION_APPWIDGET_PICK`)
|
- **一键添桌面**:Dashboard 顶部栏 + 按钮 → 二次确认弹窗 → 权限检测(Manifest `INSTALL_SHORTCUT` + MIUI AppOps `android:install_shortcut`)→ `requestPinAppWidget`(有 fallback 到 `ACTION_APPWIDGET_PICK`)
|
||||||
- 服务切换状态持久化到 SharedPreferences(`widget_auto_refresh` 中的 `display_service` key),切换后立即调用 `notifyDataChanged` 触发 `onUpdate()` 渲染
|
- 服务切换状态持久化到 SharedPreferences(`widget_auto_refresh` 中的 `display_service` key),切换后立即调用 `notifyDataChanged` 触发 `onUpdate()` 渲染
|
||||||
|
|
||||||
|
**小组件点击绑定**:
|
||||||
|
|
||||||
|
> `widget_wordmark` + `widget_open_hint`(`›` 箭头)→ 打开 APP;`widget_content` / `widget_switch` / `widget_service_title` → 切换服务广播 `ACTION_SWITCH_SERVICE`;`widget_refresh` → 刷新广播。
|
||||||
|
> 切换 requestCode=2,刷新 requestCode=1。左上角 `›` 箭头是可点击进 APP 的视觉提示。
|
||||||
|
|
||||||
|
**使用小技巧系统**:
|
||||||
|
|
||||||
|
> - `AppTips`(`ui/components/AppTips.kt`)集中管理 13 条技巧,每条含 `title` / `hint`(一句话)/ `detail`(详细说明)
|
||||||
|
> - Dashboard 卡片上方每次启动随机显示一条 `hint`(`remember { AppTips.randomHint() }`,不自动轮换)
|
||||||
|
> - 设置页 `💡 使用小技巧` 卡片 → `TipsScreen` 独立页面(Route `tips`),LazyColumn 逐条展示
|
||||||
|
> - 首次进入 Dashboard 显示一次性"长按卡片可拖拽排序"提示横幅,SharedPreferences `dashboard_ui_hints` 的 `drag_hint_shown` 标记
|
||||||
|
|
||||||
|
**图表默认 tooltip**:
|
||||||
|
|
||||||
|
> `StackedBarChart` / `LineChart` 的 `tooltipBucket` 初始值为 `buckets.lastOrNull()`,进入图表页即可看到最新时段数值详情,暗示图表可交互。
|
||||||
|
|
||||||
## RemoteViews 兼容性红线
|
## RemoteViews 兼容性红线
|
||||||
|
|
||||||
以下元素在 Widget 布局中**不可用**,会导致「载入出现问题」:
|
以下元素在 Widget 布局中**不可用**,会导致「载入出现问题」:
|
||||||
|
|||||||
@ -34,10 +34,11 @@ Android AI 余额与用量查询 APP —— 统一查看 DeepSeek、OpenCode Go
|
|||||||
| 📋 **详细数据** | 原始记录分页浏览,支持时间 + 模型筛选,点击查看完整字段 |
|
| 📋 **详细数据** | 原始记录分页浏览,支持时间 + 模型筛选,点击查看完整字段 |
|
||||||
| 🔍 **多粒度筛选** | 5小时 / 12小时(10分钟桶) / 24小时 / 今天 / 昨天 / 最近7天 / 当月 / 自定义日·月·范围 |
|
| 🔍 **多粒度筛选** | 5小时 / 12小时(10分钟桶) / 24小时 / 今天 / 昨天 / 最近7天 / 当月 / 自定义日·月·范围 |
|
||||||
| 🏷️ **模型筛选** | 多选 / 单选 / 全选,动态图例自适应换行 |
|
| 🏷️ **模型筛选** | 多选 / 单选 / 全选,动态图例自适应换行 |
|
||||||
| 📱 **桌面小组件** | 不打开 APP 也能看用量;支持四服务切换(OCGO/CCGO/Codex/Ollama)+ DeepSeek 余额;可拖入负一屏;划到即自动刷新(MIUI 曝光刷新) |
|
| 📱 **桌面小组件** | 不打开 APP 也能看用量;支持四服务切换(OCGO/CCGO/Codex/Ollama)+ DeepSeek 余额;左上角 › 进 APP、其他区域点切换、↻ 刷新;可拖入负一屏;划到即自动刷新(MIUI 曝光刷新) |
|
||||||
| 🔄 **自动同步** | 首页下拉自动同步用量;无缓存时启动自动全量同步;CCGO 详情页支持手动清除并重新同步 |
|
| 🔄 **自动同步** | 首页下拉自动同步用量;无缓存时启动自动全量同步;CCGO 详情页支持手动清除并重新同步 |
|
||||||
| 🌙 **深色模式** | 全局自适应 — App 内文字/图标/背景自动切换,小组件独立适配暗色布局 |
|
| 🌙 **深色模式** | 全局自适应 — App 内文字/图标/背景自动切换,小组件独立适配暗色布局 |
|
||||||
| ➕ **一键添桌面** | APP 内点 + 直接添加小组件,不用去系统列表翻 |
|
| ➕ **一键添桌面** | APP 内点 + 直接添加小组件,不用去系统列表翻;二次确认 + 权限检测 |
|
||||||
|
| 💡 **使用小技巧** | 首页随机展示一条操作提示(每次启动刷新);设置页可查看全部 13 条隐藏操作技巧 |
|
||||||
| ⚡ **Room 数据库** | 用量记录存 Room(indexed on workspaceId+timeCreated),DAO 查询替代全量 JSON 序列化;首次启动自动从旧 DataStore 迁移 |
|
| ⚡ **Room 数据库** | 用量记录存 Room(indexed on workspaceId+timeCreated),DAO 查询替代全量 JSON 序列化;首次启动自动从旧 DataStore 迁移 |
|
||||||
| 🎀 **雨晴粉主题** | Material Design 3 · 草莓粉 #FF85A2 · 樱粉 #FFD1DC |
|
| 🎀 **雨晴粉主题** | Material Design 3 · 草莓粉 #FF85A2 · 樱粉 #FFD1DC |
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".RainyTokenApplication"
|
android:name=".RainyTokenApplication"
|
||||||
|
|||||||
@ -35,6 +35,7 @@ import androidx.navigation.navArgument
|
|||||||
import com.rainy.token.data.repository.CommandCodeUsageRepository
|
import com.rainy.token.data.repository.CommandCodeUsageRepository
|
||||||
import com.rainy.token.domain.service.ServiceType
|
import com.rainy.token.domain.service.ServiceType
|
||||||
import com.rainy.token.ui.components.rememberWindowSizeClass
|
import com.rainy.token.ui.components.rememberWindowSizeClass
|
||||||
|
import com.rainy.token.ui.components.TipsScreen
|
||||||
import com.rainy.token.ui.dashboard.DashboardScreen
|
import com.rainy.token.ui.dashboard.DashboardScreen
|
||||||
import com.rainy.token.ui.dashboard.UsageChartViewModel
|
import com.rainy.token.ui.dashboard.UsageChartViewModel
|
||||||
import com.rainy.token.ui.dashboard.UsageDataScreen
|
import com.rainy.token.ui.dashboard.UsageDataScreen
|
||||||
@ -61,6 +62,7 @@ import com.rainy.token.ui.webview.WebViewLoginScreen
|
|||||||
object Routes {
|
object Routes {
|
||||||
const val DASHBOARD = "dashboard"
|
const val DASHBOARD = "dashboard"
|
||||||
const val SETTINGS = "settings"
|
const val SETTINGS = "settings"
|
||||||
|
const val TIPS = "tips"
|
||||||
const val CREDENTIAL_EDIT = "credential_edit/{type}"
|
const val CREDENTIAL_EDIT = "credential_edit/{type}"
|
||||||
fun credentialEdit(type: ServiceType) = "credential_edit/${type.name}"
|
fun credentialEdit(type: ServiceType) = "credential_edit/${type.name}"
|
||||||
const val WEBVIEW_LOGIN = "webview_login/{type}"
|
const val WEBVIEW_LOGIN = "webview_login/{type}"
|
||||||
@ -240,9 +242,13 @@ private fun CompactNavHost() {
|
|||||||
composable(Routes.SETTINGS) {
|
composable(Routes.SETTINGS) {
|
||||||
SettingsScreen(
|
SettingsScreen(
|
||||||
onBack = guardedPop,
|
onBack = guardedPop,
|
||||||
onEditCredential = { type -> navController.navigate(Routes.credentialEdit(type)) }
|
onEditCredential = { type -> navController.navigate(Routes.credentialEdit(type)) },
|
||||||
|
onOpenTips = { navController.navigate(Routes.TIPS) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
composable(Routes.TIPS) {
|
||||||
|
TipsScreen(onBack = guardedPop)
|
||||||
|
}
|
||||||
composable(
|
composable(
|
||||||
route = Routes.CREDENTIAL_EDIT,
|
route = Routes.CREDENTIAL_EDIT,
|
||||||
arguments = listOf(navArgument("type") { type = NavType.StringType })
|
arguments = listOf(navArgument("type") { type = NavType.StringType })
|
||||||
@ -415,9 +421,13 @@ private fun ExpandedDetailPane(
|
|||||||
onBack = onClose,
|
onBack = onClose,
|
||||||
onEditCredential = { type ->
|
onEditCredential = { type ->
|
||||||
settingsNavController.navigate(Routes.credentialEdit(type))
|
settingsNavController.navigate(Routes.credentialEdit(type))
|
||||||
}
|
},
|
||||||
|
onOpenTips = { settingsNavController.navigate("tips") }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
composable("tips") {
|
||||||
|
TipsScreen(onBack = { settingsNavController.popBackStack() })
|
||||||
|
}
|
||||||
composable(
|
composable(
|
||||||
route = Routes.CREDENTIAL_EDIT,
|
route = Routes.CREDENTIAL_EDIT,
|
||||||
arguments = listOf(navArgument("type") { type = NavType.StringType })
|
arguments = listOf(navArgument("type") { type = NavType.StringType })
|
||||||
|
|||||||
145
app/src/main/java/com/rainy/token/ui/components/AppTips.kt
Normal file
145
app/src/main/java/com/rainy/token/ui/components/AppTips.kt
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
package com.rainy.token.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.rainy.token.ui.theme.InkMuted
|
||||||
|
import com.rainy.token.ui.theme.StrawberryPink
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APP 使用小技巧列表。
|
||||||
|
* Dashboard 卡片上方随机展示一条,设置页可查看全部。
|
||||||
|
*/
|
||||||
|
object AppTips {
|
||||||
|
|
||||||
|
data class Tip(
|
||||||
|
val title: String,
|
||||||
|
val hint: String,
|
||||||
|
val detail: String
|
||||||
|
)
|
||||||
|
|
||||||
|
val tips: List<Tip> = listOf(
|
||||||
|
Tip("长按卡片拖拽排序", "长按首页卡片可以拖拽排序", "在首页长按任意服务卡片即可拖拽到想要的位置,排序会自动保存。"),
|
||||||
|
Tip("小组件点左上角进 APP", "小组件左上角标识可打开 APP", "桌面小组件左上角 RainyToken › 标识可以点击打开应用,其它区域点击切换服务。"),
|
||||||
|
Tip("图表点击查看详情", "点击图表柱子可查看时段详情", "在用量图表上点击或滑动手指,可查看对应时段的详细数值。"),
|
||||||
|
Tip("图表标题可点击", "点击图表标题弹出按模型明细", "图表卡片标题(如「消耗金额」)点击后会弹出按模型拆分的明细。"),
|
||||||
|
Tip("切换图表时区", "用量详情页可切换 UTC+0 / UTC+8", "用量详情页顶部可切换 UTC+0 / UTC+8,图表标签会跟随切换。"),
|
||||||
|
Tip("CCGO 清除并重新同步", "CCGO 详情页可清除缓存重新同步", "CCGO 用量详情页右上角「清除」可删除本地缓存并重新拉取全部数据。"),
|
||||||
|
Tip("查看用量详情", "服务卡片底部可进入用量图表", "有凭据的服务卡片底部会出现「查看用量详情」入口,点击进入图表统计。"),
|
||||||
|
Tip("原始数据浏览", "用量详情页右上角可查看原始记录", "用量详情页右上角「详细数据」可查看每条原始调用记录,支持分页跳转。"),
|
||||||
|
Tip("下拉刷新联动用量", "下拉刷新会同步余额和用量", "首页下拉刷新不仅刷新余额,还会自动同步 OCGO / CCGO 用量记录。"),
|
||||||
|
Tip("凭据获取教程", "凭据页底部有获取教程", "凭据编辑页底部的「如何获取」按钮有针对每个服务的详细操作步骤。"),
|
||||||
|
Tip("剪贴板自动导入", "Codex 支持从剪贴板一键导入", "Codex 凭据页支持「从剪贴板自动导入」,粘贴 auth.json 后一键填入。"),
|
||||||
|
Tip("详细数据页码跳转", "原始数据页可输入页码直跳", "原始数据页底部可输入页码直接跳转,数据量大时不必逐页翻。"),
|
||||||
|
Tip("小组件自动刷新", "划到小组件会自动刷新余额", "桌面小组件划到负一屏时会自动刷新余额,也可手动点 ↻ 按钮刷新。"),
|
||||||
|
)
|
||||||
|
|
||||||
|
/** 首页轮换用的一句话文案。 */
|
||||||
|
val shortHints: List<String> = tips.map { it.hint }
|
||||||
|
|
||||||
|
/** 随机取一条短文案。 */
|
||||||
|
fun randomHint(): String = shortHints.random()
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun TipsScreen(onBack: () -> Unit) {
|
||||||
|
Scaffold(
|
||||||
|
containerColor = Color.Transparent,
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
"使用小技巧",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = onBack) {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.ArrowBack,
|
||||||
|
contentDescription = "返回",
|
||||||
|
tint = StrawberryPink
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = Color.Transparent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { innerPadding ->
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(innerPadding)
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||||
|
) {
|
||||||
|
itemsIndexed(AppTips.tips) { idx, tip ->
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface
|
||||||
|
),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text(
|
||||||
|
text = "${idx + 1}",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = StrawberryPink
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
text = tip.title,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
|
Text(
|
||||||
|
text = tip.detail,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = InkMuted
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -32,6 +32,7 @@ import androidx.compose.material.icons.filled.Add
|
|||||||
import androidx.compose.material.icons.filled.KeyboardArrowRight
|
import androidx.compose.material.icons.filled.KeyboardArrowRight
|
||||||
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.AlertDialog
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
@ -81,11 +82,16 @@ 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.components.AppTips
|
||||||
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.app.AppOpsManager
|
||||||
import android.appwidget.AppWidgetManager
|
import android.appwidget.AppWidgetManager
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Process
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.rainy.token.ui.widget.OpenCodeGoWidgetProvider
|
import com.rainy.token.ui.widget.OpenCodeGoWidgetProvider
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
@ -114,6 +120,15 @@ fun DashboardScreen(
|
|||||||
) {
|
) {
|
||||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
var showAddWidgetConfirm by remember { mutableStateOf(false) }
|
||||||
|
// 一次性"长按拖拽排序"提示
|
||||||
|
var showDragHint by remember { mutableStateOf(false) }
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
val prefs = context.getSharedPreferences("dashboard_ui_hints", android.content.Context.MODE_PRIVATE)
|
||||||
|
if (!prefs.getBoolean("drag_hint_shown", false)) {
|
||||||
|
showDragHint = true
|
||||||
|
}
|
||||||
|
}
|
||||||
val cardOrder = remember { mutableStateListOf<String>() }
|
val cardOrder = remember { mutableStateListOf<String>() }
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
val saved = context.getSharedPreferences(DASHBOARD_ORDER_PREFS, android.content.Context.MODE_PRIVATE)
|
val saved = context.getSharedPreferences(DASHBOARD_ORDER_PREFS, android.content.Context.MODE_PRIVATE)
|
||||||
@ -136,6 +151,29 @@ fun DashboardScreen(
|
|||||||
lastRefreshing = uiState.refreshing
|
lastRefreshing = uiState.refreshing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showAddWidgetConfirm) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showAddWidgetConfirm = false },
|
||||||
|
title = { Text("添加桌面小组件?") },
|
||||||
|
text = { Text("确认后会请求桌面启动器添加雨晴Token小组件。部分系统仍会弹出系统确认窗口。") },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
showAddWidgetConfirm = false
|
||||||
|
requestRainyTokenWidgetPin(context)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("确认添加", color = StrawberryPink)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { showAddWidgetConfirm = false }) {
|
||||||
|
Text("取消")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
containerColor = Color.Transparent,
|
containerColor = Color.Transparent,
|
||||||
topBar = {
|
topBar = {
|
||||||
@ -155,24 +193,7 @@ fun DashboardScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
// 添加小组件:优先 requestPinAppWidget,不支持则打开系统小组件选择器
|
IconButton(onClick = { showAddWidgetConfirm = true }) {
|
||||||
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(
|
Icon(
|
||||||
imageVector = Icons.Filled.Add,
|
imageVector = Icons.Filled.Add,
|
||||||
contentDescription = "添加小组件到桌面",
|
contentDescription = "添加小组件到桌面",
|
||||||
@ -234,6 +255,14 @@ fun DashboardScreen(
|
|||||||
.padding(contentPadding),
|
.padding(contentPadding),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
) {
|
) {
|
||||||
|
// 随机小技巧提示(每次启动随机一条)
|
||||||
|
val tipText = remember { AppTips.randomHint() }
|
||||||
|
Text(
|
||||||
|
text = "💡 $tipText",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = inkMuted(),
|
||||||
|
modifier = Modifier.padding(horizontal = 4.dp)
|
||||||
|
)
|
||||||
val items = rememberDashboardItems(
|
val items = rememberDashboardItems(
|
||||||
cards = uiState.cards,
|
cards = uiState.cards,
|
||||||
order = cardOrder,
|
order = cardOrder,
|
||||||
@ -257,6 +286,41 @@ fun DashboardScreen(
|
|||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
// 一次性"长按拖拽排序"提示横幅
|
||||||
|
if (showDragHint) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
|
showDragHint = false
|
||||||
|
context.getSharedPreferences("dashboard_ui_hints", android.content.Context.MODE_PRIVATE)
|
||||||
|
.edit().putBoolean("drag_hint_shown", true).apply()
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = StrawberryPink.copy(alpha = 0.12f)
|
||||||
|
),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "💡 长按卡片可拖拽排序",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = StrawberryPink,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "知道了",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = StrawberryPink,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// 底部 footer:填空白 + 提供版本号
|
// 底部 footer:填空白 + 提供版本号
|
||||||
DashboardFooter()
|
DashboardFooter()
|
||||||
}
|
}
|
||||||
@ -635,3 +699,45 @@ private fun DashboardFooter() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun canInstallLauncherShortcut(context: Context): Boolean {
|
||||||
|
val manifestPermissionGranted = context.packageManager.checkPermission(
|
||||||
|
"com.android.launcher.permission.INSTALL_SHORTCUT",
|
||||||
|
context.packageName
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
if (!manifestPermissionGranted) return false
|
||||||
|
|
||||||
|
val appOpsManager = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
|
||||||
|
val mode = runCatching {
|
||||||
|
appOpsManager.checkOpNoThrow("android:install_shortcut", Process.myUid(), context.packageName)
|
||||||
|
}.getOrNull() ?: return true
|
||||||
|
|
||||||
|
return mode == AppOpsManager.MODE_ALLOWED || mode == AppOpsManager.MODE_DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestRainyTokenWidgetPin(context: Context) {
|
||||||
|
val appWidgetManager = AppWidgetManager.getInstance(context)
|
||||||
|
val component = ComponentName(context, OpenCodeGoWidgetProvider::class.java)
|
||||||
|
if (!canInstallLauncherShortcut(context)) {
|
||||||
|
Toast.makeText(context, "桌面快捷方式权限已被拒绝,请在系统设置中允许后重试", Toast.LENGTH_LONG).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appWidgetManager.isRequestPinAppWidgetSupported) {
|
||||||
|
val requested = appWidgetManager.requestPinAppWidget(component, null, null)
|
||||||
|
if (!requested) {
|
||||||
|
Toast.makeText(context, "桌面未接受添加请求,请检查启动器权限", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val pickerIntent = Intent(AppWidgetManager.ACTION_APPWIDGET_PICK).apply {
|
||||||
|
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
|
||||||
|
}
|
||||||
|
val canOpenPicker = pickerIntent.resolveActivity(context.packageManager) != null
|
||||||
|
if (canOpenPicker) {
|
||||||
|
context.startActivity(pickerIntent)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, "当前桌面不支持应用内添加,请在桌面长按 → 小组件 → 查找雨晴Token", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -547,7 +547,7 @@ internal fun StackedBarChart(
|
|||||||
Text("暂无数据", color = InkMuted, style = MaterialTheme.typography.bodySmall)
|
Text("暂无数据", color = InkMuted, style = MaterialTheme.typography.bodySmall)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var tooltipBucket by remember { mutableStateOf<ChartBucket?>(null) }
|
var tooltipBucket by remember { mutableStateOf(buckets.lastOrNull()) }
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
val d = density.density
|
val d = density.density
|
||||||
val barCount = buckets.size
|
val barCount = buckets.size
|
||||||
@ -677,7 +677,7 @@ internal fun LineChart(
|
|||||||
Text("暂无数据", color = InkMuted, style = MaterialTheme.typography.bodySmall)
|
Text("暂无数据", color = InkMuted, style = MaterialTheme.typography.bodySmall)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var tooltipBucket by remember { mutableStateOf<ChartBucket?>(null) }
|
var tooltipBucket by remember { mutableStateOf(buckets.lastOrNull()) }
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
val d = density.density
|
val d = density.density
|
||||||
val barCount = buckets.size
|
val barCount = buckets.size
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Row
|
|||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
@ -42,6 +43,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.components.AppTips
|
||||||
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
|
||||||
|
|
||||||
@ -58,6 +60,7 @@ import com.rainy.token.ui.theme.StrawberryPink
|
|||||||
fun SettingsScreen(
|
fun SettingsScreen(
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onEditCredential: (ServiceType) -> Unit,
|
onEditCredential: (ServiceType) -> Unit,
|
||||||
|
onOpenTips: () -> Unit = {},
|
||||||
viewModel: SettingsViewModel = hiltViewModel()
|
viewModel: SettingsViewModel = hiltViewModel()
|
||||||
) {
|
) {
|
||||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
@ -120,6 +123,9 @@ fun SettingsScreen(
|
|||||||
onClick = { onEditCredential(status.service) }
|
onClick = { onEditCredential(status.service) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
item {
|
||||||
|
TipsCard(onClick = { onOpenTips() })
|
||||||
|
}
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.padding(top = 8.dp))
|
Spacer(modifier = Modifier.padding(top = 8.dp))
|
||||||
AboutCard()
|
AboutCard()
|
||||||
@ -200,3 +206,40 @@ private fun stateToChip(state: CredentialStatus.State): StatusStyle = when (stat
|
|||||||
CredentialStatus.State.EXPIRED -> StatusStyle("已过期", StatusLevel.ERROR)
|
CredentialStatus.State.EXPIRED -> StatusStyle("已过期", StatusLevel.ERROR)
|
||||||
CredentialStatus.State.WARNING -> StatusStyle("需重登", StatusLevel.WARNING)
|
CredentialStatus.State.WARNING -> StatusStyle("需重登", StatusLevel.WARNING)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TipsCard(onClick: () -> Unit) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { onClick() },
|
||||||
|
shape = RoundedCornerShape(20.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = StrawberryPink.copy(alpha = 0.08f)),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "💡",
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = "使用小技巧",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = StrawberryPink
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "查看全部 ${AppTips.tips.size} 条隐藏操作",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = InkMuted,
|
||||||
|
modifier = Modifier.padding(top = 2.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -60,15 +60,24 @@ class OpenCodeGoWidgetProvider : AppWidgetProvider() {
|
|||||||
for (widgetId in appWidgetIds) {
|
for (widgetId in appWidgetIds) {
|
||||||
val views = RemoteViews(context.packageName, R.layout.widget_opencode_go)
|
val views = RemoteViews(context.packageName, R.layout.widget_opencode_go)
|
||||||
|
|
||||||
// 点击 Widget → 打开 APP
|
// 点击左上角品牌 → 打开 APP;点击其它内容区域 → 切换服务;刷新按钮单独刷新。
|
||||||
val intent = Intent(context, MainActivity::class.java).apply {
|
val openAppIntent = Intent(context, MainActivity::class.java).apply {
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||||
}
|
}
|
||||||
val pendingIntent = PendingIntent.getActivity(
|
val openAppPendingIntent = PendingIntent.getActivity(
|
||||||
context, 0, intent,
|
context, 0, openAppIntent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
)
|
)
|
||||||
views.setOnClickPendingIntent(R.id.widget_content, pendingIntent)
|
views.setOnClickPendingIntent(R.id.widget_wordmark, openAppPendingIntent)
|
||||||
|
views.setOnClickPendingIntent(R.id.widget_open_hint, openAppPendingIntent)
|
||||||
|
|
||||||
|
val switchPendingIntent = PendingIntent.getBroadcast(
|
||||||
|
context, 2, Intent(context, OpenCodeGoWidgetProvider::class.java).apply { action = ACTION_SWITCH_SERVICE },
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
views.setOnClickPendingIntent(R.id.widget_content, switchPendingIntent)
|
||||||
|
views.setOnClickPendingIntent(R.id.widget_switch, switchPendingIntent)
|
||||||
|
views.setOnClickPendingIntent(R.id.widget_service_title, switchPendingIntent)
|
||||||
|
|
||||||
// 刷新按钮 → 后台广播刷新
|
// 刷新按钮 → 后台广播刷新
|
||||||
val refreshPendingIntent = PendingIntent.getBroadcast(
|
val refreshPendingIntent = PendingIntent.getBroadcast(
|
||||||
@ -77,12 +86,6 @@ class OpenCodeGoWidgetProvider : AppWidgetProvider() {
|
|||||||
)
|
)
|
||||||
views.setOnClickPendingIntent(R.id.widget_refresh, refreshPendingIntent)
|
views.setOnClickPendingIntent(R.id.widget_refresh, refreshPendingIntent)
|
||||||
|
|
||||||
val switchPendingIntent = PendingIntent.getBroadcast(
|
|
||||||
context, 2, Intent(context, OpenCodeGoWidgetProvider::class.java).apply { action = ACTION_SWITCH_SERVICE },
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
||||||
)
|
|
||||||
views.setOnClickPendingIntent(R.id.widget_switch, switchPendingIntent)
|
|
||||||
|
|
||||||
// 读缓存并填充数据
|
// 读缓存并填充数据
|
||||||
runBlocking {
|
runBlocking {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -30,6 +30,26 @@
|
|||||||
android:contentDescription="RainyToken"
|
android:contentDescription="RainyToken"
|
||||||
android:adjustViewBounds="true" />
|
android:adjustViewBounds="true" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_open_hint"
|
||||||
|
android:layout_width="10dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:text="›"
|
||||||
|
android:textColor="#8A7A82"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:gravity="center" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_switch"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:text="OCGO"
|
||||||
|
android:textColor="#FF85A2"
|
||||||
|
android:textSize="10sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginStart="6dp"
|
||||||
|
android:gravity="center" />
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
style="@android:style/Widget.ProgressBar.Horizontal"
|
style="@android:style/Widget.ProgressBar.Horizontal"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@ -50,12 +70,13 @@
|
|||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/widget_refresh"
|
android:id="@+id/widget_refresh"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="40dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="24dp"
|
||||||
android:text="↻"
|
android:text="↻"
|
||||||
android:textColor="#FF85A2"
|
android:textColor="#FF85A2"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:layout_marginStart="6dp" />
|
android:layout_marginStart="6dp"
|
||||||
|
android:gravity="center" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- OpenCode Go 标识 -->
|
<!-- OpenCode Go 标识 -->
|
||||||
@ -72,6 +93,7 @@
|
|||||||
android:contentDescription="OpenCode Go"
|
android:contentDescription="OpenCode Go"
|
||||||
android:layout_marginEnd="5dp" />
|
android:layout_marginEnd="5dp" />
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/widget_service_title"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="OpenCode Go · 用量"
|
android:text="OpenCode Go · 用量"
|
||||||
|
|||||||
@ -31,17 +31,25 @@
|
|||||||
android:contentDescription="RainyToken"
|
android:contentDescription="RainyToken"
|
||||||
android:adjustViewBounds="true" />
|
android:adjustViewBounds="true" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/widget_open_hint"
|
||||||
|
android:layout_width="10dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:text="›"
|
||||||
|
android:textColor="#C4A0AB"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:gravity="center" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/widget_switch"
|
android:id="@+id/widget_switch"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="48dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="24dp"
|
||||||
android:text="OCGO"
|
android:text="OCGO"
|
||||||
android:textColor="#FF85A2"
|
android:textColor="#FF85A2"
|
||||||
android:textSize="10sp"
|
android:textSize="10sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:layout_marginStart="6dp"
|
android:layout_marginStart="6dp"
|
||||||
android:paddingStart="4dp"
|
android:gravity="center" />
|
||||||
android:paddingEnd="4dp" />
|
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
style="@android:style/Widget.ProgressBar.Horizontal"
|
style="@android:style/Widget.ProgressBar.Horizontal"
|
||||||
@ -64,12 +72,13 @@
|
|||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/widget_refresh"
|
android:id="@+id/widget_refresh"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="40dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="24dp"
|
||||||
android:text="↻"
|
android:text="↻"
|
||||||
android:textColor="#FF85A2"
|
android:textColor="#FF85A2"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:layout_marginStart="6dp" />
|
android:layout_marginStart="6dp"
|
||||||
|
android:gravity="center" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user