Add predictive back navigation polish
This commit is contained in:
parent
4e5dda5521
commit
9cfb962bc8
27
AGENTS.md
27
AGENTS.md
@ -2,9 +2,9 @@
|
||||
|
||||
## 项目概述
|
||||
|
||||
Android(Jetpack Compose + Kotlin)APP,统一查询 DeepSeek、OpenCode Go、CommandCode Go 三项服务的余额/配额。
|
||||
Android(Jetpack Compose + Kotlin)APP,统一查询 DeepSeek、OpenCode Go、CommandCode Go、Codex / ChatGPT Plus 的余额/配额。
|
||||
DeepSeek 走 REST API,OpenCode Go 通过 OkHttp 抓取 dashboard HTML 解析 SSR hydration 数据。
|
||||
CommandCode Go 走 JSON API 抓取用量数据。APP 名为「雨晴Token」(粉色调品牌),配套桌面小组件。
|
||||
CommandCode Go 走 JSON API 抓取用量数据,Codex / ChatGPT Plus 通过 auth.json 刷新 token 后查询 wham 用量。APP 名为「雨晴Token」(粉色调品牌),配套桌面小组件。
|
||||
|
||||
## 技术栈
|
||||
|
||||
@ -76,7 +76,7 @@ CommandCode Go 走 JSON API 抓取用量数据。APP 名为「雨晴Token」(
|
||||
> - `visualDragOffsetX/Y` —— 视觉跟手偏移,持续累计
|
||||
> - `dragStartCenterX/YInWindow` —— 拖动开始时卡片中心在窗口位置,作为偏移基准
|
||||
> - `dragFromIndex` / `dragTargetIndex` —— 真实 index 和目标 index,不触发重组
|
||||
> - `itemCenterById` —— 拖动中冻结的格子中心坐标表
|
||||
> - `itemCenterById` —— 拖动中冻结的格子中心坐标表;使用普通 `HashMap`,不要改回 Compose StateMap(滚动/返回动画期间 `onGloballyPositioned` 高频写入会触发重组卡顿)
|
||||
> - `displayOrder` —— 真实布局顺序,拖动中不改
|
||||
> - `cardOrder`(外层 SharedPreferences)—— 持久化的用户偏好顺序
|
||||
|
||||
@ -92,24 +92,13 @@ Compact(手机):
|
||||
→ CCGO: CCGO_USAGE_DETAIL(图表) → CCGO_USAGE_OVERVIEW(总统计)
|
||||
↘ CCGO_USAGE_DATA(原始数据)
|
||||
|
||||
返回用 guardedPop()(200ms 时间戳围栏,PopGuard 非 State 对象)+ popExit=fadeOut(1ms)+popEnter=None。
|
||||
围栏防同一帧/连续帧的第二次 pop;popExit=fadeOut(1ms) 确保旧 composable 正确从 layout 树移除
|
||||
(ExitTransition.None 是字面零帧,连续 pop 时退出 composable 可能未清除 → 幽灵残影卡在上层),
|
||||
popEnter=None 消除进入动画与连点 pop 的竞态(根因:默认 popEnter fadeIn(700ms) 时,
|
||||
第二次 pop 中断进入动画导致 AnimatedContent 状态不一致 → 空白页)。
|
||||
enter/exit 保留默认动画(仅影响前进导航)。
|
||||
返回用 guardedPop()(200ms 时间戳围栏,PopGuard 非 State 对象)+ Android predictive back。
|
||||
Manifest 开启 `android:enableOnBackInvokedCallback="true"`;`navigation-compose` 保持 2.9.x 以上,使用后续 predictive back 修复。
|
||||
Compact 根 `NavHost` 必须显式配置 `enterTransition` / `exitTransition` / `popEnterTransition` / `popExitTransition` 四项:
|
||||
前进统一左滑,返回统一右滑,避免只配置 pop 时短时间返回混入默认淡入淡出。
|
||||
当前页面背景/渐变层不适合 `scaleOut` 或长透明淡出类返回动画,容易出现透明背景和文字叠影;若要改动画,先处理 destination 的不透明背景层。
|
||||
PopGuard 额外检查 previousBackStackEntry != null,且 popBackStack() 返回 false 时 reset 围栏。
|
||||
|
||||
✅ 已修复:快速连点左上角返回 → 空页面 / 旧页面残影卡在上层。
|
||||
根因:① mutableStateOf 写入触发 NavHost 重组,干扰 AnimatedContent 过渡状态机;
|
||||
② popExitTransition=None 字面零帧,连续 pop 时退出 composable 未从 layout 树清除;
|
||||
③ popExitTransition=None + 默认 popEnterTransition(700ms) 不匹配,第二次 pop
|
||||
中断进入动画导致 AnimatedContent 状态不一致。
|
||||
修复:① PopGuard 用普通对象替代 mutableStateOf,不触发重组;
|
||||
② popExitTransition=fadeOut(tween(1)),极短非零帧确保 composable 正确移除;
|
||||
③ popEnterTransition=EnterTransition.None,返回导航瞬时完成;
|
||||
④ previousBackStackEntry 空值检查 + popBackStack() 返回值检查。
|
||||
|
||||
Expanded(平板,≥840dp):
|
||||
┌─ 左侧 35%: Dashboard(固定) ─┐ ┌─ 右侧 65%: when(pane) 原子切换 ─────┐
|
||||
│ │ │ ServiceDetail / OCGOUsage / CCGOUsage │
|
||||
|
||||
@ -30,7 +30,7 @@ Android AI 余额与用量查询 APP —— 统一查看 DeepSeek、OpenCode Go
|
||||
|------|------|
|
||||
| 📊 **仪表盘** | DeepSeek 余额(¥)+ 各服务用量/余额卡片;OCGO/CCGO 卡片可直达用量详情;长按拖动自由排序(持久化);下拉全局刷新 · 平板自适应双窗格布局 |
|
||||
| 📈 **用量图表** | 3 张 Canvas 手绘图表 — 消耗金额 / API 请求次数 / Token 消耗(OCGO & CCGO 双数据源);支持 UTC+0/UTC+8 时区切换和自定义日/月/范围;自动降级(近5h无数据→12h→7天→当月);平板并排展示 |
|
||||
| 📱 **平板适配** | 全局 `BoxWithConstraints` 自适应容器宽度;≥600dp 卡片双列,≥700dp 图表并排;双窗格 35/65 左右分栏(Expanded 模式) |
|
||||
| 📱 **平板适配** | 全局 `BoxWithConstraints` 自适应容器宽度;≥600dp 卡片双列,≥700dp 图表并排;双窗格 35/65 左右分栏(Expanded 模式);支持 Android 13+ 预见性返回手势 |
|
||||
| 📋 **详细数据** | 原始记录分页浏览,支持时间 + 模型筛选,点击查看完整字段 |
|
||||
| 🔍 **多粒度筛选** | 5小时 / 12小时(10分钟桶) / 24小时 / 今天 / 昨天 / 最近7天 / 当月 / 自定义日·月·范围 |
|
||||
| 🏷️ **模型筛选** | 多选 / 单选 / 全选,动态图例自适应换行 |
|
||||
@ -172,7 +172,7 @@ chmod +x ./setup_android_env.sh
|
||||
|
||||
- ✅ API Key / Session 凭据存入 **Android Keystore**(AES-256 GCM 加密)
|
||||
- ✅ 网络请求仅向 DeepSeek / OpenCode 官方 API 发出
|
||||
- ✅ `allowBackup="false"`,拒绝应用数据被备份
|
||||
- ✅ API Key / Session 由 Android Keystore 加密;应用数据备份规则由 `data_extraction_rules.xml` / `backup_rules.xml` 控制
|
||||
- ✅ 签名密钥固定,每次 Release 可覆盖安装
|
||||
- ✅ GitHub Secrets 加密存储签名密钥,CI 中解码使用
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
<application
|
||||
android:name=".RainyTokenApplication"
|
||||
android:allowBackup="true"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
package com.rainy.token.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@ -54,7 +54,7 @@ import com.rainy.token.ui.webview.WebViewLoginScreen
|
||||
*
|
||||
* 自适应策略:
|
||||
* - **Compact**(< 600dp,手机):单栈 NavHost。guardedPop 时间戳围栏(200ms)防连点栈损坏,
|
||||
* popExit=fadeOut(1ms)+popEnter=None 消除过渡动画与连点 pop 的竞态,enter/exit 保留默认动画。
|
||||
* enter/exit/popEnter/popExit 显式使用同一套滑动过渡,避免默认淡入淡出与返回动画混用。
|
||||
* - **Expanded**(≥ 840dp,平板横屏):左侧 Dashboard + 右侧 when 分支原子切换,
|
||||
* 用量子路由用局部 NavHost。
|
||||
*/
|
||||
@ -149,14 +149,32 @@ private fun CompactNavHost() {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = Routes.DASHBOARD,
|
||||
// popExit=fadeOut(1ms):极短但非零的退出动画,确保 AnimatedContent 正确执行
|
||||
// transition 结束帧来移除旧 composable。ExitTransition.None 是字面零帧,
|
||||
// 连续 pop 时上一帧的退出 composable 可能未从 layout 树中清除 → 幽灵残影。
|
||||
// popEnter=None:新 composable 瞬时出现,消除进入动画与连点 pop 的竞态
|
||||
// (根因:默认 popEnter fadeIn(700ms) 时,第二次 pop 中断进入动画导致
|
||||
// AnimatedContent 状态不一致 → 空白页)
|
||||
popExitTransition = { fadeOut(animationSpec = tween(1)) },
|
||||
popEnterTransition = { EnterTransition.None }
|
||||
enterTransition = {
|
||||
slideIntoContainer(
|
||||
AnimatedContentTransitionScope.SlideDirection.Left,
|
||||
animationSpec = tween(220)
|
||||
) + fadeIn(animationSpec = tween(90))
|
||||
},
|
||||
exitTransition = {
|
||||
slideOutOfContainer(
|
||||
AnimatedContentTransitionScope.SlideDirection.Left,
|
||||
animationSpec = tween(220)
|
||||
) + fadeOut(animationSpec = tween(90))
|
||||
},
|
||||
// Navigation Compose 会把 pop 过渡绑定到 Android 13+ predictive back 手势进度,
|
||||
// 因此这里使用同一套滑动动画,避免默认淡入淡出与返回动画混用。
|
||||
popExitTransition = {
|
||||
slideOutOfContainer(
|
||||
AnimatedContentTransitionScope.SlideDirection.Right,
|
||||
animationSpec = tween(220)
|
||||
) + fadeOut(animationSpec = tween(90))
|
||||
},
|
||||
popEnterTransition = {
|
||||
slideIntoContainer(
|
||||
AnimatedContentTransitionScope.SlideDirection.Right,
|
||||
animationSpec = tween(220)
|
||||
) + fadeIn(animationSpec = tween(90))
|
||||
}
|
||||
) {
|
||||
composable(Routes.DASHBOARD) {
|
||||
DashboardScreen(
|
||||
|
||||
@ -53,7 +53,6 @@ import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
@ -346,7 +345,7 @@ private fun DraggableDashboardCards(
|
||||
var cardHeightPx by remember { mutableIntStateOf(1) }
|
||||
var dragFromIndex by remember { mutableIntStateOf(-1) }
|
||||
var dragTargetIndex by remember { mutableIntStateOf(-1) }
|
||||
val itemCenterById = remember { mutableStateMapOf<String, androidx.compose.ui.geometry.Offset>() }
|
||||
val itemCenterById = remember { HashMap<String, androidx.compose.ui.geometry.Offset>() }
|
||||
|
||||
fun autoScrollDelta(): Float {
|
||||
if (dragCenterYInViewport < 0f || scrollState.maxValue <= 0) return 0f
|
||||
|
||||
@ -11,7 +11,7 @@ activityCompose = "1.9.3"
|
||||
composeBom = "2024.10.01"
|
||||
|
||||
# Navigation
|
||||
navigationCompose = "2.8.4"
|
||||
navigationCompose = "2.9.8"
|
||||
|
||||
# Network (Retrofit 2.11.0 配 OkHttp 4.12.0 — 稳定组合,避开 Retrofit 3.x + OkHttp 5.x 的前沿兼容性问题)
|
||||
retrofit = "2.11.0"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user