Skip to content

TimeSlot System

lingion edited this page Sep 12, 2026 · 1 revision

节次系统

一句话 TL;DR:Sleepy 里「第几节课」到「几点到几点」的映射规则——每张课表独立配置,支持逐节手填和智慧节次自动推导两种模式,课表显示、提醒、ICS 导出全部从这里取时间。

节次是什么,存在哪

节次 = 节次编号 + 上课时间。课程只存编号(startNode 起始节 + step 连堂节数),具体时间由所在课表的节次表反查。

节次配置跟着课表走,每张课表一份,存在 TimeTableEntity 的两个字段里:

  • timeJson — 手动模式的节次表,JSON 数组,每项 {node, start, end}。默认值是 12 节制的标准作息,第一节 08:00-08:45(app/src/main/java/com/lingion/sleepy/data/entity/TimeTableEntity.kt:30)。
  • smartConfigJson — 自动模式(智慧节次)的配置,JSON 序列化的 SmartPeriodConfig。空串表示这张表走手动模式(app/src/main/java/com/lingion/sleepy/data/entity/TimeTableEntity.kt:41)。

另有 nodesPerDay 记录一天的节次数,默认 12(app/src/main/java/com/lingion/sleepy/data/entity/TimeTableEntity.kt:24),导入时按解析出的最大节次更新。

// timeJson 默认值片段(app/src/main/java/com/lingion/sleepy/util/TimeTableUtils.kt:27)
[
  {"node":1,"start":"08:00","end":"08:45"},
  {"node":2,"start":"08:55","end":"09:40"},
  ...
  {"node":12,"start":"21:45","end":"22:30"}
]

解析走 TimeTableUtils.parseTimeSlotRows,产出编辑用行模型 TimeSlotRow(node, start, end, edgeClass)(app/src/main/java/com/lingion/sleepy/util/TimeTableUtils.kt:291)。edgeClass 只在「非常规节次」出现,见下文。

手动模式

逐节编辑起止时间。每行三个东西:节次编号、开始时间、结束时间,外加一个删除按钮(app/src/main/java/com/lingion/sleepy/ui/component/TimeSlotEditor.kt:166)。

  • 点「添加节次」追加一行,时间为空,编号取现有最大值 +1(app/src/main/java/com/lingion/sleepy/util/TimeTableUtils.kt:424)。
  • 删除某节后,剩余节次重新编号为 1..N(app/src/main/java/com/lingion/sleepy/util/TimeTableUtils.kt:340)。删第 3 节,原第 4 节变成第 3 节。
  • 至少保留 1 节,只剩一行时删除按钮消失(app/src/main/java/com/lingion/sleepy/ui/component/TimeSlotEditor.kt:149)。

保存时统一校验:每行必须是 HH:mm 格式,且开始时间早于结束时间(app/src/main/java/com/lingion/sleepy/ui/screen/mine/EditTableScreen.kt:275)。校验不过,保存按钮直接报错返回。

自动模式(智慧节次)

不想一节一节填,就告诉 Sleepy 三件事:几点开始、每节多长、一共几节,再描述课间怎么插。SmartPeriodConfig 负责算出整张节次表(app/src/main/java/com/lingion/sleepy/data/entity/SmartPeriodConfig.kt:23)。

输入参数

参数 含义 默认值
startTime 第一节开始时间 "08:00"
periodMinutes 每节时长(分钟) 45
totalPeriods 总节数 12
breaks 课间模板列表,每项一个 BreakOption(minutes, isLong, label)
transitionAssignments 每个课间位置选用哪个模板的索引,null = 0 分钟连续

课间模板默认名是「小课间 X 分钟」「大课间 X 分钟」,自定义 label 非空时优先用自定义名(app/src/main/java/com/lingion/sleepy/data/entity/SmartPeriodConfig.kt:97)。isLong 只影响颜色和标签,大课间用主色、小课间用第三色(app/src/main/java/com/lingion/sleepy/ui/component/SmartPeriodEditor.kt:209)。

推导算法

公式(app/src/main/java/com/lingion/sleepy/data/entity/SmartPeriodConfig.kt:19):

