From 1c8af1afae756306ca68a7c91fca6a8064812ee0 Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Mon, 10 Mar 2025 16:37:23 -0700 Subject: [PATCH] Mweb checkbox (#2000) * [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 Co-authored-by: Omar Hatem --- cw_bitcoin/lib/electrum_wallet.dart | 4 +- lib/di.dart | 2 +- lib/src/screens/send/send_page.dart | 594 ++++++++++--------- lib/src/screens/send/widgets/send_card.dart | 620 +++++++++++--------- lib/src/widgets/adaptable_page_view.dart | 202 +++++++ lib/src/widgets/standard_checkbox.dart | 4 +- lib/view_model/send/send_view_model.dart | 12 +- res/values/strings_ar.arb | 1 + res/values/strings_bg.arb | 1 + res/values/strings_cs.arb | 1 + res/values/strings_de.arb | 1 + res/values/strings_en.arb | 1 + res/values/strings_es.arb | 1 + res/values/strings_fr.arb | 1 + res/values/strings_ha.arb | 1 + res/values/strings_hi.arb | 1 + res/values/strings_hr.arb | 1 + res/values/strings_hy.arb | 1 + res/values/strings_id.arb | 1 + res/values/strings_it.arb | 1 + res/values/strings_ja.arb | 1 + res/values/strings_ko.arb | 1 + res/values/strings_my.arb | 1 + res/values/strings_nl.arb | 1 + res/values/strings_pl.arb | 1 + res/values/strings_pt.arb | 1 + res/values/strings_ru.arb | 1 + res/values/strings_th.arb | 1 + res/values/strings_tl.arb | 1 + res/values/strings_tr.arb | 1 + res/values/strings_uk.arb | 1 + res/values/strings_ur.arb | 1 + res/values/strings_vi.arb | 1 + res/values/strings_yo.arb | 1 + res/values/strings_zh.arb | 1 + 35 files changed, 885 insertions(+), 581 deletions(-) create mode 100644 lib/src/widgets/adaptable_page_view.dart diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 64595b253..fd778571f 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -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]; diff --git a/lib/di.dart b/lib/di.dart index 83b3efaea..ccd0908d1 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -748,7 +748,7 @@ Future setup({ getIt.get(), _transactionDescriptionBox, getIt.get().wallet!.isHardwareWallet ? getIt.get() : null, - coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.any, + coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.nonMweb, getIt.get(param1: coinTypeToSpendFrom), ), ); diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index d3c741c0c..7e7080d0f 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -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: [ - 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 sendCards = []; + List 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()!.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: [ + 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()! + .indicatorDotColor, + activeDotColor: Theme.of(context) + .extension()! + .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: [ + 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( + 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()! - .indicatorDotColor, - activeDotColor: Theme.of(context) - .extension()! - .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()!.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()!.hintTextColor, + isDottedBorder: true, + borderColor: Theme.of(context) + .extension()! + .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: [ - 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( - 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()!.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()!.hintTextColor, - isDottedBorder: true, - borderColor: - Theme.of(context).extension()!.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(); diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index 24cbd2061..f1cac5c9f 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -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 with AutomaticKeepAliveClientMixin { - 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 with AutomaticKeepAliveClientMixin with AutomaticKeepAliveClientMixin()!.keyboardBarColor, - nextFocus: false, - actions: [ - KeyboardActionsItem( - focusNode: cryptoAmountFocus, - toolbarButtons: [(_) => KeyboardDoneButton()], + // return Stack( + // children: [ + // return KeyboardActions( + // config: KeyboardActionsConfig( + // keyboardActionsPlatform: KeyboardActionsPlatform.IOS, + // keyboardBarColor: Theme.of(context).extension()!.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()!.firstGradientColor, + Theme.of(context).extension()!.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()!.firstGradientColor, - Theme.of(context).extension()!.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: [ - Observer(builder: (_) { - final validator = output.isParsedAddress - ? sendViewModel.textValidator - : sendViewModel.addressValidator; + child: Observer( + builder: (_) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + 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()!.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()!.textFieldButtonColor, + borderColor: Theme.of(context).extension()!.textFieldBorderColor, + textStyle: + TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white), + hintStyle: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.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()!.textFieldBorderColor, textStyle: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white), - hintStyle: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: - Theme.of(context).extension()!.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()! - .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()!.textFieldHintColor), - Observer( - builder: (_) => Padding( - padding: EdgeInsets.only(top: 10), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - S.of(context).available_balance + ':', - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Theme.of(context) - .extension()! - .textFieldHintColor), - ), - ), - Text( - sendViewModel.balance, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Theme.of(context) - .extension()! - .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()!.textFieldHintColor), + Observer( + builder: (_) => Padding( + padding: EdgeInsets.only(top: 10), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + S.of(context).available_balance + ':', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: + Theme.of(context).extension()!.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()!.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()!.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()!.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()!.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()!.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()!.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: [ + 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: [ - Text( - S.of(context).send_estimated_fee, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Colors.white), - ), - Container( - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - 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()! - .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()! + .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()!.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); + }, + ), + ], + ), + ), + ), + ), + ), + ], ), - ) - ], + ), + ), ); } diff --git a/lib/src/widgets/adaptable_page_view.dart b/lib/src/widgets/adaptable_page_view.dart new file mode 100644 index 000000000..c6800ae22 --- /dev/null +++ b/lib/src/widgets/adaptable_page_view.dart @@ -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 children; + + @override + State createState() => _PageViewHeightAdaptableState(); +} + +class _PageViewHeightAdaptableState extends State { + final _sizes = {}; + + @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 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 sizes, + required double page, + }) : _sizes = sizes, + _page = page, + super(child); + + Map _sizes; + Map get sizes => _sizes; + set sizes(Map 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), + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/widgets/standard_checkbox.dart b/lib/src/widgets/standard_checkbox.dart index d61b84d1d..2b06ff1c6 100644 --- a/lib/src/widgets/standard_checkbox.dart +++ b/lib/src/widgets/standard_checkbox.dart @@ -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()!.titleColor, + color: captionColor ?? Theme.of(context).extension()!.titleColor, decoration: TextDecoration.none, ), ), diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 998521ede..3dce212af 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -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 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; diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 84f0c17ce..faf14e18d 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -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": "رفض", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index c67d8915a..7ac75f00e 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -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": "Уволнение", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 349ae67bc..f777ffe60 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -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", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 895fa4183..35012b20d 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -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", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index c03841cb9..6875794fd 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -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", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index dcff955bb..42e7eedc0 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -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", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 379290a29..4960d980a 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -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", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 125ceeaf5..068457568 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -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", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 074f3ba09..248b0230c 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -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": "नकार देना", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index be447f24d..5c83942a1 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -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", diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index 551e28cef..2719015cc 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -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": "Հեռացնել", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index a171f3243..ccf4367e3 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -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", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index f035c3493..a1682a11f 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -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", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 89fcc5391..f97baf7b4 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -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": "却下する", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index e8e23a1a2..75af5c195 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -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": "해고하다", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index c21413a44..9f200a030 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -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": "ထုတ်ပစ်", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 0b32a7fc9..1b41eea22 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -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", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index b5acc57ed..8b96bde04 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -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ć", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 8ec99f0f8..096c3f413 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -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", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 8ef7ac579..6fe82abcf 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -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": "Увольнять", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 414bbe52f..35e94e508 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -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": "อนุญาตให้ออกไป", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 46a7540bd..d9cdbbb3e 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -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", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 8fcacb9ba..5d7c77050 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -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", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 6b111f72f..1a6d113b0 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -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": "Звільнити", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index d1a2b4ca9..a7b70dae2 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -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": "خارج", diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index 2aba61ef3..ff547e77b 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -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", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 744159a30..6eb66754c 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -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", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index a3b5ec421..ed890c17a 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -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": "解雇",