Add multi calendar support and protected web app
This commit is contained in:
+102
-6
@@ -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
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user