Skip to content

Edit Table

lingion edited this page Sep 12, 2026 · 1 revision

课表编辑屏

TL;DR:编辑单张课表的属性(名称/学期开始日期/总周数/节次时间表),以及「所有课表」列表页的新建、复制、切换、删除动线。给想了解多课表机制的用户和贡献者。

数据模型

一张课表对应 Room 表 time_tables 中的一行,实体是 TimeTableEntity。编辑屏直接操作的四个字段:

字段 含义 默认值
name 课表名称 —
startDate 学期开始日期,yyyy-MM-dd —
maxWeek 学期总周数 20
timeJson 每节课的上下课时间 JSON TimeTableUtils.DEFAULT_TIME_JSON

完整的实体定义见 app/src/main/java/com/lingion/sleepy/data/entity/TimeTableEntity.kt:13。另有 nodesPerDay(默认 12,TimeTableEntity.kt:24)、smartConfigJson(v1.0.16 自动模式配置,空串表示走手动模式,TimeTableEntity.kt:41)、createdAt(列表页展示导入时间,TimeTableEntity.kt:43)。

EditTableScreen:课表级属性编辑

入口在「我的」页的「所有课表」项(app/src/main/java/com/lingion/sleepy/ui/screen/mine/MineScreen.kt:115),经 MainActivity 以覆盖层方式打开(app/src/main/java/com/lingion/sleepy/MainActivity.kt:300)。tableId == null 时编辑当前表,否则按 id 查找(app/src/main/java/com/lingion/sleepy/ui/screen/mine/EditTableScreen.kt:90);查不到显示「课表数据未找到」(EditTableScreen.kt:98)。

基础信息三个字段

  • 课表名称:单行输入,保存时空名称回落到原名(EditTableScreen.kt:288)。
  • 学期开始日期:手输加日历图标,弹出原生 DatePicker,与导入确认弹窗同款组件(EditTableScreen.kt:184)。
  • 总周数:只允许输入数字,onValueChange 里 filter { it.isDigit() }(EditTableScreen.kt:193);解析失败保存时取 20(EditTableScreen.kt:273)。

节次时间表

折叠面板,标题下方显示「N 节课 · 点击展开/收起」(EditTableScreen.kt:228)。展开后是 TimeSlotEditor,支持手动逐行编辑和基于 SmartPeriodConfig 的自动模式两条路径(EditTableScreen.kt:249)。保存时把行重建为 timeJson 写回(EditTableScreen.kt:291)。

保存校验与归一

点「保存课表设置」先跑三条校验,任一不过就显示「日期需为 yyyy-MM-dd,时间需为 HH:mm 且开始早于结束」(EditTableScreen.kt:274-279)。通过后做两件事:

  1. DateUtils.normalizeStartDate(startDate) 把日期归一到所在周的周一再写库(EditTableScreen.kt:289,归一逻辑在 app/src/main/java/com/lingion/sleepy/util/DateUtils.kt:60)。
  2. 走 updateTableRemappingCourses 而非普通更新:如果 timeJson 变了,课程节次按绝对时间自适应新表。ownTime 课用 timeToNode 直接定位,普通课用 remapCourseNodes 重排,否则 16 节改成 12 节后课程会停在已不存在的 13-16 节(app/src/main/java/com/lingion/sleepy/data/repository/ScheduleRepository.kt:98)。

删除与删除保护

「删除课表」按钮只在 pendingNewTableId == null 时显示——新建还没保存的表,退出即丢弃,没有删除入口(EditTableScreen.kt:312)。点击后弹确认框:表里有课时文案是「确定要删除课表「%1$s」吗?共 %2$d 门课程将一并删除,此操作不可撤销。」,空表则是「确定要删除课表「%1$s」吗?此操作不可撤销。」,课程数量明示给用户(EditTableScreen.kt:337-343)。

最后一张表也允许删,删完由主界面真空态接管(EditTableScreen.kt:310-311 的设计注释)。删除时仓库层先收集该表全部课程 id,级联删除后对这些 id 显式取消课程级课前闹钟,避免孤儿闹钟继续响(ScheduleRepository.kt:123)。

AllTablesScreen:多课表列表

覆盖层入口同上(MainActivity.kt:288)。列表按 createdAt 倒序排,最新建的/最新导入的排最前(app/src/main/java/com/lingion/sleepy/data/dao/TimeTableDao.kt:37)。

当前行标记与选中

每行对比 table.id == state.selectedTableId 判断是否当前表(app/src/main/java/com/lingion/sleepy/ui/screen/mine/AllTablesScreen.kt:84)。当前行:

  • 背景用 primaryContainer,左侧是 CheckCircle 对勾图标;非当前行是 surfaceContainer 加一个实心方块(AllTablesScreen.kt:89,AllTablesScreen.kt:100)。
  • 副标题显示「当前课表 · 第 N 周」;非当前行显示「开始日期: yyyy-MM-dd」(AllTablesScreen.kt:131)。
  • 每行还显示导入时间,格式 yyyy-MM-dd HH:mm:ss,前缀「导入于」(AllTablesScreen.kt:136)。

点非当前行就切换:viewModel.selectTable(table.id),选中态就地高亮,页面留在原地,靠返回键离开(AllTablesScreen.kt:90)。切表会同步数据库 isDefault,让桌面组件跟随 App 当前选中的表(app/src/main/java/com/lingion/sleepy/ui/screen/schedule/ScheduleViewModel.kt:119)。

每行两个操作图标

  • 复制图标:调 duplicateTable,全量复制表配置和课程;新副本命名沿用导入路径的去重规则(原名追加「2」「3」…),groupId 整组映射到新 UUID 保证副本内课程仍可整组编辑;副本不接管默认表也不切选中;建表加插课包在一个撤回批次里,撤回一次整步回退(ScheduleViewModel.kt:141)。
  • 设置图标:进入该表的 EditTableScreen(AllTablesScreen.kt:158)。

新建课表

列表底部是「新建课表」按钮(AllTablesScreen.kt:169)。点下后 MainActivity 先调 createEmptyTable(commitSelection = false)——插入一张空表但不动选中——然后直接带着 pendingNewTableId 跳进编辑屏(MainActivity.kt:290-296)。空表的初始值:名称按「默认 N」去重取号,startDate 自动填上周周一,isDefault 只在全库无表时为 true(ScheduleViewModel.kt:171)。

这条动线是一个「暂存-确认」流程:

  • 点保存:正常走 onSaved,暂存表转正。
  • 点返回:触发 onDiscardPending,调 discardNewTable 删掉这张从未保存的表,选中回退到之前的默认表或剩余第一张(ScheduleViewModel.kt:227,MainActivity.kt:302-306)。

学期开始日期如何决定「第几周」

核心计算在 DateUtils.currentWeek(app/src/main/java/com/lingion/sleepy/util/DateUtils.kt:35):

val start = mondayOf(LocalDate.parse(startDate, dateFormat))
val days = ChronoUnit.DAYS.between(start, today)
maxOf(1, (days / 7).toInt() + 1)

三条规则:

  1. 起点归一到周一。应用约定 startDate 必须是周一(day=1 对应周一),但用户手填或历史数据可能是任意日期,mondayOf 用 previousOrSame(MONDAY) 把它拉回所在周的周一(DateUtils.kt:56)。不归一会出现「startDate 填周二,日期偏移一整周」的错位。
  2. 7 天一周向上取整。从归一起点到今天隔了几天,除以 7 加 1,得到 1-based 周次。开学第一天所在周就是第 1 周。
  3. 下限钳制到 1。学期开始前 currentWeek 返回 1;学期结束后,semesterStatus 按 maxWeek 判定 AFTER_END,周次可浏览但不落在学期内(DateUtils.kt:20)。

反方向换算也有:由「第几周 + 星期几」推具体日期用 dateOfWeek,同样以归一后的周一为基准加 (week-1) 周加 (dayOfWeek-1) 天(DateUtils.kt:68)。

保存编辑屏时 normalizeStartDate 已把日期落到周一,所以库里的 startDate 日常就是周一;归一逻辑是为历史数据留的保险。

相关页面

Clone this wiki locally