import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'di/injection.dart'; import '../data/models/reminder_payload.dart'; import '../data/services/calendar_service.dart'; import '../data/services/local_notification_service.dart'; import '../data/services/reminder_notification_callbacks.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/notification/domain/models/reminder_action.dart'; import '../features/notification/domain/services/reminder_action_executor.dart'; import 'router/app_router.dart'; import '../core/theme/app_theme.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; String? _reminderBootstrapUserId; @override void initState() { super.initState(); _authBloc = sl(); _authBloc.add(AuthStarted()); _router = createAppRouter(_authBloc); unawaited(_bindNotificationResponseHandler()); } @override void dispose() { _router.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return BlocProvider.value( value: _authBloc, child: BlocListener( listener: (context, state) { if (state is AuthAuthenticated && state.user.id != _reminderBootstrapUserId) { _reminderBootstrapUserId = state.user.id; unawaited(_rebuildUpcomingReminders()); } if (state is AuthUnauthenticated) { _reminderBootstrapUserId = null; } }, 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, ), ), ); } Future _rebuildUpcomingReminders() async { final now = DateTime.now(); final start = now.subtract(const Duration(days: 90)); final end = now.add(const Duration(days: 90)); try { final events = await sl().getEventsForRange(start, end); await sl().rebuildUpcomingReminders(events); } catch (error) { debugPrint('reminder bootstrap skipped: $error'); } } Future _bindNotificationResponseHandler() async { await ReminderNotificationCallbacks.bindResponseHandler((response) async { final payloadRaw = response.payload; if (payloadRaw == null || payloadRaw.isEmpty) { return; } ReminderPayload payload; try { payload = ReminderPayload.fromJson( Map.from(jsonDecode(payloadRaw) as Map), ); } catch (_) { return; } final actionId = response.actionId; ReminderAction? action; if (actionId != null) { try { action = ReminderAction.fromValue(actionId); } catch (_) { action = null; } } if (action == null) { ReminderNotificationCallbacks.onNotificationPayloadReceived?.call( payload, ); return; } await sl().handleAction( action: action, payload: payload, ); }); } }