Rework sms
This commit is contained in:
@@ -64,6 +64,7 @@
|
||||
"ruleTypeLabel": "Processing type",
|
||||
"regexpType": "Regular expression",
|
||||
"customFunctionType": "Custom function",
|
||||
"noProcessingType": "No processing required",
|
||||
"ruleTypeRequired": "Processing type is required",
|
||||
"regexpPatternHint": "Regular expression pattern",
|
||||
"regexpPatternRequired": "Pattern is required",
|
||||
|
||||
@@ -482,6 +482,12 @@ abstract class AppLocalizations {
|
||||
/// **'Custom function'**
|
||||
String get customFunctionType;
|
||||
|
||||
/// No description provided for @noProcessingType.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No processing required'**
|
||||
String get noProcessingType;
|
||||
|
||||
/// No description provided for @ruleTypeRequired.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
||||
@@ -203,6 +203,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get customFunctionType => 'Custom function';
|
||||
|
||||
@override
|
||||
String get noProcessingType => 'No processing required';
|
||||
|
||||
@override
|
||||
String get ruleTypeRequired => 'Processing type is required';
|
||||
|
||||
|
||||
@@ -206,6 +206,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get customFunctionType => 'Кастомная функция';
|
||||
|
||||
@override
|
||||
String get noProcessingType => 'Не требует обработки';
|
||||
|
||||
@override
|
||||
String get ruleTypeRequired => 'Тип обработки обязателен';
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
"ruleTypeLabel": "Тип обработки",
|
||||
"regexpType": "Регулярное выражение",
|
||||
"customFunctionType": "Кастомная функция",
|
||||
"noProcessingType": "Не требует обработки",
|
||||
"ruleTypeRequired": "Тип обработки обязателен",
|
||||
"regexpPatternHint": "Шаблон регулярного выражения",
|
||||
"regexpPatternRequired": "Шаблон обязателен",
|
||||
|
||||
@@ -26,12 +26,35 @@ class SmsItemCubit extends Cubit<SmsItemState> {
|
||||
emit(SmsItemProcessing());
|
||||
try {
|
||||
// Вызываем сервис для создания транзакции
|
||||
await _smsTransactionService.createTransactionFromSms(message);
|
||||
// Помечаем СМС как обработанное
|
||||
final updatedMessage = message.copyWith(status: SmsStatus.processed);
|
||||
await _smsService.markAsProcessed(message.id);
|
||||
emit(SmsItemSuccess());
|
||||
onProcessed(updatedMessage); // Передаем обновленное сообщение
|
||||
final result = await _smsTransactionService.createTransactionFromSms(message);
|
||||
|
||||
switch (result) {
|
||||
case SmsProcessingResult.success:
|
||||
// Транзакция успешно создана - помечаем СМС как обработанное
|
||||
final updatedMessage = message.copyWith(status: SmsStatus.processed);
|
||||
await _smsService.markAsProcessed(message.id);
|
||||
emit(SmsItemSuccess());
|
||||
onProcessed(updatedMessage);
|
||||
break;
|
||||
|
||||
case SmsProcessingResult.ignored:
|
||||
// SMS должно быть проигнорировано - помечаем как ignored
|
||||
final updatedMessage = message.copyWith(status: SmsStatus.ignored);
|
||||
await _smsService.markAsIgnored(message.id);
|
||||
emit(SmsItemIgnored());
|
||||
onProcessed(updatedMessage);
|
||||
break;
|
||||
|
||||
case SmsProcessingResult.noRule:
|
||||
// Правило не найдено - показываем ошибку и не меняем статус
|
||||
emit(SmsItemError('Правило обработки не настроено для отправителя: ${message.sender}'));
|
||||
break;
|
||||
|
||||
case SmsProcessingResult.error:
|
||||
// Ошибка обработки - показываем ошибку и не меняем статус
|
||||
emit(SmsItemError('Ошибка при обработке SMS'));
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
emit(SmsItemError(e.toString()));
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@ enum SmsProcessingType {
|
||||
/// Обработка с использованием кастомной функции.
|
||||
@HiveField(1)
|
||||
customFunction,
|
||||
|
||||
/// Не требует обработки.
|
||||
@HiveField(2)
|
||||
noProcessing,
|
||||
}
|
||||
|
||||
/// Модель для хранения правила обработки СМС от конкретного отправителя.
|
||||
@@ -43,7 +47,8 @@ class SmsProcessingRule extends HiveObject {
|
||||
this.customFunctionId,
|
||||
}) : assert(
|
||||
(type == SmsProcessingType.regexp && pattern != null) ||
|
||||
(type == SmsProcessingType.customFunction && customFunctionId != null),
|
||||
(type == SmsProcessingType.customFunction && customFunctionId != null) ||
|
||||
(type == SmsProcessingType.noProcessing),
|
||||
'Pattern must be provided for regexp type, and customFunctionId for customFunction type.',
|
||||
), id = id ?? IdGenerator.generateId();
|
||||
|
||||
@@ -64,6 +69,9 @@ class SmsProcessingRule extends HiveObject {
|
||||
} else if (newType == SmsProcessingType.customFunction) {
|
||||
newCustomFunctionId = customFunctionId ?? this.customFunctionId;
|
||||
newPattern = null; // Сбрасываем pattern для customFunction
|
||||
} else if (newType == SmsProcessingType.noProcessing) {
|
||||
newPattern = null; // Сбрасываем pattern для noProcessing
|
||||
newCustomFunctionId = null; // Сбрасываем customFunctionId для noProcessing
|
||||
}
|
||||
|
||||
return SmsProcessingRule(
|
||||
|
||||
@@ -97,6 +97,8 @@ class SmsProcessingTypeAdapter extends TypeAdapter<SmsProcessingType> {
|
||||
return SmsProcessingType.regexp;
|
||||
case 1:
|
||||
return SmsProcessingType.customFunction;
|
||||
case 2:
|
||||
return SmsProcessingType.noProcessing;
|
||||
default:
|
||||
return SmsProcessingType.regexp;
|
||||
}
|
||||
@@ -109,6 +111,8 @@ class SmsProcessingTypeAdapter extends TypeAdapter<SmsProcessingType> {
|
||||
writer.writeByte(0);
|
||||
case SmsProcessingType.customFunction:
|
||||
writer.writeByte(1);
|
||||
case SmsProcessingType.noProcessing:
|
||||
writer.writeByte(2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,20 +12,9 @@ class SmsMessageWidget extends StatelessWidget {
|
||||
required this.message,
|
||||
});
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is SmsMessageWidget &&
|
||||
runtimeType == other.runtimeType &&
|
||||
message == other.message;
|
||||
|
||||
@override
|
||||
int get hashCode => message.hashCode;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cubit = context.read<SmsItemCubit>();
|
||||
|
||||
return BlocBuilder<SmsItemCubit, SmsItemState>(
|
||||
builder: (context, state) {
|
||||
return Card(
|
||||
@@ -37,7 +26,7 @@ class SmsMessageWidget extends StatelessWidget {
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
gradient: _getGradientForStatus(message.status),
|
||||
gradient: _getGradientForStatus(message.status, context),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
@@ -53,10 +42,14 @@ class SmsMessageWidget extends StatelessWidget {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.account_circle,
|
||||
color: Colors.grey[600],
|
||||
size: 20,
|
||||
Builder(
|
||||
builder: (context) => Icon(
|
||||
Icons.account_circle,
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.grey[400]
|
||||
: Colors.grey[600],
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
@@ -71,26 +64,30 @@ class SmsMessageWidget extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
message.body ?? '',
|
||||
style: TextStyle(
|
||||
color: Colors.grey[700],
|
||||
fontSize: 14,
|
||||
Builder(
|
||||
builder: (context) => Text(
|
||||
message.body ?? '',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.grey[300]
|
||||
: Colors.grey[700],
|
||||
fontSize: 14,
|
||||
),
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_buildStatusIndicator(message.status),
|
||||
_buildStatusIndicator(message.status, context),
|
||||
const SizedBox(width: 8),
|
||||
_buildMenuButton(context, message, state),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildBottomRow(message, state),
|
||||
_buildBottomRow(message, state, context),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -100,29 +97,40 @@ class SmsMessageWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
LinearGradient _getGradientForStatus(SmsStatus status) {
|
||||
LinearGradient _getGradientForStatus(SmsStatus status, BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
switch (status) {
|
||||
case SmsStatus.pending:
|
||||
return LinearGradient(
|
||||
colors: [
|
||||
Colors.blue.shade50,
|
||||
Colors.blue.shade100,
|
||||
colors: isDark ? [
|
||||
Colors.grey.shade800,
|
||||
Colors.grey.shade700,
|
||||
] : [
|
||||
Colors.grey.shade50,
|
||||
Colors.grey.shade100,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
);
|
||||
case SmsStatus.processed:
|
||||
return LinearGradient(
|
||||
colors: [
|
||||
Colors.green.shade50,
|
||||
Colors.green.shade100,
|
||||
colors: isDark ? [
|
||||
Colors.grey.shade900,
|
||||
Colors.grey.shade800,
|
||||
] : [
|
||||
Colors.grey.shade200,
|
||||
Colors.grey.shade300,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
);
|
||||
case SmsStatus.ignored:
|
||||
return LinearGradient(
|
||||
colors: [
|
||||
colors: isDark ? [
|
||||
Colors.grey.shade900,
|
||||
Colors.grey.shade800,
|
||||
] : [
|
||||
Colors.grey.shade100,
|
||||
Colors.grey.shade200,
|
||||
],
|
||||
@@ -131,9 +139,12 @@ class SmsMessageWidget extends StatelessWidget {
|
||||
);
|
||||
case SmsStatus.error:
|
||||
return LinearGradient(
|
||||
colors: [
|
||||
Colors.red.shade50,
|
||||
Colors.red.shade100,
|
||||
colors: isDark ? [
|
||||
Colors.grey.shade800,
|
||||
Colors.grey.shade700,
|
||||
] : [
|
||||
Colors.grey.shade100,
|
||||
Colors.grey.shade200,
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
@@ -141,29 +152,30 @@ class SmsMessageWidget extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildStatusIndicator(SmsStatus status) {
|
||||
Widget _buildStatusIndicator(SmsStatus status, BuildContext context) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
Color color;
|
||||
IconData icon;
|
||||
String label;
|
||||
|
||||
switch (status) {
|
||||
case SmsStatus.pending:
|
||||
color = Colors.blue;
|
||||
color = isDark ? Colors.grey.shade400 : Colors.grey.shade600;
|
||||
icon = Icons.access_time;
|
||||
label = 'Ожидает';
|
||||
break;
|
||||
case SmsStatus.processed:
|
||||
color = Colors.green;
|
||||
color = isDark ? Colors.grey.shade300 : Colors.black87;
|
||||
icon = Icons.check_circle;
|
||||
label = 'Обработано';
|
||||
break;
|
||||
case SmsStatus.ignored:
|
||||
color = Colors.grey;
|
||||
color = isDark ? Colors.grey.shade500 : Colors.grey.shade700;
|
||||
icon = Icons.block;
|
||||
label = 'Игнорировать';
|
||||
break;
|
||||
case SmsStatus.error:
|
||||
color = Colors.red;
|
||||
color = isDark ? Colors.grey.shade400 : Colors.grey.shade600;
|
||||
icon = Icons.error;
|
||||
label = 'Ошибка';
|
||||
break;
|
||||
@@ -194,24 +206,71 @@ class SmsMessageWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBottomRow(SmsMessage message, SmsItemState state) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
Widget _buildBottomRow(SmsMessage message, SmsItemState state, BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (message.date != null)
|
||||
Text(
|
||||
_formatDate(message.date!),
|
||||
style: TextStyle(
|
||||
color: Colors.grey[600],
|
||||
fontSize: 12,
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (message.date != null)
|
||||
Text(
|
||||
_formatDate(message.date!),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.grey[400]
|
||||
: Colors.grey[600],
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
if (state is SmsItemProcessing)
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (state is SmsItemError) ...[
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.grey.shade800
|
||||
: Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.grey.shade600
|
||||
: Colors.grey.shade400,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.grey.shade400
|
||||
: Colors.grey.shade700,
|
||||
size: 16,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
state.error,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.grey.shade300
|
||||
: Colors.grey.shade700,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (state is SmsItemProcessing)
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -143,7 +143,9 @@ class _SmsSettingsDialogViewState extends State<_SmsSettingsDialogView> {
|
||||
child: Text(
|
||||
type == SmsProcessingType.regexp
|
||||
? loc.regexpType
|
||||
: loc.customFunctionType,
|
||||
: type == SmsProcessingType.customFunction
|
||||
? loc.customFunctionType
|
||||
: loc.noProcessingType,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
@@ -171,6 +173,12 @@ class _SmsSettingsDialogViewState extends State<_SmsSettingsDialogView> {
|
||||
context.read<SmsSettingsCubit>().updateRule(
|
||||
rule.copyWith(type: type, pattern: currentPattern),
|
||||
);
|
||||
} else if (type == SmsProcessingType.noProcessing) {
|
||||
// Очищаем контроллер при переключении на noProcessing
|
||||
_patternController.clear();
|
||||
context.read<SmsSettingsCubit>().updateRule(
|
||||
rule.copyWith(type: type),
|
||||
);
|
||||
} else {
|
||||
context.read<SmsSettingsCubit>().updateRule(
|
||||
rule.copyWith(type: type),
|
||||
@@ -199,7 +207,7 @@ class _SmsSettingsDialogViewState extends State<_SmsSettingsDialogView> {
|
||||
);
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
if (rule.type == SmsProcessingType.regexp && (value == null || value.isEmpty)) {
|
||||
return loc.regexpPatternRequired;
|
||||
}
|
||||
return null;
|
||||
@@ -224,7 +232,7 @@ class _SmsSettingsDialogViewState extends State<_SmsSettingsDialogView> {
|
||||
}
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
if (rule.type == SmsProcessingType.customFunction && (value == null || value.isEmpty)) {
|
||||
return loc.customFunctionIdRequired;
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -5,6 +5,14 @@ import 'package:budget_app/models/sms_message.dart';
|
||||
import 'package:budget_app/services/custom_sms_functions.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
/// Результат обработки SMS сообщения
|
||||
enum SmsProcessingResult {
|
||||
success, // Транзакция успешно создана
|
||||
ignored, // SMS должно быть проигнорировано (noProcessing)
|
||||
noRule, // Правило не найдено для данного отправителя
|
||||
error, // Ошибка при обработке
|
||||
}
|
||||
|
||||
/// Сервис для обработки транзакций из SMS сообщений
|
||||
class SmsTransactionService {
|
||||
final ISmsHandlerRepository _smsHandlerRepository;
|
||||
@@ -16,15 +24,26 @@ class SmsTransactionService {
|
||||
this._transactionBloc,
|
||||
);
|
||||
|
||||
Future<void> createTransactionFromSms(
|
||||
Future<SmsProcessingResult> createTransactionFromSms(
|
||||
SmsMessage message,
|
||||
) async {
|
||||
try {
|
||||
final settings = await _smsHandlerRepository.getSmsHandlerSettings();
|
||||
final rule = settings?.rulesBySender[message.sender];
|
||||
|
||||
if (rule != null &&
|
||||
rule.type == SmsProcessingType.customFunction &&
|
||||
if (rule == null) {
|
||||
// Правило не найдено для данного отправителя
|
||||
_logger.w('Правило для создания транзакции не найдено для сендера: ${message.sender}');
|
||||
return SmsProcessingResult.noRule;
|
||||
}
|
||||
|
||||
if (rule.type == SmsProcessingType.noProcessing) {
|
||||
// SMS должно быть проигнорировано
|
||||
_logger.i('SMS от ${message.sender} помечено как не требующее обработки');
|
||||
return SmsProcessingResult.ignored;
|
||||
}
|
||||
|
||||
if (rule.type == SmsProcessingType.customFunction &&
|
||||
rule.customFunctionId != null) {
|
||||
final processingFunction = CustomSmsFunctions.getFunctionById(
|
||||
rule.customFunctionId!,
|
||||
@@ -39,24 +58,24 @@ class SmsTransactionService {
|
||||
_logger.i('Creating transaction from SMS: ${message.id}');
|
||||
_logger.d('Transaction details: ${transaction.toMap()}');
|
||||
|
||||
// Статус будет обновлен в SmsItemCubit
|
||||
|
||||
_logger.i('Transaction created successfully for SMS: ${message.id}');
|
||||
return SmsProcessingResult.success;
|
||||
} else {
|
||||
throw Exception('Не удалось создать транзакцию');
|
||||
}
|
||||
} else {
|
||||
throw Exception('Функция обработки не найдена для id: ${rule.customFunctionId}');
|
||||
}
|
||||
} else if (rule.type == SmsProcessingType.regexp) {
|
||||
// TODO: Implement regexp processing
|
||||
_logger.w('Regexp processing not yet implemented');
|
||||
throw Exception('Regexp processing not yet implemented');
|
||||
} else {
|
||||
// Если правило не найдено, просто ничего не делаем.
|
||||
// Логика игнорирования находится в SmsItemCubit
|
||||
_logger.w('Правило для создания транзакции не найдено для сендера: ${message.sender}');
|
||||
throw Exception('Неизвестный тип обработки: ${rule.type}');
|
||||
}
|
||||
} catch (e) {
|
||||
_logger.e('Ошибка создания транзакции: $e');
|
||||
// Перебрасываем ошибку, чтобы ее обработал SmsItemCubit
|
||||
rethrow;
|
||||
return SmsProcessingResult.error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user