Cw 231 display balance in send and transaction screens when long pressing and when show balance is disabled (#926)

* feat: allow reversing displayMode by long pressing on transactions page

* feat: allow reversing displayMode by long pressing on send page

* revert: revert transaction_list_item.dart but keep BalanceDisplayMode get displayMode => balanceViewModel.displayMode
This commit is contained in:
Rafael Saes 2023-06-09 19:29:34 -03:00 committed by GitHub
parent a4c279b538
commit b16cfaaff5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 396 additions and 342 deletions

View file

@ -25,107 +25,126 @@ class TransactionsPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return GestureDetector(
color: ResponsiveLayoutUtil.instance.isMobile(context) onLongPress: () => dashboardViewModel.balanceViewModel.isReversing =
? null !dashboardViewModel.balanceViewModel.isReversing,
: Theme.of(context).colorScheme.background, onLongPressUp: () => dashboardViewModel.balanceViewModel.isReversing =
padding: EdgeInsets.only(top: 24, bottom: 24), !dashboardViewModel.balanceViewModel.isReversing,
child: Column( child: Container(
children: <Widget>[ color: ResponsiveLayoutUtil.instance.isMobile(context)
HeaderRow(dashboardViewModel: dashboardViewModel), ? null
Expanded(child: Observer(builder: (_) { : Theme.of(context).colorScheme.background,
final items = dashboardViewModel.items; padding: EdgeInsets.only(top: 24, bottom: 24),
child: Column(
children: <Widget>[
HeaderRow(dashboardViewModel: dashboardViewModel),
Expanded(child: Observer(builder: (_) {
final items = dashboardViewModel.items;
return items.isNotEmpty return items.isNotEmpty
? ListView.builder( ? ListView.builder(
itemCount: items.length, itemCount: items.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = items[index]; final item = items[index];
if (item is DateSectionItem) { if (item is DateSectionItem) {
return DateSectionRaw(date: item.date); return DateSectionRaw(date: item.date);
} }
if (item is TransactionListItem) { if (item is TransactionListItem) {
final transaction = item.transaction; final transaction = item.transaction;
return Observer( return Observer(
builder: (_) => TransactionRow( builder: (_) => TransactionRow(
onTap: () => Navigator.of(context) onTap: () => Navigator.of(context).pushNamed(
.pushNamed(Routes.transactionDetails, arguments: transaction), Routes.transactionDetails,
direction: transaction.direction, arguments: transaction),
formattedDate: DateFormat('HH:mm').format(transaction.date), direction: transaction.direction,
formattedAmount: item.formattedCryptoAmount, formattedDate: DateFormat('HH:mm')
formattedFiatAmount: .format(transaction.date),
dashboardViewModel.balanceViewModel.isFiatDisabled formattedAmount: item.formattedCryptoAmount,
? '' formattedFiatAmount: dashboardViewModel
: item.formattedFiatAmount, .balanceViewModel.isFiatDisabled
isPending: transaction.isPending, ? ''
title: item.formattedTitle + item.formattedStatus)); : item.formattedFiatAmount,
} isPending: transaction.isPending,
title: item.formattedTitle +
item.formattedStatus));
}
if (item is AnonpayTransactionListItem) { if (item is AnonpayTransactionListItem) {
final transactionInfo = item.transaction; final transactionInfo = item.transaction;
return AnonpayTransactionRow( return AnonpayTransactionRow(
onTap: () => Navigator.of(context) onTap: () => Navigator.of(context).pushNamed(
.pushNamed(Routes.anonPayDetailsPage, arguments: transactionInfo), Routes.anonPayDetailsPage,
currency: transactionInfo.fiatAmount != null arguments: transactionInfo),
? transactionInfo.fiatEquiv ?? '' currency: transactionInfo.fiatAmount != null
: CryptoCurrency.fromFullName(transactionInfo.coinTo) ? transactionInfo.fiatEquiv ?? ''
.name : CryptoCurrency.fromFullName(
.toUpperCase(), transactionInfo.coinTo)
provider: transactionInfo.provider, .name
amount: transactionInfo.fiatAmount?.toString() ?? .toUpperCase(),
(transactionInfo.amountTo?.toString() ?? ''), provider: transactionInfo.provider,
createdAt: DateFormat('HH:mm').format(transactionInfo.createdAt), amount: transactionInfo.fiatAmount?.toString() ??
); (transactionInfo.amountTo?.toString() ?? ''),
} createdAt: DateFormat('HH:mm')
.format(transactionInfo.createdAt),
);
}
if (item is TradeListItem) { if (item is TradeListItem) {
final trade = item.trade; final trade = item.trade;
return Observer( return Observer(
builder: (_) => TradeRow( builder: (_) => TradeRow(
onTap: () => Navigator.of(context) onTap: () => Navigator.of(context).pushNamed(
.pushNamed(Routes.tradeDetails, arguments: trade), Routes.tradeDetails,
provider: trade.provider, arguments: trade),
from: trade.from, provider: trade.provider,
to: trade.to, from: trade.from,
createdAtFormattedDate: trade.createdAt != null to: trade.to,
? DateFormat('HH:mm').format(trade.createdAt!)
: null,
formattedAmount: item.tradeFormattedAmount));
}
if (item is OrderListItem) {
final order = item.order;
return Observer(
builder: (_) => OrderRow(
onTap: () => Navigator.of(context)
.pushNamed(Routes.orderDetails, arguments: order),
provider: order.provider,
from: order.from!,
to: order.to!,
createdAtFormattedDate: createdAtFormattedDate:
DateFormat('HH:mm').format(order.createdAt), trade.createdAt != null
formattedAmount: item.orderFormattedAmount, ? DateFormat('HH:mm')
)); .format(trade.createdAt!)
} : null,
formattedAmount: item.tradeFormattedAmount));
}
return Container(color: Colors.transparent, height: 1); if (item is OrderListItem) {
}) final order = item.order;
: Center(
child: Text( return Observer(
S.of(context).placeholder_transactions, builder: (_) => OrderRow(
style: TextStyle( onTap: () => Navigator.of(context)
fontSize: 14, .pushNamed(Routes.orderDetails,
color: Theme.of(context).primaryTextTheme!.labelSmall!.decorationColor!), arguments: order),
), provider: order.provider,
); from: order.from!,
})) to: order.to!,
], createdAtFormattedDate: DateFormat('HH:mm')
.format(order.createdAt),
formattedAmount: item.orderFormattedAmount,
));
}
return Container(color: Colors.transparent, height: 1);
})
: Center(
child: Text(
S.of(context).placeholder_transactions,
style: TextStyle(
fontSize: 14,
color: Theme.of(context)
.primaryTextTheme
.labelSmall!
.decorationColor!),
),
);
}))
],
),
), ),
); );
} }

