mirror of
https://github.com/smmarty/friflex_flutter_starter.git
synced 2025-12-21 17:10:45 +00:00
feat(app): Вынести инициализацию приложения за splash (#4)
Co-authored-by: PetrovY <y.petrov@friflex.com>
This commit is contained in:
19
.friflex_config/stateless_widget.json
Normal file
19
.friflex_config/stateless_widget.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"imports": [
|
||||
"import 'package:flutter/widgets.dart';"
|
||||
],
|
||||
"classStructure": [
|
||||
"/// {@template {className}}",
|
||||
"/// ",
|
||||
"/// {@endtemplate}",
|
||||
"class {className} extends StatelessWidget {",
|
||||
" /// {@macro {className}}",
|
||||
" const {className}({super.key});",
|
||||
" ",
|
||||
" @override",
|
||||
" Widget build(BuildContext context) {",
|
||||
" return const Placeholder();",
|
||||
" }",
|
||||
"}"
|
||||
]
|
||||
}
|
||||
1
assets/lottie/splash.json
Normal file
1
assets/lottie/splash.json
Normal file
File diff suppressed because one or more lines are too long
114
lib/app/app.dart
114
lib/app/app.dart
@@ -1,46 +1,96 @@
|
||||
import 'package:flutter/material.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/l10n/gen/app_localizations.dart';
|
||||
import 'package:friflex_starter/l10n/localization_notifier.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// Класс для реализации объекта приложения
|
||||
class App extends StatelessWidget {
|
||||
/// Создает экземпляр приложения
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [diContainer] - набор зависимостей приложения
|
||||
/// - [router] - экземпляр роутера приложения
|
||||
/// Класс приложения
|
||||
class App extends StatefulWidget {
|
||||
const App({
|
||||
super.key,
|
||||
required this.diContainer,
|
||||
required this.router,
|
||||
required this.initDependencies,
|
||||
});
|
||||
|
||||
/// Набор зависимостей приложения
|
||||
final DiContainer diContainer;
|
||||
|
||||
/// Экземпляр роутера приложения
|
||||
/// Роутер приложения
|
||||
final GoRouter router;
|
||||
/// Функция для инициализации зависимостей
|
||||
final Future<DiContainer> Function() initDependencies;
|
||||
|
||||
@override
|
||||
State<App> createState() => _AppState();
|
||||
}
|
||||
|
||||
class _AppState extends State<App> {
|
||||
/// Мутабельная Future для инициализации зависимостей
|
||||
late Future<DiContainer> _initFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initFuture = widget.initDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppProviders(
|
||||
diContainer: diContainer,
|
||||
// Consumer для локализации добавляем выше чем DependsProviders
|
||||
// чтобы при изменении локализации перестраивался весь виджет
|
||||
// Но, это не обязательно, можно добавить в DependsProviders
|
||||
child: LocalizationConsumer(
|
||||
builder: () => ThemeConsumer(
|
||||
builder: () => _App(router: router),
|
||||
builder: () => FutureBuilder<DiContainer>(
|
||||
future: _initFuture,
|
||||
builder: (_, snapshot) {
|
||||
switch (snapshot.connectionState) {
|
||||
case ConnectionState.none:
|
||||
case ConnectionState.waiting:
|
||||
case ConnectionState.active:
|
||||
// Пока инициализация показываем Splash
|
||||
return const SplashScreen();
|
||||
case ConnectionState.done:
|
||||
if (snapshot.hasError) {
|
||||
return ErrorScreen(
|
||||
error: snapshot.error,
|
||||
stackTrace: snapshot.stackTrace,
|
||||
onRetry: _retryInit,
|
||||
);
|
||||
}
|
||||
|
||||
final diContainer = snapshot.data;
|
||||
if (diContainer == null) {
|
||||
return ErrorScreen(
|
||||
error:
|
||||
'Ошибка инициализации зависимостей, diContainer = null',
|
||||
stackTrace: null,
|
||||
onRetry: _retryInit,
|
||||
);
|
||||
}
|
||||
return DependsProviders(
|
||||
diContainer: diContainer,
|
||||
child: ThemeConsumer(
|
||||
builder: () => _App(router: widget.router),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _retryInit() {
|
||||
setState(() {
|
||||
_initFuture = widget.initDependencies();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Класс для реализации объекта приложения
|
||||
class _App extends StatelessWidget {
|
||||
const _App({required this.router});
|
||||
|
||||
@@ -59,31 +109,3 @@ class _App extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Класс для реализации провайдеров приложения
|
||||
final class AppProviders extends StatelessWidget {
|
||||
const AppProviders({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.diContainer,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final DiContainer diContainer;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: diContainer), // Передаем контейнер зависимостей
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => ThemeNotifier(),
|
||||
), // Провайдер для темы
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => LocalizationNotifier(),
|
||||
), // Провайдер для локализации
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
29
lib/app/app_providers.dart
Normal file
29
lib/app/app_providers.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friflex_starter/app/theme/theme_notifier.dart';
|
||||
import 'package:friflex_starter/l10n/localization_notifier.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// Класс для добавления провайдеров темы и локализации
|
||||
final class AppProviders extends StatelessWidget {
|
||||
const AppProviders({
|
||||
super.key,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => ThemeNotifier(),
|
||||
), // Провайдер для темы
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => LocalizationNotifier(),
|
||||
), // Провайдер для локализации
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
26
lib/app/depends_providers.dart
Normal file
26
lib/app/depends_providers.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friflex_starter/di/di_container.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// Класс для внедрения глобальных зависимостей
|
||||
final class DependsProviders extends StatelessWidget {
|
||||
const DependsProviders({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.diContainer,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final DiContainer diContainer;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
// Сюда добавляем глобальные блоки, inherited и т.д.
|
||||
Provider.value(value: diContainer), // Передаем контейнер зависимостей
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
import 'package:app_services/app_services.dart';
|
||||
import 'package:friflex_starter/app/app_config/app_config.dart';
|
||||
import 'package:friflex_starter/app/app_config/i_app_config.dart';
|
||||
import 'package:friflex_starter/app/app_env.dart';
|
||||
import 'package:friflex_starter/app/http/app_http_client.dart';
|
||||
import 'package:friflex_starter/app/http/i_http_client.dart';
|
||||
import 'package:friflex_starter/di/di_repositories.dart';
|
||||
import 'package:friflex_starter/di/di_services.dart';
|
||||
import 'package:friflex_starter/di/di_typedefs.dart';
|
||||
import 'package:friflex_starter/features/debug/i_debug_service.dart';
|
||||
import 'package:i_app_services/i_app_services.dart';
|
||||
|
||||
/// {@template dependencies_container}
|
||||
/// Контейнер для зависимостей
|
||||
@@ -22,82 +21,53 @@ final class DiContainer {
|
||||
/// Сервис для отладки, получаем из конструктора
|
||||
late final IDebugService debugService;
|
||||
|
||||
/// Сервис для работы с путями
|
||||
late final IPathProvider pathProvider;
|
||||
|
||||
/// Конфигурация приложения
|
||||
late final IAppConfig appConfig;
|
||||
|
||||
/// Сервис для работы с локальным хранилищем
|
||||
late final ISecureStorage secureStorage;
|
||||
|
||||
/// Сервис для работы с HTTP запросами
|
||||
late final IHttpClient httpClient;
|
||||
late final IHttpClient Function(IDebugService, IAppConfig) httpClientFactory;
|
||||
|
||||
/// Репозитории приложения
|
||||
late final DiRepositories repositories;
|
||||
|
||||
/// Сервисы приложения
|
||||
late final DiServices services;
|
||||
|
||||
/// Метод для инициализации зависимостей
|
||||
Future<void> init({
|
||||
required OnProgress onProgress,
|
||||
required OnComplete onComplete,
|
||||
required OnError onError,
|
||||
}) async {
|
||||
// Инициализация сервисов
|
||||
await _initServices(
|
||||
onComplete: onComplete,
|
||||
onError: onError,
|
||||
onProgress: onProgress,
|
||||
);
|
||||
|
||||
// Инициализация репозиториев
|
||||
repositories = DiRepositories();
|
||||
repositories.init(
|
||||
onProgress: onProgress,
|
||||
onComplete: onComplete,
|
||||
onError: onError,
|
||||
diContainer: this,
|
||||
);
|
||||
|
||||
onComplete('Инициализация зависимостей завершена!');
|
||||
}
|
||||
|
||||
/// Метод для инициализации сервисов
|
||||
Future<void> _initServices({
|
||||
required OnComplete onComplete,
|
||||
required OnError onError,
|
||||
required OnProgress onProgress,
|
||||
}) async {
|
||||
// Инициализация конфигурации приложения
|
||||
appConfig = switch (env) {
|
||||
AppEnv.dev => AppConfigDev(),
|
||||
AppEnv.prod => AppConfigProd(),
|
||||
AppEnv.stage => AppConfigStage()
|
||||
};
|
||||
|
||||
httpClient = AppHttpClient(
|
||||
debugService: debugService,
|
||||
appConfig: appConfig,
|
||||
);
|
||||
// Инициализация HTTP клиента
|
||||
httpClientFactory = (debugService, appConfig) => AppHttpClient(
|
||||
debugService: debugService,
|
||||
appConfig: appConfig,
|
||||
);
|
||||
|
||||
try {
|
||||
pathProvider = AppPathProvider();
|
||||
onProgress(AppPathProvider.name);
|
||||
} on Object catch (error, stackTrace) {
|
||||
onError(
|
||||
'Ошибка инициализации ${IPathProvider.name}',
|
||||
error,
|
||||
stackTrace,
|
||||
// Инициализация сервисов
|
||||
services = DiServices()
|
||||
..init(
|
||||
onProgress: onProgress,
|
||||
onError: onError,
|
||||
diContainer: this,
|
||||
);
|
||||
// throw Exception('Тестовая - ошибка инициализации зависимостей');
|
||||
// Инициализация репозиториев
|
||||
repositories = DiRepositories()
|
||||
..init(
|
||||
onProgress: onProgress,
|
||||
onError: onError,
|
||||
diContainer: this,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
secureStorage = AppSecureStorage(secretKey: appConfig.secretKey);
|
||||
onProgress(AppSecureStorage.name);
|
||||
} on Object catch (error, stackTrace) {
|
||||
onError(
|
||||
'Ошибка инициализации ${ISecureStorage.name}',
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
onComplete('Инициализация зависимостей завершена!');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,20 +38,21 @@ final class DiRepositories {
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [onProgress] - обратный вызов при прогрессе
|
||||
/// - [onComplete] - обратный вызов при успешной инициализации
|
||||
/// - [diContainer] - контейнер зависимостей
|
||||
void init({
|
||||
required OnProgress onProgress,
|
||||
required OnComplete onComplete,
|
||||
required OnError onError,
|
||||
required DiContainer diContainer,
|
||||
}) {
|
||||
try {
|
||||
//Инициализация репозитория авторизации
|
||||
authRepository = lazyInitRepo<IAuthRepository>(
|
||||
authRepository = _lazyInitRepo<IAuthRepository>(
|
||||
mockFactory: AuthMockRepository.new,
|
||||
mainFactory: () => AuthRepository(
|
||||
httpClient: diContainer.httpClient,
|
||||
httpClient: diContainer.httpClientFactory(
|
||||
diContainer.debugService,
|
||||
diContainer.appConfig,
|
||||
),
|
||||
),
|
||||
onProgress: onProgress,
|
||||
environment: diContainer.env,
|
||||
@@ -67,10 +68,13 @@ final class DiRepositories {
|
||||
|
||||
try {
|
||||
// Инициализация репозитория сервиса управления токеном доступа
|
||||
mainRepository = lazyInitRepo<IMainRepository>(
|
||||
mainRepository = _lazyInitRepo<IMainRepository>(
|
||||
mockFactory: MainMockRepository.new,
|
||||
mainFactory: () => MainRepository(
|
||||
httpClient: diContainer.httpClient,
|
||||
httpClient: diContainer.httpClientFactory(
|
||||
diContainer.debugService,
|
||||
diContainer.appConfig,
|
||||
),
|
||||
),
|
||||
onProgress: onProgress,
|
||||
environment: diContainer.env,
|
||||
@@ -84,7 +88,7 @@ final class DiRepositories {
|
||||
);
|
||||
}
|
||||
|
||||
onComplete(
|
||||
onProgress(
|
||||
'Инициализация репозиториев завершена! Было подменено репозиториев - ${_mockReposToSwitch.length} (${_mockReposToSwitch.join(', ')})',
|
||||
);
|
||||
}
|
||||
@@ -96,18 +100,22 @@ final class DiRepositories {
|
||||
/// - [mockFactory] - функция - фабрика для инициализации репозитория для управления моковыми запросами
|
||||
/// - [mainFactory] - функция - фабрика для инициализации основного репозиторий
|
||||
/// - [onProgress] - обратный вызов при прогрессе
|
||||
T lazyInitRepo<T extends DiBaseRepo>({
|
||||
T _lazyInitRepo<T extends DiBaseRepo>({
|
||||
required AppEnv environment,
|
||||
required T Function() mainFactory,
|
||||
required T Function() mockFactory,
|
||||
required OnProgress onProgress,
|
||||
}) {
|
||||
final mockRepo = mockFactory();
|
||||
final mainRepo = mainFactory();
|
||||
|
||||
final repo = switch (environment) {
|
||||
AppEnv.dev => mockFactory(),
|
||||
AppEnv.prod => mainFactory(),
|
||||
AppEnv.dev => mockRepo,
|
||||
AppEnv.prod => mainRepo,
|
||||
AppEnv.stage =>
|
||||
_mockReposToSwitch.contains(mockFactory().name) ? mockFactory() : mainFactory(),
|
||||
_mockReposToSwitch.contains(mockRepo.name) ? mockRepo : mainRepo,
|
||||
};
|
||||
|
||||
onProgress(repo.name);
|
||||
return repo;
|
||||
}
|
||||
|
||||
50
lib/di/di_services.dart
Normal file
50
lib/di/di_services.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
import 'package:app_services/app_services.dart';
|
||||
import 'package:friflex_starter/di/di_container.dart';
|
||||
import 'package:friflex_starter/di/di_typedefs.dart';
|
||||
import 'package:i_app_services/i_app_services.dart';
|
||||
|
||||
/// Класс для инициализации сервисов
|
||||
final class DiServices {
|
||||
/// Сервис для работы с путями
|
||||
late final IPathProvider pathProvider;
|
||||
|
||||
/// Сервис для работы с локальным хранилищем
|
||||
late final ISecureStorage secureStorage;
|
||||
|
||||
/// Метод для инициализации репозиториев в приложении
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [onProgress] - обратный вызов при прогрессе
|
||||
/// - [diContainer] - контейнер зависимостей
|
||||
/// - [onError] - обратный вызов при ошибке
|
||||
void init({
|
||||
required OnProgress onProgress,
|
||||
required OnError onError,
|
||||
required DiContainer diContainer,
|
||||
}) {
|
||||
try {
|
||||
pathProvider = AppPathProvider();
|
||||
onProgress(AppPathProvider.name);
|
||||
} on Object catch (error, stackTrace) {
|
||||
onError(
|
||||
'Ошибка инициализации ${IPathProvider.name}',
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
try {
|
||||
secureStorage = AppSecureStorage(
|
||||
secretKey: diContainer.appConfig.secretKey,
|
||||
);
|
||||
onProgress(AppSecureStorage.name);
|
||||
} on Object catch (error, stackTrace) {
|
||||
onError(
|
||||
'Ошибка инициализации ${ISecureStorage.name}',
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
|
||||
onProgress('Инициализация сервисов завершена!');
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ class DebugScreen extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
Text(
|
||||
'Реализация SecureStorage: ${context.di.secureStorage.nameImpl}',
|
||||
'Реализация SecureStorage: ${context.di.services.secureStorage.nameImpl}',
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
@@ -82,14 +82,37 @@ class DebugScreen extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text('Тестовая иконка из assets'),
|
||||
const SizedBox(height: 16),
|
||||
Assets.icons.home.svg(),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
throw Exception(
|
||||
'Тестовая ошибка Exception для отладки FlutterError',
|
||||
);
|
||||
},
|
||||
child: const Text('Вызывать ошибку FlutterError'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
await _callError();
|
||||
},
|
||||
child: const Text('Вызывать ошибку PlatformDispatcher'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
await context.di.debugService.openDebugScreen(context);
|
||||
},
|
||||
child: const Text('Вызывать Экран отладки'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> callError() async {
|
||||
Future<void> _callError() async {
|
||||
throw Exception('Тестовая ошибка Exception для отладки PlatformDispatcher');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,16 +5,42 @@ import 'package:flutter/material.dart';
|
||||
/// {@endtemplate}
|
||||
class ErrorScreen extends StatelessWidget {
|
||||
/// {@macro ErrorScreen}
|
||||
const ErrorScreen({super.key});
|
||||
const ErrorScreen({
|
||||
super.key,
|
||||
required this.error,
|
||||
required this.stackTrace,
|
||||
this.onRetry,
|
||||
});
|
||||
|
||||
final Object? error;
|
||||
final StackTrace? stackTrace;
|
||||
final VoidCallback? onRetry;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const MaterialApp(
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: Text(
|
||||
'Что-то пошло не так, попробуйте перезагрузить приложение',
|
||||
textAlign: TextAlign.center,
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: onRetry,
|
||||
child: const Text('Перезагрузить приложение'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'''
|
||||
Что-то пошло не так, попробуйте перезагрузить приложение
|
||||
error: $error
|
||||
stackTrace: $stackTrace
|
||||
''',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
18
lib/features/splash/splash_screen.dart
Normal file
18
lib/features/splash/splash_screen.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:friflex_starter/gen/assets.gen.dart';
|
||||
|
||||
/// {@template SplashScreen}
|
||||
/// Экран загрузки приложения.
|
||||
/// {@endtemplate}
|
||||
class SplashScreen extends StatelessWidget {
|
||||
/// {@macro SplashScreen}
|
||||
const SplashScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Assets.lottie.splash.lottie(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart' as _svg;
|
||||
import 'package:lottie/lottie.dart' as _lottie;
|
||||
import 'package:vector_graphics/vector_graphics.dart' as _vg;
|
||||
|
||||
class $AssetsFontsGen {
|
||||
@@ -50,11 +51,23 @@ class $AssetsIconsGen {
|
||||
List<SvgGenImage> get values => [home];
|
||||
}
|
||||
|
||||
class $AssetsLottieGen {
|
||||
const $AssetsLottieGen();
|
||||
|
||||
/// File path: assets/lottie/splash.json
|
||||
LottieGenImage get splash =>
|
||||
const LottieGenImage('assets/lottie/splash.json');
|
||||
|
||||
/// List of all assets
|
||||
List<LottieGenImage> get values => [splash];
|
||||
}
|
||||
|
||||
class Assets {
|
||||
Assets._();
|
||||
|
||||
static const $AssetsFontsGen fonts = $AssetsFontsGen();
|
||||
static const $AssetsIconsGen icons = $AssetsIconsGen();
|
||||
static const $AssetsLottieGen lottie = $AssetsLottieGen();
|
||||
}
|
||||
|
||||
class SvgGenImage {
|
||||
@@ -133,3 +146,70 @@ class SvgGenImage {
|
||||
|
||||
String get keyName => _assetName;
|
||||
}
|
||||
|
||||
class LottieGenImage {
|
||||
const LottieGenImage(
|
||||
this._assetName, {
|
||||
this.flavors = const {},
|
||||
});
|
||||
|
||||
final String _assetName;
|
||||
final Set<String> flavors;
|
||||
|
||||
_lottie.LottieBuilder lottie({
|
||||
Animation<double>? controller,
|
||||
bool? animate,
|
||||
_lottie.FrameRate? frameRate,
|
||||
bool? repeat,
|
||||
bool? reverse,
|
||||
_lottie.LottieDelegates? delegates,
|
||||
_lottie.LottieOptions? options,
|
||||
void Function(_lottie.LottieComposition)? onLoaded,
|
||||
_lottie.LottieImageProviderFactory? imageProviderFactory,
|
||||
Key? key,
|
||||
AssetBundle? bundle,
|
||||
Widget Function(
|
||||
BuildContext,
|
||||
Widget,
|
||||
_lottie.LottieComposition?,
|
||||
)? frameBuilder,
|
||||
ImageErrorWidgetBuilder? errorBuilder,
|
||||
double? width,
|
||||
double? height,
|
||||
BoxFit? fit,
|
||||
AlignmentGeometry? alignment,
|
||||
String? package,
|
||||
bool? addRepaintBoundary,
|
||||
FilterQuality? filterQuality,
|
||||
void Function(String)? onWarning,
|
||||
}) {
|
||||
return _lottie.Lottie.asset(
|
||||
_assetName,
|
||||
controller: controller,
|
||||
animate: animate,
|
||||
frameRate: frameRate,
|
||||
repeat: repeat,
|
||||
reverse: reverse,
|
||||
delegates: delegates,
|
||||
options: options,
|
||||
onLoaded: onLoaded,
|
||||
imageProviderFactory: imageProviderFactory,
|
||||
key: key,
|
||||
bundle: bundle,
|
||||
frameBuilder: frameBuilder,
|
||||
errorBuilder: errorBuilder,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: fit,
|
||||
alignment: alignment,
|
||||
package: package,
|
||||
addRepaintBoundary: addRepaintBoundary,
|
||||
filterQuality: filterQuality,
|
||||
onWarning: onWarning,
|
||||
);
|
||||
}
|
||||
|
||||
String get path => _assetName;
|
||||
|
||||
String get keyName => _assetName;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:friflex_starter/features/debug/debug_routes.dart';
|
||||
import 'package:friflex_starter/features/debug/i_debug_service.dart';
|
||||
import 'package:friflex_starter/features/main/presentation/main_routes.dart';
|
||||
import 'package:friflex_starter/features/root/root_screen.dart';
|
||||
import 'package:friflex_starter/features/splash/splash_screen.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
/// Класс, реализующий роутер приложения и все поля классов
|
||||
@@ -14,7 +15,7 @@ class AppRouter {
|
||||
static final rootNavigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
/// Начальный роут приложения
|
||||
static String get initialLocation => '/debug';
|
||||
static String get initialLocation => '/debug';
|
||||
|
||||
/// Метод для создания экземпляра GoRouter
|
||||
static GoRouter createRouter(IDebugService debugService) {
|
||||
@@ -32,6 +33,10 @@ class AppRouter {
|
||||
DebugRoutes.buildShellBranch(),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: '/splash',
|
||||
builder: (context, state) => const SplashScreen(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@ import 'package:go_router/go_router.dart';
|
||||
|
||||
part 'errors_handlers.dart';
|
||||
|
||||
/// Время ожидания инициализации зависимостей
|
||||
/// Если время превышено, то будет показан экран ошибки
|
||||
/// В дальнейшем нужно убрать в env
|
||||
const _initTimeout = Duration(seconds: 7);
|
||||
|
||||
/// Класс, реализующий раннер для конфигурирования приложения при запуске
|
||||
///
|
||||
/// Порядок инициализации:
|
||||
@@ -33,38 +38,63 @@ class AppRunner {
|
||||
final AppEnv env;
|
||||
|
||||
/// Контейнер зависимостей приложения
|
||||
late final IDebugService _debugService;
|
||||
late IDebugService _debugService;
|
||||
|
||||
/// Роутер приложения
|
||||
late final GoRouter router;
|
||||
late GoRouter router;
|
||||
|
||||
/// Таймер для отслеживания времени инициализации приложения
|
||||
late final TimerRunner _timerRunner;
|
||||
late TimerRunner _timerRunner;
|
||||
|
||||
/// Метод для запуска приложения
|
||||
Future<void> run() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
// Инициализация сервиса отладки
|
||||
_debugService = DebugService();
|
||||
try {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
// Инициализация сервиса отладки
|
||||
_debugService = DebugService();
|
||||
|
||||
_timerRunner = TimerRunner(_debugService);
|
||||
_timerRunner = TimerRunner(_debugService);
|
||||
|
||||
// Инициализация приложения
|
||||
await _initApp();
|
||||
// Инициализация приложения
|
||||
await _initApp();
|
||||
|
||||
// Инициализация метода обработки ошибок
|
||||
_initErrorHandlers(_debugService);
|
||||
// Инициализация метода обработки ошибок
|
||||
_initErrorHandlers(_debugService);
|
||||
|
||||
// Инициализация репозиториев и сервисов
|
||||
final diContainer = await _initDependencies(_debugService);
|
||||
// Инициализация роутера
|
||||
router = AppRouter.createRouter(_debugService);
|
||||
|
||||
// Инициализация роутера
|
||||
router = AppRouter.createRouter(_debugService);
|
||||
// throw Exception('Test error');
|
||||
|
||||
runApp(
|
||||
App(diContainer: diContainer, router: router),
|
||||
);
|
||||
await _onAppLoaded();
|
||||
runApp(
|
||||
App(
|
||||
router: router,
|
||||
initDependencies: () {
|
||||
return _initDependencies(
|
||||
debugService: _debugService,
|
||||
env: env,
|
||||
timerRunner: _timerRunner,
|
||||
).timeout(
|
||||
_initTimeout,
|
||||
onTimeout: () {
|
||||
return Future.error(
|
||||
TimeoutException(
|
||||
'Превышено время ожидания инициализации зависимостей',
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
await _onAppLoaded();
|
||||
} on Object catch (e, stackTrace) {
|
||||
await _onAppLoaded();
|
||||
|
||||
/// Если произошла ошибка при инициализации приложения,
|
||||
/// то запускаем экран ошибки
|
||||
runApp(ErrorScreen(error: e, stackTrace: stackTrace, onRetry: run));
|
||||
}
|
||||
}
|
||||
|
||||
/// Метод инициализации приложения,
|
||||
@@ -85,23 +115,36 @@ class AppRunner {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
WidgetsBinding.instance.allowFirstFrame();
|
||||
});
|
||||
|
||||
_timerRunner.stop();
|
||||
}
|
||||
|
||||
/// Метод для инициализации зависимостей приложения
|
||||
Future<DiContainer> _initDependencies(IDebugService debugService) async {
|
||||
// Метод для инициализации зависимостей приложения
|
||||
Future<DiContainer> _initDependencies({
|
||||
required IDebugService debugService,
|
||||
required AppEnv env,
|
||||
required TimerRunner timerRunner,
|
||||
}) async {
|
||||
// Имитация задержки инициализации
|
||||
// TODO(yura): Удалить после проверки
|
||||
await Future.delayed(const Duration(seconds: 3));
|
||||
debugService.log(() => 'Тип сборки: ${env.name}');
|
||||
final diContainer = DiContainer(
|
||||
env: env,
|
||||
dService: debugService,
|
||||
);
|
||||
await diContainer.init(
|
||||
onProgress: _timerRunner.logOnProgress,
|
||||
onComplete: _timerRunner.logOnComplete,
|
||||
onError: _timerRunner.logOnError,
|
||||
onProgress: (name) => timerRunner.logOnProgress(name),
|
||||
onComplete: (name) {
|
||||
timerRunner
|
||||
..logOnComplete(name)
|
||||
..stop();
|
||||
},
|
||||
onError: (message, error, [stackTrace]) => debugService.logError(
|
||||
message,
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
),
|
||||
);
|
||||
|
||||
//throw Exception('Test error');
|
||||
return diContainer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ part of 'app_runner.dart';
|
||||
void _initErrorHandlers(IDebugService debugService) {
|
||||
// Обработка ошибок в приложении
|
||||
FlutterError.onError = (details) {
|
||||
_showErrorScreen();
|
||||
_showErrorScreen(details.exception, details.stack);
|
||||
debugService.logError(
|
||||
() => 'FlutterError.onError: ${details.exceptionAsString()}',
|
||||
error: details.exception,
|
||||
@@ -13,7 +13,7 @@ void _initErrorHandlers(IDebugService debugService) {
|
||||
};
|
||||
// Обработка асинхронных ошибок в приложении
|
||||
PlatformDispatcher.instance.onError = (error, stack) {
|
||||
_showErrorScreen();
|
||||
_showErrorScreen(error, stack);
|
||||
debugService.logError(
|
||||
() => 'PlatformDispatcher.instance.onError',
|
||||
error: error,
|
||||
@@ -24,11 +24,11 @@ void _initErrorHandlers(IDebugService debugService) {
|
||||
}
|
||||
|
||||
/// Метод для показа экрана ошибки
|
||||
void _showErrorScreen() {
|
||||
void _showErrorScreen(Object error, StackTrace? stackTrace) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
AppRouter.rootNavigatorKey.currentState?.push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const ErrorScreen(),
|
||||
builder: (_) => ErrorScreen(error: error, stackTrace: stackTrace),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -36,6 +36,7 @@ class TimerRunner {
|
||||
_debugService.log(
|
||||
'$message, прогресс: ${_stopwatch.elapsedMilliseconds} мс',
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/// Метод для обработки прогресса инициализации зависимостей
|
||||
|
||||
26
pubspec.lock
26
pubspec.lock
@@ -29,6 +29,14 @@ packages:
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -585,6 +593,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
lottie:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: lottie
|
||||
sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.1"
|
||||
macros:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -754,6 +770,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
posix:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: posix
|
||||
sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.1"
|
||||
provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -993,4 +1017,4 @@ packages:
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.6.0 <4.0.0"
|
||||
flutter: ">=3.24.0"
|
||||
flutter: ">=3.27.0"
|
||||
|
||||
@@ -21,6 +21,7 @@ dependencies:
|
||||
flutter_svg: 2.0.17
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
lottie: 3.3.1
|
||||
|
||||
### основной сервис с интерфейсами
|
||||
i_app_services:
|
||||
@@ -51,6 +52,7 @@ flutter:
|
||||
- assets/icons/
|
||||
- assets/fonts/
|
||||
- assets/images/
|
||||
- assets/lottie/
|
||||
|
||||
fonts:
|
||||
- family: Montserrat
|
||||
@@ -72,4 +74,5 @@ flutter_gen:
|
||||
line_length: 100
|
||||
integrations:
|
||||
flutter_svg: true
|
||||
lottie: true
|
||||
|
||||
|
||||
Reference in New Issue
Block a user