Refactors HiveService and dependency injection

Improves the management of Hive boxes by introducing separate initialization for global and user-specific data.

This change enhances data isolation and allows for dynamic dependency injection based on the authentication state. It introduces `initGlobalDependencies` and `initUserSpecificDependencies` to manage the application's dependencies. User-scoped repositories and cubits are now registered/unregistered upon login/logout. The UserCubit is updated to allow dynamic injection of user-specific repositories after authentication. The TagCubit is refactored to use a repository.
This commit is contained in:
2025-07-09 23:26:00 +03:00
parent 8040839ba3
commit 1ceacf88fb
9 changed files with 418 additions and 257 deletions
+93 -67
View File
@@ -1,81 +1,107 @@
import 'package:budget_app/hive/hive_registrar.g.dart';
import 'package:budget_app/models/app_settings.dart'; // Добавлен импорт AppSettings
import 'package:budget_app/models/global_settings.dart';
import 'package:budget_app/models/sms_handler_settings.dart';
import 'package:hive_ce_flutter/hive_flutter.dart';
import 'package:logger/logger.dart';
import '/models/category.dart';
import '/models/sms_message.dart';
import '/models/tag.dart';
import '/models/transaction_record.dart';
import '/models/user.dart';
final _logger = Logger();
import '../../models/app_settings.dart';
import '../../models/category.dart';
import '../../models/global_settings.dart';
import '../../models/sms_handler_settings.dart';
import '../../models/sms_message.dart';
import '../../models/tag.dart';
import '../../models/transaction_record.dart';
import '../../models/user.dart';
/// Сервис для управления Hive Box.
/// Отвечает за инициализацию, открытие и закрытие глобальных и пользовательских хранилищ.
class HiveService {
static const String _appSettingsBox =
'app_settings'; // Переименовано для настроек приложения
static const String _categoryBox = 'categories';
static const String _tagBox = 'tags';
static const String _transactionBox = 'transactions';
static const String _userBox = 'users';
static const String _smsMessageBox = 'smsMessages';
static const String _globalSettings = 'global_settings';
static const String _smsHandlerSettingsBox = 'sms_handler_settings';
// --- Глобальные Box ---
// Эти хранилища не зависят от пользователя и инициализируются один раз.
static late final Box<User> users;
static late final Box<GlobalSettings> globalSettings;
static Future<void> init() async {
_logger.i('Initializing Hive database');
/// Инициализация глобальных Hive Box.
/// Должен вызываться при старте приложения.
static Future<void> initGlobalBoxes() async {
await Hive.initFlutter();
// Регистрация адаптеров Hive...
_registerAdapters();
Hive.registerAdapters();
// Открытие всех Box'ов
await Future.wait([
Hive.openBox<AppSettings>(
_appSettingsBox,
), // Открываем Box для AppSettings
Hive.openBox<Category>(_categoryBox),
Hive.openBox<Tag>(_tagBox),
Hive.openBox<TransactionRecord>(_transactionBox),
Hive.openBox<User>(_userBox),
Hive.openBox<SmsMessage>(_smsMessageBox),
Hive.openBox<GlobalSettings>(_globalSettings),
Hive.openBox<SmsHandlerSettings>(_smsHandlerSettingsBox),
]);
// Проверка и заполнение начальными данными
_checkAndFillInitialData();
// Открытие глобальных хранилищ
users = await Hive.openBox<User>('users');
globalSettings = await Hive.openBox<GlobalSettings>('global_settings');
}
static Box<AppSettings> get appSettings =>
Hive.box<AppSettings>(_appSettingsBox); // Геттер для настроек
static Box<Category> get categories => Hive.box<Category>(_categoryBox);
static Box<Tag> get tags => Hive.box<Tag>(_tagBox);
static Box<TransactionRecord> get transactions =>
Hive.box<TransactionRecord>(_transactionBox);
static Box<User> get users => Hive.box<User>(_userBox);
static Box<SmsMessage> get smsMessages =>
Hive.box<SmsMessage>(_smsMessageBox);
static Box<GlobalSettings> get globalSettings =>
Hive.box<GlobalSettings>(_globalSettings);
static Box<SmsHandlerSettings> get smsHandlerSettings =>
Hive.box<SmsHandlerSettings>(_smsHandlerSettingsBox);
// --- Пользовательские Box ---
// Эти хранилища привязаны к конкретному пользователю.
// Они открываются после входа пользователя в систему.
static late Box<Category> categories;
static late Box<Tag> tags;
static late Box<TransactionRecord> transactions;
static late Box<AppSettings> appSettings;
static late Box<SmsHandlerSettings> smsHandlerSettings;
static late Box<SmsMessage> smsMessages;
/// Проверяет и заполняет боксы начальными данными при первом запуске
static void _checkAndFillInitialData() {
final userBox = users;
final catBox = categories;
final tagBox = tags;
final transactionBox = transactions;
/// Инициализация Hive Box для конкретного пользователя.
/// [userId] - Уникальный идентификатор пользователя.
static Future<void> initUserBoxes(String userId) async {
// Открываем Box'ы с именами, включающими userId для изоляции данных.
// Например: 'categories_user123'
categories = await Hive.openBox<Category>('categories_\$userId');
tags = await Hive.openBox<Tag>('tags_\$userId');
transactions = await Hive.openBox<TransactionRecord>(
'transactions_\$userId',
);
appSettings = await Hive.openBox<AppSettings>('app_settings_\$userId');
smsHandlerSettings = await Hive.openBox<SmsHandlerSettings>(
'sms_handler_settings_\$userId',
);
smsMessages = await Hive.openBox<SmsMessage>('sms_messages_\$userId');
}
if (userBox.isEmpty &&
catBox.isEmpty &&
tagBox.isEmpty &&
transactionBox.isEmpty) {
_logger.i(
'Initial data is empty. It will be filled when a user is created.',
);
/// Закрытие пользовательских Hive Box.
/// Должен вызываться при выходе пользователя из системы.
static Future<void> closeUserBoxes() async {
await categories.close();
await tags.close();
await transactions.close();
await appSettings.close();
await smsHandlerSettings.close();
await smsMessages.close();
}
/// Регистрация всех адаптеров Hive.
// Регистрация всех адаптеров Hive.
static void _registerAdapters() {
if (!Hive.isAdapterRegistered(UserAdapter().typeId)) {
Hive.registerAdapter(UserAdapter());
}
if (!Hive.isAdapterRegistered(CategoryAdapter().typeId)) {
Hive.registerAdapter(CategoryAdapter());
}
if (!Hive.isAdapterRegistered(TagAdapter().typeId)) {
Hive.registerAdapter(TagAdapter());
}
if (!Hive.isAdapterRegistered(TransactionRecordAdapter().typeId)) {
Hive.registerAdapter(TransactionRecordAdapter());
}
if (!Hive.isAdapterRegistered(AppSettingsAdapter().typeId)) {
Hive.registerAdapter(AppSettingsAdapter());
}
if (!Hive.isAdapterRegistered(GlobalSettingsAdapter().typeId)) {
Hive.registerAdapter(GlobalSettingsAdapter());
}
if (!Hive.isAdapterRegistered(SmsHandlerSettingsAdapter().typeId)) {
Hive.registerAdapter(SmsHandlerSettingsAdapter());
}
if (!Hive.isAdapterRegistered(SmsMessageAdapter().typeId)) {
Hive.registerAdapter(SmsMessageAdapter());
}
// Добавляем регистрацию SmsProcessingRuleAdapter
if (!Hive.isAdapterRegistered(SmsProcessingRuleAdapter().typeId)) {
Hive.registerAdapter(SmsProcessingRuleAdapter());
}
// Добавляем регистрацию SmsProcessingTypeAdapter
if (!Hive.isAdapterRegistered(SmsProcessingTypeAdapter().typeId)) {
Hive.registerAdapter(SmsProcessingTypeAdapter());
}
}
}
+14 -9
View File
@@ -1,11 +1,19 @@
import 'package:budget_app/data/database/hive_service.dart';
import 'package:budget_app/data/repositories/interfaces/itag_repository.dart';
import 'package:budget_app/models/tag.dart';
import 'package:hive_ce/hive.dart';
import '/data/repositories/interfaces/itag_repository.dart';
import '../../models/tag.dart';
class HiveTagRepository implements ITagRepository {
final Box<Tag> _box;
HiveTagRepository(); // Конструктор без параметров
HiveTagRepository(this._box);
// Получаем бокс тегов из HiveService
Box<Tag> get _box {
if (HiveService.tags.isOpen) {
return HiveService.tags;
} else {
throw Exception('Tags box is not initialized');
}
}
@override
Future<List<Tag>> getAll() async {
@@ -32,13 +40,10 @@ class HiveTagRepository implements ITagRepository {
await _box.delete(id);
}
// Комментарий: Реализуем новый метод, объявленный в интерфейсе ITagRepository.
@override
Future<List<Tag>> getAllByUser(String userId) async {
// Комментарий: Мы используем метод `where` для фильтрации всех записей в хранилище Hive.
// Он перебирает все теги (`_box.values`) и возвращает только те,
// у которых поле `userId` совпадает с идентификатором, переданным в метод.
return _box.values.where((tag) => tag.userId == userId).toList();
// Фильтрация по userId больше не требуется, так как бокс уже пользовательский
return _box.values.toList();
}
@override
+134 -49
View File
@@ -1,100 +1,185 @@
import 'package:budget_app/data/repositories/hive_sms_handler_repository.dart';
import 'package:budget_app/data/repositories/hive_sms_message_repository.dart';
import 'package:budget_app/data/repositories/interfaces/isms_handler_repository.dart';
import 'package:budget_app/data/repositories/interfaces/isms_message_repository.dart';
import 'package:get_it/get_it.dart';
import 'data/database/hive_service.dart';
import 'data/repositories/hive_category_repository.dart';
import 'data/repositories/hive_global_settings_repository.dart';
import 'data/repositories/hive_settings_repository.dart'; // Добавляем импорт HiveSettingsRepository
import 'data/repositories/hive_settings_repository.dart';
import 'data/repositories/hive_sms_handler_repository.dart';
import 'data/repositories/hive_sms_message_repository.dart';
import 'data/repositories/hive_tag_repository.dart';
import 'data/repositories/hive_transaction_repository.dart';
import 'data/repositories/hive_user_repository.dart';
import 'data/repositories/interfaces/icategory_repository.dart';
import 'data/repositories/interfaces/iglobal_settings_repository.dart';
import 'data/repositories/interfaces/isettings_repository.dart'; // Добавляем импорт ISettingsRepository
import 'data/repositories/interfaces/isettings_repository.dart';
import 'data/repositories/interfaces/isms_handler_repository.dart';
import 'data/repositories/interfaces/isms_message_repository.dart';
import 'data/repositories/interfaces/itag_repository.dart';
import 'data/repositories/interfaces/itransaction_repository.dart';
import 'data/repositories/interfaces/iuser_repository.dart';
import 'logic/auth/auth_bloc.dart';
import 'logic/category/category_cubit.dart';
import 'logic/settings/settings_cubit.dart'; // Импортируем SettingsCubit
import 'logic/settings/settings_cubit.dart';
import 'logic/sms/sms_cubit.dart';
import 'logic/tag/tag_cubit.dart';
import 'logic/transaction/transaction_bloc.dart';
import 'logic/user/user_cubit.dart'; // Импортируем UserCubit вместо UserService
import 'logic/user/user_cubit.dart';
import 'services/sms_service.dart';
final getIt = GetIt.instance;
Future<void> initDependencies() async {
/// Инициализация глобальных зависимостей, которые не зависят от пользователя.
/// Вызывается один раз при старте приложения.
Future<void> initGlobalDependencies() async {
// Инициализация Hive
await HiveService.init();
// Регистрация репозиториев
getIt.registerSingleton<ICategoryRepository>(
HiveCategoryRepository(HiveService.categories),
);
getIt.registerSingleton<ITagRepository>(HiveTagRepository(HiveService.tags));
getIt.registerSingleton<ITransactionRepository>(
HiveTransactionRepository(HiveService.transactions),
);
await HiveService.initGlobalBoxes();
// Глобальные репозитории
getIt.registerSingleton<IUserRepository>(
HiveUserRepository(HiveService.users),
);
// Регистрируем ISettingsRepository с реализацией HiveSettingsRepository
getIt.registerSingleton<ISettingsRepository>(
HiveSettingsRepository(HiveService.appSettings),
);
getIt.registerSingleton<IGlobalSettingsRepository>(
HiveGlobalSettingsRepository(HiveService.globalSettings),
);
getIt.registerSingleton<ISmsHandlerRepository>(
HiveSmsHandlerRepository(HiveService.smsHandlerSettings),
);
// Services
// Сервисы
getIt.registerSingleton<SmsService>(SmsService());
// Регистрируем UserCubit с передачей всех необходимых репозиториев
// Cubits & Blocs, которые нужны до входа пользователя
getIt.registerSingleton<UserCubit>(
UserCubit(
settingsRepository: getIt<IGlobalSettingsRepository>(),
userRepository: getIt<IUserRepository>(),
categoryRepository: getIt<ICategoryRepository>(),
tagRepository: getIt<ITagRepository>(),
transactionRepository: getIt<ITransactionRepository>(),
// Эти репозитории будут заменены после входа пользователя
categoryRepository:
null, // Временно null, будет заменен в initUserSpecificDependencies
tagRepository:
null, // Временно null, будет заменен в initUserSpecificDependencies
transactionRepository:
null, // Временно null, будет заменен в initUserSpecificDependencies
),
);
// Blocs
getIt.registerFactory<AuthBloc>(() => AuthBloc(userCubit: getIt()));
}
/// Инициализация зависимостей, специфичных для пользователя.
/// Вызывается после успешной аутентификации.
Future<void> initUserSpecificDependencies(String userId) async {
// Открываем пользовательские Hive Box'ы
await HiveService.initUserBoxes(userId);
// --- Регистрация пользовательских репозиториев ---
// Используем lazy singletons, чтобы их можно было легко сбросить и создать заново при смене пользователя.
// Категории
if (getIt.isRegistered<ICategoryRepository>()) {
await getIt.unregister<ICategoryRepository>();
}
getIt.registerLazySingleton<ICategoryRepository>(
() => HiveCategoryRepository(HiveService.categories),
);
// Теги
if (getIt.isRegistered<ITagRepository>()) {
await getIt.unregister<ITagRepository>();
}
getIt.registerLazySingleton<ITagRepository>(
() => HiveTagRepository(),
);
// Транзакции
if (getIt.isRegistered<ITransactionRepository>()) {
await getIt.unregister<ITransactionRepository>();
}
getIt.registerLazySingleton<ITransactionRepository>(
() => HiveTransactionRepository(HiveService.transactions),
);
// Настройки приложения
if (getIt.isRegistered<ISettingsRepository>()) {
await getIt.unregister<ISettingsRepository>();
}
getIt.registerLazySingleton<ISettingsRepository>(
() => HiveSettingsRepository(HiveService.appSettings),
);
// Настройки обработчика SMS
if (getIt.isRegistered<ISmsHandlerRepository>()) {
await getIt.unregister<ISmsHandlerRepository>();
}
getIt.registerLazySingleton<ISmsHandlerRepository>(
() => HiveSmsHandlerRepository(HiveService.smsHandlerSettings),
);
// Сообщения SMS
if (getIt.isRegistered<ISmsMessageRepository>()) {
await getIt.unregister<ISmsMessageRepository>();
}
getIt.registerLazySingleton<ISmsMessageRepository>(
() => HiveSmsMessageRepository(HiveService.smsMessages),
);
// --- Обновление UserCubit новыми репозиториями ---
// Мы не пересоздаем UserCubit, а просто обновляем его зависимости.
final userCubit = getIt<UserCubit>();
userCubit.categoryRepository = getIt<ICategoryRepository>();
userCubit.tagRepository = getIt<ITagRepository>();
userCubit.transactionRepository = getIt<ITransactionRepository>();
// --- Регистрация Cubits & Blocs, которые зависят от пользовательских данных ---
// Настройки
if (getIt.isRegistered<SettingsCubit>()) {
await getIt.unregister<SettingsCubit>();
}
getIt.registerSingleton<SettingsCubit>(
SettingsCubit(getIt()),
);
// Транзакции
if (getIt.isRegistered<TransactionBloc>()) {
await getIt.unregister<TransactionBloc>();
}
getIt.registerFactory<TransactionBloc>(
() => TransactionBloc(transactionRepository: getIt()),
);
// SMS
if (getIt.isRegistered<SmsCubit>()) {
await getIt.unregister<SmsCubit>();
}
getIt.registerFactory<SmsCubit>(() => SmsCubit(getIt(), getIt(), getIt()));
// Категории
if (getIt.isRegistered<CategoryCubit>()) {
await getIt.unregister<CategoryCubit>();
}
getIt.registerFactory<CategoryCubit>(
() => CategoryCubit(getIt()),
); // Регистрируем CategoryCubit с зависимостью от UserCubit
getIt.registerFactory<TagCubit>(() => TagCubit(getIt()));
getIt.registerSingleton<ISmsMessageRepository>(
HiveSmsMessageRepository(HiveService.smsMessages),
);
// Регистрация сервисов
getIt.registerSingleton<SettingsCubit>(
SettingsCubit(getIt()),
); // Регистрируем Cubit и передаем IUserRepository и UserService
// Теги
if (getIt.isRegistered<TagCubit>()) {
await getIt.unregister<TagCubit>();
}
getIt.registerFactory<TagCubit>(() => TagCubit(getIt()));
}
/// Сброс пользовательских зависимостей при выходе из системы.
Future<void> resetUserSpecificDependencies() async {
// Закрываем пользовательские Hive Box'ы
await HiveService.closeUserBoxes();
// Разрегистрация всех пользовательских зависимостей
await getIt.unregister<ICategoryRepository>();
await getIt.unregister<ITagRepository>();
await getIt.unregister<ITransactionRepository>();
await getIt.unregister<ISettingsRepository>();
await getIt.unregister<ISmsHandlerRepository>();
await getIt.unregister<ISmsMessageRepository>();
await getIt.unregister<SettingsCubit>();
await getIt.unregister<TransactionBloc>();
await getIt.unregister<SmsCubit>();
await getIt.unregister<CategoryCubit>();
await getIt.unregister<TagCubit>();
}
+10 -8
View File
@@ -2,6 +2,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:budget_app/models/user.dart';
import 'package:budget_app/logic/user/user_cubit.dart';
import 'package:budget_app/injection_container.dart' as di;
part 'auth_event.dart';
part 'auth_state.dart';
@@ -16,27 +17,28 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
}
void _onAuthStarted(AuthStarted event, Emitter<AuthState> emit) async {
// Комментарий: Этот метод теперь просто проверяет ТЕКУЩЕЕ состояние UserCubit,
// не инициируя его повторную загрузку. Инициализация происходит один раз в main.dart.
final userState = _userCubit.state;
if (userState is UserLoaded && userState.user != null) {
// Если пользователь уже загружен в UserCubit, считаем его аутентифицированным.
// Если пользователь уже загружен, инициализируем его зависимости.
await di.initUserSpecificDependencies(userState.user!.id);
emit(AuthAuthenticated(user: userState.user!));
} else {
// Если пользователь не загружен (null) или состояние другое, считаем его неаутентифицированным.
emit(AuthUnauthenticated());
}
}
void _onAuthLoggedIn(AuthLoggedIn event, Emitter<AuthState> emit) {
// Комментарий: Обновляем UserCubit с новым пользователем.
void _onAuthLoggedIn(AuthLoggedIn event, Emitter<AuthState> emit) async {
// Инициализируем зависимости для вошедшего пользователя.
await di.initUserSpecificDependencies(event.user.id);
_userCubit.setUser(event.user);
emit(AuthAuthenticated(user: event.user));
}
void _onAuthLoggedOut(AuthLoggedOut event, Emitter<AuthState> emit) {
// Вызываем logout у UserCubit
void _onAuthLoggedOut(AuthLoggedOut event, Emitter<AuthState> emit) async {
// Сбрасываем пользовательские зависимости.
await di.resetUserSpecificDependencies();
_userCubit.logout();
emit(AuthUnauthenticated());
}
}
+37 -38
View File
@@ -1,53 +1,52 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:budget_app/models/tag.dart';
import 'package:hive_ce/hive.dart';
import 'package:budget_app/logic/user/user_cubit.dart';
import 'package:budget_app/data/repositories/interfaces/itag_repository.dart';
import 'package:budget_app/logic/tag/tag_state.dart';
import 'package:budget_app/models/tag.dart'; // Добавляем импорт модели Tag
class TagCubit extends Cubit<List<Tag>> {
final UserCubit _userCubit;
final Box<Tag> _tagBox;
class TagCubit extends Cubit<TagState> {
final ITagRepository _repository;
TagCubit(this._userCubit)
: _tagBox = Hive.box<Tag>('tags'),
super([]);
TagCubit(this._repository) : super(TagInitial());
void loadTags() {
// Получаем текущего пользователя из состояния UserCubit
final userState = _userCubit.state;
if (userState is UserLoaded && userState.user != null) {
final currentUserId = userState.user!.id;
final userTags = _tagBox.values
.where((tag) => tag.userId == currentUserId)
.toList();
emit(List.from(userTags));
} else {
emit([]);
// Загрузка тегов
Future<void> loadTags() async {
emit(TagLoading());
try {
final tags = await _repository.getAll();
emit(TagLoaded(tags));
} catch (e) {
emit(TagError('Ошибка загрузки тегов: ${e.toString()}'));
}
}
void addTag(Tag tag) {
// Получаем текущего пользователя из состояния UserCubit
final userState = _userCubit.state;
if (userState is UserLoaded && userState.user != null) {
final currentUserId = userState.user!.id;
final newTag = tag.copyWith(userId: currentUserId);
_tagBox.put(newTag.id, newTag);
loadTags();
// Добавление тега
Future<void> addTag(Tag tag) async {
try {
await _repository.add(tag);
await loadTags(); // Перезагружаем список
} catch (e) {
emit(TagError('Ошибка добавления тега: ${e.toString()}'));
}
}
void updateTag(Tag tag) {
_tagBox.put(tag.id, tag);
loadTags();
// Обновление тега
Future<void> updateTag(Tag tag) async {
try {
await _repository.update(tag);
await loadTags(); // Перезагружаем список
} catch (e) {
emit(TagError('Ошибка обновления тега: ${e.toString()}'));
}
}
void deleteTag(String id) {
_tagBox.delete(id);
loadTags();
// Удаление тега
Future<void> deleteTag(String id) async {
try {
await _repository.delete(id);
await loadTags(); // Перезагружаем список
} catch (e) {
emit(TagError('Ошибка удаления тега: ${e.toString()}'));
}
}
}
+18
View File
@@ -0,0 +1,18 @@
import 'package:budget_app/models/tag.dart';
// Состояния для TagCubit
abstract class TagState {}
class TagInitial extends TagState {}
class TagLoading extends TagState {}
class TagLoaded extends TagState {
final List<Tag> tags;
TagLoaded(this.tags);
}
class TagError extends TagState {
final String message;
TagError(this.message);
}
+32 -15
View File
@@ -20,9 +20,10 @@ part 'user_state.dart';
class UserCubit extends Cubit<UserState> {
final IGlobalSettingsRepository _settingsRepository;
final IUserRepository _userRepository;
final ICategoryRepository _categoryRepository;
final ITagRepository _tagRepository;
final ITransactionRepository _transactionRepository;
// Эти репозитории теперь могут быть null и устанавливаются после входа пользователя.
ICategoryRepository? _categoryRepository;
ITagRepository? _tagRepository;
ITransactionRepository? _transactionRepository;
final Logger _logger = Logger(
printer: PrettyPrinter(
methodCount: 2, // Number of method calls to be displayed
@@ -38,15 +39,22 @@ class UserCubit extends Cubit<UserState> {
UserCubit({
required IGlobalSettingsRepository settingsRepository,
required IUserRepository userRepository,
required ICategoryRepository categoryRepository,
required ITagRepository tagRepository,
required ITransactionRepository transactionRepository,
}) : _settingsRepository = settingsRepository,
_userRepository = userRepository,
_categoryRepository = categoryRepository,
_tagRepository = tagRepository,
_transactionRepository = transactionRepository,
super(UserInitial());
// Репозитории сделаны опциональными в конструкторе.
ICategoryRepository? categoryRepository,
ITagRepository? tagRepository,
ITransactionRepository? transactionRepository,
}) : _settingsRepository = settingsRepository,
_userRepository = userRepository,
_categoryRepository = categoryRepository,
_tagRepository = tagRepository,
_transactionRepository = transactionRepository,
super(UserInitial());
// Сеттеры для внедрения зависимостей после аутентификации.
set categoryRepository(ICategoryRepository? repo) => _categoryRepository = repo;
set tagRepository(ITagRepository? repo) => _tagRepository = repo;
set transactionRepository(ITransactionRepository? repo) =>
_transactionRepository = repo;
// Метод для инициализации UserCubit
Future<void> init() async {
@@ -95,21 +103,30 @@ class UserCubit extends Cubit<UserState> {
}
Future<void> _createInitialData(String userId) async {
// Проверяем, что репозитории были установлены, прежде чем их использовать.
if (_categoryRepository == null ||
_tagRepository == null ||
_transactionRepository == null) {
_logger.w('User-specific repositories are not initialized. Skipping initial data creation.');
return;
}
try {
// Разбиваем создание данных на этапы
emit(UserLoading(progress: 0.7, message: 'Создание категорий...'));
await _categoryRepository.addAll(
// Используем '!', так как мы уже проверили на null.
await _categoryRepository!.addAll(
CategoryUtils.getDefaultCategories(userId),
);
emit(UserLoading(progress: 0.8, message: 'Создание тегов...'));
await _tagRepository.addAll(TagUtils.getDefaultTags(userId));
await _tagRepository!.addAll(TagUtils.getDefaultTags(userId));
emit(UserLoading(progress: 0.9, message: 'Создание транзакций...'));
await _transactionRepository.addAll(
await _transactionRepository!.addAll(
TransactionUtils.getSampleTransactions(userId),
);
} catch (e, stack) {
_logger.e(
'Error creating initial data for user: $userId',
error: e,
+52 -43
View File
@@ -5,6 +5,7 @@ import 'package:flutter_localizations/flutter_localizations.dart'; // Добав
import 'package:get_it/get_it.dart';
import '/l10n/app_localizations.dart';
import '/pages/splash/splash_screen.dart';
import 'injection_container.dart' as di;
import 'logic/auth/auth_bloc.dart';
import 'logic/settings/settings_cubit.dart'; // Импортируем SettingsCubit
@@ -14,25 +15,9 @@ import 'logic/user/user_cubit.dart'; // Импортируем UserCubit
import 'pages/login/login_page.dart';
import 'theme/app_theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import '/l10n/app_localizations.dart';
import '/logic/auth/auth_bloc.dart';
import '/logic/settings/settings_cubit.dart';
import '/logic/sms/sms_cubit.dart';
import '/logic/transaction/transaction_bloc.dart';
import '/logic/user/user_cubit.dart';
import '/pages/home/home_page.dart';
import '/pages/login/login_page.dart';
import '/pages/splash/splash_screen.dart';
import '/theme/app_theme.dart';
import 'injection_container.dart' as di;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await di.initDependencies();
await di.initGlobalDependencies();
runApp(const MyApp());
}
@@ -41,6 +26,7 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 1. Предоставляем глобальные Blocs, которые доступны всегда.
return MultiBlocProvider(
providers: [
BlocProvider(
@@ -49,31 +35,63 @@ class MyApp extends StatelessWidget {
BlocProvider(
create: (context) => GetIt.instance<AuthBloc>(),
),
BlocProvider(create: (context) => GetIt.instance<SettingsCubit>()),
BlocProvider(create: (context) => GetIt.instance<TransactionBloc>()),
BlocProvider(create: (context) => GetIt.instance<SmsCubit>()),
],
child: BlocListener<UserCubit, UserState>(
// Запускаем AuthBloc, как только UserCubit завершил загрузку,
// независимо от того, найден пользователь или нет.
// 2. Запускаем проверку аутентификации, как только UserCubit загрузил данные.
listenWhen: (previous, current) => current is UserLoaded,
listener: (context, userState) {
context.read<AuthBloc>().add(AuthStarted());
},
child: BlocBuilder<SettingsCubit, SettingsState>(
builder: (context, settingsState) {
final isDarkMode = settingsState is SettingsLoaded
? settingsState.isDarkMode
: false;
final languageCode = settingsState is SettingsLoaded
? settingsState.languageCode
: 'ru';
// 3. В зависимости от статуса аутентификации, строим разное дерево виджетов.
child: BlocBuilder<AuthBloc, AuthState>(
builder: (context, authState) {
// 4. Если пользователь аутентифицирован...
if (authState is AuthAuthenticated) {
// ...предоставляем все пользовательские зависимости.
return MultiBlocProvider(
providers: [
BlocProvider(create: (context) => GetIt.instance<SettingsCubit>()),
BlocProvider(create: (context) => GetIt.instance<TransactionBloc>()),
BlocProvider(create: (context) => GetIt.instance<SmsCubit>()),
// Можно добавить и остальные, если они нужны глобально в авторизованной зоне
],
// И строим приложение с пользовательской темой.
child: BlocBuilder<SettingsCubit, SettingsState>(
builder: (context, settingsState) {
final isDarkMode = settingsState is SettingsLoaded
? settingsState.isDarkMode
: false;
final languageCode = settingsState is SettingsLoaded
? settingsState.languageCode
: 'ru';
return MaterialApp(
title: 'Budget App',
theme: AppTheme.lightTheme(),
darkTheme: AppTheme.darkTheme(),
themeMode: isDarkMode ? ThemeMode.dark : ThemeMode.light,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales,
locale: Locale(languageCode),
home: const HomePage(),
);
},
),
);
}
// 5. Если пользователь НЕ аутентифицирован, показываем SplashScreen или LoginPage
// с темой по умолчанию.
return MaterialApp(
title: AppLocalizations.of(context)?.appTitle ?? 'Budget App',
title: 'Budget App',
theme: AppTheme.lightTheme(),
darkTheme: AppTheme.darkTheme(),
themeMode: isDarkMode ? ThemeMode.dark : ThemeMode.light,
themeMode: ThemeMode.light, // Тема по умолчанию
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
@@ -81,22 +99,13 @@ class MyApp extends StatelessWidget {
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales,
locale: Locale(languageCode),
locale: const Locale('ru'), // Язык по умолчанию
home: BlocBuilder<UserCubit, UserState>(
builder: (context, userState) {
if (userState is UserLoading || userState is UserInitial) {
return const SplashScreen();
}
return BlocBuilder<AuthBloc, AuthState>(
builder: (context, authState) {
if (authState is AuthAuthenticated) {
return const HomePage();
} else {
return const LoginPage();
}
},
);
return const LoginPage();
},
),
);
+28 -28
View File
@@ -1,10 +1,11 @@
import 'package:budget_app/l10n/app_localizations.dart';
import 'package:budget_app/logic/tag/tag_cubit.dart';
import 'package:budget_app/logic/tag/tag_state.dart'; // Импортируем состояния
import 'package:budget_app/models/tag.dart';
import 'package:budget_app/pages/tag/tag_edit_page.dart';
import 'package:budget_app/pages/tag/widgets/add_tag_button.dart';
import 'package:budget_app/pages/tag/widgets/tag_list_item.dart';
import 'package:budget_app/logic/user/user_cubit.dart'; // Импортируем UserCubit
import 'package:budget_app/logic/user/user_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
@@ -21,36 +22,40 @@ class _TagListPageState extends State<TagListPage> {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => GetIt.instance<TagCubit>()..loadTags(),
child: Builder(builder: (context) {
final localizations = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(localizations.editTags)),
body: BlocBuilder<TagCubit, List<Tag>>(
builder: (context, tags) {
return AnimatedList(
key: _listKey,
initialItemCount: tags.length,
itemBuilder: (context, index, animation) {
final tag = tags[index];
return SizeTransition(
sizeFactor: animation,
child: TagListItem(
tag: tag,
onEdit: () => _editTag(context, tag),
onDelete: () =>
_deleteTag(context, tag, index),
),
);
},
);
body: BlocBuilder<TagCubit, TagState>(
builder: (context, state) {
if (state is TagLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is TagError) {
return Center(child: Text(state.message));
} else if (state is TagLoaded) {
return AnimatedList(
key: _listKey,
initialItemCount: state.tags.length,
itemBuilder: (context, index, animation) {
final tag = state.tags[index];
return SizeTransition(
sizeFactor: animation,
child: TagListItem(
tag: tag,
onEdit: () => _editTag(context, tag),
onDelete: () => _deleteTag(context, tag, index),
),
);
},
);
} else {
return const Center(child: Text('Начните работу с тегами'));
}
},
),
floatingActionButton: AddTagButton(
onPressed: () => _addTag(context),
),
);
@@ -64,11 +69,8 @@ class _TagListPageState extends State<TagListPage> {
MaterialPageRoute(
builder: (_) => TagEditPage(
onSave: (name) {
// Получаем ID текущего пользователя из UserCubit
final userState = context.read<UserCubit>().state;
if (userState is! UserLoaded || userState.user == null) {
// Обработка случая, когда пользователь не аутентифицирован
// Возможно, показать сообщение об ошибке или перенаправить на страницу входа
return;
}
final currentUserId = userState.user!.id;
@@ -91,9 +93,7 @@ class _TagListPageState extends State<TagListPage> {
builder: (_) => TagEditPage(
tag: tag,
onSave: (name) {
final updatedTag = tag.copyWith(
name: name,
);
final updatedTag = tag.copyWith(name: name);
context.read<TagCubit>().updateTag(updatedTag);
},
),