Files
TPSchedule/docs/superpowers/plans/2026-10-12-hepan-everyday-android.md

27 KiB
Raw Permalink Blame History

河畔 Everyday Android Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 交付可安装的 Android 首版,实现离线周课表、玻璃悬浮 Dock、我的与设置、学校认证入口、经过确认的课程导入、课前提醒和手机日历同步。

Architecture: Flutter UI 与纯 Dart 学期/课程计算分离,由一个 ChangeNotifier 应用控制器管理本地快照、导入与平台服务。认证使用设备 WebView 的学校会话;通知采用 Flutter 插件;日历通过独立 Kotlin Calendar Provider 桥接,保留未来 iOS 适配接口。不设自建账号或服务器。

Tech Stack: 已安装 Flutter 3.47.7 / Dart 3.13.5 / Java 17 / Android API 36;依赖固定为 webview_flutter 4.14.1、flutter_local_notifications 22.3.2、timezone 0.11.1、path_provider 2.1.6、file_picker 13.1.0;Flutter SDK 自带 flutter_test、integration_test。提交 pubspec.lock。应用业务位于 mobile/,已有工具与文档继续位于仓库根目录。

Spec: docs/superpowers/specs/2026-10-12-hepan-everyday-design.md(用户已确认)。当前环境结果见 docs/android-setup-status.md。

Global Constraints

  • 首版只有课表和我的两个标签页;底部图标不显示文字,保留无障碍标签;玻璃效果采用模糊、渐变、细边框和高光。
  • 学校课表使用 Asia/Shanghai;11 节起止时间完全采用设计文件表格;连续多节课程只提醒一次。
  • 第 1 周为学期开始日所在的周一至周日;开始日前不生成实例;支持明确周次集合、单双周与不连续周次。
  • 学期周数 1–30,初始建议 20,首次需确认真实开始日;提前量关闭或 5/10/15/30/60 分钟,默认 15。
  • 示例显著标注,不写入真实课表、不默认排提醒/日历;学校认证成功和导入成功分别显示。
  • 密码不存盘、不进入日志、不发送到自建后端;仅向验证过的学校 HTTPS 表单提交;验证码使用学校原页面。
  • 未验证的真实课表结构不能用猜测解析器声称接入;错误或空解析不得覆盖旧课表。明确无课程需单独确认。
  • 日历默认关闭;只管理本 APP 创建的事件;不开启重复的日历提醒;关闭时选择保留或删除自己的事件。
  • Android 先交付,minSdk 24、compile/targetSdk 36;测试 applicationId com.example.hepan_everyday,显示名称严格为“河畔Everyday”。
  • 本轮计划不生成产品代码;完成计划审阅并选择执行方式后才实施。执行时检查隔离工作区;共享现有 .tools,避免重复下载。

Review Focus

  1. 开始日不是周一,以及手机位于其他时区:课程日期和 08:30 上课时间仍正确(Task 1)。
  2. 本地文件损坏或保存失败:保留旧课表和设置,显示恢复/保存失败状态(Task 2)。
  3. 超长中文、200% 字体、同日课程重叠及周日:文字可查看,所有天数可达,Dock 不遮挡末节(Task 3)。
  4. 非学校域名、认证过期与重复点击同步:不注入密码、不重复提交、不误报成功(Task 4)。
  5. 权限被撤销、系统日历手工删除事件、同步途中退出:后续能恢复,避免复制/误删日程(Task 5/6)。

文件与边界

目录/文件 责任
mobile/lib/domain/{course,semester,app_settings,app_snapshot,course_occurrence,lesson_times}.dart 不可变值对象、验证和快照序列化
mobile/lib/domain/schedule_engine.dart 周次、实例、时区与冲突计算
mobile/lib/data/{snapshot_store,file_snapshot_store,demo_schedule,json_schedule_import}.dart 持久化、显著标记的示例、结构化导入
mobile/lib/app/{app_controller,hepan_app,app_theme}.dart 状态、服务协调与主题
mobile/lib/features/timetable/{timetable_page,week_grid,course_details}.dart 周课表与详情
mobile/lib/features/profile/{profile_page,settings_page,import_preview}.dart 我的、设置、确认导入
mobile/lib/features/school/{school_auth_page,school_session,school_page_adapter,school_navigation_policy}.dart 学校 WebView、认证和已验证页面适配边界
mobile/lib/services/{notification_service,local_notification_service,calendar_service,android_calendar_service}.dart 平台服务接口与适配
mobile/lib/widgets/glass_dock.dart 两个纯图标的 Dock
mobile/android/app/src/main/kotlin/com/example/hepan_everyday/{MainActivity,CalendarBridge}.kt Android MethodChannel 和日历权限/事件操作
mobile/test/、mobile/integration_test/ 业务测试、关键交互与设备验证
scripts/android-env.ps1、scripts/build-android.ps1 复用本地工具、构建并导出 APK

