Mweb checkbox ()

* [skip-ci] wip

* [skip-ci] styles still need updating

* working but needs style updates

* fix checkbox caption color

* sort mweb coins to be last when selecting inputs

* ui fixes

* [skip-ci] default to mweb-checkbox being off

* adaptable page view builder + workaround for keyboard actions

* Fix checkbox themeing and send card sizing

* Update lib/src/screens/send/widgets/send_card.dart

---------

Co-authored-by: tuxpizza <tuxsudo@tux.pizza>
Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
Matthew Fosse 2025-03-10 16:37:23 -07:00 committed by GitHub
parent 1e4dbb5bc9
commit 1c8af1afae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 885 additions and 581 deletions

View file

@ -632,8 +632,8 @@ abstract class ElectrumWalletBase
}).toList();
final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList();
// sort the unconfirmed coins so that mweb coins are first:
availableInputs.sort((a, b) => a.bitcoinAddressRecord.type == SegwitAddresType.mweb ? -1 : 1);
// sort the unconfirmed coins so that mweb coins are last:
availableInputs.sort((a, b) => a.bitcoinAddressRecord.type == SegwitAddresType.mweb ? 1 : -1);
for (int i = 0; i < availableInputs.length; i++) {
final utx = availableInputs[i];

View file

@ -748,7 +748,7 @@ Future<void> setup({
getIt.get<ContactListViewModel>(),
_transactionDescriptionBox,
getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null,
coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.any,
coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.nonMweb,
getIt.get<UnspentCoinsListViewModel>(param1: coinTypeToSpendFrom),
),
);

View file

@ -14,14 +14,17 @@ import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart'
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
import 'package:cake_wallet/src/screens/send/widgets/send_card.dart';
import 'package:cake_wallet/src/widgets/adaptable_page_view.dart';
import 'package:cake_wallet/src/widgets/add_template_button.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/src/widgets/template_tile.dart';
import 'package:cake_wallet/src/widgets/trail_button.dart';
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
import 'package:cake_wallet/themes/extensions/seed_widget_theme.dart';
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
import 'package:cake_wallet/themes/theme_base.dart';
@ -38,6 +41,7 @@ import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:mobx/mobx.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
import 'package:url_launcher/url_launcher.dart';
@ -93,7 +97,7 @@ class SendPage extends BasePage {
return MergeSemantics(
child: SizedBox(
height: isMobileView ? 37 : 45,
width: isMobileView ? 37 : 45,
width: isMobileView ? 47: 45,
child: ButtonTheme(
minWidth: double.minPositive,
child: Semantics(
@ -114,18 +118,6 @@ class SendPage extends BasePage {
@override
AppBarStyle get appBarStyle => AppBarStyle.transparent;
double _sendCardHeight(BuildContext context) {
double initialHeight = 480;
if (sendViewModel.hasCoinControl) {
initialHeight += 55;
}
if (!responsiveLayoutUtil.shouldRenderMobileUI) {
return initialHeight - 66;
}
return initialHeight;
}
@override
void onClose(BuildContext context) {
sendViewModel.onClose();
@ -174,285 +166,316 @@ class SendPage extends BasePage {
Widget body(BuildContext context) {
_setEffects(context);
return GestureDetector(
onLongPress: () =>
sendViewModel.balanceViewModel.isReversing = !sendViewModel.balanceViewModel.isReversing,
onLongPressUp: () =>
sendViewModel.balanceViewModel.isReversing = !sendViewModel.balanceViewModel.isReversing,
child: Form(
key: _formKey,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24),
content: FocusTraversalGroup(
policy: OrderedTraversalPolicy(),
child: Column(
children: <Widget>[
Container(
height: _sendCardHeight(context),
child: Observer(
builder: (_) {
return PageView.builder(
scrollDirection: Axis.horizontal,
controller: controller,
itemCount: sendViewModel.outputs.length,
itemBuilder: (context, index) {
final output = sendViewModel.outputs[index];
return Observer(builder: (_) {
List<Widget> sendCards = [];
List<KeyboardActionsItem> keyboardActions = [];
for (var output in sendViewModel.outputs) {
var cryptoAmountFocus = FocusNode();
var fiatAmountFocus = FocusNode();
sendCards.add(SendCard(
currentTheme: currentTheme,
key: output.key,
output: output,
sendViewModel: sendViewModel,
initialPaymentRequest: initialPaymentRequest,
cryptoAmountFocus: cryptoAmountFocus,
fiatAmountFocus: fiatAmountFocus,
));
keyboardActions.add(KeyboardActionsItem(
focusNode: cryptoAmountFocus, toolbarButtons: [(_) => KeyboardDoneButton()]));
keyboardActions.add(KeyboardActionsItem(
focusNode: fiatAmountFocus, toolbarButtons: [(_) => KeyboardDoneButton()]));
}
return Stack(
children: [
KeyboardActions(
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.ALL,
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
nextFocus: false,
actions: keyboardActions,
),
child: Container(
height: 0,
color: Colors.transparent,
),
),
GestureDetector(
onLongPress: () => sendViewModel.balanceViewModel.isReversing =
!sendViewModel.balanceViewModel.isReversing,
onLongPressUp: () => sendViewModel.balanceViewModel.isReversing =
!sendViewModel.balanceViewModel.isReversing,
child: Form(
key: _formKey,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24),
content: FocusTraversalGroup(
policy: OrderedTraversalPolicy(),
child: Column(
children: <Widget>[
PageViewHeightAdaptable(
controller: controller,
children: sendCards,
),
SizedBox(height: 10),
Padding(
padding: EdgeInsets.only(left: 24, right: 24, bottom: 10),
child: Container(
height: 10,
child: Observer(
builder: (_) {
final count = sendViewModel.outputs.length;
return SendCard(
key: output.key,
output: output,
sendViewModel: sendViewModel,
initialPaymentRequest: initialPaymentRequest,
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),
))
: Offstage();
},
),
),
),
Container(
height: 40,
width: double.infinity,
padding: EdgeInsets.only(left: 24),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Observer(
builder: (_) {
final templates = sendViewModel.templates;
final itemCount = templates.length;
return Row(
children: <Widget>[
AddTemplateButton(
key: ValueKey('send_page_add_template_button_key'),
onTap: () =>
Navigator.of(context).pushNamed(Routes.sendTemplate),
currentTemplatesLength: templates.length,
),
ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: itemCount,
itemBuilder: (context, index) {
final template = templates[index];
return TemplateTile(
key: UniqueKey(),
to: template.name,
hasMultipleRecipients:
template.additionalRecipients != null &&
template.additionalRecipients!.length > 1,
amount: template.isCurrencySelected
? template.amount
: template.amountFiat,
from: template.isCurrencySelected
? template.cryptoCurrency
: template.fiatCurrency,
onTap: () async {
sendViewModel.state = IsExecutingState();
if (template.additionalRecipients?.isNotEmpty ??
false) {
sendViewModel.clearOutputs();
for (int i = 0;
i < template.additionalRecipients!.length;
i++) {
Output output;
try {
output = sendViewModel.outputs[i];
} catch (e) {
sendViewModel.addOutput();
output = sendViewModel.outputs[i];
}
await _setInputsFromTemplate(
context,
output: output,
template: template.additionalRecipients![i],
);
}
} else {
final output = _defineCurrentOutput();
await _setInputsFromTemplate(
context,
output: output,
template: template,
);
}
sendViewModel.state = InitialExecutionState();
},
onRemove: () {
showPopUp<void>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle: S.of(context).template,
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());
},
);
},
);
},
),
],
);
});
},
)),
Padding(
padding: EdgeInsets.only(left: 24, right: 24, bottom: 10),
child: Container(
height: 10,
child: Observer(
builder: (_) {
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),
))
: Offstage();
},
),
},
),
),
),
],
),
),
Container(
height: 40,
width: double.infinity,
padding: EdgeInsets.only(left: 24),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Observer(
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(
key: ValueKey('send_page_change_asset_button_key'),
onPressed: () => presentCurrencyPicker(context),
text: 'Change your asset (${sendViewModel.selectedCryptoCurrency})',
color: Colors.transparent,
textColor:
Theme.of(context).extension<SeedWidgetTheme>()!.hintTextColor,
),
),
),
if (sendViewModel.sendTemplateViewModel.hasMultiRecipient)
Padding(
padding: EdgeInsets.only(bottom: 12),
child: PrimaryButton(
key: ValueKey('send_page_add_receiver_button_key'),
onPressed: () {
sendViewModel.addOutput();
Future.delayed(const Duration(milliseconds: 250), () {
controller.jumpToPage(sendViewModel.outputs.length - 1);
});
},
text: S.of(context).add_receiver,
color: Colors.transparent,
textColor:
Theme.of(context).extension<SeedWidgetTheme>()!.hintTextColor,
isDottedBorder: true,
borderColor: Theme.of(context)
.extension<SendPageTheme>()!
.templateDottedBorderColor,
)),
Observer(
builder: (_) {
final templates = sendViewModel.templates;
final itemCount = templates.length;
return LoadingPrimaryButton(
key: ValueKey('send_page_send_button_key'),
onPressed: () async {
if (sendViewModel.state is IsExecutingState) return;
if (_formKey.currentState != null &&
!_formKey.currentState!.validate()) {
if (sendViewModel.outputs.length > 1) {
showErrorValidationAlert(context);
}
return Row(
children: <Widget>[
AddTemplateButton(
key: ValueKey('send_page_add_template_button_key'),
onTap: () => Navigator.of(context).pushNamed(Routes.sendTemplate),
currentTemplatesLength: templates.length,
),
ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: itemCount,
itemBuilder: (context, index) {
final template = templates[index];
return TemplateTile(
key: UniqueKey(),
to: template.name,
hasMultipleRecipients: template.additionalRecipients != null &&
template.additionalRecipients!.length > 1,
amount: template.isCurrencySelected
? template.amount
: template.amountFiat,
from: template.isCurrencySelected
? template.cryptoCurrency
: template.fiatCurrency,
onTap: () async {
sendViewModel.state = IsExecutingState();
if (template.additionalRecipients?.isNotEmpty ?? false) {
sendViewModel.clearOutputs();
return;
}
for (int i = 0;
i < template.additionalRecipients!.length;
i++) {
Output output;
try {
output = sendViewModel.outputs[i];
} catch (e) {
sendViewModel.addOutput();
output = sendViewModel.outputs[i];
}
final notValidItems = sendViewModel.outputs
.where(
(item) => item.address.isEmpty || item.cryptoAmount.isEmpty)
.toList();
await _setInputsFromTemplate(
context,
output: output,
template: template.additionalRecipients![i],
);
}
} else {
final output = _defineCurrentOutput();
await _setInputsFromTemplate(
context,
output: output,
template: template,
);
}
sendViewModel.state = InitialExecutionState();
},
onRemove: () {
showPopUp<void>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle: S.of(context).template,
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());
if (notValidItems.isNotEmpty) {
showErrorValidationAlert(context);
return;
}
if (sendViewModel.wallet.isHardwareWallet) {
if (!sendViewModel.ledgerViewModel!.isConnected) {
await Navigator.of(context).pushNamed(Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: sendViewModel.walletType,
onConnectDevice: (BuildContext context, _) {
sendViewModel.ledgerViewModel!
.setLedger(sendViewModel.wallet);
Navigator.of(context).pop();
},
);
},
);
));
} else {
sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet);
}
}
if (sendViewModel.wallet.type == WalletType.monero) {
int amount = 0;
for (var item in sendViewModel.outputs) {
amount += item.formattedCryptoAmount;
}
if (monero!.needExportOutputs(sendViewModel.wallet, amount)) {
await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage,
arguments: 'export-outputs');
await Future.delayed(
Duration(seconds: 1)); // wait for monero to refresh the state
}
if (monero!.needExportOutputs(sendViewModel.wallet, amount)) {
return;
}
}
final check = sendViewModel.shouldDisplayTotp();
authService.authenticateAction(
context,
conditionToDetermineIfToUse2FA: check,
onAuthSuccess: (value) async {
if (value) {
await sendViewModel.createTransaction();
}
},
),
],
);
},
text: S.of(context).send,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
isLoading: sendViewModel.state is IsExecutingState ||
sendViewModel.state is TransactionCommitting ||
sendViewModel.state is IsAwaitingDeviceResponseState,
isDisabled: !sendViewModel.isReadyForSend,
);
},
),
),
),
],
),
)
],
)),
),
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(
key: ValueKey('send_page_change_asset_button_key'),
onPressed: () => presentCurrencyPicker(context),
text: 'Change your asset (${sendViewModel.selectedCryptoCurrency})',
color: Colors.transparent,
textColor: Theme.of(context).extension<SeedWidgetTheme>()!.hintTextColor,
),
),
),
if (sendViewModel.sendTemplateViewModel.hasMultiRecipient)
Padding(
padding: EdgeInsets.only(bottom: 12),
child: PrimaryButton(
key: ValueKey('send_page_add_receiver_button_key'),
onPressed: () {
sendViewModel.addOutput();
Future.delayed(const Duration(milliseconds: 250), () {
controller.jumpToPage(sendViewModel.outputs.length - 1);
});
},
text: S.of(context).add_receiver,
color: Colors.transparent,
textColor: Theme.of(context).extension<SeedWidgetTheme>()!.hintTextColor,
isDottedBorder: true,
borderColor:
Theme.of(context).extension<SendPageTheme>()!.templateDottedBorderColor,
)),
Observer(
builder: (_) {
return LoadingPrimaryButton(
key: ValueKey('send_page_send_button_key'),
onPressed: () async {
if (sendViewModel.state is IsExecutingState) return;
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
if (sendViewModel.outputs.length > 1) {
showErrorValidationAlert(context);
}
return;
}
final notValidItems = sendViewModel.outputs
.where((item) => item.address.isEmpty || item.cryptoAmount.isEmpty)
.toList();
if (notValidItems.isNotEmpty) {
showErrorValidationAlert(context);
return;
}
if (sendViewModel.wallet.isHardwareWallet) {
if (!sendViewModel.ledgerViewModel!.isConnected) {
await Navigator.of(context).pushNamed(
Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: sendViewModel.walletType,
onConnectDevice: (BuildContext context, _) {
sendViewModel.ledgerViewModel!
.setLedger(sendViewModel.wallet);
Navigator.of(context).pop();
},
));
} else {
sendViewModel.ledgerViewModel!
.setLedger(sendViewModel.wallet);
}
}
if (sendViewModel.wallet.type == WalletType.monero) {
int amount = 0;
for (var item in sendViewModel.outputs) {
amount += item.formattedCryptoAmount;
}
if (monero!.needExportOutputs(sendViewModel.wallet, amount)) {
await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage, arguments: 'export-outputs');
await Future.delayed(Duration(seconds: 1)); // wait for monero to refresh the state
}
if (monero!.needExportOutputs(sendViewModel.wallet, amount)) {
return;
}
}
final check = sendViewModel.shouldDisplayTotp();
authService.authenticateAction(
context,
conditionToDetermineIfToUse2FA: check,
onAuthSuccess: (value) async {
if (value) {
await sendViewModel.createTransaction();
}
},
);
},
text: S.of(context).send,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
isLoading: sendViewModel.state is IsExecutingState ||
sendViewModel.state is TransactionCommitting ||
sendViewModel.state is IsAwaitingDeviceResponseState,
isDisabled: !sendViewModel.isReadyForSend,
);
},
)
],
)),
),
);
),
],
);
});
}
BuildContext? dialogContext;
@ -525,13 +548,12 @@ class SendPage extends BasePage {
if (state is TransactionCommitted) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (!context.mounted) {
return;
}
final successMessage = S.of(context).send_success(
sendViewModel.selectedCryptoCurrency.toString());
final successMessage =
S.of(context).send_success(sendViewModel.selectedCryptoCurrency.toString());
final waitMessage = sendViewModel.walletType == WalletType.solana
? '. ${S.of(context).waitFewSecondForTxUpdate}'
@ -539,10 +561,8 @@ class SendPage extends BasePage {
String alertContent = "$successMessage$waitMessage";
await Navigator.of(context).pushNamed(
Routes.transactionSuccessPage,
arguments: alertContent
);
await Navigator.of(context)
.pushNamed(Routes.transactionSuccessPage, arguments: alertContent);
newContactAddress = newContactAddress ?? sendViewModel.newContactAddress();
if (newContactAddress?.address != null && isRegularElectrumAddress(newContactAddress!.address)) {
@ -562,7 +582,7 @@ class SendPage extends BasePage {
leftButtonText: S.of(_dialogContext).ignor,
alertLeftActionButtonKey: ValueKey('send_page_sent_dialog_ignore_button_key'),
alertRightActionButtonKey:
ValueKey('send_page_sent_dialog_add_contact_button_key'),
ValueKey('send_page_sent_dialog_add_contact_button_key'),
actionRightButton: () {
Navigator.of(_dialogContext).pop();
RequestReviewHandler.requestReview();

View file

@ -1,6 +1,7 @@
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
@ -12,6 +13,7 @@ import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@ -24,40 +26,58 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
import '../../../../themes/extensions/cake_text_theme.dart';
import '../../../../themes/theme_base.dart';
class SendCard extends StatefulWidget {
SendCard({
Key? key,
required this.output,
required this.sendViewModel,
required this.currentTheme,
this.initialPaymentRequest,
this.cryptoAmountFocus,
this.fiatAmountFocus,
}) : super(key: key);
final Output output;
final SendViewModel sendViewModel;
final PaymentRequest? initialPaymentRequest;
final FocusNode? cryptoAmountFocus;
final FocusNode? fiatAmountFocus;
final ThemeBase currentTheme;
@override
SendCardState createState() => SendCardState(
output: output,
sendViewModel: sendViewModel,
initialPaymentRequest: initialPaymentRequest,
currentTheme: currentTheme
// cryptoAmountFocus: cryptoAmountFocus ?? FocusNode(),
// fiatAmountFocus: fiatAmountFocus ?? FocusNode(),
// cryptoAmountFocus: FocusNode(),
// fiatAmountFocus: FocusNode(),
);
}
class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<SendCard> {
SendCardState({required this.output, required this.sendViewModel, this.initialPaymentRequest})
: addressController = TextEditingController(),
SendCardState({
required this.output,
required this.sendViewModel,
this.initialPaymentRequest,
required this.currentTheme,
}) : addressController = TextEditingController(),
cryptoAmountController = TextEditingController(),
fiatAmountController = TextEditingController(),
noteController = TextEditingController(),
extractedAddressController = TextEditingController(),
cryptoAmountFocus = FocusNode(),
fiatAmountFocus = FocusNode(),
addressFocusNode = FocusNode();
static const prefixIconWidth = 34.0;
static const prefixIconHeight = 34.0;
final ThemeBase currentTheme;
final Output output;
final SendViewModel sendViewModel;
final PaymentRequest? initialPaymentRequest;
@ -67,8 +87,6 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
final TextEditingController fiatAmountController;
final TextEditingController noteController;
final TextEditingController extractedAddressController;
final FocusNode cryptoAmountFocus;
final FocusNode fiatAmountFocus;
final FocusNode addressFocusNode;
bool _effectsInstalled = false;
@ -101,310 +119,336 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
super.build(context);
_setEffects(context);
return Stack(
children: [
KeyboardActions(
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: cryptoAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
// return Stack(
// children: [
// return KeyboardActions(
// config: KeyboardActionsConfig(
// keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
// keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
// nextFocus: false,
// actions: [
// KeyboardActionsItem(
// focusNode: cryptoAmountFocus,
// toolbarButtons: [(_) => KeyboardDoneButton()],
// ),
// KeyboardActionsItem(
// focusNode: fiatAmountFocus,
// toolbarButtons: [(_) => KeyboardDoneButton()],
// )
// ],
// ),
// // child: Container(
// // height: 0,
// // color: Colors.transparent,
// // ), child:
// child: SizedBox(
// height: 100,
// width: 100,
// child: Text('Send Card'),
// ),
// );
return Container(
decoration: responsiveLayoutUtil.shouldRenderMobileUI
? BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
gradient: LinearGradient(
colors: [
Theme.of(context).extension<SendPageTheme>()!.firstGradientColor,
Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
KeyboardActionsItem(
focusNode: fiatAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
)
],
),
child: Container(
height: 0,
color: Colors.transparent,
),
)
: null,
child: Padding(
padding: EdgeInsets.fromLTRB(
24,
responsiveLayoutUtil.shouldRenderMobileUI ? 110 : 55,
24,
responsiveLayoutUtil.shouldRenderMobileUI ? 32 : 0,
),
Container(
decoration: responsiveLayoutUtil.shouldRenderMobileUI
? BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
gradient: LinearGradient(
colors: [
Theme.of(context).extension<SendPageTheme>()!.firstGradientColor,
Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
)
: null,
child: Padding(
padding: EdgeInsets.fromLTRB(
24,
responsiveLayoutUtil.shouldRenderMobileUI ? 110 : 55,
24,
responsiveLayoutUtil.shouldRenderMobileUI ? 32 : 0,
),
child: SingleChildScrollView(
child: Observer(
builder: (_) => Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Observer(builder: (_) {
final validator = output.isParsedAddress
? sendViewModel.textValidator
: sendViewModel.addressValidator;
child: Observer(
builder: (_) => Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Observer(builder: (_) {
final validator = output.isParsedAddress
? sendViewModel.textValidator
: sendViewModel.addressValidator;
return AddressTextField(
addressKey: ValueKey('send_page_address_textfield_key'),
focusNode: addressFocusNode,
controller: addressController,
onURIScanned: (uri) {
final paymentRequest = PaymentRequest.fromUri(uri);
addressController.text = paymentRequest.address;
cryptoAmountController.text = paymentRequest.amount;
noteController.text = paymentRequest.note;
},
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode,
AddressTextFieldOption.addressBook
],
buttonColor:
Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
return AddressTextField(
addressKey: ValueKey('send_page_address_textfield_key'),
focusNode: addressFocusNode,
controller: addressController,
onURIScanned: (uri) {
final paymentRequest = PaymentRequest.fromUri(uri);
addressController.text = paymentRequest.address;
cryptoAmountController.text = paymentRequest.amount;
noteController.text = paymentRequest.note;
},
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode,
AddressTextFieldOption.addressBook
],
buttonColor: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
textStyle:
TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
hintStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
onPushPasteButton: (context) async {
output.resetParsedAddress();
await output.fetchParsedAddress(context);
},
onPushAddressBookButton: (context) async {
output.resetParsedAddress();
},
onSelectedContact: (contact) {
output.loadContact(contact);
},
validator: validator,
selectedCurrency: sendViewModel.selectedCryptoCurrency,
);
}),
if (output.isParsedAddress)
Padding(
padding: const EdgeInsets.only(top: 20),
child: BaseTextFormField(
controller: extractedAddressController,
readOnly: true,
borderColor:
Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
textStyle: TextStyle(
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
hintStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color:
Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
onPushPasteButton: (context) async {
output.resetParsedAddress();
await output.fetchParsedAddress(context);
},
onPushAddressBookButton: (context) async {
output.resetParsedAddress();
},
onSelectedContact: (contact) {
output.loadContact(contact);
},
validator: validator,
selectedCurrency: sendViewModel.selectedCryptoCurrency,
);
}),
if (output.isParsedAddress)
Padding(
padding: const EdgeInsets.only(top: 20),
child: BaseTextFormField(
controller: extractedAddressController,
readOnly: true,
borderColor: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldBorderColor,
textStyle: TextStyle(
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
validator: sendViewModel.addressValidator)),
CurrencyAmountTextField(
currencyPickerButtonKey: ValueKey('send_page_currency_picker_button_key'),
amountTextfieldKey: ValueKey('send_page_amount_textfield_key'),
sendAllButtonKey: ValueKey('send_page_send_all_button_key'),
currencyAmountTextFieldWidgetKey:
ValueKey('send_page_crypto_currency_amount_textfield_widget_key'),
selectedCurrency: sendViewModel.selectedCryptoCurrency.title,
amountFocusNode: cryptoAmountFocus,
amountController: cryptoAmountController,
isAmountEditable: true,
onTapPicker: () => _presentPicker(context),
isPickerEnable: sendViewModel.hasMultipleTokens,
tag: sendViewModel.selectedCryptoCurrency.tag,
allAmountButton:
!sendViewModel.isBatchSending && sendViewModel.shouldDisplaySendALL,
currencyValueValidator: output.sendAll
? sendViewModel.allAmountValidator
: sendViewModel.amountValidator,
allAmountCallback: () async => output.setSendAll(sendViewModel.balance)),
Divider(
height: 1,
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
Observer(
builder: (_) => Padding(
padding: EdgeInsets.only(top: 10),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: Text(
S.of(context).available_balance + ':',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldHintColor),
),
),
Text(
sendViewModel.balance,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldHintColor),
)
],
validator: sendViewModel.addressValidator)),
CurrencyAmountTextField(
currencyPickerButtonKey: ValueKey('send_page_currency_picker_button_key'),
amountTextfieldKey: ValueKey('send_page_amount_textfield_key'),
sendAllButtonKey: ValueKey('send_page_send_all_button_key'),
currencyAmountTextFieldWidgetKey:
ValueKey('send_page_crypto_currency_amount_textfield_widget_key'),
selectedCurrency: sendViewModel.selectedCryptoCurrency.title,
amountFocusNode: widget.cryptoAmountFocus,
amountController: cryptoAmountController,
isAmountEditable: true,
onTapPicker: () => _presentPicker(context),
isPickerEnable: sendViewModel.hasMultipleTokens,
tag: sendViewModel.selectedCryptoCurrency.tag,
allAmountButton:
!sendViewModel.isBatchSending && sendViewModel.shouldDisplaySendALL,
currencyValueValidator: output.sendAll
? sendViewModel.allAmountValidator
: sendViewModel.amountValidator,
allAmountCallback: () async => output.setSendAll(sendViewModel.balance)),
Divider(
height: 1,
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
Observer(
builder: (_) => Padding(
padding: EdgeInsets.only(top: 10),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: Text(
S.of(context).available_balance + ':',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color:
Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
),
),
),
if (!sendViewModel.isFiatDisabled)
CurrencyAmountTextField(
amountTextfieldKey: ValueKey('send_page_fiat_amount_textfield_key'),
currencyAmountTextFieldWidgetKey:
ValueKey('send_page_fiat_currency_amount_textfield_widget_key'),
selectedCurrency: sendViewModel.fiat.title,
amountFocusNode: fiatAmountFocus,
amountController: fiatAmountController,
hintText: '0.00',
isAmountEditable: true,
allAmountButton: false),
Divider(
height: 1,
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
Padding(
padding: EdgeInsets.only(top: 20),
child: BaseTextFormField(
key: ValueKey('send_page_note_textfield_key'),
controller: noteController,
keyboardType: TextInputType.multiline,
maxLines: null,
borderColor:
Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
textStyle: TextStyle(
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
hintText: S.of(context).note_optional,
placeholderTextStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
Text(
sendViewModel.balance,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color:
Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
),
),
if (sendViewModel.hasFees)
Observer(
builder: (_) => GestureDetector(
key: ValueKey('send_page_select_fee_priority_button_key'),
onTap: sendViewModel.hasFeesPriority
? () => pickTransactionPriority(context)
: () {},
child: Container(
padding: EdgeInsets.only(top: 24),
)
],
),
),
),
if (!sendViewModel.isFiatDisabled)
CurrencyAmountTextField(
amountTextfieldKey: ValueKey('send_page_fiat_amount_textfield_key'),
currencyAmountTextFieldWidgetKey:
ValueKey('send_page_fiat_currency_amount_textfield_widget_key'),
selectedCurrency: sendViewModel.fiat.title,
amountFocusNode: widget.fiatAmountFocus,
amountController: fiatAmountController,
hintText: '0.00',
isAmountEditable: true,
allAmountButton: false),
Divider(
height: 1,
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
Padding(
padding: EdgeInsets.only(top: 20),
child: BaseTextFormField(
key: ValueKey('send_page_note_textfield_key'),
controller: noteController,
keyboardType: TextInputType.multiline,
maxLines: null,
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
textStyle:
TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
hintText: S.of(context).note_optional,
placeholderTextStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
),
),
if (sendViewModel.hasFees)
Observer(
builder: (_) => GestureDetector(
key: ValueKey('send_page_select_fee_priority_button_key'),
onTap: sendViewModel.hasFeesPriority
? () => pickTransactionPriority(context)
: () {},
child: Container(
padding: EdgeInsets.only(top: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
S.of(context).send_estimated_fee,
style: TextStyle(
fontSize: 12, fontWeight: FontWeight.w500, color: Colors.white),
),
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
S.of(context).send_estimated_fee,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.white),
),
Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
output.estimatedFee.toString() +
' ' +
sendViewModel.currency.toString(),
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
Padding(
padding: EdgeInsets.only(top: 5),
child: sendViewModel.isFiatDisabled
? const SizedBox(height: 14)
: Text(
output.estimatedFeeFiatAmount +
' ' +
sendViewModel.fiat.title,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldHintColor,
),
),
),
],
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
output.estimatedFee.toString() +
' ' +
sendViewModel.currency.toString(),
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.white,
),
Padding(
padding: EdgeInsets.only(top: 2, left: 5),
child: Icon(
Icons.arrow_forward_ios,
size: 12,
color: Colors.white,
),
)
],
),
Padding(
padding: EdgeInsets.only(top: 5),
child: sendViewModel.isFiatDisabled
? const SizedBox(height: 14)
: Text(
output.estimatedFeeFiatAmount +
' ' +
sendViewModel.fiat.title,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.extension<SendPageTheme>()!
.textFieldHintColor,
),
),
),
],
),
Padding(
padding: EdgeInsets.only(top: 2, left: 5),
child: Icon(
Icons.arrow_forward_ios,
size: 12,
color: Colors.white,
),
)
],
),
),
),
)
],
),
if (sendViewModel.hasCoinControl)
Padding(
padding: EdgeInsets.only(top: 6),
child: GestureDetector(
key: ValueKey('send_page_unspent_coin_button_key'),
onTap: () => Navigator.of(context).pushNamed(
Routes.unspentCoinsList,
arguments: widget.sendViewModel.coinTypeToSpendFrom,
),
child: Container(
color: Colors.transparent,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
S.of(context).coin_control,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.white),
),
Icon(
Icons.arrow_forward_ios,
size: 12,
color: Colors.white,
),
],
),
),
),
),
],
),
),
),
),
),
if (sendViewModel.hasCoinControl)
Padding(
padding: EdgeInsets.only(top: 6),
child: GestureDetector(
key: ValueKey('send_page_unspent_coin_button_key'),
onTap: () => Navigator.of(context).pushNamed(
Routes.unspentCoinsList,
arguments: widget.sendViewModel.coinTypeToSpendFrom,
),
child: Container(
color: Colors.transparent,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
S.of(context).coin_control,
style: TextStyle(
fontSize: 12, fontWeight: FontWeight.w600, color: Colors.white),
),
Icon(
Icons.arrow_forward_ios,
size: 12,
color: Colors.white,
),
],
),
),
),
),
if (sendViewModel.currency == CryptoCurrency.ltc)
Observer(
builder: (_) => Padding(
padding: EdgeInsets.only(top: 14),
child: GestureDetector(
key: ValueKey('send_page_unspent_coin_button_key'),
onTap: () {
bool value =
widget.sendViewModel.coinTypeToSpendFrom == UnspentCoinType.any;
sendViewModel.setAllowMwebCoins(!value);
},
child: Container(
color: Colors.transparent,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
StandardCheckbox(
caption: S.of(context).litecoin_mweb_allow_coins,
captionColor: Colors.white,
borderColor: currentTheme.type == ThemeType.bright
? Colors.white
: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
iconColor: currentTheme.type == ThemeType.bright
? Colors.white
: Theme.of(context).primaryColor,
value:
widget.sendViewModel.coinTypeToSpendFrom == UnspentCoinType.any,
onChanged: (bool? value) {
sendViewModel.setAllowMwebCoins(value ?? false);
},
),
],
),
),
),
),
),
],
),
)
],
),
),
);
}

