import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'core/constants/app_constants.dart'; import 'core/cache/cache_refresh_coordinator.dart'; import 'core/di/injection.dart'; import 'core/notifications/ios_notification_payload_bridge.dart'; import 'core/notifications/local_notification_service.dart'; import 'core/notifications/reminder_notification_callbacks.dart'; import 'core/router/app_router.dart'; import 'core/startup/auth_session_bootstrapper.dart'; import 'core/theme/app_theme.dart'; import 'features/auth/presentation/bloc/auth_bloc.dart'; import 'features/auth/presentation/bloc/auth_event.dart'; import 'features/auth/presentation/bloc/auth_state.dart'; import 'features/calendar/data/services/calendar_service.dart'; import 'features/calendar/data/services/calendar_repository.dart'; import 'features/calendar/reminders/reminder_action_executor.dart'; import 'features/calendar/reminders/reminder_queue_manager.dart'; import 'features/calendar/reminders/models/reminder_action.dart'; import 'features/calendar/reminders/models/reminder_payload.dart'; import 'features/calendar/reminders/ui/reminder_overlay.dart'; import 'features/calendar/ui/calendar_state_manager.dart'; import 'features/chat/presentation/bloc/chat_bloc.dart'; import 'features/settings/data/services/settings_user_cache.dart'; import 'features/todo/data/todo_repository.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await configureDependencies(); await AppConstants.init(); final rootNavigatorKey = GlobalKey(); sl().bindActionHandler(({ required action, required payload, }) { return sl().handleAction( action: action, payload: payload, ); }); await sl().initialize(); final prefs = sl(); final payloadBridge = IOSNotificationPayloadBridge(prefs); final queueManager = ReminderQueueManager(); final pendingPayload = await payloadBridge.getPendingPayload(); if (pendingPayload != null) { queueManager.enqueueFromClick(pendingPayload); await payloadBridge.clearPendingPayload(); } final authBloc = sl(); authBloc.add(AuthStarted()); final cacheRefreshCoordinator = CacheRefreshCoordinator( minInterval: const Duration(minutes: 5), onRefresh: () { final selected = sl().selectedDate; unawaited( sl().getDayEvents(selected, forceRefresh: true), ); unawaited( sl().getMonthEvents( DateTime(selected.year, selected.month, 1), forceRefresh: true, ), ); unawaited(sl().getPendingTodos(forceRefresh: true)); unawaited(sl().getProfile(forceRefresh: true)); }, ); WidgetsBinding.instance.addObserver(cacheRefreshCoordinator); runApp( LinksyApp( authBloc: authBloc, rootNavigatorKey: rootNavigatorKey, sessionBootstrapper: AuthSessionBootstrapper( calendarService: sl(), notificationService: sl(), reminderActionExecutor: sl(), ), pendingReminderPayload: pendingPayload, reminderQueueManager: queueManager, ), ); WidgetsBinding.instance.addPostFrameCallback((_) { unawaited( ReminderNotificationCallbacks.bindResponseHandler( sl().handleNotificationResponse, ), ); }); } class LinksyApp extends StatefulWidget { final AuthBloc authBloc; final GlobalKey rootNavigatorKey; final AuthSessionBootstrapper sessionBootstrapper; final ReminderPayload? pendingReminderPayload; final ReminderQueueManager reminderQueueManager; const LinksyApp({ super.key, required this.authBloc, required this.rootNavigatorKey, required this.sessionBootstrapper, this.pendingReminderPayload, required this.reminderQueueManager, }); @override State createState() => _LinksyAppState(); } class _LinksyAppState extends State { OverlayEntry? _reminderOverlayEntry; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _maybeShowReminderOverlay(); }); } void _maybeShowReminderOverlay() { if (widget.pendingReminderPayload == null) { return; } final context = widget.rootNavigatorKey.currentContext; if (context == null) { return; } _reminderOverlayEntry = OverlayEntry( builder: (context) => ReminderOverlay( queueManager: widget.reminderQueueManager, onComplete: _dismissReminderOverlay, onSnooze: (minutes) { final action = minutes >= 10 ? ReminderAction.snooze10m : ReminderAction.archive; sl().handleAction( action: action, payload: widget.pendingReminderPayload!, ); }, onArchive: () { sl().handleAction( action: ReminderAction.archive, payload: widget.pendingReminderPayload!, ); }, ), ); Overlay.of(context).insert(_reminderOverlayEntry!); } void _dismissReminderOverlay() { _reminderOverlayEntry?.remove(); _reminderOverlayEntry = null; } @override Widget build(BuildContext context) { return MultiBlocProvider( providers: [ BlocProvider.value(value: widget.authBloc), BlocProvider(create: (_) => ChatBloc(apiClient: sl())), ], child: BlocListener( listenWhen: (previous, current) => previous != current, listener: (context, state) { unawaited(widget.sessionBootstrapper.syncForAuthState(state)); }, child: MaterialApp.router( title: 'Linksy', debugShowCheckedModeBanner: false, theme: AppTheme.light, routerConfig: createAppRouter(widget.authBloc), ), ), ); } }