所有命令先激活根目录 scripts/android-env.ps1;进入 mobile/ 后执行 Flutter 命令。隔离工作区通过 HEPAN_TOOLCHAIN_ROOT 指向已安装的 C:/Users/david/Desktop/app/.tools;环境脚本添加此可选参数,默认行为仍使用本目录 .tools。APK 与日志不进入 Git。

Task 1: 可运行项目与准确的课程计算

Files: 创建 mobile/ Flutter Android 工程、上述 domain 文件、mobile/lib/main.dart、mobile/lib/app/hepan_app.dart、mobile/test/domain/schedule_engine_test.dart;修改 scripts/android-env.ps1、根 .gitignore。

Interfaces:

  • Course({required String id, required String name, required String teacher, required String room, required int weekday, required int startPeriod, required int endPeriod, required Set<int> weeks, required CourseSource source});CourseSource 为 school/file/demo。

  • Semester({required DateTime startDate, required int totalWeeks});startDate 的年月日代表学校当地日期,totalWeeks 限制 1–30。

  • AppSettings({Semester? semester, bool remindersEnabled=true, int reminderMinutes=15, ColorMode colorMode=ColorMode.multicolor, int singleColorArgb=0xFF348F91, bool calendarEnabled=false, String? calendarId});定义 ColorMode.single/multicolor。

  • LessonSlot(int period, int startMinute, int endMinute);lessonSlots 为 11 项常量。

  • CourseOccurrence({required String key, required Course course, required int week, required tz.TZDateTime start, required tz.TZDateTime end});key = course.id/YYYY-MM-DD,上课时区固定。

  • ScheduleEngine.weekNumber(Semester semester, DateTime schoolDate) -> int?;学期外返回 null。

  • ScheduleEngine.occurrences(List<Course> courses, Semester semester) -> List<CourseOccurrence>,按开始时间/ID 排序。

  • ScheduleEngine.overlaps(CourseOccurrence a, CourseOccurrence b) -> bool。

  • 建立隔离执行目录并复用已安装工具;执行 flutter create --platforms=android --org=com.example --project-name=hepan_everyday mobile。仅添加本计划依赖和 SDK 测试依赖,保存锁文件;将 Android APP 名称设置为“河畔Everyday”。

  • 先写失败测试:11 节开始分钟 [510,560,620,670,870,920,980,1030,1170,1220,1270]、结束分钟 [555,605,665,715,915,965,1025,1075,1215,1265,1315];2026-09-09 开始的学期中,9 月 7 日不生成实例,9 月 9 日属于第 1 周,9 月 14 日属于第 2 周;周日课程可达;2–3 节实例结束为 11:05。

  • 增加失败测试:周次 {1,3,5} 只生成 3 个实例;节次 0/12、星期 8、非法周次拒绝;相邻但不重叠返回 false;把 2026-09-09T00:30Z 转为学校日期后,上课仍为学校时间 08:30,开始 UTC 为 00:30。

test('morning break before period 3 is fifteen minutes', () {
  expect(lessonSlots[2].startMinute - lessonSlots[1].endMinute, 15);
  expect(lessonSlots.last.endMinute, 1315);
});
  • 运行 flutter test test/domain/schedule_engine_test.dart,确认因缺少实现失败;实现上述类型与计算,初始化 timezone 数据并固定 Asia/Shanghai。clock 的瞬间先转换为上海时间;日期选择器的年月日仅解释为学校当地日期,不能用手机当前时区重新偏移。
  • 运行同一测试并确认全通过;让基础 APP 能启动,默认无学期、无真实课程,无权限弹窗;flutter analyze 无问题。提交本任务。

Task 2: 持久化、导入和单一应用状态

Files: 创建 data 文件、mobile/lib/domain/app_snapshot.dart、mobile/lib/app/app_controller.dart、mobile/lib/services/notification_service.dart、mobile/lib/services/calendar_service.dart、mobile/test/data/{snapshot_store_test,json_schedule_import_test}.dart、mobile/test/app/app_controller_test.dart、mobile/test/support/fakes.dart。

