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 <tuxsudo@tux.pizza> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

Matthew Fosse committed Mar 10, 2025 at 16:37 UTC 1c8af1afae756306ca68a7c91fca6a8064812ee0
35 files changed +886 -582
cw_bitcoin/lib/electrum_wallet.dart
+2 -2
@@ -632,8 +632,8 @@ abstract class ElectrumWalletBase
632 }).toList();
633 final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList();
634
635 - // sort the unconfirmed coins so that mweb coins are first:
636 - availableInputs.sort((a, b) => a.bitcoinAddressRecord.type == SegwitAddresType.mweb ? -1 : 1);
635 + // sort the unconfirmed coins so that mweb coins are last:
636 + availableInputs.sort((a, b) => a.bitcoinAddressRecord.type == SegwitAddresType.mweb ? 1 : -1);
637
638 for (int i = 0; i < availableInputs.length; i++) {
639 final utx = availableInputs[i];
lib/di.dart
+1 -1
@@ -748,7 +748,7 @@ Future<void> setup({
748 getIt.get<ContactListViewModel>(),
749 _transactionDescriptionBox,
750 getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null,
751 - coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.any,
751 + coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.nonMweb,
752 getIt.get<UnspentCoinsListViewModel>(param1: coinTypeToSpendFrom),
753 ),
754 );
lib/src/screens/send/send_page.dart
+311 -291
@@ -14,14 +14,17 @@ import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart'
14 import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
15 import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
16 import 'package:cake_wallet/src/screens/send/widgets/send_card.dart';
17 +import 'package:cake_wallet/src/widgets/adaptable_page_view.dart';
18 import 'package:cake_wallet/src/widgets/add_template_button.dart';
19 import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
20 import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
21 +import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
22 import 'package:cake_wallet/src/widgets/picker.dart';
23 import 'package:cake_wallet/src/widgets/primary_button.dart';
24 import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
25 import 'package:cake_wallet/src/widgets/template_tile.dart';
26 import 'package:cake_wallet/src/widgets/trail_button.dart';
27 +import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
28 import 'package:cake_wallet/themes/extensions/seed_widget_theme.dart';
29 import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
30 import 'package:cake_wallet/themes/theme_base.dart';
@@ -38,6 +41,7 @@ import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
41 import 'package:cw_core/crypto_currency.dart';
42 import 'package:flutter/material.dart';
43 import 'package:flutter_mobx/flutter_mobx.dart';
44 +import 'package:keyboard_actions/keyboard_actions.dart';
45 import 'package:mobx/mobx.dart';
46 import 'package:smooth_page_indicator/smooth_page_indicator.dart';
47 import 'package:url_launcher/url_launcher.dart';
@@ -93,7 +97,7 @@ class SendPage extends BasePage {
97 return MergeSemantics(
98 child: SizedBox(
99 height: isMobileView ? 37 : 45,
96 - width: isMobileView ? 37 : 45,
100 + width: isMobileView ? 47: 45,
101 child: ButtonTheme(
102 minWidth: double.minPositive,
103 child: Semantics(
@@ -114,18 +118,6 @@ class SendPage extends BasePage {
118 @override
119 AppBarStyle get appBarStyle => AppBarStyle.transparent;
120
117 - double _sendCardHeight(BuildContext context) {
118 - double initialHeight = 480;
119 - if (sendViewModel.hasCoinControl) {
120 - initialHeight += 55;
121 - }
122 -
123 - if (!responsiveLayoutUtil.shouldRenderMobileUI) {
124 - return initialHeight - 66;
125 - }
126 - return initialHeight;
127 - }
128 -
121 @override
122 void onClose(BuildContext context) {
123 sendViewModel.onClose();
@@ -174,285 +166,316 @@ class SendPage extends BasePage {
166 Widget body(BuildContext context) {
167 _setEffects(context);
168
177 - return GestureDetector(
178 - onLongPress: () =>
179 - sendViewModel.balanceViewModel.isReversing = !sendViewModel.balanceViewModel.isReversing,
180 - onLongPressUp: () =>
181 - sendViewModel.balanceViewModel.isReversing = !sendViewModel.balanceViewModel.isReversing,
182 - child: Form(
183 - key: _formKey,
184 - child: ScrollableWithBottomSection(
185 - contentPadding: EdgeInsets.only(bottom: 24),
186 - content: FocusTraversalGroup(
187 - policy: OrderedTraversalPolicy(),
188 - child: Column(
189 - children: <Widget>[
190 - Container(
191 - height: _sendCardHeight(context),
192 - child: Observer(
193 - builder: (_) {
194 - return PageView.builder(
195 - scrollDirection: Axis.horizontal,
196 - controller: controller,
197 - itemCount: sendViewModel.outputs.length,
198 - itemBuilder: (context, index) {
199 - final output = sendViewModel.outputs[index];
200 -
201 - return SendCard(
202 - key: output.key,
203 - output: output,
204 - sendViewModel: sendViewModel,
205 - initialPaymentRequest: initialPaymentRequest,
169 + return Observer(builder: (_) {
170 + List<Widget> sendCards = [];
171 + List<KeyboardActionsItem> keyboardActions = [];
172 + for (var output in sendViewModel.outputs) {
173 + var cryptoAmountFocus = FocusNode();
174 + var fiatAmountFocus = FocusNode();
175 + sendCards.add(SendCard(
176 + currentTheme: currentTheme,
177 + key: output.key,
178 + output: output,
179 + sendViewModel: sendViewModel,
180 + initialPaymentRequest: initialPaymentRequest,
181 + cryptoAmountFocus: cryptoAmountFocus,
182 + fiatAmountFocus: fiatAmountFocus,
183 + ));
184 + keyboardActions.add(KeyboardActionsItem(
185 + focusNode: cryptoAmountFocus, toolbarButtons: [(_) => KeyboardDoneButton()]));
186 + keyboardActions.add(KeyboardActionsItem(
187 + focusNode: fiatAmountFocus, toolbarButtons: [(_) => KeyboardDoneButton()]));
188 + }
189 + return Stack(
190 + children: [
191 + KeyboardActions(
192 + config: KeyboardActionsConfig(
193 + keyboardActionsPlatform: KeyboardActionsPlatform.ALL,
194 + keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
195 + nextFocus: false,
196 + actions: keyboardActions,
197 + ),
198 + child: Container(
199 + height: 0,
200 + color: Colors.transparent,
201 + ),
202 + ),
203 + GestureDetector(
204 + onLongPress: () => sendViewModel.balanceViewModel.isReversing =
205 + !sendViewModel.balanceViewModel.isReversing,
206 + onLongPressUp: () => sendViewModel.balanceViewModel.isReversing =
207 + !sendViewModel.balanceViewModel.isReversing,
208 + child: Form(
209 + key: _formKey,
210 + child: ScrollableWithBottomSection(
211 + contentPadding: EdgeInsets.only(bottom: 24),
212 + content: FocusTraversalGroup(
213 + policy: OrderedTraversalPolicy(),
214 + child: Column(
215 + children: <Widget>[
216 + PageViewHeightAdaptable(
217 + controller: controller,
218 + children: sendCards,
219 + ),
220 + SizedBox(height: 10),
221 + Padding(
222 + padding: EdgeInsets.only(left: 24, right: 24, bottom: 10),
223 + child: Container(
224 + height: 10,
225 + child: Observer(
226 + builder: (_) {
227 + final count = sendViewModel.outputs.length;
228 +
229 + return count > 1
230 + ? Semantics(
231 + label: 'Page Indicator',
232 + hint: 'Swipe to change receiver',
233 + excludeSemantics: true,
234 + child: SmoothPageIndicator(
235 + controller: controller,
236 + count: count,
237 + effect: ScrollingDotsEffect(
238 + spacing: 6.0,
239 + radius: 6.0,
240 + dotWidth: 6.0,
241 + dotHeight: 6.0,
242 + dotColor: Theme.of(context)
243 + .extension<SendPageTheme>()!
244 + .indicatorDotColor,
245 + activeDotColor: Theme.of(context)
246 + .extension<SendPageTheme>()!
247 + .templateBackgroundColor),
248 + ))
249 + : Offstage();
250 + },
251 + ),
252 + ),
253 + ),
254 + Container(
255 + height: 40,
256 + width: double.infinity,
257 + padding: EdgeInsets.only(left: 24),
258 + child: SingleChildScrollView(
259 + scrollDirection: Axis.horizontal,
260 + child: Observer(
261 + builder: (_) {
262 + final templates = sendViewModel.templates;
263 + final itemCount = templates.length;
264 +
265 + return Row(
266 + children: <Widget>[
267 + AddTemplateButton(
268 + key: ValueKey('send_page_add_template_button_key'),
269 + onTap: () =>
270 + Navigator.of(context).pushNamed(Routes.sendTemplate),
271 + currentTemplatesLength: templates.length,
272 + ),
273 + ListView.builder(
274 + scrollDirection: Axis.horizontal,
275 + shrinkWrap: true,
276 + physics: NeverScrollableScrollPhysics(),
277 + itemCount: itemCount,
278 + itemBuilder: (context, index) {
279 + final template = templates[index];
280 + return TemplateTile(
281 + key: UniqueKey(),
282 + to: template.name,
283 + hasMultipleRecipients:
284 + template.additionalRecipients != null &&
285 + template.additionalRecipients!.length > 1,
286 + amount: template.isCurrencySelected
287 + ? template.amount
288 + : template.amountFiat,
289 + from: template.isCurrencySelected
290 + ? template.cryptoCurrency
291 + : template.fiatCurrency,
292 + onTap: () async {
293 + sendViewModel.state = IsExecutingState();
294 + if (template.additionalRecipients?.isNotEmpty ??
295 + false) {
296 + sendViewModel.clearOutputs();
297 +
298 + for (int i = 0;
299 + i < template.additionalRecipients!.length;
300 + i++) {
301 + Output output;
302 + try {
303 + output = sendViewModel.outputs[i];
304 + } catch (e) {
305 + sendViewModel.addOutput();
306 + output = sendViewModel.outputs[i];
307 + }
308 +
309 + await _setInputsFromTemplate(
310 + context,
311 + output: output,
312 + template: template.additionalRecipients![i],
313 + );
314 + }
315 + } else {
316 + final output = _defineCurrentOutput();
317 + await _setInputsFromTemplate(
318 + context,
319 + output: output,
320 + template: template,
321 + );
322 + }
323 + sendViewModel.state = InitialExecutionState();
324 + },
325 + onRemove: () {
326 + showPopUp<void>(
327 + context: context,
328 + builder: (dialogContext) {
329 + return AlertWithTwoActions(
330 + alertTitle: S.of(context).template,
331 + alertContent:
332 + S.of(context).confirm_delete_template,
333 + rightButtonText: S.of(context).delete,
334 + leftButtonText: S.of(context).cancel,
335 + actionRightButton: () {
336 + Navigator.of(dialogContext).pop();
337 + sendViewModel.sendTemplateViewModel
338 + .removeTemplate(template: template);
339 + },
340 + actionLeftButton: () =>
341 + Navigator.of(dialogContext).pop());
342 + },
343 + );
344 + },
345 + );
346 + },
347 + ),
348 + ],
349 );
207 - });
208 - },
209 - )),
210 - Padding(
211 - padding: EdgeInsets.only(left: 24, right: 24, bottom: 10),
212 - child: Container(
213 - height: 10,
214 - child: Observer(
215 - builder: (_) {
216 - final count = sendViewModel.outputs.length;
217 -
218 - return count > 1
219 - ? Semantics(
220 - label: 'Page Indicator',
221 - hint: 'Swipe to change receiver',
222 - excludeSemantics: true,
223 - child: SmoothPageIndicator(
224 - controller: controller,
225 - count: count,
226 - effect: ScrollingDotsEffect(
227 - spacing: 6.0,
228 - radius: 6.0,
229 - dotWidth: 6.0,
230 - dotHeight: 6.0,
231 - dotColor: Theme.of(context)
232 - .extension<SendPageTheme>()!
233 - .indicatorDotColor,
234 - activeDotColor: Theme.of(context)
235 - .extension<SendPageTheme>()!
236 - .templateBackgroundColor),
237 - ))
238 - : Offstage();
239 - },
240 - ),
350 + },
351 + ),
352 + ),
353 + ),
354 + ],
355 ),
356 ),
243 - Container(
244 - height: 40,
245 - width: double.infinity,
246 - padding: EdgeInsets.only(left: 24),
247 - child: SingleChildScrollView(
248 - scrollDirection: Axis.horizontal,
249 - child: Observer(
357 + bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
358 + bottomSection: Column(
359 + children: [
360 + if (sendViewModel.hasCurrecyChanger)
361 + Observer(
362 + builder: (_) => Padding(
363 + padding: EdgeInsets.only(bottom: 12),
364 + child: PrimaryButton(
365 + key: ValueKey('send_page_change_asset_button_key'),
366 + onPressed: () => presentCurrencyPicker(context),
367 + text: 'Change your asset (${sendViewModel.selectedCryptoCurrency})',
368 + color: Colors.transparent,
369 + textColor:
370 + Theme.of(context).extension<SeedWidgetTheme>()!.hintTextColor,
371 + ),
372 + ),
373 + ),
374 + if (sendViewModel.sendTemplateViewModel.hasMultiRecipient)
375 + Padding(
376 + padding: EdgeInsets.only(bottom: 12),
377 + child: PrimaryButton(
378 + key: ValueKey('send_page_add_receiver_button_key'),
379 + onPressed: () {
380 + sendViewModel.addOutput();
381 + Future.delayed(const Duration(milliseconds: 250), () {
382 + controller.jumpToPage(sendViewModel.outputs.length - 1);
383 + });
384 + },
385 + text: S.of(context).add_receiver,
386 + color: Colors.transparent,
387 + textColor:
388 + Theme.of(context).extension<SeedWidgetTheme>()!.hintTextColor,
389 + isDottedBorder: true,
390 + borderColor: Theme.of(context)
391 + .extension<SendPageTheme>()!
392 + .templateDottedBorderColor,
393 + )),
394 + Observer(
395 builder: (_) {
251 - final templates = sendViewModel.templates;
252 - final itemCount = templates.length;
253 -
254 - return Row(
255 - children: <Widget>[
256 - AddTemplateButton(
257 - key: ValueKey('send_page_add_template_button_key'),
258 - onTap: () => Navigator.of(context).pushNamed(Routes.sendTemplate),
259 - currentTemplatesLength: templates.length,
260 - ),
261 - ListView.builder(
262 - scrollDirection: Axis.horizontal,
263 - shrinkWrap: true,
264 - physics: NeverScrollableScrollPhysics(),
265 - itemCount: itemCount,
266 - itemBuilder: (context, index) {
267 - final template = templates[index];
268 - return TemplateTile(
269 - key: UniqueKey(),
270 - to: template.name,
271 - hasMultipleRecipients: template.additionalRecipients != null &&
272 - template.additionalRecipients!.length > 1,
273 - amount: template.isCurrencySelected
274 - ? template.amount
275 - : template.amountFiat,
276 - from: template.isCurrencySelected
277 - ? template.cryptoCurrency
278 - : template.fiatCurrency,
279 - onTap: () async {
280 - sendViewModel.state = IsExecutingState();
281 - if (template.additionalRecipients?.isNotEmpty ?? false) {
282 - sendViewModel.clearOutputs();
283 -
284 - for (int i = 0;
285 - i < template.additionalRecipients!.length;
286 - i++) {
287 - Output output;
288 - try {
289 - output = sendViewModel.outputs[i];
290 - } catch (e) {
291 - sendViewModel.addOutput();
292 - output = sendViewModel.outputs[i];
293 - }
294 -
295 - await _setInputsFromTemplate(
296 - context,
297 - output: output,
298 - template: template.additionalRecipients![i],
299 - );
300 - }
301 - } else {
302 - final output = _defineCurrentOutput();
303 - await _setInputsFromTemplate(
304 - context,
305 - output: output,
306 - template: template,
307 - );
308 - }
309 - sendViewModel.state = InitialExecutionState();
310 - },
311 - onRemove: () {
312 - showPopUp<void>(
313 - context: context,
314 - builder: (dialogContext) {
315 - return AlertWithTwoActions(
316 - alertTitle: S.of(context).template,
317 - alertContent: S.of(context).confirm_delete_template,
318 - rightButtonText: S.of(context).delete,
319 - leftButtonText: S.of(context).cancel,
320 - actionRightButton: () {
321 - Navigator.of(dialogContext).pop();
322 - sendViewModel.sendTemplateViewModel
323 - .removeTemplate(template: template);
324 - },
325 - actionLeftButton: () =>
326 - Navigator.of(dialogContext).pop());
396 + return LoadingPrimaryButton(
397 + key: ValueKey('send_page_send_button_key'),
398 + onPressed: () async {
399 + if (sendViewModel.state is IsExecutingState) return;
400 + if (_formKey.currentState != null &&
401 + !_formKey.currentState!.validate()) {
402 + if (sendViewModel.outputs.length > 1) {
403 + showErrorValidationAlert(context);
404 + }
405 +
406 + return;
407 + }
408 +
409 + final notValidItems = sendViewModel.outputs
410 + .where(
411 + (item) => item.address.isEmpty || item.cryptoAmount.isEmpty)
412 + .toList();
413 +
414 + if (notValidItems.isNotEmpty) {
415 + showErrorValidationAlert(context);
416 + return;
417 + }
418 +
419 + if (sendViewModel.wallet.isHardwareWallet) {
420 + if (!sendViewModel.ledgerViewModel!.isConnected) {
421 + await Navigator.of(context).pushNamed(Routes.connectDevices,
422 + arguments: ConnectDevicePageParams(
423 + walletType: sendViewModel.walletType,
424 + onConnectDevice: (BuildContext context, _) {
425 + sendViewModel.ledgerViewModel!
426 + .setLedger(sendViewModel.wallet);
427 + Navigator.of(context).pop();
428 },
328 - );
329 - },
330 - );
429 + ));
430 + } else {
431 + sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet);
432 + }
433 + }
434 +
435 + if (sendViewModel.wallet.type == WalletType.monero) {
436 + int amount = 0;
437 + for (var item in sendViewModel.outputs) {
438 + amount += item.formattedCryptoAmount;
439 + }
440 + if (monero!.needExportOutputs(sendViewModel.wallet, amount)) {
441 + await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage,
442 + arguments: 'export-outputs');
443 + await Future.delayed(
444 + Duration(seconds: 1)); // wait for monero to refresh the state
445 + }
446 + if (monero!.needExportOutputs(sendViewModel.wallet, amount)) {
447 + return;
448 + }
449 + }
450 +
451 + final check = sendViewModel.shouldDisplayTotp();
452 + authService.authenticateAction(
453 + context,
454 + conditionToDetermineIfToUse2FA: check,
455 + onAuthSuccess: (value) async {
456 + if (value) {
457 + await sendViewModel.createTransaction();
458 + }
459 },
332 - ),
333 - ],
460 + );
461 + },
462 + text: S.of(context).send,
463 + color: Theme.of(context).primaryColor,
464 + textColor: Colors.white,
465 + isLoading: sendViewModel.state is IsExecutingState ||
466 + sendViewModel.state is TransactionCommitting ||
467 + sendViewModel.state is IsAwaitingDeviceResponseState,
468 + isDisabled: !sendViewModel.isReadyForSend,
469 );
470 },
336 - ),
337 - ),
338 - ),
339 - ],
340 - ),
471 + )
472 + ],
473 + )),
474 ),
342 - bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
343 - bottomSection: Column(
344 - children: [
345 - if (sendViewModel.hasCurrecyChanger)
346 - Observer(
347 - builder: (_) => Padding(
348 - padding: EdgeInsets.only(bottom: 12),
349 - child: PrimaryButton(
350 - key: ValueKey('send_page_change_asset_button_key'),
351 - onPressed: () => presentCurrencyPicker(context),
352 - text: 'Change your asset (${sendViewModel.selectedCryptoCurrency})',
353 - color: Colors.transparent,
354 - textColor: Theme.of(context).extension<SeedWidgetTheme>()!.hintTextColor,
355 - ),
356 - ),
357 - ),
358 - if (sendViewModel.sendTemplateViewModel.hasMultiRecipient)
359 - Padding(
360 - padding: EdgeInsets.only(bottom: 12),
361 - child: PrimaryButton(
362 - key: ValueKey('send_page_add_receiver_button_key'),
363 - onPressed: () {
364 - sendViewModel.addOutput();
365 - Future.delayed(const Duration(milliseconds: 250), () {
366 - controller.jumpToPage(sendViewModel.outputs.length - 1);
367 - });
368 - },
369 - text: S.of(context).add_receiver,
370 - color: Colors.transparent,
371 - textColor: Theme.of(context).extension<SeedWidgetTheme>()!.hintTextColor,
372 - isDottedBorder: true,
373 - borderColor:
374 - Theme.of(context).extension<SendPageTheme>()!.templateDottedBorderColor,
375 - )),
376 - Observer(
377 - builder: (_) {
378 - return LoadingPrimaryButton(
379 - key: ValueKey('send_page_send_button_key'),
380 - onPressed: () async {
381 - if (sendViewModel.state is IsExecutingState) return;
382 - if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
383 - if (sendViewModel.outputs.length > 1) {
384 - showErrorValidationAlert(context);
385 - }
386 -
387 - return;
388 - }
389 -
390 - final notValidItems = sendViewModel.outputs
391 - .where((item) => item.address.isEmpty || item.cryptoAmount.isEmpty)
392 - .toList();
393 -
394 - if (notValidItems.isNotEmpty) {
395 - showErrorValidationAlert(context);
396 - return;
397 - }
398 -
399 - if (sendViewModel.wallet.isHardwareWallet) {
400 - if (!sendViewModel.ledgerViewModel!.isConnected) {
401 - await Navigator.of(context).pushNamed(
402 - Routes.connectDevices,
403 - arguments: ConnectDevicePageParams(
404 - walletType: sendViewModel.walletType,
405 - onConnectDevice: (BuildContext context, _) {
406 - sendViewModel.ledgerViewModel!
407 - .setLedger(sendViewModel.wallet);
408 - Navigator.of(context).pop();
409 - },
410 - ));
411 - } else {
412 - sendViewModel.ledgerViewModel!
413 - .setLedger(sendViewModel.wallet);
414 - }
415 - }
416 -
417 - if (sendViewModel.wallet.type == WalletType.monero) {
418 - int amount = 0;
419 - for (var item in sendViewModel.outputs) {
420 - amount += item.formattedCryptoAmount;
421 - }
422 - if (monero!.needExportOutputs(sendViewModel.wallet, amount)) {
423 - await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage, arguments: 'export-outputs');
424 - await Future.delayed(Duration(seconds: 1)); // wait for monero to refresh the state
425 - }
426 - if (monero!.needExportOutputs(sendViewModel.wallet, amount)) {
427 - return;
428 - }
429 - }
430 -
431 - final check = sendViewModel.shouldDisplayTotp();
432 - authService.authenticateAction(
433 - context,
434 - conditionToDetermineIfToUse2FA: check,
435 - onAuthSuccess: (value) async {
436 - if (value) {
437 - await sendViewModel.createTransaction();
438 - }
439 - },
440 - );
441 - },
442 - text: S.of(context).send,
443 - color: Theme.of(context).primaryColor,
444 - textColor: Colors.white,
445 - isLoading: sendViewModel.state is IsExecutingState ||
446 - sendViewModel.state is TransactionCommitting ||
447 - sendViewModel.state is IsAwaitingDeviceResponseState,
448 - isDisabled: !sendViewModel.isReadyForSend,
449 - );
450 - },
451 - )
452 - ],
453 - )),
454 - ),
455 - );
475 + ),
476 + ],
477 + );
478 + });
479 }
480
481 BuildContext? dialogContext;
@@ -525,13 +548,12 @@ class SendPage extends BasePage {
548
549 if (state is TransactionCommitted) {
550 WidgetsBinding.instance.addPostFrameCallback((_) async {
528 -
551 if (!context.mounted) {
552 return;
553 }
554
533 - final successMessage = S.of(context).send_success(
534 - sendViewModel.selectedCryptoCurrency.toString());
555 + final successMessage =
556 + S.of(context).send_success(sendViewModel.selectedCryptoCurrency.toString());
557
558 final waitMessage = sendViewModel.walletType == WalletType.solana
559 ? '. ${S.of(context).waitFewSecondForTxUpdate}'
@@ -539,10 +561,8 @@ class SendPage extends BasePage {
561
562 String alertContent = "$successMessage$waitMessage";
563
542 - await Navigator.of(context).pushNamed(
543 - Routes.transactionSuccessPage,
544 - arguments: alertContent
545 - );
564 + await Navigator.of(context)
565 + .pushNamed(Routes.transactionSuccessPage, arguments: alertContent);
566
567 newContactAddress = newContactAddress ?? sendViewModel.newContactAddress();
568 if (newContactAddress?.address != null && isRegularElectrumAddress(newContactAddress!.address)) {
@@ -562,7 +582,7 @@ class SendPage extends BasePage {
582 leftButtonText: S.of(_dialogContext).ignor,
583 alertLeftActionButtonKey: ValueKey('send_page_sent_dialog_ignore_button_key'),
584 alertRightActionButtonKey:
565 - ValueKey('send_page_sent_dialog_add_contact_button_key'),
585 + ValueKey('send_page_sent_dialog_add_contact_button_key'),
586 actionRightButton: () {
587 Navigator.of(_dialogContext).pop();
588 RequestReviewHandler.requestReview();
lib/src/screens/send/widgets/send_card.dart
+329 -285
@@ -1,6 +1,7 @@
1 import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
2 import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart';
3 import 'package:cake_wallet/src/widgets/picker.dart';
4 +import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
5 import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
6 import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
7 import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
@@ -12,6 +13,7 @@ import 'package:cake_wallet/routes.dart';
13 import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
14 import 'package:cake_wallet/view_model/send/output.dart';
15 import 'package:cw_core/transaction_priority.dart';
16 +import 'package:cw_core/unspent_coin_type.dart';
17 import 'package:cw_core/wallet_type.dart';
18 import 'package:flutter/material.dart';
19 import 'package:flutter_mobx/flutter_mobx.dart';
@@ -24,40 +26,58 @@ import 'package:cake_wallet/generated/i18n.dart';
26 import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
27 import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
28
29 +import '../../../../themes/extensions/cake_text_theme.dart';
30 +import '../../../../themes/theme_base.dart';
31 +
32 class SendCard extends StatefulWidget {
33 SendCard({
34 Key? key,
35 required this.output,
36 required this.sendViewModel,
37 + required this.currentTheme,
38 this.initialPaymentRequest,
39 + this.cryptoAmountFocus,
40 + this.fiatAmountFocus,
41 }) : super(key: key);
42
43 final Output output;
44 final SendViewModel sendViewModel;
45 final PaymentRequest? initialPaymentRequest;
46 + final FocusNode? cryptoAmountFocus;
47 + final FocusNode? fiatAmountFocus;
48 + final ThemeBase currentTheme;
49 +
50
51 @override
52 SendCardState createState() => SendCardState(
53 output: output,
54 sendViewModel: sendViewModel,
55 initialPaymentRequest: initialPaymentRequest,
56 + currentTheme: currentTheme
57 + // cryptoAmountFocus: cryptoAmountFocus ?? FocusNode(),
58 + // fiatAmountFocus: fiatAmountFocus ?? FocusNode(),
59 + // cryptoAmountFocus: FocusNode(),
60 + // fiatAmountFocus: FocusNode(),
61 );
62 }
63
64 class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<SendCard> {
48 - SendCardState({required this.output, required this.sendViewModel, this.initialPaymentRequest})
49 - : addressController = TextEditingController(),
65 + SendCardState({
66 + required this.output,
67 + required this.sendViewModel,
68 + this.initialPaymentRequest,
69 + required this.currentTheme,
70 + }) : addressController = TextEditingController(),
71 cryptoAmountController = TextEditingController(),
72 fiatAmountController = TextEditingController(),
73 noteController = TextEditingController(),
74 extractedAddressController = TextEditingController(),
54 - cryptoAmountFocus = FocusNode(),
55 - fiatAmountFocus = FocusNode(),
75 addressFocusNode = FocusNode();
76
77 static const prefixIconWidth = 34.0;
78 static const prefixIconHeight = 34.0;
79
80 + final ThemeBase currentTheme;
81 final Output output;
82 final SendViewModel sendViewModel;
83 final PaymentRequest? initialPaymentRequest;
@@ -67,8 +87,6 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
87 final TextEditingController fiatAmountController;
88 final TextEditingController noteController;
89 final TextEditingController extractedAddressController;
70 - final FocusNode cryptoAmountFocus;
71 - final FocusNode fiatAmountFocus;
90 final FocusNode addressFocusNode;
91
92 bool _effectsInstalled = false;
@@ -101,310 +119,336 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
119 super.build(context);
120 _setEffects(context);
121
104 - return Stack(
105 - children: [
106 - KeyboardActions(
107 - config: KeyboardActionsConfig(
108 - keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
109 - keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
110 - nextFocus: false,
111 - actions: [
112 - KeyboardActionsItem(
113 - focusNode: cryptoAmountFocus,
114 - toolbarButtons: [(_) => KeyboardDoneButton()],
122 + // return Stack(
123 + // children: [
124 + // return KeyboardActions(
125 + // config: KeyboardActionsConfig(
126 + // keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
127 + // keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
128 + // nextFocus: false,
129 + // actions: [
130 + // KeyboardActionsItem(
131 + // focusNode: cryptoAmountFocus,
132 + // toolbarButtons: [(_) => KeyboardDoneButton()],
133 + // ),
134 + // KeyboardActionsItem(
135 + // focusNode: fiatAmountFocus,
136 + // toolbarButtons: [(_) => KeyboardDoneButton()],
137 + // )
138 + // ],
139 + // ),
140 + // // child: Container(
141 + // // height: 0,
142 + // // color: Colors.transparent,
143 + // // ), child:
144 + // child: SizedBox(
145 + // height: 100,
146 + // width: 100,
147 + // child: Text('Send Card'),
148 + // ),
149 + // );
150 + return Container(
151 + decoration: responsiveLayoutUtil.shouldRenderMobileUI
152 + ? BoxDecoration(
153 + borderRadius: BorderRadius.only(
154 + bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
155 + gradient: LinearGradient(
156 + colors: [
157 + Theme.of(context).extension<SendPageTheme>()!.firstGradientColor,
158 + Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
159 + ],
160 + begin: Alignment.topLeft,
161 + end: Alignment.bottomRight,
162 ),
116 - KeyboardActionsItem(
117 - focusNode: fiatAmountFocus,
118 - toolbarButtons: [(_) => KeyboardDoneButton()],
119 - )
120 - ],
121 - ),
122 - child: Container(
123 - height: 0,
124 - color: Colors.transparent,
125 - ),
163 + )
164 + : null,
165 + child: Padding(
166 + padding: EdgeInsets.fromLTRB(
167 + 24,
168 + responsiveLayoutUtil.shouldRenderMobileUI ? 110 : 55,
169 + 24,
170 + responsiveLayoutUtil.shouldRenderMobileUI ? 32 : 0,
171 ),
127 - Container(
128 - decoration: responsiveLayoutUtil.shouldRenderMobileUI
129 - ? BoxDecoration(
130 - borderRadius: BorderRadius.only(
131 - bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
132 - gradient: LinearGradient(
133 - colors: [
134 - Theme.of(context).extension<SendPageTheme>()!.firstGradientColor,
135 - Theme.of(context).extension<SendPageTheme>()!.secondGradientColor,
136 - ],
137 - begin: Alignment.topLeft,
138 - end: Alignment.bottomRight,
139 - ),
140 - )
141 - : null,
142 - child: Padding(
143 - padding: EdgeInsets.fromLTRB(
144 - 24,
145 - responsiveLayoutUtil.shouldRenderMobileUI ? 110 : 55,
146 - 24,
147 - responsiveLayoutUtil.shouldRenderMobileUI ? 32 : 0,
148 - ),
149 - child: SingleChildScrollView(
150 - child: Observer(
151 - builder: (_) => Column(
152 - mainAxisSize: MainAxisSize.min,
153 - children: <Widget>[
154 - Observer(builder: (_) {
155 - final validator = output.isParsedAddress
156 - ? sendViewModel.textValidator
157 - : sendViewModel.addressValidator;
158 -
159 - return AddressTextField(
160 - addressKey: ValueKey('send_page_address_textfield_key'),
161 - focusNode: addressFocusNode,
162 - controller: addressController,
163 - onURIScanned: (uri) {
164 - final paymentRequest = PaymentRequest.fromUri(uri);
165 - addressController.text = paymentRequest.address;
166 - cryptoAmountController.text = paymentRequest.amount;
167 - noteController.text = paymentRequest.note;
168 - },
169 - options: [
170 - AddressTextFieldOption.paste,
171 - AddressTextFieldOption.qrCode,
172 - AddressTextFieldOption.addressBook
173 - ],
174 - buttonColor:
175 - Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
172 + child: Observer(
173 + builder: (_) => Column(
174 + mainAxisSize: MainAxisSize.min,
175 + children: <Widget>[
176 + Observer(builder: (_) {
177 + final validator = output.isParsedAddress
178 + ? sendViewModel.textValidator
179 + : sendViewModel.addressValidator;
180 +
181 + return AddressTextField(
182 + addressKey: ValueKey('send_page_address_textfield_key'),
183 + focusNode: addressFocusNode,
184 + controller: addressController,
185 + onURIScanned: (uri) {
186 + final paymentRequest = PaymentRequest.fromUri(uri);
187 + addressController.text = paymentRequest.address;
188 + cryptoAmountController.text = paymentRequest.amount;
189 + noteController.text = paymentRequest.note;
190 + },
191 + options: [
192 + AddressTextFieldOption.paste,
193 + AddressTextFieldOption.qrCode,
194 + AddressTextFieldOption.addressBook
195 + ],
196 + buttonColor: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
197 + borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
198 + textStyle:
199 + TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
200 + hintStyle: TextStyle(
201 + fontSize: 14,
202 + fontWeight: FontWeight.w500,
203 + color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
204 + onPushPasteButton: (context) async {
205 + output.resetParsedAddress();
206 + await output.fetchParsedAddress(context);
207 + },
208 + onPushAddressBookButton: (context) async {
209 + output.resetParsedAddress();
210 + },
211 + onSelectedContact: (contact) {
212 + output.loadContact(contact);
213 + },
214 + validator: validator,
215 + selectedCurrency: sendViewModel.selectedCryptoCurrency,
216 + );
217 + }),
218 + if (output.isParsedAddress)
219 + Padding(
220 + padding: const EdgeInsets.only(top: 20),
221 + child: BaseTextFormField(
222 + controller: extractedAddressController,
223 + readOnly: true,
224 borderColor:
225 Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
226 textStyle: TextStyle(
227 fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
180 - hintStyle: TextStyle(
181 - fontSize: 14,
182 - fontWeight: FontWeight.w500,
183 - color:
184 - Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
185 - onPushPasteButton: (context) async {
186 - output.resetParsedAddress();
187 - await output.fetchParsedAddress(context);
188 - },
189 - onPushAddressBookButton: (context) async {
190 - output.resetParsedAddress();
191 - },
192 - onSelectedContact: (contact) {
193 - output.loadContact(contact);
194 - },
195 - validator: validator,
196 - selectedCurrency: sendViewModel.selectedCryptoCurrency,
197 - );
198 - }),
199 - if (output.isParsedAddress)
200 - Padding(
201 - padding: const EdgeInsets.only(top: 20),
202 - child: BaseTextFormField(
203 - controller: extractedAddressController,
204 - readOnly: true,
205 - borderColor: Theme.of(context)
206 - .extension<SendPageTheme>()!
207 - .textFieldBorderColor,
208 - textStyle: TextStyle(
209 - fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
210 - validator: sendViewModel.addressValidator)),
211 - CurrencyAmountTextField(
212 - currencyPickerButtonKey: ValueKey('send_page_currency_picker_button_key'),
213 - amountTextfieldKey: ValueKey('send_page_amount_textfield_key'),
214 - sendAllButtonKey: ValueKey('send_page_send_all_button_key'),
215 - currencyAmountTextFieldWidgetKey:
216 - ValueKey('send_page_crypto_currency_amount_textfield_widget_key'),
217 - selectedCurrency: sendViewModel.selectedCryptoCurrency.title,
218 - amountFocusNode: cryptoAmountFocus,
219 - amountController: cryptoAmountController,
220 - isAmountEditable: true,
221 - onTapPicker: () => _presentPicker(context),
222 - isPickerEnable: sendViewModel.hasMultipleTokens,
223 - tag: sendViewModel.selectedCryptoCurrency.tag,
224 - allAmountButton:
225 - !sendViewModel.isBatchSending && sendViewModel.shouldDisplaySendALL,
226 - currencyValueValidator: output.sendAll
227 - ? sendViewModel.allAmountValidator
228 - : sendViewModel.amountValidator,
229 - allAmountCallback: () async => output.setSendAll(sendViewModel.balance)),
230 - Divider(
231 - height: 1,
232 - color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
233 - Observer(
234 - builder: (_) => Padding(
235 - padding: EdgeInsets.only(top: 10),
236 - child: Row(
237 - mainAxisSize: MainAxisSize.max,
238 - mainAxisAlignment: MainAxisAlignment.spaceBetween,
239 - children: <Widget>[
240 - Expanded(
241 - child: Text(
242 - S.of(context).available_balance + ':',
243 - style: TextStyle(
244 - fontSize: 12,
245 - fontWeight: FontWeight.w600,
246 - color: Theme.of(context)
247 - .extension<SendPageTheme>()!
248 - .textFieldHintColor),
249 - ),
250 - ),
251 - Text(
252 - sendViewModel.balance,
253 - style: TextStyle(
254 - fontSize: 12,
255 - fontWeight: FontWeight.w600,
256 - color: Theme.of(context)
257 - .extension<SendPageTheme>()!
258 - .textFieldHintColor),
259 - )
260 - ],
228 + validator: sendViewModel.addressValidator)),
229 + CurrencyAmountTextField(
230 + currencyPickerButtonKey: ValueKey('send_page_currency_picker_button_key'),
231 + amountTextfieldKey: ValueKey('send_page_amount_textfield_key'),
232 + sendAllButtonKey: ValueKey('send_page_send_all_button_key'),
233 + currencyAmountTextFieldWidgetKey:
234 + ValueKey('send_page_crypto_currency_amount_textfield_widget_key'),
235 + selectedCurrency: sendViewModel.selectedCryptoCurrency.title,
236 + amountFocusNode: widget.cryptoAmountFocus,
237 + amountController: cryptoAmountController,
238 + isAmountEditable: true,
239 + onTapPicker: () => _presentPicker(context),
240 + isPickerEnable: sendViewModel.hasMultipleTokens,
241 + tag: sendViewModel.selectedCryptoCurrency.tag,
242 + allAmountButton:
243 + !sendViewModel.isBatchSending && sendViewModel.shouldDisplaySendALL,
244 + currencyValueValidator: output.sendAll
245 + ? sendViewModel.allAmountValidator
246 + : sendViewModel.amountValidator,
247 + allAmountCallback: () async => output.setSendAll(sendViewModel.balance)),
248 + Divider(
249 + height: 1,
250 + color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
251 + Observer(
252 + builder: (_) => Padding(
253 + padding: EdgeInsets.only(top: 10),
254 + child: Row(
255 + mainAxisSize: MainAxisSize.max,
256 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
257 + children: <Widget>[
258 + Expanded(
259 + child: Text(
260 + S.of(context).available_balance + ':',
261 + style: TextStyle(
262 + fontSize: 12,
263 + fontWeight: FontWeight.w600,
264 + color:
265 + Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
266 ),
267 ),
263 - ),
264 - if (!sendViewModel.isFiatDisabled)
265 - CurrencyAmountTextField(
266 - amountTextfieldKey: ValueKey('send_page_fiat_amount_textfield_key'),
267 - currencyAmountTextFieldWidgetKey:
268 - ValueKey('send_page_fiat_currency_amount_textfield_widget_key'),
269 - selectedCurrency: sendViewModel.fiat.title,
270 - amountFocusNode: fiatAmountFocus,
271 - amountController: fiatAmountController,
272 - hintText: '0.00',
273 - isAmountEditable: true,
274 - allAmountButton: false),
275 - Divider(
276 - height: 1,
277 - color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
278 - Padding(
279 - padding: EdgeInsets.only(top: 20),
280 - child: BaseTextFormField(
281 - key: ValueKey('send_page_note_textfield_key'),
282 - controller: noteController,
283 - keyboardType: TextInputType.multiline,
284 - maxLines: null,
285 - borderColor:
286 - Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
287 - textStyle: TextStyle(
288 - fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
289 - hintText: S.of(context).note_optional,
290 - placeholderTextStyle: TextStyle(
291 - fontSize: 14,
292 - fontWeight: FontWeight.w500,
268 + Text(
269 + sendViewModel.balance,
270 + style: TextStyle(
271 + fontSize: 12,
272 + fontWeight: FontWeight.w600,
273 color:
274 Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
295 - ),
296 - ),
297 - if (sendViewModel.hasFees)
298 - Observer(
299 - builder: (_) => GestureDetector(
300 - key: ValueKey('send_page_select_fee_priority_button_key'),
301 - onTap: sendViewModel.hasFeesPriority
302 - ? () => pickTransactionPriority(context)
303 - : () {},
304 - child: Container(
305 - padding: EdgeInsets.only(top: 24),
275 + )
276 + ],
277 + ),
278 + ),
279 + ),
280 + if (!sendViewModel.isFiatDisabled)
281 + CurrencyAmountTextField(
282 + amountTextfieldKey: ValueKey('send_page_fiat_amount_textfield_key'),
283 + currencyAmountTextFieldWidgetKey:
284 + ValueKey('send_page_fiat_currency_amount_textfield_widget_key'),
285 + selectedCurrency: sendViewModel.fiat.title,
286 + amountFocusNode: widget.fiatAmountFocus,
287 + amountController: fiatAmountController,
288 + hintText: '0.00',
289 + isAmountEditable: true,
290 + allAmountButton: false),
291 + Divider(
292 + height: 1,
293 + color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
294 + Padding(
295 + padding: EdgeInsets.only(top: 20),
296 + child: BaseTextFormField(
297 + key: ValueKey('send_page_note_textfield_key'),
298 + controller: noteController,
299 + keyboardType: TextInputType.multiline,
300 + maxLines: null,
301 + borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
302 + textStyle:
303 + TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
304 + hintText: S.of(context).note_optional,
305 + placeholderTextStyle: TextStyle(
306 + fontSize: 14,
307 + fontWeight: FontWeight.w500,
308 + color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
309 + ),
310 + ),
311 + if (sendViewModel.hasFees)
312 + Observer(
313 + builder: (_) => GestureDetector(
314 + key: ValueKey('send_page_select_fee_priority_button_key'),
315 + onTap: sendViewModel.hasFeesPriority
316 + ? () => pickTransactionPriority(context)
317 + : () {},
318 + child: Container(
319 + padding: EdgeInsets.only(top: 24),
320 + child: Row(
321 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
322 + crossAxisAlignment: CrossAxisAlignment.start,
323 + children: <Widget>[
324 + Text(
325 + S.of(context).send_estimated_fee,
326 + style: TextStyle(
327 + fontSize: 12, fontWeight: FontWeight.w500, color: Colors.white),
328 + ),
329 + Container(
330 child: Row(
307 - mainAxisAlignment: MainAxisAlignment.spaceBetween,
331 crossAxisAlignment: CrossAxisAlignment.start,
332 children: <Widget>[
310 - Text(
311 - S.of(context).send_estimated_fee,
312 - style: TextStyle(
313 - fontSize: 12,
314 - fontWeight: FontWeight.w500,
315 - color: Colors.white),
316 - ),
317 - Container(
318 - child: Row(
319 - crossAxisAlignment: CrossAxisAlignment.start,
320 - children: <Widget>[
321 - Column(
322 - mainAxisAlignment: MainAxisAlignment.start,
323 - crossAxisAlignment: CrossAxisAlignment.end,
324 - children: [
325 - Text(
326 - output.estimatedFee.toString() +
327 - ' ' +
328 - sendViewModel.currency.toString(),
329 - style: TextStyle(
330 - fontSize: 12,
331 - fontWeight: FontWeight.w600,
332 - color: Colors.white,
333 - ),
334 - ),
335 - Padding(
336 - padding: EdgeInsets.only(top: 5),
337 - child: sendViewModel.isFiatDisabled
338 - ? const SizedBox(height: 14)
339 - : Text(
340 - output.estimatedFeeFiatAmount +
341 - ' ' +
342 - sendViewModel.fiat.title,
343 - style: TextStyle(
344 - fontSize: 12,
345 - fontWeight: FontWeight.w600,
346 - color: Theme.of(context)
347 - .extension<SendPageTheme>()!
348 - .textFieldHintColor,
349 - ),
350 - ),
351 - ),
352 - ],
333 + Column(
334 + mainAxisAlignment: MainAxisAlignment.start,
335 + crossAxisAlignment: CrossAxisAlignment.end,
336 + children: [
337 + Text(
338 + output.estimatedFee.toString() +
339 + ' ' +
340 + sendViewModel.currency.toString(),
341 + style: TextStyle(
342 + fontSize: 12,
343 + fontWeight: FontWeight.w600,
344 + color: Colors.white,
345 ),
354 - Padding(
355 - padding: EdgeInsets.only(top: 2, left: 5),
356 - child: Icon(
357 - Icons.arrow_forward_ios,
358 - size: 12,
359 - color: Colors.white,
360 - ),
361 - )
362 - ],
346 + ),
347 + Padding(
348 + padding: EdgeInsets.only(top: 5),
349 + child: sendViewModel.isFiatDisabled
350 + ? const SizedBox(height: 14)
351 + : Text(
352 + output.estimatedFeeFiatAmount +
353 + ' ' +
354 + sendViewModel.fiat.title,
355 + style: TextStyle(
356 + fontSize: 12,
357 + fontWeight: FontWeight.w600,
358 + color: Theme.of(context)
359 + .extension<SendPageTheme>()!
360 + .textFieldHintColor,
361 + ),
362 + ),
363 + ),
364 + ],
365 + ),
366 + Padding(
367 + padding: EdgeInsets.only(top: 2, left: 5),
368 + child: Icon(
369 + Icons.arrow_forward_ios,
370 + size: 12,
371 + color: Colors.white,
372 ),
373 )
374 ],
375 ),
367 - ),
368 - ),
376 + )
377 + ],
378 ),
370 - if (sendViewModel.hasCoinControl)
371 - Padding(
372 - padding: EdgeInsets.only(top: 6),
373 - child: GestureDetector(
374 - key: ValueKey('send_page_unspent_coin_button_key'),
375 - onTap: () => Navigator.of(context).pushNamed(
376 - Routes.unspentCoinsList,
377 - arguments: widget.sendViewModel.coinTypeToSpendFrom,
379 + ),
380 + ),
381 + ),
382 + if (sendViewModel.hasCoinControl)
383 + Padding(
384 + padding: EdgeInsets.only(top: 6),
385 + child: GestureDetector(
386 + key: ValueKey('send_page_unspent_coin_button_key'),
387 + onTap: () => Navigator.of(context).pushNamed(
388 + Routes.unspentCoinsList,
389 + arguments: widget.sendViewModel.coinTypeToSpendFrom,
390 + ),
391 + child: Container(
392 + color: Colors.transparent,
393 + child: Row(
394 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
395 + children: [
396 + Text(
397 + S.of(context).coin_control,
398 + style: TextStyle(
399 + fontSize: 12, fontWeight: FontWeight.w600, color: Colors.white),
400 ),
379 - child: Container(
380 - color: Colors.transparent,
381 - child: Row(
382 - mainAxisAlignment: MainAxisAlignment.spaceBetween,
383 - children: [
384 - Text(
385 - S.of(context).coin_control,
386 - style: TextStyle(
387 - fontSize: 12,
388 - fontWeight: FontWeight.w600,
389 - color: Colors.white),
390 - ),
391 - Icon(
392 - Icons.arrow_forward_ios,
393 - size: 12,
394 - color: Colors.white,
395 - ),
396 - ],
397 - ),
401 + Icon(
402 + Icons.arrow_forward_ios,
403 + size: 12,
404 + color: Colors.white,
405 ),
406 + ],
407 + ),
408 + ),
409 + ),
410 + ),
411 + if (sendViewModel.currency == CryptoCurrency.ltc)
412 + Observer(
413 + builder: (_) => Padding(
414 + padding: EdgeInsets.only(top: 14),
415 + child: GestureDetector(
416 + key: ValueKey('send_page_unspent_coin_button_key'),
417 + onTap: () {
418 + bool value =
419 + widget.sendViewModel.coinTypeToSpendFrom == UnspentCoinType.any;
420 + sendViewModel.setAllowMwebCoins(!value);
421 + },
422 + child: Container(
423 + color: Colors.transparent,
424 + child: Row(
425 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
426 + children: [
427 + StandardCheckbox(
428 + caption: S.of(context).litecoin_mweb_allow_coins,
429 + captionColor: Colors.white,
430 + borderColor: currentTheme.type == ThemeType.bright
431 + ? Colors.white
432 + : Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
433 + iconColor: currentTheme.type == ThemeType.bright
434 + ? Colors.white
435 + : Theme.of(context).primaryColor,
436 + value:
437 + widget.sendViewModel.coinTypeToSpendFrom == UnspentCoinType.any,
438 + onChanged: (bool? value) {
439 + sendViewModel.setAllowMwebCoins(value ?? false);
440 + },
441 + ),
442 + ],
443 ),
444 ),
401 - ],
445 + ),
446 + ),
447 ),
403 - ),
404 - ),
448 + ],
449 ),
406 - )
407 - ],
450 + ),
451 + ),
452 );
453 }
454
lib/src/widgets/adaptable_page_view.dart new
+202
@@ -0,0 +1,202 @@
1 +import 'dart:ui';
2 +
3 +import 'package:flutter/material.dart';
4 +import 'package:flutter/rendering.dart';
5 +
6 +const _firstLayoutMaxHeight = 10000.0;
7 +
8 +class PageViewHeightAdaptable extends StatefulWidget {
9 + const PageViewHeightAdaptable({
10 + super.key,
11 + required this.controller,
12 + required this.children,
13 + }) : assert(children.length > 0, 'children must not be empty');
14 +
15 + final PageController controller;
16 + final List<Widget> children;
17 +
18 + @override
19 + State<PageViewHeightAdaptable> createState() => _PageViewHeightAdaptableState();
20 +}
21 +
22 +class _PageViewHeightAdaptableState extends State<PageViewHeightAdaptable> {
23 + final _sizes = <int, Size>{};
24 +
25 + @override
26 + void didUpdateWidget(PageViewHeightAdaptable oldWidget) {
27 + super.didUpdateWidget(oldWidget);
28 +
29 + _sizes.clear();
30 + }
31 +
32 + @override
33 + Widget build(BuildContext context) {
34 + return ListenableBuilder(
35 + listenable: widget.controller,
36 + builder: (context, child) => _SizingContainer(
37 + sizes: _sizes,
38 + page: widget.controller.hasClients ? widget.controller.page ?? 0 : 0,
39 + child: child!,
40 + ),
41 + child: LayoutBuilder(
42 + builder: (context, constraints) => PageView(
43 + controller: widget.controller,
44 + children: [
45 + for (final (i, child) in widget.children.indexed)
46 + Stack(
47 + alignment: Alignment.topCenter,
48 + clipBehavior: Clip.hardEdge,
49 + children: [
50 + SizedBox.fromSize(size: _sizes[i]),
51 + Positioned(
52 + left: 0,
53 + top: 0,
54 + right: 0,
55 + child: _SizeAware(
56 + child: child,
57 + // don't setState, we'll use it in the layout phase
58 + onSizeLaidOut: (size) {
59 + _sizes[i] = size;
60 + },
61 + ),
62 + ),
63 + ],
64 + ),
65 + ],
66 + ),
67 + ),
68 + );
69 + }
70 +}
71 +
72 +typedef _OnSizeLaidOutCallback = void Function(Size);
73 +
74 +class _SizingContainer extends SingleChildRenderObjectWidget {
75 + const _SizingContainer({
76 + super.child,
77 + required this.sizes,
78 + required this.page,
79 + });
80 +
81 + final Map<int, Size> sizes;
82 + final double page;
83 +
84 + @override
85 + _RenderSizingContainer createRenderObject(BuildContext context) {
86 + return _RenderSizingContainer(
87 + sizes: sizes,
88 + page: page,
89 + );
90 + }
91 +
92 + @override
93 + void updateRenderObject(
94 + BuildContext context,
95 + _RenderSizingContainer renderObject,
96 + ) {
97 + renderObject
98 + ..sizes = sizes
99 + ..page = page;
100 + }
101 +}
102 +
103 +class _RenderSizingContainer extends RenderProxyBox {
104 + _RenderSizingContainer({
105 + RenderBox? child,
106 + required Map<int, Size> sizes,
107 + required double page,
108 + }) : _sizes = sizes,
109 + _page = page,
110 + super(child);
111 +
112 + Map<int, Size> _sizes;
113 + Map<int, Size> get sizes => _sizes;
114 + set sizes(Map<int, Size> value) {
115 + if (_sizes == value) return;
116 + _sizes = value;
117 + markNeedsLayout();
118 + }
119 +
120 + double _page;
121 + double get page => _page;
122 + set page(double value) {
123 + if (_page == value) return;
124 + _page = value;
125 + markNeedsLayout();
126 + }
127 +
128 + @override
129 + void performLayout() {
130 + if (child case final child?) {
131 + child.layout(
132 + constraints.copyWith(
133 + minWidth: constraints.maxWidth,
134 + minHeight: 0,
135 + maxHeight: constraints.hasBoundedHeight ? null : _firstLayoutMaxHeight,
136 + ),
137 + parentUsesSize: true,
138 + );
139 +
140 + final a = sizes[page.floor()]!;
141 + final b = sizes[page.ceil()]!;
142 +
143 + final height = lerpDouble(a.height, b.height, page - page.floor());
144 +
145 + child.layout(
146 + constraints.copyWith(minHeight: height, maxHeight: height),
147 + parentUsesSize: true,
148 + );
149 + size = child.size;
150 + } else {
151 + size = computeSizeForNoChild(constraints);
152 + }
153 + }
154 +}
155 +
156 +class _SizeAware extends SingleChildRenderObjectWidget {
157 + const _SizeAware({
158 + required Widget child,
159 + required this.onSizeLaidOut,
160 + }) : super(child: child);
161 +
162 + final _OnSizeLaidOutCallback onSizeLaidOut;
163 +
164 + @override
165 + _RenderSizeAware createRenderObject(BuildContext context) {
166 + return _RenderSizeAware(
167 + onSizeLaidOut: onSizeLaidOut,
168 + );
169 + }
170 +
171 + @override
172 + void updateRenderObject(BuildContext context, _RenderSizeAware renderObject) {
173 + renderObject.onSizeLaidOut = onSizeLaidOut;
174 + }
175 +}
176 +
177 +class _RenderSizeAware extends RenderProxyBox {
178 + _RenderSizeAware({
179 + RenderBox? child,
180 + required _OnSizeLaidOutCallback onSizeLaidOut,
181 + }) : _onSizeLaidOut = onSizeLaidOut,
182 + super(child);
183 +
184 + _OnSizeLaidOutCallback? _onSizeLaidOut;
185 + _OnSizeLaidOutCallback get onSizeLaidOut => _onSizeLaidOut!;
186 + set onSizeLaidOut(_OnSizeLaidOutCallback value) {
187 + if (_onSizeLaidOut == value) return;
188 + _onSizeLaidOut = value;
189 + markNeedsLayout();
190 + }
191 +
192 + @override
193 + void performLayout() {
194 + super.performLayout();
195 +
196 + onSizeLaidOut(
197 + getDryLayout(
198 + constraints.copyWith(maxHeight: double.infinity),
199 + ),
200 + );
201 + }
202 +}
\ No newline at end of file
lib/src/widgets/standard_checkbox.dart
+3 -1
@@ -9,6 +9,7 @@ class StandardCheckbox extends StatelessWidget {
9 this.gradientBackground = false,
10 this.borderColor,
11 this.iconColor,
12 + this.captionColor,
13 required this.onChanged});
14
15 final bool value;
@@ -16,6 +17,7 @@ class StandardCheckbox extends StatelessWidget {
17 final bool gradientBackground;
18 final Color? borderColor;
19 final Color? iconColor;
20 + final Color? captionColor;
21 final Function(bool) onChanged;
22
23 @override
@@ -68,7 +70,7 @@ class StandardCheckbox extends StatelessWidget {
70 fontSize: 16.0,
71 fontFamily: 'Lato',
72 fontWeight: FontWeight.normal,
71 - color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
73 + color: captionColor ?? Theme.of(context).extension<CakeTextTheme>()!.titleColor,
74 decoration: TextDecoration.none,
75 ),
76 ),
lib/view_model/send/send_view_model.dart
+10 -2
@@ -77,7 +77,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
77 this.transactionDescriptionBox,
78 this.ledgerViewModel,
79 this.unspentCoinsListViewModel, {
80 - this.coinTypeToSpendFrom = UnspentCoinType.any,
80 + this.coinTypeToSpendFrom = UnspentCoinType.nonMweb,
81 }) : state = InitialExecutionState(),
82 currencies = appStore.wallet!.balance.keys.toList(),
83 selectedCryptoCurrency = appStore.wallet!.currency,
@@ -112,7 +112,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
112
113 ObservableList<Output> outputs;
114
115 - final UnspentCoinType coinTypeToSpendFrom;
115 + @observable
116 + UnspentCoinType coinTypeToSpendFrom;
117
118 bool get showAddressBookPopup => _settingsStore.showAddressBookPopupEnabled;
119
@@ -135,6 +136,13 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
136 addOutput();
137 }
138
139 + @action
140 + void setAllowMwebCoins(bool allow) {
141 + if (wallet.type == WalletType.litecoin) {
142 + coinTypeToSpendFrom = allow ? UnspentCoinType.any : UnspentCoinType.nonMweb;
143 + }
144 + }
145 +
146 @computed
147 bool get isBatchSending => outputs.length > 1;
148
res/values/strings_ar.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "فاتح",
395 "litecoin_enable_mweb_sync": "تمكين MWEB المسح الضوئي",
396 "litecoin_mweb": "mweb",
397 + "litecoin_mweb_allow_coins": "السماح للعملات المعدنية MWEB",
398 "litecoin_mweb_always_scan": "اضبط MWEB دائمًا على المسح الضوئي",
399 "litecoin_mweb_description": "MWEB هو بروتوكول جديد يجلب معاملات أسرع وأرخص وأكثر خصوصية إلى Litecoin",
400 "litecoin_mweb_dismiss": "رفض",
res/values/strings_bg.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "Светло",
395 "litecoin_enable_mweb_sync": "Активирайте сканирането на MWeb",
396 "litecoin_mweb": "Mweb",
397 + "litecoin_mweb_allow_coins": "Позволете на MWeb монети",
398 "litecoin_mweb_always_scan": "Задайте MWeb винаги сканиране",
399 "litecoin_mweb_description": "MWeb е нов протокол, който носи по -бърз, по -евтин и повече частни транзакции на Litecoin",
400 "litecoin_mweb_dismiss": "Уволнение",
res/values/strings_cs.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "Světlý",
395 "litecoin_enable_mweb_sync": "Povolit skenování MWeb",
396 "litecoin_mweb": "MWeb",
397 + "litecoin_mweb_allow_coins": "Povolte mweb mince",
398 "litecoin_mweb_always_scan": "Nastavit MWeb vždy skenování",
399 "litecoin_mweb_description": "MWEB je nový protokol, který do Litecoin přináší rychlejší, levnější a více soukromých transakcí",
400 "litecoin_mweb_dismiss": "Propustit",
res/values/strings_de.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "Hell",
395 "litecoin_enable_mweb_sync": "Aktivieren Sie das MWEB-Scannen",
396 "litecoin_mweb": "MWeb",
397 + "litecoin_mweb_allow_coins": "MWEB -Münzen zulassen",
398 "litecoin_mweb_always_scan": "Setzen Sie MWeb immer scannen",
399 "litecoin_mweb_description": "MWWB ist ein neues Protokoll, das schnellere, billigere und privatere Transaktionen zu Litecoin bringt",
400 "litecoin_mweb_dismiss": "Zurückweisen",
res/values/strings_en.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "Light",
395 "litecoin_enable_mweb_sync": "Enable MWEB scanning",
396 "litecoin_mweb": "MWEB",
397 + "litecoin_mweb_allow_coins": "Allow MWEB coins",
398 "litecoin_mweb_always_scan": "Set MWEB always scanning",
399 "litecoin_mweb_description": "MWEB is a new protocol that brings faster, cheaper, and more private transactions to Litecoin",
400 "litecoin_mweb_dismiss": "Dismiss",
res/values/strings_es.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "Ligero",
395 "litecoin_enable_mweb_sync": "Habilitar el escaneo mweb",
396 "litecoin_mweb": "Mweb",
397 + "litecoin_mweb_allow_coins": "Permitir monedas mweb",
398 "litecoin_mweb_always_scan": "Establecer mweb siempre escaneo",
399 "litecoin_mweb_description": "Mweb es un nuevo protocolo que trae transacciones más rápidas, más baratas y más privadas a Litecoin",
400 "litecoin_mweb_dismiss": "Despedir",
res/values/strings_fr.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "Clair",
395 "litecoin_enable_mweb_sync": "Activer la numérisation MWEB",
396 "litecoin_mweb": "Mweb",
397 + "litecoin_mweb_allow_coins": "Autoriser les pièces MWeb",
398 "litecoin_mweb_always_scan": "Définir MWEB Score Scanning",
399 "litecoin_mweb_description": "MWEB est un nouveau protocole qui apporte des transactions plus rapides, moins chères et plus privées à Litecoin",
400 "litecoin_mweb_dismiss": "Rejeter",
res/values/strings_ha.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "Haske",
395 "litecoin_enable_mweb_sync": "Kunna binciken Mweb",
396 "litecoin_mweb": "Mweb",
397 + "litecoin_mweb_allow_coins": "Bada izinin Coins na Mweb",
398 "litecoin_mweb_always_scan": "Saita Mweb koyaushe",
399 "litecoin_mweb_description": "Mweb shine sabon tsarin yarjejeniya da ya kawo da sauri, mai rahusa, da kuma ma'amaloli masu zaman kansu zuwa Litecoin",
400 "litecoin_mweb_dismiss": "Tuɓe \\ sallama",
res/values/strings_hi.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "रोशनी",
395 "litecoin_enable_mweb_sync": "MWEB स्कैनिंग सक्षम करें",
396 "litecoin_mweb": "मावली",
397 + "litecoin_mweb_allow_coins": "MWEB सिक्कों की अनुमति दें",
398 "litecoin_mweb_always_scan": "MWEB हमेशा स्कैनिंग सेट करें",
399 "litecoin_mweb_description": "MWEB एक नया प्रोटोकॉल है जो लिटकोइन के लिए तेजी से, सस्ता और अधिक निजी लेनदेन लाता है",
400 "litecoin_mweb_dismiss": "नकार देना",
res/values/strings_hr.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "Svijetla",
395 "litecoin_enable_mweb_sync": "Omogućite MWEB skeniranje",
396 "litecoin_mweb": "MWeb",
397 + "litecoin_mweb_allow_coins": "Dopustite MWeb kovanice",
398 "litecoin_mweb_always_scan": "Postavite MWeb uvijek skeniranje",
399 "litecoin_mweb_description": "MWEB je novi protokol koji u Litecoin donosi brže, jeftinije i privatnije transakcije",
400 "litecoin_mweb_dismiss": "Odbaciti",
res/values/strings_hy.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "Լուսավոր",
395 "litecoin_enable_mweb_sync": "Միացնել MWEB սկան",
396 "litecoin_mweb": "Մուեբ",
397 + "litecoin_mweb_allow_coins": "Թույլ տվեք MWeb մետաղադրամներ",
398 "litecoin_mweb_always_scan": "Սահմանեք Mweb Միշտ սկանավորում",
399 "litecoin_mweb_description": "Mweb- ը նոր արձանագրություն է, որը բերում է ավելի արագ, ավելի էժան եւ ավելի մասնավոր գործարքներ դեպի LITECOIN",
400 "litecoin_mweb_dismiss": "Հեռացնել",
res/values/strings_id.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "Terang",
395 "litecoin_enable_mweb_sync": "Aktifkan pemindaian MWEB",
396 "litecoin_mweb": "Mweb",
397 + "litecoin_mweb_allow_coins": "Izinkan koin mWeb",
398 "litecoin_mweb_always_scan": "Atur mWeb selalu memindai",
399 "litecoin_mweb_description": "MWEB adalah protokol baru yang membawa transaksi yang lebih cepat, lebih murah, dan lebih pribadi ke Litecoin",
400 "litecoin_mweb_dismiss": "Membubarkan",
res/values/strings_it.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "Chiaro",
395 "litecoin_enable_mweb_sync": "Abilita la scansione MWeb",
396 "litecoin_mweb": "MWeb",
397 + "litecoin_mweb_allow_coins": "Consenti monete mWeb",
398 "litecoin_mweb_always_scan": "Imposta MWeb per scansionare sempre",
399 "litecoin_mweb_description": "MWeb è un nuovo protocollo che porta transazioni più veloci, più economiche e più private a Litecoin",
400 "litecoin_mweb_dismiss": "Chiudi",
res/values/strings_ja.arb
+1
@@ -395,6 +395,7 @@
395 "light_theme": "光",
396 "litecoin_enable_mweb_sync": "MWEBスキャンを有効にします",
397 "litecoin_mweb": "mweb",
398 + "litecoin_mweb_allow_coins": "MWEBコインを許可します",
399 "litecoin_mweb_always_scan": "MWEBを常にスキャンします",
400 "litecoin_mweb_description": "MWEBは、Litecoinにより速く、より安価で、よりプライベートなトランザクションをもたらす新しいプロトコルです",
401 "litecoin_mweb_dismiss": "却下する",
res/values/strings_ko.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "빛",
395 "litecoin_enable_mweb_sync": "mweb 스캔을 활성화합니다",
396 "litecoin_mweb": "mweb",
397 + "litecoin_mweb_allow_coins": "mweb 코인을 허용하십시오",
398 "litecoin_mweb_always_scan": "mweb는 항상 스캔을 설정합니다",
399 "litecoin_mweb_description": "MWEB는 Litecoin에 더 빠르고 저렴하며 개인 거래를 제공하는 새로운 프로토콜입니다.",
400 "litecoin_mweb_dismiss": "해고하다",
res/values/strings_my.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "အလင်း",
395 "litecoin_enable_mweb_sync": "mweb scanning ဖွင့်ပါ",
396 "litecoin_mweb": "မင်္ဂလာပါ",
397 + "litecoin_mweb_allow_coins": "mweb ဒင်္ဂါးများကိုခွင့်ပြုပါ",
398 "litecoin_mweb_always_scan": "Mweb အမြဲစကင်ဖတ်စစ်ဆေးပါ",
399 "litecoin_mweb_description": "Mweb သည် Protocol အသစ်ဖြစ်ပြီး LitCoin သို့ပိုမိုဈေးချိုသာသော, စျေးသက်သက်သာသာသုံးခြင်းနှင့်ပိုမိုများပြားသောပုဂ္ဂလိကငွေပို့ဆောင်မှုများကိုဖြစ်ပေါ်စေသည်",
400 "litecoin_mweb_dismiss": "ထုတ်ပစ်",
res/values/strings_nl.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "Licht",
395 "litecoin_enable_mweb_sync": "MWEB -scanning inschakelen",
396 "litecoin_mweb": "Mweb",
397 + "litecoin_mweb_allow_coins": "Sta mweb munten toe",
398 "litecoin_mweb_always_scan": "Stel mweb altijd op scannen",
399 "litecoin_mweb_description": "MWEB is een nieuw protocol dat snellere, goedkopere en meer privé -transacties naar Litecoin brengt",
400 "litecoin_mweb_dismiss": "Afwijzen",
res/values/strings_pl.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "Jasny",
395 "litecoin_enable_mweb_sync": "Włącz skanowanie MWEB",
396 "litecoin_mweb": "MWEB",
397 + "litecoin_mweb_allow_coins": "Zezwalaj na monety MWEB",
398 "litecoin_mweb_always_scan": "Ustaw MWEB zawsze skanowanie",
399 "litecoin_mweb_description": "MWEB to nowy protokół, który przynosi szybciej, tańsze i bardziej prywatne transakcje do Litecoin",
400 "litecoin_mweb_dismiss": "Odrzucać",
res/values/strings_pt.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "Luz",
395 "litecoin_enable_mweb_sync": "Ativar digitalização do MWEB",
396 "litecoin_mweb": "Mweb",
397 + "litecoin_mweb_allow_coins": "Permitir moedas MWEB",
398 "litecoin_mweb_always_scan": "Definir mweb sempre digitalizando",
399 "litecoin_mweb_description": "MWEB é um novo protocolo que traz transações mais rápidas, baratas e mais privadas para o Litecoin",
400 "litecoin_mweb_dismiss": "Liberar",
res/values/strings_ru.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "Светлая",
395 "litecoin_enable_mweb_sync": "Включить MWEB сканирование",
396 "litecoin_mweb": "Мвеб",
397 + "litecoin_mweb_allow_coins": "Разрешить монеты MWEB",
398 "litecoin_mweb_always_scan": "Установить MWEB всегда сканирование",
399 "litecoin_mweb_description": "MWEB - это новый протокол, который приносит быстрее, дешевле и более частные транзакции в Litecoin",
400 "litecoin_mweb_dismiss": "Увольнять",
res/values/strings_th.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "สว่าง",
395 "litecoin_enable_mweb_sync": "เปิดใช้งานการสแกน MWEB",
396 "litecoin_mweb": "mweb",
397 + "litecoin_mweb_allow_coins": "อนุญาตให้เหรียญ MWEB",
398 "litecoin_mweb_always_scan": "ตั้งค่าการสแกน MWEB เสมอ",
399 "litecoin_mweb_description": "MWEB เป็นโปรโตคอลใหม่ที่นำการทำธุรกรรมที่เร็วกว่าราคาถูกกว่าและเป็นส่วนตัวมากขึ้นไปยัง Litecoin",
400 "litecoin_mweb_dismiss": "อนุญาตให้ออกไป",
res/values/strings_tl.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "Light",
395 "litecoin_enable_mweb_sync": "Paganahin ang pag -scan ng MWeb",
396 "litecoin_mweb": "Mweb",
397 + "litecoin_mweb_allow_coins": "Payagan ang mga barya ng MWEB",
398 "litecoin_mweb_always_scan": "Itakda ang MWeb na laging nag -scan",
399 "litecoin_mweb_description": "Ang MWeb ay isang bagong protocol na nagdadala ng mas mabilis, mas mura, at mas maraming pribadong mga transaksyon sa Litecoin",
400 "litecoin_mweb_dismiss": "Tanggalin",
res/values/strings_tr.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "Aydınlık",
395 "litecoin_enable_mweb_sync": "MWEB taramasını etkinleştir",
396 "litecoin_mweb": "Mweb",
397 + "litecoin_mweb_allow_coins": "MWEB Coins'e izin ver",
398 "litecoin_mweb_always_scan": "MWEB'i her zaman taramayı ayarlayın",
399 "litecoin_mweb_description": "MWEB, Litecoin'e daha hızlı, daha ucuz ve daha fazla özel işlem getiren yeni bir protokoldür",
400 "litecoin_mweb_dismiss": "Azletmek",
res/values/strings_uk.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "Світла",
395 "litecoin_enable_mweb_sync": "Увімкнути сканування MWEB",
396 "litecoin_mweb": "Мвеб",
397 + "litecoin_mweb_allow_coins": "Дозволити монети MWEB",
398 "litecoin_mweb_always_scan": "Встановити mweb завжди сканувати",
399 "litecoin_mweb_description": "MWEB - це новий протокол, який приносить швидкі, дешевші та більш приватні транзакції Litecoin",
400 "litecoin_mweb_dismiss": "Звільнити",
res/values/strings_ur.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "روشنی",
395 "litecoin_enable_mweb_sync": "MWEB اسکیننگ کو فعال کریں",
396 "litecoin_mweb": "MWEB",
397 + "litecoin_mweb_allow_coins": "MWEB سکے کی اجازت دیں",
398 "litecoin_mweb_always_scan": "MWEB ہمیشہ اسکیننگ سیٹ کریں",
399 "litecoin_mweb_description": "MWEB ایک نیا پروٹوکول ہے جو لیٹیکوئن میں تیز ، سستا اور زیادہ نجی لین دین لاتا ہے",
400 "litecoin_mweb_dismiss": "خارج",
res/values/strings_vi.arb
+1
@@ -393,6 +393,7 @@
393 "light_theme": "Chủ đề sáng",
394 "litecoin_enable_mweb_sync": "Bật quét MWEB",
395 "litecoin_mweb": "Mweb",
396 + "litecoin_mweb_allow_coins": "Cho phép tiền xu MWEB",
397 "litecoin_mweb_always_scan": "Đặt MWEB luôn quét",
398 "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",
399 "litecoin_mweb_dismiss": "Miễn nhiệm",
res/values/strings_yo.arb
+1
@@ -395,6 +395,7 @@
395 "light_theme": "Funfun bí eérú",
396 "litecoin_enable_mweb_sync": "Mu mweb ọlọjẹ",
397 "litecoin_mweb": "Mweb",
398 + "litecoin_mweb_allow_coins": "Gba awọn owo Mweb gba",
399 "litecoin_mweb_always_scan": "Ṣeto mweb nigbagbogbo n ṣayẹwo",
400 "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",
401 "litecoin_mweb_dismiss": "Tuka",
res/values/strings_zh.arb
+1
@@ -394,6 +394,7 @@
394 "light_theme": "艳丽",
395 "litecoin_enable_mweb_sync": "启用MWEB扫描",
396 "litecoin_mweb": "MWEB",
397 + "litecoin_mweb_allow_coins": "允许MWEB硬币",
398 "litecoin_mweb_always_scan": "设置MWEB总是扫描",
399 "litecoin_mweb_description": "MWEB是一项新协议,它将更快,更便宜和更多的私人交易带给Litecoin",
400 "litecoin_mweb_dismiss": "解雇",