[feat] 为添加课程页面提供更自由的时间选项#128
Conversation
- Added CustomClass and CustomClassTimeRange models for handling custom classes and their time ranges. - Introduced CustomClassController to manage the lifecycle of custom classes, including adding, editing, and deleting classes and their time ranges. - Updated ClassTableWidgetState to integrate custom class data and provide methods for managing custom classes. - Enhanced ClassCard to support displaying and interacting with custom classes. - Created CustomClassDetailCard for detailed view and actions on custom classes. - Implemented DateSelectorFree for selecting date ranges and time slots for classes. - Updated class organization logic to include custom classes in the timetable. - Refactored existing code to accommodate new custom class features and ensure smooth integration with the existing class table functionality.
…on desktop platform
BenderBlog
left a comment
There was a problem hiding this comment.
我接下来一周会忙其他的事情,所以您先慢慢改。现在没有发版本压力,所以不着急。
Co-authored-by: Copilot <copilot@github.com>
…s file - Introduced a new `clearAll` method in `CustomClassController` to clear all custom class data. - Updated `CustomClassController` to filter out invalid entries when loading custom classes. - Removed the `UserDefinedClassFile` class and its associated methods, consolidating functionality into `CustomClassController`. - Updated various controllers and pages to utilize the new custom class management system. - Added a Swift model for custom classes to support iOS widget integration, and so did Android.
|
已经完成的工作:
剩余工作:
与原计划不同的地方: 我完全放弃了在添加课程界面对周重复索引的支持相关回复,由于新的数据结构使用的是 |
There was a problem hiding this comment.
本人TODO:我得查查这玩意代码咋简化,这时间序列化感觉不应如此复杂。
There was a problem hiding this comment.
确实没必要这么复杂,这个代码是AI写的,做了很多的降级处理。您看看这个怎么修改吧
|
|
ContentClassTablePageState 这块,应该仅在热重载时候触发,对日常使用无影响。热重载时候先把输入课程信息页面退了吧。 |
好的,那我就这两天再改改 |
上面提到的先修改列表后写入文件,我觉得不太稳妥,因为文件是同步写入的,如果文件写入失败的话,列表就不会更新,这样可以一定程度上保证数据内容的一致。 然后我完成了将文件IO重构到repository的工作。 以下是更改总结,来自deepseek v4 pro: 提示词:
CustomClass 功能演进总结
一、提交概览本分支包含 14 个提交(11 个功能提交 + 3 次 main 合并),围绕自定义课程功能的实现、集成和重构展开。 1.1 功能提交(按时间顺序)
1.2 Main 分支合并
二、架构演进2.1 初始架构(d752d63e)
2.2 重构后架构(f1a59652 → 7c67974)关键变化:
2.3 数据流三、涉及文件3.1 新增文件
3.2 重大修改文件
四、代码质量评估4.1 优点
4.2 待改进
4.3 总体评价代码整体质量 良好。从初始实现到两轮重构,架构越来越清晰:
主要技术债务是 |
| const val EXAM_FILE_NAME = "exam.json" | ||
| const val PHYSICS_EXPERIMENT_FILE_NAME = "PhysicsExperiment.json" | ||
| const val OTHER_EXPERIMENT_FILE_NAME = "OtherExperiment.json" | ||
| const val CUSTOM_CLASS_FILE_NAME = "CustomClassesV2.json" |
There was a problem hiding this comment.
按照我的要求,直接使用新结构体。这是在安卓小部件方面的修改。
| val termStartDay: String, | ||
| val classDetail: List<ClassDetail>, | ||
| val userDefinedDetail: List<ClassDetail>, | ||
| val userDefinedDetail: List<ClassDetail> = emptyList(), |
| Source.EXAM -> "Unknown Exam" | ||
| Source.EXPERIMENT -> "Unknown Experiment" | ||
| Source.EMPTY -> "Unknown Empty" | ||
| Source.USER -> "Unknown Custom Class" |
There was a problem hiding this comment.
安卓小部件方面,理论上不应该报错的东西(至少我这么写的注释)
| } | ||
| } | ||
|
|
||
| @Serializable |
There was a problem hiding this comment.
记得 Kotlin 要想解析 Dart 的 Record 对象要这么搞?需要核实下面的代码。
|
|
||
| otherExperimentJsonData = loadFileContent( | ||
| context, ClassTableConstants.OTHER_EXPERIMENT_FILE_NAME | ||
| customClassJsonData = loadFileContent( |
| val termStartDayStr = classTableData.termStartDay | ||
| if (termStartDayStr.isBlank()) { | ||
| if (classTableData == ClassTableData.EMPTY && userDefinedClassData == UserDefinedClassData.EMPTY) { | ||
| if (classTableData == ClassTableData.EMPTY) { |
| } | ||
| } | ||
| } No newline at end of file | ||
|
|
There was a problem hiding this comment.
加载某一天的用户定义课程信息。日期由总体状态 currentTime 定义
| } | ||
| } | ||
|
|
||
| struct UserDefinedClassData : Codable { |
| var semesterCode : String | ||
| var termStartDay : String | ||
| var classDetail : [ClassDetail] | ||
| var userDefinedDetail : [ClassDetail] |
There was a problem hiding this comment.
需要观察接下来这玩意咋搞的,userDefinedDetail 现在已经作废了
| private let widgetGroupId = "group.xyz.superbart.xdyou" | ||
| private let classTableFile = "ClassTable.json" | ||
| private let userClassFile = "UserClass.json" | ||
| private let customClassFile = "CustomClassesV2.json" |
There was a problem hiding this comment.
开始看看 ios 组件,咋改的这个部分,尤其要回到 Dart 代码里面看看同步过来没有?
| var timeArrangement : [TimeArrangement] | ||
| var classChanges : [ClassChange] | ||
|
|
||
|
|
| return | ||
| } | ||
|
|
||
|
|
|
|
||
| import 'package:intl/intl.dart'; | ||
| import 'package:signals/signals.dart'; | ||
| import 'package:watermeter/controller/custom_class_controller.dart'; |
There was a problem hiding this comment.
没错,现在用户添加的日程需要单独放到一个控制器管理了,因为和课程表没关系了。
| userDefinedClassSignal.value = UserDefinedClassData.empty(); | ||
| ClassTableSession.deleteCache(); | ||
| UserDefinedClassFile.clearUserDefinedClass(); | ||
| unawaited(CustomClassController.i.clearAll()); |
There was a problem hiding this comment.
有必要上 unawaited 吗,需要看看这个控制器里面的方法。因为都是对小文件的读写,用同步方法没有任何问题。
| @@ -34,102 +34,6 @@ class ClassTableController { | |||
|
|
|||
| SemesterSyncEvent? _lastHandledSemesterSyncEvent; | |||
|
|
|||
| @@ -191,22 +94,18 @@ class ClassTableController { | |||
|
|
|||
|
|
||
| import 'package:signals/signals.dart'; | ||
| import 'package:watermeter/model/pda_service/custom_class.dart'; | ||
| import 'package:watermeter/repository/custom_class_service.dart'; |
| import 'package:watermeter/repository/custom_class_service.dart'; | ||
| import 'package:watermeter/repository/logger.dart'; | ||
|
|
||
| enum CustomClassState { fetching, fetched, error, none } |
| } | ||
|
|
||
| /// 通过周索引、日索引和学期开始日期来找到有日程的那天 | ||
| List<CustomClassOccurrence> getOccurrenceOfDay({ |
| @@ -1,15 +1,17 @@ | |||
| // Copyright 2026 Traintime PDA Authours, originally by BenderBlog Rodriguez. | |||
| final List<CustomClassTimeRange> timeRanges; | ||
|
|
||
| CustomClass({ | ||
| required this.id, |
| final String? teacher; | ||
| final String? classroom; | ||
| @JsonKey(name: 'time_ranges') | ||
| final List<CustomClassTimeRange> timeRanges; |
There was a problem hiding this comment.
这个好,后续我要重写 XDYou 的话我会这么考虑新的课程结构体的。因为之前的设计完全按照数据库来的:P
| part 'classtable.g.dart'; | ||
|
|
||
| enum Source { empty, school, user } | ||
| enum Source { empty, school } |
| ); | ||
| } else if (classDetailState.information[i] | ||
| is (CustomClass, CustomClassTimeRange)) { | ||
| // 颜色降级 |
| elevation: 0, | ||
| color: infoColor.shade100, | ||
| child: Container( | ||
| padding: EdgeInsets.fromLTRB( |
| @@ -4,18 +4,19 @@ | |||
|
|
|||
| import 'package:flutter/material.dart'; | |||
| @@ -0,0 +1,543 @@ | |||
| // Copyright 2026 Hazuki Keatsu. | |||
| /// IDs stay in the same numeric bucket so `isCourseReminderNotificationId` | ||
| /// continues to work, while removing random collisions from parallel | ||
| /// scheduling. | ||
| int _generateNotificationId(String uniqueKey) { |
| } | ||
| } | ||
|
|
||
| void _syncToWidget(String data) { |
| // Initialize notification services | ||
| try { | ||
| await NotificationServiceRegistrar().initializeAllServices(); | ||
| if (Platform.isAndroid || Platform.isIOS) { |

新增自定义课程类支持,包含对应的数据模型、控制器与界面集成;为所有支持的语言添加了月份翻译,并完善课程表相关的本地化文案;同时包含若干代码优化与问题修复(例如,使用散列空间更大的通知ID生成器)。
Closes: #116