fixes list sms
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(flutter gen-l10n:*)"
|
||||
"Bash(flutter gen-l10n:*)",
|
||||
"Bash(flutter analyze:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import 'logic/auth/auth_bloc.dart';
|
||||
import 'logic/category/category_cubit.dart';
|
||||
import 'logic/settings/settings_cubit.dart';
|
||||
import 'logic/sms/sms_cubit.dart';
|
||||
import 'logic/sms/sms_transaction_cubit.dart';
|
||||
import 'logic/sms/sms_settings_cubit.dart'; // Добавляем импорт
|
||||
import 'logic/tag/tag_cubit.dart';
|
||||
import 'logic/transaction/transaction_bloc.dart';
|
||||
@@ -157,9 +158,16 @@ Future<void> initUserSpecificDependencies(String userId) async {
|
||||
getIt<SmsService>(),
|
||||
getIt<ISmsMessageRepository>(),
|
||||
getIt<UserCubit>(),
|
||||
));
|
||||
|
||||
// SMS Transaction
|
||||
if (getIt.isRegistered<SmsTransactionCubit>()) {
|
||||
await getIt.unregister<SmsTransactionCubit>();
|
||||
}
|
||||
getIt.registerSingleton<SmsTransactionCubit>(SmsTransactionCubit(
|
||||
getIt<ISmsHandlerRepository>(),
|
||||
getIt<TransactionBloc>(),
|
||||
getIt<ISettingsRepository>()
|
||||
getIt<ISmsMessageRepository>(),
|
||||
));
|
||||
|
||||
// Категории
|
||||
@@ -202,6 +210,7 @@ Future<void> resetUserSpecificDependencies() async {
|
||||
await getIt.unregister<SettingsCubit>();
|
||||
await getIt.unregister<TransactionBloc>();
|
||||
await getIt.unregister<SmsCubit>();
|
||||
await getIt.unregister<SmsTransactionCubit>();
|
||||
await getIt.unregister<CategoryCubit>();
|
||||
await getIt.unregister<TagCubit>();
|
||||
}
|
||||
|
||||
+5
-1
@@ -104,5 +104,9 @@
|
||||
"processedFilter": "Processed",
|
||||
"notRequiredFilter": "Not required",
|
||||
"errorFilter": "Error",
|
||||
"transactionCreated": "Transaction created"
|
||||
"transactionCreated": "Transaction created",
|
||||
"smsStatusProcessed": "Processed",
|
||||
"smsStatusError": "Error",
|
||||
"smsStatusNotRequired": "Not required",
|
||||
"smsStatusPending": "Pending"
|
||||
}
|
||||
|
||||
@@ -727,6 +727,30 @@ abstract class AppLocalizations {
|
||||
/// In en, this message translates to:
|
||||
/// **'Transaction created'**
|
||||
String get transactionCreated;
|
||||
|
||||
/// No description provided for @smsStatusProcessed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Processed'**
|
||||
String get smsStatusProcessed;
|
||||
|
||||
/// No description provided for @smsStatusError.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Error'**
|
||||
String get smsStatusError;
|
||||
|
||||
/// No description provided for @smsStatusNotRequired.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Not required'**
|
||||
String get smsStatusNotRequired;
|
||||
|
||||
/// No description provided for @smsStatusPending.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Pending'**
|
||||
String get smsStatusPending;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
||||
@@ -328,4 +328,16 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get transactionCreated => 'Transaction created';
|
||||
|
||||
@override
|
||||
String get smsStatusProcessed => 'Processed';
|
||||
|
||||
@override
|
||||
String get smsStatusError => 'Error';
|
||||
|
||||
@override
|
||||
String get smsStatusNotRequired => 'Not required';
|
||||
|
||||
@override
|
||||
String get smsStatusPending => 'Pending';
|
||||
}
|
||||
|
||||
@@ -329,4 +329,16 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get transactionCreated => 'Транзакция создана';
|
||||
|
||||
@override
|
||||
String get smsStatusProcessed => 'Обработано';
|
||||
|
||||
@override
|
||||
String get smsStatusError => 'Ошибка';
|
||||
|
||||
@override
|
||||
String get smsStatusNotRequired => 'Не требуется';
|
||||
|
||||
@override
|
||||
String get smsStatusPending => 'Ожидает';
|
||||
}
|
||||
|
||||
+5
-1
@@ -104,5 +104,9 @@
|
||||
"processedFilter": "Обработанные",
|
||||
"notRequiredFilter": "Не требуется",
|
||||
"errorFilter": "Ошибка",
|
||||
"transactionCreated": "Транзакция создана"
|
||||
"transactionCreated": "Транзакция создана",
|
||||
"smsStatusProcessed": "Обработано",
|
||||
"smsStatusError": "Ошибка",
|
||||
"smsStatusNotRequired": "Не требуется",
|
||||
"smsStatusPending": "Ожидает"
|
||||
}
|
||||
|
||||
+10
-104
@@ -1,16 +1,10 @@
|
||||
import 'package:budget_app/logic/sms/sms_state.dart';
|
||||
import 'package:budget_app/models/sms_message.dart';
|
||||
import 'package:budget_app/data/repositories/interfaces/isms_handler_repository.dart';
|
||||
import 'package:budget_app/logic/transaction/transaction_bloc.dart';
|
||||
import 'package:budget_app/models/sms_handler_settings.dart';
|
||||
import 'package:budget_app/services/custom_sms_functions.dart';
|
||||
|
||||
import 'package:budget_app/services/sms_service.dart';
|
||||
import 'package:budget_app/logic/user/user_cubit.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:budget_app/data/repositories/interfaces/isms_message_repository.dart';
|
||||
import 'package:budget_app/data/repositories/interfaces/isettings_repository.dart';
|
||||
|
||||
/// Cubit для управления состоянием SMS.
|
||||
///
|
||||
@@ -19,9 +13,6 @@ class SmsCubit extends Cubit<SmsState> {
|
||||
final SmsService _smsService;
|
||||
final ISmsMessageRepository _smsRepository;
|
||||
final UserCubit _userCubit;
|
||||
final ISmsHandlerRepository _smsHandlerRepository;
|
||||
final TransactionBloc _transactionBloc;
|
||||
final ISettingsRepository _settingsRepository;
|
||||
final Logger _logger = Logger();
|
||||
|
||||
List<SmsMessage> _messages = [];
|
||||
@@ -37,9 +28,6 @@ class SmsCubit extends Cubit<SmsState> {
|
||||
this._smsService,
|
||||
this._smsRepository,
|
||||
this._userCubit,
|
||||
this._smsHandlerRepository,
|
||||
this._transactionBloc,
|
||||
this._settingsRepository,
|
||||
) : super(SmsInitial());
|
||||
|
||||
/// Загружает последние 10 SMS сообщений.
|
||||
@@ -95,16 +83,8 @@ class SmsCubit extends Cubit<SmsState> {
|
||||
_currentPage = 1;
|
||||
emit(const SmsState.loaded());
|
||||
|
||||
// 8. Обрабатываем новые сообщения (если разрешено в настройках)
|
||||
final appSettings = await _settingsRepository.getSettings();
|
||||
if (appSettings.autoCreateTransactionsFromSms) {
|
||||
for (final message in newMessages) {
|
||||
// Обрабатываем только сообщения со статусом "pending"
|
||||
if (message.processingStatus == SmsProcessingStatus.pending) {
|
||||
_createTransactionFromSms(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 8. Новые сообщения загружены - обработка транзакций теперь в SmsTransactionCubit
|
||||
// Примечание: автоматическая обработка SMS должна быть настроена в UI
|
||||
} else {
|
||||
emit(SmsError("User not loaded"));
|
||||
}
|
||||
@@ -141,91 +121,17 @@ class SmsCubit extends Cubit<SmsState> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Создает транзакцию из СМС сообщения
|
||||
Future<void> createTransactionFromSms(SmsMessage message) async {
|
||||
try {
|
||||
final settings = await _smsHandlerRepository.getSmsHandlerSettings();
|
||||
final rule = settings?.rulesBySender[message.sender];
|
||||
|
||||
if (rule != null &&
|
||||
rule.type == SmsProcessingType.customFunction &&
|
||||
rule.customFunctionId != null) {
|
||||
final processingFunction = CustomSmsFunctions.getFunctionById(
|
||||
rule.customFunctionId!,
|
||||
);
|
||||
if (processingFunction != null) {
|
||||
final transaction = await processingFunction(message.body ?? '');
|
||||
if (transaction != null) {
|
||||
_transactionBloc.add(AddTransaction(transaction: transaction));
|
||||
// Обновляем сообщение с ссылкой на транзакцию и статусом "processed"
|
||||
_logger.i('Creating transaction from SMS: ${message.id}');
|
||||
_logger.d('Transaction details: ${transaction.toMap()}');
|
||||
|
||||
_logger.d('Before setting transactionId: ${message.transactionId}');
|
||||
message.transactionId = transaction.id;
|
||||
_logger.d('After setting transactionId: ${message.transactionId}');
|
||||
|
||||
// Обновляем статус сообщения на "processed"
|
||||
final updatedMessage = message.copyWith(
|
||||
transactionId: transaction.id,
|
||||
processingStatus: SmsProcessingStatus.processed,
|
||||
);
|
||||
|
||||
_logger.d('Calling updateMessage...');
|
||||
await updateMessage(updatedMessage);
|
||||
_logger.d('After updateMessage call');
|
||||
|
||||
_logger.i('Transaction created successfully for SMS: ${message.id}');
|
||||
_logger.i('Final transactionId: ${message.transactionId}');
|
||||
|
||||
// Показываем уведомление об успешном создании транзакции
|
||||
emit(const SmsState.transactionCreated());
|
||||
|
||||
} else {
|
||||
// Обновляем статус сообщения на "error" при неудачном создании транзакции
|
||||
final updatedMessage = message.copyWith(
|
||||
processingStatus: SmsProcessingStatus.error,
|
||||
errorMessage: 'Не удалось создать транзакцию',
|
||||
);
|
||||
await updateMessage(updatedMessage);
|
||||
emit(const SmsState.transactionError('Не удалось создать транзакцию'));
|
||||
}
|
||||
} else {
|
||||
// Обновляем статус сообщения на "error" при отсутствии функции обработки
|
||||
final updatedMessage = message.copyWith(
|
||||
processingStatus: SmsProcessingStatus.error,
|
||||
errorMessage: 'Функция обработки не найдена для id: ${rule.customFunctionId}',
|
||||
);
|
||||
await updateMessage(updatedMessage);
|
||||
emit(SmsState.transactionError(
|
||||
'Функция обработки не найдена для id: ${rule.customFunctionId}',
|
||||
));
|
||||
}
|
||||
} else {
|
||||
// Обновляем статус сообщения на "notRequired" при отсутствии правила
|
||||
final updatedMessage = message.copyWith(
|
||||
processingStatus: SmsProcessingStatus.notRequired,
|
||||
errorMessage: 'Правило для создания транзакции не найдено',
|
||||
);
|
||||
await updateMessage(updatedMessage);
|
||||
emit(const SmsState.transactionError('Правило для создания транзакции не найдено'));
|
||||
}
|
||||
} catch (e) {
|
||||
// Обновляем статус сообщения на "error" при любой ошибке
|
||||
final updatedMessage = message.copyWith(
|
||||
processingStatus: SmsProcessingStatus.error,
|
||||
errorMessage: 'Ошибка создания транзакции: $e',
|
||||
);
|
||||
await updateMessage(updatedMessage);
|
||||
emit(SmsState.transactionError('Ошибка создания транзакции: $e'));
|
||||
/// Callback для обновления сообщения после обработки транзакции
|
||||
void onMessageUpdated(SmsMessage updatedMessage) {
|
||||
_messages = _messages.map((m) =>
|
||||
m.id == updatedMessage.id ? updatedMessage : m
|
||||
).toList();
|
||||
|
||||
if (state is SmsLoaded) {
|
||||
emit(const SmsState.loaded());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _createTransactionFromSms(SmsMessage sms) async {
|
||||
// Оставлю заглушку для совместимости
|
||||
await createTransactionFromSms(sms);
|
||||
}
|
||||
|
||||
/// Загружает следующую страницу сообщений
|
||||
Future<void> loadMoreMessages() async {
|
||||
if (state is! SmsLoaded) return;
|
||||
|
||||
@@ -9,11 +9,4 @@ class SmsState with _$SmsState {
|
||||
const factory SmsState.loaded() = SmsLoaded;
|
||||
const factory SmsState.permissionDenied() = SmsPermissionDenied;
|
||||
const factory SmsState.error(String message) = SmsError;
|
||||
const factory SmsState.transactionLoading() = SmsTransactionLoading;
|
||||
const factory SmsState.transactionError(String message) = SmsTransactionError;
|
||||
const factory SmsState.transactionCreated() = SmsTransactionCreated;
|
||||
const factory SmsState.transactionNotification(
|
||||
String message, {
|
||||
@Default(true) bool isSuccess,
|
||||
}) = SmsTransactionNotification;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ extension SmsStatePatterns on SmsState {
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>({TResult Function( SmsInitial value)? initial,TResult Function( SmsLoading value)? loading,TResult Function( SmsLoaded value)? loaded,TResult Function( SmsPermissionDenied value)? permissionDenied,TResult Function( SmsError value)? error,TResult Function( SmsTransactionLoading value)? transactionLoading,TResult Function( SmsTransactionError value)? transactionError,TResult Function( SmsTransactionCreated value)? transactionCreated,TResult Function( SmsTransactionNotification value)? transactionNotification,required TResult orElse(),}){
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>({TResult Function( SmsInitial value)? initial,TResult Function( SmsLoading value)? loading,TResult Function( SmsLoaded value)? loaded,TResult Function( SmsPermissionDenied value)? permissionDenied,TResult Function( SmsError value)? error,required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case SmsInitial() when initial != null:
|
||||
@@ -63,11 +63,7 @@ return initial(_that);case SmsLoading() when loading != null:
|
||||
return loading(_that);case SmsLoaded() when loaded != null:
|
||||
return loaded(_that);case SmsPermissionDenied() when permissionDenied != null:
|
||||
return permissionDenied(_that);case SmsError() when error != null:
|
||||
return error(_that);case SmsTransactionLoading() when transactionLoading != null:
|
||||
return transactionLoading(_that);case SmsTransactionError() when transactionError != null:
|
||||
return transactionError(_that);case SmsTransactionCreated() when transactionCreated != null:
|
||||
return transactionCreated(_that);case SmsTransactionNotification() when transactionNotification != null:
|
||||
return transactionNotification(_that);case _:
|
||||
return error(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -85,7 +81,7 @@ return transactionNotification(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>({required TResult Function( SmsInitial value) initial,required TResult Function( SmsLoading value) loading,required TResult Function( SmsLoaded value) loaded,required TResult Function( SmsPermissionDenied value) permissionDenied,required TResult Function( SmsError value) error,required TResult Function( SmsTransactionLoading value) transactionLoading,required TResult Function( SmsTransactionError value) transactionError,required TResult Function( SmsTransactionCreated value) transactionCreated,required TResult Function( SmsTransactionNotification value) transactionNotification,}){
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>({required TResult Function( SmsInitial value) initial,required TResult Function( SmsLoading value) loading,required TResult Function( SmsLoaded value) loaded,required TResult Function( SmsPermissionDenied value) permissionDenied,required TResult Function( SmsError value) error,}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case SmsInitial():
|
||||
@@ -93,11 +89,7 @@ return initial(_that);case SmsLoading():
|
||||
return loading(_that);case SmsLoaded():
|
||||
return loaded(_that);case SmsPermissionDenied():
|
||||
return permissionDenied(_that);case SmsError():
|
||||
return error(_that);case SmsTransactionLoading():
|
||||
return transactionLoading(_that);case SmsTransactionError():
|
||||
return transactionError(_that);case SmsTransactionCreated():
|
||||
return transactionCreated(_that);case SmsTransactionNotification():
|
||||
return transactionNotification(_that);case _:
|
||||
return error(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
@@ -114,7 +106,7 @@ return transactionNotification(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({TResult? Function( SmsInitial value)? initial,TResult? Function( SmsLoading value)? loading,TResult? Function( SmsLoaded value)? loaded,TResult? Function( SmsPermissionDenied value)? permissionDenied,TResult? Function( SmsError value)? error,TResult? Function( SmsTransactionLoading value)? transactionLoading,TResult? Function( SmsTransactionError value)? transactionError,TResult? Function( SmsTransactionCreated value)? transactionCreated,TResult? Function( SmsTransactionNotification value)? transactionNotification,}){
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>({TResult? Function( SmsInitial value)? initial,TResult? Function( SmsLoading value)? loading,TResult? Function( SmsLoaded value)? loaded,TResult? Function( SmsPermissionDenied value)? permissionDenied,TResult? Function( SmsError value)? error,}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case SmsInitial() when initial != null:
|
||||
@@ -122,11 +114,7 @@ return initial(_that);case SmsLoading() when loading != null:
|
||||
return loading(_that);case SmsLoaded() when loaded != null:
|
||||
return loaded(_that);case SmsPermissionDenied() when permissionDenied != null:
|
||||
return permissionDenied(_that);case SmsError() when error != null:
|
||||
return error(_that);case SmsTransactionLoading() when transactionLoading != null:
|
||||
return transactionLoading(_that);case SmsTransactionError() when transactionError != null:
|
||||
return transactionError(_that);case SmsTransactionCreated() when transactionCreated != null:
|
||||
return transactionCreated(_that);case SmsTransactionNotification() when transactionNotification != null:
|
||||
return transactionNotification(_that);case _:
|
||||
return error(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -143,18 +131,14 @@ return transactionNotification(_that);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function()? initial,TResult Function()? loading,TResult Function()? loaded,TResult Function()? permissionDenied,TResult Function( String message)? error,TResult Function()? transactionLoading,TResult Function( String message)? transactionError,TResult Function()? transactionCreated,TResult Function( String message, bool isSuccess)? transactionNotification,required TResult orElse(),}) {final _that = this;
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function()? initial,TResult Function()? loading,TResult Function()? loaded,TResult Function()? permissionDenied,TResult Function( String message)? error,required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case SmsInitial() when initial != null:
|
||||
return initial();case SmsLoading() when loading != null:
|
||||
return loading();case SmsLoaded() when loaded != null:
|
||||
return loaded();case SmsPermissionDenied() when permissionDenied != null:
|
||||
return permissionDenied();case SmsError() when error != null:
|
||||
return error(_that.message);case SmsTransactionLoading() when transactionLoading != null:
|
||||
return transactionLoading();case SmsTransactionError() when transactionError != null:
|
||||
return transactionError(_that.message);case SmsTransactionCreated() when transactionCreated != null:
|
||||
return transactionCreated();case SmsTransactionNotification() when transactionNotification != null:
|
||||
return transactionNotification(_that.message,_that.isSuccess);case _:
|
||||
return error(_that.message);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
@@ -172,18 +156,14 @@ return transactionNotification(_that.message,_that.isSuccess);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function() initial,required TResult Function() loading,required TResult Function() loaded,required TResult Function() permissionDenied,required TResult Function( String message) error,required TResult Function() transactionLoading,required TResult Function( String message) transactionError,required TResult Function() transactionCreated,required TResult Function( String message, bool isSuccess) transactionNotification,}) {final _that = this;
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function() initial,required TResult Function() loading,required TResult Function() loaded,required TResult Function() permissionDenied,required TResult Function( String message) error,}) {final _that = this;
|
||||
switch (_that) {
|
||||
case SmsInitial():
|
||||
return initial();case SmsLoading():
|
||||
return loading();case SmsLoaded():
|
||||
return loaded();case SmsPermissionDenied():
|
||||
return permissionDenied();case SmsError():
|
||||
return error(_that.message);case SmsTransactionLoading():
|
||||
return transactionLoading();case SmsTransactionError():
|
||||
return transactionError(_that.message);case SmsTransactionCreated():
|
||||
return transactionCreated();case SmsTransactionNotification():
|
||||
return transactionNotification(_that.message,_that.isSuccess);case _:
|
||||
return error(_that.message);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
@@ -200,18 +180,14 @@ return transactionNotification(_that.message,_that.isSuccess);case _:
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function()? initial,TResult? Function()? loading,TResult? Function()? loaded,TResult? Function()? permissionDenied,TResult? Function( String message)? error,TResult? Function()? transactionLoading,TResult? Function( String message)? transactionError,TResult? Function()? transactionCreated,TResult? Function( String message, bool isSuccess)? transactionNotification,}) {final _that = this;
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function()? initial,TResult? Function()? loading,TResult? Function()? loaded,TResult? Function()? permissionDenied,TResult? Function( String message)? error,}) {final _that = this;
|
||||
switch (_that) {
|
||||
case SmsInitial() when initial != null:
|
||||
return initial();case SmsLoading() when loading != null:
|
||||
return loading();case SmsLoaded() when loaded != null:
|
||||
return loaded();case SmsPermissionDenied() when permissionDenied != null:
|
||||
return permissionDenied();case SmsError() when error != null:
|
||||
return error(_that.message);case SmsTransactionLoading() when transactionLoading != null:
|
||||
return transactionLoading();case SmsTransactionError() when transactionError != null:
|
||||
return transactionError(_that.message);case SmsTransactionCreated() when transactionCreated != null:
|
||||
return transactionCreated();case SmsTransactionNotification() when transactionNotification != null:
|
||||
return transactionNotification(_that.message,_that.isSuccess);case _:
|
||||
return error(_that.message);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
@@ -411,204 +387,6 @@ as String,
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class SmsTransactionLoading implements SmsState {
|
||||
const SmsTransactionLoading();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SmsTransactionLoading);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SmsState.transactionLoading()';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class SmsTransactionError implements SmsState {
|
||||
const SmsTransactionError(this.message);
|
||||
|
||||
|
||||
final String message;
|
||||
|
||||
/// Create a copy of SmsState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$SmsTransactionErrorCopyWith<SmsTransactionError> get copyWith => _$SmsTransactionErrorCopyWithImpl<SmsTransactionError>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SmsTransactionError&&(identical(other.message, message) || other.message == message));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,message);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SmsState.transactionError(message: $message)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $SmsTransactionErrorCopyWith<$Res> implements $SmsStateCopyWith<$Res> {
|
||||
factory $SmsTransactionErrorCopyWith(SmsTransactionError value, $Res Function(SmsTransactionError) _then) = _$SmsTransactionErrorCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String message
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$SmsTransactionErrorCopyWithImpl<$Res>
|
||||
implements $SmsTransactionErrorCopyWith<$Res> {
|
||||
_$SmsTransactionErrorCopyWithImpl(this._self, this._then);
|
||||
|
||||
final SmsTransactionError _self;
|
||||
final $Res Function(SmsTransactionError) _then;
|
||||
|
||||
/// Create a copy of SmsState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') $Res call({Object? message = null,}) {
|
||||
return _then(SmsTransactionError(
|
||||
null == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class SmsTransactionCreated implements SmsState {
|
||||
const SmsTransactionCreated();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SmsTransactionCreated);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SmsState.transactionCreated()';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class SmsTransactionNotification implements SmsState {
|
||||
const SmsTransactionNotification(this.message, {this.isSuccess = true});
|
||||
|
||||
|
||||
final String message;
|
||||
@JsonKey() final bool isSuccess;
|
||||
|
||||
/// Create a copy of SmsState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$SmsTransactionNotificationCopyWith<SmsTransactionNotification> get copyWith => _$SmsTransactionNotificationCopyWithImpl<SmsTransactionNotification>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SmsTransactionNotification&&(identical(other.message, message) || other.message == message)&&(identical(other.isSuccess, isSuccess) || other.isSuccess == isSuccess));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,message,isSuccess);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SmsState.transactionNotification(message: $message, isSuccess: $isSuccess)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $SmsTransactionNotificationCopyWith<$Res> implements $SmsStateCopyWith<$Res> {
|
||||
factory $SmsTransactionNotificationCopyWith(SmsTransactionNotification value, $Res Function(SmsTransactionNotification) _then) = _$SmsTransactionNotificationCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String message, bool isSuccess
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$SmsTransactionNotificationCopyWithImpl<$Res>
|
||||
implements $SmsTransactionNotificationCopyWith<$Res> {
|
||||
_$SmsTransactionNotificationCopyWithImpl(this._self, this._then);
|
||||
|
||||
final SmsTransactionNotification _self;
|
||||
final $Res Function(SmsTransactionNotification) _then;
|
||||
|
||||
/// Create a copy of SmsState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') $Res call({Object? message = null,Object? isSuccess = null,}) {
|
||||
return _then(SmsTransactionNotification(
|
||||
null == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
|
||||
as String,isSuccess: null == isSuccess ? _self.isSuccess : isSuccess // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
import 'package:budget_app/logic/sms/sms_transaction_state.dart';
|
||||
import 'package:budget_app/models/sms_message.dart';
|
||||
import 'package:budget_app/data/repositories/interfaces/isms_handler_repository.dart';
|
||||
import 'package:budget_app/logic/transaction/transaction_bloc.dart';
|
||||
import 'package:budget_app/models/sms_handler_settings.dart';
|
||||
import 'package:budget_app/services/custom_sms_functions.dart';
|
||||
import 'package:budget_app/data/repositories/interfaces/isms_message_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
/// Cubit для обработки транзакций из SMS сообщений
|
||||
class SmsTransactionCubit extends Cubit<SmsTransactionState> {
|
||||
final ISmsHandlerRepository _smsHandlerRepository;
|
||||
final TransactionBloc _transactionBloc;
|
||||
final ISmsMessageRepository _smsRepository;
|
||||
final Logger _logger = Logger();
|
||||
|
||||
SmsTransactionCubit(
|
||||
this._smsHandlerRepository,
|
||||
this._transactionBloc,
|
||||
this._smsRepository,
|
||||
) : super(const SmsTransactionInitial());
|
||||
|
||||
/// Создает транзакцию из SMS сообщения
|
||||
Future<void> createTransactionFromSms(
|
||||
SmsMessage message,
|
||||
Function(SmsMessage) onMessageUpdated,
|
||||
) async {
|
||||
emit(const SmsTransactionProcessing());
|
||||
|
||||
try {
|
||||
final settings = await _smsHandlerRepository.getSmsHandlerSettings();
|
||||
final rule = settings?.rulesBySender[message.sender];
|
||||
|
||||
if (rule != null &&
|
||||
rule.type == SmsProcessingType.customFunction &&
|
||||
rule.customFunctionId != null) {
|
||||
final processingFunction = CustomSmsFunctions.getFunctionById(
|
||||
rule.customFunctionId!,
|
||||
);
|
||||
|
||||
if (processingFunction != null) {
|
||||
final transaction = await processingFunction(message.body ?? '');
|
||||
|
||||
if (transaction != null) {
|
||||
_transactionBloc.add(AddTransaction(transaction: transaction));
|
||||
|
||||
_logger.i('Creating transaction from SMS: ${message.id}');
|
||||
_logger.d('Transaction details: ${transaction.toMap()}');
|
||||
|
||||
// Обновляем сообщение с ссылкой на транзакцию и статусом "processed"
|
||||
final updatedMessage = message.copyWith(
|
||||
transactionId: transaction.id,
|
||||
processingStatus: SmsProcessingStatus.processed,
|
||||
);
|
||||
|
||||
await _smsRepository.update(updatedMessage);
|
||||
onMessageUpdated(updatedMessage);
|
||||
|
||||
_logger.i('Transaction created successfully for SMS: ${message.id}');
|
||||
emit(const SmsTransactionCreated());
|
||||
} else {
|
||||
// Обновляем статус сообщения на "error" при неудачном создании транзакции
|
||||
final updatedMessage = message.copyWith(
|
||||
processingStatus: SmsProcessingStatus.error,
|
||||
errorMessage: 'Не удалось создать транзакцию',
|
||||
);
|
||||
await _smsRepository.update(updatedMessage);
|
||||
onMessageUpdated(updatedMessage);
|
||||
emit(const SmsTransactionError('Не удалось создать транзакцию'));
|
||||
}
|
||||
} else {
|
||||
// Обновляем статус сообщения на "error" при отсутствии функции обработки
|
||||
final updatedMessage = message.copyWith(
|
||||
processingStatus: SmsProcessingStatus.error,
|
||||
errorMessage: 'Функция обработки не найдена для id: ${rule.customFunctionId}',
|
||||
);
|
||||
await _smsRepository.update(updatedMessage);
|
||||
onMessageUpdated(updatedMessage);
|
||||
emit(SmsTransactionError(
|
||||
'Функция обработки не найдена для id: ${rule.customFunctionId}',
|
||||
));
|
||||
}
|
||||
} else {
|
||||
// Обновляем статус сообщения на "notRequired" при отсутствии правила
|
||||
final updatedMessage = message.copyWith(
|
||||
processingStatus: SmsProcessingStatus.notRequired,
|
||||
errorMessage: 'Правило для создания транзакции не найдено',
|
||||
);
|
||||
await _smsRepository.update(updatedMessage);
|
||||
onMessageUpdated(updatedMessage);
|
||||
emit(const SmsTransactionError('Правило для создания транзакции не найдено'));
|
||||
}
|
||||
} catch (e) {
|
||||
// Обновляем статус сообщения на "error" при любой ошибке
|
||||
final updatedMessage = message.copyWith(
|
||||
processingStatus: SmsProcessingStatus.error,
|
||||
errorMessage: 'Ошибка создания транзакции: $e',
|
||||
);
|
||||
await _smsRepository.update(updatedMessage);
|
||||
onMessageUpdated(updatedMessage);
|
||||
emit(SmsTransactionError('Ошибка создания транзакции: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Обрабатывает SMS сообщение (wrapper для совместимости)
|
||||
Future<void> processMessage(
|
||||
SmsMessage message,
|
||||
Function(SmsMessage) onMessageUpdated,
|
||||
) async {
|
||||
await createTransactionFromSms(message, onMessageUpdated);
|
||||
}
|
||||
|
||||
/// Сбрасывает состояние в начальное
|
||||
void resetState() {
|
||||
emit(const SmsTransactionInitial());
|
||||
}
|
||||
|
||||
/// Отмечает уведомление как показанное
|
||||
void markNotificationAsShown() {
|
||||
final currentState = state;
|
||||
if (currentState is SmsTransactionCreated && !currentState.wasShown) {
|
||||
emit(SmsTransactionCreated(wasShown: true));
|
||||
} else if (currentState is SmsTransactionError && !currentState.wasShown) {
|
||||
emit(SmsTransactionError(currentState.message, wasShown: true));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
abstract class SmsTransactionState extends Equatable {
|
||||
const SmsTransactionState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class SmsTransactionInitial extends SmsTransactionState {
|
||||
const SmsTransactionInitial();
|
||||
}
|
||||
|
||||
class SmsTransactionProcessing extends SmsTransactionState {
|
||||
const SmsTransactionProcessing();
|
||||
}
|
||||
|
||||
class SmsTransactionCreated extends SmsTransactionState {
|
||||
final bool wasShown;
|
||||
|
||||
const SmsTransactionCreated({this.wasShown = false});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [wasShown];
|
||||
}
|
||||
|
||||
class SmsTransactionError extends SmsTransactionState {
|
||||
final String message;
|
||||
final bool wasShown;
|
||||
|
||||
const SmsTransactionError(this.message, {this.wasShown = false});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message, wasShown];
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:hive_ce/hive.dart';
|
||||
|
||||
import '/utils/id_generator.dart';
|
||||
@@ -17,7 +18,7 @@ enum SmsProcessingStatus {
|
||||
}
|
||||
|
||||
@HiveType(typeId: 1004)
|
||||
class SmsMessage extends HiveObject {
|
||||
class SmsMessage extends HiveObject with EquatableMixin {
|
||||
@HiveField(0)
|
||||
final String id;
|
||||
|
||||
@@ -70,6 +71,17 @@ class SmsMessage extends HiveObject {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
body,
|
||||
sender,
|
||||
date,
|
||||
transactionId,
|
||||
processingStatus,
|
||||
errorMessage,
|
||||
];
|
||||
|
||||
/// Преобразует объект SmsMessage в Map
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
|
||||
+78
-78
@@ -1,5 +1,7 @@
|
||||
import 'package:budget_app/injection_container.dart' as di;
|
||||
import 'package:budget_app/logic/sms/sms_settings_cubit.dart';
|
||||
import 'package:budget_app/logic/sms/sms_transaction_cubit.dart';
|
||||
import 'package:budget_app/logic/sms/sms_transaction_state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:budget_app/logic/sms/sms_cubit.dart';
|
||||
@@ -47,6 +49,9 @@ class _SmsPageState extends State<SmsPage> {
|
||||
BlocProvider(
|
||||
create: (context) => SmsFilterCubit(context.read<SmsCubit>()),
|
||||
),
|
||||
BlocProvider.value(
|
||||
value: di.getIt<SmsTransactionCubit>(),
|
||||
),
|
||||
],
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
@@ -129,87 +134,82 @@ class _SmsPageState extends State<SmsPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
body: BlocBuilder<SmsCubit, SmsState>(
|
||||
builder: (context, state) {
|
||||
return state.when(
|
||||
initial: () {
|
||||
context.read<SmsCubit>().loadSmsMessages();
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
loaded: () {
|
||||
final cubit = context.read<SmsCubit>();
|
||||
return BlocProvider(
|
||||
create: (context) => di.getIt<SmsSettingsCubit>(),
|
||||
child: NotificationListener<ScrollNotification>(
|
||||
onNotification: (notification) {
|
||||
if (notification is ScrollEndNotification &&
|
||||
notification.metrics.pixels ==
|
||||
notification.metrics.maxScrollExtent &&
|
||||
cubit.hasMore) {
|
||||
cubit.loadMoreMessages();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => SmsMessageWidget(
|
||||
message: cubit.messages[index],
|
||||
key: ValueKey(cubit.messages[index].id),
|
||||
),
|
||||
childCount: cubit.messages.length,
|
||||
),
|
||||
),
|
||||
if (cubit.hasMore)
|
||||
const SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
permissionDenied: () => Center(
|
||||
child: Text(AppLocalizations.of(context)!.smsPermissionDenied),
|
||||
),
|
||||
error: (message) => Center(
|
||||
child: Text(message),
|
||||
),
|
||||
transactionLoading: () => const Center(child: CircularProgressIndicator()),
|
||||
transactionError: (message) => Center(
|
||||
child: Text(message),
|
||||
),
|
||||
transactionCreated: () => Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.check_circle, color: Colors.green, size: 64),
|
||||
const SizedBox(height: 16),
|
||||
Text(AppLocalizations.of(context)!.transactionCreated),
|
||||
],
|
||||
body: BlocListener<SmsTransactionCubit, SmsTransactionState>(
|
||||
listener: (context, state) {
|
||||
if (state is SmsTransactionError && !state.wasShown) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.message),
|
||||
backgroundColor: Colors.red,
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
),
|
||||
transactionNotification: (message, isSuccess) => Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
isSuccess ? Icons.check_circle : Icons.error,
|
||||
color: isSuccess ? Colors.green : Colors.red,
|
||||
size: 64,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(message),
|
||||
],
|
||||
);
|
||||
context.read<SmsTransactionCubit>().markNotificationAsShown();
|
||||
} else if (state is SmsTransactionCreated && !state.wasShown) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(AppLocalizations.of(context)!.transactionCreated),
|
||||
backgroundColor: Colors.green,
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
context.read<SmsTransactionCubit>().markNotificationAsShown();
|
||||
}
|
||||
},
|
||||
child: BlocBuilder<SmsCubit, SmsState>(
|
||||
builder: (context, state) {
|
||||
return state.when(
|
||||
initial: () {
|
||||
context.read<SmsCubit>().loadSmsMessages();
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
loaded: () {
|
||||
final cubit = context.read<SmsCubit>();
|
||||
return BlocProvider(
|
||||
create: (context) => di.getIt<SmsSettingsCubit>(),
|
||||
child: NotificationListener<ScrollNotification>(
|
||||
onNotification: (notification) {
|
||||
if (notification is ScrollEndNotification &&
|
||||
notification.metrics.pixels ==
|
||||
notification.metrics.maxScrollExtent &&
|
||||
cubit.hasMore) {
|
||||
cubit.loadMoreMessages();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => SmsMessageWidget(
|
||||
message: cubit.messages[index],
|
||||
key: ValueKey(cubit.messages[index].id),
|
||||
),
|
||||
childCount: cubit.messages.length,
|
||||
),
|
||||
),
|
||||
if (cubit.hasMore)
|
||||
const SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
permissionDenied: () => Center(
|
||||
child: Text(AppLocalizations.of(context)!.smsPermissionDenied),
|
||||
),
|
||||
error: (message) => Center(
|
||||
child: Text(message),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2,6 +2,8 @@ import 'package:budget_app/injection_container.dart' as di;
|
||||
import 'package:budget_app/l10n/app_localizations.dart';
|
||||
import 'package:budget_app/logic/sms/sms_cubit.dart';
|
||||
import 'package:budget_app/logic/sms/sms_state.dart';
|
||||
import 'package:budget_app/logic/sms/sms_transaction_cubit.dart';
|
||||
import 'package:budget_app/logic/sms/sms_transaction_state.dart';
|
||||
import 'package:budget_app/models/sms_message.dart';
|
||||
import 'package:budget_app/pages/sms/widgets/sms_settings_dialog.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -44,7 +46,15 @@ class SmsMessageWidget extends StatelessWidget {
|
||||
PopupMenuItem(
|
||||
child: Text(AppLocalizations.of(context)!.createTransaction),
|
||||
onTap: () {
|
||||
context.read<SmsCubit>().createTransactionFromSms(message);
|
||||
final smsCubit = context.read<SmsCubit>();
|
||||
final currentMessage = smsCubit.messages.firstWhere(
|
||||
(m) => m.id == message.id,
|
||||
orElse: () => message,
|
||||
);
|
||||
context.read<SmsTransactionCubit>().createTransactionFromSms(
|
||||
currentMessage,
|
||||
smsCubit.onMessageUpdated,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -53,92 +63,126 @@ class SmsMessageWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: di.getIt<SmsCubit>(),
|
||||
child: BlocConsumer<SmsCubit, SmsState>(
|
||||
listener: (context, state) {
|
||||
if (state is SmsTransactionCreated) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(context)!.transactionCreatedSuccessfully),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
} else if (state is SmsTransactionError) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.message),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state is SmsLoading || state is SmsTransactionLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
return GestureDetector(
|
||||
onTapDown: (details) => _showPopupMenu(context, details),
|
||||
child: Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.person_outline, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
message.sender ??
|
||||
AppLocalizations.of(context)!.unknownSender,
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
return BlocBuilder<SmsCubit, SmsState>(
|
||||
builder: (context, smsState) {
|
||||
final smsCubit = context.read<SmsCubit>();
|
||||
final currentMessage = smsCubit.messages.firstWhere(
|
||||
(m) => m.id == message.id,
|
||||
orElse: () => message,
|
||||
);
|
||||
|
||||
return BlocBuilder<SmsTransactionCubit, SmsTransactionState>(
|
||||
builder: (context, transactionState) {
|
||||
final isProcessing = transactionState is SmsTransactionProcessing;
|
||||
return GestureDetector(
|
||||
onTapDown: (details) => _showPopupMenu(context, details),
|
||||
child: Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.person_outline, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
currentMessage.sender ??
|
||||
AppLocalizations.of(context)!.unknownSender,
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
_formatDate(context, message.date),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
message.body ?? '',
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
message.transactionId != null
|
||||
? Icons.check_circle_outline
|
||||
: Icons.error_outline,
|
||||
size: 16,
|
||||
color: message.transactionId != null
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
message.transactionId != null
|
||||
? AppLocalizations.of(context)!.smsProcessed
|
||||
: AppLocalizations.of(context)!.smsNotProcessed,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 16),
|
||||
if (isProcessing)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 8),
|
||||
child: SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
_formatDate(context, currentMessage.date),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
currentMessage.body ?? '',
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
_getStatusIcon(currentMessage),
|
||||
size: 16,
|
||||
color: _getStatusColor(context, currentMessage),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
_getStatusText(context, currentMessage),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
IconData _getStatusIcon(SmsMessage msg) {
|
||||
switch (msg.processingStatus) {
|
||||
case SmsProcessingStatus.processed:
|
||||
return Icons.check_circle_outline;
|
||||
case SmsProcessingStatus.error:
|
||||
return Icons.error_outline;
|
||||
case SmsProcessingStatus.notRequired:
|
||||
return Icons.info_outline;
|
||||
case SmsProcessingStatus.pending:
|
||||
default:
|
||||
return Icons.pending_outlined;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getStatusColor(BuildContext context, SmsMessage msg) {
|
||||
switch (msg.processingStatus) {
|
||||
case SmsProcessingStatus.processed:
|
||||
return Theme.of(context).colorScheme.primary;
|
||||
case SmsProcessingStatus.error:
|
||||
return Theme.of(context).colorScheme.error;
|
||||
case SmsProcessingStatus.notRequired:
|
||||
return Theme.of(context).colorScheme.secondary;
|
||||
case SmsProcessingStatus.pending:
|
||||
default:
|
||||
return Theme.of(context).colorScheme.outline;
|
||||
}
|
||||
}
|
||||
|
||||
String _getStatusText(BuildContext context, SmsMessage msg) {
|
||||
switch (msg.processingStatus) {
|
||||
case SmsProcessingStatus.processed:
|
||||
return AppLocalizations.of(context)!.smsStatusProcessed;
|
||||
case SmsProcessingStatus.error:
|
||||
return msg.errorMessage ?? AppLocalizations.of(context)!.smsStatusError;
|
||||
case SmsProcessingStatus.notRequired:
|
||||
return AppLocalizations.of(context)!.smsStatusNotRequired;
|
||||
case SmsProcessingStatus.pending:
|
||||
default:
|
||||
return AppLocalizations.of(context)!.smsStatusPending;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user