Skip to content
Snippets Groups Projects
Commit 528b2ca7 authored by vjrj's avatar vjrj
Browse files

More work with pending and failed transactions

parent 83055d58
No related branches found
No related tags found
No related merge requests found
...@@ -93,7 +93,7 @@ ...@@ -93,7 +93,7 @@
"transaction_receiving": "Receiving", "transaction_receiving": "Receiving",
"transaction_sent": "Sent", "transaction_sent": "Sent",
"transaction_received": "Received", "transaction_received": "Received",
"transaction_missing": "Payment failed", "transaction_failed": "Payment failed",
"valid_comment": "The comment cannot have accents or commas", "valid_comment": "The comment cannot have accents or commas",
"search_limitation": "The search term must have at least 3 characters", "search_limitation": "The search term must have at least 3 characters",
"faq_title": "Frequently Asked Questions", "faq_title": "Frequently Asked Questions",
......
...@@ -93,7 +93,7 @@ ...@@ -93,7 +93,7 @@
"transaction_receiving": "Recibiendo", "transaction_receiving": "Recibiendo",
"transaction_sent": "Enviado", "transaction_sent": "Enviado",
"transaction_received": "Recibido", "transaction_received": "Recibido",
"transaction_missing": "Pago fallido", "transaction_fallido": "Pago fallido",
"valid_comment": "El comentario no puede tener tildes o comas", "valid_comment": "El comentario no puede tener tildes o comas",
"search_limitation": "La búsqueda debe tener al menos 3 caracteres", "search_limitation": "La búsqueda debe tener al menos 3 caracteres",
"faq_title": "Preguntas frecuentes", "faq_title": "Preguntas frecuentes",
......
...@@ -139,5 +139,5 @@ const _$TransactionTypeEnumMap = { ...@@ -139,5 +139,5 @@ const _$TransactionTypeEnumMap = {
TransactionType.receiving: 'receiving', TransactionType.receiving: 'receiving',
TransactionType.sent: 'sent', TransactionType.sent: 'sent',
TransactionType.pending: 'pending', TransactionType.pending: 'pending',
TransactionType.missing: 'missing', TransactionType.failed: 'failed',
}; };
import 'dart:collection';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:ginkgo/data/models/transactions_bloc.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
...@@ -8,6 +11,7 @@ import '../../g1/g1_helper.dart'; ...@@ -8,6 +11,7 @@ import '../../g1/g1_helper.dart';
import '../../shared_prefs.dart'; import '../../shared_prefs.dart';
import '../../ui/logger.dart'; import '../../ui/logger.dart';
import '../../ui/notification_controller.dart'; import '../../ui/notification_controller.dart';
import '../../ui/pay_helper.dart';
import '../../ui/ui_helpers.dart'; import '../../ui/ui_helpers.dart';
import 'contact.dart'; import 'contact.dart';
import 'node.dart'; import 'node.dart';
...@@ -20,10 +24,10 @@ import 'transaction_type.dart'; ...@@ -20,10 +24,10 @@ import 'transaction_type.dart';
class TransactionCubit extends HydratedCubit<TransactionState> { class TransactionCubit extends HydratedCubit<TransactionState> {
TransactionCubit() TransactionCubit()
: super(TransactionState( : super(TransactionState(
transactions: const <Transaction>[], transactions: const <Transaction>[],
pendingTransactions: const <Transaction>[], pendingTransactions: const <Transaction>[],
balance: 0, balance: 0,
lastChecked: DateTime.now())); lastChecked: DateTime.now()));
@override @override
String get storagePrefix => String get storagePrefix =>
...@@ -32,16 +36,16 @@ class TransactionCubit extends HydratedCubit<TransactionState> { ...@@ -32,16 +36,16 @@ class TransactionCubit extends HydratedCubit<TransactionState> {
void addPendingTransaction(Transaction pendingTransaction) { void addPendingTransaction(Transaction pendingTransaction) {
final TransactionState currentState = state; final TransactionState currentState = state;
final List<Transaction> newPendingTransactions = final List<Transaction> newPendingTransactions =
List<Transaction>.of(currentState.pendingTransactions) List<Transaction>.of(currentState.pendingTransactions)
..add(pendingTransaction); ..add(pendingTransaction);
emit(currentState.copyWith(pendingTransactions: newPendingTransactions)); emit(currentState.copyWith(pendingTransactions: newPendingTransactions));
} }
void removePendingTransaction(Transaction pendingTransaction) { void removePendingTransaction(Transaction pendingTransaction) {
final TransactionState currentState = state; final TransactionState currentState = state;
final List<Transaction> newPendingTransactions = final List<Transaction> newPendingTransactions =
List<Transaction>.of(currentState.pendingTransactions) List<Transaction>.of(currentState.pendingTransactions)
..remove(pendingTransaction); ..remove(pendingTransaction);
emit(currentState.copyWith(pendingTransactions: newPendingTransactions)); emit(currentState.copyWith(pendingTransactions: newPendingTransactions));
} }
...@@ -76,70 +80,109 @@ class TransactionCubit extends HydratedCubit<TransactionState> { ...@@ -76,70 +80,109 @@ class TransactionCubit extends HydratedCubit<TransactionState> {
success = true; success = true;
logger( logger(
'Last received notification: ${newState.latestReceivedNotification.toIso8601String()})}'); 'Last received notification: ${newState.latestReceivedNotification
.toIso8601String()})}');
logger( logger(
'Last sent notification: ${newState.latestSentNotification.toIso8601String()})}'); 'Last sent notification: ${newState.latestSentNotification
.toIso8601String()})}');
// Check pending transactions // Check pending transactions
if (cursor == null && state.pendingTransactions.isNotEmpty) { if (cursor == null) {
// First page, so let's check pending transactions // First page, so let's check pending transactions
final LinkedHashSet<Transaction> newPendingTransactions =
final List<Transaction> newPendingTransactions = <Transaction>[]; LinkedHashSet<Transaction>();
final List<Transaction> newTransactions = <Transaction>[]; final List<Transaction> newTransactions = <Transaction>[];
for (final Transaction pend in state.pendingTransactions) { // Index transactions by key
bool pendFound = false; final Map<String, Transaction> txMap = <String, Transaction>{};
for (final Transaction t in newState.transactions) { final Map<String, Transaction> pendingMap = <String, Transaction>{};
if (t.to.pubKey == pend.to.pubKey &&
t.comment == pend.comment && // or maybe it doesn't merit the effort
t.amount == pend.amount && for (final Transaction t in newState.transactions) {
(t.type == TransactionType.sending || txMap[getTxKey(t)] = t;
t.type == TransactionType.sent) && }
areDatesClose(t.time, pend.time, const Duration(minutes: 90))) { // Get a range of tx in 1h
// Found a match TransactionsBloc().lastTx().forEach((Transaction t) {
pendFound = true; txMap[getTxKey(t)] = t;
if (t.type == TransactionType.sent) { });
for (final Transaction t in newState.pendingTransactions) {
pendingMap[getTxKey(t)] = t;
}
// Adjust pending transactions
for (final Transaction pend in newState.pendingTransactions) {
if (txMap[getTxKey(pend)] != null) {
// Found a match
// VER SI SENT o que
final Transaction t = txMap[getTxKey(pend)]!;
if (t.type == TransactionType.sent) {
loggerDev(
'@@@@@ Found a sent match for pending transaction ${pend
.toStringSmall(myPubKey)}');
// Add later the tx, but don't add the pending
} else {
if (t.type == TransactionType.sending) {
loggerDev( loggerDev(
'@@@@@ Found a sent match for pending transaction ${pend.toStringSmall(myPubKey)}'); '@@@@@ Found a sending match for pending transaction ${pend
newTransactions.add(t); .toStringSmall(myPubKey)}');
// and remove it from pending // Re-add as pending
// The tx will not be add as sending (as some nodes will show it and others will not,
// we use better the pending)
newPendingTransactions.add(pend);
} else { } else {
if (t.type == TransactionType.sending) { loggerDev(
loggerDev( '@@@@@ WARNING: Found a ${t
'@@@@@ Found a sending match for pending transaction ${pend.toStringSmall(myPubKey)}'); .type} match for pending transaction ${pend
newPendingTransactions .toStringSmall(myPubKey)}');
.add(pend.copyWith(type: TransactionType.pending));
} else {
loggerDev(
'@@@@@ WARNING: Found a ${t.type} match for pending transaction ${pend.toStringSmall(myPubKey)}');
}
} }
} else {
// No match, keep it
/* loggerDev(
'@@@@@ Not found a sending/sent match for pending transaction comparing with $t <-> ${pend.toStringSmall(myPubKey)}'); */
newTransactions.add(t);
} }
} } else {
if (!pendFound) { // Not found a match
// Not found, keep it
if (areDatesClose( if (areDatesClose(
DateTime.now(), pend.time, const Duration(minutes: 60))) { DateTime.now(), pend.time, paymentTimeRange)) {
// Old pending transaction, warn user
loggerDev( loggerDev(
'@@@@@ Warn user: Not found an old pending transaction comparing with ${pend.toStringSmall(myPubKey)}'); '@@@@@ Not found yet pending transaction ${pend.toStringSmall(
myPubKey)}');
newPendingTransactions.add(pend); newPendingTransactions.add(pend);
} else { } else {
// Old pending transaction, warn user
loggerDev( loggerDev(
'@@@@@ Not found an old pending transaction comparing with ${pend.toStringSmall(myPubKey)}'); '@@@@@ Warn user: Not found an old pending transaction ${pend
.toStringSmall(myPubKey)}');
// Add it but with missing type
newPendingTransactions newPendingTransactions
.add(pend.copyWith(type: TransactionType.missing)); .add(pend.copyWith(type: TransactionType.failed));
} }
} }
} }
for (final Transaction tx in newState.transactions) {
if (pendingMap[getTxKey(tx)] != null &&
(tx.type == TransactionType.sending ||
tx.type == TransactionType.sent)) {
// Found a match
if (tx.type == TransactionType.sent) {
// Ok add it, but not as pending
newTransactions.add(tx);
} else {
// It's sending so should be added before as pending
}
} else {
// Does not match
if (tx.type == TransactionType.sending) {
// Not found, maybe we are in other client, so add as pending
newPendingTransactions
.add(tx.copyWith(type: TransactionType.pending));
} else {
// the rest
newTransactions.add(tx);
}
}
}
newState = newState.copyWith( newState = newState.copyWith(
transactions: newTransactions, transactions: newTransactions,
pendingTransactions: newPendingTransactions); pendingTransactions: newPendingTransactions.toList());
} }
if (inDevelopment) { if (inDevelopment) {
...@@ -178,6 +221,8 @@ class TransactionCubit extends HydratedCubit<TransactionState> { ...@@ -178,6 +221,8 @@ class TransactionCubit extends HydratedCubit<TransactionState> {
return <Transaction>[]; return <Transaction>[];
} }
String getTxKey(Transaction t) => '${t.to.pubKey}-${t.comment}-${t.amount}';
@override @override
TransactionState fromJson(Map<String, dynamic> json) => TransactionState fromJson(Map<String, dynamic> json) =>
TransactionState.fromJson(json); TransactionState.fromJson(json);
...@@ -207,4 +252,11 @@ class TransactionCubit extends HydratedCubit<TransactionState> { ...@@ -207,4 +252,11 @@ class TransactionCubit extends HydratedCubit<TransactionState> {
} }
emit(currentState.copyWith(pendingTransactions: newPendingTransactions)); emit(currentState.copyWith(pendingTransactions: newPendingTransactions));
} }
void insertPendingTransaction(Transaction tx) {
final TransactionState currentState = state;
final List<Transaction> newPendingTransactions = state.pendingTransactions;
newPendingTransactions.insert(0, tx);
emit(currentState.copyWith(pendingTransactions: newPendingTransactions));
}
} }
enum TransactionType { sending, received, receiving, sent, pending, missing } enum TransactionType { sending, received, receiving, sent, pending, failed }
bool isProcessing(TransactionType type) => bool isProcessing(TransactionType type) =>
type == TransactionType.sending || type == TransactionType.sending ||
...@@ -6,4 +6,4 @@ bool isProcessing(TransactionType type) => ...@@ -6,4 +6,4 @@ bool isProcessing(TransactionType type) =>
type == TransactionType.pending; type == TransactionType.pending;
bool isPending(TransactionType type) => bool isPending(TransactionType type) =>
type == TransactionType.pending || type == TransactionType.missing; type == TransactionType.pending || type == TransactionType.failed;
...@@ -2,6 +2,8 @@ import 'dart:async'; ...@@ -2,6 +2,8 @@ import 'dart:async';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import '../../g1/g1_helper.dart';
import '../../ui/pay_helper.dart';
import 'node_list_cubit.dart'; import 'node_list_cubit.dart';
import 'transaction.dart'; import 'transaction.dart';
import 'transaction_cubit.dart'; import 'transaction_cubit.dart';
...@@ -46,6 +48,17 @@ class TransactionsBloc { ...@@ -46,6 +48,17 @@ class TransactionsBloc {
Sink<String?> get onSearchInputChangedSink => Sink<String?> get onSearchInputChangedSink =>
_onSearchInputChangedSubject.sink; _onSearchInputChangedSubject.sink;
List<Transaction> lastTx() {
if (_onNewListingStateController.value.itemList != null) {
return _onNewListingStateController.value.itemList!
.where((Transaction tx) =>
areDatesClose(DateTime.now(), tx.time, paymentTimeRange))
.toList();
} else {
return <Transaction>[];
}
}
// String? get _searchInputValue => _onSearchInputChangedSubject.value; // String? get _searchInputValue => _onSearchInputChangedSubject.value;
Stream<TransactionsState> _resetSearch() async* { Stream<TransactionsState> _resetSearch() async* {
......
...@@ -40,7 +40,7 @@ class ContactsCache { ...@@ -40,7 +40,7 @@ class ContactsCache {
Future<void> addContacts(List<Contact> contacts) async { Future<void> addContacts(List<Contact> contacts) async {
for (final Contact contact in contacts) { for (final Contact contact in contacts) {
await storeContact(contact); await addContact(contact);
} }
} }
...@@ -97,7 +97,7 @@ class ContactsCache { ...@@ -97,7 +97,7 @@ class ContactsCache {
_pendingRequests[pubKey] = <Completer<Contact>>[completer]; _pendingRequests[pubKey] = <Completer<Contact>>[completer];
try { try {
cachedContact = await getProfile(pubKey); cachedContact = await getProfile(pubKey);
storeContact(cachedContact); _storeContact(cachedContact);
if (!kReleaseMode && debug) { if (!kReleaseMode && debug) {
logger('Returning non cached contact $cachedContact'); logger('Returning non cached contact $cachedContact');
} }
...@@ -136,12 +136,12 @@ class ContactsCache { ...@@ -136,12 +136,12 @@ class ContactsCache {
// Cache the merged contact // Cache the merged contact
// Cache the merged contact // Cache the merged contact
await storeContact(cachedContact); await _storeContact(cachedContact);
// logger('Added contact $cachedContact to cache'); // logger('Added contact $cachedContact to cache');
} }
Future<void> storeContact(Contact contact) async { Future<void> _storeContact(Contact contact) async {
final Box<dynamic> box = await _openBox(); final Box<dynamic> box = await _openBox();
await box.put(contact.pubKey, <String, dynamic>{ await box.put(contact.pubKey, <String, dynamic>{
'timestamp': DateTime.now().toIso8601String(), 'timestamp': DateTime.now().toIso8601String(),
......
...@@ -13,6 +13,7 @@ import '../../../data/models/transaction_cubit.dart'; ...@@ -13,6 +13,7 @@ import '../../../data/models/transaction_cubit.dart';
import '../../../data/models/transaction_type.dart'; import '../../../data/models/transaction_type.dart';
import '../../../g1/api.dart'; import '../../../g1/api.dart';
import '../../../shared_prefs.dart'; import '../../../shared_prefs.dart';
import '../data/models/bottom_nav_cubit.dart';
import 'contacts_cache.dart'; import 'contacts_cache.dart';
import 'logger.dart'; import 'logger.dart';
import 'ui_helpers.dart'; import 'ui_helpers.dart';
...@@ -35,6 +36,13 @@ Future<void> payWithRetry(BuildContext context, Contact to, double amount, ...@@ -35,6 +36,13 @@ Future<void> payWithRetry(BuildContext context, Contact to, double amount,
} else { } else {
final String response = final String response =
await pay(to: contactPubKey, comment: comment, amount: amount); await pay(to: contactPubKey, comment: comment, amount: amount);
final Transaction tx = Transaction(
type: TransactionType.pending,
from: fromContact,
to: to,
amount: -amount * 100,
comment: comment,
time: DateTime.now());
if (response == 'success') { if (response == 'success') {
paymentCubit.sent(); paymentCubit.sent();
if (!context.mounted) { if (!context.mounted) {
...@@ -43,18 +51,11 @@ Future<void> payWithRetry(BuildContext context, Contact to, double amount, ...@@ -43,18 +51,11 @@ Future<void> payWithRetry(BuildContext context, Contact to, double amount,
showTooltip( showTooltip(
context, tr('payment_successful'), tr('payment_successful_desc')); context, tr('payment_successful'), tr('payment_successful_desc'));
final Transaction tx = Transaction(
type: TransactionType.pending,
from: fromContact,
to: to,
amount: -amount * 100,
comment: comment,
time: DateTime.now());
if (!isRetry) { if (!isRetry) {
// Add here the transaction to the pending list (so we can check it the tx is confirmed) // Add here the transaction to the pending list (so we can check it the tx is confirmed)
txCubit.addPendingTransaction(tx); txCubit.addPendingTransaction(tx);
} else { } else {
// Update the tx with an update time and type // Update the previously failed tx with an update time and type pending
txCubit.addUpdatePendingTransaction(tx); txCubit.addUpdatePendingTransaction(tx);
} }
} else { } else {
...@@ -75,6 +76,9 @@ Future<void> payWithRetry(BuildContext context, Contact to, double amount, ...@@ -75,6 +76,9 @@ Future<void> payWithRetry(BuildContext context, Contact to, double amount,
// We try to translate the error, like "insufficient balance" // We try to translate the error, like "insufficient balance"
'error': tr(response) 'error': tr(response)
})); }));
txCubit
.insertPendingTransaction(tx.copyWith(type: TransactionType.failed));
context.read<BottomNavCubit>().updateIndex(3);
} }
} }
} }
...@@ -121,3 +125,5 @@ void showPayError(BuildContext context, String desc) { ...@@ -121,3 +125,5 @@ void showPayError(BuildContext context, String desc) {
// Shuffle the nodes so we can retry with other // Shuffle the nodes so we can retry with other
context.read<NodeListCubit>().shuffle(NodeType.gva, true); context.read<NodeListCubit>().shuffle(NodeType.gva, true);
} }
const Duration paymentTimeRange = Duration(minutes: 60);
...@@ -16,12 +16,11 @@ import '../../ui_helpers.dart'; ...@@ -16,12 +16,11 @@ import '../../ui_helpers.dart';
import '../third_screen/contact_form.dart'; import '../third_screen/contact_form.dart';
class TransactionListItem extends StatelessWidget { class TransactionListItem extends StatelessWidget {
const TransactionListItem( const TransactionListItem({super.key,
{super.key, required this.pubKey,
required this.pubKey, required this.transaction,
required this.transaction, required this.index,
required this.index, this.onCancel});
this.onCancel});
final String pubKey; final String pubKey;
final Transaction transaction; final Transaction transaction;
...@@ -37,14 +36,15 @@ class TransactionListItem extends StatelessWidget { ...@@ -37,14 +36,15 @@ class TransactionListItem extends StatelessWidget {
_buildTransactionItem(context, transaction)); _buildTransactionItem(context, transaction));
} }
Slidable _buildTransactionItem( Slidable _buildTransactionItem(BuildContext context,
BuildContext context, Transaction transaction) { Transaction transaction) {
IconData? icon; IconData? icon;
Color? iconColor; Color? iconColor;
String statusText; String statusText;
final String amountS = final String amountS =
'${transaction.amount < 0 ? "" : "+"}${formatKAmount(context, transaction.amount)}'; '${transaction.amount < 0 ? "" : "+"}${formatKAmount(
context, transaction.amount)}';
statusText = tr('transaction_${transaction.type.name}'); statusText = tr('transaction_${transaction.type.name}');
switch (transaction.type) { switch (transaction.type) {
case TransactionType.pending: case TransactionType.pending:
...@@ -59,7 +59,7 @@ class TransactionListItem extends StatelessWidget { ...@@ -59,7 +59,7 @@ class TransactionListItem extends StatelessWidget {
icon = Icons.flight_land; icon = Icons.flight_land;
iconColor = Colors.grey; iconColor = Colors.grey;
break; break;
case TransactionType.missing: case TransactionType.failed:
icon = Icons.warning_amber_rounded; icon = Icons.warning_amber_rounded;
iconColor = Colors.red; iconColor = Colors.red;
break; break;
...@@ -73,11 +73,11 @@ class TransactionListItem extends StatelessWidget { ...@@ -73,11 +73,11 @@ class TransactionListItem extends StatelessWidget {
final ContactsCubit contactsCubit = context.read<ContactsCubit>(); final ContactsCubit contactsCubit = context.read<ContactsCubit>();
return Slidable( return Slidable(
// Specify a key if the Slidable is dismissible. // Specify a key if the Slidable is dismissible.
key: ValueKey<int>(index), key: ValueKey<int>(index),
// The end action pane is the one at the right or the bottom side. // The end action pane is the one at the right or the bottom side.
startActionPane: startActionPane:
ActionPane(motion: const ScrollMotion(), children: <SlidableAction>[ ActionPane(motion: const ScrollMotion(), children: <SlidableAction>[
if (isPending(transaction.type)) if (isPending(transaction.type))
SlidableAction( SlidableAction(
onPressed: (BuildContext c) { onPressed: (BuildContext c) {
...@@ -101,13 +101,15 @@ class TransactionListItem extends StatelessWidget { ...@@ -101,13 +101,15 @@ class TransactionListItem extends StatelessWidget {
endActionPane: ActionPane( endActionPane: ActionPane(
motion: const ScrollMotion(), motion: const ScrollMotion(),
children: <SlidableAction>[ children: <SlidableAction>[
if (isPending(transaction.type)) if (transaction.type == TransactionType.failed)
SlidableAction( SlidableAction(
onPressed: (BuildContext c) async { onPressed: (BuildContext c) async {
await payWithRetry(context, transaction.to, await payWithRetry(context, transaction.to,
transaction.amount, transaction.comment, true); transaction.amount, transaction.comment, true);
}, },
backgroundColor: Theme.of(context).primaryColorDark, backgroundColor: Theme
.of(context)
.primaryColorDark,
foregroundColor: Colors.white, foregroundColor: Colors.white,
icon: Icons.replay, icon: Icons.replay,
label: tr('retry_payment'), label: tr('retry_payment'),
...@@ -136,7 +138,9 @@ class TransactionListItem extends StatelessWidget { ...@@ -136,7 +138,9 @@ class TransactionListItem extends StatelessWidget {
}, },
); );
}, },
backgroundColor: Theme.of(context).primaryColor, backgroundColor: Theme
.of(context)
.primaryColor,
foregroundColor: Colors.white, foregroundColor: Colors.white,
icon: Icons.contacts, icon: Icons.contacts,
label: tr('add_contact'), label: tr('add_contact'),
...@@ -146,11 +150,11 @@ class TransactionListItem extends StatelessWidget { ...@@ -146,11 +150,11 @@ class TransactionListItem extends StatelessWidget {
child: ListTile( child: ListTile(
leading: (icon != null) leading: (icon != null)
? Padding( ? Padding(
padding: const EdgeInsets.fromLTRB(10, 16, 0, 16), padding: const EdgeInsets.fromLTRB(10, 16, 0, 16),
child: Icon( child: Icon(
icon, icon,
color: iconColor, color: iconColor,
)) ))
: null, : null,
tileColor: tileColor(index, context), tileColor: tileColor(index, context),
title: Row( title: Row(
...@@ -176,7 +180,7 @@ class TransactionListItem extends StatelessWidget { ...@@ -176,7 +180,7 @@ class TransactionListItem extends StatelessWidget {
tr('transaction_from_to', namedArgs: <String, tr('transaction_from_to', namedArgs: <String,
String>{ String>{
'from': 'from':
humanizeContact(myPubKey, transaction.from), humanizeContact(myPubKey, transaction.from),
'to': humanizeContact(myPubKey, transaction.to) 'to': humanizeContact(myPubKey, transaction.to)
}), }),
style: const TextStyle( style: const TextStyle(
...@@ -210,7 +214,7 @@ class TransactionListItem extends StatelessWidget { ...@@ -210,7 +214,7 @@ class TransactionListItem extends StatelessWidget {
style: TextStyle( style: TextStyle(
// fontWeight: FontWeight.bold, // fontWeight: FontWeight.bold,
color: transaction.type == TransactionType.received || color: transaction.type == TransactionType.received ||
transaction.type == TransactionType.receiving transaction.type == TransactionType.receiving
? Colors.blue ? Colors.blue
: Colors.red, : Colors.red,
), ),
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment