import 'dart:async'; import 'package:flutter/scheduler.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'di/injection.dart'; import '../core/l10n/l10n.dart'; import '../l10n/app_localizations.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/chat/presentation/bloc/chat_bloc.dart'; import '../data/cache/cache_scope.dart'; import 'services/app_prewarm_orchestrator.dart'; import 'services/session_scope_manager.dart'; import 'router/app_router.dart'; import '../core/theme/app_theme.dart'; import '../core/inbox/inbox_sync_store.dart'; import '../core/notification/services/reminder_permission_service.dart'; import '../core/notification/services/reminder_notification_router.dart'; import '../core/notification/models/reminder_alarm.dart'; import 'router/app_routes.dart'; class LinksyApp extends StatefulWidget { const LinksyApp({super.key}); @override State createState() => _LinksyAppState(); } class _LinksyAppState extends State { late final AuthBloc _authBloc; late final GoRouter _router; StreamSubscription? _reminderTapSubscription; String? _pendingReminderRoute; Future _authTransitionQueue = Future.value(); Future _onAuthenticated(String userId) async { await sl().activate(userId); await sl().resetForUser(userId); await sl().ensureStartedFor(userId); } Future _onUnauthenticated() async { await sl().clearActiveUserScope(); await sl().resetForUser(null); await sl().switchUser(null); sl().reset(); } @override void initState() { super.initState(); _authBloc = sl(); CacheScope.resetProvider(); _authBloc.add(AuthStarted()); _router = createAppRouter(_authBloc); SchedulerBinding.instance.addPostFrameCallback((_) { unawaited(_bootstrapReminderNotification()); }); } Future _bootstrapReminderNotification() async { await sl().initializeAtBoot(); final router = sl(); _reminderTapSubscription ??= router.taps.listen(_onReminderTap); await router.start(); } void _onReminderTap(ReminderNotificationTap tap) { final route = AppRoutes.calendarReminderAlarm(tap.eventId); _pendingReminderRoute = route; _enqueueAuthTransition(() async { if (_authBloc.state is! AuthAuthenticated) { return; } final pendingRoute = _pendingReminderRoute; if (pendingRoute == null) { return; } _pendingReminderRoute = null; _router.go(pendingRoute); }); } @override void dispose() { _reminderTapSubscription?.cancel(); _router.dispose(); super.dispose(); } void _enqueueAuthTransition(Future Function() transition) { _authTransitionQueue = _authTransitionQueue .catchError((Object error, StackTrace stackTrace) { FlutterError.reportError( FlutterErrorDetails(exception: error, stack: stackTrace), ); Zone.current.handleUncaughtError(error, stackTrace); }) .then((_) => transition()) .catchError((Object error, StackTrace stackTrace) { FlutterError.reportError( FlutterErrorDetails(exception: error, stack: stackTrace), ); Zone.current.handleUncaughtError(error, stackTrace); }); } @override Widget build(BuildContext context) { return BlocProvider.value( value: _authBloc, child: BlocListener( listener: (context, state) { if (state is AuthAuthenticated) { _enqueueAuthTransition(() => _onAuthenticated(state.user.id)); } if (state is AuthUnauthenticated) { _enqueueAuthTransition(_onUnauthenticated); } }, child: MaterialApp.router( onGenerateTitle: (context) => AppLocalizations.of(context).appTitle, debugShowCheckedModeBanner: false, theme: AppTheme.light, darkTheme: AppTheme.dark, themeMode: ThemeMode.system, locale: const Locale('zh'), supportedLocales: AppLocalizations.supportedLocales, localizationsDelegates: AppLocalizations.localizationsDelegates, builder: (context, child) { L10n.setLocale(Localizations.localeOf(context)); return child ?? const SizedBox.shrink(); }, routerConfig: _router, ), ), ); } }