View file

@ -0,0 +1,202 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
const _firstLayoutMaxHeight = 10000.0;
class PageViewHeightAdaptable extends StatefulWidget {
const PageViewHeightAdaptable({
super.key,
required this.controller,
required this.children,
}) : assert(children.length > 0, 'children must not be empty');
final PageController controller;
final List<Widget> children;
@override
State<PageViewHeightAdaptable> createState() => _PageViewHeightAdaptableState();
}
class _PageViewHeightAdaptableState extends State<PageViewHeightAdaptable> {
final _sizes = <int, Size>{};
@override
void didUpdateWidget(PageViewHeightAdaptable oldWidget) {
super.didUpdateWidget(oldWidget);
_sizes.clear();
}
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: widget.controller,
builder: (context, child) => _SizingContainer(
sizes: _sizes,
page: widget.controller.hasClients ? widget.controller.page ?? 0 : 0,
child: child!,
),
child: LayoutBuilder(
builder: (context, constraints) => PageView(
controller: widget.controller,
children: [
for (final (i, child) in widget.children.indexed)
Stack(
alignment: Alignment.topCenter,
clipBehavior: Clip.hardEdge,
children: [
SizedBox.fromSize(size: _sizes[i]),
Positioned(
left: 0,
top: 0,
right: 0,
child: _SizeAware(
child: child,
// don't setState, we'll use it in the layout phase
onSizeLaidOut: (size) {
_sizes[i] = size;
},
),
),
],
),
],
),
),
);
}
}
typedef _OnSizeLaidOutCallback = void Function(Size);
class _SizingContainer extends SingleChildRenderObjectWidget {
const _SizingContainer({
super.child,
required this.sizes,
required this.page,
});
final Map<int, Size> sizes;
final double page;
@override
_RenderSizingContainer createRenderObject(BuildContext context) {
return _RenderSizingContainer(
sizes: sizes,
page: page,
);
}
@override
void updateRenderObject(
BuildContext context,
_RenderSizingContainer renderObject,
) {
renderObject
..sizes = sizes
..page = page;
}
}
class _RenderSizingContainer extends RenderProxyBox {
_RenderSizingContainer({
RenderBox? child,
required Map<int, Size> sizes,
required double page,
}) : _sizes = sizes,
_page = page,
super(child);
Map<int, Size> _sizes;
Map<int, Size> get sizes => _sizes;
set sizes(Map<int, Size> value) {
if (_sizes == value) return;
_sizes = value;
markNeedsLayout();
}
double _page;
double get page => _page;
set page(double value) {
if (_page == value) return;
_page = value;
markNeedsLayout();
}
@override
void performLayout() {
if (child case final child?) {
child.layout(
constraints.copyWith(
minWidth: constraints.maxWidth,
minHeight: 0,
maxHeight: constraints.hasBoundedHeight ? null : _firstLayoutMaxHeight,
),
parentUsesSize: true,
);
final a = sizes[page.floor()]!;
final b = sizes[page.ceil()]!;
final height = lerpDouble(a.height, b.height, page - page.floor());
child.layout(
constraints.copyWith(minHeight: height, maxHeight: height),
parentUsesSize: true,
);
size = child.size;
} else {
size = computeSizeForNoChild(constraints);
}
}
}
class _SizeAware extends SingleChildRenderObjectWidget {
const _SizeAware({
required Widget child,
required this.onSizeLaidOut,
}) : super(child: child);
final _OnSizeLaidOutCallback onSizeLaidOut;
@override
_RenderSizeAware createRenderObject(BuildContext context) {
return _RenderSizeAware(
onSizeLaidOut: onSizeLaidOut,
);
}
@override
void updateRenderObject(BuildContext context, _RenderSizeAware renderObject) {
renderObject.onSizeLaidOut = onSizeLaidOut;
}
}
class _RenderSizeAware extends RenderProxyBox {
_RenderSizeAware({
RenderBox? child,
required _OnSizeLaidOutCallback onSizeLaidOut,
}) : _onSizeLaidOut = onSizeLaidOut,
super(child);
_OnSizeLaidOutCallback? _onSizeLaidOut;
_OnSizeLaidOutCallback get onSizeLaidOut => _onSizeLaidOut!;
set onSizeLaidOut(_OnSizeLaidOutCallback value) {
if (_onSizeLaidOut == value) return;
_onSizeLaidOut = value;
markNeedsLayout();
}
@override
void performLayout() {
super.performLayout();
onSizeLaidOut(
getDryLayout(
constraints.copyWith(maxHeight: double.infinity),
),
);
}
}

