refactored data providers with centralized cache resolution, unified UI using custom dialogs and bottom sheets, and enhanced network error handling for Dio and TLS errors

This commit is contained in:
2026-05-08 20:01:45 +02:00
parent c62a14645a
commit 9e139b5704
37 changed files with 595 additions and 753 deletions
@@ -3,14 +3,22 @@ const double kCalendarEndHour = 17.25;
const Duration kCalendarTimeInterval = Duration(minutes: 30);
const double kCalendarViewHeaderHeight = 60;
/// Minimum pixels per hour. Below this, the grid scrolls vertically rather
/// than compressing further.
/// Below this, the grid scrolls vertically rather than compressing further.
const double kCalendarMinPxPerHour = 56;
/// Minimum height of a lesson block in the period-based layout. The grid
/// scrolls vertically once lessons would otherwise be smaller than this.
/// The grid scrolls vertically once lessons would otherwise be smaller.
const double kLessonBlockMinHeight = 50;
/// Fixed height of a break block in the period-based layout. Independent of
/// the actual break duration; breaks are rendered as a compact indicator.
/// Fixed (independent of actual break duration); breaks render as a compact
/// indicator.
const double kBreakBlockHeight = 28;
const int kOutsideChipsMaxVisible = 2;
const double kOutsideChipHeight = 22;
const double kOutsideChipSpacing = 3;
const double kOutsideStripVerticalPadding = 3;
const double kAppointmentTitleFontSize = 15;
const double kAppointmentTitleMinFontSize = 11;
const double kAppointmentBodyFontSize = 10;
const double kAppointmentBodyLineHeight = 1.15;
@@ -2,14 +2,11 @@ import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_calendar/calendar.dart';
import '../data/arbitrary_appointment.dart';
import '../data/calendar_layout.dart';
import 'cross_painter.dart';
class AppointmentTile extends StatelessWidget {
static const _radius = BorderRadius.all(Radius.circular(7));
static const _titleFontSize = 15.0;
static const _titleMinFontSize = 11.0;
static const _bodyFontSize = 10.0;
static const _bodyLineHeight = 1.15;
final Appointment appointment;
final bool crossedOut;
@@ -42,8 +39,8 @@ class AppointmentTile extends StatelessWidget {
children: [
_AdaptiveTitle(
text: appointment.subject,
fontSize: _titleFontSize,
minFontSize: _titleMinFontSize,
fontSize: kAppointmentTitleFontSize,
minFontSize: kAppointmentTitleMinFontSize,
fontWeight: FontWeight.w500,
),
if (isCustom) ...[
@@ -53,8 +50,8 @@ class AppointmentTile extends StatelessWidget {
padding: const EdgeInsets.only(top: 1),
child: _WrappingBody(
text: description,
fontSize: _bodyFontSize,
lineHeight: _bodyLineHeight,
fontSize: kAppointmentBodyFontSize,
lineHeight: kAppointmentBodyLineHeight,
),
),
),
@@ -63,7 +60,7 @@ class AppointmentTile extends StatelessWidget {
.split('\n')
.where((p) => p.isNotEmpty)
.take(2))
_ScaledLine(text: line, fontSize: _bodyFontSize),
_ScaledLine(text: line, fontSize: kAppointmentBodyFontSize),
],
],
),
@@ -1,11 +1,6 @@
part of '../custom_workweek_calendar.dart';
class _OutsideHoursStrip extends StatelessWidget {
static const int _maxVisibleChips = 2;
static const double _chipHeight = 22;
static const double _chipSpacing = 3;
static const double _verticalPadding = 3;
final DateTime weekStart;
final List<Appointment> appointments;
final double rulerWidth;
@@ -28,17 +23,17 @@ class _OutsideHoursStrip extends StatelessWidget {
final theme = Theme.of(context);
final maxChipsPerDay = outside
.map((day) => day.length > _maxVisibleChips ? _maxVisibleChips : day.length)
.map((day) => day.length > kOutsideChipsMaxVisible ? kOutsideChipsMaxVisible : day.length)
.fold<int>(0, (m, c) => c > m ? c : m);
final stripHeight = _verticalPadding * 2 +
maxChipsPerDay * _chipHeight +
(maxChipsPerDay > 1 ? (maxChipsPerDay - 1) * _chipSpacing : 0);
final stripHeight = kOutsideStripVerticalPadding * 2 +
maxChipsPerDay * kOutsideChipHeight +
(maxChipsPerDay > 1 ? (maxChipsPerDay - 1) * kOutsideChipSpacing : 0);
return Container(
color: theme.colorScheme.surfaceContainerLowest,
padding: const EdgeInsets.symmetric(vertical: _verticalPadding),
padding: const EdgeInsets.symmetric(vertical: kOutsideStripVerticalPadding),
child: SizedBox(
height: stripHeight - _verticalPadding * 2,
height: stripHeight - kOutsideStripVerticalPadding * 2,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -47,9 +42,6 @@ class _OutsideHoursStrip extends StatelessWidget {
Expanded(
child: _OutsideDayColumn(
appointments: outside[d],
maxVisible: _maxVisibleChips,
chipHeight: _chipHeight,
chipSpacing: _chipSpacing,
onAppointmentTap: onAppointmentTap,
isCrossedOut: isCrossedOut,
),
@@ -63,17 +55,11 @@ class _OutsideHoursStrip extends StatelessWidget {
class _OutsideDayColumn extends StatelessWidget {
final List<Appointment> appointments;
final int maxVisible;
final double chipHeight;
final double chipSpacing;
final void Function(Appointment) onAppointmentTap;
final bool Function(Appointment) isCrossedOut;
const _OutsideDayColumn({
required this.appointments,
required this.maxVisible,
required this.chipHeight,
required this.chipSpacing,
required this.onAppointmentTap,
required this.isCrossedOut,
});
@@ -132,11 +118,12 @@ class _OutsideDayColumn extends StatelessWidget {
if (!aLike && bLike) return 1;
return a.startTime.compareTo(b.startTime);
});
final visible = sorted.length <= maxVisible
final visible = sorted.length <= kOutsideChipsMaxVisible
? sorted
: sorted.take(maxVisible - 1).toList();
final overflow =
sorted.length <= maxVisible ? const <Appointment>[] : sorted.skip(maxVisible - 1).toList();
: sorted.take(kOutsideChipsMaxVisible - 1).toList();
final overflow = sorted.length <= kOutsideChipsMaxVisible
? const <Appointment>[]
: sorted.skip(kOutsideChipsMaxVisible - 1).toList();
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 2),
@@ -145,9 +132,9 @@ class _OutsideDayColumn extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
for (var i = 0; i < visible.length; i++) ...[
if (i > 0) SizedBox(height: chipSpacing),
if (i > 0) const SizedBox(height: kOutsideChipSpacing),
SizedBox(
height: chipHeight,
height: kOutsideChipHeight,
child: _OutsideChip(
appointment: visible[i],
onTap: () => onAppointmentTap(visible[i]),
@@ -155,9 +142,9 @@ class _OutsideDayColumn extends StatelessWidget {
),
],
if (overflow.isNotEmpty) ...[
SizedBox(height: chipSpacing),
const SizedBox(height: kOutsideChipSpacing),
SizedBox(
height: chipHeight,
height: kOutsideChipHeight,
child: _OutsideOverflowChip(
count: overflow.length,
onTap: () => _showOverflow(context, overflow),
@@ -429,8 +429,7 @@ class _OverflowTile extends StatelessWidget {
padding: const EdgeInsets.all(1),
child: Stack(
children: [
// Card peeking out at the bottom — visual hint that more cards lie
// underneath the visible one.
// Stacked-cards effect: a darker layer peeks out below the front card.
Positioned(
top: 4,
left: 2,
@@ -443,7 +442,6 @@ class _OverflowTile extends StatelessWidget {
),
),
),
// Front card with the "+N" indicator.
Positioned(
top: 0,
left: 0,