Interfaces:

  • 消费 Task 1 类型。ImportMetadata({required CourseSource source, required DateTime importedAt, required String parserVersion})。

  • AppSnapshot({required AppSettings settings, required List<Course> courses, ImportMetadata? importMetadata, Map<String,String> calendarEventIds=const {}});schemaVersion=1,不包含密码/Cookie/认证令牌。

  • SnapshotStore.load() -> Future<AppSnapshot?>,SnapshotStore.save(AppSnapshot snapshot) -> Future<void>;FileSnapshotStore(Directory directory),使用 path_provider Application Support 的目录。

  • ImportDraft({required List<Course> courses, required bool explicitlyEmpty, required CourseSource source, required String parserVersion});JsonScheduleImport.parse(String text) -> ImportDraft,只接受 schemaVersion 1、结构化字段和最大 2 MiB UTF-8 数据。根字段为 schemaVersion、isDemo、explicitlyEmpty、courses;课程字段为 id/name/teacher/room/weekday/startPeriod/endPeriod/weeks,weeks 是整数数组。文件来源仅可为 file 或 demo,不能冒充 school;isDemo=true 进入示例体验。

  • NotificationService.reconcile(List<CourseOccurrence> occurrences, AppSettings settings, DateTime now) -> Future<ReminderResult>,requestPermissions() -> Future<void>、cancelOwned() -> Future<void>;reconcile 仅检查权限,不自行弹窗。ReminderResult 的 status 为 off/permissionDenied/exact/inexact/error,包含 scheduledCount 与说明。

  • CalendarService.requestAccess() -> Future<bool>,listWritableCalendars() -> Future<List<DeviceCalendar>>,sync(List<CourseOccurrence> occurrences, String calendarId, Map<String,String> knownEventIds) -> Future<Map<String,String>>,deleteOwned(Map<String,String> eventIds) -> Future<void>;DeviceCalendar(id,name)。

  • AppController(SnapshotStore store, NotificationService notifications, CalendarService calendar, DateTime Function() clock) extends ChangeNotifier;暴露持久化 snapshot、显示用 visibleCourses/visibleSettings、isDemo、lastError、reminderResult、calendarSyncError。initialize()、showDemo({List<Course>? courses})、leaveDemo()、updateSettings(AppSettings value)、applyImport(ImportDraft draft,{required bool confirmedEmpty})、clearCourses()、disableCalendar({required bool deleteEvents})、enableCalendarFromUserAction(String calendarId)、enableRemindersFromUserAction() 均返回 Future。示例有单独临时数据和“示例”学期,以 clock 所在周为第 1 周,不改变真实快照。clearCourses 始终清理对应通知和自有日历事件;仅关闭日历的保留选择由 disableCalendar 处理。

  • 写失败测试:有效 JSON 转为确定周次集合;部分记录非法使整个草稿失败;空数组且 explicitlyEmpty=false 不得覆盖;重复 ID 拒绝;schemaVersion 未知和超过 2 MiB 拒绝;isDemo=true 导入仅改变显示数据,不改真实快照、不调用平台服务。

  • 写失败测试:临时目录保存、重新打开恢复所有课程/设置;损坏文件报错且不返回“空课表”;模拟保存异常后控制器仍保留旧 snapshot;showDemo 不调用通知或日历;快速并发两次更新按顺序保留最后一份设置;移除课程后只清理本 APP 的副作用。

test('unknown import version is rejected', () {
  expect(() => JsonScheduleImport.parse('{"schemaVersion":2,"courses":[]}'),
      throwsFormatException);
});
  • 运行 flutter test test/data test/app 确认失败;实现 JSON 文件 .tmp 写入并 flush、rename 替换,保存前保留最近有效 .bak;损坏时提示可恢复备份,不静默丢弃。用串行队列处理持久化和服务协调。
  • 实现控制器:只有确认非 demo 草稿才保存真实快照;保存成功后分别协调服务,一项失败不阻止另一项,记录状态供重试;初始化离线恢复后也执行幂等协调。认证连接状态仅由学校会话确认,不能用存在课程推断已登录。
  • 运行同一测试全通过并提交。演示课程与真实快照分开,demo 学期仅供展示,名称/教师标记“示例”。

Task 3: 两个页面、玻璃 Dock 与所有设置

Files: 创建 app_theme、widgets 和 timetable/profile 文件;修改 hepan_app/main;创建 mobile/test/widgets/{navigation_test,timetable_test,settings_test,import_preview_test}.dart。