第 i 节开始时间 = startTime + i × periodMinutes + Σ(第 i 节之前的所有课间分钟数)

derive()startTime 出发逐节累加:每节先走完 periodMinutes,再补上该节之后的课间分钟,得出下一节起点(app/src/main/java/com/lingion/sleepy/data/entity/SmartPeriodConfig.kt:57)。第 i 个 transition 表示第 i 节与第 i+1 节之间的课间。

分配索引做了两层保护:超出 totalPeriods - 1 的尾部截掉,越界或非法的索引当作 null(app/src/main/java/com/lingion/sleepy/data/entity/SmartPeriodConfig.kt:35)。null 和越界统一落到 0 分钟,即两节连上(app/src/main/java/com/lingion/sleepy/data/entity/SmartPeriodConfig.kt:47)。

一个真实作息的例子,来自单元测试(app/src/test/java/com/lingion/sleepy/data/entity/SmartPeriodConfigTest.kt:47):5 节、每节 45 分钟、08:00 开始,课间模板 10 / 15 / 65 分钟,分配 [0, 1, 0, 1, null],推出:

第1节 08:00 ~ 08:45   ↓ 10 分钟小课间
第2节 08:55 ~ 09:40   ↓ 15 分钟大课间
第3节 09:55 ~ 10:40   ↓ 10 分钟小课间
第4节 10:50 ~ 11:35   ↓ 15 分钟大课间
第5节 11:50 ~ 12:35

编辑器怎么用

自动模式的编辑器分三块(app/src/main/java/com/lingion/sleepy/ui/component/SmartPeriodEditor.kt:59):

  1. 输入区:每节时长、总节数、第一节开始时间。时长和节数最小钳到 1(app/src/main/java/com/lingion/sleepy/ui/component/SmartPeriodEditor.kt:92)。
  2. 课间分配区:点「添加小课间」(默认 10 分钟)或「添加大课间」(默认 30 分钟)建模板(app/src/main/java/com/lingion/sleepy/ui/component/SmartPeriodEditor.kt:122),每个模板下面铺一排位置卡片,编号 1.52.5、…、(N-1).5,点卡片表示「第 i、i+1 节之间插这个课间」。同一模板内可多选位置,跨模板互斥——一个位置只能属于一个课间(app/src/main/java/com/lingion/sleepy/ui/component/SmartPeriodEditor.kt:52)。
  3. 预览区:实时列出推导结果,每节一行,课间位置显示「↓ 0 分钟连续」或「↓ N 分钟 小课间/大课间」(app/src/main/java/com/lingion/sleepy/ui/component/SmartPeriodEditor.kt:194)。

两个容易踩的细节:

  • 删除课间模板时,分配索引会重映射——被删组的位置清空,大于被删索引的组号全部减 1,否则后面所有课间会静默清空(app/src/main/java/com/lingion/sleepy/ui/component/SmartPeriodEditor.kt:169)。
  • 总节数不足 2 时不显示位置卡片,提示「总节数至少 2 节才能分配课间」(app/src/main/res/values/strings.xml:721)。

自动模式一旦有改动,derive() 的结果立即回写节次行,保存时 timeJsonsmartConfigJson 一起落库(app/src/main/java/com/lingion/sleepy/ui/component/TimeSlotEditor.kt:62app/src/main/java/com/lingion/sleepy/ui/screen/mine/EditTableScreen.kt:282)。切回手动模式能看到自动模式的成果,两模式共享同一份 timeJson

编辑入口

TimeSlotEditor(手动/自动双模式,顶部 Tab 切换,app/src/main/java/com/lingion/sleepy/ui/component/TimeSlotEditor.kt:51)出现在三处:

  • 课表管理 → 编辑课表 → 「节次时间表」折叠区,默认收起,展开后显示「N 节 · 展开」(app/src/main/java/com/lingion/sleepy/ui/screen/mine/EditTableScreen.kt:206)。
  • 文件导入的确认弹窗(app/src/main/java/com/lingion/sleepy/ui/screen/imports/ImportSheet.kt:1173)。初始值从导入解析结果播种,避免切自动模式时被 08:00/45 分钟默认值覆盖(app/src/main/java/com/lingion/sleepy/ui/screen/imports/ImportSheet.kt:1122)。
  • 教务导入的配置页(app/src/main/java/com/lingion/sleepy/ui/screen/imports/JwImportActivity.kt:174)。

