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
+102 -6
View File
@@ -11,6 +11,7 @@ class _OtthonAppState extends State<OtthonApp> {
final store = AppJsonStore();
final weatherService = WeatherService();
List<Person> people = defaultPeople();
List<AppCalendar> appCalendars = defaultAppCalendars();
List<GoogleCalendarConnection> googleConnections = defaultGoogleConnections();
List<CalendarEvent> syncedEvents = [];
List<String> todoSuggestions = [];
@@ -38,6 +39,7 @@ class _OtthonAppState extends State<OtthonApp> {
if (!mounted) return;
setState(() {
people = settings.people;
appCalendars = settings.appCalendars;
googleConnections = settings.googleConnections;
syncedEvents = settings.syncedEvents;
todoSuggestions = settings.todoSuggestions;
@@ -54,6 +56,7 @@ class _OtthonAppState extends State<OtthonApp> {
await store.save(
AppSettings(
people: people,
appCalendars: appCalendars,
themeId: themeId,
languageCode: languageCode,
googleConnections: googleConnections,
@@ -98,6 +101,11 @@ class _OtthonAppState extends State<OtthonApp> {
_saveSettings();
}
void _updateAppCalendars(List<AppCalendar> nextCalendars) {
setState(() => appCalendars = nextCalendars);
_saveSettings();
}
void _updateTheme(String nextThemeId) {
setState(() => themeId = nextThemeId);
_saveSettings();
@@ -177,6 +185,7 @@ class _OtthonAppState extends State<OtthonApp> {
),
home: HomeShell(
people: people,
appCalendars: appCalendars,
googleConnections: googleConnections,
syncedEvents: syncedEvents,
todoSuggestions: todoSuggestions,
@@ -187,6 +196,7 @@ class _OtthonAppState extends State<OtthonApp> {
languageCode: languageCode,
loaded: loaded,
onPeopleChanged: _updatePeople,
onAppCalendarsChanged: _updateAppCalendars,
onThemeChanged: _updateTheme,
onLanguageChanged: _updateLanguage,
onGoogleConnectionsChanged: _updateGoogleConnections,
@@ -202,6 +212,7 @@ class HomeShell extends StatefulWidget {
const HomeShell({
super.key,
required this.people,
required this.appCalendars,
required this.googleConnections,
required this.syncedEvents,
required this.todoSuggestions,
@@ -212,6 +223,7 @@ class HomeShell extends StatefulWidget {
required this.languageCode,
required this.loaded,
required this.onPeopleChanged,
required this.onAppCalendarsChanged,
required this.onThemeChanged,
required this.onLanguageChanged,
required this.onGoogleConnectionsChanged,
@@ -221,6 +233,7 @@ class HomeShell extends StatefulWidget {
});
final List<Person> people;
final List<AppCalendar> appCalendars;
final List<GoogleCalendarConnection> googleConnections;
final List<CalendarEvent> syncedEvents;
final List<String> todoSuggestions;
@@ -231,6 +244,7 @@ class HomeShell extends StatefulWidget {
final String languageCode;
final bool loaded;
final ValueChanged<List<Person>> onPeopleChanged;
final ValueChanged<List<AppCalendar>> onAppCalendarsChanged;
final ValueChanged<String> onThemeChanged;
final ValueChanged<String> onLanguageChanged;
final ValueChanged<List<GoogleCalendarConnection>> onGoogleConnectionsChanged;
@@ -363,25 +377,51 @@ class _HomeShellState extends State<HomeShell> {
Widget build(BuildContext context) {
final wide = MediaQuery.sizeOf(context).width >= 850;
final text = AppText.of(widget.languageCode);
final mainCalendarEvents = allEvents
.where((event) => event.appCalendarId == null)
.toList();
final secondaryCalendarEvents = allEvents
.where((event) => event.appCalendarId != null)
.toList();
final labels = [
text.calendar,
...widget.appCalendars.map((calendar) => calendar.name),
text.shopping,
text.stock,
text.familyMembers,
text.style,
text.accounts,
];
final navIcons = [
icons.first,
...List.filled(widget.appCalendars.length, Icons.event_note_outlined),
...icons.skip(1),
];
final body = IndexedStack(
index: selected,
children: [
CalendarPage(
events: allEvents,
events: mainCalendarEvents,
people: widget.people,
weather: widget.weather,
onAdd: _addEvent,
secondaryEvents: secondaryCalendarEvents,
appCalendars: widget.appCalendars,
onAdd: () => _addEvent(),
onEdit: _editEvent,
onAssignPerson: _assignSyncedEventToPerson,
),
...widget.appCalendars.map(
(calendar) => CalendarPage(
title: calendar.name,
events: allEvents
.where((event) => event.appCalendarId == calendar.id)
.toList(),
people: widget.people,
weather: widget.weather,
onAdd: () => _addEvent(appCalendarId: calendar.id),
onEdit: _editEvent,
),
),
ShoppingPage(
items: shopping,
languageCode: widget.languageCode,
@@ -413,6 +453,7 @@ class _HomeShellState extends State<HomeShell> {
connections: widget.googleConnections,
cachedEvents: widget.syncedEvents,
people: widget.people,
appCalendars: widget.appCalendars,
jarvisSettings: widget.jarvisSettings,
serverClientId: widget.googleServerClientId,
languageCode: widget.languageCode,
@@ -517,6 +558,23 @@ class _HomeShellState extends State<HomeShell> {
],
),
),
IconButton(
tooltip: '\u00daj napt\u00e1r',
onPressed: _addAppCalendar,
icon: const Icon(
Icons.add_circle_outline,
size: 19,
),
style: IconButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: sage,
minimumSize: const Size(30, 30),
tapTargetSize:
MaterialTapTargetSize.shrinkWrap,
padding: EdgeInsets.zero,
),
),
const SizedBox(width: 4),
IconButton(
tooltip: text.jarvisSettings,
onPressed: _openJarvisSettings,
@@ -540,8 +598,8 @@ class _HomeShellState extends State<HomeShell> {
destinations: List.generate(
labels.length,
(i) => NavigationRailDestination(
icon: Icon(icons[i]),
selectedIcon: Icon(icons[i]),
icon: Icon(navIcons[i]),
selectedIcon: Icon(navIcons[i]),
label: Text(labels[i]),
),
),
@@ -728,7 +786,8 @@ class _HomeShellState extends State<HomeShell> {
destinations: List.generate(
labels.length,
(i) => NavigationDestination(
icon: Icon(icons[i]),
icon: Icon(navIcons[i]),
selectedIcon: Icon(navIcons[i]),
label: labels[i],
),
),
@@ -756,6 +815,30 @@ class _HomeShellState extends State<HomeShell> {
}
}
Future<void> _addAppCalendar() async {
final data = await showDialog<List<String>>(
context: context,
builder: (_) => const QuickAddDialog(
title: 'Napt\u00e1r hozz\u00e1ad\u00e1sa',
firstLabel: 'Napt\u00e1r neve',
secondLabel: 'Le\u00edr\u00e1s (opcion\u00e1lis)',
),
);
final name = data == null ? '' : data.first.trim();
if (name.isEmpty) return;
final color =
appCalendarColors[widget.appCalendars.length %
appCalendarColors.length];
final nextCalendar = AppCalendar(
id: uniqueId(),
name: name,
colorValue: color.toARGB32(),
);
widget.onAppCalendarsChanged([...widget.appCalendars, nextCalendar]);
setState(() => selected = widget.appCalendars.length + 1);
}
Future<void> _openJarvisSettings() async {
final result = await showDialog<JarvisSettings>(
context: context,
@@ -963,11 +1046,12 @@ class _HomeShellState extends State<HomeShell> {
);
}
Future<void> _addEvent() async {
Future<void> _addEvent({String? appCalendarId}) async {
final data = await showDialog<EventInput>(
context: context,
builder: (_) => EventAddDialog(
people: widget.people,
appCalendars: widget.appCalendars,
todoSuggestions: widget.todoSuggestions,
),
);
@@ -986,6 +1070,7 @@ class _HomeShellState extends State<HomeShell> {
.color,
personId: assignedPersonId,
date: data.date,
appCalendarId: data.appCalendarId ?? appCalendarId,
note: data.note,
hasTodos: data.hasTodos,
todos: data.todos,
@@ -1001,6 +1086,7 @@ class _HomeShellState extends State<HomeShell> {
context: context,
builder: (_) => EventAddDialog(
people: widget.people,
appCalendars: widget.appCalendars,
event: event,
todoSuggestions: widget.todoSuggestions,
),
@@ -1017,6 +1103,8 @@ class _HomeShellState extends State<HomeShell> {
date: data.date,
color: color,
personId: assignedPersonId,
appCalendarId: data.appCalendarId,
clearAppCalendar: data.appCalendarId == null,
note: data.note,
hasTodos: data.hasTodos,
todos: data.todos,
@@ -1032,6 +1120,14 @@ class _HomeShellState extends State<HomeShell> {
});
if (event.isSynced) {
if (widget.googleConnections.isNotEmpty) {
final currentConnection = widget.googleConnections.first;
final nextEventPersonIds = {...currentConnection.eventPersonIds};
nextEventPersonIds[importedEventKey(event)] = assignedPersonId;
widget.onGoogleConnectionsChanged([
currentConnection.copyWith(eventPersonIds: nextEventPersonIds),
]);
}
final nextSyncedEvents = [
for (final syncedEvent in widget.syncedEvents)
if (identical(syncedEvent, event)) updated else syncedEvent,
+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,
+72
View File
@@ -41,6 +41,15 @@ const personColors = [
Color(0xFF6A9F58),
];
const appCalendarColors = [
Color(0xFF4C9BCF),
Color(0xFFD6A74D),
Color(0xFFD65A6F),
Color(0xFF9B78B4),
Color(0xFF6A9F58),
Color(0xFFE89552),
];
class AvatarPreset {
const AvatarPreset(this.id, this.label, this.symbol);
@@ -121,6 +130,7 @@ class AppJsonStore {
if (!await file.exists()) {
final settings = AppSettings(
people: defaultPeople(),
appCalendars: defaultAppCalendars(),
themeId: 'sage',
languageCode: defaultLanguageCode,
googleConnections: defaultGoogleConnections(),
@@ -139,6 +149,7 @@ class AppJsonStore {
} catch (_) {
return AppSettings(
people: defaultPeople(),
appCalendars: defaultAppCalendars(),
themeId: 'sage',
languageCode: defaultLanguageCode,
googleConnections: defaultGoogleConnections(),
@@ -161,6 +172,7 @@ class AppJsonStore {
class AppSettings {
AppSettings({
required this.people,
required this.appCalendars,
required this.themeId,
required this.languageCode,
required this.googleConnections,
@@ -171,6 +183,7 @@ class AppSettings {
});
final List<Person> people;
final List<AppCalendar> appCalendars;
final String themeId;
final String languageCode;
final List<GoogleCalendarConnection> googleConnections;
@@ -181,6 +194,7 @@ class AppSettings {
factory AppSettings.fromJson(Map<String, dynamic> json) {
final rawPeople = json['people'];
final rawAppCalendars = json['appCalendars'];
final rawConnections = json['googleConnections'];
final rawSyncedEvents = json['syncedEvents'];
final rawTodoSuggestions = json['todoSuggestions'];
@@ -194,6 +208,12 @@ class AppSettings {
.toList(),
)
: defaultPeople(),
appCalendars: rawAppCalendars is List
? rawAppCalendars
.whereType<Map<String, dynamic>>()
.map(AppCalendar.fromJson)
.toList()
: defaultAppCalendars(),
themeId: json['themeId'] as String? ?? 'sage',
languageCode: json['languageCode'] == 'en' ? 'en' : defaultLanguageCode,
googleConnections: rawConnections is List
@@ -223,6 +243,7 @@ class AppSettings {
Map<String, dynamic> toJson() => {
'themeId': themeId,
'appCalendars': appCalendars.map((calendar) => calendar.toJson()).toList(),
'languageCode': languageCode,
'people': people.map((person) => person.toJson()).toList(),
'googleConnections': googleConnections
@@ -310,6 +331,7 @@ class GoogleCalendarConnection {
required this.accountEmail,
required this.enabledCalendarIds,
this.calendarPersonIds = const {},
this.calendarAppCalendarIds = const {},
this.eventPersonIds = const {},
required this.lastSync,
});
@@ -318,6 +340,7 @@ class GoogleCalendarConnection {
final String accountEmail;
final List<String> enabledCalendarIds;
final Map<String, String> calendarPersonIds;
final Map<String, String> calendarAppCalendarIds;
final Map<String, String> eventPersonIds;
final DateTime? lastSync;
@@ -325,6 +348,7 @@ class GoogleCalendarConnection {
String? accountEmail,
List<String>? enabledCalendarIds,
Map<String, String>? calendarPersonIds,
Map<String, String>? calendarAppCalendarIds,
Map<String, String>? eventPersonIds,
DateTime? lastSync,
}) {
@@ -333,6 +357,8 @@ class GoogleCalendarConnection {
accountEmail: accountEmail ?? this.accountEmail,
enabledCalendarIds: enabledCalendarIds ?? this.enabledCalendarIds,
calendarPersonIds: calendarPersonIds ?? this.calendarPersonIds,
calendarAppCalendarIds:
calendarAppCalendarIds ?? this.calendarAppCalendarIds,
eventPersonIds: eventPersonIds ?? this.eventPersonIds,
lastSync: lastSync ?? this.lastSync,
);
@@ -341,6 +367,7 @@ class GoogleCalendarConnection {
factory GoogleCalendarConnection.fromJson(Map<String, dynamic> json) {
final rawCalendarIds = json['enabledCalendarIds'];
final rawCalendarPersonIds = json['calendarPersonIds'];
final rawCalendarAppCalendarIds = json['calendarAppCalendarIds'];
final rawEventPersonIds = json['eventPersonIds'];
return GoogleCalendarConnection(
id: json['id'] as String? ?? uniqueId(),
@@ -353,6 +380,11 @@ class GoogleCalendarConnection {
(key, value) => MapEntry(key.toString(), value.toString()),
)
: const {},
calendarAppCalendarIds: rawCalendarAppCalendarIds is Map
? rawCalendarAppCalendarIds.map(
(key, value) => MapEntry(key.toString(), value.toString()),
)
: const {},
eventPersonIds: rawEventPersonIds is Map
? rawEventPersonIds.map(
(key, value) => MapEntry(key.toString(), value.toString()),
@@ -367,11 +399,41 @@ class GoogleCalendarConnection {
'accountEmail': accountEmail,
'enabledCalendarIds': enabledCalendarIds,
'calendarPersonIds': calendarPersonIds,
'calendarAppCalendarIds': calendarAppCalendarIds,
'eventPersonIds': eventPersonIds,
'lastSync': lastSync?.toIso8601String(),
};
}
class AppCalendar {
const AppCalendar({
required this.id,
required this.name,
required this.colorValue,
});
final String id;
final String name;
final int colorValue;
Color get color => Color(colorValue);
factory AppCalendar.fromJson(Map<String, dynamic> json) => AppCalendar(
id: json['id'] as String? ?? uniqueId(),
name: json['name'] as String? ?? 'Napt\u00e1r',
colorValue:
json['colorValue'] as int? ?? appCalendarColors.first.toARGB32(),
);
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'colorValue': colorValue,
};
}
List<AppCalendar> defaultAppCalendars() => [];
class GoogleCalendarInfo {
const GoogleCalendarInfo({
required this.id,
@@ -478,6 +540,7 @@ class CalendarEvent {
this.externalId,
this.calendarId,
this.calendarName,
this.appCalendarId,
this.note = '',
this.hasTodos = false,
this.todos = const [],
@@ -494,6 +557,7 @@ class CalendarEvent {
final String? externalId;
final String? calendarId;
final String? calendarName;
final String? appCalendarId;
final String note;
final bool hasTodos;
final List<String> todos;
@@ -514,7 +578,9 @@ class CalendarEvent {
String? note,
bool? hasTodos,
List<String>? todos,
String? appCalendarId,
bool clearPerson = false,
bool clearAppCalendar = false,
}) {
return CalendarEvent(
title ?? this.title,
@@ -527,6 +593,9 @@ class CalendarEvent {
externalId: externalId,
calendarId: calendarId,
calendarName: calendarName,
appCalendarId: clearAppCalendar
? null
: appCalendarId ?? this.appCalendarId,
note: note ?? this.note,
hasTodos: hasTodos ?? this.hasTodos,
todos: todos ?? this.todos,
@@ -546,6 +615,7 @@ class CalendarEvent {
externalId: json['externalId'] as String?,
calendarId: json['calendarId'] as String?,
calendarName: json['calendarName'] as String?,
appCalendarId: json['appCalendarId'] as String?,
note: json['note'] as String? ?? '',
hasTodos: json['hasTodos'] as bool? ?? false,
todos: json['todos'] is List
@@ -586,6 +656,7 @@ class CalendarEvent {
externalId: event['id'] as String?,
calendarId: calendar.id,
calendarName: calendar.name,
appCalendarId: null,
personId: personId,
note: description ?? '',
source: EventSource.googleCalendar,
@@ -603,6 +674,7 @@ class CalendarEvent {
'externalId': externalId,
'calendarId': calendarId,
'calendarName': calendarName,
'appCalendarId': appCalendarId,
'note': note,
'hasTodos': hasTodos,
'todos': todos,
+29
View File
@@ -166,6 +166,7 @@ class EventInput {
required this.start,
required this.date,
required this.personId,
required this.appCalendarId,
required this.note,
required this.hasTodos,
required this.todos,
@@ -175,6 +176,7 @@ class EventInput {
final String start;
final DateTime date;
final String? personId;
final String? appCalendarId;
final String note;
final bool hasTodos;
final List<String> todos;
@@ -184,11 +186,13 @@ class EventAddDialog extends StatefulWidget {
const EventAddDialog({
super.key,
required this.people,
required this.appCalendars,
this.event,
this.todoSuggestions = const [],
});
final List<Person> people;
final List<AppCalendar> appCalendars;
final CalendarEvent? event;
final List<String> todoSuggestions;
@@ -205,6 +209,7 @@ class _EventAddDialogState extends State<EventAddDialog> {
late bool hasTodos;
late List<String> todos;
String? personId;
String? appCalendarId;
@override
void initState() {
@@ -219,6 +224,7 @@ class _EventAddDialogState extends State<EventAddDialog> {
hasTodos = event?.hasTodos ?? false;
todos = [...event?.todos ?? const <String>[]];
personId = event?.personId == everyonePersonId ? null : event?.personId;
appCalendarId = event?.appCalendarId;
}
@override
@@ -303,6 +309,28 @@ class _EventAddDialogState extends State<EventAddDialog> {
],
onChanged: (value) => setState(() => personId = value),
),
if (widget.appCalendars.isNotEmpty) ...[
const SizedBox(height: 12),
DropdownButtonFormField<String?>(
initialValue: appCalendarId,
decoration: const InputDecoration(
labelText: 'Melyik napt\u00e1rban legyen?',
),
items: [
const DropdownMenuItem<String?>(
value: null,
child: Text('F\u0151 csal\u00e1di napt\u00e1r'),
),
...widget.appCalendars.map(
(calendar) => DropdownMenuItem<String?>(
value: calendar.id,
child: Text(calendar.name),
),
),
],
onChanged: (value) => setState(() => appCalendarId = value),
),
],
const SizedBox(height: 12),
TextField(
controller: note,
@@ -503,6 +531,7 @@ class _EventAddDialogState extends State<EventAddDialog> {
start: start.text,
date: eventDate,
personId: personId,
appCalendarId: appCalendarId,
note: note.text,
hasTodos: cleanTodos.isNotEmpty,
todos: cleanTodos,
+88 -24
View File
@@ -6,6 +6,7 @@ class AccountsPage extends StatefulWidget {
required this.connections,
required this.cachedEvents,
required this.people,
required this.appCalendars,
required this.jarvisSettings,
required this.serverClientId,
required this.languageCode,
@@ -16,6 +17,7 @@ class AccountsPage extends StatefulWidget {
final List<GoogleCalendarConnection> connections;
final List<CalendarEvent> cachedEvents;
final List<Person> people;
final List<AppCalendar> appCalendars;
final JarvisSettings jarvisSettings;
final String serverClientId;
final String languageCode;
@@ -180,6 +182,7 @@ class _AccountsPageState extends State<AccountsPage>
accountEmail: email ?? current?.accountEmail ?? 'Google fi\u00f3k',
enabledCalendarIds: current?.enabledCalendarIds ?? const [],
calendarPersonIds: current?.calendarPersonIds ?? const {},
calendarAppCalendarIds: current?.calendarAppCalendarIds ?? const {},
eventPersonIds: current?.eventPersonIds ?? const {},
lastSync: current?.lastSync,
),
@@ -300,6 +303,7 @@ class _AccountsPageState extends State<AccountsPage>
accountEmail: 'Google Calendar',
enabledCalendarIds: const [],
calendarPersonIds: const {},
calendarAppCalendarIds: const {},
eventPersonIds: const {},
lastSync: null,
),
@@ -357,10 +361,8 @@ class _AccountsPageState extends State<AccountsPage>
final defaultPersonId =
current.calendarPersonIds[cachedEvent.calendarId];
if (cachedEvent.personId == defaultPersonId) continue;
eventPersonOverrides.putIfAbsent(
importedEventKey(cachedEvent),
() => cachedEvent.personId!,
);
eventPersonOverrides[importedEventKey(cachedEvent)] =
cachedEvent.personId!;
}
final now = DateTime.now();
final start = DateTime(
@@ -399,7 +401,12 @@ class _AccountsPageState extends State<AccountsPage>
final assignedPersonId =
eventPersonOverrides[eventKey] ??
current.calendarPersonIds[calendar.id];
synced.add(imported.copyWith(personId: assignedPersonId));
synced.add(
imported.copyWith(
personId: assignedPersonId,
appCalendarId: current.calendarAppCalendarIds[calendar.id],
),
);
}
}
@@ -479,10 +486,16 @@ class _AccountsPageState extends State<AccountsPage>
calendar: calendar,
enabled: current.enabledCalendarIds.contains(calendar.id),
people: widget.people,
appCalendars: widget.appCalendars,
selectedPersonId: current.calendarPersonIds[calendar.id],
selectedAppCalendarId:
current.calendarAppCalendarIds[calendar.id],
onChanged: (enabled) {
final nextIds = [...current.enabledCalendarIds];
final nextPersonIds = {...current.calendarPersonIds};
final nextAppCalendarIds = {
...current.calendarAppCalendarIds,
};
if (enabled) {
if (!nextIds.contains(calendar.id)) {
nextIds.add(calendar.id);
@@ -490,11 +503,13 @@ class _AccountsPageState extends State<AccountsPage>
} else {
nextIds.remove(calendar.id);
nextPersonIds.remove(calendar.id);
nextAppCalendarIds.remove(calendar.id);
}
widget.onChanged([
current.copyWith(
enabledCalendarIds: nextIds,
calendarPersonIds: nextPersonIds,
calendarAppCalendarIds: nextAppCalendarIds,
lastSync: current.lastSync,
),
]);
@@ -513,6 +528,22 @@ class _AccountsPageState extends State<AccountsPage>
),
]);
},
onAppCalendarChanged: (appCalendarId) {
final nextAppCalendarIds = {
...current.calendarAppCalendarIds,
};
if (appCalendarId == null) {
nextAppCalendarIds.remove(calendar.id);
} else {
nextAppCalendarIds[calendar.id] = appCalendarId;
}
widget.onChanged([
current.copyWith(
calendarAppCalendarIds: nextAppCalendarIds,
lastSync: current.lastSync,
),
]);
},
),
),
const SizedBox(height: 16),
@@ -694,17 +725,23 @@ class GoogleCalendarToggleCard extends StatelessWidget {
required this.calendar,
required this.enabled,
required this.people,
required this.appCalendars,
required this.selectedPersonId,
required this.selectedAppCalendarId,
required this.onChanged,
required this.onPersonChanged,
required this.onAppCalendarChanged,
});
final GoogleCalendarInfo calendar;
final bool enabled;
final List<Person> people;
final List<AppCalendar> appCalendars;
final String? selectedPersonId;
final String? selectedAppCalendarId;
final ValueChanged<bool> onChanged;
final ValueChanged<String?> onPersonChanged;
final ValueChanged<String?> onAppCalendarChanged;
@override
Widget build(BuildContext context) {
@@ -738,28 +775,55 @@ class GoogleCalendarToggleCard extends StatelessWidget {
if (enabled)
Padding(
padding: const EdgeInsets.fromLTRB(72, 0, 16, 0),
child: DropdownButtonFormField<String?>(
initialValue: selectedPersonId,
decoration: const InputDecoration(
labelText: 'Kihez tartozzanak az esem\u00e9nyek?',
),
items: [
const DropdownMenuItem<String?>(
value: null,
child: Text('Kategoriz\u00e1latlan'),
),
const DropdownMenuItem<String?>(
value: everyonePersonId,
child: Text('Mindenki'),
),
...people.map(
(person) => DropdownMenuItem<String?>(
value: person.id,
child: Text('${person.avatar} ${person.name}'),
child: Column(
children: [
DropdownButtonFormField<String?>(
initialValue: selectedPersonId,
decoration: const InputDecoration(
labelText: 'Kihez tartozzanak az esem\u00e9nyek?',
),
items: [
const DropdownMenuItem<String?>(
value: null,
child: Text('Kategoriz\u00e1latlan'),
),
const DropdownMenuItem<String?>(
value: everyonePersonId,
child: Text('Mindenki'),
),
...people.map(
(person) => DropdownMenuItem<String?>(
value: person.id,
child: Text('${person.avatar} ${person.name}'),
),
),
],
onChanged: onPersonChanged,
),
if (appCalendars.isNotEmpty) ...[
const SizedBox(height: 10),
DropdownButtonFormField<String?>(
initialValue: selectedAppCalendarId,
decoration: const InputDecoration(
labelText:
'Melyik K\u00f6z\u00f6sT\u00e9r napt\u00e1rba import\u00e1ljon?',
),
items: [
const DropdownMenuItem<String?>(
value: null,
child: Text('F\u0151 csal\u00e1di napt\u00e1r'),
),
...appCalendars.map(
(calendar) => DropdownMenuItem<String?>(
value: calendar.id,
child: Text(calendar.name),
),
),
],
onChanged: onAppCalendarChanged,
),
],
],
onChanged: onPersonChanged,
),
),
],