Sp enhancements (#1672)

* fix: enhance regex, fix multiline

* feat: improve scan msg, fix missing txs, use date api

* feat: node fixes, enhance send modal, TX list tag & filter, refactors

* fix: continuous scanning

* fix: missing close

* fix: resubscribe tweaks

* feat: use mempool api setting toggle

* handle any failure of height API and fallback to the old method [skip ci]

---------

Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
This commit is contained in:
Rafael
2024-09-20 17:24:25 +03:00
committed by GitHub
co-authored by OmarHatem
parent b6bc3df622
commit 3a391f10a3
61 changed files with 872 additions and 531 deletions
@@ -1,3 +1,4 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/anonpay_transaction_row.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/order_row.dart';
import 'package:cake_wallet/themes/extensions/placeholder_theme.dart';
@@ -52,7 +53,7 @@ class TransactionsPage extends StatelessWidget {
try {
final uri = Uri.parse(
"https://guides.cakewallet.com/docs/FAQ/why_are_my_funds_not_appearing/");
launchUrl(uri, mode: LaunchMode.externalApplication);
launchUrl(uri, mode: LaunchMode.externalApplication);
} catch (_) {}
},
title: S.of(context).syncing_wallet_alert_title,
@@ -84,7 +85,7 @@ class TransactionsPage extends StatelessWidget {
final transaction = item.transaction;
final transactionType = dashboardViewModel.type == WalletType.ethereum &&
transaction.evmSignatureName == 'approval'
transaction.evmSignatureName == 'approval'
? ' (${transaction.evmSignatureName})'
: '';
@@ -100,8 +101,11 @@ class TransactionsPage extends StatelessWidget {
? ''
: item.formattedFiatAmount,
isPending: transaction.isPending,
title: item.formattedTitle +
item.formattedStatus + ' $transactionType',
title:
item.formattedTitle + item.formattedStatus + ' $transactionType',
isReceivedSilentPayment:
dashboardViewModel.type == WalletType.bitcoin &&
bitcoin!.txIsReceivedSilentPayment(transaction),
),
);
}
@@ -1,3 +1,4 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:flutter/material.dart';
import 'package:cw_core/transaction_direction.dart';
@@ -5,14 +6,16 @@ import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
class TransactionRow extends StatelessWidget {
TransactionRow(
{required this.direction,
required this.formattedDate,
required this.formattedAmount,
required this.formattedFiatAmount,
required this.isPending,
required this.title,
required this.onTap});
TransactionRow({
required this.direction,
required this.formattedDate,
required this.formattedAmount,
required this.formattedFiatAmount,
required this.isPending,
required this.isReceivedSilentPayment,
required this.title,
required this.onTap,
});
final VoidCallback onTap;
final TransactionDirection direction;
@@ -20,6 +23,7 @@ class TransactionRow extends StatelessWidget {
final String formattedAmount;
final String formattedFiatAmount;
final bool isPending;
final bool isReceivedSilentPayment;
final String title;
@override
@@ -38,50 +42,80 @@ class TransactionRow extends StatelessWidget {
width: 36,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).extension<TransactionTradeTheme>()!.rowsColor
),
child: Image.asset(
direction == TransactionDirection.incoming
? 'assets/images/down_arrow.png'
: 'assets/images/up_arrow.png'),
color: Theme.of(context).extension<TransactionTradeTheme>()!.rowsColor),
child: Image.asset(direction == TransactionDirection.incoming
? 'assets/images/down_arrow.png'
: 'assets/images/up_arrow.png'),
),
SizedBox(width: 12),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor)),
Text(formattedAmount,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor))
]),
SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(formattedDate,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor)),
Text(formattedFiatAmount,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor))
])
],
)
)
mainAxisSize: MainAxisSize.min,
children: [
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
Row(
children: [
Text(title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
)),
if (isReceivedSilentPayment) TxTag(tag: S.of(context).silent_payment),
],
),
Text(formattedAmount,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor))
]),
SizedBox(height: 5),
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[
Text(formattedDate,
style: TextStyle(
fontSize: 14,
color:
Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor)),
Text(formattedFiatAmount,
style: TextStyle(
fontSize: 14,
color:
Theme.of(context).extension<CakeTextTheme>()!.dateSectionRowColor))
])
],
))
],
),
));
}
}
// A tag to add context to a transaction
// example use: differ silent payments from regular txs
class TxTag extends StatelessWidget {
TxTag({required this.tag});
final String tag;
@override
Widget build(BuildContext context) {
return Container(
height: 17,
padding: EdgeInsets.only(left: 6, right: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8.5)),
color: Theme.of(context).extension<TransactionTradeTheme>()!.rowsColor,
),
alignment: Alignment.center,
child: Text(
tag.toLowerCase(),
style: TextStyle(
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
fontSize: 9,
fontWeight: FontWeight.w600,
),
),
);
}
}
+1
View File
@@ -37,6 +37,7 @@ class RescanPage extends BasePage {
toggleSingleScan: () =>
_rescanViewModel.doSingleScan = !_rescanViewModel.doSingleScan,
walletType: _rescanViewModel.wallet.type,
bitcoinMempoolAPIEnabled: _rescanViewModel.isBitcoinMempoolAPIEnabled,
)),
Observer(
builder: (_) => LoadingPrimaryButton(
+25 -25
View File
@@ -68,11 +68,11 @@ class SendPage extends BasePage {
@override
Function(BuildContext)? get pushToNextWidget => (context) {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.focusedChild?.unfocus();
}
};
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.focusedChild?.unfocus();
}
};
@override
Widget? leading(BuildContext context) {
@@ -212,26 +212,25 @@ class SendPage extends BasePage {
final count = sendViewModel.outputs.length;
return count > 1
? Semantics (
label: 'Page Indicator',
hint: 'Swipe to change receiver',
excludeSemantics: true,
child:
SmoothPageIndicator(
controller: controller,
count: count,
effect: ScrollingDotsEffect(
spacing: 6.0,
radius: 6.0,
dotWidth: 6.0,
dotHeight: 6.0,
dotColor: Theme.of(context)
.extension<SendPageTheme>()!
.indicatorDotColor,
activeDotColor: Theme.of(context)
.extension<SendPageTheme>()!
.templateBackgroundColor),
))
? Semantics(
label: 'Page Indicator',
hint: 'Swipe to change receiver',
excludeSemantics: true,
child: SmoothPageIndicator(
controller: controller,
count: count,
effect: ScrollingDotsEffect(
spacing: 6.0,
radius: 6.0,
dotWidth: 6.0,
dotHeight: 6.0,
dotColor: Theme.of(context)
.extension<SendPageTheme>()!
.indicatorDotColor,
activeDotColor: Theme.of(context)
.extension<SendPageTheme>()!
.templateBackgroundColor),
))
: Offstage();
},
),
@@ -478,6 +477,7 @@ class SendPage extends BasePage {
feeValue: sendViewModel.pendingTransaction!.feeFormatted,
feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted,
outputs: sendViewModel.outputs,
change: sendViewModel.pendingTransaction!.change,
rightButtonText: S.of(_dialogContext).send,
leftButtonText: S.of(_dialogContext).cancel,
actionRightButton: () async {
@@ -1,6 +1,7 @@
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/base_alert_dialog.dart';
import 'package:cake_wallet/generated/i18n.dart';
@@ -21,6 +22,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
required this.feeValue,
required this.feeFiatAmount,
required this.outputs,
this.change,
required this.leftButtonText,
required this.rightButtonText,
required this.actionLeftButton,
@@ -44,6 +46,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
final String feeValue;
final String feeFiatAmount;
final List<Output> outputs;
final PendingChange? change;
final String leftButtonText;
final String rightButtonText;
final VoidCallback actionLeftButton;
@@ -101,6 +104,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
feeValue: feeValue,
feeFiatAmount: feeFiatAmount,
outputs: outputs,
change: change,
onDispose: onDispose);
}
@@ -117,6 +121,7 @@ class ConfirmSendingAlertContent extends StatefulWidget {
required this.feeValue,
required this.feeFiatAmount,
required this.outputs,
this.change,
required this.onDispose}) {}
final String? paymentId;
@@ -130,6 +135,7 @@ class ConfirmSendingAlertContent extends StatefulWidget {
final String feeValue;
final String feeFiatAmount;
final List<Output> outputs;
final PendingChange? change;
final Function? onDispose;
@override
@@ -145,6 +151,7 @@ class ConfirmSendingAlertContent extends StatefulWidget {
feeValue: feeValue,
feeFiatAmount: feeFiatAmount,
outputs: outputs,
change: change,
onDispose: onDispose);
}
@@ -161,6 +168,7 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
required this.feeValue,
required this.feeFiatAmount,
required this.outputs,
this.change,
this.onDispose})
: recipientTitle = '' {
recipientTitle = outputs.length > 1
@@ -179,6 +187,7 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
final String feeValue;
final String feeFiatAmount;
final List<Output> outputs;
final PendingChange? change;
final Function? onDispose;
final double backgroundHeight = 160;
@@ -391,100 +400,57 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
decoration: TextDecoration.none,
),
),
outputs.length > 1
? ListView.builder(
padding: EdgeInsets.only(top: 0),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: outputs.length,
itemBuilder: (context, index) {
final item = outputs[index];
final _address =
item.isParsedAddress ? item.extractedAddress : item.address;
final _amount = item.cryptoAmount.replaceAll(',', '.');
ListView.builder(
padding: EdgeInsets.only(top: 0),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: outputs.length,
itemBuilder: (context, index) {
final item = outputs[index];
final _address =
item.isParsedAddress ? item.extractedAddress : item.address;
final _amount = item.cryptoAmount.replaceAll(',', '.');
return Column(
children: [
if (item.isParsedAddress)
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
item.parsedAddress.name,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)),
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
_address,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)),
Padding(
padding: EdgeInsets.only(top: 8),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
_amount,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)
],
))
],
);
})
: Column(children: [
if (outputs.first.isParsedAddress)
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
outputs.first.parsedAddress.name,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)),
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
outputs.first.isParsedAddress
? outputs.first.extractedAddress
: outputs.first.address,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)),
])
return Column(
children: [
if (item.isParsedAddress)
AddressText(text: item.parsedAddress.name),
AddressText(text: _address, fontSize: 10),
if (stealthAddressText(item.stealthAddress) != null)
AddressText(
text: stealthAddressText(item.stealthAddress)!, fontSize: 10),
AmountText(text: _amount),
],
);
},
)
],
),
)
),
if (change != null)
Padding(
padding: EdgeInsets.only(top: 16),
child: Column(
children: [
Text(
S.of(context).send_change_to_you,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
fontFamily: 'Lato',
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
),
Column(
children: [
AddressText(text: change!.address, fontSize: 10),
AmountText(text: change!.amount),
],
)
],
),
)
],
))),
if (showScrollbar)
@@ -539,3 +505,78 @@ class ExpirationTimeWidget extends StatelessWidget {
);
}
}
class AddressText extends StatelessWidget {
final String text;
final double fontSize;
final FontWeight fontWeight;
final TextAlign? textAlign;
const AddressText({
required this.text,
this.fontSize = 14,
this.fontWeight = FontWeight.w600,
this.textAlign,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
text,
style: TextStyle(
fontSize: fontSize,
fontWeight: fontWeight,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
),
);
}
}
class AmountText extends StatelessWidget {
final String text;
final double fontSize;
final FontWeight fontWeight;
final TextAlign? textAlign;
const AmountText({
required this.text,
this.fontSize = 10,
this.fontWeight = FontWeight.w600,
this.textAlign,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(top: 8),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
text,
style: TextStyle(
fontSize: fontSize,
fontWeight: fontWeight,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)
],
));
}
}
String? stealthAddressText(String? stealthAddress) {
if (stealthAddress == null) {
return null;
}
return stealthAddress.isNotEmpty ? "-> $stealthAddress" : null;
}
+13 -13
View File
@@ -58,8 +58,8 @@ class PrivacyPage extends BasePage {
if (_privacySettingsViewModel.isAutoGenerateSubaddressesVisible)
SettingsSwitcherCell(
title: _privacySettingsViewModel.isMoneroWallet
? S.current.auto_generate_subaddresses
: S.current.auto_generate_addresses,
? S.current.auto_generate_subaddresses
: S.current.auto_generate_addresses,
value: _privacySettingsViewModel.isAutoGenerateSubaddressesEnabled,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setAutoGenerateSubaddresses(value);
@@ -115,21 +115,21 @@ class PrivacyPage extends BasePage {
),
if (_privacySettingsViewModel.canUseMempoolFeeAPI)
SettingsSwitcherCell(
title: S.current.live_fee_rates,
title: S.current.enable_mempool_api,
value: _privacySettingsViewModel.useMempoolFeeAPI,
onValueChange: (BuildContext _, bool isEnabled) async {
if (!isEnabled) {
final bool confirmation = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).warning,
alertContent: S.of(context).disable_fee_api_warning,
rightButtonText: S.of(context).confirm,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.of(context).pop(true),
actionLeftButton: () => Navigator.of(context).pop(false));
}) ??
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).warning,
alertContent: S.of(context).disable_fee_api_warning,
rightButtonText: S.of(context).confirm,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.of(context).pop(true),
actionLeftButton: () => Navigator.of(context).pop(false));
}) ??
false;
if (confirmation) {
_privacySettingsViewModel.setUseMempoolFeeAPI(isEnabled);
+11 -3
View File
@@ -20,6 +20,7 @@ class BlockchainHeightWidget extends StatefulWidget {
this.isSilentPaymentsScan = false,
this.toggleSingleScan,
this.doSingleScan = false,
this.bitcoinMempoolAPIEnabled,
required this.walletType,
}) : super(key: key);
@@ -29,6 +30,7 @@ class BlockchainHeightWidget extends StatefulWidget {
final bool hasDatePicker;
final bool isSilentPaymentsScan;
final bool doSingleScan;
final Future<bool>? bitcoinMempoolAPIEnabled;
final Function()? toggleSingleScan;
final WalletType walletType;
@@ -79,7 +81,8 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
child: BaseTextFormField(
focusNode: widget.focusNode,
controller: restoreHeightController,
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: false),
keyboardType:
TextInputType.numberWithOptions(signed: false, decimal: false),
hintText: widget.isSilentPaymentsScan
? S.of(context).silent_payments_scan_from_height
: S.of(context).widgets_restore_from_blockheight,
@@ -146,7 +149,9 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
: S.of(context).restore_from_date_or_blockheight,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12, fontWeight: FontWeight.normal, color: Theme.of(context).hintColor),
fontSize: 12,
fontWeight: FontWeight.normal,
color: Theme.of(context).hintColor),
),
)
]
@@ -166,7 +171,10 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
if (date != null) {
int height;
if (widget.isSilentPaymentsScan) {
height = bitcoin!.getHeightByDate(date: date);
height = await bitcoin!.getHeightByDate(
date: date,
bitcoinMempoolAPIEnabled: await widget.bitcoinMempoolAPIEnabled,
);
} else {
if (widget.walletType == WalletType.monero) {
height = monero!.getHeightByDate(date: date);