View file

@ -9,6 +9,7 @@ class StandardCheckbox extends StatelessWidget {
this.gradientBackground = false,
this.borderColor,
this.iconColor,
this.captionColor,
required this.onChanged});
final bool value;
@ -16,6 +17,7 @@ class StandardCheckbox extends StatelessWidget {
final bool gradientBackground;
final Color? borderColor;
final Color? iconColor;
final Color? captionColor;
final Function(bool) onChanged;
@override
@ -68,7 +70,7 @@ class StandardCheckbox extends StatelessWidget {
fontSize: 16.0,
fontFamily: 'Lato',
fontWeight: FontWeight.normal,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
color: captionColor ?? Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
),

View file

@ -77,7 +77,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
this.transactionDescriptionBox,
this.ledgerViewModel,
this.unspentCoinsListViewModel, {
this.coinTypeToSpendFrom = UnspentCoinType.any,
this.coinTypeToSpendFrom = UnspentCoinType.nonMweb,
}) : state = InitialExecutionState(),
currencies = appStore.wallet!.balance.keys.toList(),
selectedCryptoCurrency = appStore.wallet!.currency,
@ -112,7 +112,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
ObservableList<Output> outputs;
final UnspentCoinType coinTypeToSpendFrom;
@observable
UnspentCoinType coinTypeToSpendFrom;
bool get showAddressBookPopup => _settingsStore.showAddressBookPopupEnabled;
@ -135,6 +136,13 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
addOutput();
}
@action
void setAllowMwebCoins(bool allow) {
if (wallet.type == WalletType.litecoin) {
coinTypeToSpendFrom = allow ? UnspentCoinType.any : UnspentCoinType.nonMweb;
}
}
@computed
bool get isBatchSending => outputs.length > 1;

