Fix ai
This commit is contained in:
@@ -24,7 +24,7 @@ android {
|
||||
applicationId = "ru.sanderrs.budget_app"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = 23
|
||||
minSdk = 230
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:hive_ce/hive.dart';
|
||||
|
||||
|
||||
@GenerateAdapters([
|
||||
AdapterSpec<IconData>(),
|
||||
])
|
||||
part 'hive_adapters.g.dart';
|
||||
@@ -1,53 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'hive_adapters.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// AdaptersGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class IconDataAdapter extends TypeAdapter<IconData> {
|
||||
@override
|
||||
final typeId = 1;
|
||||
|
||||
@override
|
||||
IconData read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return IconData(
|
||||
(fields[0] as num).toInt(),
|
||||
fontFamily: fields[1] as String?,
|
||||
fontPackage: fields[2] as String?,
|
||||
matchTextDirection: fields[3] == null ? false : fields[3] as bool,
|
||||
fontFamilyFallback: (fields[4] as List?)?.cast<String>(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, IconData obj) {
|
||||
writer
|
||||
..writeByte(5)
|
||||
..writeByte(0)
|
||||
..write(obj.codePoint)
|
||||
..writeByte(1)
|
||||
..write(obj.fontFamily)
|
||||
..writeByte(2)
|
||||
..write(obj.fontPackage)
|
||||
..writeByte(3)
|
||||
..write(obj.matchTextDirection)
|
||||
..writeByte(4)
|
||||
..write(obj.fontFamilyFallback);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is IconDataAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
@@ -2,18 +2,4 @@
|
||||
# Manual modifications may be necessary for certain migrations
|
||||
# Check in to version control
|
||||
nextTypeId: 3
|
||||
types:
|
||||
IconData:
|
||||
typeId: 1
|
||||
nextIndex: 5
|
||||
fields:
|
||||
codePoint:
|
||||
index: 0
|
||||
fontFamily:
|
||||
index: 1
|
||||
fontPackage:
|
||||
index: 2
|
||||
matchTextDirection:
|
||||
index: 3
|
||||
fontFamilyFallback:
|
||||
index: 4
|
||||
types: {}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// Check in to version control
|
||||
|
||||
import 'package:hive_ce/hive.dart';
|
||||
import 'package:budget_app/hive/hive_adapters.dart';
|
||||
import 'package:budget_app/models/ai_response.dart';
|
||||
import 'package:budget_app/models/ai_rule.dart';
|
||||
import 'package:budget_app/models/ai_settings.dart';
|
||||
@@ -27,7 +26,6 @@ extension HiveRegistrar on HiveInterface {
|
||||
registerAdapter(AppSettingsAdapter());
|
||||
registerAdapter(CategoryAdapter());
|
||||
registerAdapter(GlobalSettingsAdapter());
|
||||
registerAdapter(IconDataAdapter());
|
||||
registerAdapter(PrefilledTransactionAdapter());
|
||||
registerAdapter(ProcessingStatusAdapter());
|
||||
registerAdapter(SmsHandlerSettingsAdapter());
|
||||
@@ -51,7 +49,6 @@ extension IsolatedHiveRegistrar on IsolatedHiveInterface {
|
||||
registerAdapter(AppSettingsAdapter());
|
||||
registerAdapter(CategoryAdapter());
|
||||
registerAdapter(GlobalSettingsAdapter());
|
||||
registerAdapter(IconDataAdapter());
|
||||
registerAdapter(PrefilledTransactionAdapter());
|
||||
registerAdapter(ProcessingStatusAdapter());
|
||||
registerAdapter(SmsHandlerSettingsAdapter());
|
||||
|
||||
@@ -239,6 +239,7 @@ Future<void> initUserSpecificDependencies(String userId) async {
|
||||
getIt.registerSingleton<SmsTransactionService>(SmsTransactionService(
|
||||
getIt<ISmsHandlerRepository>(),
|
||||
getIt<ITransactionRepository>(),
|
||||
getIt<IAiRuleRepository>(),
|
||||
getIt<TransactionBloc>(),
|
||||
));
|
||||
|
||||
|
||||
@@ -61,17 +61,41 @@ class SmsItemCubit extends Cubit<SmsItemState> {
|
||||
break;
|
||||
|
||||
case SmsProcessingResult.noRule:
|
||||
// Правило не найдено - показываем ошибку и не меняем статус
|
||||
emit(SmsItemError(loc.noRuleForSender(_currentMessage.sender ?? loc.unknownSender)));
|
||||
// Правило не найдено - сохраняем ошибку в сообщение
|
||||
final errorMessage = loc.noRuleForSender(_currentMessage.sender ?? loc.unknownSender);
|
||||
await _smsService.markAsError(_currentMessage.id, errorMessage);
|
||||
final updatedMessage = _currentMessage.copyWith(
|
||||
status: SmsStatus.error,
|
||||
errorMessage: errorMessage,
|
||||
);
|
||||
_currentMessage = updatedMessage; // Обновляем локальную копию
|
||||
emit(SmsItemError(errorMessage));
|
||||
onProcessed(updatedMessage);
|
||||
break;
|
||||
|
||||
case SmsProcessingResult.error:
|
||||
// Ошибка обработки - показываем ошибку и не меняем статус
|
||||
// Ошибка обработки - сохраняем ошибку в сообщение
|
||||
await _smsService.markAsError(_currentMessage.id, loc.processingError);
|
||||
final updatedMessage = _currentMessage.copyWith(
|
||||
status: SmsStatus.error,
|
||||
errorMessage: loc.processingError,
|
||||
);
|
||||
_currentMessage = updatedMessage; // Обновляем локальную копию
|
||||
emit(SmsItemError(loc.processingError));
|
||||
onProcessed(updatedMessage);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
emit(SmsItemError(e.toString()));
|
||||
// Сохраняем ошибку исключения в сообщение
|
||||
final errorMessage = e.toString();
|
||||
await _smsService.markAsError(_currentMessage.id, errorMessage);
|
||||
final updatedMessage = _currentMessage.copyWith(
|
||||
status: SmsStatus.error,
|
||||
errorMessage: errorMessage,
|
||||
);
|
||||
_currentMessage = updatedMessage; // Обновляем локальную копию
|
||||
emit(SmsItemError(errorMessage));
|
||||
onProcessed(updatedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +110,16 @@ class SmsItemCubit extends Cubit<SmsItemState> {
|
||||
emit(SmsItemIgnored());
|
||||
onProcessed(updatedMessage); // Передаем обновленное сообщение
|
||||
} catch (e) {
|
||||
emit(SmsItemError(e.toString()));
|
||||
// Сохраняем ошибку исключения в сообщение
|
||||
final errorMessage = e.toString();
|
||||
await _smsService.markAsError(_currentMessage.id, errorMessage);
|
||||
final updatedMessage = _currentMessage.copyWith(
|
||||
status: SmsStatus.error,
|
||||
errorMessage: errorMessage,
|
||||
);
|
||||
_currentMessage = updatedMessage; // Обновляем локальную копию
|
||||
emit(SmsItemError(errorMessage));
|
||||
onProcessed(updatedMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ class Category extends Equatable {
|
||||
final Color color;
|
||||
|
||||
@HiveField(3)
|
||||
/// Иконка категории для быстрой визуальной идентификации
|
||||
final IconData icon;
|
||||
/// Код иконки категории для быстрой визуальной идентификации
|
||||
final int iconCode;
|
||||
|
||||
@HiveField(4)
|
||||
/// Флаг указывающий тип операции:
|
||||
@@ -41,7 +41,7 @@ class Category extends Equatable {
|
||||
String? id,
|
||||
required this.name,
|
||||
required this.color,
|
||||
required this.icon,
|
||||
required this.iconCode,
|
||||
required this.isIncome,
|
||||
DateTime? updatedAt, // Добавлено поле updatedAt, теперь необязательное
|
||||
}) : id = id ?? IdGenerator.generateId(),
|
||||
@@ -53,7 +53,7 @@ class Category extends Equatable {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'color': color.toARGB32(), // Сохраняем только значение цвета
|
||||
'icon': icon.codePoint,
|
||||
'icon': iconCode, // Сохраняем код иконки вместо IconData
|
||||
'isIncome': isIncome,
|
||||
'updatedAt': updatedAt.toIso8601String(), // Добавлено updatedAt в Map
|
||||
};
|
||||
@@ -65,7 +65,7 @@ class Category extends Equatable {
|
||||
id: map['id'],
|
||||
name: map['name'],
|
||||
color: Color(map['color']),
|
||||
icon: IconData(map['icon'], fontFamily: 'MaterialIcons'),
|
||||
iconCode: map['icon'], // Используем код иконки вместо IconData
|
||||
isIncome: map['isIncome'],
|
||||
updatedAt: DateTime.parse(map['updatedAt']), // Добавлено updatedAt при создании из Map
|
||||
);
|
||||
@@ -76,7 +76,7 @@ class Category extends Equatable {
|
||||
String? id,
|
||||
String? name,
|
||||
Color? color,
|
||||
IconData? icon,
|
||||
int? iconCode,
|
||||
bool? isIncome,
|
||||
String? userId,
|
||||
}) {
|
||||
@@ -84,12 +84,16 @@ class Category extends Equatable {
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
color: color ?? this.color,
|
||||
icon: icon ?? this.icon,
|
||||
iconCode: iconCode ?? this.iconCode,
|
||||
isIncome: isIncome ?? this.isIncome,
|
||||
updatedAt: DateTime.now(), // Обновляем updatedAt при каждом копировании
|
||||
);
|
||||
}
|
||||
|
||||
/// Геттер для получения IconData из кода иконки
|
||||
/// Это позволяет использовать иконки в UI без хранения IconData в Hive
|
||||
IconData get icon => IconData(iconCode, fontFamily: 'MaterialIcons');
|
||||
|
||||
// Используем Equatable для сравнения объектов по их свойствам.
|
||||
// В данном случае, мы считаем категории уникальными по их 'id'.
|
||||
@override
|
||||
|
||||
@@ -20,7 +20,7 @@ class CategoryAdapter extends TypeAdapter<Category> {
|
||||
id: fields[0] as String?,
|
||||
name: fields[1] as String,
|
||||
color: fields[2] as Color,
|
||||
icon: fields[3] as IconData,
|
||||
iconCode: (fields[3] as num).toInt(),
|
||||
isIncome: fields[4] as bool,
|
||||
updatedAt: fields[6] as DateTime?,
|
||||
);
|
||||
@@ -37,7 +37,7 @@ class CategoryAdapter extends TypeAdapter<Category> {
|
||||
..writeByte(2)
|
||||
..write(obj.color)
|
||||
..writeByte(3)
|
||||
..write(obj.icon)
|
||||
..write(obj.iconCode)
|
||||
..writeByte(4)
|
||||
..write(obj.isIncome)
|
||||
..writeByte(6)
|
||||
|
||||
@@ -206,7 +206,10 @@ class _AiRuleEditDialogState extends State<AiRuleEditDialog> {
|
||||
bottom: Radius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.end,
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: [
|
||||
if (widget.rule != null) ...[
|
||||
TextButton.icon(
|
||||
@@ -214,14 +217,11 @@ class _AiRuleEditDialogState extends State<AiRuleEditDialog> {
|
||||
icon: const Icon(Icons.play_arrow),
|
||||
label: Text(AppLocalizations.of(context)!.testRule),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
],
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
ElevatedButton(
|
||||
onPressed: _saveRule,
|
||||
child: Text(widget.rule == null ? AppLocalizations.of(context)!.createRule : AppLocalizations.of(context)!.save),
|
||||
@@ -623,4 +623,4 @@ class _AiRuleEditDialogState extends State<AiRuleEditDialog> {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,12 +59,12 @@ class _CategoryListPageState extends State<CategoryListPage> {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => CategoryEditPage(
|
||||
builder: (_) => CategoryEditPage(
|
||||
onSave: (name, color, icon, isIncome) {
|
||||
final newCategory = Category(
|
||||
name: name,
|
||||
color: color,
|
||||
icon: icon,
|
||||
iconCode: icon.codePoint, // Используем код иконки вместо IconData
|
||||
isIncome: isIncome,
|
||||
);
|
||||
|
||||
@@ -85,7 +85,7 @@ class _CategoryListPageState extends State<CategoryListPage> {
|
||||
final updatedCategory = category.copyWith(
|
||||
name: name,
|
||||
color: color,
|
||||
icon: icon,
|
||||
iconCode: icon.codePoint, // Используем код иконки вместо IconData
|
||||
isIncome: isIncome,
|
||||
);
|
||||
|
||||
|
||||
@@ -235,7 +235,8 @@ class SmsMessageWidget extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
if (state is SmsItemError) ...[
|
||||
// Показываем ошибку из состояния или из сохраненного сообщения
|
||||
if ((state is SmsItemError) || (message.status == SmsStatus.error && message.errorMessage != null)) ...[
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
@@ -262,7 +263,8 @@ class SmsMessageWidget extends StatelessWidget {
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
state.error,
|
||||
// Используем ошибку из состояния, если она есть, иначе из сохраненного сообщения
|
||||
state is SmsItemError ? state.error : message.errorMessage!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.grey.shade300
|
||||
|
||||
@@ -68,6 +68,18 @@ class SmsService {
|
||||
await _smsRepository.updateSmsStatus(smsId, SmsStatus.ignored);
|
||||
}
|
||||
|
||||
/// Помечает СМС как содержащее ошибку с указанным сообщением.
|
||||
Future<void> markAsError(String smsId, String errorMessage) async {
|
||||
final message = await _smsRepository.getById(smsId);
|
||||
if (message != null) {
|
||||
final updatedMessage = message.copyWith(
|
||||
status: SmsStatus.error,
|
||||
errorMessage: errorMessage,
|
||||
);
|
||||
await _smsRepository.update(updatedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// Приватный метод для получения SMS с устройства с определенной даты.
|
||||
Future<List<SmsMessage>> _getSmsMessagesSince(DateTime? sinceDate) async {
|
||||
final List<telephony_package.SmsMessage> messages = await _telephony.getInboxSms(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:budget_app/data/repositories/interfaces/iai_rule_repository.dart';
|
||||
import 'package:budget_app/data/repositories/interfaces/isms_handler_repository.dart';
|
||||
import 'package:budget_app/data/repositories/interfaces/itransaction_repository.dart';
|
||||
import 'package:budget_app/logic/transaction/transaction_bloc.dart';
|
||||
import 'package:budget_app/models/ai_rule.dart';
|
||||
import 'package:budget_app/models/sms_handler_settings.dart';
|
||||
import 'package:budget_app/models/sms_message.dart';
|
||||
import 'package:budget_app/services/custom_sms_functions.dart';
|
||||
@@ -18,19 +20,69 @@ enum SmsProcessingResult {
|
||||
class SmsTransactionService {
|
||||
final ISmsHandlerRepository _smsHandlerRepository;
|
||||
final ITransactionRepository _transactionRepository;
|
||||
final IAiRuleRepository _aiRuleRepository;
|
||||
final TransactionBloc _transactionBloc;
|
||||
final Logger _logger = Logger();
|
||||
|
||||
SmsTransactionService(
|
||||
this._smsHandlerRepository,
|
||||
this._transactionRepository,
|
||||
this._aiRuleRepository,
|
||||
this._transactionBloc,
|
||||
);
|
||||
|
||||
/// Проверяет, соответствует ли текст SMS любому exclusion regex из активных skip правил
|
||||
Future<bool> _shouldSkipSms(String smsBody) async {
|
||||
try {
|
||||
// Получаем все активные правила типа skipTemplate
|
||||
final skipRules = await _aiRuleRepository.getByType(AiRuleType.skipTemplate);
|
||||
|
||||
// Фильтруем только активные правила с непустым skipRegex
|
||||
final activeSkipRules = skipRules.where((rule) =>
|
||||
rule.isActive && rule.skipRegex != null && rule.skipRegex!.isNotEmpty
|
||||
).toList();
|
||||
|
||||
if (activeSkipRules.isEmpty) {
|
||||
return false; // Нет активных skip правил
|
||||
}
|
||||
|
||||
// Проверяем каждый regex на соответствие тексту SMS
|
||||
for (final rule in activeSkipRules) {
|
||||
try {
|
||||
final regex = RegExp(rule.skipRegex!, caseSensitive: false);
|
||||
if (regex.hasMatch(smsBody)) {
|
||||
_logger.i(
|
||||
'SMS соответствует exclusion regex правила "${rule.name}": ${rule.skipRegex}',
|
||||
);
|
||||
return true; // SMS должно быть пропущено
|
||||
}
|
||||
} catch (e) {
|
||||
_logger.w(
|
||||
'Ошибка компиляции regex "${rule.skipRegex}" в правиле "${rule.name}": $e',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return false; // SMS не соответствует ни одному exclusion regex
|
||||
} catch (e) {
|
||||
_logger.e('Ошибка при проверке skip правил: $e');
|
||||
return false; // В случае ошибки продолжаем обработку
|
||||
}
|
||||
}
|
||||
|
||||
Future<SmsProcessingResult> createTransactionFromSms(
|
||||
SmsMessage message,
|
||||
) async {
|
||||
try {
|
||||
// Проверяем, соответствует ли SMS exclusion regex для пропуска
|
||||
final shouldSkip = await _shouldSkipSms(message.body ?? '');
|
||||
if (shouldSkip) {
|
||||
_logger.i(
|
||||
'SMS от ${message.sender} пропущено по exclusion regex: ${message.body}',
|
||||
);
|
||||
return SmsProcessingResult.ignored;
|
||||
}
|
||||
|
||||
final settings = await _smsHandlerRepository.getSmsHandlerSettings();
|
||||
final rule = settings?.rulesBySender[message.sender];
|
||||
|
||||
@@ -78,7 +130,13 @@ class SmsTransactionService {
|
||||
throw Exception('Не удалось сохранить транзакцию: $e');
|
||||
}
|
||||
} else {
|
||||
throw Exception('Не удалось создать транзакцию');
|
||||
// Кастомная функция вернула null - это нормально для не-транзакционных SMS
|
||||
// (например, реклама, спам, уведомления безопасности)
|
||||
_logger.i(
|
||||
'Кастомная функция обработки вернула null для SMS: ${message.id}. '
|
||||
'Это не-транзакционное сообщение, которое должно быть проигнорировано.',
|
||||
);
|
||||
return SmsProcessingResult.ignored;
|
||||
}
|
||||
} else {
|
||||
throw Exception(
|
||||
|
||||
@@ -14,21 +14,21 @@ class CategoryUtils {
|
||||
id: 'income_salary',
|
||||
name: 'Зарплата',
|
||||
color: Colors.green,
|
||||
icon: Icons.attach_money,
|
||||
iconCode: Icons.attach_money.codePoint, // Используем код иконки вместо IconData
|
||||
isIncome: true,
|
||||
),
|
||||
Category(
|
||||
id: 'income_gift',
|
||||
name: 'Подарки',
|
||||
color: Colors.blue,
|
||||
icon: Icons.card_giftcard,
|
||||
iconCode: Icons.card_giftcard.codePoint, // Используем код иконки вместо IconData
|
||||
isIncome: true,
|
||||
),
|
||||
Category(
|
||||
id: 'income_freelance',
|
||||
name: 'Фриланс',
|
||||
color: Colors.teal,
|
||||
icon: Icons.computer,
|
||||
iconCode: Icons.computer.codePoint, // Используем код иконки вместо IconData
|
||||
isIncome: true,
|
||||
),
|
||||
|
||||
@@ -37,42 +37,42 @@ class CategoryUtils {
|
||||
id: 'expense_food',
|
||||
name: 'Еда',
|
||||
color: Colors.red,
|
||||
icon: Icons.fastfood,
|
||||
iconCode: Icons.fastfood.codePoint, // Используем код иконки вместо IconData
|
||||
isIncome: false,
|
||||
),
|
||||
Category(
|
||||
id: 'expense_transport',
|
||||
name: 'Транспорт',
|
||||
color: Colors.orange,
|
||||
icon: Icons.directions_car,
|
||||
iconCode: Icons.directions_car.codePoint, // Используем код иконки вместо IconData
|
||||
isIncome: false,
|
||||
),
|
||||
Category(
|
||||
id: 'expense_entertainment',
|
||||
name: 'Развлечения',
|
||||
color: Colors.purple,
|
||||
icon: Icons.movie,
|
||||
iconCode: Icons.movie.codePoint, // Используем код иконки вместо IconData
|
||||
isIncome: false,
|
||||
),
|
||||
Category(
|
||||
id: 'expense_utilities',
|
||||
name: 'Коммунальные',
|
||||
color: Colors.blueGrey,
|
||||
icon: Icons.home,
|
||||
iconCode: Icons.home.codePoint, // Используем код иконки вместо IconData
|
||||
isIncome: false,
|
||||
),
|
||||
Category(
|
||||
id: 'expense_shopping',
|
||||
name: 'Покупки',
|
||||
color: Colors.pink,
|
||||
icon: Icons.shopping_bag,
|
||||
iconCode: Icons.shopping_bag.codePoint, // Используем код иконки вместо IconData
|
||||
isIncome: false,
|
||||
),
|
||||
Category(
|
||||
id: 'ai_category',
|
||||
name: 'AI Транзакция',
|
||||
color: Colors.black,
|
||||
icon: Icons.smart_toy,
|
||||
iconCode: Icons.smart_toy.codePoint, // Используем код иконки вместо IconData
|
||||
isIncome: false,
|
||||
),
|
||||
];
|
||||
|
||||
@@ -19,7 +19,7 @@ class TestObjects {
|
||||
id: id ?? 'test_category_id',
|
||||
name: name,
|
||||
color: Color(int.parse(color.replaceFirst('#', '0xFF'))),
|
||||
icon: const IconData(0xe59c, fontFamily: 'MaterialIcons'), // shopping_cart icon
|
||||
iconCode: 0xe59c, // shopping_cart icon code вместо IconData
|
||||
isIncome: isIncome,
|
||||
);
|
||||
}
|
||||
@@ -295,4 +295,4 @@ class TestObjects {
|
||||
...testPointOfSaleRules,
|
||||
...testSkipRules,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ void main() {
|
||||
setUp(() {
|
||||
// Создаем тестовые категории для использования в промптах
|
||||
testCategories = [
|
||||
Category(id: '1', name: 'Покупки', isIncome: false, icon: Icons.shopping_cart, color: Color(0xFF2196F3)),
|
||||
Category(id: '2', name: 'Транспорт', isIncome: false, icon: Icons.directions_bus, color: Color(0xFF4CAF50)),
|
||||
Category(id: '3', name: 'Зарплата', isIncome: true, icon: Icons.attach_money, color: Color(0xFFFF9800)),
|
||||
Category(id: '1', name: 'Покупки', isIncome: false, iconCode: Icons.shopping_cart.codePoint, color: Color(0xFF2196F3)),
|
||||
Category(id: '2', name: 'Транспорт', isIncome: false, iconCode: Icons.directions_bus.codePoint, color: Color(0xFF4CAF50)),
|
||||
Category(id: '3', name: 'Зарплата', isIncome: true, iconCode: Icons.attach_money.codePoint, color: Color(0xFFFF9800)),
|
||||
];
|
||||
});
|
||||
|
||||
@@ -76,8 +76,8 @@ void main() {
|
||||
test('должен корректно обрабатывать только категории расходов', () {
|
||||
// Проверяет работу с односторонними категориями
|
||||
final expenseOnlyCategories = [
|
||||
Category(id: '1', name: 'Покупки', isIncome: false, icon: Icons.shopping_cart, color: Color(0xFF2196F3)),
|
||||
Category(id: '2', name: 'Еда', isIncome: false, icon: Icons.restaurant, color: Color(0xFF4CAF50)),
|
||||
Category(id: '1', name: 'Покупки', isIncome: false, iconCode: Icons.shopping_cart.codePoint, color: Color(0xFF2196F3)),
|
||||
Category(id: '2', name: 'Еда', isIncome: false, iconCode: Icons.restaurant.codePoint, color: Color(0xFF4CAF50)),
|
||||
];
|
||||
|
||||
const smsBody = 'Тестовое SMS';
|
||||
@@ -90,8 +90,8 @@ void main() {
|
||||
test('должен корректно обрабатывать только категории доходов', () {
|
||||
// Проверяет работу только с доходными категориями
|
||||
final incomeOnlyCategories = [
|
||||
Category(id: '1', name: 'Зарплата', isIncome: true, icon: Icons.attach_money, color: Color(0xFFFF9800)),
|
||||
Category(id: '2', name: 'Премия', isIncome: true, icon: Icons.card_giftcard, color: Color(0xFF9C27B0)),
|
||||
Category(id: '1', name: 'Зарплата', isIncome: true, iconCode: Icons.attach_money.codePoint, color: Color(0xFFFF9800)),
|
||||
Category(id: '2', name: 'Премия', isIncome: true, iconCode: Icons.card_giftcard.codePoint, color: Color(0xFF9C27B0)),
|
||||
];
|
||||
|
||||
const smsBody = 'Тестовое SMS';
|
||||
@@ -165,4 +165,4 @@ void main() {
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user