mirror of
https://github.com/smmarty/friflex_flutter_starter.git
synced 2026-02-05 03:32:18 +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:flutter/material.dart';
|
||||||
import 'package:friflex_starter/app/app_context_ext.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/app_theme.dart';
|
||||||
import 'package:friflex_starter/app/theme/theme_notifier.dart';
|
import 'package:friflex_starter/app/theme/theme_notifier.dart';
|
||||||
import 'package:friflex_starter/di/di_container.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/gen/app_localizations.dart';
|
||||||
import 'package:friflex_starter/l10n/localization_notifier.dart';
|
import 'package:friflex_starter/l10n/localization_notifier.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
/// Класс для реализации объекта приложения
|
/// Класс приложения
|
||||||
class App extends StatelessWidget {
|
class App extends StatefulWidget {
|
||||||
/// Создает экземпляр приложения
|
|
||||||
///
|
|
||||||
/// Принимает:
|
|
||||||
/// - [diContainer] - набор зависимостей приложения
|
|
||||||
/// - [router] - экземпляр роутера приложения
|
|
||||||
const App({
|
const App({
|
||||||
super.key,
|
super.key,
|
||||||
required this.diContainer,
|
|
||||||
required this.router,
|
required this.router,
|
||||||
|
required this.initDependencies,
|
||||||
});
|
});
|
||||||
|
/// Роутер приложения
|
||||||
/// Набор зависимостей приложения
|
|
||||||
final DiContainer diContainer;
|
|
||||||
|
|
||||||
/// Экземпляр роутера приложения
|
|
||||||
final GoRouter router;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AppProviders(
|
return AppProviders(
|
||||||
diContainer: diContainer,
|
// Consumer для локализации добавляем выше чем DependsProviders
|
||||||
|
// чтобы при изменении локализации перестраивался весь виджет
|
||||||
|
// Но, это не обязательно, можно добавить в DependsProviders
|
||||||
child: LocalizationConsumer(
|
child: LocalizationConsumer(
|
||||||
builder: () => ThemeConsumer(
|
builder: () => FutureBuilder<DiContainer>(
|
||||||
builder: () => _App(router: router),
|
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 {
|
class _App extends StatelessWidget {
|
||||||
const _App({required this.router});
|
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/app_config.dart';
|
||||||
import 'package:friflex_starter/app/app_config/i_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/app_env.dart';
|
||||||
import 'package:friflex_starter/app/http/app_http_client.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/app/http/i_http_client.dart';
|
||||||
import 'package:friflex_starter/di/di_repositories.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/di/di_typedefs.dart';
|
||||||
import 'package:friflex_starter/features/debug/i_debug_service.dart';
|
import 'package:friflex_starter/features/debug/i_debug_service.dart';
|
||||||
import 'package:i_app_services/i_app_services.dart';
|
|
||||||
|
|
||||||
/// {@template dependencies_container}
|
/// {@template dependencies_container}
|
||||||
/// Контейнер для зависимостей
|
/// Контейнер для зависимостей
|
||||||
@@ -22,82 +21,53 @@ final class DiContainer {
|
|||||||
/// Сервис для отладки, получаем из конструктора
|
/// Сервис для отладки, получаем из конструктора
|
||||||
late final IDebugService debugService;
|
late final IDebugService debugService;
|
||||||
|
|
||||||
/// Сервис для работы с путями
|
|
||||||
late final IPathProvider pathProvider;
|
|
||||||
|
|
||||||
/// Конфигурация приложения
|
/// Конфигурация приложения
|
||||||
late final IAppConfig appConfig;
|
late final IAppConfig appConfig;
|
||||||
|
|
||||||
/// Сервис для работы с локальным хранилищем
|
|
||||||
late final ISecureStorage secureStorage;
|
|
||||||
|
|
||||||
/// Сервис для работы с HTTP запросами
|
/// Сервис для работы с HTTP запросами
|
||||||
late final IHttpClient httpClient;
|
late final IHttpClient Function(IDebugService, IAppConfig) httpClientFactory;
|
||||||
|
|
||||||
|
/// Репозитории приложения
|
||||||
late final DiRepositories repositories;
|
late final DiRepositories repositories;
|
||||||
|
|
||||||
|
/// Сервисы приложения
|
||||||
|
late final DiServices services;
|
||||||
|
|
||||||
/// Метод для инициализации зависимостей
|
/// Метод для инициализации зависимостей
|
||||||
Future<void> init({
|
Future<void> init({
|
||||||
required OnProgress onProgress,
|
required OnProgress onProgress,
|
||||||
required OnComplete onComplete,
|
required OnComplete onComplete,
|
||||||
required OnError onError,
|
required OnError onError,
|
||||||
}) async {
|
}) 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) {
|
appConfig = switch (env) {
|
||||||
AppEnv.dev => AppConfigDev(),
|
AppEnv.dev => AppConfigDev(),
|
||||||
AppEnv.prod => AppConfigProd(),
|
AppEnv.prod => AppConfigProd(),
|
||||||
AppEnv.stage => AppConfigStage()
|
AppEnv.stage => AppConfigStage()
|
||||||
};
|
};
|
||||||
|
|
||||||
httpClient = AppHttpClient(
|
// Инициализация HTTP клиента
|
||||||
debugService: debugService,
|
httpClientFactory = (debugService, appConfig) => AppHttpClient(
|
||||||
appConfig: appConfig,
|
debugService: debugService,
|
||||||
);
|
appConfig: appConfig,
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
// Инициализация сервисов
|
||||||
pathProvider = AppPathProvider();
|
services = DiServices()
|
||||||
onProgress(AppPathProvider.name);
|
..init(
|
||||||
} on Object catch (error, stackTrace) {
|
onProgress: onProgress,
|
||||||
onError(
|
onError: onError,
|
||||||
'Ошибка инициализации ${IPathProvider.name}',
|
diContainer: this,
|
||||||
error,
|
);
|
||||||
stackTrace,
|
// throw Exception('Тестовая - ошибка инициализации зависимостей');
|
||||||
|
// Инициализация репозиториев
|
||||||
|
repositories = DiRepositories()
|
||||||
|
..init(
|
||||||
|
onProgress: onProgress,
|
||||||
|
onError: onError,
|
||||||
|
diContainer: this,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
onComplete('Инициализация зависимостей завершена!');
|
||||||
secureStorage = AppSecureStorage(secretKey: appConfig.secretKey);
|
|
||||||
onProgress(AppSecureStorage.name);
|
|
||||||
} on Object catch (error, stackTrace) {
|
|
||||||
onError(
|
|
||||||
'Ошибка инициализации ${ISecureStorage.name}',
|
|
||||||
error,
|
|
||||||
stackTrace,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,20 +38,21 @@ final class DiRepositories {
|
|||||||
///
|
///
|
||||||
/// Принимает:
|
/// Принимает:
|
||||||
/// - [onProgress] - обратный вызов при прогрессе
|
/// - [onProgress] - обратный вызов при прогрессе
|
||||||
/// - [onComplete] - обратный вызов при успешной инициализации
|
|
||||||
/// - [diContainer] - контейнер зависимостей
|
/// - [diContainer] - контейнер зависимостей
|
||||||
void init({
|
void init({
|
||||||
required OnProgress onProgress,
|
required OnProgress onProgress,
|
||||||
required OnComplete onComplete,
|
|
||||||
required OnError onError,
|
required OnError onError,
|
||||||
required DiContainer diContainer,
|
required DiContainer diContainer,
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
//Инициализация репозитория авторизации
|
//Инициализация репозитория авторизации
|
||||||
authRepository = lazyInitRepo<IAuthRepository>(
|
authRepository = _lazyInitRepo<IAuthRepository>(
|
||||||
mockFactory: AuthMockRepository.new,
|
mockFactory: AuthMockRepository.new,
|
||||||
mainFactory: () => AuthRepository(
|
mainFactory: () => AuthRepository(
|
||||||
httpClient: diContainer.httpClient,
|
httpClient: diContainer.httpClientFactory(
|
||||||
|
diContainer.debugService,
|
||||||
|
diContainer.appConfig,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onProgress: onProgress,
|
onProgress: onProgress,
|
||||||
environment: diContainer.env,
|
environment: diContainer.env,
|
||||||
@@ -67,10 +68,13 @@ final class DiRepositories {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Инициализация репозитория сервиса управления токеном доступа
|
// Инициализация репозитория сервиса управления токеном доступа
|
||||||
mainRepository = lazyInitRepo<IMainRepository>(
|
mainRepository = _lazyInitRepo<IMainRepository>(
|
||||||
mockFactory: MainMockRepository.new,
|
mockFactory: MainMockRepository.new,
|
||||||
mainFactory: () => MainRepository(
|
mainFactory: () => MainRepository(
|
||||||
httpClient: diContainer.httpClient,
|
httpClient: diContainer.httpClientFactory(
|
||||||
|
diContainer.debugService,
|
||||||
|
diContainer.appConfig,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onProgress: onProgress,
|
onProgress: onProgress,
|
||||||
environment: diContainer.env,
|
environment: diContainer.env,
|
||||||
@@ -84,7 +88,7 @@ final class DiRepositories {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onComplete(
|
onProgress(
|
||||||
'Инициализация репозиториев завершена! Было подменено репозиториев - ${_mockReposToSwitch.length} (${_mockReposToSwitch.join(', ')})',
|
'Инициализация репозиториев завершена! Было подменено репозиториев - ${_mockReposToSwitch.length} (${_mockReposToSwitch.join(', ')})',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -96,18 +100,22 @@ final class DiRepositories {
|
|||||||
/// - [mockFactory] - функция - фабрика для инициализации репозитория для управления моковыми запросами
|
/// - [mockFactory] - функция - фабрика для инициализации репозитория для управления моковыми запросами
|
||||||
/// - [mainFactory] - функция - фабрика для инициализации основного репозиторий
|
/// - [mainFactory] - функция - фабрика для инициализации основного репозиторий
|
||||||
/// - [onProgress] - обратный вызов при прогрессе
|
/// - [onProgress] - обратный вызов при прогрессе
|
||||||
T lazyInitRepo<T extends DiBaseRepo>({
|
T _lazyInitRepo<T extends DiBaseRepo>({
|
||||||
required AppEnv environment,
|
required AppEnv environment,
|
||||||
required T Function() mainFactory,
|
required T Function() mainFactory,
|
||||||
required T Function() mockFactory,
|
required T Function() mockFactory,
|
||||||
required OnProgress onProgress,
|
required OnProgress onProgress,
|
||||||
}) {
|
}) {
|
||||||
|
final mockRepo = mockFactory();
|
||||||
|
final mainRepo = mainFactory();
|
||||||
|
|
||||||
final repo = switch (environment) {
|
final repo = switch (environment) {
|
||||||
AppEnv.dev => mockFactory(),
|
AppEnv.dev => mockRepo,
|
||||||
AppEnv.prod => mainFactory(),
|
AppEnv.prod => mainRepo,
|
||||||
AppEnv.stage =>
|
AppEnv.stage =>
|
||||||
_mockReposToSwitch.contains(mockFactory().name) ? mockFactory() : mainFactory(),
|
_mockReposToSwitch.contains(mockRepo.name) ? mockRepo : mainRepo,
|
||||||
};
|
};
|
||||||
|
|
||||||
onProgress(repo.name);
|
onProgress(repo.name);
|
||||||
return repo;
|
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),
|
padding: const EdgeInsets.all(16),
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Реализация SecureStorage: ${context.di.secureStorage.nameImpl}',
|
'Реализация SecureStorage: ${context.di.services.secureStorage.nameImpl}',
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
@@ -82,14 +82,37 @@ class DebugScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
const Text('Тестовая иконка из assets'),
|
const Text('Тестовая иконка из assets'),
|
||||||
|
const SizedBox(height: 16),
|
||||||
Assets.icons.home.svg(),
|
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');
|
throw Exception('Тестовая ошибка Exception для отладки PlatformDispatcher');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,16 +5,42 @@ import 'package:flutter/material.dart';
|
|||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class ErrorScreen extends StatelessWidget {
|
class ErrorScreen extends StatelessWidget {
|
||||||
/// {@macro ErrorScreen}
|
/// {@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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const MaterialApp(
|
return MaterialApp(
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
body: Center(
|
body: SafeArea(
|
||||||
child: Text(
|
child: Center(
|
||||||
'Что-то пошло не так, попробуйте перезагрузить приложение',
|
child: ListView(
|
||||||
textAlign: TextAlign.center,
|
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/services.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart' as _svg;
|
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;
|
import 'package:vector_graphics/vector_graphics.dart' as _vg;
|
||||||
|
|
||||||
class $AssetsFontsGen {
|
class $AssetsFontsGen {
|
||||||
@@ -50,11 +51,23 @@ class $AssetsIconsGen {
|
|||||||
List<SvgGenImage> get values => [home];
|
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 {
|
class Assets {
|
||||||
Assets._();
|
Assets._();
|
||||||
|
|
||||||
static const $AssetsFontsGen fonts = $AssetsFontsGen();
|
static const $AssetsFontsGen fonts = $AssetsFontsGen();
|
||||||
static const $AssetsIconsGen icons = $AssetsIconsGen();
|
static const $AssetsIconsGen icons = $AssetsIconsGen();
|
||||||
|
static const $AssetsLottieGen lottie = $AssetsLottieGen();
|
||||||
}
|
}
|
||||||
|
|
||||||
class SvgGenImage {
|
class SvgGenImage {
|
||||||
@@ -133,3 +146,70 @@ class SvgGenImage {
|
|||||||
|
|
||||||
String get keyName => _assetName;
|
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/debug/i_debug_service.dart';
|
||||||
import 'package:friflex_starter/features/main/presentation/main_routes.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/root/root_screen.dart';
|
||||||
|
import 'package:friflex_starter/features/splash/splash_screen.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
/// Класс, реализующий роутер приложения и все поля классов
|
/// Класс, реализующий роутер приложения и все поля классов
|
||||||
@@ -32,6 +33,10 @@ class AppRouter {
|
|||||||
DebugRoutes.buildShellBranch(),
|
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';
|
part 'errors_handlers.dart';
|
||||||
|
|
||||||
|
/// Время ожидания инициализации зависимостей
|
||||||
|
/// Если время превышено, то будет показан экран ошибки
|
||||||
|
/// В дальнейшем нужно убрать в env
|
||||||
|
const _initTimeout = Duration(seconds: 7);
|
||||||
|
|
||||||
/// Класс, реализующий раннер для конфигурирования приложения при запуске
|
/// Класс, реализующий раннер для конфигурирования приложения при запуске
|
||||||
///
|
///
|
||||||
/// Порядок инициализации:
|
/// Порядок инициализации:
|
||||||
@@ -33,38 +38,63 @@ class AppRunner {
|
|||||||
final AppEnv env;
|
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 {
|
Future<void> run() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
try {
|
||||||
// Инициализация сервиса отладки
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
_debugService = DebugService();
|
// Инициализация сервиса отладки
|
||||||
|
_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);
|
||||||
|
|
||||||
// Инициализация роутера
|
// throw Exception('Test error');
|
||||||
router = AppRouter.createRouter(_debugService);
|
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
App(diContainer: diContainer, router: router),
|
App(
|
||||||
);
|
router: router,
|
||||||
await _onAppLoaded();
|
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.addPostFrameCallback((_) {
|
||||||
WidgetsBinding.instance.allowFirstFrame();
|
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}');
|
debugService.log(() => 'Тип сборки: ${env.name}');
|
||||||
final diContainer = DiContainer(
|
final diContainer = DiContainer(
|
||||||
env: env,
|
env: env,
|
||||||
dService: debugService,
|
dService: debugService,
|
||||||
);
|
);
|
||||||
await diContainer.init(
|
await diContainer.init(
|
||||||
onProgress: _timerRunner.logOnProgress,
|
onProgress: (name) => timerRunner.logOnProgress(name),
|
||||||
onComplete: _timerRunner.logOnComplete,
|
onComplete: (name) {
|
||||||
onError: _timerRunner.logOnError,
|
timerRunner
|
||||||
|
..logOnComplete(name)
|
||||||
|
..stop();
|
||||||
|
},
|
||||||
|
onError: (message, error, [stackTrace]) => debugService.logError(
|
||||||
|
message,
|
||||||
|
error: error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
//throw Exception('Test error');
|
||||||
return diContainer;
|
return diContainer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ part of 'app_runner.dart';
|
|||||||
void _initErrorHandlers(IDebugService debugService) {
|
void _initErrorHandlers(IDebugService debugService) {
|
||||||
// Обработка ошибок в приложении
|
// Обработка ошибок в приложении
|
||||||
FlutterError.onError = (details) {
|
FlutterError.onError = (details) {
|
||||||
_showErrorScreen();
|
_showErrorScreen(details.exception, details.stack);
|
||||||
debugService.logError(
|
debugService.logError(
|
||||||
() => 'FlutterError.onError: ${details.exceptionAsString()}',
|
() => 'FlutterError.onError: ${details.exceptionAsString()}',
|
||||||
error: details.exception,
|
error: details.exception,
|
||||||
@@ -13,7 +13,7 @@ void _initErrorHandlers(IDebugService debugService) {
|
|||||||
};
|
};
|
||||||
// Обработка асинхронных ошибок в приложении
|
// Обработка асинхронных ошибок в приложении
|
||||||
PlatformDispatcher.instance.onError = (error, stack) {
|
PlatformDispatcher.instance.onError = (error, stack) {
|
||||||
_showErrorScreen();
|
_showErrorScreen(error, stack);
|
||||||
debugService.logError(
|
debugService.logError(
|
||||||
() => 'PlatformDispatcher.instance.onError',
|
() => 'PlatformDispatcher.instance.onError',
|
||||||
error: error,
|
error: error,
|
||||||
@@ -24,11 +24,11 @@ void _initErrorHandlers(IDebugService debugService) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Метод для показа экрана ошибки
|
/// Метод для показа экрана ошибки
|
||||||
void _showErrorScreen() {
|
void _showErrorScreen(Object error, StackTrace? stackTrace) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
AppRouter.rootNavigatorKey.currentState?.push(
|
AppRouter.rootNavigatorKey.currentState?.push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => const ErrorScreen(),
|
builder: (_) => ErrorScreen(error: error, stackTrace: stackTrace),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class TimerRunner {
|
|||||||
_debugService.log(
|
_debugService.log(
|
||||||
'$message, прогресс: ${_stopwatch.elapsedMilliseconds} мс',
|
'$message, прогресс: ${_stopwatch.elapsedMilliseconds} мс',
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Метод для обработки прогресса инициализации зависимостей
|
/// Метод для обработки прогресса инициализации зависимостей
|
||||||
|
|||||||
26
pubspec.lock
26
pubspec.lock
@@ -29,6 +29,14 @@ packages:
|
|||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
archive:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: archive
|
||||||
|
sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.2"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -585,6 +593,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.0"
|
||||||
|
lottie:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: lottie
|
||||||
|
sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.3.1"
|
||||||
macros:
|
macros:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -754,6 +770,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.1"
|
version: "1.5.1"
|
||||||
|
posix:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: posix
|
||||||
|
sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.1"
|
||||||
provider:
|
provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -993,4 +1017,4 @@ packages:
|
|||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.6.0 <4.0.0"
|
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_svg: 2.0.17
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
lottie: 3.3.1
|
||||||
|
|
||||||
### основной сервис с интерфейсами
|
### основной сервис с интерфейсами
|
||||||
i_app_services:
|
i_app_services:
|
||||||
@@ -51,6 +52,7 @@ flutter:
|
|||||||
- assets/icons/
|
- assets/icons/
|
||||||
- assets/fonts/
|
- assets/fonts/
|
||||||
- assets/images/
|
- assets/images/
|
||||||
|
- assets/lottie/
|
||||||
|
|
||||||
fonts:
|
fonts:
|
||||||
- family: Montserrat
|
- family: Montserrat
|
||||||
@@ -72,4 +74,5 @@ flutter_gen:
|
|||||||
line_length: 100
|
line_length: 100
|
||||||
integrations:
|
integrations:
|
||||||
flutter_svg: true
|
flutter_svg: true
|
||||||
|
lottie: true
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user