View file

@ -394,6 +394,7 @@
"light_theme": "فاتح",
"litecoin_enable_mweb_sync": "تمكين MWEB المسح الضوئي",
"litecoin_mweb": "mweb",
"litecoin_mweb_allow_coins": "السماح للعملات المعدنية MWEB",
"litecoin_mweb_always_scan": "اضبط MWEB دائمًا على المسح الضوئي",
"litecoin_mweb_description": "MWEB هو بروتوكول جديد يجلب معاملات أسرع وأرخص وأكثر خصوصية إلى Litecoin",
"litecoin_mweb_dismiss": "رفض",

View file

@ -394,6 +394,7 @@
"light_theme": "Светло",
"litecoin_enable_mweb_sync": "Активирайте сканирането на MWeb",
"litecoin_mweb": "Mweb",
"litecoin_mweb_allow_coins": "Позволете на MWeb монети",
"litecoin_mweb_always_scan": "Задайте MWeb винаги сканиране",
"litecoin_mweb_description": "MWeb е нов протокол, който носи по -бърз, по -евтин и повече частни транзакции на Litecoin",
"litecoin_mweb_dismiss": "Уволнение",

View file

@ -394,6 +394,7 @@
"light_theme": "Světlý",
"litecoin_enable_mweb_sync": "Povolit skenování MWeb",
"litecoin_mweb": "MWeb",
"litecoin_mweb_allow_coins": "Povolte mweb mince",
"litecoin_mweb_always_scan": "Nastavit MWeb vždy skenování",
"litecoin_mweb_description": "MWEB je nový protokol, který do Litecoin přináší rychlejší, levnější a více soukromých transakcí",
"litecoin_mweb_dismiss": "Propustit",

