import 'dart:async'; import 'package:flutter/foundation.dart'; import '../../features/calendar/data/repositories/calendar_repository.dart'; import '../../features/messages/data/repositories/inbox_repository.dart'; import '../../core/chat/chat_history_repository.dart'; enum AppPrewarmStatus { idle, running, completed, timedOut, failed } class AppPrewarmOrchestrator extends ChangeNotifier { AppPrewarmOrchestrator({ required CalendarRepository calendarRepository, required InboxRepository inboxRepository, required ChatHistoryRepository chatHistoryRepository, this.bootBudget = const Duration(milliseconds: 1200), Future Function()? prewarmChatHistory, Future Function()? prewarmCalendarToday, Future Function()? prewarmCalendarReminderWindow, Future Function()? prewarmUnreadInbox, }) : _calendarRepository = calendarRepository, _inboxRepository = inboxRepository, _chatHistoryRepository = chatHistoryRepository, _prewarmChatHistory = prewarmChatHistory, _prewarmCalendarToday = prewarmCalendarToday, _prewarmCalendarReminderWindow = prewarmCalendarReminderWindow, _prewarmUnreadInbox = prewarmUnreadInbox; final CalendarRepository _calendarRepository; final InboxRepository _inboxRepository; final ChatHistoryRepository _chatHistoryRepository; final Duration bootBudget; final Future Function()? _prewarmChatHistory; final Future Function()? _prewarmCalendarToday; final Future Function()? _prewarmCalendarReminderWindow; final Future Function()? _prewarmUnreadInbox; AppPrewarmStatus _status = AppPrewarmStatus.idle; AppPrewarmStatus get status => _status; String? _userId; Future? _running; int _runToken = 0; bool get isBootBlocking => _status == AppPrewarmStatus.running; Future ensureStartedFor(String userId) { if (_userId == userId && (_status == AppPrewarmStatus.completed || _status == AppPrewarmStatus.timedOut)) { return Future.value(); } if (_userId == userId && _running != null) { return _running!; } _userId = userId; final runToken = ++_runToken; _status = AppPrewarmStatus.running; notifyListeners(); final tasks = Future.wait([ _runPrewarmChatHistory(), _runPrewarmCalendarToday(), _runPrewarmCalendarReminderWindow(), _runPrewarmUnreadInbox(), ]); final running = _runWithBudget(tasks, userId: userId, runToken: runToken); _running = running; return running.whenComplete(() { if (identical(_running, running)) { _running = null; } }); } Future _runPrewarmChatHistory() { final override = _prewarmChatHistory; if (override != null) { return override(); } return _chatHistoryRepository.loadHistory(); } Future _runPrewarmCalendarToday() { final override = _prewarmCalendarToday; if (override != null) { return override(); } return _calendarRepository.getDayEvents(DateTime.now()); } Future _runPrewarmUnreadInbox() { final override = _prewarmUnreadInbox; if (override != null) { return override(); } return _inboxRepository.getMessages(isRead: false); } Future _runPrewarmCalendarReminderWindow() { final override = _prewarmCalendarReminderWindow; if (override != null) { return override(); } final now = DateTime.now(); final start = DateTime( now.year, now.month, now.day, ).subtract(const Duration(days: 1)); final end = start.add(const Duration(days: 90)); return _calendarRepository.listByRange(startAt: start, endAt: end); } Future _runWithBudget( Future tasks, { required String userId, required int runToken, }) async { bool isLatestRun() => _runToken == runToken && _userId == userId; try { await tasks.timeout(bootBudget); if (!isLatestRun()) { return; } _status = AppPrewarmStatus.completed; notifyListeners(); } on TimeoutException { if (!isLatestRun()) { return; } _status = AppPrewarmStatus.timedOut; notifyListeners(); } catch (_) { if (!isLatestRun()) { return; } _status = AppPrewarmStatus.failed; notifyListeners(); } } void reset() { _userId = null; _running = null; _runToken += 1; if (_status != AppPrewarmStatus.idle) { _status = AppPrewarmStatus.idle; notifyListeners(); } } }