diff --git a/lib/school/entity/school.dart b/lib/school/entity/school.dart index 9c8dc4071..a935aa09a 100644 --- a/lib/school/entity/school.dart +++ b/lib/school/entity/school.dart @@ -114,6 +114,13 @@ class SemesterInfo implements Comparable { } } +@JsonEnum() +enum StudentType { + undergraduate, + postgraduate, + ; +} + @HiveType(typeId: CacheHiveType.courseCat) enum CourseCat { @HiveField(0) diff --git a/lib/timetable/entity/timetable.dart b/lib/timetable/entity/timetable.dart index 4faf2dac9..5038cf3d5 100644 --- a/lib/timetable/entity/timetable.dart +++ b/lib/timetable/entity/timetable.dart @@ -1,9 +1,11 @@ +import 'dart:math'; import 'dart:typed_data'; import 'package:copy_with_extension/copy_with_extension.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; +import 'package:mimir/credentials/entity/user_type.dart'; import 'package:mimir/credentials/init.dart'; import 'package:mimir/entity/campus.dart'; import 'package:mimir/school/entity/school.dart'; @@ -19,6 +21,12 @@ part 'timetable.g.dart'; DateTime _kNow() => DateTime.now(); +StudentType _kStudentType() => switch (CredentialsInit.storage.oa.userType) { + OaUserType.undergraduate => StudentType.undergraduate, + OaUserType.postgraduate => StudentType.postgraduate, + _ => StudentType.undergraduate, + }; + List _patchesFromJson(List? list) { return list ?.map((e) => TimetablePatchEntry.fromJson(e as Map)) @@ -35,11 +43,16 @@ String _defaultStudentId() { return CredentialsInit.storage.oa.credentials?.account ?? ""; } +String _parseName(String name) { + return name.substring(0, min(SitTimetable.maxNameLength, name.length)); +} + @JsonSerializable() @CopyWith(skipFields: true) @immutable class SitTimetable { - @JsonKey() + static int maxNameLength = 50; + @JsonKey(fromJson: _parseName) final String name; @JsonKey() final DateTime startDate; @@ -49,6 +62,8 @@ class SitTimetable { final int schoolYear; @JsonKey() final Semester semester; + @JsonKey(defaultValue: _kStudentType) + final StudentType studentType; @JsonKey() final int lastCourseKey; @JsonKey() @@ -84,6 +99,7 @@ class SitTimetable { required this.lastModified, required this.createdTime, required this.studentId, + required this.studentType, this.patches = const [], this.signature = "", this.version = 2, @@ -112,6 +128,7 @@ class SitTimetable { "createdTime": createdTime, "signature": signature, "studentId": studentId, + "studentType": studentType, "patches": patches, }.toString(); } @@ -121,11 +138,14 @@ class SitTimetable { 'name:"$name",' 'signature:"$signature",' 'studentId:"$studentId",' + 'studentType:"$studentType",' "campus:$campus," 'startDate:DateTime.parse("$startDate"),' 'createdTime:DateTime.parse("$createdTime"),' 'lastModified:DateTime.parse("$lastModified"),' "courses:${courses.map((key, value) => MapEntry('"$key"', value.toDartCode()))}," + "studentId:$studentId," + "studentType:$studentType," "schoolYear:$schoolYear," "semester:$semester," "lastCourseKey:$lastCourseKey," @@ -146,7 +166,10 @@ class SitTimetable { name == other.name && signature == other.signature && startDate == other.startDate && + studentId == other.studentId && + studentType == other.studentType && lastModified == other.lastModified && + createdTime == other.createdTime && courses.equalsKeysValues(courses.keys, other.courses) && patches.equalsElements(other.patches); } @@ -161,6 +184,9 @@ class SitTimetable { semester, startDate, lastModified, + createdTime, + studentId, + studentType, Object.hashAllUnordered(courses.entries.map((e) => (e.key, e.value))), Object.hashAll(patches), version, @@ -175,6 +201,7 @@ class SitTimetable { writer.strUtf8(name, ByteLength.bit8); writer.strUtf8(signature, ByteLength.bit8); writer.strUtf8(studentId, ByteLength.bit8); + writer.uint8(studentType.index); writer.uint8(campus.index); writer.uint8(schoolYear); writer.uint8(semester.index); @@ -198,6 +225,7 @@ class SitTimetable { name: reader.strUtf8(ByteLength.bit8), signature: reader.strUtf8(ByteLength.bit8), studentId: revision == 1 ? _defaultStudentId() : reader.strUtf8(ByteLength.bit8), + studentType: StudentType.values[reader.uint8()], campus: Campus.values[reader.uint8()], schoolYear: reader.uint8(), semester: Semester.values[reader.uint8()], diff --git a/lib/timetable/entity/timetable.g.dart b/lib/timetable/entity/timetable.g.dart index 65104dc66..8f8442362 100644 --- a/lib/timetable/entity/timetable.g.dart +++ b/lib/timetable/entity/timetable.g.dart @@ -24,6 +24,7 @@ abstract class _$SitTimetableCWProxy { DateTime? lastModified, DateTime? createdTime, String? studentId, + StudentType? studentType, List? patches, String? signature, int? version, @@ -55,6 +56,7 @@ class _$SitTimetableCWProxyImpl implements _$SitTimetableCWProxy { Object? lastModified = const $CopyWithPlaceholder(), Object? createdTime = const $CopyWithPlaceholder(), Object? studentId = const $CopyWithPlaceholder(), + Object? studentType = const $CopyWithPlaceholder(), Object? patches = const $CopyWithPlaceholder(), Object? signature = const $CopyWithPlaceholder(), Object? version = const $CopyWithPlaceholder(), @@ -100,6 +102,10 @@ class _$SitTimetableCWProxyImpl implements _$SitTimetableCWProxy { ? _value.studentId // ignore: cast_nullable_to_non_nullable : studentId as String, + studentType: studentType == const $CopyWithPlaceholder() || studentType == null + ? _value.studentType + // ignore: cast_nullable_to_non_nullable + : studentType as StudentType, patches: patches == const $CopyWithPlaceholder() || patches == null ? _value.patches // ignore: cast_nullable_to_non_nullable @@ -293,6 +299,7 @@ SitTimetable _$SitTimetableFromJson(Map json) => SitTimetable( lastModified: json['lastModified'] == null ? _kNow() : DateTime.parse(json['lastModified'] as String), createdTime: json['createdTime'] == null ? _kNow() : DateTime.parse(json['createdTime'] as String), studentId: json['studentId'] as String? ?? _defaultStudentId(), + studentType: $enumDecodeNullable(_$StudentTypeEnumMap, json['studentType']) ?? _kStudentType(), patches: json['patches'] == null ? const [] : _patchesFromJson(json['patches'] as List?), signature: json['signature'] as String? ?? "", version: (json['version'] as num?)?.toInt() ?? 2, @@ -304,6 +311,7 @@ Map _$SitTimetableToJson(SitTimetable instance) => json) => SitCourse( courseKey: (json['courseKey'] as num).toInt(), courseName: json['courseName'] as String, diff --git a/lib/timetable/page/edit/editor.dart b/lib/timetable/page/edit/editor.dart index 3b0cf1d62..bd8d1c716 100644 --- a/lib/timetable/page/edit/editor.dart +++ b/lib/timetable/page/edit/editor.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:go_router/go_router.dart'; import 'package:mimir/design/adaptive/foundation.dart'; @@ -335,7 +336,10 @@ class _TimetableEditorPageState extends State { children: [ TextFormField( controller: $name, - maxLines: 1, + maxLines: 2, + inputFormatters: [ + LengthLimitingTextInputFormatter(SitTimetable.maxNameLength), + ], decoration: InputDecoration( labelText: i18n.editor.name, border: const OutlineInputBorder(), diff --git a/lib/timetable/page/mine.dart b/lib/timetable/page/mine.dart index 9e9ebd313..d938faa86 100644 --- a/lib/timetable/page/mine.dart +++ b/lib/timetable/page/mine.dart @@ -34,6 +34,7 @@ import '../entity/timetable.dart'; import '../init.dart'; import '../utils/export.dart'; import '../utils/import.dart'; +import '../utils/update.dart'; import '../widgets/focus.dart'; import '../p13n/widget/style.dart'; import 'import.dart'; @@ -353,7 +354,7 @@ class TimetableCard extends StatelessWidget { ); }, ), - if (!kIsWeb && kDebugMode) + if (!kIsWeb && canUpdateTimetable(timetable)) EntryAction( icon: context.icons.refresh, label: i18n.update, @@ -465,10 +466,20 @@ class TimetableDetailsPage extends ConsumerWidget { subtitle: context.formatYmdText(timetable.startDate).text(), ), ListTile( - leading: const Icon(Icons.drive_file_rename_outline), + leading: const Icon(Icons.create), + title: "Created when".text(), + subtitle: context.formatYmdText(timetable.createdTime).text(), + ), + ListTile( + leading: const Icon(Icons.person), title: i18n.signature.text(), subtitle: timetable.signature.text(), ), + ListTile( + leading: const Icon(Icons.school), + title: "Student type".text(), + subtitle: timetable.studentType.toString().text(), + ), ]), if (code2Courses.isNotEmpty) const SliverToBoxAdapter(child: Divider()), SliverList.builder( diff --git a/lib/timetable/service/school.demo.dart b/lib/timetable/service/school.demo.dart index 6cd8a4dde..d7561fb35 100644 --- a/lib/timetable/service/school.demo.dart +++ b/lib/timetable/service/school.demo.dart @@ -18,6 +18,7 @@ class DemoTimetableService implements TimetableService { return SitTimetable( campus: Campus.fengxian, studentId: CredentialsInit.storage.oa.credentials?.account ?? "", + studentType: StudentType.undergraduate, createdTime: DateTime.now(), courses: { "$key": SitCourse( diff --git a/lib/timetable/utils/parse.pg.dart b/lib/timetable/utils/parse.pg.dart index 71e686ed4..a2a142d31 100644 --- a/lib/timetable/utils/parse.pg.dart +++ b/lib/timetable/utils/parse.pg.dart @@ -195,6 +195,7 @@ SitTimetable parsePostgraduateTimetableFromCourseRaw( lastCourseKey: counter, name: "", studentId: studentId, + studentType: StudentType.postgraduate, campus: campus, startDate: DateTime.utc(0), createdTime: DateTime.now(), diff --git a/lib/timetable/utils/parse.ug.dart b/lib/timetable/utils/parse.ug.dart index 7422e599e..a978b28ca 100644 --- a/lib/timetable/utils/parse.ug.dart +++ b/lib/timetable/utils/parse.ug.dart @@ -138,6 +138,7 @@ SitTimetable parseUndergraduateTimetableFromRaw( return SitTimetable( courses: courses, studentId: studentId, + studentType: StudentType.undergraduate, lastCourseKey: lastCourseKey, signature: name, name: i18n.import.defaultName(semester.l10n(), schoolYear.toString(), (schoolYear + 1).toString()), diff --git a/lib/timetable/utils/update.dart b/lib/timetable/utils/update.dart new file mode 100644 index 000000000..5ba8577aa --- /dev/null +++ b/lib/timetable/utils/update.dart @@ -0,0 +1,16 @@ +import 'package:mimir/credentials/init.dart'; +import 'package:mimir/settings/settings.dart'; +import 'package:mimir/timetable/entity/timetable.dart'; +import 'package:mimir/timetable/init.dart'; + +bool canUpdateTimetable(SitTimetable old) { + final credentials = CredentialsInit.storage.oa.credentials; + if (credentials == null) return false; + if (old.studentId != credentials.account) return false; + if (old.campus != Settings.campus) return false; + return true; +} + +Future updateTimetable(SitTimetable old) async { + // TimetableInit.service.fetchUgTimetable(info) +} diff --git a/lib/utils/byte_io/writer.dart b/lib/utils/byte_io/writer.dart index b2096424f..db7489192 100644 --- a/lib/utils/byte_io/writer.dart +++ b/lib/utils/byte_io/writer.dart @@ -44,7 +44,7 @@ class ByteWriter { } void _minimalByteLength(int actualBytes, [ByteLength expectedBytes = ByteLength.bit32, Endian endian = Endian.big]) { - assert(expectedBytes.maxValue >= actualBytes, "Expect $expectedBytes"); + assert(expectedBytes.maxValue >= actualBytes, "Expect $expectedBytes but $actualBytes given"); _checkCapacity(requireBytes: actualBytes + expectedBytes.byteLengths); switch (expectedBytes) { case ByteLength.bit8: diff --git a/test/timetable_entity_test.dart b/test/timetable_entity_test.dart index e663277f3..fe294b725 100644 --- a/test/timetable_entity_test.dart +++ b/test/timetable_entity_test.dart @@ -62,6 +62,7 @@ SitTimetable _get() { name: "Spring, 2023 Timetable", signature: "Liplum", studentId: "114514", + studentType: StudentType.undergraduate, startDate: DateTime.parse("2024-02-26"), createdTime: DateTime.now(), lastModified: DateTime.now(),