View file

@ -394,6 +394,7 @@
"light_theme": "Hell",
"litecoin_enable_mweb_sync": "Aktivieren Sie das MWEB-Scannen",
"litecoin_mweb": "MWeb",
"litecoin_mweb_allow_coins": "MWEB -Münzen zulassen",
"litecoin_mweb_always_scan": "Setzen Sie MWeb immer scannen",
"litecoin_mweb_description": "MWWB ist ein neues Protokoll, das schnellere, billigere und privatere Transaktionen zu Litecoin bringt",
"litecoin_mweb_dismiss": "Zurückweisen",

View file

@ -394,6 +394,7 @@
"light_theme": "Light",
"litecoin_enable_mweb_sync": "Enable MWEB scanning",
"litecoin_mweb": "MWEB",
"litecoin_mweb_allow_coins": "Allow MWEB coins",
"litecoin_mweb_always_scan": "Set MWEB always scanning",
"litecoin_mweb_description": "MWEB is a new protocol that brings faster, cheaper, and more private transactions to Litecoin",
"litecoin_mweb_dismiss": "Dismiss",

View file

@ -394,6 +394,7 @@
"light_theme": "Ligero",
"litecoin_enable_mweb_sync": "Habilitar el escaneo mweb",
"litecoin_mweb": "Mweb",
"litecoin_mweb_allow_coins": "Permitir monedas mweb",
"litecoin_mweb_always_scan": "Establecer mweb siempre escaneo",
"litecoin_mweb_description": "Mweb es un nuevo protocolo que trae transacciones más rápidas, más baratas y más privadas a Litecoin",
"litecoin_mweb_dismiss": "Despedir",

