Skip to content

Codebase Map

lingion edited this page Sep 16, 2026 · 3 revisions

代码库地图

一句话 TL;DR:Sleepy 主源码 177 个 Kotlin 文件怎么分组、每组干什么、测试放在哪。给想动手改代码的贡献者用,读完你知道该开哪个文件。

主源码在 app/src/main/java/com/lingion/sleepy/,共 177 个文件,分成四个包加根包。行数最多的两块是 data(61 文件)和 ui(48 文件),widget 39 个、util 19 个,根包只有 MainActivity 和 SleepyApp 两个文件。

com.lingion.sleepy/
├── MainActivity.kt          (523 行)
├── SleepyApp.kt             (102 行)
├── data/            61 文件
│   ├── dao/          2    entity/  3    diff/ 3    undo/ 1
│   ├── repository/   1    parser/  5
│   └── jw/          43   ← 最大子包,教务导入
├── ui/              48 文件
│   ├── component/   12    theme/   4
│   └── screen/      32   (edit/imports/manage/mine/schedule/today/widget)
├── util/            19 文件
└── widget/          39 文件  (含 notification/ 2 个)

根包:入口

两个文件撑起整个 App 骨架。SleepyApp 是 Application 类,只初始化 Room 数据库、课表仓库、课前通知调度和桌面组件定期刷新四样全局依赖(启动时另做节假日预取),没有 SDK 和广告(app/src/main/java/com/lingion/sleepy/SleepyApp.kt:16)。MainActivity 持有四个底部 tab:Schedule / Today / Manage / Mine(app/src/main/java/com/lingion/sleepy/MainActivity.kt:172),加课、编辑课程这类 overlay 也压在这一层,状态提升到 Activity 级才不会被 tab 切换杀掉(app/src/main/java/com/lingion/sleepy/MainActivity.kt:223)。

data — Room 持久层

子包 文件数 职责 代表文件
data 直属 3 数据库定义、迁移、自定义主题存储 AppDatabase.kt、Migrations.kt、CustomThemeStore.kt
data/dao 2 Room DAO,只有课程和课表两张表 CourseDao.kt、TimeTableDao.kt
data/entity 3 表实体 CourseEntity.kt、TimeTableEntity.kt、SmartPeriodConfig.kt
data/diff 3 编辑器保存的行级 diff/patch RowKeyDiffer.kt
data/undo 1 单级撤回 UndoManager.kt
data/repository 1 业务数据访问唯一入口 ScheduleRepository.kt
data/jw 43 教务系统导入(见下节) JwParser.kt
data/parser 5 课表格式解析与导出 ScheduleParser.kt

数据库只有两张表:courses 和 time_tables,当前 version 6(5 → 6 加了课程别名字段,app/src/main/java/com/lingion/sleepy/data/AppDatabase.kt:14)。任何 schema 改动必须在 Migrations.kt 登记一条 Migration 并挂进 ALL_MIGRATIONS 数组(app/src/main/java/com/lingion/sleepy/data/Migrations.kt:7)。

CourseEntity 的字段名与 WakeUp 课表的 CourseBean schema 兼容(type / day / startNode / step / startWeek / endWeek / color / tableId),这是各类导入格式能无损进库的前提(app/src/main/java/com/lingion/sleepy/data/entity/CourseEntity.kt:10)。SmartPeriodConfig 承载智慧节次的自动模式配置(app/src/main/java/com/lingion/sleepy/data/entity/SmartPeriodConfig.kt:7)。

UI 层只调 ScheduleRepository,不直接碰 DAO(app/src/main/java/com/lingion/sleepy/data/repository/ScheduleRepository.kt:16)。写方法执行前先在 UndoManager 里存全库快照,用户点撤回时取走清空——单级撤回,进程内单例(app/src/main/java/com/lingion/sleepy/data/undo/UndoManager.kt:16)。编辑器保存走 RowKeyDiffer:按 groupId 分桶、用 RowKey 判定同一行,得出 update / insert / delete 三类动作,改颜色这类字段变化原地 update,不删了重插(app/src/main/java/com/lingion/sleepy/data/diff/RowKeyDiffer.kt:6)。

data/jw — 教务导入,43 个文件