Interfaces: 消费 AppController。HepanApp({required AppController controller});GlassDock({required int selectedIndex, required ValueChanged<int> onSelected});TimetablePage({required AppController controller})、ProfilePage({required AppController controller})、SettingsPage({required AppController controller})。ImportPreview({required ImportDraft draft, required AppController controller}) 返回用户确认结果;所有平台能力经控制器接口调用。

  • 写失败交互测试:点击日历/我的图标切换两页,没有第三个标签或文字 Dock;屏幕 360×800、底部安全区 34 时,9–11 节可滚动到且不被 Dock 遮挡;周日可横向滚动到。
  • 写失败测试:200% 字体下超长名称/教师/教室无渲染溢出,详情可完整阅读;跨 1–4 节课程仅一个条;重叠课程同时可见并标冲突;当前周跳转边界不能越过配置学期。
  • 写失败测试:颜色切换、提前 15→30 分钟、关闭提醒、周数 0/31 拒绝、确认开始日后保存;未配置学期不生成提醒;取消导入保留旧课程;示例标签常驻且不能开启示例提醒/日历。
testWidgets('dock opens profile', (tester) async {
  // 本测试 setup 用 Fake 服务创建并挂载 HepanApp。
  await tester.tap(find.bySemanticsLabel('我的'));
  await tester.pumpAndSettle();
  expect(find.byType(ProfilePage), findsOneWidget);
});
  • 运行 flutter test test/widgets 确认失败;实现浅色蓝绿主题、上午/下午/晚上分组、同步横向滚动日期头、课程详情;周次按钮、今天列与日期范围明确。Dock 用 BackdropFilter+渐变高光,图标提供 Semantics。
  • 实现我的未登录状态、最近真实导入状态、示例体验、JSON 文件选择/预览/确认;file_picker 13 使用 pickFile() 和有 2 MiB 上限的 readAsByteStream(),未知长度也必须有累计字节限制,非法 UTF-8 拒绝,取消不报失败。设置默认日历关闭、真实开始日必选;日历开启准备权限/选择入口,Task 6 接入。
  • 同一测试通过、模拟器目视检查两个页面和大字体、截图保存到 artifacts/screenshots/;提交。本任务交付本地完整体验,学校入口暂明确“待接入”,不显示虚假同步成功。

Task 4: 学校认证和真实导入适配边界

Files: 创建 school 文件、mobile/test/school/{navigation_policy_test,school_page_adapter_test}.dart、mobile/test/fixtures/school/README.md;修改 profile_page 与 import_preview。

Interfaces: SchoolNavigationPolicy.canInjectCredentials(Uri uri) -> bool 仅允许 https://idas.uestc.edu.cn/authserver/login;isTimetable(Uri uri) -> bool 精确匹配 https://yjsjy.uestc.edu.cn/pyxx/pygl/xskbcx/index/1。SchoolPageAdapter.extract(String html, Uri source) -> ImportDraft;没有经确认的页面匹配器时抛 UnsupportedSchoolLayout。SchoolSession 包装 WebViewController,暴露 connectionState(signedOut/authenticating/connected/expired/unknown)、busy、open()、readTimetable() -> Future<ImportDraft>、logout() -> Future<void>。SchoolAuthPage({required SchoolSession session, required AppController controller})。

  • 写失败测试:idas.uestc.edu.cn.evil.test、HTTP、userinfo 欺骗 URL、其他路径均不能注入凭据;只加载登录页不构成认证成功;课表标题/合法目标和验证结构满足时才解析;未知结构、验证码页和空 HTML 都不得覆盖旧课表;重复同步在 busy 时仅执行一次。
test('lookalike auth host cannot receive credentials', () {
  expect(SchoolNavigationPolicy.canInjectCredentials(
      Uri.parse('https://idas.uestc.edu.cn.evil.test/authserver/login')), isFalse);
});
  • 运行 flutter test test/school 确认失败;实现 URI 精确校验、导航提示、仅主文档错误状态、认证过期保留离线课表。同一 WebView 管理会话,密码字段 submit/cancel/dispose 后清空。
  • 在公开认证页面核对表单字段、提交脚本和动态验证机制;仅能核实时接通自定义输入向官方表单交接。无法验证时显示学校原页面进行认证,自定义界面明确当前方式,不能假装原生账号登录已经联通。
  • 使用目标 URL 的学校实际跳转流程;没有本人会话/页面样本时,只交付认证入口与 fail-closed 解析边界,UI 明确“课表识别待联调”;fixture README 明确没有真实样本。不得用人工编造表格作为已验证学校 fixture。
  • 若用户本人设备取得去标识化页面样本,加入对应 fixture、精确字段匹配器和周次解析测试;真实字段缺失时整体拒绝;真实明确无课程仍必须二次确认。无样本则把该联调状态记录在交付说明。
  • 同一测试通过;退出登录清除 Cookie、WebView 缓存与 localStorage,选择保留时不清课程,选择清除时调用 clearCourses。提交;文案分别表示认证状态与课表导入状态。

