Fix login and settings

This commit is contained in:
2025-07-07 18:44:08 +03:00
parent 4670cdf5fb
commit c997feac4a
4 changed files with 174 additions and 162 deletions
+4 -6
View File
@@ -16,16 +16,14 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
}
void _onAuthStarted(AuthStarted event, Emitter<AuthState> emit) async {
// Комментарий: Инициализируем UserCubit при старте AuthBloc.
// Это гарантирует, что UserCubit загрузит данные пользователя
// перед тем, как AuthBloc будет принимать решение об аутентификации.
await _userCubit.init();
// Получаем текущее состояние пользователя из UserCubit после инициализации
// Комментарий: Этот метод теперь просто проверяет ТЕКУЩЕЕ состояние UserCubit,
// не инициируя его повторную загрузку. Инициализация происходит один раз в main.dart.
final userState = _userCubit.state;
if (userState is UserLoaded && userState.user != null) {
// Если пользователь уже загружен в UserCubit, считаем его аутентифицированным.
emit(AuthAuthenticated(user: userState.user!));
} else {
// Если пользователь не загружен (null) или состояние другое, считаем его неаутентифицированным.
emit(AuthUnauthenticated());
}
}
+16 -29
View File
@@ -23,7 +23,17 @@ class UserCubit extends Cubit<UserState> {
final ICategoryRepository _categoryRepository;
final ITagRepository _tagRepository;
final ITransactionRepository _transactionRepository;
final Logger _logger = Logger();
final Logger _logger = Logger(
printer: PrettyPrinter(
methodCount: 15, // Number of method calls to be displayed
errorMethodCount: 8, // Number of method calls if stacktrace is provided
lineLength: 120, // Width of the output
colors: true, // Colorful log messages
printEmojis: true, // Print an emoji for each log message
// Should each log print contain a timestamp
dateTimeFormat: DateTimeFormat.onlyTimeAndSinceStart,
),
);
UserCubit({
required IGlobalSettingsRepository settingsRepository,
@@ -44,48 +54,25 @@ class UserCubit extends Cubit<UserState> {
}
Future<void> _init() async {
emit(UserLoading(progress: 0.1, message: 'Поиск пользователя...'));
emit(UserLoading(progress: 0.1, message: 'Инициализация...'));
try {
// Этап 1: Проверка существующего пользователя
final userId = await _settingsRepository.getCurrentUserId();
emit(UserLoading(progress: 0.3, message: 'Проверка пользователя...'));
User? currentUser;
if (userId != null) {
emit(UserLoading(progress: 0.3, message: 'Поиск пользователя...'));
currentUser = await _userRepository.getById(userId);
if (currentUser == null) {
_logger.w('User with ID $userId not found. Clearing key.');
await _settingsRepository.setCurrentUserId(null);
}
}
// Этап 2: Создание пользователя по умолчанию при необходимости
if (currentUser == null) {
emit(UserLoading(progress: 0.5, message: 'Проверка данных...'));
final allUsers = await _userRepository.getAll();
if (allUsers.isEmpty) {
_logger.i('No users found, creating default user');
emit(UserLoading(progress: 0.6, message: 'Создание пользователя...'));
// Комментарий: Вызываем создание пользователя по умолчанию.
// Этот метод сам установит состояние UserLoaded, поэтому после него нужно завершить выполнение _init.
await _createDefaultUser();
return;
} else {
_logger.i('Setting first user as current');
currentUser = allUsers.first;
// Комментарий: Устанавливаем первого пользователя как текущего.
// Этот метод также устанавливает состояние UserLoaded, поэтому выходим.
await _setCurrentUser(currentUser);
return;
}
}
// Этап 3: Завершение инициализации (этот блок теперь выполняется только для уже существующих пользователей)
emit(UserLoading(progress: 1.0, message: 'Завершение...'));
await Future.delayed(const Duration(milliseconds: 300));
emit(UserLoaded(currentUser));
} catch (e, stack) {
_logger.e('Error initializing user cubit', error: e, stackTrace: stack);
_logger.e('--- UserCubit: FATAL ERROR in _init ---', error: e, stackTrace: stack);
emit(UserError('Ошибка загрузки пользователя: ${e.toString()}'));
}
}
+5 -4
View File
@@ -54,10 +54,11 @@ class MyApp extends StatelessWidget {
BlocProvider(create: (context) => GetIt.instance<SmsCubit>()),
],
child: BlocListener<UserCubit, UserState>(
// Запускаем AuthBloc, как только UserCubit завершил загрузку,
// независимо от того, найден пользователь или нет.
listenWhen: (previous, current) => current is UserLoaded,
listener: (context, userState) {
if (userState is UserLoaded && userState.user != null) {
context.read<AuthBloc>().add(AuthStarted());
}
context.read<AuthBloc>().add(AuthStarted());
},
child: BlocBuilder<SettingsCubit, SettingsState>(
builder: (context, settingsState) {
@@ -83,7 +84,7 @@ class MyApp extends StatelessWidget {
locale: Locale(languageCode),
home: BlocBuilder<UserCubit, UserState>(
builder: (context, userState) {
if (userState is UserLoading) {
if (userState is UserLoading || userState is UserInitial) {
return const SplashScreen();
}
+149 -123
View File
@@ -33,131 +33,157 @@ class SettingsPage extends StatelessWidget {
);
}
},
child: BlocBuilder<SettingsCubit, SettingsState>(
builder: (context, state) {
if (state is SettingsLoaded) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SwitchListTile(
title: Text(localizations.darkModeSetting),
subtitle: Text(localizations.darkModeDescription),
value: state.isDarkMode,
// Комментарий: При изменении положения переключателя (onChanged) мы вызываем метод `toggleDarkMode` у Cubit.
// `context.read<SettingsCubit>()` используется для доступа к Cubit без подписки на его изменения.
// Это хорошо для вызова методов. Также передаем userId из UserService.
onChanged: (value) {
// Получаем ID текущего пользователя из UserCubit
final userState = context.read<UserCubit>().state;
if (userState is UserLoaded && userState.user != null) {
final userId = userState.user!.id;
context.read<SettingsCubit>().toggleDarkMode(value, userId);
}
},
),
const Divider(),
ListTile(
title: Text(localizations.languageSetting),
subtitle: Text(localizations.languageDescription),
trailing: DropdownButton<String>(
value: state.languageCode,
// Комментарий: При выборе нового языка вызываем метод `changeLanguage` у Cubit.
// Также передаем userId из UserService.
onChanged: (String? newValue) {
if (newValue != null) {
// Получаем ID текущего пользователя из UserCubit
final userState = context.read<UserCubit>().state;
if (userState is UserLoaded && userState.user != null) {
final userId = userState.user!.id;
context.read<SettingsCubit>().changeLanguage(newValue, userId);
}
}
},
// Комментарий: Формируем список доступных языков.
items: <String>['en', 'ru']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
// Комментарий: Отображаем локализованное название языка.
child: Text(value == 'en'
? localizations.englishLanguage
: localizations.russianLanguage),
);
}).toList(),
),
),
const Divider(),
ListTile(
title: Text(localizations.currencySetting),
subtitle: Text(localizations.currencyDescription),
trailing: DropdownButton<String>(
value: state.defaultCurrency,
// Комментарий: При выборе новой валюты вызываем метод `changeCurrency` у Cubit.
// Также передаем userId из UserService.
onChanged: (String? newValue) {
if (newValue != null) {
// Получаем ID текущего пользователя из UserCubit
final userState = context.read<UserCubit>().state;
if (userState is UserLoaded && userState.user != null) {
final userId = userState.user!.id;
context.read<SettingsCubit>().changeCurrency(newValue, userId);
}
}
},
// Комментарий: Формируем список доступных валют. Можно расширить этот список.
items: <String>['RUB', 'USD', 'EUR']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
),
const Divider(),
// Комментарий: ListTile для перехода на страницу редактирования категорий.
ListTile(
title: Text(localizations.editCategories),
subtitle: Text(localizations.editCategoriesDescription),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const CategoryListPage(),
child: BlocBuilder<UserCubit, UserState>(
builder: (context, userState) {
if (userState is UserLoaded && userState.user != null) {
// Комментарий: Как только пользователь загружен, мы загружаем его настройки.
context.read<SettingsCubit>().loadSettings(userState.user!.id);
return BlocBuilder<SettingsCubit, SettingsState>(
builder: (context, state) {
if (state is SettingsLoaded) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SwitchListTile(
title: Text(localizations.darkModeSetting),
subtitle: Text(localizations.darkModeDescription),
value: state.isDarkMode,
// Комментарий: При изменении положения переключателя (onChanged) мы вызываем метод `toggleDarkMode` у Cubit.
// `context.read<SettingsCubit>()` используется для доступа к Cubit без подписки на его изменения.
// Это хорошо для вызова методов. Также передаем userId из UserService.
onChanged: (value) {
// Получаем ID текущего пользователя из UserCubit
final userState = context.read<UserCubit>().state;
if (userState is UserLoaded &&
userState.user != null) {
final userId = userState.user!.id;
context
.read<SettingsCubit>()
.toggleDarkMode(value, userId);
}
},
),
const Divider(),
ListTile(
title: Text(localizations.languageSetting),
subtitle: Text(localizations.languageDescription),
trailing: DropdownButton<String>(
value: state.languageCode,
// Комментарий: При выборе нового языка вызываем метод `changeLanguage` у Cubit.
// Также передаем userId из UserService.
onChanged: (String? newValue) {
if (newValue != null) {
// Получаем ID текущего пользователя из UserCubit
final userState =
context.read<UserCubit>().state;
if (userState is UserLoaded &&
userState.user != null) {
final userId = userState.user!.id;
context
.read<SettingsCubit>()
.changeLanguage(newValue, userId);
}
}
},
// Комментарий: Формируем список доступных языков.
items: <String>['en', 'ru']
.map<DropdownMenuItem<String>>(
(String value) {
return DropdownMenuItem<String>(
value: value,
// Комментарий: Отображаем локализованное название языка.
child: Text(value == 'en'
? localizations.englishLanguage
: localizations.russianLanguage),
);
}).toList(),
),
),
const Divider(),
ListTile(
title: Text(localizations.currencySetting),
subtitle: Text(localizations.currencyDescription),
trailing: DropdownButton<String>(
value: state.defaultCurrency,
// Комментарий: При выборе новой валюты вызываем метод `changeCurrency` у Cubit.
// Также передаем userId из UserService.
onChanged: (String? newValue) {
if (newValue != null) {
// Получаем ID текущего пользователя из UserCubit
final userState =
context.read<UserCubit>().state;
if (userState is UserLoaded &&
userState.user != null) {
final userId = userState.user!.id;
context
.read<SettingsCubit>()
.changeCurrency(newValue, userId);
}
}
},
// Комментарий: Формируем список доступных валют. Можно расширить этот список.
items: <String>['RUB', 'USD', 'EUR']
.map<DropdownMenuItem<String>>(
(String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
),
const Divider(),
// Комментарий: ListTile для перехода на страницу редактирования категорий.
ListTile(
title: Text(localizations.editCategories),
subtitle:
Text(localizations.editCategoriesDescription),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const CategoryListPage(),
),
);
},
),
const Divider(),
ListTile(
title: Text(localizations.editTags),
subtitle: Text(localizations.editTagsDescription),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const TagListPage(),
),
);
},
),
const Divider(),
// Комментарий: ListTile для запуска процесса загрузки SMS-сообщений.
ListTile(
title: Text(localizations.loadSmsMessages),
subtitle:
Text(localizations.loadSmsMessagesDescription),
onTap: () {
// Комментарий: При нажатии на кнопку мы вызываем метод `loadSmsMessages` у SmsCubit.
// Это инициирует процесс получения и сохранения SMS-сообщений.
context.read<SmsCubit>().loadSmsMessages();
},
),
const Divider(),
],
),
);
},
),
const Divider(),
ListTile(
title: Text(localizations.editTags),
subtitle: Text(localizations.editTagsDescription),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const TagListPage(),
),
);
},
),
const Divider(),
// Комментарий: ListTile для запуска процесса загрузки SMS-сообщений.
ListTile(
title: Text(localizations.loadSmsMessages),
subtitle: Text(localizations.loadSmsMessagesDescription),
onTap: () {
// Комментарий: При нажатии на кнопку мы вызываем метод `loadSmsMessages` у SmsCubit.
// Это инициирует процесс получения и сохранения SMS-сообщений.
context.read<SmsCubit>().loadSmsMessages();
},
),
const Divider(),
],
),
);
} else {
return const Center(child: CircularProgressIndicator());
}
},
);
} else {
return const Center(child: CircularProgressIndicator());
}