全仓库最大的子包,负责「选学校 → WebView 登录 → 抓 HTML → 解析出课」。文件分四类:

  • 基建:JwProtocol.kt 定义协议类型常量,继承自 WakeupSchedule_BUPT(Apache-2.0)的协议枚举(app/src/main/java/com/lingion/sleepy/data/jw/JwProtocol.kt:4);JwParser.kt 是全部解析器的抽象基类,同样注明了上游出处(app/src/main/java/com/lingion/sleepy/data/jw/JwParser.kt:21);JwParserRegistry 持有「协议 type → parser 工厂」这张单一来源表,type 为空时跑全部候选、按 confidence 和课程数裁决(app/src/main/java/com/lingion/sleepy/data/jw/JwParserRegistry.kt:6);JwParseDiagnostics 在 parser 之上做失败分类,区分会话过期、登录页、无课表容器(app/src/main/java/com/lingion/sleepy/data/jw/JwParseDiagnostics.kt:19)。
  • 解析器:30 个具体 parser,一个文件一个,强智 QZ 系 7 个变体、正方 ZF 系 2 个、URP 2 个,再加 EAMS5、CQU、UCAS、BJTU、ZJU、USTC、SCU、SEU、NEU、WHUT、 PKU 等单校实现。单双周修正集中在 JwParity,五个同型端点的 parser 共用一份语义(app/src/main/java/com/lingion/sleepy/data/jw/JwParity.kt:10)。
  • 学校元数据:JwSchoolInfo 定义学校条目结构,数据从 assets/schools.json 读进 JwImportViewModel(app/src/main/java/com/lingion/sleepy/data/jw/JwImportViewModel.kt:54)。当前 schools.json 有 340 条条目。SchoolDomainMatch 把用户手输的 typed URL 映射回目录条目,处理门户域名混进来的场景(app/src/main/java/com/lingion/sleepy/data/jw/SchoolDomainMatch.kt:18)。Eams5PathPrefix 推断 supwisdom 新版平台的两种部署前缀(app/src/main/java/com/lingion/sleepy/data/jw/Eams5PathPrefix.kt:4)。
  • WebView 内 fetch:JwFetchProtocol 按学校声明 fetch 模式,四种 FetchKind(WISEDU / ZF_NEW / QZ / QZ_IEAS),pick 返回 null 就走原来的 outerHTML 路径(app/src/main/java/com/lingion/sleepy/data/jw/JwFetchProtocol.kt:9);JwFetchError 定义桥协议错误分类。

给新学校加解析,动这里 + schools.json + 测试 fixture,三处缺一不可。

data/parser — 格式解析与导出

ScheduleParser 负责 WakeUp 分享文本、ics 等外部格式的导入;SleepyNativeFormat 是 sleepy-v1 原生交换格式的纯函数层(app/src/main/java/com/lingion/sleepy/data/parser/SleepyNativeFormat.kt:9),配套 SleepyNativeParser 和 SleepyNativeExporter;ScheduleExporter 负责导出,有 exportWakeUpJson / exportWakeUpShareText / exportIcs 三个出口(app/src/main/java/com/lingion/sleepy/data/parser/ScheduleExporter.kt:26)。

ui — Compose 界面

ui/component 12 个文件是跨屏复用件:课表网格 CourseTableView、冲突卡 ConflictCard、课程详情 CourseDetailSheet、药丸导航 PillNavigationBar、时间段编辑 SmartPeriodEditor 和 TimeSlotEditor、分享面板 ShareScheduleSheet 等。

ui/theme 4 个文件管配色。核心是 Theme.kt 里的 WakeUpColorScheme(沿用 WakeUp 原版语义的色板,app/src/main/java/com/lingion/sleepy/ui/theme/Theme.kt:39)和 SleepyTheme(app/src/main/java/com/lingion/sleepy/ui/theme/Theme.kt:303);CustomSchemeDeriver 从用户配的种子色按 M3 角色关系派生完整 scheme;ThemePresets 是静态预设。

ui/screen 按导航拆成七个子包:

子包 文件数 内容
screen/schedule 2 主课表屏 ScheduleScreen + ScheduleViewModel(app/src/main/java/com/lingion/sleepy/ui/screen/schedule/ScheduleViewModel.kt:44)
screen/today 1 今日课程屏 TodayScreen
screen/edit 1 加课/编辑课程 AddCourseScreen(含非常规节次逐卡编辑)
screen/imports 10 教务导入全流程:选校 SchoolSelectScreen、WebView 登录 JwWebViewLoginScreen、正方新版页内 fetch JwZfNewFetchJs、UCAS 的 XRW 头剥除拦截器 SepXrwRequestInterceptor
screen/manage 1 课表管理页 ManagementPage
screen/mine 12 我的页与设置子页:外观、通用设置、导出、节假日、提醒、关于、开源许可 LicenseScreen(app/src/main/java/com/lingion/sleepy/ui/screen/mine/LicenseScreen.kt:61)、多表管理、自定义主题编辑器
screen/widget 5 桌面组件的应用内配置界面:WidgetEditScreen 及三个分区、管理页 WidgetManagementScreen

util — 19 个工具类

纯逻辑层,多数刻意不依赖 Android Context,方便 JVM 单测。几块大的:

  • 冲突布局:ConflictLayoutEngine 把同一天里节次相连的课程算成冲突簇并排布图层(app/src/main/java/com/lingion/sleepy/util/ConflictLayoutEngine.kt:6);ConflictDetailReporter 在保存时生成撞车明细文案。
  • 时间与周次:TimeTableUtils 解析 timeJson(每节课起止时间)、DateUtils 完全不依赖 Android Context(app/src/main/java/com/lingion/sleepy/util/DateUtils.kt:11)、WeekRangeOverlap 判两个周次区间含单双周是否相交、HolidayManager 与 HolidayRange 管节假日。
  • 展示辅助:CourseColorUtil 课程配色、CourseDisplayUtil 课程别名的展示名解析(app/src/main/java/com/lingion/sleepy/util/CourseDisplayUtil.kt:6)、PinyinMatcher 拼音首字母搜索(app/src/main/java/com/lingion/sleepy/util/PinyinMatcher.kt:6)、MarkdownBlocks 解析更新日志。
  • 更新链:UpdateInfo(纯数据)、UpdateManager(下载安装)、UpdateNotifier、VersionUtils 语义化版本比较。
  • 其他:AppPrefs 全部 SharedPreferences 偏好的集中地、LocaleHelper 多语言、HighRefreshRate 高刷、FeedbackComposer 拼反馈链接。

widget — 桌面组件

39 个文件(含 notification/ 2 个),全走 RemoteViews + Canvas bitmap,Glance 渲染层已删除,数据模型和配色集中在 WidgetContent(app/src/main/java/com/lingion/sleepy/widget/WidgetContent.kt:12)。

五种基础排版 × 常规/紧凑两档 = 10 个变体,登记在 ALL_WIDGET_VARIANTS 这张单一来源表里(app/src/main/java/com/lingion/sleepy/widget/WidgetVariantInfo.kt:19):WeekGrid、Today、WeekList、WeekView、TwoDay。每个变体一个 receiver 文件,small 档 receiver 继承常规档。

按职责分:

  • 渲染:WidgetBitmapRenderers 画 bitmap;TodayRowGeometry 是 Today 内容行几何的单一真值(app/src/main/java/com/lingion/sleepy/widget/TodayRowGeometry.kt:7);RemoteViewsWidgetHelper 负责静态面和可滚动条带两条推送路径。
  • 刷新:WidgetUpdater 从 ALL_WIDGET_VARIANTS 派生刷新广播的目标 receiver 列表(app/src/main/java/com/lingion/sleepy/widget/WidgetUpdater.kt:47);WidgetUpdateWorker 用 WorkManager 每 15 分钟刷一次,绕开系统 updatePeriodMillis 最低 30 分钟的限制(app/src/main/java/com/lingion/sleepy/widget/WidgetUpdateWorker.kt:8)。
  • 数据:WidgetTableResolver 定「当前展示哪张课表」(默认表优先,其次课程最多的表,app/src/main/java/com/lingion/sleepy/widget/WidgetTableResolver.kt:7);WidgetBindingStore 按 appWidgetId 存绑定的 tableId。
  • 交互:WidgetRoutes 集中桌面组件点回 App 的路由(app/src/main/java/com/lingion/sleepy/widget/WidgetRoutes.kt:5);PinWidgetRouting 解析 pin 请求的 widget_type 字符串,路由表同样从 ALL_WIDGET_VARIANTS 派生,10 个变体全部可 pin(app/src/main/java/com/lingion/sleepy/widget/PinWidgetRouting.kt:4);Today 的日期翻页状态在纯 JVM 核心 TodayDateNavCore(app/src/main/java/com/lingion/sleepy/widget/TodayDateNavCore.kt:4)。
  • 配置:WidgetConfigureActivity(launcher 添加时的免 UI 快速通道)、WidgetEditCore + ui/screen/widget/ 的编辑界面、WidgetSizeCore / WidgetResizeCore 尺寸。
  • widget/notification/:上课前提醒 CourseNotificationScheduler(app/src/main/java/com/lingion/sleepy/widget/notification/CourseNotificationScheduler.kt:40)和息屏通知进度同步的 FluidCloudService(app/src/main/java/com/lingion/sleepy/widget/notification/FluidCloudService.kt:21)。