Task 5: Android 课前提醒

Files: 创建 local_notification_service、mobile/test/services/notification_service_test.dart;修改 mobile/android/app/build.gradle.kts、mobile/android/app/src/main/AndroidManifest.xml、mobile/android/app/src/main/res/drawable/ic_stat_calendar.xml、settings_page/main。

Interfaces: LocalNotificationService implements Task 2 NotificationService;可注入 ReminderGateway,其 permissions({required bool request}) -> Future<ReminderPermissions>、pendingIds() -> Future<Set<int>>、schedule(ReminderRequest value) -> Future<void>、cancel(int id) -> Future<void>。ReminderRequest(id, occurrenceKey, fireAt, title, body, exact);ReminderPermissions(notifications,exactAlarm)。notificationId(String occurrenceKey) -> int 使用 FNV-1a 32-bit 后取正 31-bit;本次集合碰撞按排序后线性探测解决,不能依赖 Dart hashCode。ID 变化时先取消旧计划再重排,不能覆盖碰撞课程。

  • 写失败测试:08:30 课程默认 08:15 触发;已过提醒时间不排任务;只排实际上课周、每连续课程一次;demo、无学期、提醒关闭不排;重复 reconcile 无重复;删除课程取消旧 ID;通知权限拒绝返回 permissionDenied;精确权限拒绝可用 inexact 并显示说明;撤销权限后不显示已生效。
  • 写失败测试:500 条以上未来实例时仅保留最早 400 条并显示“后续提醒在再次打开时补充”;初始化/设置更改补充未来任务,Android 重启恢复已安排的任务。用 FakeReminderGateway 检验相同实例保留稳定 ID。
test('notification identity is stable', () {
  expect(notificationId('math/2026-09-09'), 1794407394);
});
  • 运行 flutter test test/services/notification_service_test.dart 确认失败;实现时区通知、点击进入课表详情。设置中启用/授权按钮调用 enableRemindersFromUserAction,再请求通知和精确闹钟能力;启动与后台协调只读权限状态,不自动弹窗。
  • 按插件 22.3.2 官方 README 配置 Java 17、core library desugaring 2.1.4、SDK 36、RECEIVE_BOOT_COMPLETED、SCHEDULE_EXACT_ALARM、通知接收器与小图标;使用所需最小权限,不使用 USE_EXACT_ALARM。
  • 同一测试通过;在模拟器安排短时验证通知并退到后台,核实收到/点击/关闭后取消;重启验证恢复。记录厂商省电限制仍需真机检查。提交。

Task 6: 手机日历同步与精确清理

Files: 创建 android_calendar_service、CalendarBridge.kt、mobile/test/services/calendar_service_test.dart、mobile/integration_test/calendar_bridge_test.dart;修改 MainActivity.kt、Manifest、app_controller、settings_page。

Interfaces: AndroidCalendarService 实现 Task 2 CalendarService;MethodChannel hepan_everyday/calendar 的方法为 requestAccess、listCalendars、syncEvents、deleteOwnedEvents。CalendarBridge 用 CalendarContract 查询可写日历,按 occurrenceKey 和当前 applicationId 设置并查验 CUSTOM_APP_URI/CUSTOM_APP_PACKAGE;Dart 保存 key→eventId 的映射。

  • 写失败测试:重复同步仅一个事件;教师/教室改变更新原事件;用户手工删除日历事件后下一次恢复一条;事件已被改成非本 APP 的标记时不修改/删除;同步到一半失败后重试通过 app URI 找回已创建事件。
  • 写失败测试:权限拒绝/撤销、只读日历、无可写日历都保持未启用状态;关闭选择保留时不删除;选择清除仅删除拥有标记的事件;删除整张课表也不影响其他 APP 日程;不创建 CalendarContract.Reminders。
