import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:friflex_starter/app/app_context_ext.dart'; import 'package:friflex_starter/app/app_providers.dart'; import 'package:friflex_starter/app/depends_providers.dart'; import 'package:friflex_starter/app/theme/app_theme.dart'; import 'package:friflex_starter/app/theme/theme_notifier.dart'; import 'package:friflex_starter/di/di_container.dart'; import 'package:friflex_starter/features/error/error_screen.dart'; import 'package:friflex_starter/features/splash/splash_screen.dart'; import 'package:friflex_starter/features/update/domain/state/cubit/update_cubit.dart'; import 'package:friflex_starter/features/update/update_routes.dart'; import 'package:friflex_starter/l10n/gen/app_localizations.dart'; import 'package:friflex_starter/l10n/localization_notifier.dart'; import 'package:friflex_starter/router/app_router.dart'; import 'package:go_router/go_router.dart'; /// {@template app} /// Главный виджет приложения, управляющий инициализацией зависимостей /// и отображением основного интерфейса приложения. /// /// Отвечает за: /// - Инициализацию зависимостей приложения /// - Отображение экрана загрузки во время инициализации /// - Обработку ошибок инициализации /// - Настройку провайдеров для темы и локализации /// {@endtemplate} class App extends StatefulWidget { /// {@macro app} const App({required this.initDependencies, super.key}); /// Функция для инициализации зависимостей приложения /// Возвращает Future с контейнером зависимостей final Future Function() initDependencies; @override State createState() => _AppState(); } /// {@template app_state} /// Состояние главного виджета приложения. /// /// Управляет процессом инициализации зависимостей и отображением /// соответствующих экранов в зависимости от состояния инициализации. /// {@endtemplate} class _AppState extends State { /// {@macro app_state} _AppState(); /// Мутабельная Future для инициализации зависимостей /// Позволяет перезапускать инициализацию при ошибках late Future _initFuture; @override void initState() { super.initState(); _initFuture = widget.initDependencies(); } @override Widget build(BuildContext context) { return AppProviders( // Consumer для локализации добавляем выше чем DependsProviders // чтобы при изменении локализации перестраивался весь виджет // Но, это не обязательно, можно добавить в DependsProviders child: LocalizationConsumer( builder: () => FutureBuilder( future: _initFuture, builder: (_, snapshot) { return switch (snapshot.connectionState) { // Если состояние не определено, ожидается или активно, то отображаем экран загрузки ConnectionState.none || ConnectionState.waiting || ConnectionState.active => const SplashScreen(), ConnectionState.done => // Если данные получены и не равны null, то отображаем внутренний виджет приложения // Иначе отображаем экран ошибки (snapshot.hasData && snapshot.data != null) ? _AppInternal(diContainer: snapshot.data!) : ErrorScreen( error: snapshot.error, stackTrace: snapshot.stackTrace, onRetry: _retryInit, ), }; }, ), ), ); } /// Метод для перезапуска инициализации зависимостей /// Вызывается при ошибках инициализации для повторной попытки void _retryInit() { setState(() { _initFuture = widget.initDependencies(); }); } } /// {@template app_internal} /// Внутренний виджет приложения, отображающий основной интерфейс /// после успешной инициализации зависимостей. /// /// Настраивает MaterialApp с роутером, темами и локализацией. /// {@endtemplate} class _AppInternal extends StatefulWidget { /// {@macro app_internal} const _AppInternal({required this.diContainer}); /// Роутер приложения для навигации /// Контейнер зависимостей final DiContainer diContainer; @override State<_AppInternal> createState() => _AppInternalState(); } class _AppInternalState extends State<_AppInternal> { /// Роутер приложения для навигации late final GoRouter router; @override void initState() { super.initState(); router = AppRouter.createRouter(widget.diContainer.debugService); } @override void dispose() { router.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return DependsProviders( diContainer: widget.diContainer, child: BlocConsumer( listener: (context, state) { if (state is UpdateSuccessState && state.updateInfo.updateType == .hard && context.mounted) { router.goNamed(UpdateRoutes.hardUpdateScreenName); } }, builder: (context, state) { // Если состояние загрузки, то отображаем экран загрузки if (state is UpdateLoadingState) { return const SplashScreen(); } return ThemeConsumer( builder: () => MediaQuery( key: const ValueKey('prevent_rebuild'), data: MediaQuery.of( context, ).copyWith(textScaler: TextScaler.noScaling, boldText: false), child: MaterialApp.router( routerConfig: router, darkTheme: AppTheme.dark, theme: AppTheme.light, themeMode: context.theme.themeMode, locale: context.localization.locale, localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, ), ), ); }, ), ); } }