mirror of
https://github.com/smmarty/friflex_flutter_starter.git
synced 2026-02-05 03:32:18 +00:00
Merge branch 'main' into feat/перенести-роутер-ближе-к-месту-инициализации
This commit is contained in:
180
lib/app/app.dart
180
lib/app/app.dart
@@ -1,180 +0,0 @@
|
||||
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<DiContainer> Function() initDependencies;
|
||||
|
||||
@override
|
||||
State<App> createState() => _AppState();
|
||||
}
|
||||
|
||||
/// {@template app_state}
|
||||
/// Состояние главного виджета приложения.
|
||||
///
|
||||
/// Управляет процессом инициализации зависимостей и отображением
|
||||
/// соответствующих экранов в зависимости от состояния инициализации.
|
||||
/// {@endtemplate}
|
||||
class _AppState extends State<App> {
|
||||
/// {@macro app_state}
|
||||
_AppState();
|
||||
|
||||
/// Мутабельная Future для инициализации зависимостей
|
||||
/// Позволяет перезапускать инициализацию при ошибках
|
||||
late Future<DiContainer> _initFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initFuture = widget.initDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppProviders(
|
||||
// Consumer для локализации добавляем выше чем DependsProviders
|
||||
// чтобы при изменении локализации перестраивался весь виджет
|
||||
// Но, это не обязательно, можно добавить в DependsProviders
|
||||
child: LocalizationConsumer(
|
||||
builder: () => FutureBuilder<DiContainer>(
|
||||
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,
|
||||
@visibleForTesting this.mockRouter, // ignore: unused_element_parameter
|
||||
});
|
||||
|
||||
/// Контейнер зависимостей
|
||||
final DiContainer diContainer;
|
||||
|
||||
/// Роутер приложения для навигации для тестирования
|
||||
final GoRouter? mockRouter;
|
||||
|
||||
@override
|
||||
State<_AppInternal> createState() => _AppInternalState();
|
||||
}
|
||||
|
||||
class _AppInternalState extends State<_AppInternal> {
|
||||
/// Роутер приложения для навигации
|
||||
late final GoRouter router;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
router =
|
||||
widget.mockRouter ??
|
||||
AppRouter.createRouter(widget.diContainer.debugService);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
router.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DependsProviders(
|
||||
diContainer: widget.diContainer,
|
||||
child: BlocConsumer<UpdateCubit, UpdateState>(
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,28 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:friflex_starter/app/theme/theme_notifier.dart';
|
||||
import 'package:friflex_starter/di/di_container.dart';
|
||||
import 'package:friflex_starter/features/update/domain/state/cubit/update_cubit.dart';
|
||||
import 'package:friflex_starter/l10n/localization_notifier.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// Класс для добавления провайдеров темы и локализации
|
||||
/// {@template app_providers}
|
||||
/// Класс для добавления зависимостей приложения
|
||||
/// {@endtemplate}
|
||||
final class AppProviders extends StatelessWidget {
|
||||
const AppProviders({required this.child, super.key});
|
||||
/// {@macro app_providers}
|
||||
const AppProviders({
|
||||
required this.child,
|
||||
required this.diContainer,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// Виджет, который будет отображаться внутри провайдеров
|
||||
final Widget child;
|
||||
|
||||
/// Контейнер зависимостей
|
||||
final DiContainer diContainer;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiProvider(
|
||||
@@ -19,6 +33,10 @@ final class AppProviders extends StatelessWidget {
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => LocalizationNotifier(),
|
||||
), // Провайдер для локализации
|
||||
Provider.value(value: diContainer), // Передаем контейнер зависимостей
|
||||
BlocProvider(
|
||||
create: (_) => UpdateCubit(diContainer.repositories.updateRepository),
|
||||
),
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
|
||||
54
lib/app/app_root.dart
Normal file
54
lib/app/app_root.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
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/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/l10n/gen/app_localizations.dart';
|
||||
import 'package:friflex_starter/l10n/localization_notifier.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
/// {@template app}
|
||||
/// Главный виджет приложения, отображающий основной интерфейс приложения
|
||||
///
|
||||
/// Отвечает за:
|
||||
/// - Настройку провайдеров для темы и локализации
|
||||
/// {@endtemplate}
|
||||
class AppRoot extends StatelessWidget {
|
||||
/// {@macro app_root}
|
||||
const AppRoot({required this.diContainer, required this.router, super.key});
|
||||
|
||||
/// Контейнер зависимостей
|
||||
final DiContainer diContainer;
|
||||
|
||||
/// Роутер приложения
|
||||
final GoRouter router;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppProviders(
|
||||
diContainer: diContainer,
|
||||
child: LocalizationConsumer(
|
||||
builder: (localizationContext) {
|
||||
return ThemeConsumer(
|
||||
builder: (themeContext) => MediaQuery(
|
||||
key: const ValueKey('prevent_rebuild'),
|
||||
data: MediaQuery.of(
|
||||
themeContext,
|
||||
).copyWith(textScaler: TextScaler.noScaling, boldText: false),
|
||||
child: MaterialApp.router(
|
||||
darkTheme: AppTheme.dark,
|
||||
theme: AppTheme.light,
|
||||
themeMode: themeContext.theme.themeMode,
|
||||
locale: localizationContext.localization.locale,
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
routerConfig: router,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:friflex_starter/di/di_container.dart';
|
||||
import 'package:friflex_starter/features/update/domain/state/cubit/update_cubit.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// Класс для внедрения глобальных зависимостей
|
||||
final class DependsProviders extends StatelessWidget {
|
||||
const DependsProviders({
|
||||
required this.child,
|
||||
required this.diContainer,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final DiContainer diContainer;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
// Сюда добавляем глобальные блоки, inherited и т.д.
|
||||
Provider.value(value: diContainer), // Передаем контейнер зависимостей
|
||||
BlocProvider(
|
||||
create: (_) {
|
||||
final updateCubit = UpdateCubit(
|
||||
diContainer.repositories.updatesRepository,
|
||||
);
|
||||
unawaited(
|
||||
updateCubit.checkForUpdates(
|
||||
versionCode:
|
||||
'1.0.0', // TODO(yura): заменить на получение из diContainer
|
||||
platform: 'android',
|
||||
),
|
||||
);
|
||||
return updateCubit;
|
||||
},
|
||||
),
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:friflex_starter/app/app_config/app_config.dart';
|
||||
import 'package:friflex_starter/app/http/i_http_client.dart';
|
||||
|
||||
import 'package:friflex_starter/features/debug/i_debug_service.dart';
|
||||
|
||||
/// {@template app_http_client}
|
||||
/// Класс для реализации HTTP-клиента для управления запросами
|
||||
/// {@endtemplate}
|
||||
final class AppHttpClient implements IHttpClient {
|
||||
final class AppHttpClient {
|
||||
/// Создает HTTP клиент
|
||||
///
|
||||
/// Принимает:
|
||||
@@ -18,7 +17,6 @@ final class AppHttpClient implements IHttpClient {
|
||||
required IAppConfig appConfig,
|
||||
}) {
|
||||
_httpClient = Dio();
|
||||
_appConfig = appConfig;
|
||||
|
||||
_httpClient.options
|
||||
..baseUrl = appConfig.baseUrl
|
||||
@@ -30,111 +28,8 @@ final class AppHttpClient implements IHttpClient {
|
||||
_httpClient.interceptors.add(debugService.dioLogger);
|
||||
}
|
||||
|
||||
/// Конфигурация приложения
|
||||
late final IAppConfig _appConfig;
|
||||
|
||||
/// Экземпляр HTTP клиента
|
||||
late final Dio _httpClient;
|
||||
|
||||
@override
|
||||
Future<Response> get(
|
||||
String path, {
|
||||
Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Options? options,
|
||||
}) async {
|
||||
_httpClient.options.baseUrl = _appConfig.baseUrl;
|
||||
|
||||
return _httpClient.get(
|
||||
path,
|
||||
data: data,
|
||||
queryParameters: queryParameters,
|
||||
options: options,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response> post(
|
||||
String path, {
|
||||
Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Options? options,
|
||||
}) async {
|
||||
_httpClient.options.baseUrl = _appConfig.baseUrl;
|
||||
|
||||
return _httpClient.post(
|
||||
path,
|
||||
data: data,
|
||||
queryParameters: queryParameters,
|
||||
options: options,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response> patch(
|
||||
String path, {
|
||||
Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Options? options,
|
||||
}) async {
|
||||
_httpClient.options.baseUrl = _appConfig.baseUrl;
|
||||
|
||||
return _httpClient.patch(
|
||||
path,
|
||||
data: data,
|
||||
queryParameters: queryParameters,
|
||||
options: options,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response> put(
|
||||
String path, {
|
||||
Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Options? options,
|
||||
}) async {
|
||||
_httpClient.options.baseUrl = _appConfig.baseUrl;
|
||||
|
||||
return _httpClient.put(
|
||||
path,
|
||||
data: data,
|
||||
queryParameters: queryParameters,
|
||||
options: options,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response> delete(
|
||||
String path, {
|
||||
Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Options? options,
|
||||
}) async {
|
||||
_httpClient.options.baseUrl = _appConfig.baseUrl;
|
||||
|
||||
return _httpClient.delete(
|
||||
path,
|
||||
data: data,
|
||||
queryParameters: queryParameters,
|
||||
options: options,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Response> head(
|
||||
String path, {
|
||||
Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Options? options,
|
||||
}) async {
|
||||
_httpClient.options.baseUrl = _appConfig.baseUrl;
|
||||
|
||||
return _httpClient.head(
|
||||
path,
|
||||
data: data,
|
||||
queryParameters: queryParameters,
|
||||
options: options,
|
||||
);
|
||||
}
|
||||
Dio get client => _httpClient;
|
||||
}
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
/// Класс для описания интерфейса сервиса по управлению HTTP запросами
|
||||
abstract interface class IHttpClient {
|
||||
/// Описывает поля HTTP клиента
|
||||
const IHttpClient();
|
||||
|
||||
/// Наименование сервиса
|
||||
static const name = 'IHttpClient';
|
||||
|
||||
/// Метод для реализации запроса GET
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [path] - путь к ресурсу
|
||||
/// - [data] - тело запроса
|
||||
/// - [queryParameters] - параметры запроса
|
||||
/// - [options] - конфигурация запроса
|
||||
Future<Response> get(
|
||||
String path, {
|
||||
Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Options? options,
|
||||
});
|
||||
|
||||
/// Метод для реализации запроса POST
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [path] - путь к ресурсу
|
||||
/// - [data] - тело запроса
|
||||
/// - [queryParameters] - параметры запроса
|
||||
/// - [options] - конфигурация запроса
|
||||
Future<Response> post(
|
||||
String path, {
|
||||
Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Options? options,
|
||||
});
|
||||
|
||||
/// Метод для реализации запроса PATCH
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [path] - путь к ресурсу
|
||||
/// - [data] - тело запроса
|
||||
/// - [queryParameters] - параметры запроса
|
||||
/// - [options] - конфигурация запроса
|
||||
Future<Response> patch(
|
||||
String path, {
|
||||
Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Options? options,
|
||||
});
|
||||
|
||||
/// Метод для реализации запроса PUT
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [path] - путь к ресурсу
|
||||
/// - [data] - тело запроса
|
||||
/// - [queryParameters] - параметры запроса
|
||||
/// - [options] - конфигурация запроса
|
||||
Future<Response> put(
|
||||
String path, {
|
||||
Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Options? options,
|
||||
});
|
||||
|
||||
/// Метод для реализации запроса DELETE
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [path] - путь к ресурсу
|
||||
/// - [data] - тело запроса
|
||||
/// - [queryParameters] - параметры запроса
|
||||
/// - [options] - конфигурация запроса
|
||||
Future<Response> delete(
|
||||
String path, {
|
||||
Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Options? options,
|
||||
});
|
||||
|
||||
/// Метод для реализации запроса POST
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [path] - путь к ресурсу
|
||||
/// - [data] - тело запроса
|
||||
/// - [queryParameters] - параметры запроса
|
||||
/// - [options] - конфигурация запроса
|
||||
Future<Response> head(
|
||||
String path, {
|
||||
Object? data,
|
||||
Map<String, dynamic>? queryParameters,
|
||||
Options? options,
|
||||
});
|
||||
}
|
||||
@@ -55,7 +55,7 @@ class AppColors extends ThemeExtension<AppColors> with _$AppColorsTailorMixin {
|
||||
);
|
||||
|
||||
/// Цвета тёмной темы
|
||||
static const AppColors dark = AppColors(
|
||||
static const AppColors dark = AppColors(
|
||||
testColor: Colors.green,
|
||||
errorSnackbarBackground: Color(0xFF638B8B),
|
||||
successSnackbarBackground: Color(0xFF93C499),
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// Тип функции для построения виджета с учетом темы
|
||||
typedef ThemeBuilder = Widget Function();
|
||||
typedef ThemeBuilder = Widget Function(BuildContext context);
|
||||
|
||||
/// {@template theme_consumer}
|
||||
/// Виджет для подписки на изменения темы приложения.
|
||||
@@ -20,8 +20,8 @@ class ThemeConsumer extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<ThemeNotifier>(
|
||||
builder: (_, _, _) {
|
||||
return builder();
|
||||
builder: (context, _, _) {
|
||||
return builder(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -290,21 +290,9 @@ class _Icon extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return switch (type) {
|
||||
.success => const Icon(
|
||||
Icons.check_circle,
|
||||
color: Colors.white,
|
||||
size: 32,
|
||||
),
|
||||
.error => const Icon(
|
||||
Icons.error,
|
||||
color: Colors.white,
|
||||
size: 32,
|
||||
),
|
||||
.info => const Icon(
|
||||
Icons.info,
|
||||
color: Colors.white,
|
||||
size: 32,
|
||||
),
|
||||
.success => const Icon(Icons.check_circle, color: Colors.white, size: 32),
|
||||
.error => const Icon(Icons.error, color: Colors.white, size: 32),
|
||||
.info => const Icon(Icons.info, color: Colors.white, size: 32),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:friflex_starter/app/app_config/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';
|
||||
@@ -25,7 +24,7 @@ final class DiContainer {
|
||||
late final IAppConfig appConfig;
|
||||
|
||||
/// Сервис для работы с HTTP запросами
|
||||
late final IHttpClient Function(IDebugService, IAppConfig) httpClientFactory;
|
||||
late final AppHttpClient httpClient;
|
||||
|
||||
/// Репозитории приложения
|
||||
late final DiRepositories repositories;
|
||||
@@ -47,8 +46,10 @@ final class DiContainer {
|
||||
};
|
||||
|
||||
// Инициализация HTTP клиента
|
||||
httpClientFactory = (debugService, appConfig) =>
|
||||
AppHttpClient(debugService: debugService, appConfig: appConfig);
|
||||
httpClient = AppHttpClient(
|
||||
debugService: debugService,
|
||||
appConfig: appConfig,
|
||||
);
|
||||
|
||||
// Инициализация сервисов
|
||||
services = DiServices()
|
||||
|
||||
@@ -2,9 +2,6 @@ import 'package:friflex_starter/app/app_env.dart';
|
||||
import 'package:friflex_starter/di/di_base_repo.dart';
|
||||
import 'package:friflex_starter/di/di_container.dart';
|
||||
import 'package:friflex_starter/di/di_typedefs.dart';
|
||||
import 'package:friflex_starter/features/auth/data/repository/auth_mock_repository.dart';
|
||||
import 'package:friflex_starter/features/auth/data/repository/auth_repository.dart';
|
||||
import 'package:friflex_starter/features/auth/domain/repository/i_auth_repository.dart';
|
||||
import 'package:friflex_starter/features/main/data/repository/main_mock_repository.dart';
|
||||
import 'package:friflex_starter/features/main/data/repository/main_repository.dart';
|
||||
import 'package:friflex_starter/features/main/domain/repository/i_main_repository.dart';
|
||||
@@ -45,9 +42,6 @@ final class DiRepositories {
|
||||
/// {@macro di_repositories}
|
||||
DiRepositories();
|
||||
|
||||
/// Интерфейс для работы с репозиторием авторизации
|
||||
late final IAuthRepository authRepository;
|
||||
|
||||
/// Интерфейс для работы с репозиторием главного сервиса
|
||||
late final IMainRepository mainRepository;
|
||||
|
||||
@@ -55,7 +49,7 @@ final class DiRepositories {
|
||||
late final IProfileRepository profileRepository;
|
||||
|
||||
/// Интерфейс для работы с репозиторием обновлений
|
||||
late final IUpdateRepository updatesRepository;
|
||||
late final IUpdateRepository updateRepository;
|
||||
|
||||
/// Метод для инициализации репозиториев в приложении.
|
||||
///
|
||||
@@ -76,23 +70,9 @@ final class DiRepositories {
|
||||
onProgress('Начинаем инициализацию репозиториев...');
|
||||
|
||||
// Инициализация репозитория обновлений
|
||||
updatesRepository = _lazyInitRepo<IUpdateRepository>(
|
||||
mockFactory: UpdateMockRepository.new,
|
||||
mainFactory: UpdateRepository.new,
|
||||
onProgress: onProgress,
|
||||
onError: onError,
|
||||
environment: diContainer.env,
|
||||
);
|
||||
|
||||
// Инициализация репозитория авторизации
|
||||
authRepository = _lazyInitRepo<IAuthRepository>(
|
||||
mockFactory: AuthMockRepository.new,
|
||||
mainFactory: () => AuthRepository(
|
||||
httpClient: diContainer.httpClientFactory(
|
||||
diContainer.debugService,
|
||||
diContainer.appConfig,
|
||||
),
|
||||
),
|
||||
updateRepository = _lazyInitRepo<IUpdateRepository>(
|
||||
mockFactory: () => const UpdateMockRepository(),
|
||||
mainFactory: () => UpdateRepository(httpClient: diContainer.httpClient),
|
||||
onProgress: onProgress,
|
||||
onError: onError,
|
||||
environment: diContainer.env,
|
||||
@@ -100,13 +80,8 @@ final class DiRepositories {
|
||||
|
||||
// Инициализация репозитория сервиса управления токеном доступа
|
||||
mainRepository = _lazyInitRepo<IMainRepository>(
|
||||
mockFactory: MainMockRepository.new,
|
||||
mainFactory: () => MainRepository(
|
||||
httpClient: diContainer.httpClientFactory(
|
||||
diContainer.debugService,
|
||||
diContainer.appConfig,
|
||||
),
|
||||
),
|
||||
mockFactory: () => const MainMockRepository(),
|
||||
mainFactory: () => MainRepository(httpClient: diContainer.httpClient),
|
||||
onProgress: onProgress,
|
||||
onError: onError,
|
||||
environment: diContainer.env,
|
||||
@@ -114,13 +89,8 @@ final class DiRepositories {
|
||||
|
||||
// Инициализация репозитория профиля
|
||||
profileRepository = _lazyInitRepo<IProfileRepository>(
|
||||
mockFactory: ProfileMockRepository.new,
|
||||
mainFactory: () => ProfileRepository(
|
||||
httpClient: diContainer.httpClientFactory(
|
||||
diContainer.debugService,
|
||||
diContainer.appConfig,
|
||||
),
|
||||
),
|
||||
mockFactory: () => const ProfileMockRepository(),
|
||||
mainFactory: () => ProfileRepository(httpClient: diContainer.httpClient),
|
||||
onProgress: onProgress,
|
||||
onError: onError,
|
||||
environment: diContainer.env,
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import 'package:friflex_starter/features/auth/domain/repository/i_auth_repository.dart';
|
||||
|
||||
/// {@template AuthMockRepository}
|
||||
/// Mock реализация репозитория авторизации
|
||||
/// {@endtemplate}
|
||||
final class AuthMockRepository implements IAuthRepository {
|
||||
@override
|
||||
String get name => 'AuthMockRepository';
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import 'package:friflex_starter/app/http/i_http_client.dart';
|
||||
|
||||
import 'package:friflex_starter/features/auth/domain/repository/i_auth_repository.dart';
|
||||
|
||||
/// {@template AuthRepository}
|
||||
/// Реализация репозитория авторизации
|
||||
/// {@endtemplate}
|
||||
final class AuthRepository implements IAuthRepository {
|
||||
AuthRepository({required this.httpClient});
|
||||
final IHttpClient httpClient;
|
||||
|
||||
@override
|
||||
String get name => 'AuthRepository';
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import 'package:friflex_starter/di/di_base_repo.dart';
|
||||
|
||||
/// {@template IAuthRepository}
|
||||
/// Интерфейс для работы с репозиторием авторизации
|
||||
/// {@endtemplate}
|
||||
abstract interface class IAuthRepository with DiBaseRepo {}
|
||||
@@ -1,24 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// {@template auth_screen}
|
||||
/// Экран авторизации пользователя.
|
||||
///
|
||||
/// Отвечает за:
|
||||
/// - Отображение формы входа в приложение
|
||||
/// - Обработку процесса аутентификации
|
||||
/// - Навигацию после успешной авторизации
|
||||
///
|
||||
/// В текущей реализации является заглушкой для будущей функциональности.
|
||||
/// {@endtemplate}
|
||||
class AuthScreen extends StatelessWidget {
|
||||
/// {@macro auth_screen}
|
||||
const AuthScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('AuthScreen')),
|
||||
body: const Center(child: Text('AuthScreen')),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
import 'package:friflex_starter/features/main/domain/repository/i_main_repository.dart';
|
||||
|
||||
/// {@template MainMockRepository}
|
||||
///
|
||||
/// Мок реализация репозитория главного сервиса
|
||||
/// {@endtemplate}
|
||||
final class MainMockRepository implements IMainRepository {
|
||||
/// {@macro MainMockRepository}
|
||||
const MainMockRepository();
|
||||
|
||||
@override
|
||||
String get name => 'MainMockRepository';
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import 'package:friflex_starter/app/http/i_http_client.dart';
|
||||
|
||||
import 'package:friflex_starter/app/http/app_http_client.dart';
|
||||
import 'package:friflex_starter/features/main/domain/repository/i_main_repository.dart';
|
||||
|
||||
/// {@template MainRepository}
|
||||
///
|
||||
/// Реализация репозитория главного сервиса
|
||||
/// {@endtemplate}
|
||||
final class MainRepository implements IMainRepository {
|
||||
MainRepository({required this.httpClient});
|
||||
final IHttpClient httpClient;
|
||||
|
||||
/// Экземпляр HTTP клиента для взаимодействия с сервером
|
||||
final AppHttpClient httpClient;
|
||||
|
||||
@override
|
||||
String get name => 'MainRepository';
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import 'package:friflex_starter/features/profile/domain/repository/i_profile_repository.dart';
|
||||
|
||||
/// {@template ProfileMockRepository}
|
||||
///
|
||||
/// Мок реализация репозитория профиля пользователя
|
||||
/// {@endtemplate}
|
||||
final class ProfileMockRepository implements IProfileRepository {
|
||||
/// {@macro ProfileMockRepository}
|
||||
const ProfileMockRepository();
|
||||
|
||||
@override
|
||||
String get name => 'ProfileMockRepository';
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import 'package:friflex_starter/app/http/i_http_client.dart';
|
||||
import 'package:friflex_starter/app/http/app_http_client.dart';
|
||||
|
||||
import 'package:friflex_starter/features/profile/domain/repository/i_profile_repository.dart';
|
||||
|
||||
/// {@template ProfileRepository}
|
||||
///
|
||||
/// Реализация репозитория профиля пользователя
|
||||
/// {@endtemplate}
|
||||
final class ProfileRepository implements IProfileRepository {
|
||||
ProfileRepository({required this.httpClient});
|
||||
final IHttpClient httpClient;
|
||||
|
||||
/// Экземпляр HTTP клиента для взаимодействия с сервером
|
||||
final AppHttpClient httpClient;
|
||||
|
||||
@override
|
||||
String get name => 'ProfileRepository';
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:friflex_starter/app/http/app_http_client.dart';
|
||||
import 'package:friflex_starter/features/update/domain/entity/update_entity.dart';
|
||||
import 'package:friflex_starter/features/update/domain/repository/i_update_repository.dart';
|
||||
|
||||
@@ -6,7 +7,10 @@ import 'package:friflex_starter/features/update/domain/repository/i_update_repos
|
||||
/// {@endtemplate}
|
||||
final class UpdateRepository implements IUpdateRepository {
|
||||
/// {@macro UpdateRepository}
|
||||
const UpdateRepository();
|
||||
UpdateRepository({required this.httpClient});
|
||||
|
||||
/// Экземпляр HTTP клиента для взаимодействия с сервером
|
||||
final AppHttpClient httpClient;
|
||||
|
||||
@override
|
||||
Future<UpdateEntity> checkForUpdates({
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// Тип функции для построения виджета с учетом локализации
|
||||
typedef LocalizationBuilder = Widget Function();
|
||||
typedef LocalizationBuilder = Widget Function(BuildContext context);
|
||||
|
||||
/// {@template localization_consumer}
|
||||
/// Виджет для подписки на изменения локализации приложения.
|
||||
@@ -20,8 +20,8 @@ class LocalizationConsumer extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<LocalizationNotifier>(
|
||||
builder: (_, _, _) {
|
||||
return builder();
|
||||
builder: (context, _, _) {
|
||||
return builder(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import 'package:friflex_starter/runner/app_runner.dart';
|
||||
import 'package:friflex_starter/targets/prod.dart' as prod;
|
||||
|
||||
void main() => AppRunner(.prod).run();
|
||||
void main(List<String> arguments) => prod.main(arguments);
|
||||
|
||||
@@ -4,7 +4,6 @@ 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/profile/presentation/profile_routes.dart';
|
||||
import 'package:friflex_starter/features/root/root_screen.dart';
|
||||
import 'package:friflex_starter/features/splash/splash_screen.dart';
|
||||
import 'package:friflex_starter/features/update/update_routes.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
@@ -39,10 +38,6 @@ class AppRouter {
|
||||
],
|
||||
),
|
||||
DebugRoutes.buildRoutes(),
|
||||
GoRoute(
|
||||
path: '/splash',
|
||||
builder: (context, state) => const SplashScreen(),
|
||||
),
|
||||
UpdateRoutes.buildRoutes(),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -4,23 +4,17 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:friflex_starter/app/app.dart';
|
||||
import 'package:friflex_starter/app/app_env.dart';
|
||||
import 'package:friflex_starter/app/app_root.dart';
|
||||
import 'package:friflex_starter/di/di_container.dart';
|
||||
import 'package:friflex_starter/features/debug/debug_service.dart';
|
||||
import 'package:friflex_starter/features/debug/i_debug_service.dart';
|
||||
import 'package:friflex_starter/features/error/error_screen.dart';
|
||||
import 'package:friflex_starter/router/app_router.dart';
|
||||
import 'package:friflex_starter/runner/timer_runner.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
part 'errors_handlers.dart';
|
||||
|
||||
/// Время ожидания инициализации зависимостей
|
||||
/// Если время превышено, то будет показан экран ошибки
|
||||
/// В дальнейшем нужно убрать в env
|
||||
const _initTimeout = Duration(seconds: 7);
|
||||
|
||||
/// Класс, реализующий раннер для конфигурирования приложения при запуске
|
||||
///
|
||||
/// Порядок инициализации:
|
||||
@@ -48,7 +42,7 @@ class AppRunner {
|
||||
late TimerRunner _timerRunner;
|
||||
|
||||
/// Метод для запуска приложения
|
||||
Future<void> run() async {
|
||||
Future<void> run(List<String> arguments) async {
|
||||
try {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
// Инициализация сервиса отладки
|
||||
@@ -62,38 +56,28 @@ class AppRunner {
|
||||
// Инициализация приложения
|
||||
await _initApp();
|
||||
|
||||
final diContainer = await _initDependencies(
|
||||
debugService: _debugService,
|
||||
env: env,
|
||||
timerRunner: _timerRunner,
|
||||
);
|
||||
// Инициализация метода обработки ошибок
|
||||
_initErrorHandlers(_debugService);
|
||||
|
||||
// throw Exception('Test error');
|
||||
|
||||
runApp(
|
||||
App(
|
||||
initDependencies: () {
|
||||
return _initDependencies(
|
||||
debugService: _debugService,
|
||||
env: env,
|
||||
timerRunner: _timerRunner,
|
||||
).timeout(
|
||||
_initTimeout,
|
||||
onTimeout: () {
|
||||
return Future.error(
|
||||
TimeoutException(
|
||||
'Превышено время ожидания инициализации зависимостей',
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
runApp(AppRoot(diContainer: diContainer, router: router));
|
||||
await _onAppLoaded();
|
||||
} on Object catch (e, stackTrace) {
|
||||
await _onAppLoaded();
|
||||
_timerRunner.stop();
|
||||
|
||||
/// Если произошла ошибка при инициализации приложения,
|
||||
/// то запускаем экран ошибки
|
||||
runApp(ErrorScreen(error: e, stackTrace: stackTrace, onRetry: run));
|
||||
runApp(
|
||||
ErrorScreen(
|
||||
error: e,
|
||||
stackTrace: stackTrace,
|
||||
onRetry: () => run(arguments),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,20 +107,32 @@ class AppRunner {
|
||||
}) async {
|
||||
debugService.log(() => 'Тип сборки: ${env.name}');
|
||||
final diContainer = DiContainer(env: env, dService: debugService);
|
||||
await diContainer.init(
|
||||
onProgress: (name) => timerRunner.logOnProgress(name),
|
||||
onComplete: (name) {
|
||||
timerRunner
|
||||
..logOnComplete(name)
|
||||
..stop();
|
||||
},
|
||||
onError: (message, error, [stackTrace]) {
|
||||
timerRunner.stop();
|
||||
_debugService.logError(message, error: error, stackTrace: stackTrace);
|
||||
throw Exception('Ошибка инициализации зависимостей: $message');
|
||||
},
|
||||
);
|
||||
//throw Exception('Test error');
|
||||
await diContainer
|
||||
.init(
|
||||
onProgress: (name) => timerRunner.logOnProgress(name),
|
||||
onComplete: (name) {
|
||||
timerRunner
|
||||
..logOnComplete(name)
|
||||
..stop();
|
||||
},
|
||||
onError: (message, error, [stackTrace]) {
|
||||
timerRunner.stop();
|
||||
_debugService.logError(
|
||||
message,
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
throw Exception('Ошибка инициализации зависимостей: $message');
|
||||
},
|
||||
)
|
||||
.timeout(
|
||||
const Duration(seconds: 7),
|
||||
onTimeout: () {
|
||||
throw Exception(
|
||||
'Превышено время ожидания инициализации зависимостей',
|
||||
);
|
||||
},
|
||||
);
|
||||
return diContainer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ part of 'app_runner.dart';
|
||||
void _initErrorHandlers(IDebugService debugService) {
|
||||
// Обработка ошибок в приложении
|
||||
FlutterError.onError = (details) {
|
||||
_showErrorScreen(details.exception, details.stack);
|
||||
debugService.logError(
|
||||
() => 'FlutterError.onError: ${details.exceptionAsString()}',
|
||||
error: details.exception,
|
||||
@@ -13,7 +12,6 @@ void _initErrorHandlers(IDebugService debugService) {
|
||||
};
|
||||
// Обработка асинхронных ошибок в приложении
|
||||
PlatformDispatcher.instance.onError = (error, stack) {
|
||||
_showErrorScreen(error, stack);
|
||||
debugService.logError(
|
||||
() => 'PlatformDispatcher.instance.onError',
|
||||
error: error,
|
||||
@@ -22,14 +20,3 @@ void _initErrorHandlers(IDebugService debugService) {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/// Метод для показа экрана ошибки
|
||||
void _showErrorScreen(Object error, StackTrace? stackTrace) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await AppRouter.rootNavigatorKey.currentState?.push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => ErrorScreen(error: error, stackTrace: stackTrace),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,6 +24,11 @@ class TimerRunner {
|
||||
);
|
||||
}
|
||||
|
||||
/// Метод для сброса секундомера
|
||||
void reset() {
|
||||
_stopwatch.reset();
|
||||
}
|
||||
|
||||
/// Метод для обработки прогресса инициализации зависимостей
|
||||
void logOnProgress(String name) {
|
||||
_debugService.log(
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import 'package:friflex_starter/runner/app_runner.dart';
|
||||
|
||||
void main() => AppRunner(.dev).run();
|
||||
void main(List<String> arguments) => AppRunner(.dev).run(arguments);
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import 'package:friflex_starter/runner/app_runner.dart';
|
||||
|
||||
void main() => AppRunner(.prod).run();
|
||||
void main(List<String> arguments) => AppRunner(.prod).run(arguments);
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import 'package:friflex_starter/runner/app_runner.dart';
|
||||
|
||||
void main() => AppRunner(.stage).run();
|
||||
void main(List<String> arguments) => AppRunner(.stage).run(arguments);
|
||||
|
||||
Reference in New Issue
Block a user