implemented recurrence exception (EXDATE) support for custom events, refactored timetable break and holiday generation logic, and refined RRule editor UI/theming and tile layouts
This commit is contained in:
@@ -106,11 +106,10 @@ class _TileContent extends StatelessWidget {
|
||||
|
||||
if (isCustom) {
|
||||
if (description.isEmpty || bodyLineCapacity <= 0) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [titleWidget],
|
||||
);
|
||||
// Explicit height + FittedBox in the title keeps a too-tall
|
||||
// intrinsic title (full font size > min-font-line-height) from
|
||||
// overflowing the tile by a couple of pixels.
|
||||
return SizedBox(height: titleLineHeight, child: titleWidget);
|
||||
}
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
|
||||
@@ -22,36 +22,56 @@ class SpecialRegionsBuilder {
|
||||
});
|
||||
|
||||
List<TimeRegion> build() {
|
||||
final lastMonday = DateTime.now()
|
||||
final rangeStart = DateTime.now()
|
||||
.subtract(const Duration(days: 14))
|
||||
.nextWeekday(DateTime.monday);
|
||||
// Far enough out to cover comfortable scrolling without rebuilding;
|
||||
// Syncfusion only paints regions that intersect the visible window so
|
||||
// the extra entries don't hurt rendering cost.
|
||||
final rangeEnd = DateTime.now().add(const Duration(days: 180));
|
||||
|
||||
final holidayRegions = _buildHolidayRegions().toList();
|
||||
bool isInHoliday(DateTime time) =>
|
||||
holidayRegions.any((region) => region.startTime.isSameDay(time));
|
||||
final holidayDays = holidayRegions
|
||||
.map((r) => _dayKey(r.startTime))
|
||||
.toSet();
|
||||
|
||||
final breakRegions = schedule.periods
|
||||
.where((p) => p.isBreak)
|
||||
.map((p) {
|
||||
final start = lastMonday.copyWith(
|
||||
hour: p.start.hour,
|
||||
minute: p.start.minute,
|
||||
);
|
||||
return _breakRegion(start, p.duration);
|
||||
})
|
||||
.where((region) => !isInHoliday(region.startTime));
|
||||
// Materialise one TimeRegion per break per non-holiday day. Tried
|
||||
// `recurrenceRule: FREQ=DAILY` with `recurrenceExceptionDates` first,
|
||||
// but Syncfusion's recurrence exception matching is unreliable in
|
||||
// practice — the break overlay kept showing on holiday days. Generating
|
||||
// explicit per-day regions and just skipping holidays is robust.
|
||||
final breakPeriods = schedule.periods.where((p) => p.isBreak).toList();
|
||||
final breakRegions = <TimeRegion>[];
|
||||
for (
|
||||
var day = rangeStart;
|
||||
!day.isAfter(rangeEnd);
|
||||
day = day.add(const Duration(days: 1))
|
||||
) {
|
||||
if (holidayDays.contains(_dayKey(day))) continue;
|
||||
for (final p in breakPeriods) {
|
||||
final start = day.copyWith(
|
||||
hour: p.start.hour,
|
||||
minute: p.start.minute,
|
||||
);
|
||||
breakRegions.add(_breakRegion(start, p.duration));
|
||||
}
|
||||
}
|
||||
|
||||
return [...holidayRegions, ...breakRegions];
|
||||
}
|
||||
|
||||
static String _dayKey(DateTime d) => '${d.year}-${d.month}-${d.day}';
|
||||
|
||||
Iterable<TimeRegion> _buildHolidayRegions() => holidays.result.expand((
|
||||
holiday,
|
||||
) {
|
||||
final startDay = WebuntisTime.parse(holiday.startDate, 0);
|
||||
final dayCount = WebuntisTime.parse(
|
||||
holiday.endDate,
|
||||
0,
|
||||
).difference(startDay).inDays;
|
||||
final endDay = WebuntisTime.parse(holiday.endDate, 0);
|
||||
// Webuntis treats endDate inclusively (last day of the break) — the
|
||||
// `+ 1` covers single-day public holidays (where startDate == endDate)
|
||||
// and the final day of a multi-day vacation, both of which would
|
||||
// otherwise be skipped.
|
||||
final dayCount = endDay.difference(startDay).inDays + 1;
|
||||
final days = List<DateTime>.generate(
|
||||
dayCount,
|
||||
(i) => startDay.add(Duration(days: i)),
|
||||
@@ -66,7 +86,6 @@ class SpecialRegionsBuilder {
|
||||
endTime: day.copyWith(hour: gridEndHour, minute: gridEndMinute),
|
||||
text: '$kTimeRegionHolidayPrefix${holiday.name}',
|
||||
color: disabledColor.withAlpha(50),
|
||||
iconData: Icons.holiday_village_outlined,
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -74,7 +93,6 @@ class SpecialRegionsBuilder {
|
||||
TimeRegion _breakRegion(DateTime start, Duration duration) => TimeRegion(
|
||||
startTime: start,
|
||||
endTime: start.add(duration),
|
||||
recurrenceRule: 'FREQ=DAILY;INTERVAL=1',
|
||||
text: kTimeRegionCenterIcon,
|
||||
color: colorScheme.primary.withAlpha(50),
|
||||
iconData: Icons.restaurant,
|
||||
|
||||
@@ -33,9 +33,10 @@ class TimeRegionTile extends StatelessWidget {
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 15),
|
||||
const Icon(Icons.cake),
|
||||
const Text('FREI'),
|
||||
const SizedBox(height: 10),
|
||||
Icon(region.iconData ?? Icons.event_outlined),
|
||||
const SizedBox(height: 5),
|
||||
const Text('Schulfrei'),
|
||||
const SizedBox(height: 15),
|
||||
RotatedBox(
|
||||
quarterTurns: 1,
|
||||
child: Text(
|
||||
|
||||
Reference in New Issue
Block a user