diff --git a/firka/lib/helpers/api/client/live_activity_backend_client.dart b/firka/lib/helpers/api/client/live_activity_backend_client.dart index d319a033..ae711dec 100644 --- a/firka/lib/helpers/api/client/live_activity_backend_client.dart +++ b/firka/lib/helpers/api/client/live_activity_backend_client.dart @@ -73,7 +73,9 @@ class LiveActivityBackendClient { _logger.info('Registering device with backend. Sending ${lessonsData.length} lessons.'); if (_logger.isLoggable(Level.FINE)) { - lessonsData.forEach((lesson) => _logger.fine(' Lesson data: ${lesson}')); + for (var lesson in lessonsData) { + _logger.fine(' Lesson data: $lesson'); + } } final response = await _dio.post( @@ -126,7 +128,9 @@ class LiveActivityBackendClient { _logger.info('Updating timetable with backend. Sending ${lessonsData.length} lessons.'); if (_logger.isLoggable(Level.FINE)) { - lessonsData.forEach((lesson) => _logger.fine(' Lesson data: ${lesson}')); + for (var lesson in lessonsData) { + _logger.fine(' Lesson data: $lesson'); + } } final requestData = { diff --git a/firka/lib/helpers/live_activity_manager.dart b/firka/lib/helpers/live_activity_manager.dart index 8f93eede..e43a6a5a 100644 --- a/firka/lib/helpers/live_activity_manager.dart +++ b/firka/lib/helpers/live_activity_manager.dart @@ -4,15 +4,6 @@ import 'package:flutter/services.dart'; import 'package:firka/helpers/api/model/timetable.dart'; import 'package:logging/logging.dart'; -class _ActivityState { - final Lesson? currentLesson; - final Lesson? nextLesson; - final bool isBreak; - final String? mode; - - _ActivityState({this.currentLesson, this.nextLesson, this.isBreak = false, this.mode}); -} - class LiveActivityManager { static const MethodChannel _channel = MethodChannel('firka.app/live_activity'); static final Logger _logger = Logger('LiveActivityManager'); @@ -244,179 +235,4 @@ class LiveActivityManager { }; return payload; } - - static _ActivityState _findCurrentActivityState(List lessons, DateTime now) { - lessons.sort((a, b) => a.start.compareTo(b.start)); - - for (int i = 0; i < lessons.length; i++) { - final lesson = lessons[i]; - final lessonStart = DateTime(now.year, now.month, now.day, lesson.start.hour, lesson.start.minute); - final lessonEnd = DateTime(now.year, now.month, now.day, lesson.end.hour, lesson.end.minute); - - if (i == 0 && now.isBefore(lessonStart)) { - if (lessonStart.difference(now).inHours < 2) { - return _ActivityState(currentLesson: lesson, mode: 'beforeSchool'); - } - } - - if (now.isAfter(lessonStart) && now.isBefore(lessonEnd)) { - final correctedLesson = Lesson( - uid: lesson.uid, - date: lesson.date, - start: lessonStart, - end: lessonEnd, - name: lesson.name, - type: lesson.type, - state: lesson.state, - lessonNumber: lesson.lessonNumber, - roomName: lesson.roomName, - teacher: lesson.teacher, - theme: lesson.theme, - substituteTeacher: lesson.substituteTeacher, - canStudentEditHomework: lesson.canStudentEditHomework, - isHomeworkComplete: lesson.isHomeworkComplete, - attachments: lesson.attachments, - isDigitalLesson: lesson.isDigitalLesson, - digitalSupportDeviceTypeList: lesson.digitalSupportDeviceTypeList, - createdAt: lesson.createdAt, - lastModifiedAt: lesson.lastModifiedAt - ); - - final nextLesson = i + 1 < lessons.length ? lessons[i + 1] : null; - Lesson? correctedNextLesson; - if (nextLesson != null) { - correctedNextLesson = Lesson( - uid: nextLesson.uid, date: nextLesson.date, - start: DateTime(now.year, now.month, now.day, nextLesson.start.hour, nextLesson.start.minute), - end: DateTime(now.year, now.month, now.day, nextLesson.end.hour, nextLesson.end.minute), - name: nextLesson.name, type: nextLesson.type, state: nextLesson.state, - lessonNumber: nextLesson.lessonNumber, roomName: nextLesson.roomName, teacher: nextLesson.teacher, - theme: nextLesson.theme, substituteTeacher: nextLesson.substituteTeacher, - canStudentEditHomework: nextLesson.canStudentEditHomework, isHomeworkComplete: nextLesson.isHomeworkComplete, - attachments: nextLesson.attachments, isDigitalLesson: nextLesson.isDigitalLesson, - digitalSupportDeviceTypeList: nextLesson.digitalSupportDeviceTypeList, - createdAt: nextLesson.createdAt, lastModifiedAt: nextLesson.lastModifiedAt - ); - } - - return _ActivityState( - currentLesson: correctedLesson, - nextLesson: correctedNextLesson, - ); - } - - if (i + 1 < lessons.length) { - final nextLesson = lessons[i + 1]; - final nextLessonStart = DateTime(now.year, now.month, now.day, nextLesson.start.hour, nextLesson.start.minute); - if (now.isAfter(lessonEnd) && now.isBefore(nextLessonStart)) { - final breakLesson = Lesson( - uid: 'break-${lesson.uid}', - date: lesson.date, - start: lessonEnd, - end: nextLessonStart, - name: 'Szünet', - type: lesson.type, - state: lesson.state, - canStudentEditHomework: false, - isHomeworkComplete: false, - attachments: [], - isDigitalLesson: false, - digitalSupportDeviceTypeList: [], - createdAt: now, - lastModifiedAt: now, - ); - return _ActivityState(currentLesson: breakLesson, nextLesson: nextLesson, isBreak: true); - } - } - } - - if (lessons.isNotEmpty) { - final lastLesson = lessons.last; - final lastLessonEnd = DateTime(now.year, now.month, now.day, lastLesson.end.hour, lastLesson.end.minute); - final afterSchoolBreakEnd = lastLessonEnd.add(const Duration(minutes: 15)); - - if (now.isAfter(lastLessonEnd) && now.isBefore(afterSchoolBreakEnd)) { - final breakLesson = Lesson( - uid: 'break-after-school', - date: lastLesson.date, - start: lastLessonEnd, - end: afterSchoolBreakEnd, - name: 'Szünet', - type: lastLesson.type, - state: lastLesson.state, - canStudentEditHomework: false, - isHomeworkComplete: false, - attachments: [], - isDigitalLesson: false, - digitalSupportDeviceTypeList: [], - createdAt: now, - lastModifiedAt: now, - ); - return _ActivityState(currentLesson: breakLesson, isBreak: true); - } - } - - return _ActivityState(); - } - - static Future updateActivityFromTimetable( - List todayLessons, - String studentName, - String schoolName, - ) async { - if (!Platform.isIOS) return; - - final now = DateTime.now(); - _logger.info("Checking for activity update at ${now.toIso8601String()}"); - - final lessons = todayLessons.where((lesson) { - return !(lesson.state.name?.toLowerCase().contains('törölt') ?? false); - }).toList(); - - if (lessons.isEmpty) { - _logger.info("No lessons today. Backend will control activity state."); - return; - } - - final schoolLessons = lessons.where((lesson) { - final type = lesson.type.name?.toLowerCase() ?? ''; - return !type.contains('tanevrendje'); - }).toList(); - - if (schoolLessons.isEmpty) { - _logger.info("No school lessons today (only break events). Backend will control activity state."); - return; - } - - final state = _findCurrentActivityState(schoolLessons, now); - - _logger.info("Current state: lesson=${state.currentLesson?.name}, break=${state.isBreak}, mode=${state.mode}"); - - await _syncActivityState(); - - if (state.currentLesson != null) { - if (_isActivityActive) { - _logger.info("Updating existing activity."); - await updateActivity( - currentLesson: state.currentLesson!, - nextLesson: state.nextLesson, - isBreak: state.isBreak, - mode: state.mode, - ); - } else { - _logger.info("Starting new activity."); - await startActivity( - studentName: studentName, - schoolName: schoolName, - currentLesson: state.currentLesson!, - nextLesson: state.nextLesson, - isBreak: state.isBreak, - mode: state.mode, - ); - } - } else { - _logger.info("No current lesson or break. Ending activity if running."); - await endAllActivities(); - } - } } diff --git a/firka/lib/helpers/live_activity_service.dart b/firka/lib/helpers/live_activity_service.dart index b8fe4ccc..c46849b5 100644 --- a/firka/lib/helpers/live_activity_service.dart +++ b/firka/lib/helpers/live_activity_service.dart @@ -44,7 +44,7 @@ class LiveActivityService { /// Get current bellDelay value from settings static double? _getCurrentBellDelay() { try { - if (!initDone || initData.settings == null) { + if (!initDone) { return null; } final bellDelaySetting = initData.settings.group("settings") @@ -60,10 +60,10 @@ class LiveActivityService { /// If client is provided, use it directly instead of initData.client static String? _getCurrentStudentId({KretaClient? client}) { try { - if (client != null && client.model != null) { + if (client != null) { return client.model.studentId; } - if (!initDone || initData.client == null || initData.client.model == null) { + if (!initDone) { return null; } return initData.client.model.studentId; @@ -149,7 +149,7 @@ class LiveActivityService { /// Get current language code from settings static String? _getCurrentLanguageCode() { try { - if (!initDone || initData.settings == null) { + if (!initDone) { return 'hu'; } @@ -281,7 +281,7 @@ class LiveActivityService { static bool _hasRemainingLessonsToday(List lessons) { final now = DateTime.now(); final todayLessons = lessons.where((lesson) { - final uid = lesson.uid?.toLowerCase() ?? ''; + final uid = lesson.uid.toLowerCase(); return lesson.date == now.toIso8601String().split('T').first && lesson.end.isAfter(now) && (uid.contains('orarendiora') || @@ -302,10 +302,6 @@ class LiveActivityService { try { final client = initData.client; - if (client == null || client.model == null) { - _logger.warning('Background fetch skipped: no client available'); - return false; - } final enabled = await isEnabled(initData.settings, client); if (!enabled) { @@ -389,7 +385,7 @@ class LiveActivityService { attachments: firstLesson.attachments, isDigitalLesson: firstLesson.isDigitalLesson, digitalSupportDeviceTypeList: firstLesson.digitalSupportDeviceTypeList, - createdAt: firstLesson.createdAt ?? firstLesson.lastModifiedAt ?? DateTime.now(), + createdAt: firstLesson.createdAt, lastModifiedAt: firstLesson.lastModifiedAt, ); @@ -695,69 +691,136 @@ class LiveActivityService { return; } - _logger.info('Searching for first school day of next week'); + final nextWeekStart = endOfWeek.add(const Duration(days: 1)); + final nextWeekEnd = nextWeekStart.add(const Duration(days: 6)); - bool foundFirstSchoolDay = false; - for (int dayOffset = 1; dayOffset <= 5; dayOffset++) { - final candidateDay = endOfWeek.add(Duration(days: dayOffset)); + _logger.info('[GlobalSearch] onUserLogin: Checking next week (${nextWeekStart.toString().split(' ')[0]} - ${nextWeekEnd.toString().split(' ')[0]}) for events'); - if (candidateDay.weekday == DateTime.saturday || candidateDay.weekday == DateTime.sunday) { - continue; - } + List nextWeekBreakEvents = []; + try { + final nextWeekResponse = await client.getTimeTable(nextWeekStart, nextWeekEnd, forceCache: false); - try { - final candidateDayEnd = candidateDay.add(const Duration(days: 1)); - final response = await client.getTimeTable(candidateDay, candidateDayEnd, forceCache: false); + if (nextWeekResponse.response != null) { + nextWeekBreakEvents = nextWeekResponse.response!.where((lesson) { + final uid = lesson.uid.toLowerCase(); + final name = lesson.name.toLowerCase(); + return uid.contains('tanevrendjeesemeny') && + !name.contains('tanítási nap') && + (name.contains('szünet') || + name.contains('pihenőnap') || + name.contains('munkaszüneti') || + name.contains('ünnepnap') || + name.contains('tanítás nélküli') || + name.contains('nem órarendi nap')); + }).toList(); - if (response.response != null && response.response!.isNotEmpty) { - final schoolLessons = response.response!.where((lesson) { - final uid = lesson.uid.toLowerCase(); - return uid.contains('orarendiora') || uid.contains('tanitasiora') || uid.contains('uresora'); - }).toList(); - - if (schoolLessons.isNotEmpty) { - schoolLessons.sort((a, b) => a.start.compareTo(b.start)); - final firstLesson = schoolLessons.first; - - final notificationLesson = Lesson( - uid: '${firstLesson.uid}__FOR_NOTIFICATION_ONLY', - date: firstLesson.date, - start: firstLesson.start, - end: firstLesson.end, - name: firstLesson.name, - lessonNumber: firstLesson.lessonNumber, - teacher: firstLesson.teacher, - theme: firstLesson.theme, - roomName: firstLesson.roomName, - substituteTeacher: firstLesson.substituteTeacher, - type: firstLesson.type, - state: firstLesson.state, - canStudentEditHomework: firstLesson.canStudentEditHomework, - isHomeworkComplete: firstLesson.isHomeworkComplete, - attachments: firstLesson.attachments, - isDigitalLesson: firstLesson.isDigitalLesson, - digitalSupportDeviceTypeList: firstLesson.digitalSupportDeviceTypeList, - createdAt: firstLesson.createdAt ?? firstLesson.lastModifiedAt ?? DateTime.now(), - lastModifiedAt: firstLesson.lastModifiedAt, - ); - - allLessons.add(notificationLesson); - - const dayNames = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; - final dayName = dayNames[candidateDay.weekday - 1]; - _logger.info('Added first lesson from next week $dayName (${firstLesson.name}) marked for notification scheduling only'); - - foundFirstSchoolDay = true; - break; - } + if (nextWeekBreakEvents.isNotEmpty) { + _logger.info('[GlobalSearch] onUserLogin: Found ${nextWeekBreakEvents.length} break event(s) in next week - triggering global searcher'); } - } catch (e) { - _logger.warning('Failed to fetch lessons for day offset $dayOffset: $e'); } + } catch (e) { + _logger.warning('[GlobalSearch] onUserLogin: Could not fetch next week: $e'); } - if (!foundFirstSchoolDay) { - _logger.info('No school lessons found in next week for push notification scheduling'); + if (nextWeekBreakEvents.isNotEmpty) { + nextWeekBreakEvents.sort((a, b) => a.start.compareTo(b.start)); + final firstBreakEvent = nextWeekBreakEvents.first; + + _logger.info('[GlobalSearch] onUserLogin: Triggering global break searcher from ${firstBreakEvent.date.split('T')[0]}'); + + final searchResult = await _globalBreakSearcher( + client: client, + searchStartDate: nextWeekStart, + ); + + allLessons.addAll(searchResult.allLessons); + + _logger.info('[GlobalSearch] onUserLogin: Global searcher returned ${searchResult.allLessons.length} lessons'); + + if (searchResult.firstSchoolDayAfterBreak != null) { + final notificationLesson = Lesson( + uid: '${searchResult.firstSchoolDayAfterBreak!.uid}__FOR_NOTIFICATION_ONLY', + date: searchResult.firstSchoolDayAfterBreak!.date, + start: searchResult.firstSchoolDayAfterBreak!.start, + end: searchResult.firstSchoolDayAfterBreak!.end, + name: searchResult.firstSchoolDayAfterBreak!.name, + lessonNumber: searchResult.firstSchoolDayAfterBreak!.lessonNumber, + teacher: searchResult.firstSchoolDayAfterBreak!.teacher, + theme: searchResult.firstSchoolDayAfterBreak!.theme, + roomName: searchResult.firstSchoolDayAfterBreak!.roomName, + substituteTeacher: searchResult.firstSchoolDayAfterBreak!.substituteTeacher, + type: searchResult.firstSchoolDayAfterBreak!.type, + state: searchResult.firstSchoolDayAfterBreak!.state, + canStudentEditHomework: searchResult.firstSchoolDayAfterBreak!.canStudentEditHomework, + isHomeworkComplete: searchResult.firstSchoolDayAfterBreak!.isHomeworkComplete, + attachments: searchResult.firstSchoolDayAfterBreak!.attachments, + isDigitalLesson: searchResult.firstSchoolDayAfterBreak!.isDigitalLesson, + digitalSupportDeviceTypeList: searchResult.firstSchoolDayAfterBreak!.digitalSupportDeviceTypeList, + createdAt: searchResult.firstSchoolDayAfterBreak!.createdAt, + lastModifiedAt: searchResult.firstSchoolDayAfterBreak!.lastModifiedAt, + ); + + allLessons.add(notificationLesson); + _logger.info('[GlobalSearch] onUserLogin: Added first school day after break for push notification: ${notificationLesson.date.split('T')[0]}'); + } + } else { + _logger.info('[GlobalSearch] onUserLogin: No break events in next week, searching for first school day...'); + + bool foundFirstSchoolDay = false; + for (int dayOffset = 1; dayOffset <= 14; dayOffset++) { + final candidateDay = endOfWeek.add(Duration(days: dayOffset)); + + try { + final candidateDayEnd = candidateDay.add(const Duration(days: 1)); + final response = await client.getTimeTable(candidateDay, candidateDayEnd, forceCache: false); + + if (response.response != null && response.response!.isNotEmpty) { + final schoolLessons = response.response!.where((lesson) { + final uid = lesson.uid.toLowerCase(); + return uid.contains('orarendiora') || uid.contains('tanitasiora') || uid.contains('uresora'); + }).toList(); + + if (schoolLessons.isNotEmpty) { + schoolLessons.sort((a, b) => a.start.compareTo(b.start)); + final firstLesson = schoolLessons.first; + + final markedLesson = Lesson( + uid: '${firstLesson.uid}__FOR_NOTIFICATION_ONLY', + date: firstLesson.date, + start: firstLesson.start, + end: firstLesson.end, + name: firstLesson.name, + lessonNumber: firstLesson.lessonNumber, + teacher: firstLesson.teacher, + theme: firstLesson.theme, + roomName: firstLesson.roomName, + substituteTeacher: firstLesson.substituteTeacher, + type: firstLesson.type, + state: firstLesson.state, + canStudentEditHomework: firstLesson.canStudentEditHomework, + isHomeworkComplete: firstLesson.isHomeworkComplete, + attachments: firstLesson.attachments, + isDigitalLesson: firstLesson.isDigitalLesson, + digitalSupportDeviceTypeList: firstLesson.digitalSupportDeviceTypeList, + createdAt: firstLesson.createdAt, + lastModifiedAt: firstLesson.lastModifiedAt, + ); + + allLessons.add(markedLesson); + _logger.info('[GlobalSearch] onUserLogin: Found first school day for push notification: ${candidateDay.toString().split(' ')[0]}'); + + foundFirstSchoolDay = true; + break; + } + } + } catch (e) { + _logger.warning('[GlobalSearch] onUserLogin: Could not fetch day offset $dayOffset: $e'); + } + } + + if (!foundFirstSchoolDay) { + _logger.info('[GlobalSearch] onUserLogin: No school lessons found in next 14 days for push notification scheduling'); + } } final deviceToken = await _getOrWaitDeviceToken(); @@ -933,201 +996,136 @@ class LiveActivityService { } } - bool foundFirstSchoolDay = false; - for (int dayOffset = 1; dayOffset <= 5; dayOffset++) { - final candidateDay = endOfWeek.add(Duration(days: dayOffset)); + final nextWeekStart = endOfWeek.add(const Duration(days: 1)); + final nextWeekEnd = nextWeekStart.add(const Duration(days: 6)); - if (candidateDay.weekday == DateTime.saturday || candidateDay.weekday == DateTime.sunday) { - continue; - } + _logger.info('[GlobalSearch] Checking next week (${nextWeekStart.toString().split(' ')[0]} - ${nextWeekEnd.toString().split(' ')[0]}) for events'); - try { - final candidateDayEnd = candidateDay.add(const Duration(days: 1)); - final response = await client.getTimeTable(candidateDay, candidateDayEnd, forceCache: false); + List nextWeekBreakEvents = []; + try { + final nextWeekResponse = await client.getTimeTable(nextWeekStart, nextWeekEnd, forceCache: false); - if (response.response != null && response.response!.isNotEmpty) { - final schoolLessons = response.response!.where((lesson) { - final uid = lesson.uid.toLowerCase(); - return uid.contains('orarendiora') || uid.contains('tanitasiora') || uid.contains('uresora'); - }).toList(); + if (nextWeekResponse.response != null) { + nextWeekBreakEvents = nextWeekResponse.response!.where((lesson) { + final uid = lesson.uid.toLowerCase(); + final name = lesson.name.toLowerCase(); + return uid.contains('tanevrendjeesemeny') && + !name.contains('tanítási nap') && + (name.contains('szünet') || + name.contains('pihenőnap') || + name.contains('munkaszüneti') || + name.contains('ünnepnap') || + name.contains('tanítás nélküli') || + name.contains('nem órarendi nap')); + }).toList(); - if (schoolLessons.isNotEmpty) { - schoolLessons.sort((a, b) => a.start.compareTo(b.start)); - final firstLesson = schoolLessons.first; - - final markedLesson = Lesson( - uid: '${firstLesson.uid}__FOR_NOTIFICATION_ONLY', - date: firstLesson.date, - start: firstLesson.start, - end: firstLesson.end, - name: firstLesson.name, - lessonNumber: firstLesson.lessonNumber, - teacher: firstLesson.teacher, - theme: firstLesson.theme, - roomName: firstLesson.roomName, - substituteTeacher: firstLesson.substituteTeacher, - type: firstLesson.type, - state: firstLesson.state, - canStudentEditHomework: firstLesson.canStudentEditHomework, - isHomeworkComplete: firstLesson.isHomeworkComplete, - attachments: firstLesson.attachments, - isDigitalLesson: firstLesson.isDigitalLesson, - digitalSupportDeviceTypeList: firstLesson.digitalSupportDeviceTypeList, - createdAt: firstLesson.createdAt ?? firstLesson.lastModifiedAt ?? DateTime.now(), - lastModifiedAt: firstLesson.lastModifiedAt, - ); - - allLessons.add(markedLesson); - - const dayNames = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; - final dayName = dayNames[candidateDay.weekday - 1]; - _logger.info('Added first lesson from next week $dayName (${firstLesson.name}) marked for notification scheduling only'); - - foundFirstSchoolDay = true; - break; - } + if (nextWeekBreakEvents.isNotEmpty) { + _logger.info('[GlobalSearch] Found ${nextWeekBreakEvents.length} break event(s) in next week - triggering global searcher'); } - } catch (e) { - _logger.warning('Could not fetch lessons for day offset $dayOffset: $e'); } + } catch (e) { + _logger.warning('[GlobalSearch] Could not fetch next week: $e'); } - if (!foundFirstSchoolDay) { - _logger.info('No school lessons found in next week for push notification scheduling'); - } + if (nextWeekBreakEvents.isNotEmpty) { + nextWeekBreakEvents.sort((a, b) => a.start.compareTo(b.start)); + final firstBreakEvent = nextWeekBreakEvents.first; - final breakEventsInWeek = allLessons.where((lesson) { - final uid = lesson.uid.toLowerCase(); - final name = lesson.name.toLowerCase(); + _logger.info('[GlobalSearch] Triggering global break searcher from ${firstBreakEvent.date.split('T')[0]}'); - return uid.contains('tanevrendjeesemeny') && - !name.contains('tanítási nap') && - (name.contains('szünet') || - name.contains('pihenőnap') || - name.contains('munkaszüneti') || - name.contains('ünnepnap') || - name.contains('tanítás nélküli') || - name.contains('nem órarendi nap')); - }).toList(); + final searchResult = await _globalBreakSearcher( + client: client, + searchStartDate: nextWeekStart, + ); - if (breakEventsInWeek.isNotEmpty) { - final firstBreak = breakEventsInWeek.first; - final breakName = firstBreak.name.toLowerCase(); + allLessons.addAll(searchResult.allLessons); - String? breakType; - if (breakName.contains('őszi')) { - breakType = 'őszi'; - } else if (breakName.contains('téli')) { - breakType = 'téli'; - } else if (breakName.contains('tavaszi')) { - breakType = 'tavaszi'; - } else if (breakName.contains('nyári')) { - breakType = 'nyári'; + _logger.info('[GlobalSearch] Global searcher returned ${searchResult.allLessons.length} lessons'); + + if (searchResult.firstSchoolDayAfterBreak != null) { + final notificationLesson = Lesson( + uid: '${searchResult.firstSchoolDayAfterBreak!.uid}__FOR_NOTIFICATION_ONLY', + date: searchResult.firstSchoolDayAfterBreak!.date, + start: searchResult.firstSchoolDayAfterBreak!.start, + end: searchResult.firstSchoolDayAfterBreak!.end, + name: searchResult.firstSchoolDayAfterBreak!.name, + lessonNumber: searchResult.firstSchoolDayAfterBreak!.lessonNumber, + teacher: searchResult.firstSchoolDayAfterBreak!.teacher, + theme: searchResult.firstSchoolDayAfterBreak!.theme, + roomName: searchResult.firstSchoolDayAfterBreak!.roomName, + substituteTeacher: searchResult.firstSchoolDayAfterBreak!.substituteTeacher, + type: searchResult.firstSchoolDayAfterBreak!.type, + state: searchResult.firstSchoolDayAfterBreak!.state, + canStudentEditHomework: searchResult.firstSchoolDayAfterBreak!.canStudentEditHomework, + isHomeworkComplete: searchResult.firstSchoolDayAfterBreak!.isHomeworkComplete, + attachments: searchResult.firstSchoolDayAfterBreak!.attachments, + isDigitalLesson: searchResult.firstSchoolDayAfterBreak!.isDigitalLesson, + digitalSupportDeviceTypeList: searchResult.firstSchoolDayAfterBreak!.digitalSupportDeviceTypeList, + createdAt: searchResult.firstSchoolDayAfterBreak!.createdAt, + lastModifiedAt: searchResult.firstSchoolDayAfterBreak!.lastModifiedAt, + ); + + allLessons.add(notificationLesson); + _logger.info('[GlobalSearch] Added first school day after break for push notification: ${notificationLesson.date.split('T')[0]}'); } + } else { + _logger.info('[GlobalSearch] No break events in next week, searching for first school day...'); - if (breakType != null) { - _logger.info('Found $breakType break in current week, fetching all break days...'); - - final extendedStart = startOfWeek.subtract(const Duration(days: 28)); - final extendedEnd = endOfWeek.add(const Duration(days: 28)); + bool foundFirstSchoolDay = false; + for (int dayOffset = 1; dayOffset <= 14; dayOffset++) { + final candidateDay = endOfWeek.add(Duration(days: dayOffset)); try { - final extendedResponse = await client.getTimeTable(extendedStart, extendedEnd); - if (extendedResponse.response != null) { - final allBreakEventsOfType = extendedResponse.response!.where((lesson) { - final uid = lesson.uid.toLowerCase(); - final name = lesson.name.toLowerCase(); + final candidateDayEnd = candidateDay.add(const Duration(days: 1)); + final response = await client.getTimeTable(candidateDay, candidateDayEnd, forceCache: false); - return uid.contains('tanevrendjeesemeny') && - !name.contains('tanítási nap') && - name.contains(breakType!); + if (response.response != null && response.response!.isNotEmpty) { + final schoolLessons = response.response!.where((lesson) { + final uid = lesson.uid.toLowerCase(); + return uid.contains('orarendiora') || uid.contains('tanitasiora') || uid.contains('uresora'); }).toList(); - int addedCount = 0; - for (final breakEvent in allBreakEventsOfType) { - if (!allLessons.any((l) => l.uid == breakEvent.uid && l.date == breakEvent.date)) { - allLessons.add(breakEvent); - addedCount++; - _logger.info('[Firka] [INFO] Added break day: ${breakEvent.date} - ${breakEvent.name}'); - } - } + if (schoolLessons.isNotEmpty) { + schoolLessons.sort((a, b) => a.start.compareTo(b.start)); + final firstLesson = schoolLessons.first; - if (addedCount > 0) { - _logger.info('[Firka] [INFO] Added $addedCount $breakType break day(s) to timetable for backend processing'); - } + final markedLesson = Lesson( + uid: '${firstLesson.uid}__FOR_NOTIFICATION_ONLY', + date: firstLesson.date, + start: firstLesson.start, + end: firstLesson.end, + name: firstLesson.name, + lessonNumber: firstLesson.lessonNumber, + teacher: firstLesson.teacher, + theme: firstLesson.theme, + roomName: firstLesson.roomName, + substituteTeacher: firstLesson.substituteTeacher, + type: firstLesson.type, + state: firstLesson.state, + canStudentEditHomework: firstLesson.canStudentEditHomework, + isHomeworkComplete: firstLesson.isHomeworkComplete, + attachments: firstLesson.attachments, + isDigitalLesson: firstLesson.isDigitalLesson, + digitalSupportDeviceTypeList: firstLesson.digitalSupportDeviceTypeList, + createdAt: firstLesson.createdAt, + lastModifiedAt: firstLesson.lastModifiedAt, + ); - if (allBreakEventsOfType.isNotEmpty) { - allBreakEventsOfType.sort((a, b) => a.start.compareTo(b.start)); - final lastBreakDay = allBreakEventsOfType.last.start; + allLessons.add(markedLesson); + _logger.info('[GlobalSearch] Found first school day for push notification: ${candidateDay.toString().split(' ')[0]}'); - _logger.info('Searching for first school day after $breakType break (after ${lastBreakDay.toIso8601String().split('T')[0]})...'); - - bool foundFirstSchoolDayAfterBreak = false; - for (int dayOffset = 1; dayOffset <= 14; dayOffset++) { - final candidateDay = lastBreakDay.add(Duration(days: dayOffset)); - - if (candidateDay.weekday == DateTime.saturday || candidateDay.weekday == DateTime.sunday) { - continue; - } - - try { - final candidateDayEnd = candidateDay.add(const Duration(days: 1)); - final response = await client.getTimeTable(candidateDay, candidateDayEnd, forceCache: false); - - if (response.response != null && response.response!.isNotEmpty) { - final schoolLessons = response.response!.where((lesson) { - final uid = lesson.uid.toLowerCase(); - return uid.contains('orarendiora') || uid.contains('tanitasiora') || uid.contains('uresora'); - }).toList(); - - if (schoolLessons.isNotEmpty) { - schoolLessons.sort((a, b) => a.start.compareTo(b.start)); - final firstLesson = schoolLessons.first; - - final notificationLesson = Lesson( - uid: '${firstLesson.uid}__FOR_NOTIFICATION_ONLY', - date: firstLesson.date, - start: firstLesson.start, - end: firstLesson.end, - name: firstLesson.name, - lessonNumber: firstLesson.lessonNumber, - teacher: firstLesson.teacher, - theme: firstLesson.theme, - roomName: firstLesson.roomName, - substituteTeacher: firstLesson.substituteTeacher, - type: firstLesson.type, - state: firstLesson.state, - canStudentEditHomework: firstLesson.canStudentEditHomework, - isHomeworkComplete: firstLesson.isHomeworkComplete, - attachments: firstLesson.attachments, - isDigitalLesson: firstLesson.isDigitalLesson, - digitalSupportDeviceTypeList: firstLesson.digitalSupportDeviceTypeList, - createdAt: firstLesson.createdAt ?? firstLesson.lastModifiedAt ?? DateTime.now(), - lastModifiedAt: firstLesson.lastModifiedAt, - ); - - allLessons.add(notificationLesson); - - _logger.info('Added first lesson after $breakType break: ${firstLesson.date.split('T')[0]} - ${firstLesson.name} (marked for notification scheduling only)'); - - foundFirstSchoolDayAfterBreak = true; - break; - } - } - } catch (e) { - _logger.warning('Could not fetch lessons after break for day offset $dayOffset: $e'); - } - } - - if (!foundFirstSchoolDayAfterBreak) { - _logger.warning('No school day found within 14 days after $breakType break'); - } + foundFirstSchoolDay = true; + break; } } } catch (e) { - _logger.warning('Could not fetch extended break events: $e'); + _logger.warning('[GlobalSearch] Could not fetch day offset $dayOffset: $e'); } } + + if (!foundFirstSchoolDay) { + _logger.info('[GlobalSearch] No school lessons found in next 14 days for push notification scheduling'); + } } if (allLessons.isEmpty) { @@ -1523,20 +1521,16 @@ class LiveActivityService { final client = initData.client; final settingsStore = initData.settings; - if (client != null) { - final studentResp = await client.getStudent(); - final studentName = studentResp.response?.name ?? client.model?.studentId ?? 'Student'; + final studentResp = await client.getStudent(); + final studentName = studentResp.response?.name ?? client.model.studentId ?? 'Student'; - await checkAndUpdateTimetable( - client: client, - studentName: studentName, - settingsStore: settingsStore, - forceUpdate: true, - ); - _logger.info('Timetable fetch completed after re-enabling notifications'); - } else { - _logger.warning('Cannot fetch timetable: client is null'); - } + await checkAndUpdateTimetable( + client: client, + studentName: studentName, + settingsStore: settingsStore, + forceUpdate: true, + ); + _logger.info('Timetable fetch completed after re-enabling notifications'); } catch (e) { _logger.severe('Error fetching timetable after re-enabling notifications: $e'); } @@ -1567,7 +1561,7 @@ class LiveActivityService { /// Get current morning notification enabled value from settings static bool? _getCurrentMorningNotificationEnabled() { try { - if (!initDone || initData.settings == null) { + if (!initDone) { return null; } final setting = initData.settings.group("settings") @@ -1582,7 +1576,7 @@ class LiveActivityService { /// Get current morning notification time value from settings static double? _getCurrentMorningNotificationTime() { try { - if (!initDone || initData.settings == null) { + if (!initDone) { return null; } final setting = initData.settings.group("settings") @@ -1593,4 +1587,103 @@ class LiveActivityService { return null; } } + + /// Global break searcher result + static Future<_GlobalSearchResult> _globalBreakSearcher({ + required KretaClient client, + required DateTime searchStartDate, + }) async { + List allLessons = []; + Lesson? lastBreakDay; + Lesson? firstSchoolDayAfterBreak; + + DateTime currentSearchDate = searchStartDate; + int weeksSearched = 0; + const maxWeeks = 26; + + _logger.info('[GlobalBreakSearcher] Starting search from ${currentSearchDate.toString().split(' ')[0]}'); + + while (weeksSearched < maxWeeks) { + final weekStart = currentSearchDate; + final weekEnd = weekStart.add(const Duration(days: 6)); + + _logger.info('[GlobalBreakSearcher] Fetching week ${weeksSearched + 1}: ${weekStart.toString().split(' ')[0]} - ${weekEnd.toString().split(' ')[0]}'); + + try { + final response = await client.getTimeTable(weekStart, weekEnd, forceCache: false); + + if (response.response != null && response.response!.isNotEmpty) { + final weekLessons = response.response!; + + final breakEvents = weekLessons.where((lesson) { + final uid = lesson.uid.toLowerCase(); + final name = lesson.name.toLowerCase(); + return uid.contains('tanevrendjeesemeny') && + !name.contains('tanítási nap') && + (name.contains('pihenőnap') || + name.contains('munkaszüneti') || + name.contains('ünnepnap') || + name.contains('tanítás nélküli') || + name.contains('nem órarendi nap')); + }).toList(); + + final schoolLessons = weekLessons.where((lesson) { + final uid = lesson.uid.toLowerCase(); + return uid.contains('orarendiora') || uid.contains('tanitasiora') || uid.contains('uresora'); + }).toList(); + + allLessons.addAll(weekLessons); + + if (breakEvents.isNotEmpty) { + breakEvents.sort((a, b) => a.start.compareTo(b.start)); + lastBreakDay = breakEvents.last; + _logger.info('[GlobalBreakSearcher] Found ${breakEvents.length} break event(s) in week ${weeksSearched + 1}, last: ${lastBreakDay.name} on ${lastBreakDay.date.split('T')[0]}'); + } else if (schoolLessons.isNotEmpty) { + schoolLessons.sort((a, b) => a.start.compareTo(b.start)); + firstSchoolDayAfterBreak = schoolLessons.first; + _logger.info('[GlobalBreakSearcher] Found first school day after break: ${firstSchoolDayAfterBreak.name} on ${firstSchoolDayAfterBreak.date.split('T')[0]}'); + break; + } else { + _logger.info('[GlobalBreakSearcher] Week ${weeksSearched + 1} is empty, continuing search...'); + } + } else { + _logger.info('[GlobalBreakSearcher] Week ${weeksSearched + 1} returned no data, continuing search...'); + } + } catch (e) { + _logger.warning('[GlobalBreakSearcher] Error fetching week ${weeksSearched + 1}: $e'); + } + + currentSearchDate = currentSearchDate.add(const Duration(days: 7)); + weeksSearched++; + + if (lastBreakDay != null && firstSchoolDayAfterBreak != null) { + break; + } + } + + if (weeksSearched >= maxWeeks) { + _logger.warning('[GlobalBreakSearcher] Reached maximum search limit ($maxWeeks weeks)'); + } + + _logger.info('[GlobalBreakSearcher] Search completed: ${allLessons.length} lessons found, last break: ${lastBreakDay?.date.split('T')[0] ?? 'none'}, first school day: ${firstSchoolDayAfterBreak?.date.split('T')[0] ?? 'none'}'); + + return _GlobalSearchResult( + allLessons: allLessons, + lastBreakDay: lastBreakDay, + firstSchoolDayAfterBreak: firstSchoolDayAfterBreak, + ); + } +} + +/// Result of global break searcher +class _GlobalSearchResult { + final List allLessons; + final Lesson? lastBreakDay; + final Lesson? firstSchoolDayAfterBreak; + + _GlobalSearchResult({ + required this.allLessons, + this.lastBreakDay, + this.firstSchoolDayAfterBreak, + }); } \ No newline at end of file