View file

@ -394,6 +394,7 @@
"light_theme": "Clair",
"litecoin_enable_mweb_sync": "Activer la numérisation MWEB",
"litecoin_mweb": "Mweb",
"litecoin_mweb_allow_coins": "Autoriser les pièces MWeb",
"litecoin_mweb_always_scan": "Définir MWEB Score Scanning",
"litecoin_mweb_description": "MWEB est un nouveau protocole qui apporte des transactions plus rapides, moins chères et plus privées à Litecoin",
"litecoin_mweb_dismiss": "Rejeter",

View file

@ -394,6 +394,7 @@
"light_theme": "Haske",
"litecoin_enable_mweb_sync": "Kunna binciken Mweb",
"litecoin_mweb": "Mweb",
"litecoin_mweb_allow_coins": "Bada izinin Coins na Mweb",
"litecoin_mweb_always_scan": "Saita Mweb koyaushe",
"litecoin_mweb_description": "Mweb shine sabon tsarin yarjejeniya da ya kawo da sauri, mai rahusa, da kuma ma'amaloli masu zaman kansu zuwa Litecoin",
"litecoin_mweb_dismiss": "Tuɓe \\ sallama",

View file

@ -394,6 +394,7 @@
"light_theme": "रोशनी",
"litecoin_enable_mweb_sync": "MWEB स्कैनिंग सक्षम करें",
"litecoin_mweb": "मावली",
"litecoin_mweb_allow_coins": "MWEB सिक्कों की अनुमति दें",
"litecoin_mweb_always_scan": "MWEB हमेशा स्कैनिंग सेट करें",
"litecoin_mweb_description": "MWEB एक नया प्रोटोकॉल है जो लिटकोइन के लिए तेजी से, सस्ता और अधिक निजी लेनदेन लाता है",
"litecoin_mweb_dismiss": "नकार देना",

