// ignore_for_file: no_leading_underscores_for_local_identifiers import 'dart:async'; import 'dart:io'; import 'package:filcnaplo/helpers/subject.dart'; import 'package:filcnaplo/models/settings.dart'; import 'package:filcnaplo_kreta_api/models/lesson.dart'; import 'package:filcnaplo_kreta_api/models/week.dart'; import 'package:filcnaplo/utils/format.dart'; import 'package:filcnaplo_kreta_api/providers/timetable_provider.dart'; import 'package:flutter/foundation.dart'; import 'package:live_activities/live_activities.dart'; import 'package:filcnaplo_mobile_ui/pages/home/live_card/live_card.i18n.dart'; enum LiveCardState { empty, duringLesson, duringBreak, morning, afternoon, night, summary } class LiveCardProvider extends ChangeNotifier { Lesson? currentLesson; Lesson? nextLesson; Lesson? prevLesson; List? nextLessons; LiveCardState currentState = LiveCardState.empty; late Timer _timer; late final TimetableProvider _timetable; late final SettingsProvider _settings; late Duration _delay; final _liveActivitiesPlugin = LiveActivities(); String? _latestActivityId; Map _lastActivity = {}; bool _hasCheckedTimetable = false; LiveCardProvider({ required TimetableProvider timetable, required SettingsProvider settings, }) : _timetable = timetable, _settings = settings { // Check if live card is enabled .areActivitiesEnabled() _liveActivitiesPlugin.areActivitiesEnabled().then((value) { // Console log if (kDebugMode) { print("Live card enabled: $value"); } if (value) { _liveActivitiesPlugin.init(appGroupId: "group.refilc.livecard"); _liveActivitiesPlugin.getAllActivitiesIds().then((value) { _latestActivityId = value.isNotEmpty ? value.first : null; }); } }); _timer = Timer.periodic(const Duration(seconds: 1), (timer) => update()); _delay = settings.bellDelayEnabled ? Duration(seconds: settings.bellDelay) : Duration.zero; update(); } @override void dispose() { _timer.cancel(); if (Platform.isIOS) { _liveActivitiesPlugin.areActivitiesEnabled().then((value) { if (value) { if (_latestActivityId != null) { _liveActivitiesPlugin.endActivity(_latestActivityId!); } } }); } super.dispose(); } // Debugging static DateTime _now() { return DateTime.now(); } String getFloorDifference() { final prevFloor = prevLesson!.getFloor(); final nextFloor = nextLesson!.getFloor(); if (prevFloor == null || nextFloor == null || prevFloor == nextFloor) { return "to room"; } if (nextFloor == 0) { return "ground floor"; } if (nextFloor > prevFloor) { return "up floor"; } else { return "down floor"; } } Map toMap() { switch (currentState) { case LiveCardState.duringLesson: return { "color": _settings.liveActivityColor.toString(), "icon": currentLesson != null ? SubjectIcon.resolveName(subject: currentLesson?.subject) : "book", "index": currentLesson != null ? '${currentLesson!.lessonIndex}. ' : "", "title": currentLesson != null ? ShortSubject.resolve(subject: currentLesson?.subject).capital() : "", "subtitle": currentLesson?.room.replaceAll("_", " ") ?? "", "description": currentLesson?.description ?? "", "startDate": ((currentLesson?.start.millisecondsSinceEpoch ?? 0) - _delay.inMilliseconds) .toString(), "endDate": ((currentLesson?.end.millisecondsSinceEpoch ?? 0) - _delay.inMilliseconds) .toString(), "nextSubject": nextLesson != null ? ShortSubject.resolve(subject: nextLesson?.subject).capital() : "", "nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "", }; case LiveCardState.duringBreak: final iconFloorMap = { "to room": "chevron.right.2", "up floor": "arrow.up.right", "down floor": "arrow.down.left", "ground floor": "arrow.down.left", }; final diff = getFloorDifference(); return { "color": _settings.liveActivityColor.toString(), "icon": iconFloorMap[diff] ?? "cup.and.saucer", "title": "Szünet", "description": "go $diff".i18n.fill([ diff != "to room" ? (nextLesson!.getFloor() ?? 0) : nextLesson!.room ]), "startDate": ((prevLesson?.end.millisecondsSinceEpoch ?? 0) - _delay.inMilliseconds) .toString(), "endDate": ((nextLesson?.start.millisecondsSinceEpoch ?? 0) - _delay.inMilliseconds) .toString(), "nextSubject": (nextLesson != null ? ShortSubject.resolve(subject: nextLesson?.subject) : "") .capital(), "nextRoom": nextLesson?.room.replaceAll("_", " ") ?? "", "index": "", "subtitle": "", }; default: return {}; } } void update() async { if (Platform.isIOS) { _liveActivitiesPlugin.areActivitiesEnabled().then((value) { if (value) { final cmap = toMap(); if (!mapEquals(cmap, _lastActivity)) { _lastActivity = cmap; try { if (_lastActivity.isNotEmpty) { if (_latestActivityId == null) { _liveActivitiesPlugin .createActivity(_lastActivity) .then((value) => _latestActivityId = value); } else { _liveActivitiesPlugin.updateActivity( _latestActivityId!, _lastActivity); } } else { if (_latestActivityId != null) { _liveActivitiesPlugin.endActivity(_latestActivityId!); } } } catch (e) { if (kDebugMode) { print('ERROR: Unable to create or update iOS LiveCard!'); } } } } }); } List today = _today(_timetable); if (today.isEmpty && !_hasCheckedTimetable) { _hasCheckedTimetable = true; await _timetable.fetch(week: Week.current()); today = _today(_timetable); } _delay = _settings.bellDelayEnabled ? Duration(seconds: _settings.bellDelay) : Duration.zero; final now = _now().add(_delay); // Filter cancelled lessons #20 // Filter label lessons #128 today = today .where((lesson) => lesson.status?.name != "Elmaradt" && lesson.subject.id != '' && !lesson.isEmpty) .toList(); if (today.isNotEmpty) { // sort today.sort((a, b) => a.start.compareTo(b.start)); final _lesson = today.firstWhere( (l) => l.start.isBefore(now) && l.end.isAfter(now), orElse: () => Lesson.fromJson({})); if (_lesson.start.year != 0) { currentLesson = _lesson; } else { currentLesson = null; } final _next = today.firstWhere((l) => l.start.isAfter(now), orElse: () => Lesson.fromJson({})); nextLessons = today.where((l) => l.start.isAfter(now)).toList(); if (_next.start.year != 0) { nextLesson = _next; } else { nextLesson = null; } final _prev = today.lastWhere((l) => l.end.isBefore(now), orElse: () => Lesson.fromJson({})); if (_prev.start.year != 0) { prevLesson = _prev; } else { prevLesson = null; } } if (now.isBefore(DateTime(now.year, DateTime.august, 31)) && now.isAfter(DateTime(now.year, DateTime.june, 14))) { currentState = LiveCardState.summary; } else if (currentLesson != null) { currentState = LiveCardState.duringLesson; } else if (nextLesson != null && prevLesson != null) { currentState = LiveCardState.duringBreak; } else if (now.hour >= 12 && now.hour < 20) { currentState = LiveCardState.afternoon; } else if (now.hour >= 20) { currentState = LiveCardState.night; } else if (now.hour >= 5 && now.hour <= 10) { currentState = LiveCardState.morning; } else { currentState = LiveCardState.empty; } notifyListeners(); } bool get show => currentState != LiveCardState.empty; Duration get delay => _delay; bool _sameDate(DateTime a, DateTime b) => (a.year == b.year && a.month == b.month && a.day == b.day); List _today(TimetableProvider p) => (p.getWeek(Week.current()) ?? []) .where((l) => _sameDate(l.date, _now())) .toList(); }