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": "解雇",