节次数量

  • 手动模式:节数没有硬上限,「添加节次」随便加;下限 1 节。
  • 自动模式:总节数输入框只收数字、最多 4 位(app/src/main/java/com/lingion/sleepy/ui/component/SmartPeriodEditor.kt:378),下限钳到 1;课间分配从 2 节起才可用。
  • 默认 12 节。导入的课表有多少节就铺多少节,合并时「哪个大用哪个」(app/src/main/java/com/lingion/sleepy/util/TimeTableUtils.kt:367)——导入课程实际到达第 13 节,课表就延到 13 节,不足的时间用内置默认铺底(app/src/main/java/com/lingion/sleepy/util/TimeTableUtils.kt:604)。

课程怎么引用节次

加课/编辑课页面上,每张课程卡选「起始节」和「节数」(连堂),取值范围 1..N,N 是当前课表的标准节次上界(app/src/main/java/com/lingion/sleepy/ui/screen/edit/AddCourseScreen.kt:1106)。改起始节时,节数上限自动收缩为 N - 起始节 + 1(app/src/main/java/com/lingion/sleepy/ui/screen/edit/AddCourseScreen.kt:1123)。

保存前再验一次:课程尾节越过标准上界直接拒绝,报「第 X 张卡:第 A-B 节超出当前课表的 N 节」(app/src/main/java/com/lingion/sleepy/ui/screen/edit/AddCourseScreen.kt:851)。

标准 1..N 之外还有「非常规节次」:课表可以挂编号为 0、-1、N+1 这类边缘槽位(EdgeClass.Before/After,app/src/main/java/com/lingion/sleepy/util/TimeTableUtils.kt:443),给早自习、晚自习这类表外时间用。勾选「非常规节次」的课程绑定单个槽位、节数锁 1(app/src/main/java/com/lingion/sleepy/ui/screen/edit/AddCourseScreen.kt:174)。另有「非常规时间」:课程自带起止钟点,完全不走节次表。

与其他功能的联动

课表显示。周视图/网格把节次表转成每节一行的 TimeSlot(编号、起止、显示文本),课程按 startNode..startNode+step-1 占格(app/src/main/java/com/lingion/sleepy/util/TimeTableUtils.kt:63)。

提醒与 fluid cloud。课前提醒调度时用 parseNodes 拿节次表,再按课程的 startNode 反查具体上课钟点来排闹钟(app/src/main/java/com/lingion/sleepy/widget/notification/CourseNotificationScheduler.kt:183)。改了节次时间,提醒跟着变。

ICS 导出。导出日历时,事件起止时间从节次表反查:首节查 startNode - 1 行的开始时间,末节查 startNode + step - 2 行的结束时间(app/src/main/java/com/lingion/sleepy/data/parser/ScheduleExporter.kt:128)。「非常规时间」课程直接用自带钟点(app/src/main/java/com/lingion/sleepy/data/parser/ScheduleExporter.kt:124),事件 DESCRIPTION 里仍写「第X - Y节」(app/src/main/java/com/lingion/sleepy/data/parser/ScheduleExporter.kt:161)。

作息变更自适应。保存课表时如果节次表变了,课程节次会按旧表时间窗在新表上重新映射——首节找「结束时间晚于课程起点的第一节」,末节找「开始时间早于课程终点的最后一节」;旧表缺行或无重叠时保持原值不猜(app/src/main/java/com/lingion/sleepy/util/TimeTableUtils.kt:400)。16 节课表改 12 节后,原第 13-16 节的课程不会悬空。

测试锚点SmartPeriodConfigTest 锁推导语义(连续、插课间、越界回退、最少 1 节);Ics28SmartPeriodBreakTest 锁「加课间只平移之后的节次」和 rows↔timeJson 往返无损(app/src/test/java/com/lingion/sleepy/data/entity/Ics28SmartPeriodBreakTest.kt:31)。改动推导逻辑先跑这两个文件。

相关页面

Clone this wiki locally