View file

@ -145,216 +145,247 @@ class SendPage extends BasePage {
Widget body(BuildContext context) { Widget body(BuildContext context) {
_setEffects(context); _setEffects(context);
return Form( return GestureDetector(
key: _formKey, onLongPress: () => sendViewModel.balanceViewModel.isReversing =
child: ScrollableWithBottomSection( !sendViewModel.balanceViewModel.isReversing,
contentPadding: EdgeInsets.only(bottom: 24), onLongPressUp: () => sendViewModel.balanceViewModel.isReversing =
content: FocusTraversalGroup( !sendViewModel.balanceViewModel.isReversing,
policy: OrderedTraversalPolicy(), child: Form(
child: Column( key: _formKey,
children: <Widget>[ child: ScrollableWithBottomSection(
Container( contentPadding: EdgeInsets.only(bottom: 24),
height: _sendCardHeight(context), content: FocusTraversalGroup(
child: Observer( policy: OrderedTraversalPolicy(),
builder: (_) { child: Column(
return PageView.builder( children: <Widget>[
scrollDirection: Axis.horizontal, Container(
controller: controller, height: _sendCardHeight(context),
itemCount: sendViewModel.outputs.length, child: Observer(
itemBuilder: (context, index) { builder: (_) {
final output = sendViewModel.outputs[index]; return PageView.builder(
scrollDirection: Axis.horizontal,
return SendCard( controller: controller,
key: output.key, itemCount: sendViewModel.outputs.length,
output: output, itemBuilder: (context, index) {
sendViewModel: sendViewModel, final output = sendViewModel.outputs[index];
initialPaymentRequest: initialPaymentRequest,
); return SendCard(
}); key: output.key,
}, output: output,
)), sendViewModel: sendViewModel,
Padding( initialPaymentRequest: initialPaymentRequest,
padding: );
EdgeInsets.only(top: 10, left: 24, right: 24, bottom: 10), });
child: Container( },
height: 10, )),
child: Observer( Padding(
builder: (_) { padding: EdgeInsets.only(
final count = sendViewModel.outputs.length; top: 10, left: 24, right: 24, bottom: 10),
child: Container(
return count > 1 height: 10,
? SmoothPageIndicator( child: Observer(
controller: controller, builder: (_) {
count: count, final count = sendViewModel.outputs.length;
effect: ScrollingDotsEffect(
spacing: 6.0, return count > 1
radius: 6.0, ? SmoothPageIndicator(
dotWidth: 6.0, controller: controller,
dotHeight: 6.0, count: count,
dotColor: Theme.of(context) effect: ScrollingDotsEffect(
.primaryTextTheme!.displaySmall! spacing: 6.0,
.backgroundColor!, radius: 6.0,
activeDotColor: Theme.of(context) dotWidth: 6.0,
.primaryTextTheme!.displayMedium! dotHeight: 6.0,
.backgroundColor!), dotColor: Theme.of(context)
) .primaryTextTheme
: Offstage(); !.displaySmall!
}, .backgroundColor!,
activeDotColor: Theme.of(context)
.primaryTextTheme
!.displayMedium!
.backgroundColor!),
)
: Offstage();
},
),
), ),
), ),
), if (sendViewModel.hasMultiRecipient)
if (sendViewModel.hasMultiRecipient) Container(
Container( height: 40,
height: 40, width: double.infinity,
width: double.infinity, padding: EdgeInsets.only(left: 24),
padding: EdgeInsets.only(left: 24), child: SingleChildScrollView(
child: SingleChildScrollView( scrollDirection: Axis.horizontal,
scrollDirection: Axis.horizontal, child: Observer(
child: Observer( builder: (_) {
builder: (_) { final templates = sendViewModel.templates;
final templates = sendViewModel.templates; final itemCount = templates.length;
final itemCount = templates.length;
return Row(
return Row( children: <Widget>[
children: <Widget>[ AddTemplateButton(
AddTemplateButton( onTap: () => Navigator.of(context)
onTap: () => Navigator.of(context).pushNamed(Routes.sendTemplate), .pushNamed(Routes.sendTemplate),
currentTemplatesLength: templates.length, currentTemplatesLength: templates.length,
), ),
ListView.builder( ListView.builder(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
shrinkWrap: true, shrinkWrap: true,
physics: NeverScrollableScrollPhysics(), physics: NeverScrollableScrollPhysics(),
itemCount: itemCount, itemCount: itemCount,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final template = templates[index]; final template = templates[index];
return TemplateTile( return TemplateTile(
key: UniqueKey(), key: UniqueKey(),
to: template.name, to: template.name,
amount: template.isCurrencySelected ? template.amount : template.amountFiat, amount: template.isCurrencySelected
from: template.isCurrencySelected ? template.cryptoCurrency : template.fiatCurrency, ? template.amount
onTap: () async { : template.amountFiat,
final fiatFromTemplate = FiatCurrency.all.singleWhere((element) => element.title == template.fiatCurrency); from: template.isCurrencySelected
final output = _defineCurrentOutput(); ? template.cryptoCurrency
output.address = template.address; : template.fiatCurrency,
if(template.isCurrencySelected){ onTap: () async {
output.setCryptoAmount(template.amount); final fiatFromTemplate = FiatCurrency
}else{ .all
sendViewModel.setFiatCurrency(fiatFromTemplate); .singleWhere((element) =>
output.setFiatAmount(template.amountFiat); element.title ==
} template.fiatCurrency);
output.resetParsedAddress(); final output = _defineCurrentOutput();
await output.fetchParsedAddress(context); output.address = template.address;
}, if (template.isCurrencySelected) {
onRemove: () { output
showPopUp<void>( .setCryptoAmount(template.amount);
context: context, } else {
builder: (dialogContext) { sendViewModel.setFiatCurrency(
return AlertWithTwoActions( fiatFromTemplate);
alertTitle: S.of(context).template, output.setFiatAmount(
alertContent: S template.amountFiat);
.of(context) }
.confirm_delete_template, output.resetParsedAddress();
rightButtonText: S.of(context).delete, await output
leftButtonText: S.of(context).cancel, .fetchParsedAddress(context);
actionRightButton: () { },
Navigator.of(dialogContext).pop(); onRemove: () {
sendViewModel.sendTemplateViewModel showPopUp<void>(
.removeTemplate( context: context,
template: template); builder: (dialogContext) {
}, return AlertWithTwoActions(
actionLeftButton: () => alertTitle:
Navigator.of(dialogContext) S.of(context).template,
.pop()); alertContent: S
.of(context)
.confirm_delete_template,
rightButtonText:
S.of(context).delete,
leftButtonText:
S.of(context).cancel,
actionRightButton: () {
Navigator.of(dialogContext)
.pop();
sendViewModel
.sendTemplateViewModel
.removeTemplate(
template: template);
},
actionLeftButton: () =>
Navigator.of(dialogContext)
.pop());
},
);
}, },
); );
}, },
); ),
}, ],
), );
], },
); ),
}, ),
),
),
)
],
),
),
bottomSectionPadding:
EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Column(
children: [
if (sendViewModel.hasCurrecyChanger)
Observer(builder: (_) =>
Padding(
padding: EdgeInsets.only(bottom: 12),
child: PrimaryButton(
onPressed: () => presentCurrencyPicker(context),
text: 'Change your asset (${sendViewModel.selectedCryptoCurrency})',
color: Colors.transparent,
textColor: Theme.of(context)
.accentTextTheme!.displaySmall!
.decorationColor!,
) )
) ],
), ),
if (sendViewModel.hasMultiRecipient) ),
Padding( bottomSectionPadding:
padding: EdgeInsets.only(bottom: 12), EdgeInsets.only(left: 24, right: 24, bottom: 24),
child: PrimaryButton( bottomSection: Column(
onPressed: () { children: [
sendViewModel.addOutput(); if (sendViewModel.hasCurrecyChanger)
Future.delayed(const Duration(milliseconds: 250), () { Observer(
controller.jumpToPage(sendViewModel.outputs.length - 1); builder: (_) => Padding(
}); padding: EdgeInsets.only(bottom: 12),
}, child: PrimaryButton(
text: S.of(context).add_receiver, onPressed: () => presentCurrencyPicker(context),
color: Colors.transparent, text:
textColor: Theme.of(context) 'Change your asset (${sendViewModel.selectedCryptoCurrency})',
.accentTextTheme!.displaySmall! color: Colors.transparent,
.decorationColor!, textColor: Theme.of(context)
isDottedBorder: true, .accentTextTheme
borderColor: Theme.of(context) !.displaySmall!
.primaryTextTheme!.displaySmall! .decorationColor!,
.decorationColor!, ))),
)), if (sendViewModel.hasMultiRecipient)
Observer( Padding(
builder: (_) { padding: EdgeInsets.only(bottom: 12),
return LoadingPrimaryButton( child: PrimaryButton(
onPressed: () async { onPressed: () {
if (_formKey.currentState != null && !_formKey.currentState!.validate()) { sendViewModel.addOutput();
if (sendViewModel.outputs.length > 1) { Future.delayed(const Duration(milliseconds: 250), () {
showErrorValidationAlert(context); controller
.jumpToPage(sendViewModel.outputs.length - 1);
});
},
text: S.of(context).add_receiver,
color: Colors.transparent,
textColor: Theme.of(context)
.accentTextTheme
!.displaySmall!
.decorationColor!,
isDottedBorder: true,
borderColor: Theme.of(context)
.primaryTextTheme
!.displaySmall!
.decorationColor!,
)),
Observer(
builder: (_) {
return LoadingPrimaryButton(
onPressed: () async {
if (_formKey.currentState != null &&
!_formKey.currentState!.validate()) {
if (sendViewModel.outputs.length > 1) {
showErrorValidationAlert(context);
}
return;
} }
return; final notValidItems = sendViewModel.outputs
} .where((item) =>
item.address.isEmpty ||
item.cryptoAmount.isEmpty)
.toList();
final notValidItems = sendViewModel.outputs if (notValidItems.isNotEmpty ?? false) {
.where((item) => showErrorValidationAlert(context);
item.address.isEmpty || item.cryptoAmount.isEmpty) return;
.toList(); }
if (notValidItems.isNotEmpty ?? false) { await sendViewModel.createTransaction();
showErrorValidationAlert(context); },
return; text: S.of(context).send,
} color:
Theme.of(context).accentTextTheme!.bodyLarge!.color!,
await sendViewModel.createTransaction(); textColor: Colors.white,
isLoading: sendViewModel.state is IsExecutingState ||
}, sendViewModel.state is TransactionCommitting,
text: S.of(context).send, isDisabled: !sendViewModel.isReadyForSend,
color: Theme.of(context).accentTextTheme!.bodyLarge!.color!, );
textColor: Colors.white, },
isLoading: sendViewModel.state is IsExecutingState || )
sendViewModel.state is TransactionCommitting, ],
isDisabled: !sendViewModel.isReadyForSend, )),
); ),
},
)
],
)),
); );
} }
@ -382,51 +413,54 @@ class SendPage extends BasePage {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (context.mounted) { if (context.mounted) {
showPopUp<void>( showPopUp<void>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return ConfirmSendingAlert( return ConfirmSendingAlert(
alertTitle: S.of(context).confirm_sending, alertTitle: S.of(context).confirm_sending,
amount: S.of(context).send_amount, amount: S.of(context).send_amount,
amountValue: amountValue:
sendViewModel.pendingTransaction!.amountFormatted, sendViewModel.pendingTransaction!.amountFormatted,
fiatAmountValue: sendViewModel.pendingTransactionFiatAmountFormatted, fiatAmountValue:
fee: S.of(context).send_fee, sendViewModel.pendingTransactionFiatAmountFormatted,
feeValue: sendViewModel.pendingTransaction!.feeFormatted, fee: S.of(context).send_fee,
feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted, feeValue: sendViewModel.pendingTransaction!.feeFormatted,
outputs: sendViewModel.outputs, feeFiatAmount: sendViewModel
rightButtonText: S.of(context).ok, .pendingTransactionFeeFiatAmountFormatted,
leftButtonText: S.of(context).cancel, outputs: sendViewModel.outputs,
actionRightButton: () { rightButtonText: S.of(context).ok,
Navigator.of(context).pop(); leftButtonText: S.of(context).cancel,
sendViewModel.commitTransaction(); actionRightButton: () {
showPopUp<void>( Navigator.of(context).pop();
context: context, sendViewModel.commitTransaction();
builder: (BuildContext context) { showPopUp<void>(
return Observer(builder: (_) { context: context,
final state = sendViewModel.state; builder: (BuildContext context) {
return Observer(builder: (_) {
final state = sendViewModel.state;
if (state is FailureState) { if (state is FailureState) {
Navigator.of(context).pop(); Navigator.of(context).pop();
}
if (state is TransactionCommitted) {
return AlertWithOneAction(
alertTitle: '',
alertContent: S.of(context).send_success(
sendViewModel.selectedCryptoCurrency.toString()),
buttonText: S.of(context).ok,
buttonAction: () {
Navigator.of(context).pop();
RequestReviewHandler.requestReview();
});
} }
return Offstage(); if (state is TransactionCommitted) {
return AlertWithOneAction(
alertTitle: '',
alertContent: S.of(context).send_success(
sendViewModel.selectedCryptoCurrency
.toString()),
buttonText: S.of(context).ok,
buttonAction: () {
Navigator.of(context).pop();
RequestReviewHandler.requestReview();
});
}
return Offstage();
});
}); });
}); },
}, actionLeftButton: () => Navigator.of(context).pop());
actionLeftButton: () => Navigator.of(context).pop()); });
});
} }
}); });
} }
@ -461,16 +495,18 @@ class SendPage extends BasePage {
}); });
} }
void presentCurrencyPicker(BuildContext context) async { void presentCurrencyPicker(BuildContext context) async {
await showPopUp<CryptoCurrency>( await showPopUp<CryptoCurrency>(
builder: (_) => Picker( builder: (_) => Picker(
items: sendViewModel.currencies, items: sendViewModel.currencies,
displayItem: (Object item) => item.toString(), displayItem: (Object item) => item.toString(),
selectedAtIndex: sendViewModel.currencies.indexOf(sendViewModel.selectedCryptoCurrency), selectedAtIndex: sendViewModel.currencies
title: S.of(context).please_select, .indexOf(sendViewModel.selectedCryptoCurrency),
mainAxisAlignment: MainAxisAlignment.center, title: S.of(context).please_select,
onItemSelected: (CryptoCurrency cur) => sendViewModel.selectedCryptoCurrency = cur, mainAxisAlignment: MainAxisAlignment.center,
), onItemSelected: (CryptoCurrency cur) =>
sendViewModel.selectedCryptoCurrency = cur,
),
context: context); context: context);
} }
} }

View file

@ -13,7 +13,6 @@ import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cw_core/keyable.dart'; import 'package:cw_core/keyable.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
class TransactionListItem extends ActionListItem with Keyable { class TransactionListItem extends ActionListItem with Keyable {
TransactionListItem( TransactionListItem(
{required this.transaction, {required this.transaction,
@ -28,7 +27,7 @@ class TransactionListItem extends ActionListItem with Keyable {
FiatCurrency get fiatCurrency => settingsStore.fiatCurrency; FiatCurrency get fiatCurrency => settingsStore.fiatCurrency;
BalanceDisplayMode get displayMode => settingsStore.balanceDisplayMode; BalanceDisplayMode get displayMode => balanceViewModel.displayMode;
@override @override
dynamic get keyIndex => transaction.id; dynamic get keyIndex => transaction.id;