Refactors data repositories for single-user mode

This commit refactors the data repositories to remove user-specific filtering. It simplifies the data access logic by retrieving all data from the Hive boxes and removing the need to filter by `userId` in the repository methods.

This change makes the app function in single-user mode and removes the need to manage multiple user contexts within the data layer.

Specifically:
- Removes `userId` parameter from repository methods.
- Updates the setting repository to use fixed keys.
- Removes user ID filtering from queries.
- Removes user ID parameters from Cubits and Blocs
- Updates initial data creation
This commit is contained in:
2025-07-12 21:54:00 +03:00
parent fe61b50494
commit 8b75299d41
40 changed files with 252 additions and 531 deletions
@@ -76,7 +76,7 @@ class HiveCategoryRepository implements ICategoryRepository {
@override
Future<List<Category>> getAllByUser(String userId) async {
try {
return _box.values.where((c) => c.userId == userId).toList();
return _box.values.toList();
} catch (e) {
throw Exception('Ошибка получения категорий пользователя: $e');
}
@@ -86,7 +86,7 @@ class HiveCategoryRepository implements ICategoryRepository {
Future<List<Category>> getIncomeCategoriesByUser(String userId) async {
try {
return _box.values
.where((c) => c.userId == userId && c.isIncome)
.where((c) => c.isIncome)
.toList();
} catch (e) {
throw Exception('Ошибка получения категорий доходов пользователя: $e');
@@ -97,7 +97,7 @@ class HiveCategoryRepository implements ICategoryRepository {
Future<List<Category>> getExpenseCategoriesByUser(String userId) async {
try {
return _box.values
.where((c) => c.userId == userId && !c.isIncome)
.where((c) => !c.isIncome)
.toList();
} catch (e) {
throw Exception('Ошибка получения категорий расходов пользователя: $e');
@@ -5,15 +5,26 @@ import 'package:hive_ce/hive.dart';
/// Реализация репозитория настроек с использованием Hive
class HiveSettingsRepository implements ISettingsRepository {
final Box<AppSettings> _box;
// Фиксированный ключ для хранения настроек приложения
static const String _settingsKey = 'user_settings';
HiveSettingsRepository(this._box);
@override
Future<AppSettings> getSettings(String userId) async {
Future<AppSettings> getSettings() async {
try {
// Возвращаем настройки пользователя или создаем новые по умолчанию
final settings = _box.get(userId, defaultValue: AppSettings(userId: userId));
return settings!; // Гарантируем возврат не-null значения
// Получаем настройки по ключу вместо индекса
final settings = _box.get(_settingsKey);
// Если настройки не существуют, создаем новые с значениями по умолчанию
if (settings == null) {
final defaultSettings = AppSettings();
await _box.put(_settingsKey, defaultSettings);
return defaultSettings;
}
return settings;
} catch (e) {
throw Exception('Ошибка получения настроек: $e');
}
@@ -22,17 +33,18 @@ class HiveSettingsRepository implements ISettingsRepository {
@override
Future<void> saveSettings(AppSettings settings) async {
try {
// Сохраняем настройки с ключом = userId
await _box.put(settings.userId, settings);
// Сохраняем настройки с фиксированным ключом
await _box.put(_settingsKey, settings);
} catch (e) {
throw Exception('Ошибка сохранения настроек: $e');
}
}
@override
Future<void> deleteSettings(String userId) async {
Future<void> deleteSettings() async {
try {
await _box.delete(userId);
// Удаляем настройки по фиксированному ключу
await _box.delete(_settingsKey);
} catch (e) {
throw Exception('Ошибка удаления настроек: $e');
}
@@ -10,26 +10,25 @@ class HiveSmsHandlerRepository implements ISmsHandlerRepository {
HiveSmsHandlerRepository(this._smsHandlerBox);
@override
Future<SmsHandlerSettings?> getSmsHandlerSettings(String userId) async {
Future<SmsHandlerSettings?> getSmsHandlerSettings() async {
// В Hive мы будем использовать ID пользователя как ключ для его настроек.
return _smsHandlerBox.get(userId);
return _smsHandlerBox.getAt(1);
}
@override
Future<void> saveSmsHandlerSettings(SmsHandlerSettings settings) async {
// Сохраняем объект настроек по ключу, равному ID пользователя.
await _smsHandlerBox.put(settings.userId, settings);
await _smsHandlerBox.put(settings.id, settings);
}
@override
Future<void> saveRuleForSender(String userId, String sender, SmsProcessingRule rule) async {
Future<void> saveRuleForSender(String sender, SmsProcessingRule rule) async {
// 1. Получаем текущие настройки.
SmsHandlerSettings? settings = await getSmsHandlerSettings(userId);
SmsHandlerSettings? settings = await getSmsHandlerSettings();
if (settings == null) {
// 2. Если настроек нет, создаем новый объект.
settings = SmsHandlerSettings(
userId: userId,
rulesBySender: {sender: rule}, // Создаем карту с первым правилом
);
} else {
@@ -42,9 +41,9 @@ class HiveSmsHandlerRepository implements ISmsHandlerRepository {
}
@override
Future<void> deleteRuleForSender(String userId, String sender) async {
Future<void> deleteRuleForSender(String sender) async {
// 1. Получаем текущие настройки.
final settings = await getSmsHandlerSettings(userId);
final settings = await getSmsHandlerSettings();
if (settings != null) {
// 2. Если настройки существуют, удаляем правило для отправителя.
@@ -60,40 +60,6 @@ class HiveTransactionRepository implements ITransactionRepository {
.toList();
}
// Добавляем новый метод для получения всех транзакций конкретного пользователя
@override
Future<List<TransactionRecord>> getAllByUser(String userId) async {
// Фильтруем все транзакции в Box по userId
return _box.values.where((t) => t.userId == userId).toList();
}
// Добавляем новый метод для получения транзакций пользователя по диапазону дат
@override
Future<List<TransactionRecord>> getByUserAndDateRange(String userId, DateTime from, DateTime to) async {
// Фильтруем транзакции сначала по userId, затем по диапазону дат
return _box.values
.where((t) => t.userId == userId && t.dateTime.isAfter(from) && t.dateTime.isBefore(to))
.toList();
}
// Добавляем новый метод для получения транзакций пользователя по категории
@override
Future<List<TransactionRecord>> getByUserAndCategory(String userId, String categoryId) async {
// Фильтруем транзакции сначала по userId, затем по категории
return _box.values
.where((t) => t.userId == userId && t.category.id == categoryId)
.toList();
}
// Добавляем новый метод для получения транзакций пользователя по тегу
@override
Future<List<TransactionRecord>> getByUserAndTag(String userId, String tagId) async {
// Фильтруем транзакции сначала по userId, затем по тегу
return _box.values
.where((t) => t.userId == userId && t.tag?.id == tagId)
.toList();
}
@override
Future<void> addAll(List<TransactionRecord> transactions) async {
final Map<String, TransactionRecord> transactionMap = {
@@ -10,16 +10,6 @@ abstract class ICategoryRepository {
Future<List<Category>> getIncomeCategories();
Future<List<Category>> getExpenseCategories();
// Новые методы для работы с пользователями
/// Получить все категории конкретного пользователя
Future<List<Category>> getAllByUser(String userId);
/// Получить категории доходов конкретного пользователя
Future<List<Category>> getIncomeCategoriesByUser(String userId);
/// Получить категории расходов конкретного пользователя
Future<List<Category>> getExpenseCategoriesByUser(String userId);
/// Добавить список категорий
Future<void> addAll(List<Category> categories);
}
@@ -4,7 +4,7 @@ import 'package:budget_app/models/app_settings.dart';
abstract class ISettingsRepository {
/// Получает настройки для указанного пользователя
/// [userId] - идентификатор пользователя
Future<AppSettings> getSettings(String userId);
Future<AppSettings> getSettings();
/// Сохраняет настройки
/// [settings] - объект настроек для сохранения
@@ -12,5 +12,5 @@ abstract class ISettingsRepository {
/// Удаляет настройки для указанного пользователя
/// [userId] - идентификатор пользователя
Future<void> deleteSettings(String userId);
Future<void> deleteSettings();
}
@@ -6,9 +6,7 @@ import '../../../models/sms_handler_settings.dart';
abstract class ISmsHandlerRepository {
/// Получает настройки обработки СМС для указанного пользователя.
///
/// [userId] - Уникальный идентификатор пользователя.
/// Возвращает [SmsHandlerSettings] или null, если настроек нет.
Future<SmsHandlerSettings?> getSmsHandlerSettings(String userId);
Future<SmsHandlerSettings?> getSmsHandlerSettings();
/// Сохраняет или обновляет настройки обработки СМС для пользователя.
///
@@ -20,11 +18,11 @@ abstract class ISmsHandlerRepository {
/// [userId] - ID пользователя.
/// [sender] - Идентификатор отправителя (например, 'SBERBANK').
/// [rule] - Правило обработки.
Future<void> saveRuleForSender(String userId, String sender, SmsProcessingRule rule);
Future<void> saveRuleForSender(String sender, SmsProcessingRule rule);
/// Удаляет правило для конкретного отправителя.
///
/// [userId] - ID пользователя.
/// [sender] - Идентификатор отправителя, чье правило нужно удалить.
Future<void> deleteRuleForSender(String userId, String sender);
Future<void> deleteRuleForSender(String sender);
}
@@ -7,12 +7,6 @@ abstract class ITagRepository {
Future<void> update(Tag tag);
Future<void> delete(String id);
// Комментарий: Добавляем новый абстрактный метод в интерфейс.
// Все классы, которые реализуют этот интерфейс, должны будут предоставить
// реализацию этого метода. Это гарантирует, что наш репозиторий
// сможет получать теги для конкретного пользователя.
Future<List<Tag>> getAllByUser(String userId);
/// Добавить список тегов
Future<void> addAll(List<Tag> tags);
}
@@ -11,13 +11,6 @@ abstract class ITransactionRepository {
Future<List<TransactionRecord>> getByCategory(String categoryId);
Future<List<TransactionRecord>> getByTag(String tagId);
// Добавляем новые методы для работы с транзакциями конкретного пользователя
// Это важно для многопользовательской архитектуры
Future<List<TransactionRecord>> getAllByUser(String userId);
Future<List<TransactionRecord>> getByUserAndDateRange(String userId, DateTime from, DateTime to);
Future<List<TransactionRecord>> getByUserAndCategory(String userId, String categoryId);
Future<List<TransactionRecord>> getByUserAndTag(String userId, String tagId);
/// Добавить список транзакций
Future<void> addAll(List<TransactionRecord> transactions);
}
-2
View File
@@ -4,8 +4,6 @@ import 'package:hive_ce/hive.dart';
@GenerateAdapters([
AdapterSpec<Color>(),
AdapterSpec<IconData>(),
AdapterSpec<DateTime>(),
])
part 'hive_adapters.g.dart';
-87
View File
@@ -6,38 +6,6 @@ part of 'hive_adapters.dart';
// AdaptersGenerator
// **************************************************************************
class ColorAdapter extends TypeAdapter<Color> {
@override
final typeId = 0;
@override
Color read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return Color((fields[0] as num).toInt());
}
@override
void write(BinaryWriter writer, Color obj) {
writer
..writeByte(1)
..writeByte(0)
..write(obj.value);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ColorAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
class IconDataAdapter extends TypeAdapter<IconData> {
@override
final typeId = 1;
@@ -83,58 +51,3 @@ class IconDataAdapter extends TypeAdapter<IconData> {
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
class DateTimeAdapter extends TypeAdapter<DateTime> {
@override
final typeId = 2;
@override
DateTime read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return DateTime(
(fields[0] as num).toInt(),
fields[1] == null ? 1 : (fields[1] as num).toInt(),
fields[2] == null ? 1 : (fields[2] as num).toInt(),
fields[3] == null ? 0 : (fields[3] as num).toInt(),
fields[4] == null ? 0 : (fields[4] as num).toInt(),
fields[5] == null ? 0 : (fields[5] as num).toInt(),
fields[6] == null ? 0 : (fields[6] as num).toInt(),
fields[7] == null ? 0 : (fields[7] as num).toInt(),
);
}
@override
void write(BinaryWriter writer, DateTime obj) {
writer
..writeByte(8)
..writeByte(0)
..write(obj.year)
..writeByte(1)
..write(obj.month)
..writeByte(2)
..write(obj.day)
..writeByte(3)
..write(obj.hour)
..writeByte(4)
..write(obj.minute)
..writeByte(5)
..write(obj.second)
..writeByte(6)
..write(obj.millisecond)
..writeByte(7)
..write(obj.microsecond);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is DateTimeAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
-26
View File
@@ -3,12 +3,6 @@
# Check in to version control
nextTypeId: 3
types:
Color:
typeId: 0
nextIndex: 1
fields:
value:
index: 0
IconData:
typeId: 1
nextIndex: 5
@@ -23,23 +17,3 @@ types:
index: 3
fontFamilyFallback:
index: 4
DateTime:
typeId: 2
nextIndex: 8
fields:
year:
index: 0
month:
index: 1
day:
index: 2
hour:
index: 3
minute:
index: 4
second:
index: 5
millisecond:
index: 6
microsecond:
index: 7
-4
View File
@@ -17,8 +17,6 @@ extension HiveRegistrar on HiveInterface {
void registerAdapters() {
registerAdapter(AppSettingsAdapter());
registerAdapter(CategoryAdapter());
registerAdapter(ColorAdapter());
registerAdapter(DateTimeAdapter());
registerAdapter(GlobalSettingsAdapter());
registerAdapter(IconDataAdapter());
registerAdapter(SmsHandlerSettingsAdapter());
@@ -35,8 +33,6 @@ extension IsolatedHiveRegistrar on IsolatedHiveInterface {
void registerAdapters() {
registerAdapter(AppSettingsAdapter());
registerAdapter(CategoryAdapter());
registerAdapter(ColorAdapter());
registerAdapter(DateTimeAdapter());
registerAdapter(GlobalSettingsAdapter());
registerAdapter(IconDataAdapter());
registerAdapter(SmsHandlerSettingsAdapter());
+1 -1
View File
@@ -155,7 +155,7 @@ Future<void> initUserSpecificDependencies(String userId) async {
await getIt.unregister<CategoryCubit>();
}
getIt.registerFactory<CategoryCubit>(
() => CategoryCubit(getIt(), userId), // Добавляем userId
() => CategoryCubit(getIt()), // Добавляем userId
);
// Теги
+6 -5
View File
@@ -1,8 +1,10 @@
import 'package:equatable/equatable.dart'; // equatable для сравнения объектов
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../data/repositories/interfaces/icategory_repository.dart';
import '../../models/category.dart';
import 'category_state.dart';
part 'category_state.dart'; // Используем part для разделения файла
/// Cubit для управления категориями
/// Изменения:
@@ -10,15 +12,14 @@ import 'category_state.dart';
/// - Упрощена работа с состояниями по аналогии с TagCubit
class CategoryCubit extends Cubit<CategoryState> {
final ICategoryRepository repository;
final String userId; // Храним userId в cubit
CategoryCubit(this.repository, this.userId) : super(CategoryInitial());
CategoryCubit(this.repository) : super(CategoryInitial());
/// Загружает категории для текущего пользователя
Future<void> loadCategories() async {
emit(CategoryLoading());
try {
final categories = await repository.getAllByUser(userId);
final categories = await repository.getAll();
emit(CategoryLoaded(categories));
} catch (e) {
emit(CategoryError('Ошибка загрузки категорий: $e'));
@@ -29,7 +30,7 @@ class CategoryCubit extends Cubit<CategoryState> {
Future<void> addCategory(Category category) async {
try {
// Устанавливаем userId для категории из cubit
final newCategory = category.copyWith(userId: userId);
final newCategory = category.copyWith();
await repository.add(newCategory);
// Перезагружаем список категорий
await loadCategories();
+30
View File
@@ -0,0 +1,30 @@
part of 'category_cubit.dart';
abstract class CategoryState extends Equatable {
const CategoryState();
@override
List<Object> get props => [];
}
class CategoryInitial extends CategoryState {}
class CategoryLoading extends CategoryState {}
class CategoryLoaded extends CategoryState {
final List<Category> categories;
const CategoryLoaded(this.categories);
@override
List<Object> get props => [categories];
}
class CategoryError extends CategoryState {
final String message;
const CategoryError(this.message);
@override
List<Object> get props => [message];
}
+9 -10
View File
@@ -12,10 +12,10 @@ class SettingsCubit extends Cubit<SettingsState> {
SettingsCubit(this._settingsRepository) : super(SettingsInitial());
Future<void> loadSettings(String userId) async {
Future<void> loadSettings() async {
emit(SettingsLoading());
try {
final settings = await _settingsRepository.getSettings(userId);
final settings = await _settingsRepository.getSettings();
emit(SettingsLoaded(
isDarkMode: settings.isDarkMode,
languageCode: settings.languageCode,
@@ -26,48 +26,47 @@ class SettingsCubit extends Cubit<SettingsState> {
}
}
Future<void> toggleDarkMode(bool value, String userId) async {
Future<void> toggleDarkMode(bool value) async {
if (state is SettingsLoaded) {
try {
final currentState = state as SettingsLoaded;
final newState = currentState.copyWith(isDarkMode: value);
emit(newState);
await _saveSettings(newState, userId);
await _saveSettings(newState);
} catch (e) {
emit(SettingsError(e.toString()));
}
}
}
Future<void> changeLanguage(String languageCode, String userId) async {
Future<void> changeLanguage(String languageCode) async {
if (state is SettingsLoaded) {
try {
final currentState = state as SettingsLoaded;
final newState = currentState.copyWith(languageCode: languageCode);
emit(newState);
await _saveSettings(newState, userId);
await _saveSettings(newState);
} catch (e) {
emit(SettingsError(e.toString()));
}
}
}
Future<void> changeCurrency(String currency, String userId) async {
Future<void> changeCurrency(String currency) async {
if (state is SettingsLoaded) {
try {
final currentState = state as SettingsLoaded;
final newState = currentState.copyWith(defaultCurrency: currency);
emit(newState);
await _saveSettings(newState, userId);
await _saveSettings(newState);
} catch (e) {
emit(SettingsError(e.toString()));
}
}
}
Future<void> _saveSettings(SettingsLoaded settings, String userId) async {
Future<void> _saveSettings(SettingsLoaded settings) async {
final appSettings = AppSettings(
userId: userId,
isDarkMode: settings.isDarkMode,
languageCode: settings.languageCode,
defaultCurrency: settings.defaultCurrency,
+3 -8
View File
@@ -31,13 +31,8 @@ class SmsCubit extends Cubit<SmsState> {
try {
final hasPermissions = await _smsService.requestPermissions();
if (hasPermissions) {
final userState = _userCubit.state;
if (userState is UserLoaded) {
final messages = await _smsService.getLastSmsMessages(10, userState.user!.id);
emit(SmsLoaded(messages));
} else {
emit(SmsError("User not loaded"));
}
final messages = await _smsService.getLastSmsMessages(10);
emit(SmsLoaded(messages));
} else {
emit(SmsPermissionDenied());
}
@@ -60,7 +55,7 @@ class SmsCubit extends Cubit<SmsState> {
final user = userState.user;
// Комментарий: Получаем все SMS-сообщения с момента последней синхронизации.
final messages = await _smsService.getSmsMessagesSince(user!.lastSmsSyncTime, user.id);
final messages = await _smsService.getSmsMessagesSince(user!.lastSmsSyncTime);
// Комментарий: Сохраняем новые сообщения через репозиторий и создаем транзакции.
await _smsRepository.addAll(messages);
+4 -5
View File
@@ -21,7 +21,7 @@ class TransactionBloc extends Bloc<TransactionEvent, TransactionState> {
void _onLoadTransactions(LoadTransactions event, Emitter<TransactionState> emit) async {
emit(TransactionLoading());
try {
final transactions = await _transactionRepository.getAllByUser(event.userId);
final transactions = await _transactionRepository.getAll();
emit(TransactionLoaded(transactions: transactions));
} catch (e) {
emit(TransactionError(message: e.toString()));
@@ -31,7 +31,7 @@ class TransactionBloc extends Bloc<TransactionEvent, TransactionState> {
void _onAddTransaction(AddTransaction event, Emitter<TransactionState> emit) async {
try {
await _transactionRepository.add(event.transaction);
final transactions = await _transactionRepository.getAllByUser(event.transaction.userId);
final transactions = await _transactionRepository.getAll();
emit(TransactionLoaded(transactions: transactions));
} catch (e) {
emit(TransactionError(message: e.toString()));
@@ -41,7 +41,7 @@ class TransactionBloc extends Bloc<TransactionEvent, TransactionState> {
void _onUpdateTransaction(UpdateTransaction event, Emitter<TransactionState> emit) async {
try {
await _transactionRepository.update(event.transaction);
final transactions = await _transactionRepository.getAllByUser(event.transaction.userId);
final transactions = await _transactionRepository.getAll();
emit(TransactionLoaded(transactions: transactions));
} catch (e) {
emit(TransactionError(message: e.toString()));
@@ -54,9 +54,8 @@ class TransactionBloc extends Bloc<TransactionEvent, TransactionState> {
if (state is TransactionLoaded) {
final loadedState = state as TransactionLoaded;
if (loadedState.transactions.isNotEmpty) {
final userId = loadedState.transactions.first.userId;
await _transactionRepository.delete(event.transactionId);
final transactions = await _transactionRepository.getAllByUser(userId);
final transactions = await _transactionRepository.getAll();
emit(TransactionLoaded(transactions: transactions));
}
}
+5 -20
View File
@@ -87,22 +87,7 @@ class UserCubit extends Cubit<UserState> {
emit(UserError('Ошибка загрузки пользователя: ${e.toString()}'));
}
}
Future<User> _createDefaultUser() async {
final user = User(
name: 'Пользователь по умолчанию',
email: 'default@example.com',
);
await _userRepository.add(user);
// Создаем начальные данные для нового пользователя
await _createInitialData(user.id);
await _setCurrentUser(user);
return user;
}
Future<void> _createInitialData(String userId) async {
Future<void> createInitialData(String userId) async {
// Проверяем, что репозитории были установлены, прежде чем их использовать.
if (_categoryRepository == null ||
_tagRepository == null ||
@@ -115,15 +100,15 @@ class UserCubit extends Cubit<UserState> {
emit(UserLoading(progress: 0.7, message: 'Создание категорий...'));
// Используем '!', так как мы уже проверили на null.
await _categoryRepository!.addAll(
CategoryUtils.getDefaultCategories(userId),
CategoryUtils.getDefaultCategories(),
);
emit(UserLoading(progress: 0.8, message: 'Создание тегов...'));
await _tagRepository!.addAll(TagUtils.getDefaultTags(userId));
await _tagRepository!.addAll(TagUtils.getDefaultTags());
emit(UserLoading(progress: 0.9, message: 'Создание транзакций...'));
await _transactionRepository!.addAll(
TransactionUtils.getSampleTransactions(userId),
TransactionUtils.getSampleTransactions(),
);
} catch (e, stack) {
@@ -176,7 +161,7 @@ class UserCubit extends Cubit<UserState> {
await _userRepository.add(user);
// Создаем начальные данные для нового пользователя
await _createInitialData(user.id);
await createInitialData(user.id);
await _setCurrentUser(user);
} catch (e, stack) {
+7 -15
View File
@@ -1,13 +1,13 @@
import 'package:equatable/equatable.dart';
import 'package:hive_ce/hive.dart';
import '../utils/id_generator.dart';
part 'app_settings.g.dart';
@HiveType(typeId: 1005)
class AppSettings extends Equatable {
/// Ссылка на ID пользователя, которому принадлежат настройки
@HiveField(0)
final String userId;
final String id;
/// Код языка интерфейса (например, 'ru', 'en')
@HiveField(1)
@@ -26,17 +26,17 @@ class AppSettings extends Equatable {
final DateTime updatedAt;
AppSettings({
required this.userId,
String? id,
this.languageCode = 'ru',
this.isDarkMode = false,
this.defaultCurrency = 'RUB',
DateTime? updatedAt,
}) : updatedAt = updatedAt ?? DateTime.now();
}) : id = id ?? IdGenerator.generateId(),
updatedAt = updatedAt ?? DateTime.now();
/// Преобразование в Map для сохранения
Map<String, dynamic> toMap() {
return {
'userId': userId,
'languageCode': languageCode,
'isDarkMode': isDarkMode,
'defaultCurrency': defaultCurrency,
@@ -47,7 +47,6 @@ class AppSettings extends Equatable {
/// Создание из Map
factory AppSettings.fromMap(Map<String, dynamic> map) {
return AppSettings(
userId: map['userId'],
languageCode: map['languageCode'] ?? 'ru',
isDarkMode: map['isDarkMode'] ?? false,
defaultCurrency: map['defaultCurrency'] ?? 'RUB',
@@ -63,7 +62,6 @@ class AppSettings extends Equatable {
String? defaultCurrency,
}) {
return AppSettings(
userId: userId ?? this.userId,
languageCode: languageCode ?? this.languageCode,
isDarkMode: isDarkMode ?? this.isDarkMode,
defaultCurrency: defaultCurrency ?? this.defaultCurrency,
@@ -72,17 +70,11 @@ class AppSettings extends Equatable {
}
@override
List<Object> get props => [
userId,
languageCode,
isDarkMode,
defaultCurrency,
updatedAt,
];
List<Object> get props => [id];
@override
String toString() {
return 'AppSettings(userId: $userId, languageCode: $languageCode, '
return 'AppSettings(languageCode: $languageCode, '
'isDarkMode: $isDarkMode, defaultCurrency: $defaultCurrency)';
}
}
+1 -4
View File
@@ -17,7 +17,6 @@ class AppSettingsAdapter extends TypeAdapter<AppSettings> {
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return AppSettings(
userId: fields[0] as String,
languageCode: fields[1] == null ? 'ru' : fields[1] as String,
isDarkMode: fields[2] == null ? false : fields[2] as bool,
defaultCurrency: fields[3] == null ? 'RUB' : fields[3] as String,
@@ -28,9 +27,7 @@ class AppSettingsAdapter extends TypeAdapter<AppSettings> {
@override
void write(BinaryWriter writer, AppSettings obj) {
writer
..writeByte(5)
..writeByte(0)
..write(obj.userId)
..writeByte(4)
..writeByte(1)
..write(obj.languageCode)
..writeByte(2)
-9
View File
@@ -32,11 +32,6 @@ class Category extends Equatable {
/// false - расход (например покупки)
final bool isIncome;
@HiveField(5)
/// Идентификатор пользователя, которому принадлежит эта категория
/// Это позволяет разделять категории между разными пользователями
final String userId;
@HiveField(6)
/// Дата и время последнего обновления объекта
final DateTime updatedAt;
@@ -48,7 +43,6 @@ class Category extends Equatable {
required this.color,
required this.icon,
required this.isIncome,
required this.userId, // Теперь userId обязательный параметр
DateTime? updatedAt, // Добавлено поле updatedAt, теперь необязательное
}) : id = id ?? IdGenerator.generateId(),
updatedAt = updatedAt ?? DateTime.now(); // Устанавливаем текущее время по умолчанию
@@ -61,7 +55,6 @@ class Category extends Equatable {
'color': color.toARGB32(), // Сохраняем только значение цвета
'icon': icon.codePoint,
'isIncome': isIncome,
'userId': userId, // Добавляем userId в Map
'updatedAt': updatedAt.toIso8601String(), // Добавлено updatedAt в Map
};
}
@@ -74,7 +67,6 @@ class Category extends Equatable {
color: Color(map['color']),
icon: IconData(map['icon'], fontFamily: 'MaterialIcons'),
isIncome: map['isIncome'],
userId: map['userId'], // Добавляем userId при создании из Map
updatedAt: DateTime.parse(map['updatedAt']), // Добавлено updatedAt при создании из Map
);
}
@@ -94,7 +86,6 @@ class Category extends Equatable {
color: color ?? this.color,
icon: icon ?? this.icon,
isIncome: isIncome ?? this.isIncome,
userId: userId ?? this.userId,
updatedAt: DateTime.now(), // Обновляем updatedAt при каждом копировании
);
}
+1 -4
View File
@@ -22,7 +22,6 @@ class CategoryAdapter extends TypeAdapter<Category> {
color: fields[2] as Color,
icon: fields[3] as IconData,
isIncome: fields[4] as bool,
userId: fields[5] as String,
updatedAt: fields[6] as DateTime?,
);
}
@@ -30,7 +29,7 @@ class CategoryAdapter extends TypeAdapter<Category> {
@override
void write(BinaryWriter writer, Category obj) {
writer
..writeByte(7)
..writeByte(6)
..writeByte(0)
..write(obj.id)
..writeByte(1)
@@ -41,8 +40,6 @@ class CategoryAdapter extends TypeAdapter<Category> {
..write(obj.icon)
..writeByte(4)
..write(obj.isIncome)
..writeByte(5)
..write(obj.userId)
..writeByte(6)
..write(obj.updatedAt);
}
+10 -6
View File
@@ -1,6 +1,7 @@
import 'package:hive_ce/hive.dart';
import '/utils/id_generator.dart';
part 'sms_handler_settings.g.dart';
/// Перечисление для определения типа обработки СМС.
@@ -31,7 +32,11 @@ class SmsProcessingRule extends HiveObject {
@HiveField(2)
final String? customFunctionId;
@HiveField(3)
final String id;
SmsProcessingRule({
String? id,
required this.type,
this.pattern,
this.customFunctionId,
@@ -39,23 +44,22 @@ class SmsProcessingRule extends HiveObject {
(type == SmsProcessingType.regexp && pattern != null) ||
(type == SmsProcessingType.customFunction && customFunctionId != null),
'Pattern must be provided for regexp type, and customFunctionId for customFunction type.',
);
), id = id ?? IdGenerator.generateId();
}
/// Модель для хранения всех настроек обработки СМС для одного пользователя.
@HiveType(typeId: 12)
class SmsHandlerSettings extends HiveObject {
/// Уникальный идентификатор пользователя, к которому относятся эти настройки.
@HiveField(0)
final String userId;
@HiveField(0)
final String id;
/// Карта правил обработки, где ключ - это идентификатор отправителя (например, 'SBERBANK' или номер телефона),
/// а значение - правило обработки для этого отправителя.
@HiveField(1)
final Map<String, SmsProcessingRule> rulesBySender;
SmsHandlerSettings({
required this.userId,
String? id,
required this.rulesBySender,
});
}) : id = id ?? IdGenerator.generateId();
}
+1 -4
View File
@@ -57,7 +57,6 @@ class SmsHandlerSettingsAdapter extends TypeAdapter<SmsHandlerSettings> {
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return SmsHandlerSettings(
userId: fields[0] as String,
rulesBySender: (fields[1] as Map).cast<String, SmsProcessingRule>(),
);
}
@@ -65,9 +64,7 @@ class SmsHandlerSettingsAdapter extends TypeAdapter<SmsHandlerSettings> {
@override
void write(BinaryWriter writer, SmsHandlerSettings obj) {
writer
..writeByte(2)
..writeByte(0)
..write(obj.userId)
..writeByte(1)
..writeByte(1)
..write(obj.rulesBySender);
}
-6
View File
@@ -21,17 +21,12 @@ class SmsMessage extends HiveObject {
@HiveField(4)
String? transactionId;
// Комментарий: Добавлено поле для связи с пользователем.
@HiveField(5)
final String userId;
SmsMessage({
String? id,
this.body,
this.sender,
this.date,
this.transactionId,
required this.userId,
}) : id = id ?? IdGenerator.generateId();
// Комментарий: Добавляем метод для обновления transactionId
@@ -42,7 +37,6 @@ class SmsMessage extends HiveObject {
sender: sender,
date: date,
transactionId: transactionId ?? this.transactionId,
userId: userId ?? this.userId,
);
}
}
+2 -5
View File
@@ -22,14 +22,13 @@ class SmsMessageAdapter extends TypeAdapter<SmsMessage> {
sender: fields[2] as String?,
date: fields[3] as DateTime?,
transactionId: fields[4] as String?,
userId: fields[5] as String,
);
}
@override
void write(BinaryWriter writer, SmsMessage obj) {
writer
..writeByte(6)
..writeByte(5)
..writeByte(0)
..write(obj.id)
..writeByte(1)
@@ -39,9 +38,7 @@ class SmsMessageAdapter extends TypeAdapter<SmsMessage> {
..writeByte(3)
..write(obj.date)
..writeByte(4)
..write(obj.transactionId)
..writeByte(5)
..write(obj.userId);
..write(obj.transactionId);
}
@override
-10
View File
@@ -14,12 +14,6 @@ class Tag extends Equatable {
/// Название тега (например: "Важное", "Работа")
final String name;
// Комментарий: Добавляем новое поле для хранения идентификатора пользователя.
// Это позволит нам связать каждый тег с конкретным пользователем.
// Мы используем аннотацию @HiveField(2), чтобы указать Hive, как сохранять это поле.
@HiveField(2)
final String userId;
@HiveField(3)
/// Дата и время последнего обновления объекта
final DateTime updatedAt;
@@ -30,7 +24,6 @@ class Tag extends Equatable {
required this.name,
// Комментарий: Добавляем userId в конструктор как обязательный параметр.
// Теперь при создании тега необходимо будет указать, какому пользователю он принадлежит.
required this.userId,
DateTime? updatedAt, // Добавлено поле updatedAt, теперь необязательное
}) : id = id ?? IdGenerator.generateId(),
updatedAt = updatedAt ?? DateTime.now(); // Устанавливаем текущее время по умолчанию
@@ -42,7 +35,6 @@ class Tag extends Equatable {
'name': name,
// Комментарий: Добавляем userId в Map. Это нужно для сохранения
// данных в форматах, которые не работают напрямую с объектами Dart (например, при отправке на сервер).
'userId': userId,
'updatedAt': updatedAt.toIso8601String(), // Добавлено updatedAt в Map
};
}
@@ -54,7 +46,6 @@ class Tag extends Equatable {
name: map['name'],
// Комментарий: Извлекаем userId из Map при создании объекта.
// Это позволит восстановить полный объект Tag из данных, например, из базы данных.
userId: map['userId'],
updatedAt: DateTime.parse(map['updatedAt']), // Добавлено updatedAt при создании из Map
);
}
@@ -68,7 +59,6 @@ class Tag extends Equatable {
return Tag(
id: id ?? this.id,
name: name ?? this.name,
userId: userId ?? this.userId,
updatedAt: DateTime.now(), // Обновляем updatedAt при каждом копировании
);
}
+1 -4
View File
@@ -19,7 +19,6 @@ class TagAdapter extends TypeAdapter<Tag> {
return Tag(
id: fields[0] as String?,
name: fields[1] as String,
userId: fields[2] as String,
updatedAt: fields[3] as DateTime?,
);
}
@@ -27,13 +26,11 @@ class TagAdapter extends TypeAdapter<Tag> {
@override
void write(BinaryWriter writer, Tag obj) {
writer
..writeByte(4)
..writeByte(3)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.name)
..writeByte(2)
..write(obj.userId)
..writeByte(3)
..write(obj.updatedAt);
}
-8
View File
@@ -39,10 +39,6 @@ class TransactionRecord extends Equatable {
/// Валюта операции (код валюты, например "RUB", "USD")
final String currency;
// Добавляем новое поле для хранения идентификатора пользователя
@HiveField(7) // Используем следующий доступный номер поля Hive
final String userId;
@HiveField(8)
/// Дата и время последнего обновления объекта
final DateTime updatedAt;
@@ -56,7 +52,6 @@ class TransactionRecord extends Equatable {
required this.dateTime,
required this.vendor,
required this.currency,
required this.userId, // Добавляем userId в конструктор
DateTime? updatedAt, // Добавлено поле updatedAt, теперь необязательное
}) : id = id ?? IdGenerator.generateId(),
updatedAt =
@@ -73,7 +68,6 @@ class TransactionRecord extends Equatable {
'dateTime': dateTime.toIso8601String(),
'vendor': vendor,
'currency': currency,
'userId': userId, // Добавляем userId в Map
'updatedAt': updatedAt.toIso8601String(), // Добавлено updatedAt в Map
};
}
@@ -88,7 +82,6 @@ class TransactionRecord extends Equatable {
dateTime: DateTime.parse(map['dateTime']),
vendor: map['vendor'],
currency: map['currency'],
userId: map['userId'], // Извлекаем userId из Map
updatedAt: DateTime.parse(
map['updatedAt'],
), // Добавлено updatedAt при создании из Map
@@ -118,7 +111,6 @@ class TransactionRecord extends Equatable {
dateTime: dateTime ?? this.dateTime,
vendor: vendor ?? this.vendor,
currency: currency ?? this.currency,
userId: userId ?? this.userId,
updatedAt: DateTime.now(), // Обновляем updatedAt при каждом копировании
);
}
+1 -4
View File
@@ -24,7 +24,6 @@ class TransactionRecordAdapter extends TypeAdapter<TransactionRecord> {
dateTime: fields[4] as DateTime,
vendor: fields[5] as String,
currency: fields[6] as String,
userId: fields[7] as String,
updatedAt: fields[8] as DateTime?,
);
}
@@ -32,7 +31,7 @@ class TransactionRecordAdapter extends TypeAdapter<TransactionRecord> {
@override
void write(BinaryWriter writer, TransactionRecord obj) {
writer
..writeByte(9)
..writeByte(8)
..writeByte(0)
..write(obj.id)
..writeByte(1)
@@ -47,8 +46,6 @@ class TransactionRecordAdapter extends TypeAdapter<TransactionRecord> {
..write(obj.vendor)
..writeByte(6)
..write(obj.currency)
..writeByte(7)
..write(obj.userId)
..writeByte(8)
..write(obj.updatedAt);
}
@@ -1,6 +1,5 @@
import 'package:budget_app/l10n/app_localizations.dart';
import 'package:budget_app/logic/category/category_cubit.dart';
import 'package:budget_app/logic/category/category_state.dart';
import 'package:budget_app/models/category.dart';
import 'package:budget_app/pages/category/category_edit_page.dart';
import 'package:budget_app/pages/category/widgets/add_category_button.dart';
@@ -63,16 +62,11 @@ class _CategoryListPageState extends State<CategoryListPage> {
MaterialPageRoute(
builder: (_) => CategoryEditPage(
onSave: (name, color, icon, isIncome) {
final userState = context.read<UserCubit>().state;
if (userState is! UserLoaded || userState.user == null) return;
final currentUserId = userState.user!.id;
final newCategory = Category(
name: name,
color: color,
icon: icon,
isIncome: isIncome,
userId: currentUserId,
);
context.read<CategoryCubit>().addCategory(newCategory);
@@ -89,10 +83,6 @@ class _CategoryListPageState extends State<CategoryListPage> {
builder: (_) => CategoryEditPage(
category: category,
onSave: (name, color, icon, isIncome) {
final userState = context.read<UserCubit>().state;
if (userState is! UserLoaded || userState.user == null) return;
final currentUserId = userState.user!.id;
final updatedCategory = category.copyWith(
name: name,
color: color,
@@ -108,10 +98,6 @@ class _CategoryListPageState extends State<CategoryListPage> {
}
void _deleteCategory(BuildContext context, Category category) {
final userState = context.read<UserCubit>().state;
if (userState is! UserLoaded || userState.user == null) return;
final currentUserId = userState.user!.id;
context.read<CategoryCubit>().deleteCategory(category.id);
}
}
@@ -122,17 +122,14 @@ class _AddTransactionDialogState extends State<AddTransactionDialog> {
: 'RUB';
// Получаем ID текущего пользователя из UserCubit
final userState = context.read<UserCubit>().state;
if (userState is! UserLoaded || userState.user == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context)!.transactionErrorText('User not found'),
),
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context)!.transactionErrorText('User not found'),
),
);
return;
}
),
);
final newTransaction = TransactionRecord(
amount: amount,
@@ -141,7 +138,6 @@ class _AddTransactionDialogState extends State<AddTransactionDialog> {
dateTime: _selectedDateTime,
tag: _selectedTag,
currency: currency,
userId: userState.user!.id,
);
context.read<TransactionBloc>().add(
@@ -154,14 +150,9 @@ class _AddTransactionDialogState extends State<AddTransactionDialog> {
@override
Widget build(BuildContext context) {
final localizations = AppLocalizations.of(context)!;
// Получаем ID текущего пользователя из UserCubit
final userState = context.read<UserCubit>().state;
final userId = userState is UserLoaded && userState.user != null
? userState.user!.id
: '';
final categories = CategoryUtils.getDefaultCategories(userId)
final categories = CategoryUtils.getDefaultCategories()
.where((c) => c.isIncome == _isIncome).toList();
return AlertDialog(
@@ -233,7 +224,7 @@ class _AddTransactionDialogState extends State<AddTransactionDialog> {
// Комментарий: Добавляем выпадающий список для выбора тега.
// Он будет загружать теги асинхронно для текущего пользователя.
FutureBuilder<List<Tag>>(
future: GetIt.instance<ITagRepository>().getAllByUser(userId),
future: GetIt.instance<ITagRepository>().getAll(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
+116 -130
View File
@@ -37,145 +37,131 @@ class SettingsPage extends StatelessWidget {
builder: (context, userState) {
if (userState is UserLoaded && userState.user != null) {
// Комментарий: Как только пользователь загружен, мы загружаем его настройки.
context.read<SettingsCubit>().loadSettings(userState.user!.id);
context.read<SettingsCubit>().loadSettings();
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);
}
}
// Комментарий: Обернули Column в SingleChildScrollView, чтобы избежать переполнения по вертикали.
// Это позволяет прокручивать содержимое, если оно не помещается на экране.
child: SingleChildScrollView(
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
context
.read<SettingsCubit>()
.toggleDarkMode(value);
},
// Комментарий: Формируем список доступных языков.
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);
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) {
context
.read<SettingsCubit>()
.changeLanguage(newValue);
}
}
},
// Комментарий: Формируем список доступных валют. Можно расширить этот список.
items: <String>['RUB', 'USD', 'EUR']
.map<DropdownMenuItem<String>>(
(String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
},
// Комментарий: Формируем список доступных языков.
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 для перехода на страницу редактирования категорий.
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.currencySetting),
subtitle: Text(localizations.currencyDescription),
trailing: DropdownButton<String>(
value: state.defaultCurrency,
// Комментарий: При выборе новой валюты вызываем метод `changeCurrency` у Cubit.
// Также передаем userId из UserService.
onChanged: (String? newValue) {
if (newValue != null) {
context
.read<SettingsCubit>()
.changeCurrency(newValue);
}
},
// Комментарий: Формируем список доступных валют. Можно расширить этот список.
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(),
],
),
),
);
} else {
-6
View File
@@ -69,14 +69,8 @@ class _TagListPageState extends State<TagListPage> {
MaterialPageRoute(
builder: (_) => TagEditPage(
onSave: (name) {
final userState = context.read<UserCubit>().state;
if (userState is! UserLoaded || userState.user == null) {
return;
}
final currentUserId = userState.user!.id;
final newTag = Tag(
name: name,
userId: currentUserId,
);
context.read<TagCubit>().addTag(newTag);
_listKey.currentState?.insertItem(0);
+2 -4
View File
@@ -23,7 +23,7 @@ class SmsService {
///
/// Возвращает список объектов [SmsMessage].
/// В случае ошибки или отсутствия разрешений, возвращает пустой список.
Future<List<SmsMessage>> getLastSmsMessages(int count, String userId) async {
Future<List<SmsMessage>> getLastSmsMessages(int count) async {
final bool? permissionsGranted =
await _telephony.requestPhoneAndSmsPermissions;
if (permissionsGranted ?? false) {
@@ -36,14 +36,13 @@ class SmsService {
body: msg.body,
sender: msg.address,
date: DateTime.fromMillisecondsSinceEpoch(msg.date ?? 0),
userId: userId,
)).toList();
}
return [];
}
// Комментарий: Метод для получения SMS-сообщений с определенной даты.
Future<List<SmsMessage>> getSmsMessagesSince(DateTime sinceDate, String userId) async {
Future<List<SmsMessage>> getSmsMessagesSince(DateTime sinceDate) async {
final bool? permissionsGranted = await _telephony.requestPhoneAndSmsPermissions;
if (permissionsGranted ?? false) {
final List<telephony_package.SmsMessage> messages = await _telephony.getInboxSms(
@@ -57,7 +56,6 @@ class SmsService {
body: msg.body,
sender: msg.address,
date: DateTime.fromMillisecondsSinceEpoch(msg.date ?? 0),
userId: userId,
))
.toList();
}
+3 -11
View File
@@ -7,7 +7,7 @@ class CategoryUtils {
/// Возвращает список предопределенных категорий для конкретного пользователя
/// Включает как категории доходов, так и расходов
/// [userId] - идентификатор пользователя, для которого создаются категории
static List<Category> getDefaultCategories(String userId) {
static List<Category> getDefaultCategories() {
return [
// Категории доходов
Category(
@@ -16,7 +16,6 @@ class CategoryUtils {
color: Colors.green,
icon: Icons.attach_money,
isIncome: true,
userId: userId, // Привязываем к конкретному пользователю
),
Category(
id: 'income_gift',
@@ -24,7 +23,6 @@ class CategoryUtils {
color: Colors.blue,
icon: Icons.card_giftcard,
isIncome: true,
userId: userId, // Привязываем к конкретному пользователю
),
Category(
id: 'income_freelance',
@@ -32,7 +30,6 @@ class CategoryUtils {
color: Colors.teal,
icon: Icons.computer,
isIncome: true,
userId: userId, // Привязываем к конкретному пользователю
),
// Категории расходов
@@ -42,7 +39,6 @@ class CategoryUtils {
color: Colors.red,
icon: Icons.fastfood,
isIncome: false,
userId: userId, // Привязываем к конкретному пользователю
),
Category(
id: 'expense_transport',
@@ -50,7 +46,6 @@ class CategoryUtils {
color: Colors.orange,
icon: Icons.directions_car,
isIncome: false,
userId: userId, // Привязываем к конкретному пользователю
),
Category(
id: 'expense_entertainment',
@@ -58,7 +53,6 @@ class CategoryUtils {
color: Colors.purple,
icon: Icons.movie,
isIncome: false,
userId: userId, // Привязываем к конкретному пользователю
),
Category(
id: 'expense_utilities',
@@ -66,7 +60,6 @@ class CategoryUtils {
color: Colors.blueGrey,
icon: Icons.home,
isIncome: false,
userId: userId, // Привязываем к конкретному пользователю
),
Category(
id: 'expense_shopping',
@@ -74,7 +67,6 @@ class CategoryUtils {
color: Colors.pink,
icon: Icons.shopping_bag,
isIncome: false,
userId: userId, // Привязываем к конкретному пользователю
),
];
}
@@ -82,12 +74,12 @@ class CategoryUtils {
/// Возвращает только категории доходов для конкретного пользователя
/// [userId] - идентификатор пользователя
static List<Category> getIncomeCategories(String userId) {
return getDefaultCategories(userId).where((c) => c.isIncome).toList();
return getDefaultCategories().where((c) => c.isIncome).toList();
}
/// Возвращает только категории расходов для конкретного пользователя
/// [userId] - идентификатор пользователя
static List<Category> getExpenseCategories(String userId) {
return getDefaultCategories(userId).where((c) => !c.isIncome).toList();
return getDefaultCategories().where((c) => !c.isIncome).toList();
}
}
+1 -8
View File
@@ -5,35 +5,28 @@ class TagUtils {
// Комментарий: Мы изменили сигнатуру метода, добавив параметр `userId`.
// Это необходимо, потому что конструктор `Tag` теперь требует `userId`.
/// Возвращает список предопределенных тегов для конкретного пользователя
static List<Tag> getDefaultTags(String userId) {
static List<Tag> getDefaultTags() {
return [
Tag(
id: 'tag_important',
name: 'Важное',
// Комментарий: Передаем `userId` при создании каждого тега по умолчанию.
// Таким образом, эти теги будут принадлежать конкретному пользователю.
userId: userId,
),
Tag(
id: 'tag_work',
name: 'Работа',
userId: userId,
),
Tag(
id: 'tag_family',
name: 'Семья',
userId: userId,
),
Tag(
id: 'tag_friends',
name: 'Друзья',
userId: userId,
),
Tag(
id: 'tag_holiday',
name: 'Отпуск',
userId: userId,
),
];
}
+3 -9
View File
@@ -5,11 +5,10 @@ import 'tag_utils.dart';
/// Утилиты для работы с тестовыми транзакциями
class TransactionUtils {
/// Возвращает список тестовых транзакций для конкретного пользователя
/// [userId] - идентификатор пользователя, для которого создаются транзакции
static List<TransactionRecord> getSampleTransactions(String userId) {
static List<TransactionRecord> getSampleTransactions() {
// Получаем категории и теги для этого пользователя
final categories = CategoryUtils.getDefaultCategories(userId);
final tags = TagUtils.getDefaultTags(userId);
final categories = CategoryUtils.getDefaultCategories();
final tags = TagUtils.getDefaultTags();
return [
// Доходы
@@ -20,7 +19,6 @@ class TransactionUtils {
dateTime: DateTime.now().subtract(const Duration(days: 5)),
vendor: 'ООО "Рога и копыта"',
currency: 'RUB',
userId: userId, // Указываем userId для тестовой транзакции
),
TransactionRecord(
category: categories.firstWhere((c) => c.id == 'income_freelance'),
@@ -29,7 +27,6 @@ class TransactionUtils {
dateTime: DateTime.now().subtract(const Duration(days: 2)),
vendor: 'Фриланс проект',
currency: 'RUB',
userId: userId, // Указываем userId для тестовой транзакции
),
// Расходы
@@ -40,7 +37,6 @@ class TransactionUtils {
dateTime: DateTime.now().subtract(const Duration(days: 1)),
vendor: 'Пятерочка',
currency: 'RUB',
userId: userId, // Указываем userId для тестовой транзакции
),
TransactionRecord(
category: categories.firstWhere((c) => c.id == 'expense_transport'),
@@ -49,7 +45,6 @@ class TransactionUtils {
dateTime: DateTime.now().subtract(const Duration(hours: 12)),
vendor: 'Яндекс Такси',
currency: 'RUB',
userId: userId, // Указываем userId для тестовой транзакции
),
TransactionRecord(
category: categories.firstWhere((c) => c.id == 'expense_entertainment'),
@@ -58,7 +53,6 @@ class TransactionUtils {
dateTime: DateTime.now().subtract(const Duration(hours: 6)),
vendor: 'Кинотеатр',
currency: 'RUB',
userId: userId, // Указываем userId для тестовой транзакции
),
];
}