View file

@ -394,6 +394,7 @@
"light_theme": "Svijetla",
"litecoin_enable_mweb_sync": "Omogućite MWEB skeniranje",
"litecoin_mweb": "MWeb",
"litecoin_mweb_allow_coins": "Dopustite MWeb kovanice",
"litecoin_mweb_always_scan": "Postavite MWeb uvijek skeniranje",
"litecoin_mweb_description": "MWEB je novi protokol koji u Litecoin donosi brže, jeftinije i privatnije transakcije",
"litecoin_mweb_dismiss": "Odbaciti",

View file

@ -394,6 +394,7 @@
"light_theme": "Լուսավոր",
"litecoin_enable_mweb_sync": "Միացնել MWEB սկան",
"litecoin_mweb": "Մուեբ",
"litecoin_mweb_allow_coins": "Թույլ տվեք MWeb մետաղադրամներ",
"litecoin_mweb_always_scan": "Սահմանեք Mweb Միշտ սկանավորում",
"litecoin_mweb_description": "Mweb- ը նոր արձանագրություն է, որը բերում է ավելի արագ, ավելի էժան եւ ավելի մասնավոր գործարքներ դեպի LITECOIN",
"litecoin_mweb_dismiss": "Հեռացնել",

View file

@ -394,6 +394,7 @@
"light_theme": "Terang",
"litecoin_enable_mweb_sync": "Aktifkan pemindaian MWEB",
"litecoin_mweb": "Mweb",
"litecoin_mweb_allow_coins": "Izinkan koin mWeb",
"litecoin_mweb_always_scan": "Atur mWeb selalu memindai",
"litecoin_mweb_description": "MWEB adalah protokol baru yang membawa transaksi yang lebih cepat, lebih murah, dan lebih pribadi ke Litecoin",
"litecoin_mweb_dismiss": "Membubarkan",

View file

@ -394,6 +394,7 @@
"light_theme": "Chiaro",
"litecoin_enable_mweb_sync": "Abilita la scansione MWeb",
"litecoin_mweb": "MWeb",
"litecoin_mweb_allow_coins": "Consenti monete mWeb",
"litecoin_mweb_always_scan": "Imposta MWeb per scansionare sempre",
"litecoin_mweb_description": "MWeb è un nuovo protocollo che porta transazioni più veloci, più economiche e più private a Litecoin",
"litecoin_mweb_dismiss": "Chiudi",

View file

@ -395,6 +395,7 @@
"light_theme": "光",
"litecoin_enable_mweb_sync": "MWEBスキャンを有効にします",
"litecoin_mweb": "mweb",
"litecoin_mweb_allow_coins": "MWEBコインを許可します",
"litecoin_mweb_always_scan": "MWEBを常にスキャンします",
"litecoin_mweb_description": "MWEBは、Litecoinにより速く、より安価で、よりプライベートなトランザクションをもたらす新しいプロトコルです",
"litecoin_mweb_dismiss": "却下する",

View file

@ -394,6 +394,7 @@
"light_theme": "빛",
"litecoin_enable_mweb_sync": "mweb 스캔을 활성화합니다",
"litecoin_mweb": "mweb",
"litecoin_mweb_allow_coins": "mweb 코인을 허용하십시오",
"litecoin_mweb_always_scan": "mweb는 항상 스캔을 설정합니다",
"litecoin_mweb_description": "MWEB는 Litecoin에 더 빠르고 저렴하며 개인 거래를 제공하는 새로운 프로토콜입니다.",
"litecoin_mweb_dismiss": "해고하다",

View file

@ -394,6 +394,7 @@
"light_theme": "အလင်း",
"litecoin_enable_mweb_sync": "mweb scanning ဖွင့်ပါ",
"litecoin_mweb": "မင်္ဂလာပါ",
"litecoin_mweb_allow_coins": "mweb ဒင်္ဂါးများကိုခွင့်ပြုပါ",
"litecoin_mweb_always_scan": "Mweb အမြဲစကင်ဖတ်စစ်ဆေးပါ",
"litecoin_mweb_description": "Mweb သည် Protocol အသစ်ဖြစ်ပြီး LitCoin သို့ပိုမိုဈေးချိုသာသော, စျေးသက်သက်သာသာသုံးခြင်းနှင့်ပိုမိုများပြားသောပုဂ္ဂလိကငွေပို့ဆောင်မှုများကိုဖြစ်ပေါ်စေသည်",
"litecoin_mweb_dismiss": "ထုတ်ပစ်",

View file