资源目录两处要紧的

  • app/src/main/assets/schools.json:340 条学校教务入口,JwImportViewModel 启动时读入。改学校数据必须同步改 app/src/test/resources/jw/schools.json(测试读的就是这份)。
  • app/src/main/assets/PARSERS_NOTICE.json:WakeUpSchedule_BUPT(Apache-2.0)的署名与修改文件清单。
  • 文案在 app/src/main/res/values*,六个目录:默认目录是简体中文,另有 en、es、ja、zh-rCN、zh-rTW 五个翻译目录。改用户可见字符串时六处都要过一遍。

测试源集 app/src/test: 166 个文件

纯 JVM 单测,和主源码同包名镜像分布:

包 文件数 测什么
data/jw 52 每个 parser 一份,外加协议识别、179 校交叉验证回归 Schools179CrossValidationTest、schools.json 一致性 SchoolsJsonConsistencyTest
util 22 冲突布局、时间表合并、节假日、版本比较、iCal 导入等
widget 23 路由 WidgetRoutesTest / PinWidgetRoutingTest、Today 几何与翻页、变体渲染、绑定与刷新接线
data/parser 19 各格式往返:ExportImportRoundTripTest、sleepy-v1 全家、NEU/ics 真实样本
ui/screen/imports 10 合并闸 AppendNonConflictGateTest、WebView 契约、拦截器
ui/screen/edit 4 非常规节次校验与分组
data/entity 3 实体字段与智慧节次
data(直属)、data/undo、data/diff 6 撤回、diff、别名迁移、主题核心
ui/component、ui/theme 4 冲突卡几何、字号缩放、自定义配色派生
ui/screen/schedule 2 空态与视图模式会话契约
根包 6 License 页署名、strings 键位一致性、Manifest 外部打开过滤、AppPrefs 隔离等

测试夹具在 app/src/test/resources/,626 个文件:jw/fixtures/<校名>/ 按 school 存 HTML 原文与期望 JSON,jw_fixtures/ 收对抗样本(GBK 编码、图片表格、WebVPN 改写 URL、iframe 嵌套、登录过期等 10 类)。跑测试:./gradlew test。

新人速查表:改什么去哪个包

想做的事 去这里
新增一所学校的教务解析 data/jw/ 写 parser,JwParserRegistry 挂表,assets/schools.json 加条目,app/src/test/resources/jw/fixtures/ 加 fixture
课表网格样式、冲突排布 util/ConflictLayoutEngine.kt(排布算法)+ ui/component/CourseTableView.kt(绘制)
加课 / 编辑课程行为 ui/screen/edit/AddCourseScreen.kt + 保存路径 data/diff/RowKeyDiffer.kt + 撤回 data/undo/UndoManager.kt
导入导出某种格式 data/parser/ + ui/screen/imports/
某个桌面组件显示不对 widget/ 里对应 Receiver,几何看 TodayRowGeometry.kt,配色看 WidgetContent.kt
上课前提醒 / 通知 widget/notification/
主题、颜色、深色模式 ui/theme/ + data/CustomThemeStore.kt
应用内检查更新 util/UpdateManager.kt 及同族三件
设置项、偏好存储 util/AppPrefs.kt + ui/screen/mine/
课程别名显示 util/CourseDisplayUtil.kt
节假日显示 util/HolidayManager.kt / HolidayRange.kt
多语言文案 app/src/main/res/values* 六处同步

相关页面

Clone this wiki locally