mirror of
https://github.com/smmarty/friflex_flutter_starter.git
synced 2026-02-05 03:32:18 +00:00
refactor(full refactor): Рефакторинг стартера (#8)
This commit is contained in:
@@ -5,6 +5,7 @@ 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';
|
||||
|
||||
17
lib/app/app_box.dart
Normal file
17
lib/app/app_box.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
/// {@template h_box}
|
||||
/// HBox виджет для вертикального отступа (Надстройка над SizedBox)
|
||||
/// {@endtemplate}
|
||||
class HBox extends SizedBox {
|
||||
/// {@macro h_box}
|
||||
const HBox(double height, {super.key}) : super(height: height);
|
||||
}
|
||||
|
||||
/// {@template w_box}
|
||||
/// WBox виджет для вертикального отступа (Надстройка над SizedBox)
|
||||
/// {@endtemplate}
|
||||
class WBox extends SizedBox {
|
||||
/// {@macro w_box}
|
||||
const WBox(double width, {super.key}) : super(width: width);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import 'package:envied/envied.dart';
|
||||
import 'package:friflex_starter/app/app_config/i_app_config.dart';
|
||||
import 'package:friflex_starter/app/app_env.dart';
|
||||
|
||||
|
||||
part 'app_config.g.dart';
|
||||
|
||||
/// Класс для реализации конфигурации с моковыми данными
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:friflex_starter/app/app_config/i_app_config.dart';
|
||||
import 'package:friflex_starter/app/http/i_http_client.dart';
|
||||
|
||||
import 'package:friflex_starter/features/debug/i_debug_service.dart';
|
||||
|
||||
/// Класс для реализации HTTP-клиента для управления запросами
|
||||
/// {@template app_http_client}
|
||||
/// Класс для реализации HTTP-клиента для управления запросами
|
||||
/// {@endtemplate}
|
||||
final class AppHttpClient implements IHttpClient {
|
||||
/// Создает HTTP клиент
|
||||
///
|
||||
@@ -26,6 +29,7 @@ final class AppHttpClient implements IHttpClient {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
debugService.log('HTTP client created');
|
||||
_httpClient.interceptors.add(debugService.dioLogger);
|
||||
}
|
||||
|
||||
/// Конфигурация приложения
|
||||
|
||||
@@ -10,7 +10,8 @@ import 'package:friflex_starter/features/debug/i_debug_service.dart';
|
||||
|
||||
/// {@template dependencies_container}
|
||||
/// Контейнер для зависимостей
|
||||
/// {@macro composition_process}
|
||||
/// [env] - окружение приложения
|
||||
/// [debugService] - сервис для отладки
|
||||
/// {@endtemplate}
|
||||
final class DiContainer {
|
||||
/// {@macro dependencies_container}
|
||||
|
||||
@@ -11,9 +11,6 @@ import 'package:friflex_starter/features/main/domain/repository/i_main_repositor
|
||||
import 'package:friflex_starter/features/profile/data/repository/profile_mock_repository.dart';
|
||||
import 'package:friflex_starter/features/profile/data/repository/profile_repository.dart';
|
||||
import 'package:friflex_starter/features/profile/domain/repository/i_profile_repository.dart';
|
||||
import 'package:friflex_starter/features/profile_scope/data/repository/profile_scope_mock_repository.dart';
|
||||
import 'package:friflex_starter/features/profile_scope/data/repository/profile_scope_repository.dart';
|
||||
import 'package:friflex_starter/features/profile_scope/domain/repository/i_profile_scope_repository.dart';
|
||||
|
||||
/// Список названий моковых репозиториев, которые должны быть подменены
|
||||
/// для использования в сборке stage окружения
|
||||
@@ -43,8 +40,6 @@ final class DiRepositories {
|
||||
/// Интерфейс для работы с репозиторием профиля
|
||||
late final IProfileRepository profileRepository;
|
||||
|
||||
/// Интерфейс для работы с репозиторием профиля scope
|
||||
late final IProfileScopeRepository profileScopeRepository;
|
||||
|
||||
/// Метод для инициализации репозиториев в приложении
|
||||
///
|
||||
@@ -121,29 +116,7 @@ final class DiRepositories {
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// Инициализация репозитория профиля scope
|
||||
profileScopeRepository = _lazyInitRepo<IProfileScopeRepository>(
|
||||
mockFactory: ProfileScopeMockRepository.new,
|
||||
mainFactory: () => ProfileScopeRepository(
|
||||
httpClient: diContainer.httpClientFactory(
|
||||
diContainer.debugService,
|
||||
diContainer.appConfig,
|
||||
),
|
||||
),
|
||||
onProgress: onProgress,
|
||||
environment: diContainer.env,
|
||||
);
|
||||
onProgress(mainRepository.name);
|
||||
} on Object catch (error, stackTrace) {
|
||||
onError(
|
||||
'Ошибка инициализации репозитория IProfileScopeRepository',
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
onProgress(
|
||||
'Инициализация репозиториев завершена! Было подменено репозиториев - ${_mockReposToSwitch.length} (${_mockReposToSwitch.join(', ')})',
|
||||
);
|
||||
|
||||
@@ -23,7 +23,7 @@ final class DiServices {
|
||||
required DiContainer diContainer,
|
||||
}) {
|
||||
try {
|
||||
pathProvider = AppPathProvider();
|
||||
pathProvider = const AppPathProvider();
|
||||
onProgress(AppPathProvider.name);
|
||||
} on Object catch (error, stackTrace) {
|
||||
onError(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import '../../domain/repository/i_auth_repository.dart';
|
||||
|
||||
/// {@template AuthMockRepository}
|
||||
///
|
||||
/// Mock реализация репозитория авторизации
|
||||
/// {@endtemplate}
|
||||
final class AuthMockRepository implements IAuthRepository {
|
||||
@override
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:friflex_starter/app/http/i_http_client.dart';
|
||||
import '../../domain/repository/i_auth_repository.dart';
|
||||
|
||||
/// {@template AuthRepository}
|
||||
///
|
||||
/// Реализация репозитория авторизации
|
||||
/// {@endtemplate}
|
||||
final class AuthRepository implements IAuthRepository {
|
||||
final IHttpClient httpClient;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:friflex_starter/di/di_base_repo.dart';
|
||||
|
||||
/// {@template IAuthRepository}
|
||||
///
|
||||
/// Интерфейс для работы с репозиторием авторизации
|
||||
/// {@endtemplate}
|
||||
abstract interface class IAuthRepository with DiBaseRepo {}
|
||||
|
||||
@@ -1,31 +1,66 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:friflex_starter/features/debug/debug_screen.dart';
|
||||
import 'package:friflex_starter/features/debug/screens/debug_screen.dart';
|
||||
import 'package:friflex_starter/features/debug/screens/icons_screen.dart';
|
||||
import 'package:friflex_starter/features/debug/screens/lang_screen.dart';
|
||||
import 'package:friflex_starter/features/debug/screens/theme_screen.dart';
|
||||
import 'package:friflex_starter/features/debug/screens/tokens_screen.dart';
|
||||
import 'package:friflex_starter/features/debug/screens/ui_kit_screen.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
/// {@template debug_routes}
|
||||
/// Роуты для отладки приложения
|
||||
/// [buildRoutes] - метод для создания роутов
|
||||
/// {@endtemplate}
|
||||
abstract final class DebugRoutes {
|
||||
/// Название роута страницы профиля пользователя
|
||||
/// Название экранов
|
||||
static const String debugScreenName = 'debug_screen';
|
||||
static const String tokensScreenName = 'tokens_screen';
|
||||
static const String uiKitScreenName = 'ui_kit_screen';
|
||||
static const String iconsScreenName = 'icons_screen';
|
||||
static const String themeScreenName = 'theme_screen';
|
||||
static const String langScreenName = 'lang_screen';
|
||||
|
||||
/// Путь роута страницы профиля пользователя
|
||||
static const String _debugScreenPath = '/debug';
|
||||
/// Пути к экранам
|
||||
static const String debugScreenPath = '/debug';
|
||||
static const String tokensScreenPath = 'debug/tokens';
|
||||
static const String uiKitScreenPath = 'debug/ui_kit';
|
||||
static const String iconsScreenPath = 'debug/icons';
|
||||
static const String themeScreenPath = 'debug/theme';
|
||||
static const String langScreenPath = 'debug/lang';
|
||||
|
||||
/// Метод для построения ветки роутов по фиче профиля пользователя
|
||||
/// Метод для создания роутов для отладки
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [routes] - вложенные роуты
|
||||
static StatefulShellBranch buildShellBranch({
|
||||
List<RouteBase> routes = const [],
|
||||
List<NavigatorObserver>? observers,
|
||||
}) =>
|
||||
StatefulShellBranch(
|
||||
initialLocation: _debugScreenPath,
|
||||
observers: observers,
|
||||
static GoRoute buildRoutes({List<RouteBase> routes = const []}) => GoRoute(
|
||||
path: debugScreenPath,
|
||||
name: debugScreenName,
|
||||
builder: (context, state) => const DebugScreen(),
|
||||
routes: [
|
||||
...routes,
|
||||
GoRoute(
|
||||
path: _debugScreenPath,
|
||||
name: debugScreenName,
|
||||
builder: (context, state) => const DebugScreen(),
|
||||
routes: routes,
|
||||
path: tokensScreenPath,
|
||||
name: tokensScreenName,
|
||||
builder: (context, state) => const TokensScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: uiKitScreenPath,
|
||||
name: uiKitScreenName,
|
||||
builder: (context, state) => const UiKitScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: iconsScreenPath,
|
||||
name: iconsScreenName,
|
||||
builder: (context, state) => const IconsScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: themeScreenPath,
|
||||
name: themeScreenName,
|
||||
builder: (context, state) => const ThemeScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: langScreenPath,
|
||||
name: langScreenName,
|
||||
builder: (context, state) => const LangScreen(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||
import 'package:friflex_starter/app/theme/app_colors_scheme.dart';
|
||||
import 'package:friflex_starter/gen/assets.gen.dart';
|
||||
import 'package:friflex_starter/gen/fonts.gen.dart';
|
||||
|
||||
class DebugScreen extends StatelessWidget {
|
||||
const DebugScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Debug Screen')),
|
||||
body: Center(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
Text(
|
||||
'Реализация SecureStorage: ${context.di.services.secureStorage.nameImpl}',
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Окружение: ${context.di.appConfig.env.name}',
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.theme.changeTheme();
|
||||
},
|
||||
child: const Text('Сменить тему'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ColoredBox(
|
||||
color: context.colors.testColor,
|
||||
child: const SizedBox(height: 100, width: 100),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Текущая тема: ${context.theme.themeMode}',
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Текущий репозиторий: ${context.di.repositories.authRepository.name}',
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.localization.changeLocal(
|
||||
const Locale('ru', 'RU'),
|
||||
);
|
||||
},
|
||||
child: const Text('Сменить язык на Rусский'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.localization.changeLocal(
|
||||
const Locale('en', 'EN'),
|
||||
);
|
||||
},
|
||||
child: const Text('Сменить язык на Английский'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Тестовое слово montserrat bold: ${context.l10n.helloWorld}',
|
||||
style: TextStyle(
|
||||
color: context.colors.testColor,
|
||||
fontFamily: Assets.fonts.montserratBold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Тестовое слово montserrat medium: ${context.l10n.helloWorld}',
|
||||
style: TextStyle(
|
||||
color: context.colors.testColor,
|
||||
fontFamily: FontFamily.montserrat,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Текущий язык: ${context.l10n.localeName}',
|
||||
),
|
||||
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 {
|
||||
throw Exception('Тестовая ошибка Exception для отладки PlatformDispatcher');
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,57 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friflex_starter/features/debug/i_debug_service.dart';
|
||||
import 'package:talker_bloc_logger/talker_bloc_logger_observer.dart';
|
||||
import 'package:talker_dio_logger/talker_dio_logger_interceptor.dart';
|
||||
import 'package:talker_flutter/talker_flutter.dart';
|
||||
|
||||
/// Класс реализации интерфейса Debug сервиса
|
||||
/// {@template debug_service}
|
||||
/// Реализация сервиса отладки, с помощью Talker
|
||||
/// {@endtemplate}
|
||||
class DebugService implements IDebugService {
|
||||
/// {@macro debug_service}
|
||||
DebugService() {
|
||||
_talker = TalkerFlutter.init();
|
||||
_talkerDioLogger = TalkerDioLogger(talker: _talker);
|
||||
_talkerRouteObserver = TalkerRouteObserver(_talker);
|
||||
_talkerBlocObserver = TalkerBlocObserver(talker: _talker);
|
||||
}
|
||||
|
||||
/// Наименование сервиса
|
||||
static const name = 'DebugService';
|
||||
|
||||
@override
|
||||
void logDebug(Object message, {Object? logLevel, Map<String, dynamic>? args}) {
|
||||
if (kDebugMode) {
|
||||
print('Message: $message');
|
||||
}
|
||||
/// Реализация Talker
|
||||
late final Talker _talker;
|
||||
|
||||
/// Реализация логики
|
||||
/// Реализация TalkerDioLogger
|
||||
late final TalkerDioLogger _talkerDioLogger;
|
||||
|
||||
/// Реализация TalkerRouteObserver
|
||||
late final TalkerRouteObserver _talkerRouteObserver;
|
||||
|
||||
/// Реализация TalkerBlocLoggerObserver
|
||||
late final TalkerBlocObserver _talkerBlocObserver;
|
||||
|
||||
/// Получает TalkerDioLoggerInterceptor
|
||||
@override
|
||||
TalkerDioLogger get dioLogger => _talkerDioLogger;
|
||||
|
||||
/// Получает TalkerRouteObserver\
|
||||
@override
|
||||
TalkerRouteObserver get routeObserver => _talkerRouteObserver;
|
||||
|
||||
/// Получает TalkerBlocObserver
|
||||
@override
|
||||
TalkerBlocObserver get blocObserver => _talkerBlocObserver;
|
||||
|
||||
@override
|
||||
void logDebug(
|
||||
Object message, {
|
||||
Object? logLevel,
|
||||
Map<String, dynamic>? args,
|
||||
}) {
|
||||
_talker.debug(
|
||||
message is Function ? Function.apply(message, []) as Object : message,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -24,43 +62,34 @@ class DebugService implements IDebugService {
|
||||
Map<String, dynamic>? args,
|
||||
StackTrace? stackTrace,
|
||||
}) {
|
||||
final logMessage = message is Function ? Function.apply(message, []) as Object : message;
|
||||
if (kDebugMode) {
|
||||
print('Message: $logMessage');
|
||||
print('Error: $error');
|
||||
print('StackTrace: $stackTrace');
|
||||
}
|
||||
|
||||
/// Реализация логики
|
||||
final logMessage =
|
||||
message is Function ? Function.apply(message, []) as Object : message;
|
||||
_talker.error(logMessage, error, stackTrace);
|
||||
}
|
||||
|
||||
@override
|
||||
void log(Object message, {Object? logLevel, Map<String, dynamic>? args}) {
|
||||
final logMessage = message is Function ? Function.apply(message, []) as Object : message;
|
||||
if (kDebugMode) {
|
||||
print('Message: $logMessage');
|
||||
}
|
||||
|
||||
/// Реализация логики
|
||||
final logMessage =
|
||||
message is Function ? Function.apply(message, []) as Object : message;
|
||||
_talker.log(logMessage);
|
||||
}
|
||||
|
||||
@override
|
||||
void logWarning(Object message, {Object? logLevel, Map<String, dynamic>? args}) {
|
||||
final logMessage = message is Function ? Function.apply(message, []) as Object : message;
|
||||
if (kDebugMode) {
|
||||
print('Message: $logMessage');
|
||||
}
|
||||
|
||||
/// Реализация логики
|
||||
void logWarning(
|
||||
Object message, {
|
||||
Object? logLevel,
|
||||
Map<String, dynamic>? args,
|
||||
}) {
|
||||
final logMessage =
|
||||
message is Function ? Function.apply(message, []) as Object : message;
|
||||
_talker.warning(logMessage);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<T?> openDebugScreen<T>(BuildContext context, {bool useRootNavigator = false}) {
|
||||
if (kDebugMode) {
|
||||
print('Переход на страницу отладки');
|
||||
}
|
||||
|
||||
/// Реализация логики
|
||||
return Future.value();
|
||||
Future<void> openDebugScreen(BuildContext context,
|
||||
{bool useRootNavigator = false,}) async {
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => TalkerScreen(talker: _talker)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,15 @@ import 'package:flutter/material.dart';
|
||||
abstract interface class IDebugService {
|
||||
static const name = 'IDebugService';
|
||||
|
||||
/// Наблюдение за dio
|
||||
dynamic get dioLogger;
|
||||
|
||||
/// Наблюдение за роутами
|
||||
dynamic get routeObserver;
|
||||
|
||||
/// Наблюдение за BLoC
|
||||
dynamic get blocObserver;
|
||||
|
||||
/// Метод для логирования сообщений
|
||||
void log(
|
||||
Object message, {
|
||||
@@ -39,7 +48,7 @@ abstract interface class IDebugService {
|
||||
/// Принимает:
|
||||
/// - [context] - для определения навигатора по нему
|
||||
/// - [useRootNavigator] - при true, открывает окно в корневом навигаторе
|
||||
Future<T?> openDebugScreen<T>(
|
||||
Future<void> openDebugScreen(
|
||||
BuildContext context, {
|
||||
bool useRootNavigator = false,
|
||||
});
|
||||
|
||||
96
lib/features/debug/screens/debug_screen.dart
Normal file
96
lib/features/debug/screens/debug_screen.dart
Normal file
@@ -0,0 +1,96 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friflex_starter/app/app_box.dart';
|
||||
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||
import 'package:friflex_starter/features/debug/debug_routes.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class DebugScreen extends StatelessWidget {
|
||||
const DebugScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Debug Screen')),
|
||||
body: Center(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
Text(
|
||||
'Окружение: ${context.di.appConfig.env.name}',
|
||||
),
|
||||
const HBox(22),
|
||||
Text(
|
||||
'Реализация AppServices: ${context.di.services.secureStorage.nameImpl}',
|
||||
),
|
||||
const HBox(22),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
await context.di.debugService.openDebugScreen(context);
|
||||
},
|
||||
child: const Text('Вызывать Экран отладки'),
|
||||
),
|
||||
const HBox(22),
|
||||
const Text('Экраны для отладки:'),
|
||||
const HBox(16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.pushNamed(DebugRoutes.iconsScreenName);
|
||||
},
|
||||
child: const Text('Экран с иконками'),
|
||||
),
|
||||
const HBox(16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.pushNamed(DebugRoutes.themeScreenName);
|
||||
},
|
||||
child: const Text('Экран настроек темы'),
|
||||
),
|
||||
const HBox(16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.pushNamed(DebugRoutes.tokensScreenName);
|
||||
},
|
||||
child: const Text('Экран с токенами'),
|
||||
),
|
||||
const HBox(16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.pushNamed(DebugRoutes.uiKitScreenName);
|
||||
},
|
||||
child: const Text('Экран UI Kit'),
|
||||
),
|
||||
const HBox(16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.pushNamed(DebugRoutes.langScreenName);
|
||||
},
|
||||
child: const Text('Экран локализации'),
|
||||
),
|
||||
const HBox(22),
|
||||
const Text('Имитирование ошибок:'),
|
||||
const HBox(16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
throw Exception(
|
||||
'Тестовая ошибка Exception для отладки FlutterError',
|
||||
);
|
||||
},
|
||||
child: const Text('Вызывать ошибку FlutterError'),
|
||||
),
|
||||
const HBox(16),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
await _callError();
|
||||
},
|
||||
child: const Text('Вызывать ошибку PlatformDispatcher'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _callError() async {
|
||||
throw Exception('Тестовая ошибка Exception для отладки PlatformDispatcher');
|
||||
}
|
||||
}
|
||||
54
lib/features/debug/screens/icons_screen.dart
Normal file
54
lib/features/debug/screens/icons_screen.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friflex_starter/app/app_box.dart';
|
||||
import 'package:friflex_starter/gen/assets.gen.dart';
|
||||
|
||||
/// {@template IconsScreen}
|
||||
/// Экран для отрисовки иконок
|
||||
/// {@endtemplate}
|
||||
class IconsScreen extends StatelessWidget {
|
||||
/// {@macro IconsScreen}
|
||||
const IconsScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final iconList = Assets.icons.values
|
||||
.map(
|
||||
(icon) => _ItemIcon(icon: icon.svg(), name: icon.path),
|
||||
)
|
||||
.toList();
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Иконки')),
|
||||
body: Center(
|
||||
child: ListView.separated(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemBuilder: (context, index) {
|
||||
return iconList[index];
|
||||
},
|
||||
separatorBuilder: (context, index) => const Divider(),
|
||||
itemCount: iconList.length,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Приватный класс для реализации элемента списка иконок
|
||||
class _ItemIcon extends StatelessWidget {
|
||||
/// Создает экземпляр элемента списка иконок
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [icon] - иконка
|
||||
/// - [name] - название иконки
|
||||
const _ItemIcon({required this.icon, required this.name});
|
||||
|
||||
/// Иконка
|
||||
final Widget icon;
|
||||
|
||||
/// Название иконки
|
||||
final String name;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(children: [icon, const WBox(16), Text(name)]);
|
||||
}
|
||||
}
|
||||
65
lib/features/debug/screens/lang_screen.dart
Normal file
65
lib/features/debug/screens/lang_screen.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||
import 'package:friflex_starter/app/theme/app_colors_scheme.dart';
|
||||
import 'package:friflex_starter/gen/assets.gen.dart';
|
||||
import 'package:friflex_starter/gen/fonts.gen.dart';
|
||||
|
||||
/// {@template LangScreen}
|
||||
/// Экран для отладки языков приложения
|
||||
/// {@endtemplate}
|
||||
class LangScreen extends StatelessWidget {
|
||||
/// {@macro LangScreen}
|
||||
const LangScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Lang')),
|
||||
body: Center(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.localization.changeLocal(
|
||||
const Locale('ru', 'RU'),
|
||||
);
|
||||
},
|
||||
child: const Text('Сменить язык на Rусский'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.localization.changeLocal(
|
||||
const Locale('en', 'EN'),
|
||||
);
|
||||
},
|
||||
child: const Text('Сменить язык на Английский'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Тестовое слово bold: ${context.l10n.helloWorld}',
|
||||
style: TextStyle(
|
||||
color: context.colors.testColor,
|
||||
fontFamily: Assets.fonts.montserratBold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Тестовое слово medium: ${context.l10n.helloWorld}',
|
||||
style: TextStyle(
|
||||
color: context.colors.testColor,
|
||||
fontFamily: FontFamily.montserrat,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Текущий язык: ${context.l10n.localeName}',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
40
lib/features/debug/screens/theme_screen.dart
Normal file
40
lib/features/debug/screens/theme_screen.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||
import 'package:friflex_starter/app/theme/app_colors_scheme.dart';
|
||||
|
||||
/// {@template ThemeScreen}
|
||||
/// Экран для отладки темы приложения
|
||||
/// {@endtemplate}
|
||||
class ThemeScreen extends StatelessWidget {
|
||||
/// {@macro ThemeScreen}
|
||||
const ThemeScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Theme')),
|
||||
body: Center(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.theme.changeTheme();
|
||||
},
|
||||
child: const Text('Сменить тему'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ColoredBox(
|
||||
color: context.colors.testColor,
|
||||
child: const SizedBox(height: 100, width: 100),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Текущая тема: ${context.theme.themeMode}',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
31
lib/features/debug/screens/tokens_screen.dart
Normal file
31
lib/features/debug/screens/tokens_screen.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// {@template TokensScreen}
|
||||
/// Экран для отображения токенов
|
||||
/// {@endtemplate}
|
||||
class TokensScreen extends StatelessWidget {
|
||||
/// {@macro TokensScreen}
|
||||
const TokensScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Tokens')),
|
||||
body: Center(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: const [
|
||||
Text(
|
||||
'Access Token: ',
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
'Refresh Token: ',
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
31
lib/features/debug/screens/ui_kit_screen.dart
Normal file
31
lib/features/debug/screens/ui_kit_screen.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// {@template UiKitScreen}
|
||||
/// Экран для отрисовки UI Kit
|
||||
/// и тестирования его компонентов.
|
||||
/// {@endtemplate}
|
||||
class UiKitScreen extends StatelessWidget {
|
||||
/// {@macro UiKitScreen}
|
||||
const UiKitScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('UI Kit Screen'),
|
||||
),
|
||||
body: Center(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: const [
|
||||
Text(
|
||||
'UI Kit Screen',
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
// Здесь можно добавить другие компоненты UI Kit
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:friflex_starter/features/main/presentation/screens/main_detail_screen.dart';
|
||||
import 'package:friflex_starter/features/main/presentation/screens/main_screen.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
@@ -6,9 +7,15 @@ abstract final class MainRoutes {
|
||||
/// Название роута главной страницы
|
||||
static const String mainScreenName = 'main_screen';
|
||||
|
||||
/// Название роута экрана с деталями
|
||||
static const String mainDetailScreenName = 'main_detail_screen';
|
||||
|
||||
/// Путь роута страницы профиля пользователя
|
||||
static const String _mainScreenPath = '/main';
|
||||
|
||||
/// Путь роута экрана с деталями
|
||||
static const String _mainDetailScreenPath = '/main/detail';
|
||||
|
||||
/// Метод для построения ветки роутов по фиче профиля пользователя
|
||||
///
|
||||
/// Принимает:
|
||||
@@ -21,11 +28,19 @@ abstract final class MainRoutes {
|
||||
initialLocation: _mainScreenPath,
|
||||
observers: observers,
|
||||
routes: [
|
||||
...routes,
|
||||
GoRoute(
|
||||
path: _mainScreenPath,
|
||||
name: mainScreenName,
|
||||
builder: (context, state) => const MainScreen(),
|
||||
routes: routes,
|
||||
routes: [
|
||||
// Пример вложенного роута для главного экрана
|
||||
GoRoute(
|
||||
path: _mainDetailScreenPath,
|
||||
name: mainDetailScreenName,
|
||||
builder: (context, state) => const MainDetailScreen(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// {@template MainDetailScreen}
|
||||
/// Вложенный экран для главного экрана приложения
|
||||
/// {@endtemplate}
|
||||
class MainDetailScreen extends StatelessWidget {
|
||||
/// {@macro MainDetailScreen}
|
||||
const MainDetailScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Main Detail Screen'),
|
||||
),
|
||||
body: const Center(
|
||||
child: Text('Вложенный экран'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friflex_starter/app/app_box.dart';
|
||||
import 'package:friflex_starter/features/main/presentation/main_routes.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
/// {@template MainScreen}
|
||||
/// Главный экран приложения
|
||||
/// {@endtemplate}
|
||||
class MainScreen extends StatelessWidget {
|
||||
/// {@macro MainScreen}
|
||||
const MainScreen({super.key});
|
||||
|
||||
@override
|
||||
@@ -12,20 +18,16 @@ class MainScreen extends StatelessWidget {
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('Главный экран приложения'),
|
||||
const HBox(16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.push('/profile');
|
||||
// Переход на экран с деталями
|
||||
context.pushNamed(MainRoutes.mainDetailScreenName);
|
||||
},
|
||||
child: const Text('Открыть профиль'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.push('/profile_scope');
|
||||
},
|
||||
child: const Text('Открыть профиль с областью видимости'),
|
||||
child: const Text('Переход на экран с деталями'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
32
lib/features/profile/presentation/profile_routes.dart
Normal file
32
lib/features/profile/presentation/profile_routes.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:friflex_starter/features/profile/presentation/screens/profile_screen.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
abstract final class ProfileRoutes {
|
||||
/// Название роута главной страницы
|
||||
static const String profileScreenName = 'profile_screen';
|
||||
|
||||
/// Путь роута страницы профиля пользователя
|
||||
static const String _profileScreenPath = '/profile';
|
||||
|
||||
/// Метод для построения ветки роутов по фиче профиля пользователя
|
||||
///
|
||||
/// Принимает:
|
||||
/// - [routes] - вложенные роуты
|
||||
static StatefulShellBranch buildShellBranch({
|
||||
List<RouteBase> routes = const [],
|
||||
List<NavigatorObserver>? observers,
|
||||
}) =>
|
||||
StatefulShellBranch(
|
||||
initialLocation: _profileScreenPath,
|
||||
observers: observers,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: _profileScreenPath,
|
||||
name: profileScreenName,
|
||||
builder: (context, state) => const ProfileScreen(),
|
||||
routes: routes,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import '../../domain/repository/i_profile_scope_repository.dart';
|
||||
|
||||
/// {@template ProfileScopeMockRepository}
|
||||
///
|
||||
/// {@endtemplate}
|
||||
final class ProfileScopeMockRepository implements IProfileScopeRepository {
|
||||
@override
|
||||
String get name => 'ProfileScopeMockRepository';
|
||||
|
||||
@override
|
||||
Future<String> fetchUserProfile(String id) async {
|
||||
return 'MOCK Yura Petrov';
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import 'package:friflex_starter/app/http/i_http_client.dart';
|
||||
|
||||
import '../../domain/repository/i_profile_scope_repository.dart';
|
||||
|
||||
/// {@template ProfileScopeRepository}
|
||||
///
|
||||
/// {@endtemplate}
|
||||
final class ProfileScopeRepository implements IProfileScopeRepository {
|
||||
final IHttpClient httpClient;
|
||||
|
||||
ProfileScopeRepository({required this.httpClient});
|
||||
|
||||
@override
|
||||
String get name => 'ProfileScopeRepository';
|
||||
|
||||
@override
|
||||
Future<String> fetchUserProfile(String id) async {
|
||||
// Какой-то запрос к серверу
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
// httpClient.get('https://example.com/profile/$id');
|
||||
|
||||
return 'Yura Petrov';
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:friflex_starter/features/profile_scope/domain/repository/i_profile_scope_repository.dart';
|
||||
|
||||
part 'profile_scope_event.dart';
|
||||
part 'profile_scope_state.dart';
|
||||
|
||||
class ProfileScopeBloc extends Bloc<ProfileScopeEvent, ProfileScopeState> {
|
||||
ProfileScopeBloc({required IProfileScopeRepository profileRepository})
|
||||
: _profileRepository = profileRepository,
|
||||
super(ProfileScopeInitialState()) {
|
||||
// Вам необходимо добавлять только
|
||||
// один обработчик событий в конструкторе
|
||||
on<ProfileScopeEvent>((event, emit) async {
|
||||
return switch (event) {
|
||||
ProfileScopeFetchProfileEvent() => await _fetchProfile(event, emit),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
final IProfileScopeRepository _profileRepository;
|
||||
|
||||
Future<void> _fetchProfile(
|
||||
ProfileScopeFetchProfileEvent event,
|
||||
Emitter<ProfileScopeState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(ProfileScopeWaitingState());
|
||||
final data = await _profileRepository.fetchUserProfile(event.id);
|
||||
emit(ProfileScopeSuccessState(data: data));
|
||||
} on Object catch (error, stackTrace) {
|
||||
emit(
|
||||
ProfileScopeErrorState(
|
||||
message: 'Ошибка при загрузке профиля',
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
part of 'profile_scope_bloc.dart';
|
||||
|
||||
sealed class ProfileScopeEvent extends Equatable {
|
||||
const ProfileScopeEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class ProfileScopeFetchProfileEvent extends ProfileScopeEvent {
|
||||
final String id;
|
||||
|
||||
const ProfileScopeFetchProfileEvent({required this.id});
|
||||
|
||||
@override
|
||||
List<Object> get props => [id];
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
part of 'profile_scope_bloc.dart';
|
||||
|
||||
sealed class ProfileScopeState extends Equatable {
|
||||
const ProfileScopeState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class ProfileScopeInitialState extends ProfileScopeState {}
|
||||
|
||||
final class ProfileScopeWaitingState extends ProfileScopeState {}
|
||||
|
||||
final class ProfileScopeErrorState extends ProfileScopeState {
|
||||
final String message;
|
||||
final Object error;
|
||||
final StackTrace? stackTrace;
|
||||
|
||||
const ProfileScopeErrorState({
|
||||
required this.message,
|
||||
required this.error,
|
||||
this.stackTrace,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [message, error];
|
||||
}
|
||||
|
||||
final class ProfileScopeSuccessState extends ProfileScopeState {
|
||||
final Object data;
|
||||
|
||||
const ProfileScopeSuccessState({required this.data});
|
||||
|
||||
@override
|
||||
List<Object> get props => [data];
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import 'package:friflex_starter/di/di_base_repo.dart';
|
||||
|
||||
/// {@template IProfileScopeRepository}
|
||||
///
|
||||
/// {@endtemplate}
|
||||
abstract interface class IProfileScopeRepository with DiBaseRepo {
|
||||
Future<String> fetchUserProfile(String id);
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||
import 'package:friflex_starter/features/profile_scope/domain/bloc/profile_scope_bloc.dart';
|
||||
|
||||
class ProfileInheritedScope extends InheritedWidget {
|
||||
const ProfileInheritedScope({
|
||||
required this.profileScopeBloc,
|
||||
required super.child,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final ProfileScopeBloc profileScopeBloc;
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ProfileInheritedScope oldWidget) =>
|
||||
profileScopeBloc != oldWidget.profileScopeBloc;
|
||||
}
|
||||
|
||||
class ProfileScope extends StatefulWidget {
|
||||
const ProfileScope({
|
||||
required this.child,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
|
||||
static ProfileInheritedScope? maybeOf(
|
||||
BuildContext context, {
|
||||
bool listen = false,
|
||||
}) {
|
||||
return listen
|
||||
? context.dependOnInheritedWidgetOfExactType<ProfileInheritedScope>()
|
||||
: context.getInheritedWidgetOfExactType<ProfileInheritedScope>();
|
||||
}
|
||||
|
||||
static ProfileInheritedScope of(
|
||||
BuildContext context, {
|
||||
bool listen = false,
|
||||
}) {
|
||||
final result = maybeOf(context, listen: listen);
|
||||
|
||||
if (result == null) {
|
||||
throw StateError(
|
||||
'ProfileScope is not found above widget ${context.widget}',
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _ProfileScopeState();
|
||||
}
|
||||
|
||||
class _ProfileScopeState extends State<ProfileScope> {
|
||||
late final ProfileScopeBloc _profileScopeBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_profileScopeBloc =
|
||||
ProfileScopeBloc(profileRepository: context.di.repositories.profileScopeRepository);
|
||||
_profileScopeBloc.add(const ProfileScopeFetchProfileEvent(id: '1'));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_profileScopeBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ProfileInheritedScope(profileScopeBloc: _profileScopeBloc, child: widget.child);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:friflex_starter/features/profile_scope/domain/bloc/profile_scope_bloc.dart';
|
||||
import 'package:friflex_starter/features/profile_scope/presentation/profile_scope.dart';
|
||||
|
||||
// Класс экрана, где мы инициализируем ProfileScopeBloc
|
||||
class ProfileScopeScreen extends StatelessWidget {
|
||||
const ProfileScopeScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const ProfileScope(
|
||||
child: _ProfileScopeView(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ProfileScopeView extends StatelessWidget {
|
||||
const _ProfileScopeView();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Profile Scope')),
|
||||
body: Center(
|
||||
child: BlocBuilder<ProfileScopeBloc, ProfileScopeState>(
|
||||
bloc: ProfileScope.of(context).profileScopeBloc,
|
||||
builder: (context, state) {
|
||||
return switch (state) {
|
||||
ProfileScopeSuccessState() => Text('Data: ${state.props.first}'),
|
||||
ProfileScopeErrorState() => Text('Error: ${state.message}'),
|
||||
_ => const CircularProgressIndicator(),
|
||||
};
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friflex_starter/app/app_context_ext.dart';
|
||||
import 'package:friflex_starter/app/app_env.dart';
|
||||
import 'package:friflex_starter/features/debug/debug_routes.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
/// Класс для реализации корневой страницы приложения
|
||||
@@ -18,11 +21,19 @@ class RootScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
floatingActionButton: context.di.env != AppEnv.prod
|
||||
? FloatingActionButton(
|
||||
child: const Icon(Icons.bug_report),
|
||||
onPressed: () {
|
||||
context.pushNamed(DebugRoutes.debugScreenName);
|
||||
},
|
||||
)
|
||||
: null,
|
||||
body: navigationShell,
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.bug_report), label: 'Debug'),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Профиль'),
|
||||
],
|
||||
currentIndex: navigationShell.currentIndex,
|
||||
onTap: navigationShell.goBranch,
|
||||
|
||||
@@ -2,29 +2,33 @@ import 'package:flutter/cupertino.dart';
|
||||
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/profile/presentation/screens/profile_screen.dart';
|
||||
import 'package:friflex_starter/features/profile_scope/presentation/screens/profile_scope_screen.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:go_router/go_router.dart';
|
||||
|
||||
/// Класс, реализующий роутер приложения и все поля классов
|
||||
/// {@template app_router}
|
||||
/// AppRouter - класс для управления навигацией в приложении
|
||||
/// [createRouter] - метод для создания экземпляра GoRouter
|
||||
/// {@endtemplate}
|
||||
class AppRouter {
|
||||
/// Конструктор для инициализации роутера
|
||||
/// {@macro app_router}
|
||||
const AppRouter();
|
||||
|
||||
/// Ключ для доступа к корневому навигатору приложения
|
||||
static final rootNavigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
/// Начальный роут приложения
|
||||
static String get initialLocation => '/debug';
|
||||
static String get initialLocation => '/main';
|
||||
|
||||
/// Метод для создания экземпляра GoRouter
|
||||
static GoRouter createRouter(IDebugService debugService) {
|
||||
return GoRouter(
|
||||
navigatorKey: rootNavigatorKey,
|
||||
debugLogDiagnostics: true,
|
||||
initialLocation: initialLocation,
|
||||
observers: [
|
||||
debugService.routeObserver,
|
||||
],
|
||||
routes: [
|
||||
StatefulShellRoute.indexedStack(
|
||||
parentNavigatorKey: rootNavigatorKey,
|
||||
@@ -32,21 +36,14 @@ class AppRouter {
|
||||
RootScreen(navigationShell: navigationShell),
|
||||
branches: [
|
||||
MainRoutes.buildShellBranch(),
|
||||
DebugRoutes.buildShellBranch(),
|
||||
ProfileRoutes.buildShellBranch(),
|
||||
],
|
||||
),
|
||||
DebugRoutes.buildRoutes(),
|
||||
GoRoute(
|
||||
path: '/splash',
|
||||
builder: (context, state) => const SplashScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/profile',
|
||||
builder: (context, state) => const ProfileScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/profile_scope',
|
||||
builder: (context, state) => const ProfileScopeScreen(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:async';
|
||||
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/di/di_container.dart';
|
||||
@@ -55,6 +56,9 @@ class AppRunner {
|
||||
|
||||
_timerRunner = TimerRunner(_debugService);
|
||||
|
||||
// Инициализация Talker для логирования Bloc
|
||||
Bloc.observer = _debugService.blocObserver;
|
||||
|
||||
// Инициализация приложения
|
||||
await _initApp();
|
||||
|
||||
|
||||
21
lib/ui_kit/components/app_box.dart
Normal file
21
lib/ui_kit/components/app_box.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
/// {@template HBox}
|
||||
/// HBox виджет для вертикального отступа (Надстройка над SizedBox)
|
||||
/// [height] - высота отступа
|
||||
/// [key] - ключ виджета
|
||||
/// {@endtemplate}
|
||||
class HBox extends SizedBox {
|
||||
/// {@macro HBox}
|
||||
const HBox(double height, {super.key}) : super(height: height);
|
||||
}
|
||||
|
||||
/// {@template WBox}
|
||||
/// WBox виджет для вертикального отступа (Надстройка над SizedBox)
|
||||
/// [width] - ширина отступа
|
||||
/// [key] - ключ виджета
|
||||
/// {@endtemplate}
|
||||
class WBox extends SizedBox {
|
||||
/// {@macro WBox}
|
||||
const WBox(double width, {super.key}) : super(width: width);
|
||||
}
|
||||
Reference in New Issue
Block a user