Add multi calendar support and protected web app

This commit is contained in:
zsolt
2026-08-13 14:49:00 +02:00
parent 17e83c032a
commit a6403bf9bb
51 changed files with 220126 additions and 31 deletions
+101 -1
View File
@@ -72,6 +72,9 @@ class CalendarPage extends StatefulWidget {
required this.weather,
required this.onAdd,
required this.onEdit,
this.title,
this.secondaryEvents = const [],
this.appCalendars = const [],
this.onAssignPerson,
});
@@ -80,6 +83,9 @@ class CalendarPage extends StatefulWidget {
final WeatherSnapshot? weather;
final VoidCallback onAdd;
final ValueChanged<CalendarEvent> onEdit;
final String? title;
final List<CalendarEvent> secondaryEvents;
final List<AppCalendar> appCalendars;
final void Function(CalendarEvent event, Person person)? onAssignPerson;
@override
@@ -139,7 +145,7 @@ class _CalendarPageState extends State<CalendarPage> {
return PageFrame(
eyebrow:
'${formatDate(weekStart)} \u2013 ${formatDate(weekStart.add(const Duration(days: 6)))}',
title: '${isoWeekNumber(weekStart)}. h\u00e9t',
title: widget.title ?? '${isoWeekNumber(weekStart)}. h\u00e9t',
action: FilledButton.icon(
onPressed: widget.onAdd,
icon: const Icon(Icons.add),
@@ -184,6 +190,8 @@ class _CalendarPageState extends State<CalendarPage> {
onSelectDay: _selectDay,
onPreviousWeek: () => _moveWeek(-1),
onNextWeek: () => _moveWeek(1),
secondaryEvents: widget.secondaryEvents,
appCalendars: widget.appCalendars,
),
const SizedBox(height: 16),
DayDetailsPanel(
@@ -213,6 +221,8 @@ class _CalendarPageState extends State<CalendarPage> {
onSelectDay: _selectDay,
onPreviousWeek: () => _moveWeek(-1),
onNextWeek: () => _moveWeek(1),
secondaryEvents: widget.secondaryEvents,
appCalendars: widget.appCalendars,
),
const SizedBox(height: 14),
DayDetailsPanel(
@@ -666,6 +676,8 @@ class WeekBoard extends StatelessWidget {
required this.onSelectDay,
required this.onPreviousWeek,
required this.onNextWeek,
this.secondaryEvents = const [],
this.appCalendars = const [],
});
final List<CalendarEvent> events;
@@ -675,6 +687,8 @@ class WeekBoard extends StatelessWidget {
final ValueChanged<int> onSelectDay;
final VoidCallback onPreviousWeek;
final VoidCallback onNextWeek;
final List<CalendarEvent> secondaryEvents;
final List<AppCalendar> appCalendars;
static const days = [
'H\u00e9tf\u0151',
@@ -736,6 +750,9 @@ class WeekBoard extends StatelessWidget {
.where((event) => isSameDate(event.date, date))
.toList()
..sort((a, b) => a.start.compareTo(b.start));
final daySecondaryEvents = secondaryEvents
.where((event) => isSameDate(event.date, date))
.toList();
return Expanded(
child: Padding(
padding: EdgeInsets.only(right: index == 6 ? 0 : 6),
@@ -744,6 +761,8 @@ class WeekBoard extends StatelessWidget {
dateLabel: formatShortDate(date),
active: selectedDay == index,
events: dayEvents,
secondaryEvents: daySecondaryEvents,
appCalendars: appCalendars,
people: people,
onTap: () => onSelectDay(index),
),
@@ -765,6 +784,8 @@ class WeekDayColumn extends StatelessWidget {
required this.dateLabel,
required this.active,
required this.events,
required this.secondaryEvents,
required this.appCalendars,
required this.people,
required this.onTap,
});
@@ -773,6 +794,8 @@ class WeekDayColumn extends StatelessWidget {
final String dateLabel;
final bool active;
final List<CalendarEvent> events;
final List<CalendarEvent> secondaryEvents;
final List<AppCalendar> appCalendars;
final List<Person> people;
final VoidCallback onTap;
@@ -839,6 +862,13 @@ class WeekDayColumn extends StatelessWidget {
),
),
const SizedBox(height: 10),
if (secondaryEvents.isNotEmpty) ...[
CalendarDayIndicatorStrip(
events: secondaryEvents,
appCalendars: appCalendars,
),
const SizedBox(height: 8),
],
if (events.isEmpty)
const SizedBox(
height: 92,
@@ -954,6 +984,76 @@ class WeekEventPill extends StatelessWidget {
}
}
class CalendarDayIndicatorStrip extends StatelessWidget {
const CalendarDayIndicatorStrip({
super.key,
required this.events,
required this.appCalendars,
});
final List<CalendarEvent> events;
final List<AppCalendar> appCalendars;
@override
Widget build(BuildContext context) {
final calendarIds = events
.map((event) => event.appCalendarId)
.whereType<String>()
.toSet()
.toList();
return Wrap(
spacing: 4,
runSpacing: 4,
children: calendarIds.take(3).map((id) {
final calendar = appCalendars.firstWhere(
(item) => item.id == id,
orElse: () => AppCalendar(
id: id,
name: 'Napt\u00e1r',
colorValue: appCalendarColors.first.toARGB32(),
),
);
return Tooltip(
message:
'${calendar.name}: ${events.where((event) => event.appCalendarId == id).length} esem\u00e9ny',
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
decoration: BoxDecoration(
color: calendar.color.withValues(alpha: .14),
borderRadius: BorderRadius.circular(999),
border: Border.all(color: calendar.color.withValues(alpha: .22)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 6,
height: 6,
decoration: BoxDecoration(
color: calendar.color,
shape: BoxShape.circle,
),
),
const SizedBox(width: 3),
Text(
calendar.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: calendar.color,
fontSize: 9,
fontWeight: FontWeight.w900,
),
),
],
),
),
);
}).toList(),
);
}
}
class MiniAudienceAvatar extends StatelessWidget {
const MiniAudienceAvatar({
super.key,