merged develop

This commit is contained in:
2026-09-27 14:58:07 +02:00
188 changed files with 7474 additions and 2558 deletions
+3 -3
View File
@@ -14,9 +14,6 @@ enum WidgetLessonStatus {
irregular,
teacherChanged,
event,
// Sonder-Lesson-Types (Aufsicht, Sprechstunde, …) — Native-Widget-Renderer,
// die den Status noch nicht kennen, fallen still auf den regulären Stil
// zurück, sobald der String dort unbekannt ist.
duty,
}
@@ -35,6 +32,9 @@ abstract class WidgetLesson with _$WidgetLesson {
String? originalTeacher,
required WidgetLessonStatus status,
String? customColor,
/// Fertig aufgelöste Kachelfarbe als `#RRGGBB`. Native Renderer kennen die
/// Paletten nicht — Änderungen daran brauchen so keinen nativen Eingriff.
String? color,
@Default(0) int siblingCount,
}) = _WidgetLesson;
+78 -42
View File
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: type=lint, type=warning, deprecated_member_use, deprecated_member_use_from_same_package
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'widget_data.dart';
@@ -9,6 +9,7 @@ part of 'widget_data.dart';
// FreezedGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format off
T _$identity<T>(T value) => value;
@@ -18,7 +19,9 @@ mixin _$WidgetLesson {
DateTime get start; DateTime get end; String get subjectShort; String? get subjectLong; String? get room;/// On teacher accounts this carries the class label ("7a") instead of the
/// teacher short name — see `WidgetDataMapper` `showClassInsteadOfTeacher`;
/// [originalTeacher] is null in that case.
String? get teacher; String? get originalTeacher; WidgetLessonStatus get status; String? get customColor; int get siblingCount;
String? get teacher; String? get originalTeacher; WidgetLessonStatus get status; String? get customColor;/// Fertig aufgelöste Kachelfarbe als `#RRGGBB`. Native Renderer kennen die
/// Paletten nicht — Änderungen daran brauchen so keinen nativen Eingriff.
String? get color; int get siblingCount;
/// Create a copy of WidgetLesson
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -31,16 +34,21 @@ $WidgetLessonCopyWith<WidgetLesson> get copyWith => _$WidgetLessonCopyWithImpl<W
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is WidgetLesson&&(identical(other.start, start) || other.start == start)&&(identical(other.end, end) || other.end == end)&&(identical(other.subjectShort, subjectShort) || other.subjectShort == subjectShort)&&(identical(other.subjectLong, subjectLong) || other.subjectLong == subjectLong)&&(identical(other.room, room) || other.room == room)&&(identical(other.teacher, teacher) || other.teacher == teacher)&&(identical(other.originalTeacher, originalTeacher) || other.originalTeacher == originalTeacher)&&(identical(other.status, status) || other.status == status)&&(identical(other.customColor, customColor) || other.customColor == customColor)&&(identical(other.siblingCount, siblingCount) || other.siblingCount == siblingCount));
final _this = this as WidgetLesson;
return identical(this, other) || (other.runtimeType == runtimeType&&other is WidgetLesson&&(identical(other.start, _this.start) || other.start == _this.start)&&(identical(other.end, _this.end) || other.end == _this.end)&&(identical(other.subjectShort, _this.subjectShort) || other.subjectShort == _this.subjectShort)&&(identical(other.subjectLong, _this.subjectLong) || other.subjectLong == _this.subjectLong)&&(identical(other.room, _this.room) || other.room == _this.room)&&(identical(other.teacher, _this.teacher) || other.teacher == _this.teacher)&&(identical(other.originalTeacher, _this.originalTeacher) || other.originalTeacher == _this.originalTeacher)&&(identical(other.status, _this.status) || other.status == _this.status)&&(identical(other.customColor, _this.customColor) || other.customColor == _this.customColor)&&(identical(other.color, _this.color) || other.color == _this.color)&&(identical(other.siblingCount, _this.siblingCount) || other.siblingCount == _this.siblingCount));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,start,end,subjectShort,subjectLong,room,teacher,originalTeacher,status,customColor,siblingCount);
int get hashCode {
final _this = this as WidgetLesson;
return Object.hash(runtimeType,_this.start,_this.end,_this.subjectShort,_this.subjectLong,_this.room,_this.teacher,_this.originalTeacher,_this.status,_this.customColor,_this.color,_this.siblingCount);
}
@override
String toString() {
return 'WidgetLesson(start: $start, end: $end, subjectShort: $subjectShort, subjectLong: $subjectLong, room: $room, teacher: $teacher, originalTeacher: $originalTeacher, status: $status, customColor: $customColor, siblingCount: $siblingCount)';
final _this = this as WidgetLesson;
return 'WidgetLesson(start: ${_this.start}, end: ${_this.end}, subjectShort: ${_this.subjectShort}, subjectLong: ${_this.subjectLong}, room: ${_this.room}, teacher: ${_this.teacher}, originalTeacher: ${_this.originalTeacher}, status: ${_this.status}, customColor: ${_this.customColor}, color: ${_this.color}, siblingCount: ${_this.siblingCount})';
}
@@ -51,7 +59,7 @@ abstract mixin class $WidgetLessonCopyWith<$Res> {
factory $WidgetLessonCopyWith(WidgetLesson value, $Res Function(WidgetLesson) _then) = _$WidgetLessonCopyWithImpl;
@useResult
$Res call({
DateTime start, DateTime end, String subjectShort, String? subjectLong, String? room, String? teacher, String? originalTeacher, WidgetLessonStatus status, String? customColor, int siblingCount
DateTime start, DateTime end, String subjectShort, String? subjectLong, String? room, String? teacher, String? originalTeacher, WidgetLessonStatus status, String? customColor, String? color, int siblingCount
});
@@ -68,8 +76,8 @@ class _$WidgetLessonCopyWithImpl<$Res>
/// Create a copy of WidgetLesson
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? start = null,Object? end = null,Object? subjectShort = null,Object? subjectLong = freezed,Object? room = freezed,Object? teacher = freezed,Object? originalTeacher = freezed,Object? status = null,Object? customColor = freezed,Object? siblingCount = null,}) {
return _then(_self.copyWith(
@pragma('vm:prefer-inline') @override $Res call({Object? start = null,Object? end = null,Object? subjectShort = null,Object? subjectLong = freezed,Object? room = freezed,Object? teacher = freezed,Object? originalTeacher = freezed,Object? status = null,Object? customColor = freezed,Object? color = freezed,Object? siblingCount = null,}) {
return _then(WidgetLesson(
start: null == start ? _self.start : start // ignore: cast_nullable_to_non_nullable
as DateTime,end: null == end ? _self.end : end // ignore: cast_nullable_to_non_nullable
as DateTime,subjectShort: null == subjectShort ? _self.subjectShort : subjectShort // ignore: cast_nullable_to_non_nullable
@@ -79,6 +87,7 @@ as String?,teacher: freezed == teacher ? _self.teacher : teacher // ignore: cast
as String?,originalTeacher: freezed == originalTeacher ? _self.originalTeacher : originalTeacher // ignore: cast_nullable_to_non_nullable
as String?,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
as WidgetLessonStatus,customColor: freezed == customColor ? _self.customColor : customColor // ignore: cast_nullable_to_non_nullable
as String?,color: freezed == color ? _self.color : color // ignore: cast_nullable_to_non_nullable
as String?,siblingCount: null == siblingCount ? _self.siblingCount : siblingCount // ignore: cast_nullable_to_non_nullable
as int,
));
@@ -165,10 +174,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( DateTime start, DateTime end, String subjectShort, String? subjectLong, String? room, String? teacher, String? originalTeacher, WidgetLessonStatus status, String? customColor, int siblingCount)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( DateTime start, DateTime end, String subjectShort, String? subjectLong, String? room, String? teacher, String? originalTeacher, WidgetLessonStatus status, String? customColor, String? color, int siblingCount)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _WidgetLesson() when $default != null:
return $default(_that.start,_that.end,_that.subjectShort,_that.subjectLong,_that.room,_that.teacher,_that.originalTeacher,_that.status,_that.customColor,_that.siblingCount);case _:
return $default(_that.start,_that.end,_that.subjectShort,_that.subjectLong,_that.room,_that.teacher,_that.originalTeacher,_that.status,_that.customColor,_that.color,_that.siblingCount);case _:
return orElse();
}
@@ -186,10 +195,10 @@ return $default(_that.start,_that.end,_that.subjectShort,_that.subjectLong,_that
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( DateTime start, DateTime end, String subjectShort, String? subjectLong, String? room, String? teacher, String? originalTeacher, WidgetLessonStatus status, String? customColor, int siblingCount) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( DateTime start, DateTime end, String subjectShort, String? subjectLong, String? room, String? teacher, String? originalTeacher, WidgetLessonStatus status, String? customColor, String? color, int siblingCount) $default,) {final _that = this;
switch (_that) {
case _WidgetLesson():
return $default(_that.start,_that.end,_that.subjectShort,_that.subjectLong,_that.room,_that.teacher,_that.originalTeacher,_that.status,_that.customColor,_that.siblingCount);case _:
return $default(_that.start,_that.end,_that.subjectShort,_that.subjectLong,_that.room,_that.teacher,_that.originalTeacher,_that.status,_that.customColor,_that.color,_that.siblingCount);case _:
throw StateError('Unexpected subclass');
}
@@ -206,10 +215,10 @@ return $default(_that.start,_that.end,_that.subjectShort,_that.subjectLong,_that
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( DateTime start, DateTime end, String subjectShort, String? subjectLong, String? room, String? teacher, String? originalTeacher, WidgetLessonStatus status, String? customColor, int siblingCount)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( DateTime start, DateTime end, String subjectShort, String? subjectLong, String? room, String? teacher, String? originalTeacher, WidgetLessonStatus status, String? customColor, String? color, int siblingCount)? $default,) {final _that = this;
switch (_that) {
case _WidgetLesson() when $default != null:
return $default(_that.start,_that.end,_that.subjectShort,_that.subjectLong,_that.room,_that.teacher,_that.originalTeacher,_that.status,_that.customColor,_that.siblingCount);case _:
return $default(_that.start,_that.end,_that.subjectShort,_that.subjectLong,_that.room,_that.teacher,_that.originalTeacher,_that.status,_that.customColor,_that.color,_that.siblingCount);case _:
return null;
}
@@ -221,7 +230,7 @@ return $default(_that.start,_that.end,_that.subjectShort,_that.subjectLong,_that
@JsonSerializable()
class _WidgetLesson implements WidgetLesson {
const _WidgetLesson({required this.start, required this.end, required this.subjectShort, this.subjectLong, this.room, this.teacher, this.originalTeacher, required this.status, this.customColor, this.siblingCount = 0});
const _WidgetLesson({required this.start, required this.end, required this.subjectShort, this.subjectLong, this.room, this.teacher, this.originalTeacher, required this.status, this.customColor, this.color, this.siblingCount = 0});
factory _WidgetLesson.fromJson(Map<String, dynamic> json) => _$WidgetLessonFromJson(json);
@override final DateTime start;
@@ -236,6 +245,9 @@ class _WidgetLesson implements WidgetLesson {
@override final String? originalTeacher;
@override final WidgetLessonStatus status;
@override final String? customColor;
/// Fertig aufgelöste Kachelfarbe als `#RRGGBB`. Native Renderer kennen die
/// Paletten nicht — Änderungen daran brauchen so keinen nativen Eingriff.
@override final String? color;
@override@JsonKey() final int siblingCount;
/// Create a copy of WidgetLesson
@@ -251,16 +263,18 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _WidgetLesson&&(identical(other.start, start) || other.start == start)&&(identical(other.end, end) || other.end == end)&&(identical(other.subjectShort, subjectShort) || other.subjectShort == subjectShort)&&(identical(other.subjectLong, subjectLong) || other.subjectLong == subjectLong)&&(identical(other.room, room) || other.room == room)&&(identical(other.teacher, teacher) || other.teacher == teacher)&&(identical(other.originalTeacher, originalTeacher) || other.originalTeacher == originalTeacher)&&(identical(other.status, status) || other.status == status)&&(identical(other.customColor, customColor) || other.customColor == customColor)&&(identical(other.siblingCount, siblingCount) || other.siblingCount == siblingCount));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _WidgetLesson&&(identical(other.start, start) || other.start == start)&&(identical(other.end, end) || other.end == end)&&(identical(other.subjectShort, subjectShort) || other.subjectShort == subjectShort)&&(identical(other.subjectLong, subjectLong) || other.subjectLong == subjectLong)&&(identical(other.room, room) || other.room == room)&&(identical(other.teacher, teacher) || other.teacher == teacher)&&(identical(other.originalTeacher, originalTeacher) || other.originalTeacher == originalTeacher)&&(identical(other.status, status) || other.status == status)&&(identical(other.customColor, customColor) || other.customColor == customColor)&&(identical(other.color, color) || other.color == color)&&(identical(other.siblingCount, siblingCount) || other.siblingCount == siblingCount));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,start,end,subjectShort,subjectLong,room,teacher,originalTeacher,status,customColor,siblingCount);
int get hashCode {
return Object.hash(runtimeType,start,end,subjectShort,subjectLong,room,teacher,originalTeacher,status,customColor,color,siblingCount);
}
@override
String toString() {
return 'WidgetLesson(start: $start, end: $end, subjectShort: $subjectShort, subjectLong: $subjectLong, room: $room, teacher: $teacher, originalTeacher: $originalTeacher, status: $status, customColor: $customColor, siblingCount: $siblingCount)';
return 'WidgetLesson(start: $start, end: $end, subjectShort: $subjectShort, subjectLong: $subjectLong, room: $room, teacher: $teacher, originalTeacher: $originalTeacher, status: $status, customColor: $customColor, color: $color, siblingCount: $siblingCount)';
}
@@ -271,7 +285,7 @@ abstract mixin class _$WidgetLessonCopyWith<$Res> implements $WidgetLessonCopyWi
factory _$WidgetLessonCopyWith(_WidgetLesson value, $Res Function(_WidgetLesson) _then) = __$WidgetLessonCopyWithImpl;
@override @useResult
$Res call({
DateTime start, DateTime end, String subjectShort, String? subjectLong, String? room, String? teacher, String? originalTeacher, WidgetLessonStatus status, String? customColor, int siblingCount
DateTime start, DateTime end, String subjectShort, String? subjectLong, String? room, String? teacher, String? originalTeacher, WidgetLessonStatus status, String? customColor, String? color, int siblingCount
});
@@ -288,7 +302,7 @@ class __$WidgetLessonCopyWithImpl<$Res>
/// Create a copy of WidgetLesson
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? start = null,Object? end = null,Object? subjectShort = null,Object? subjectLong = freezed,Object? room = freezed,Object? teacher = freezed,Object? originalTeacher = freezed,Object? status = null,Object? customColor = freezed,Object? siblingCount = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? start = null,Object? end = null,Object? subjectShort = null,Object? subjectLong = freezed,Object? room = freezed,Object? teacher = freezed,Object? originalTeacher = freezed,Object? status = null,Object? customColor = freezed,Object? color = freezed,Object? siblingCount = null,}) {
return _then(_WidgetLesson(
start: null == start ? _self.start : start // ignore: cast_nullable_to_non_nullable
as DateTime,end: null == end ? _self.end : end // ignore: cast_nullable_to_non_nullable
@@ -299,6 +313,7 @@ as String?,teacher: freezed == teacher ? _self.teacher : teacher // ignore: cast
as String?,originalTeacher: freezed == originalTeacher ? _self.originalTeacher : originalTeacher // ignore: cast_nullable_to_non_nullable
as String?,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
as WidgetLessonStatus,customColor: freezed == customColor ? _self.customColor : customColor // ignore: cast_nullable_to_non_nullable
as String?,color: freezed == color ? _self.color : color // ignore: cast_nullable_to_non_nullable
as String?,siblingCount: null == siblingCount ? _self.siblingCount : siblingCount // ignore: cast_nullable_to_non_nullable
as int,
));
@@ -332,16 +347,21 @@ $WidgetPeriodCopyWith<WidgetPeriod> get copyWith => _$WidgetPeriodCopyWithImpl<W
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is WidgetPeriod&&(identical(other.name, name) || other.name == name)&&(identical(other.startMinutes, startMinutes) || other.startMinutes == startMinutes)&&(identical(other.endMinutes, endMinutes) || other.endMinutes == endMinutes)&&(identical(other.virtualStartMinutes, virtualStartMinutes) || other.virtualStartMinutes == virtualStartMinutes)&&(identical(other.virtualEndMinutes, virtualEndMinutes) || other.virtualEndMinutes == virtualEndMinutes));
final _this = this as WidgetPeriod;
return identical(this, other) || (other.runtimeType == runtimeType&&other is WidgetPeriod&&(identical(other.name, _this.name) || other.name == _this.name)&&(identical(other.startMinutes, _this.startMinutes) || other.startMinutes == _this.startMinutes)&&(identical(other.endMinutes, _this.endMinutes) || other.endMinutes == _this.endMinutes)&&(identical(other.virtualStartMinutes, _this.virtualStartMinutes) || other.virtualStartMinutes == _this.virtualStartMinutes)&&(identical(other.virtualEndMinutes, _this.virtualEndMinutes) || other.virtualEndMinutes == _this.virtualEndMinutes));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,name,startMinutes,endMinutes,virtualStartMinutes,virtualEndMinutes);
int get hashCode {
final _this = this as WidgetPeriod;
return Object.hash(runtimeType,_this.name,_this.startMinutes,_this.endMinutes,_this.virtualStartMinutes,_this.virtualEndMinutes);
}
@override
String toString() {
return 'WidgetPeriod(name: $name, startMinutes: $startMinutes, endMinutes: $endMinutes, virtualStartMinutes: $virtualStartMinutes, virtualEndMinutes: $virtualEndMinutes)';
final _this = this as WidgetPeriod;
return 'WidgetPeriod(name: ${_this.name}, startMinutes: ${_this.startMinutes}, endMinutes: ${_this.endMinutes}, virtualStartMinutes: ${_this.virtualStartMinutes}, virtualEndMinutes: ${_this.virtualEndMinutes})';
}
@@ -370,7 +390,7 @@ class _$WidgetPeriodCopyWithImpl<$Res>
/// Create a copy of WidgetPeriod
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? startMinutes = null,Object? endMinutes = null,Object? virtualStartMinutes = null,Object? virtualEndMinutes = null,}) {
return _then(_self.copyWith(
return _then(WidgetPeriod(
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,startMinutes: null == startMinutes ? _self.startMinutes : startMinutes // ignore: cast_nullable_to_non_nullable
as int,endMinutes: null == endMinutes ? _self.endMinutes : endMinutes // ignore: cast_nullable_to_non_nullable
@@ -547,16 +567,18 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _WidgetPeriod&&(identical(other.name, name) || other.name == name)&&(identical(other.startMinutes, startMinutes) || other.startMinutes == startMinutes)&&(identical(other.endMinutes, endMinutes) || other.endMinutes == endMinutes)&&(identical(other.virtualStartMinutes, virtualStartMinutes) || other.virtualStartMinutes == virtualStartMinutes)&&(identical(other.virtualEndMinutes, virtualEndMinutes) || other.virtualEndMinutes == virtualEndMinutes));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _WidgetPeriod&&(identical(other.name, name) || other.name == name)&&(identical(other.startMinutes, startMinutes) || other.startMinutes == startMinutes)&&(identical(other.endMinutes, endMinutes) || other.endMinutes == endMinutes)&&(identical(other.virtualStartMinutes, virtualStartMinutes) || other.virtualStartMinutes == virtualStartMinutes)&&(identical(other.virtualEndMinutes, virtualEndMinutes) || other.virtualEndMinutes == virtualEndMinutes));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,name,startMinutes,endMinutes,virtualStartMinutes,virtualEndMinutes);
int get hashCode {
return Object.hash(runtimeType,name,startMinutes,endMinutes,virtualStartMinutes,virtualEndMinutes);
}
@override
String toString() {
return 'WidgetPeriod(name: $name, startMinutes: $startMinutes, endMinutes: $endMinutes, virtualStartMinutes: $virtualStartMinutes, virtualEndMinutes: $virtualEndMinutes)';
return 'WidgetPeriod(name: $name, startMinutes: $startMinutes, endMinutes: $endMinutes, virtualStartMinutes: $virtualStartMinutes, virtualEndMinutes: $virtualEndMinutes)';
}
@@ -615,16 +637,21 @@ $WidgetDayInfoCopyWith<WidgetDayInfo> get copyWith => _$WidgetDayInfoCopyWithImp
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is WidgetDayInfo&&(identical(other.date, date) || other.date == date)&&(identical(other.isHoliday, isHoliday) || other.isHoliday == isHoliday)&&(identical(other.holidayName, holidayName) || other.holidayName == holidayName));
final _this = this as WidgetDayInfo;
return identical(this, other) || (other.runtimeType == runtimeType&&other is WidgetDayInfo&&(identical(other.date, _this.date) || other.date == _this.date)&&(identical(other.isHoliday, _this.isHoliday) || other.isHoliday == _this.isHoliday)&&(identical(other.holidayName, _this.holidayName) || other.holidayName == _this.holidayName));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,date,isHoliday,holidayName);
int get hashCode {
final _this = this as WidgetDayInfo;
return Object.hash(runtimeType,_this.date,_this.isHoliday,_this.holidayName);
}
@override
String toString() {
return 'WidgetDayInfo(date: $date, isHoliday: $isHoliday, holidayName: $holidayName)';
final _this = this as WidgetDayInfo;
return 'WidgetDayInfo(date: ${_this.date}, isHoliday: ${_this.isHoliday}, holidayName: ${_this.holidayName})';
}
@@ -653,7 +680,7 @@ class _$WidgetDayInfoCopyWithImpl<$Res>
/// Create a copy of WidgetDayInfo
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? date = null,Object? isHoliday = null,Object? holidayName = freezed,}) {
return _then(_self.copyWith(
return _then(WidgetDayInfo(
date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable
as DateTime,isHoliday: null == isHoliday ? _self.isHoliday : isHoliday // ignore: cast_nullable_to_non_nullable
as bool,holidayName: freezed == holidayName ? _self.holidayName : holidayName // ignore: cast_nullable_to_non_nullable
@@ -818,16 +845,18 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _WidgetDayInfo&&(identical(other.date, date) || other.date == date)&&(identical(other.isHoliday, isHoliday) || other.isHoliday == isHoliday)&&(identical(other.holidayName, holidayName) || other.holidayName == holidayName));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _WidgetDayInfo&&(identical(other.date, date) || other.date == date)&&(identical(other.isHoliday, isHoliday) || other.isHoliday == isHoliday)&&(identical(other.holidayName, holidayName) || other.holidayName == holidayName));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,date,isHoliday,holidayName);
int get hashCode {
return Object.hash(runtimeType,date,isHoliday,holidayName);
}
@override
String toString() {
return 'WidgetDayInfo(date: $date, isHoliday: $isHoliday, holidayName: $holidayName)';
return 'WidgetDayInfo(date: $date, isHoliday: $isHoliday, holidayName: $holidayName)';
}
@@ -888,16 +917,21 @@ $WidgetTimetableDataCopyWith<WidgetTimetableData> get copyWith => _$WidgetTimeta
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is WidgetTimetableData&&(identical(other.fetchedAt, fetchedAt) || other.fetchedAt == fetchedAt)&&(identical(other.anchorDate, anchorDate) || other.anchorDate == anchorDate)&&const DeepCollectionEquality().equals(other.lessons, lessons)&&const DeepCollectionEquality().equals(other.periods, periods)&&(identical(other.isHoliday, isHoliday) || other.isHoliday == isHoliday)&&(identical(other.holidayName, holidayName) || other.holidayName == holidayName)&&const DeepCollectionEquality().equals(other.days, days));
final _this = this as WidgetTimetableData;
return identical(this, other) || (other.runtimeType == runtimeType&&other is WidgetTimetableData&&(identical(other.fetchedAt, _this.fetchedAt) || other.fetchedAt == _this.fetchedAt)&&(identical(other.anchorDate, _this.anchorDate) || other.anchorDate == _this.anchorDate)&&const DeepCollectionEquality().equals(other.lessons, _this.lessons)&&const DeepCollectionEquality().equals(other.periods, _this.periods)&&(identical(other.isHoliday, _this.isHoliday) || other.isHoliday == _this.isHoliday)&&(identical(other.holidayName, _this.holidayName) || other.holidayName == _this.holidayName)&&const DeepCollectionEquality().equals(other.days, _this.days));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,fetchedAt,anchorDate,const DeepCollectionEquality().hash(lessons),const DeepCollectionEquality().hash(periods),isHoliday,holidayName,const DeepCollectionEquality().hash(days));
int get hashCode {
final _this = this as WidgetTimetableData;
return Object.hash(runtimeType,_this.fetchedAt,_this.anchorDate,const DeepCollectionEquality().hash(_this.lessons),const DeepCollectionEquality().hash(_this.periods),_this.isHoliday,_this.holidayName,const DeepCollectionEquality().hash(_this.days));
}
@override
String toString() {
return 'WidgetTimetableData(fetchedAt: $fetchedAt, anchorDate: $anchorDate, lessons: $lessons, periods: $periods, isHoliday: $isHoliday, holidayName: $holidayName, days: $days)';
final _this = this as WidgetTimetableData;
return 'WidgetTimetableData(fetchedAt: ${_this.fetchedAt}, anchorDate: ${_this.anchorDate}, lessons: ${_this.lessons}, periods: ${_this.periods}, isHoliday: ${_this.isHoliday}, holidayName: ${_this.holidayName}, days: ${_this.days})';
}
@@ -926,7 +960,7 @@ class _$WidgetTimetableDataCopyWithImpl<$Res>
/// Create a copy of WidgetTimetableData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? fetchedAt = null,Object? anchorDate = null,Object? lessons = null,Object? periods = null,Object? isHoliday = null,Object? holidayName = freezed,Object? days = null,}) {
return _then(_self.copyWith(
return _then(WidgetTimetableData(
fetchedAt: null == fetchedAt ? _self.fetchedAt : fetchedAt // ignore: cast_nullable_to_non_nullable
as DateTime,anchorDate: null == anchorDate ? _self.anchorDate : anchorDate // ignore: cast_nullable_to_non_nullable
as DateTime,lessons: null == lessons ? _self.lessons : lessons // ignore: cast_nullable_to_non_nullable
@@ -1075,7 +1109,7 @@ return $default(_that.fetchedAt,_that.anchorDate,_that.lessons,_that.periods,_th
@JsonSerializable()
class _WidgetTimetableData implements WidgetTimetableData {
const _WidgetTimetableData({required this.fetchedAt, required this.anchorDate, required final List<WidgetLesson> lessons, final List<WidgetPeriod> periods = const <WidgetPeriod>[], this.isHoliday = false, this.holidayName, final List<WidgetDayInfo> days = const <WidgetDayInfo>[]}): _lessons = lessons,_periods = periods,_days = days;
const _WidgetTimetableData({required this.fetchedAt, required this.anchorDate, required List<WidgetLesson> lessons, List<WidgetPeriod> periods = const <WidgetPeriod>[], this.isHoliday = false, this.holidayName, List<WidgetDayInfo> days = const <WidgetDayInfo>[]}): _lessons = lessons,_periods = periods,_days = days;
factory _WidgetTimetableData.fromJson(Map<String, dynamic> json) => _$WidgetTimetableDataFromJson(json);
@override final DateTime fetchedAt;
@@ -1122,16 +1156,18 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _WidgetTimetableData&&(identical(other.fetchedAt, fetchedAt) || other.fetchedAt == fetchedAt)&&(identical(other.anchorDate, anchorDate) || other.anchorDate == anchorDate)&&const DeepCollectionEquality().equals(other._lessons, _lessons)&&const DeepCollectionEquality().equals(other._periods, _periods)&&(identical(other.isHoliday, isHoliday) || other.isHoliday == isHoliday)&&(identical(other.holidayName, holidayName) || other.holidayName == holidayName)&&const DeepCollectionEquality().equals(other._days, _days));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _WidgetTimetableData&&(identical(other.fetchedAt, fetchedAt) || other.fetchedAt == fetchedAt)&&(identical(other.anchorDate, anchorDate) || other.anchorDate == anchorDate)&&const DeepCollectionEquality().equals(other.lessons, _lessons)&&const DeepCollectionEquality().equals(other.periods, _periods)&&(identical(other.isHoliday, isHoliday) || other.isHoliday == isHoliday)&&(identical(other.holidayName, holidayName) || other.holidayName == holidayName)&&const DeepCollectionEquality().equals(other.days, _days));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,fetchedAt,anchorDate,const DeepCollectionEquality().hash(_lessons),const DeepCollectionEquality().hash(_periods),isHoliday,holidayName,const DeepCollectionEquality().hash(_days));
int get hashCode {
return Object.hash(runtimeType,fetchedAt,anchorDate,const DeepCollectionEquality().hash(_lessons),const DeepCollectionEquality().hash(_periods),isHoliday,holidayName,const DeepCollectionEquality().hash(_days));
}
@override
String toString() {
return 'WidgetTimetableData(fetchedAt: $fetchedAt, anchorDate: $anchorDate, lessons: $lessons, periods: $periods, isHoliday: $isHoliday, holidayName: $holidayName, days: $days)';
return 'WidgetTimetableData(fetchedAt: $fetchedAt, anchorDate: $anchorDate, lessons: $lessons, periods: $periods, isHoliday: $isHoliday, holidayName: $holidayName, days: $days)';
}
+2
View File
@@ -17,6 +17,7 @@ _WidgetLesson _$WidgetLessonFromJson(Map<String, dynamic> json) =>
originalTeacher: json['originalTeacher'] as String?,
status: $enumDecode(_$WidgetLessonStatusEnumMap, json['status']),
customColor: json['customColor'] as String?,
color: json['color'] as String?,
siblingCount: (json['siblingCount'] as num?)?.toInt() ?? 0,
);
@@ -31,6 +32,7 @@ Map<String, dynamic> _$WidgetLessonToJson(_WidgetLesson instance) =>
'originalTeacher': instance.originalTeacher,
'status': _$WidgetLessonStatusEnumMap[instance.status]!,
'customColor': instance.customColor,
'color': instance.color,
'siblingCount': instance.siblingCount,
};
+62 -25
View File
@@ -1,6 +1,5 @@
import 'dart:developer';
import 'package:rrule/rrule.dart';
import 'dart:ui';
import '../api/marianumconnect/queries/timetable_get_holidays/timetable_get_holidays_response.dart';
import '../api/marianumconnect/queries/timetable_get_rooms/timetable_get_rooms_response.dart';
@@ -10,10 +9,14 @@ import '../api/marianumconnect/queries/timetable_get_week/timetable_get_week_res
import '../api/mhsl/custom_timetable_event/custom_timetable_event.dart';
import '../api/mhsl/custom_timetable_event/get/get_custom_timetable_event_response.dart';
import '../extensions/date_time.dart';
import '../utils/recurrence_occurrences.dart';
import '../view/pages/timetable/data/lesson_color.dart';
import '../view/pages/timetable/data/lesson_labels.dart';
import '../view/pages/timetable/data/lesson_merger.dart';
import '../view/pages/timetable/data/lesson_period_schedule.dart';
import '../view/pages/timetable/data/lesson_status.dart';
import '../view/pages/timetable/data/lesson_type_label.dart';
import '../view/pages/timetable/data/subject_color_palette.dart';
import 'widget_data.dart';
class WidgetDataMapper {
@@ -322,20 +325,26 @@ class WidgetDataMapper {
}) {
final start = lesson.startDateTime;
final end = lesson.endDateTime;
final status = _mapStatus(
LessonStatusClassifier.classify(lesson, start, end, now),
);
final subjectShortRaw = lesson.subjects.firstOrNull?.trim() ?? '';
// Webuntis sometimes ships subject-less entries (Wandertag etc.). Fall
// back to "Event" so the tile isn't just a dash.
final subjectShort = subjectShortRaw.isEmpty ? 'Event' : subjectShortRaw;
String? subjectLong;
if (subjects != null && subjectShortRaw.isNotEmpty) {
subjectLong = subjects.result
.where((s) => s.shortName == subjectShortRaw)
.firstOrNull
?.longName;
}
// Gleiche Einstufung wie TimetableAppointmentFactory, damit Aufsicht,
// Prüfung & Co. im Widget dieselbe Farbe tragen wie in der App.
final lessonStatus = LessonStatusClassifier.classify(
lesson,
start,
end,
now,
isEvent: subjectShortRaw.isEmpty && lesson.lessonType == 'LESSON',
isDuty: lesson.lessonType != 'LESSON',
);
final subjectShort = subjectShortRaw.isEmpty
? LessonTypeLabel.forEntry(lesson)
: subjectShortRaw;
final subject = subjects == null || subjectShortRaw.isEmpty
? null
: subjects.result
.where((s) => s.shortName == subjectShortRaw)
.firstOrNull;
final subjectLong = subject?.longName;
final roomShort = lesson.rooms.firstOrNull;
var roomName = roomShort;
if (rooms != null && roomShort != null) {
@@ -347,11 +356,12 @@ class WidgetDataMapper {
}
final teacher = lesson.teachers.firstOrNull;
// Lehrerpläne: Klasse in den Teacher-Slot mappen, damit die nativen
// Renderer unverändert bleiben. Klassenlose Einträge (Aufsichten) behalten
// den Lehrer als Fallback.
final classLabel = showClassInsteadOfTeacher ? lesson.classLabel : null;
final teacherName = classLabel ?? teacher?.shortName;
final originalTeacher = classLabel != null
// Renderer unverändert bleiben. Klassenlose Einträge (Aufsichten) lassen
// den Slot leer — die Lehrkraft wäre die Plan-Inhaberin selbst.
final teacherName = showClassInsteadOfTeacher
? lesson.classLabel
: teacher?.shortName;
final originalTeacher = showClassInsteadOfTeacher
? null
: teacher?.originalShortName;
return WidgetLesson(
@@ -362,10 +372,29 @@ class WidgetDataMapper {
room: roomName,
teacher: teacherName,
originalTeacher: originalTeacher,
status: status,
status: _mapStatus(lessonStatus),
color: _hex(_lessonColor(lessonStatus, subject?.color)),
);
}
/// Das Widget kennt keine Zeitzustände: laufende Stunden bekommen dieselbe
/// Farbe wie reguläre, sonst würde die Hervorhebung zwischen zwei
/// Aktualisierungen veralten.
static Color _lessonColor(LessonStatus status, String? subjectColor) {
final isRegular =
status == LessonStatus.regular ||
status == LessonStatus.ongoing ||
status == LessonStatus.past;
if (!isRegular) return LessonColor.forStatus(status);
return SubjectColorPalette.colorFromName(subjectColor) ??
LessonColor.regular;
}
static String _hex(Color color) {
final rgb = color.toARGB32() & 0xFFFFFF;
return '#${rgb.toRadixString(16).padLeft(6, '0').toUpperCase()}';
}
static WidgetLessonStatus _mapStatus(LessonStatus status) {
switch (status) {
case LessonStatus.cancelled:
@@ -439,11 +468,13 @@ class WidgetDataMapper {
}
try {
final parsed = RecurrenceRule.fromString(rule);
final anchorUtc = event.startDate.toUtc();
for (final occUtc in parsed.getInstances(start: anchorUtc)) {
if (!occUtc.isBefore(rangeEndUtc)) break;
if (occUtc.isBefore(rangeStartUtc)) continue;
for (final occUtc in RecurrenceOccurrences.between(
rule,
anchorUtc,
rangeStartUtc,
rangeEndUtc,
)) {
final occLocal = occUtc.toLocal();
final occStart = DateTime(
occLocal.year,
@@ -475,6 +506,12 @@ class WidgetDataMapper {
subjectLong: title.isEmpty ? null : title,
status: WidgetLessonStatus.event,
customColor: event.color,
color: _hex(
SubjectColorPalette.colorFromName(event.color) ??
SubjectColorPalette.displayOf(
SubjectColorPalette.defaultColor,
).color,
),
);
final isAllDay = duration == Duration.zero && _isMidnight(event.startDate);
+80 -6
View File
@@ -1,8 +1,10 @@
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../session/session_manager.dart';
import '../state/app/modules/timetable/bloc/timetable_state.dart';
import '../state/app/modules/timetable/subject/timetable_subject.dart';
import '../storage/settings.dart';
@@ -20,23 +22,75 @@ class WidgetPublisher {
static DateTime widgetNow() =>
kDebugMode ? DateTime.now().add(debugTimeShift) : DateTime.now();
/// Identical snapshots are still re-written after this long, so the stored
/// `fetchedAt` (freshness for the background refresh) doesn't go stale.
static const Duration _republishAfter = Duration(minutes: 10);
static Future<void> _queue = Future.value();
static (bool, String, bool, String?)? _lastFlags;
static String? _lastSignature;
static DateTime? _lastPublishedAt;
/// Forgets what was written last, so the next publish writes again. Called
/// whenever the stored widget data is cleared (sign-out).
static void resetDedupe() {
_lastFlags = null;
_lastSignature = null;
_lastPublishedAt = null;
}
/// Publishes run one at a time: they are fire-and-forget from the bloc
/// stream, and interleaved writes could leave day and week data from
/// different states behind.
/// [epoch] is the session the [state] belongs to; defaults to the current
/// one. Callers that delay the publish must capture it up front.
static Future<void> publishFromBlocState(
TimetableState state, {
required TimetableSubject subject,
Settings? settings,
bool showClassInsteadOfTeacher = false,
int? epoch,
}) {
final sessionEpoch = epoch ?? SessionManager().sessionEpoch;
return _queue = _queue.then(
(_) => _publish(
state,
subject: subject,
settings: settings,
showClassInsteadOfTeacher: showClassInsteadOfTeacher,
epoch: sessionEpoch,
),
);
}
static Future<void> _publish(
TimetableState state, {
required TimetableSubject subject,
required Settings? settings,
required bool showClassInsteadOfTeacher,
required int epoch,
}) async {
if (!SessionManager().isCurrentSession(epoch)) return;
try {
final connectDouble =
settings?.timetableSettings.connectDoubleLessons ?? true;
// Mirror into widget storage so the background isolate sees the same
// values the user just toggled — concurrently, they are independent.
await Future.wait([
WidgetSync.setConnectDoubleLessons(connectDouble),
WidgetSync.setThemeMode(_themeName(settings?.appTheme)),
WidgetSync.setShowClassInsteadOfTeacher(showClassInsteadOfTeacher),
WidgetSync.setSubject(subject),
]);
final flags = (
connectDouble,
_themeName(settings?.appTheme),
showClassInsteadOfTeacher,
WidgetSync.encodeSubject(subject),
);
if (flags != _lastFlags) {
await Future.wait([
WidgetSync.setConnectDoubleLessons(flags.$1),
WidgetSync.setThemeMode(flags.$2),
WidgetSync.setShowClassInsteadOfTeacher(flags.$3),
WidgetSync.setSubject(subject),
]);
_lastFlags = flags;
}
final lessons = state.getAllKnownLessons();
final now = widgetNow();
final dayData = WidgetDataMapper.buildDayData(
@@ -61,6 +115,23 @@ class WidgetPublisher {
connectDoubleLessons: connectDouble,
showClassInsteadOfTeacher: showClassInsteadOfTeacher,
);
// A publish still running at sign-out would put the previous account's
// plan back onto the just cleared widget.
if (!SessionManager().isCurrentSession(epoch)) return;
// Most bloc emits (week swipes, prefetches) don't touch the widget's
// window; skip the SharedPreferences commits and widget re-render then.
final signature = jsonEncode([
_withoutFetchedAt(dayData.toJson()),
_withoutFetchedAt(weekData.toJson()),
]);
final lastAt = _lastPublishedAt;
if (signature == _lastSignature &&
lastAt != null &&
now.difference(lastAt) < _republishAfter) {
return;
}
_lastSignature = signature;
_lastPublishedAt = now;
await WidgetSync.writeDayData(dayData);
await WidgetSync.writeWeekData(weekData);
await WidgetSync.setLoggedIn(true);
@@ -72,6 +143,9 @@ class WidgetPublisher {
}
}
static Map<String, Object?> _withoutFetchedAt(Map<String, Object?> json) =>
Map.of(json)..remove('fetchedAt');
static String _themeName(ThemeMode? mode) {
switch (mode) {
case ThemeMode.light:
+2
View File
@@ -6,6 +6,7 @@ import 'package:home_widget/home_widget.dart';
import '../state/app/modules/timetable/subject/timetable_subject.dart';
import 'widget_data.dart';
import 'widget_publisher.dart';
/// Bridge to the native widget host. All keys/names live here so the Kotlin
/// and Swift sides stay in sync.
@@ -146,6 +147,7 @@ class WidgetSync {
}
static Future<void> clear() async {
WidgetPublisher.resetDedupe();
await ensureInitialized();
await HomeWidget.saveWidgetData<String>(dayDataKey, null);
await HomeWidget.saveWidgetData<String>(weekDataKey, null);