@ -394,6 +394,7 @@
"light_theme": "Licht",
"litecoin_enable_mweb_sync": "MWEB -scanning inschakelen",
"litecoin_mweb": "Mweb",
"litecoin_mweb_allow_coins": "Sta mweb munten toe",
"litecoin_mweb_always_scan": "Stel mweb altijd op scannen",
"litecoin_mweb_description": "MWEB is een nieuw protocol dat snellere, goedkopere en meer privé -transacties naar Litecoin brengt",
"litecoin_mweb_dismiss": "Afwijzen",

View file

@ -394,6 +394,7 @@
"light_theme": "Jasny",
"litecoin_enable_mweb_sync": "Włącz skanowanie MWEB",
"litecoin_mweb": "MWEB",
"litecoin_mweb_allow_coins": "Zezwalaj na monety MWEB",
"litecoin_mweb_always_scan": "Ustaw MWEB zawsze skanowanie",
"litecoin_mweb_description": "MWEB to nowy protokół, który przynosi szybciej, tańsze i bardziej prywatne transakcje do Litecoin",
"litecoin_mweb_dismiss": "Odrzucać",

View file

@ -394,6 +394,7 @@
"light_theme": "Luz",
"litecoin_enable_mweb_sync": "Ativar digitalização do MWEB",
"litecoin_mweb": "Mweb",
"litecoin_mweb_allow_coins": "Permitir moedas MWEB",
"litecoin_mweb_always_scan": "Definir mweb sempre digitalizando",
"litecoin_mweb_description": "MWEB é um novo protocolo que traz transações mais rápidas, baratas e mais privadas para o Litecoin",
"litecoin_mweb_dismiss": "Liberar",

View file

@ -394,6 +394,7 @@
"light_theme": "Светлая",
"litecoin_enable_mweb_sync": "Включить MWEB сканирование",
"litecoin_mweb": "Мвеб",
"litecoin_mweb_allow_coins": "Разрешить монеты MWEB",
"litecoin_mweb_always_scan": "Установить MWEB всегда сканирование",
"litecoin_mweb_description": "MWEB - это новый протокол, который приносит быстрее, дешевле и более частные транзакции в Litecoin",
"litecoin_mweb_dismiss": "Увольнять",

View file

@ -394,6 +394,7 @@
"light_theme": "สว่าง",
"litecoin_enable_mweb_sync": "เปิดใช้งานการสแกน MWEB",
"litecoin_mweb": "mweb",
"litecoin_mweb_allow_coins": "อนุญาตให้เหรียญ MWEB",
"litecoin_mweb_always_scan": "ตั้งค่าการสแกน MWEB เสมอ",
"litecoin_mweb_description": "MWEB เป็นโปรโตคอลใหม่ที่นำการทำธุรกรรมที่เร็วกว่าราคาถูกกว่าและเป็นส่วนตัวมากขึ้นไปยัง Litecoin",
"litecoin_mweb_dismiss": "อนุญาตให้ออกไป",

View file

@ -394,6 +394,7 @@
"light_theme": "Light",
"litecoin_enable_mweb_sync": "Paganahin ang pag -scan ng MWeb",
"litecoin_mweb": "Mweb",
"litecoin_mweb_allow_coins": "Payagan ang mga barya ng MWEB",
"litecoin_mweb_always_scan": "Itakda ang MWeb na laging nag -scan",
"litecoin_mweb_description": "Ang MWeb ay isang bagong protocol na nagdadala ng mas mabilis, mas mura, at mas maraming pribadong mga transaksyon sa Litecoin",
"litecoin_mweb_dismiss": "Tanggalin",

View file

@ -394,6 +394,7 @@
"light_theme": "Aydınlık",
"litecoin_enable_mweb_sync": "MWEB taramasını etkinleştir",
"litecoin_mweb": "Mweb",
"litecoin_mweb_allow_coins": "MWEB Coins'e izin ver",
"litecoin_mweb_always_scan": "MWEB'i her zaman taramayı ayarlayın",
"litecoin_mweb_description": "MWEB, Litecoin'e daha hızlı, daha ucuz ve daha fazla özel işlem getiren yeni bir protokoldür",
"litecoin_mweb_dismiss": "Azletmek",

View file

@ -394,6 +394,7 @@
"light_theme": "Світла",
"litecoin_enable_mweb_sync": "Увімкнути сканування MWEB",
"litecoin_mweb": "Мвеб",
"litecoin_mweb_allow_coins": "Дозволити монети MWEB",
"litecoin_mweb_always_scan": "Встановити mweb завжди сканувати",
"litecoin_mweb_description": "MWEB - це новий протокол, який приносить швидкі, дешевші та більш приватні транзакції Litecoin",
"litecoin_mweb_dismiss": "Звільнити",

View file

@ -394,6 +394,7 @@
"light_theme": "روشنی",
"litecoin_enable_mweb_sync": "MWEB اسکیننگ کو فعال کریں",
"litecoin_mweb": "MWEB",
"litecoin_mweb_allow_coins": "MWEB سکے کی اجازت دیں",
"litecoin_mweb_always_scan": "MWEB ہمیشہ اسکیننگ سیٹ کریں",
"litecoin_mweb_description": "MWEB ایک نیا پروٹوکول ہے جو لیٹیکوئن میں تیز ، سستا اور زیادہ نجی لین دین لاتا ہے",
"litecoin_mweb_dismiss": "خارج",

View file

@ -393,6 +393,7 @@
"light_theme": "Chủ đề sáng",
"litecoin_enable_mweb_sync": "Bật quét MWEB",
"litecoin_mweb": "Mweb",
"litecoin_mweb_allow_coins": "Cho phép tiền xu MWEB",
"litecoin_mweb_always_scan": "Đặt MWEB luôn quét",
"litecoin_mweb_description": "MWEB là một giao thức mới mang lại các giao dịch nhanh hơn, rẻ hơn và riêng tư hơn cho Litecoin",
"litecoin_mweb_dismiss": "Miễn nhiệm",

View file

@ -395,6 +395,7 @@
"light_theme": "Funfun bí eérú",
"litecoin_enable_mweb_sync": "Mu mweb ọlọjẹ",
"litecoin_mweb": "Mweb",
"litecoin_mweb_allow_coins": "Gba awọn owo Mweb gba",
"litecoin_mweb_always_scan": "Ṣeto mweb nigbagbogbo n ṣayẹwo",
"litecoin_mweb_description": "Mweb jẹ ilana ilana tuntun ti o mu iyara wa yiyara, din owo, ati awọn iṣowo ikọkọ diẹ sii si Livcoin",
"litecoin_mweb_dismiss": "Tuka",

View file

@ -394,6 +394,7 @@
"light_theme": "艳丽",
"litecoin_enable_mweb_sync": "启用MWEB扫描",
"litecoin_mweb": "MWEB",
"litecoin_mweb_allow_coins": "允许MWEB硬币",
"litecoin_mweb_always_scan": "设置MWEB总是扫描",
"litecoin_mweb_description": "MWEB是一项新协议它将更快更便宜和更多的私人交易带给Litecoin",
"litecoin_mweb_dismiss": "解雇",