Imported from JeziL/carvita (
AGENTS.md). Install upstream withnpx skills add JeziL/carvita. Copyright stays with the author.
CarVita Agent 开发指南
适用范围与基本原则
本文件适用于整个仓库。CarVita 是一个以 Android 为当前发布目标、离线优先的 Flutter 车辆保养管理应用。修改前先阅读受影响模块及其调用方,不要把本项目当成通用 Flutter 脚手架。
- 以现有代码、
pubspec.yaml、l10n.yaml和 Android Gradle 配置为事实来源;README 主要描述产品能力。 - 保持改动聚焦,保留工作区中与当前任务无关的用户修改。
- 不提交密钥、签名文件、Flutter/Gradle 缓存、构建产物或本地生成文件。
- 新增面向用户的行为时,要同时考虑本地数据、预测结果、通知、国际化和备份兼容性。
- 项目采用 GNU AGPLv3;引入代码或资源前确认许可证兼容。
项目概览
CarVita 用于:
- 管理多辆车辆及其名称、当前里程、购入日期、照片和识别信息;
- 为每辆车维护按月数和/或里程计算的保养计划;
- 记录保养日期、里程、项目、费用和备注;
- 根据购入日期、历史保养记录和估算的日均里程预测下次保养;
- 在本地展示到期项目并调度 Android 本地通知;
- 将 SQLite 数据库导出为备份,或从数据库文件恢复。
应用数据不依赖远端后端。隐私页面也向用户承诺数据保存在本机,因此不要在没有明确产品决策的情况下引入遥测、远程同步或网络上传。
技术栈与工具链
- CI 固定使用 Flutter
3.47.0;当前对应 Dart3.13.0。 pubspec.yaml的最低工具链约束为 Dart>=3.13.0 <4.0.0、Flutter>=3.47.0,与 CI 固定版本一致。- 状态管理同时使用:
flutter_bloc/ Cubit:车辆、保养计划、保养记录、全局到期预测;provider/ChangeNotifier:语言、里程单位和主题;- 普通
Provider:全局QuickActionService。
- 持久化:
sqflite保存业务数据;shared_preferences保存用户偏好。
- Android 构建使用 Kotlin DSL、Java 17、AGP 9.1.0、built-in Kotlin 2.4.0 与 Gradle 9.3.1。应用模块通过
flutter.compileSdkVersion、flutter.targetSdkVersion和flutter.minSdkVersion跟随 Flutter SDK 的 Android API 基线(Flutter 3.47 当前为 compile/target SDK 36、min SDK 24)。应用模块不再应用kotlin-android,并启用android.builtInKotlin=true。顶层org.jetbrains.kotlin.android 2.4.0 apply false仅供 Flutter 3.47 校验 KGP 版本,不会把 KGP 应用到模块。Flutter 3.47 的 Gradle 插件仍依赖旧 AGP DSL 类型,因此暂时保留android.newDsl=false,待 Flutter 上游完成新 DSL 迁移后再启用。 - AGP 9 下所有含 Kotlin 源码的 Android 插件也必须支持 built-in Kotlin。当前兼容基线为
package_info_plus 10.2.1、share_plus 13.3.0、shared_preferences 2.5.5/shared_preferences_android 2.4.27;升级或降级平台插件后必须重新构建 debug APK,禁止通过关闭 built-in Kotlin 掩盖插件不兼容。 - 本地通知使用
flutter_local_notifications 22.3.0与timezone 0.11.1;通知插件 20.0 起initialize、zonedSchedule等公共方法统一使用命名参数,禁止恢复旧式位置参数调用。timezone 0.11.1将默认 UTC 位置的规范名称改为Etc/UTC,无效设备时区的安全回退应保留该名称和零偏移语义。当前通知插件要求的 Android min SDK 24、compile SDK 36 与 Flutter 3.47 基线一致;调整通知或时区依赖后必须覆盖权限、当地中午调度、时区切换、重启恢复和通知点击路径,并重新构建 debug APK。 - Flutter 3.47 的 UI 已迁移到独立
material_ui 1.0.0包;除隔离旧依赖的lib/core/widgets/legacy_material_bridge.dart外,生产代码和测试不得重新导入package:flutter/material.dart或package:flutter/cupertino.dart,手写代码也不得导入package:flutter_localizations/flutter_localizations.dart。flutter_localizations仅作为flutter gen-l10n生成代码的直接 SDK 依赖保留;Material/Cupertino/Widgets 本地化统一通过GlobalMaterialLocalizations.delegates注入。依赖仍使用旧 Material API 的第三方 Widget 必须用最小范围的LegacyMaterialBridge包裹;当前仅flutter_colorpicker 1.1.0需要该桥接,依赖迁移后应及时移除适配器和例外。 - 当前受支持并纳入版本控制的平台只有 Android;
ios/、web/、linux/、macos/、windows/均被忽略。 - Android application ID 和 namespace 均为
com.wangjinli.carvita。
不要随意升级 Flutter、Dart、Gradle、AGP、Kotlin 或核心插件。工具链升级应独立成改动,并至少完成静态分析和 Android APK 构建。
目录地图
lib/
main.dart 应用启动、全局依赖和 MaterialApp
application/
ports/ repository、Clock 与设备能力接口
queries/ 预测等跨仓储只读快照
use_cases/ 车辆、计划、记录、预测和提醒编排
core/
constants/ 路由名和固定颜色
failures/ typed failure 与内部诊断边界
services/ 偏好、预测及通知/快捷操作/设备适配器
theme/ ColorScheme 与 ThemeExtension
utils/ 日均里程估算
widgets/ 跨页面基础组件
data/
models/ 车辆、计划、记录、预测等值对象
repositories/ application repository port 的 SQLite 实现
sources/local/database_helper.dart
SQLite 建表、查询和事务
presentation/
failures/ failure 到本地化用户文案的映射
formatters/ 模型/偏好的本地化展示格式
images/ 车辆图片按需加载、尺寸解码与有界缓存
manager/ Cubit、State、语言和主题 Provider
navigation/ typed 路由、持久 MainShell 和快捷导航适配器
screens/ 页面、详情页 Tab 和局部组件;Settings 备份区独立拆分
i18n/
app_*.arb 12 个受版本控制的翻译源文件
generated/ flutter gen-l10n 生成,禁止提交
android/ 唯一受支持的平台工程
test/ 单元、数据库、Cubit、Widget 与策略回归测试
tool/ 发布元数据校验脚本
fastlane/metadata/android/ 商店文案、截图和版本更新说明
.design/ README 翻译及商店/宣传设计资源
.github/workflows/flutter.yml PR/main 质量门禁与 tag 驱动的签名发布流程
assets/icon/ 主图标及其 Cairo 生成脚本
启动、依赖注入与导航
lib/main.dart 的启动顺序是:
- 初始化 Flutter binding;
- 创建 Clock、偏好服务、数据库、repository 实现、application use case 与平台适配器;
- 注入全局 Provider/Cubit 并立即启动
MaterialApp; - 首帧后通过
AppStartupPort单飞初始化设备时区、本地通知、系统 locale 与 Android app shortcut 监听; - 通知初始化后协调应用内提醒开关与系统权限;
- Navigator 和本地化就绪后更新快捷入口,并首次加载预测和同步提醒。
平台初始化必须逐项隔离失败;可恢复的通知、locale 或快捷入口插件异常不得阻止基础 UI 启动,也不得跳过后续预测加载。通知权限仍只在用户启用提醒时请求。通知初始化后及每次 App resume 都要协调应用内开关与系统权限:偏好为开启但系统权限缺失时,持久化关闭开关并取消提醒,不自动请求权限。
全局对象:
- repository、Clock 和插件实现只在
main.dart组合;Screen/Cubit 不直接创建或依赖具体 repository; LocaleProvider、ThemeProvider、application use case、AppStartupPort、其他平台端口和QuickActionService由MultiProvider提供;VehicleCubit和UpcomingMaintenanceCubit由MultiBlocProvider提供;NavigationService.navigatorKey供 app shortcut 从应用外入口发起导航;MainNavigationController协调持久化根 tab、快捷入口与通知导航;routeObserver继续处理普通路由返回;Dashboard 还监听根 tab 切换以重读筛选偏好。
四个主页面由 MainShell 以 lazy IndexedStack 持有。底部导航只选择 tab,不再创建或清空根路由;已创建 tab 的滚动、筛选和局部状态必须保留。Android 返回键在非 Dashboard 根 tab 上先回到 Dashboard。快捷入口或通知要展示根 tab 时,应先回退到首个 shell route,再由 MainNavigationController 选择目标 tab,避免堆叠多个 MainShell。
每辆车详情页会使用全局注入的 MaintenancePlanUseCases 和 ServiceLogUseCases 创建该车辆专属的 Cubit。快捷入口直接打开记录页时也遵循同一装配方式。向编辑页传递已有 Cubit 时必须使用 BlocProvider.value,不要让子路由错误地关闭父页面拥有的 Cubit。
分层边界:
- Presentation 只能依赖 application use case/port,不 import
data/repositories,也不直接调用文件选择、分享、图片、应用信息、外链或退出插件; - application 不依赖 Flutter、生成的本地化类或 presentation;
- data model 不依赖 Flutter UI 或本地化;展示字符串放在
presentation/formatters; - core service 不 import Screen/Cubit;快捷入口导航由 presentation adapter 实现;
- 新增跨层能力时先定义可替换 port,再在
main.dart注入默认实现,并同步扩展test/architecture/dependency_rules_test.dart。
项目使用 MaterialApp.onGenerateRoute 和 Navigator 命名路由,不使用 go_router。路由名集中在 lib/core/constants/app_routes.dart,参数解析集中在 lib/presentation/navigation/app_router.dart。新增或修改路由时:
- 同步更新路由常量、路由解析和所有调用点;
- 使用
app_route_arguments.dart中的 typed arguments,不新增动态 Map 参数协议; - 对必需参数做显式校验并返回可理解的错误页;
- 所有
MaterialPageRoute保留传入的RouteSettings; - 根 tab 切换通过
MainNavigationController;只有显式恢复到全新根栈的错误恢复流程才能清空 Navigator。
数据模型与 SQLite 约束
生产数据库使用单例 DatabaseHelper,为兼容既有安装继续使用固定文件名 carvita_v1.db,当前 schema 版本为 2,共四张表;数据库测试可用指定路径的隔离 helper,避免并行测试争用同一文件:
vehicles:车辆主表;图片以 BLOB 保存,日期以 ISO-8601 字符串保存;maintenance_plan_items:车辆保养计划;baselineDate与baselineMileage静默保存首次预测起点;service_log_entries:保养记录主体;service_log_performed_items:记录与计划项目的关联,也可保存一次性的自定义项目名称。
关键语义:
Vehicle.id、计划 ID 和记录 ID 由 SQLite 自增生成,插入前会移除 Map 中的id。- 删除计划项目是软删除:
isActive设为 0。这样历史保养记录仍可保留关联;不要把它轻率改为硬删除。 - 保养记录及其项目关联必须在同一事务中新增或更新。
- 一个已执行项目要么引用
maintenancePlanItemId,要么使用customItemName。 - 仓储目前是数据库方法的薄封装;业务计算应放在 service/Cubit,而不是塞进 Widget。
- 车辆列表查询是 summary projection,故意不读取
vehicles.image;Vehicle.imageLoaded == false表示图片尚未加载,而不是没有图片。更新 summary 车辆时必须保留数据库中的原 BLOB;图片通过getVehicleImage按需读取,并使用默认最多 24 项/16 MiB 的VehicleImageCache与尺寸感知解码。 - 全局预测通过
PredictionRepositoryPort.getPredictionSnapshot()在同一个只读事务中取得车辆摘要、active plan、日志和关联,共固定 4 次查询。不得退回逐车或逐日志读取;记录与 performed items 的页面查询也应保持单次 JOIN/分组。
修改数据库时必须:
- 增加数据库版本;
- 实现并测试
onUpgrade,兼容已有用户数据; - 保持导入旧备份的行为可解释;
- 检查所有
toMap、fromMap、查询、事务和导出/恢复路径; - 覆盖全新安装、旧库升级和恢复备份三种场景。
schema 与迁移集中在 DatabaseSchema。v1→v2 先生成只读一致性报告;无法安全推断的日期、数值或非官方 FK 定义会中止并回滚。可修复的关系异常按以下规则治理:丢失日志的 performed item 删除;跨车辆、孤儿计划或同时带 custom/plan 身份的关联尽量保留显示名并转为 custom;无任何可恢复身份的关联删除;丢失车辆的日志和计划删除。迁移后必须通过 PRAGMA foreign_key_check。
所有应用数据库连接以及备份校验连接都在 onConfigure 中启用并验证 PRAGMA foreign_keys = ON。车辆和日志删除依赖已测试的级联;计划仍默认软删除,若内部硬删除则先把历史关联转成 custom 以保留名称。v2 包含以下最小二级索引:
- active plan:
(isActive, vehicleId, id); - service log:
(vehicleId, serviceDate DESC, id DESC); - performed item:
(serviceLogId, id)与(maintenancePlanItemId, serviceLogId)。
设置页的新导出格式是版本化 .cvbackup ZIP 容器,固定包含 manifest.json、database/carvita_v1.db 和 preferences.json。manifest 记录容器版本、数据库 schema version、应用版本、UTC 创建时间、内容大小和 SHA-256;偏好只允许 BackupPreferencesPort 白名单中的 typed 值。新导出包含 schema v2;恢复兼容官方 schema v1/v2 裸库和容器,v1 在原子替换后通过同一迁移路径升级到 v2。导出前仍需生成并校验一致的 SQLite 快照。备份中的 notifications_enabled 只表示用户意愿,不表示系统权限;恢复后必须按上一节的权限协调规则处理。
恢复必须先区分新容器与旧裸 SQLite .db:旧 .db 仍只恢复业务数据库且不得修改偏好;新容器必须在关闭当前库前完成文件清单、版本、checksum、数据库和偏好校验。未知未来版本安全拒绝。新容器的数据库替换和偏好提交属于一个可回滚流程;任一提交或重开失败都要恢复原数据库,偏好实现也要恢复原白名单值。成功恢复后仍要求退出应用。不要把 .cvbackup 伪装成 .db,不要把未知偏好或非白名单键写入 SharedPreferences。
保养预测规则
预测逻辑位于 PredictionService,日均里程估算位于 MileageEstimator。
- 先通过
service_log_performed_items找到某计划项目最近一次对应的保养记录。 - 没有历史记录时:
- 若设置首保周期,则从计划的持久化日期/里程起点叠加首保周期;
- 否则从计划的持久化日期/里程起点叠加常规周期。
- 有历史记录时,从最近保养日期和该次保养里程叠加常规周期。
- 同时存在时间和里程周期时,以预测更早到期的一项为准,但 basis 记录为组合类型。
- 日均里程使用可用记录中最早与最晚的有效里程差估算;数据不足或不递增时回退为每年 20,000 单位。
- 默认只返回未来一年范围内的项目;已经逾期的项目仍会包含在内。
- 结果按到期日期、再按项目名排序。
修改算法时优先写纯 Dart 单元测试。利用现有的 currentDateOverride 固定当前时间,至少覆盖:月末加月、闰年、没有历史、首保、有历史、时间优先、里程优先、已逾期、里程倒退和零日差。
首次预测采用“从创建保养计划时的当地日历日期与事务内读取的车辆当前里程起算”,有对应保养记录后再从最近一次关联记录起算;新车和二手车不做 UI 区分,也不增加基线输入或解释文案。编辑周期、车辆里程变化和无关保养记录不得改写起点;删除最近关联记录后回退到上一条关联记录或原起点。v1 旧计划迁移为“车辆购入日期 + 0 里程”,从而保持升级前的日期和绝对目标里程预测。首保兼容字段继续保持原语义,不复用为起点。
里程值本身是“单位无关”的数值。设置中的 km/mi 只改变显示标签,不做数值换算;费用继续保存和显示为无币种数值。这是当前明确保留的产品行为,不作为重构优化项。不要加入部分换算、按 locale 推断货币,或重新解释已有数据。
状态刷新与副作用
业务写操作成功后,通常不只需要刷新当前列表:
- 新增、编辑或删除车辆:
- 刷新
VehicleCubit; - 重新加载
UpcomingMaintenanceCubit。
- 刷新
- 新增、编辑或软删除保养计划:
- 刷新当前车辆的
MaintenancePlanCubit; - 必要时刷新
ServiceLogCubit,因为历史显示名依赖计划项目; - 重新加载全局到期预测。
- 刷新当前车辆的
- 新增、编辑或删除保养记录:
- 刷新当前车辆的
ServiceLogCubit; - 重新加载全局到期预测;
- 若用户同意用记录里程更新车辆,再刷新
VehicleCubit。
- 刷新当前车辆的
- 修改通知开关、提前天数或语言:
- 取消或重新调度通知;
- 通知文案应使用当前 locale。
UpcomingMaintenanceCubit.loadAllUpcomingMaintenance 调用 LoadUpcomingMaintenance 读取固定 4 次查询的预测快照,再由 SynchronizeMaintenanceReminders 按 latest-wins 协议重建通知。不要把它放入高频 build 或无意义的循环中。
通知安排在“到期日前 N 天的设备当前 IANA 时区中午 12:00”,使用 inexactAllowWhileIdle。Android 原生时区通过 application port 读取并设置为 tz.local;App resume 时只有时区或当地日历日期变化才完整重载预测并重建提醒,普通同日 resume 不扫描全库。若原提醒时间已过,则使用不晚于到期日的下一个当地中午;不存在可用时间时跳过,不立即补发。通知 ID 由车辆 ID 与计划 ID 的组合 hash 得到。
通知 payload 是带版本、目标动作、车辆 ID、计划 ID 和调度批次时间的 typed JSON。前台、后台恢复和冷启动点击统一先进入内存队列,待 Navigator 与依赖就绪后校验对象:有效对象打开车辆详情的保养计划页签;车辆或计划已删除时打开即将到期列表;非法 payload 安全忽略。后台 isolate 不直接导航,同一批次恰好消费一次。变更 ID、channel、调度时间或 payload 会影响已有通知的替换、取消与点击兼容行为。
Vehicle/Plan/Log Cubit 的初始加载由创建方显式触发,构造函数不自动 fetch。写命令在 repository 成功后等待刷新并返回 OperationResult;刷新失败通过 OperationSuccess.followUpFailure 表达,UI 仍保留最后一次成功数据。若重构该时序,必须同步修改调用方并加测试。
repository、Cubit 和平台异常使用 AppFailure/OperationFailure 分类。内部异常与堆栈只进入开发诊断;用户文案统一通过 AppFailureLocalizer 映射,禁止把 e.toString()、SQL、文件路径或硬编码英文直接展示给用户。
所有跨 await 的 UI 操作都要检查 mounted 或 context.mounted。不要仅用 lint ignore 掩盖失效的 BuildContext。
国际化
l10n.yaml 指定:
- 模板:
lib/i18n/app_en.arb; - 输入目录:
lib/i18n; - 输出目录:
lib/i18n/generated; - 输出入口:
app_localizations.dart。
当前 12 个 locale 分别为:
ar、de、en、es、fr、it、ja、ko、pt、ru、zh(简体)和 zh_Hant(繁体)。
所有 ARB 当前均有 188 个消息 key。新增用户可见文本时:
- 先在
app_en.arb添加消息和@message描述/placeholder 类型; - 同步补齐另外 11 个 ARB,保持 placeholder 名称和 ICU 类型一致;
- 运行
flutter gen-l10n; - 运行
flutter analyze; - 不提交
lib/i18n/generated/,它已被.gitignore排除。
新增 locale 还必须同步 appSupportedLocales 和设置页显示名称。简体/繁体中文依赖 script code 区分,避免只比较 language code。新 UI 不要硬编码英文;内部诊断文本可以保留稳定英文,但用户可见错误应走 AppLocalizations。
用户输入的数字通过 LocalizedNumberInput 统一规范化:接受当前 locale 的数字、.、当前 locale 小数分隔符和阿拉伯小数分隔符,不接受分组符或混合分隔符;计划周期保持整数,车辆/保养里程和费用保持各自现有精度与数值语义。购买日期和已完成保养日期按本地日历日处理,只允许今天及过去;已有未来日期进入编辑页时回落到今天。
UI、主题与资源约定
- 主题由
ColorScheme.fromSeed和AppTheme.getThemeData构建。 - 渐变及渐变上的文字颜色通过
AppThemeExtensions取得。 - 渐变页面优先复用
GradientBackground;普通表面优先使用colorScheme的 surface/onSurface 色。 - 紧急/删除语义统一使用
AppColors.urgentReminderText。 - 同时检查亮色、暗色和自定义 seed color;不要假定固定背景上的文字颜色。
- 应用遵循系统文字缩放;关键页面必须在 200% 缩放下无 overflow,并避免固定高度、单行标签或非 directional 布局导致本地化文本截断。
- Flutter 3.47 起 Android/iOS 上
Semantics(header: true)不再声明无障碍标题;标题语义必须使用大于 0 的headingLevel,并按页面层级选择合理数值。 - 项目当前没有自定义 fragment shader 或
flutter_gpu代码。若后续新增,不要为 OpenGL ES render-target texture 添加旧式 Y 轴翻转;Flutter 3.47 已统一为 top-down 存储。 - 车辆图片在选择时压缩到质量 70、最大宽度 800,并直接写入数据库;增大图片尺寸会直接放大数据库和备份。
- app icon 源文件是
assets/icon/icon.png,可由assets/icon/icon_generator.py重新生成;launcher 资源由flutter_launcher_icons.yaml控制。 - Android app shortcut 使用
action_log和action_upcoming_list,图标分别来自ic_action_log、ic_action_list的亮/暗资源。修改类型字符串时要同步监听、动态 shortcut 和原生资源。
Android、签名与通知
Android Manifest 注册了:
POST_NOTIFICATIONS;RECEIVE_BOOT_COMPLETED;- flutter_local_notifications 的调度和重启 receiver;
- 主 Activity 的
singleTop启动模式。
应用显式设置 android:allowBackup="false",并通过 Android 11- 的 backup_rules.xml 与 Android 12+ 的 data_extraction_rules.xml 排除所有云备份和设备迁移 domain。修改数据库、偏好或隐私文案时,必须继续保持这三处配置、应用内隐私页和商店 Data safety 声明一致。
精确闹钟权限当前被注释,通知使用非精确调度。新增后台、相机、存储或网络能力时,需同时检查 Manifest、运行时权限、隐私说明和 Android 版本差异。
android/app/build.gradle.kts 已分离 debug 和 release 签名。debug 使用标准 Android debug 身份,不读取 release secrets;release 仅在 android/key.properties 和 keystore 完整有效时配置签名,缺少配置的 release task 会在构建前给出明确错误。绝对不要提交:
android/key.properties;*.jks/*.keystore;- 密码、base64 keystore 或 CI secret。
CI 会从 GitHub secrets 生成 keystore 和 key.properties,并使用
APP_CERT_SHA256 校验产物证书指纹。该指纹 secret 必须与发布证书一致。
本地 debug 构建和 PR 质量门禁不得依赖 release secrets。如果本地只需分析 Dart 代码,不应为了绕过签名而修改已跟踪的 Gradle 配置。
常用命令与验证
在仓库根目录运行:
flutter pub get
flutter gen-l10n
dart format --output=none --set-exit-if-changed lib test tool
flutter analyze
flutter test
flutter build apk --debug
需要实际格式化时运行:
dart format lib test tool
图标变更后可运行:
dart run flutter_launcher_icons
仓库已有覆盖前三阶段基础重构的单元、数据库、Cubit、Widget、Golden、查询/缓存性能、架构依赖、Android 策略和发布校验测试。修改业务逻辑、状态时序、分层边界、备份或发布门禁后必须运行:
flutter test
验证要求按改动风险递增:
- 文档/纯文案:检查链接、拼写和受影响的 ARB;
- Dart 逻辑/UI:格式检查、
flutter gen-l10n、flutter analyze; - 预测/数据库/Cubit:补单元或集成测试并运行
flutter test; - 插件、Manifest、Gradle、依赖:再构建 Android debug APK;
- 发布相关:验证 release 构建、签名、version/tag 和 changelog 对应关系。
如果命令因本机缺少 SDK、签名或设备而无法运行,要在交付说明中明确指出,不要用“应该通过”代替真实结果。
建议的测试落点
第一阶段已建立基础回归测试;后续新增逻辑时继续优先补齐:
test/core/services/prediction_service_test.dart:纯计算和日期边界;test/core/utils/mileage_estimator_test.dart:回退值和异常历史;test/data/sources/local/database_migration_test.dart:fresh v2、合成异常 v1、迁移幂等/回滚、FK、索引与写入基准;test/fixtures/database/carvita_v1.db:维护者提供的脱敏官方 v1 样本;测试只能复制后升级,不得原地打开或改写;- 其他数据库测试:四张表 CRUD、事务、软删除和备份兼容;
- Cubit 测试:loading/loaded/error 及写后刷新时序;
- Widget 测试:表单校验、路由参数、主题和关键 locale;
- Android 集成验证:通知权限、重启后通知、快捷入口、导入/导出和图片选择。
通知、文件选择、分享、图片选择和 package info 都依赖平台 channel。单元/Widget 测试中应注入或 mock 边界,不要让测试依赖真实系统弹窗。
发布流程
版本号位于 pubspec.yaml,格式为 major.minor.patch+versionCode。GitHub Actions 在以下情况运行:
- 手动
workflow_dispatch; - pull request;
- push 到
main; - push 形如
vX.Y.Z+N的 tag。
pull request 和 main push 会运行不依赖发布密钥的 Quality job,包括依赖解析、本地化生成、格式检查、静态分析、测试和 debug APK 构建。main 的仓库规则要求 Quality 通过。
tag 发布会先通过 Quality,再运行 Release:
- 使用 Flutter 3.47.0;
- 验证 tag 与
pubspec.yamlversion 精确一致、versionCode 单调递增,且英文和简体中文 changelog 都存在且非空; - 验证签名 secrets,并生成临时 keystore 和
key.properties; - 构建 release APK,使用
APP_CERT_SHA256校验证书身份; - 生成 APK SHA-256 checksum;
- 根据 versionCode 读取英文 changelog,创建 GitHub Release 并上传 universal APK 与 checksum。
发布版本时至少同步:
pubspec.yaml的 version;fastlane/metadata/android/en-US/changelogs/<versionCode>.txt;fastlane/metadata/android/zh-CN/changelogs/<versionCode>.txt;- 必要的商店文案和截图。
不要创建与 pubspec.yaml version 不一致、versionCode 未递增或缺少任一双语 changelog 的发布 tag,否则 CI 会在创建 GitHub Release 前主动失败。
Agent 完成任务前检查
- 已理解受影响页面、Cubit、仓储和数据库调用链。
- Screen/Cubit 未绕过 application use case/port 直接依赖 repository 或设备插件。
- 没有提交 generated、build、缓存、签名或秘密文件。
- 所有用户可见文本已补齐 12 个 ARB。
- 用户错误文案未泄露异常、SQL 或路径,内部诊断保留在开发日志。
- 数据写入后所需的局部和全局状态均已刷新。
- 预测变更考虑了时间、里程、首保、历史和逾期。
- schema 变更包含版本升级、迁移与备份兼容处理。
- 异步 UI 代码检查了 mounted,路由参数类型保持一致。
- 已运行与改动相称的真实验证,并如实报告未能运行的检查。
git diff中只有任务所需改动,格式化没有波及无关文件。