test('denied calendar permission keeps calendar off', () async {
  // setup 中 Fake CalendarService.requestAccess 返回 false。
  await controller.enableCalendarFromUserAction('selected-calendar');
  expect(controller.snapshot.settings.calendarEnabled, isFalse);
  expect(controller.calendarSyncError, isNotNull);
});
  • 运行 flutter test test/services/calendar_service_test.dart 确认失败;实现 READ_CALENDAR/WRITE_CALENDAR 运行时权限。enableCalendarFromUserAction 检查授权和可写日历,成功同步后才开启设置;写入 TZ 实例对应的 UTC 毫秒与 EVENT_TIMEZONE=Asia/Shanghai。
  • 实现 Kotlin 桥接和 Dart 重试逻辑。系统写入与快照无法组成跨系统事务,先凭 app URI 查询去重再写;每次已完成映射及时持久化,重启后可恢复。仅操作用户选定日历,无法校验拥有标记时跳过;disableCalendar 保留时关闭后续自动同步,删除时先清理自有事件。清理失败保留待重试映射,不丢失追踪。
  • 同一测试通过;flutter test integration_test/calendar_bridge_test.dart -d emulator-5554 验证创建、更新、删除和权限结果。没有可写日历时设备测试明确跳过并报告;仅使用测试专用日历,不向用户其他日历写测试数据。提交。

Task 7: 联调、Android 测试包与交付

Files: 创建 mobile/integration_test/app_flow_test.dart、scripts/build-android.ps1、docs/android-mvp-validation.md、docs/school-integration-status.md、examples/schedule.schema-v1.json;修改 README;输出 dist/hepan-everyday-debug.apk,截图放 artifacts。

Interfaces: 应用启动组装真实 SnapshotStore、通知、日历和学校服务;集成测试注入临时存储与显式 fake 平台服务,学校实际会话不在测试中模拟为真实导入。build-android.ps1 激活已安装环境,在 mobile 执行 debug 构建,成功才复制 APK;失败不得复制旧产物冒充本次结果。

  • 写端到端测试:首次启动→我的设置真实开始日/周数→导入结构化示例文件→确认→七天课表→切换多彩/单色→重开应用恢复;进入示例体验不会修改此前真实快照;错误导入不会擦除课程。
testWidgets('installed app exposes exactly the two tabs', (tester) async {
  // setup 挂载应用并完成初始化,权限不自动弹窗。
  expect(find.bySemanticsLabel('课表'), findsOneWidget);
  expect(find.bySemanticsLabel('我的'), findsOneWidget);
});
  • 运行该测试确认未完成连接时失败;接通初始化、服务状态和所有按钮,不留无响应控件或假的成功提示;返回前台复核权限变化并重试尚未成功的通知/日历协调。
  • 完成 flutter analyze、flutter test、flutter test integration_test/app_flow_test.dart -d emulator-5554;全通过后运行 flutter build apk --debug。检查新 APK 的名称、包 ID、版本和时间戳,复制本次产物到 dist。
  • 在 Android 16 模拟器安装本次 APK 并启动;目视核实两页、滚动末节、周日、大字体、离线重启与登录错误状态,保存关键截图。通知后台/重启、日历设备结果独立记录;不能把单元测试通过当成系统验证。
  • 在验证报告区分“已验证”“需真机验证”“学校页面待联调”,写清没有本人会话时尚未完成真实学校自动导入;提供 isDemo=true 的 JSON 样例字段说明和启动/构建方式,真实数据文件使用 isDemo=false,不展示学校密码。
  • 对照设计逐项自查并完成所选执行方式要求的独立审查;修复可重现问题后,只重跑受影响检查。保存源码、报告与 APK 的可点击入口;提交交付文档,不发布到应用商店。

核查来源与执行交接

依赖版本及 Android 配置于 2026-10-12 核查:WebView 官方包、通知插件维护者文档、时区包、路径插件、文件选择器、Android Calendar Provider。执行时以锁定版本的实际 API 为准;若依赖解析或构建证明不兼容,记录证据并作最小必要版本调整。

自查:设计的页面、时间、设置、学校接入边界、数据保存、提醒、日历和设备验收均有对应任务;五项 Review Focus 均分配了测试;副作用失败不改变已保存课表;模块接口名称一致。当前状态:计划已写成并自查,等待用户审核及选择执行方式。

建议由主助手在本聊天逐项实现,最后独立审查:七项任务共享相同模型与控制器接口,顺序实现可以减少交接成本。用户也可选择每项交给独立助手实现和审查的方式;选择后按相应技能执行。