newdesign | blocked address text fields in exchange pages for wallet address; refactored exchange page and exchange template page; added base exchange widget and present provider picker
Oleksandr Sobol committed
May 18, 2020 at 20:23 UTC
8769e9302558bd4b6d874841cf51e0f4b28e3fc6
19 files changed
+680
-926
lib/generated/i18n.dart
+23
@@ -85,6 +85,7 @@ class S implements WidgetsLocalizations {
85
String get error_text_xmr => "XMR value can't exceed available balance.\nThe number of fraction digits must be less or equal to 12";
86
String get estimated => "Estimated";
87
String get exchange => "Exchange";
88
+ String get exchange_new_template => "New template";
89
String get exchange_result_write_down_ID => "*Please copy or write down your ID shown above.";
90
String get exchange_result_write_down_trade_id => "Please copy or write down the trade ID to continue.";
91
String get expired => "Expired";
@@ -869,6 +870,8 @@ class $de extends S {
870
@override
871
String get add_new_word => "Neues Wort hinzufügen";
872
@override
873
+ String get exchange_new_template => "Neue Vorlage";
874
+ @override
875
String get digit_pin => "-stelliger PIN";
876
@override
877
String get first_wallet_text => "tolle Brieftasche zum Monero";
@@ -1479,6 +1482,8 @@ class $hi extends S {
1482
@override
1483
String get add_new_word => "नया शब्द जोड़ें";
1484
@override
1485
+ String get exchange_new_template => "नया टेम्पलेट";
1486
+ @override
1487
String get digit_pin => "-अंक पिन";
1488
@override
1489
String get first_wallet_text => "बहुत बढ़िया बटुआ के लिये Monero";
@@ -2089,6 +2094,8 @@ class $ru extends S {
2094
@override
2095
String get add_new_word => "Добавить новое слово";
2096
@override
2097
+ String get exchange_new_template => "Новый шаблон";
2098
+ @override
2099
String get digit_pin => "-значный PIN";
2100
@override
2101
String get first_wallet_text => "В самом удобном кошельке для Monero";
@@ -2699,6 +2706,8 @@ class $ko extends S {
2706
@override
2707
String get add_new_word => "새로운 단어 추가";
2708
@override
2709
+ String get exchange_new_template => "새 템플릿";
2710
+ @override
2711
String get digit_pin => "숫자 PIN";
2712
@override
2713
String get first_wallet_text => "멋진 지갑 에 대한 Monero";
@@ -3309,6 +3318,8 @@ class $pt extends S {
3318
@override
3319
String get add_new_word => "Adicionar nova palavra";
3320
@override
3321
+ String get exchange_new_template => "Novo modelo";
3322
+ @override
3323
String get digit_pin => "dígitos";
3324
@override
3325
String get first_wallet_text => "Uma fantástica carteira para Monero";
@@ -3919,6 +3930,8 @@ class $uk extends S {
3930
@override
3931
String get add_new_word => "Добавити нове слово";
3932
@override
3933
+ String get exchange_new_template => "Новий шаблон";
3934
+ @override
3935
String get digit_pin => "-значний PIN";
3936
@override
3937
String get first_wallet_text => "В самому зручному гаманці для Monero";
@@ -4529,6 +4542,8 @@ class $ja extends S {
4542
@override
4543
String get add_new_word => "新しい単語を追加";
4544
@override
4545
+ String get exchange_new_template => "新しいテンプレート";
4546
+ @override
4547
String get digit_pin => "桁ピン";
4548
@override
4549
String get first_wallet_text => "素晴らしい財布 ために Monero";
@@ -5143,6 +5158,8 @@ class $pl extends S {
5158
@override
5159
String get add_new_word => "Dodaj nowe słowo";
5160
@override
5161
+ String get exchange_new_template => "Nowy szablon";
5162
+ @override
5163
String get digit_pin => "-znak PIN";
5164
@override
5165
String get first_wallet_text => "Niesamowity portfel dla Monero";
@@ -5753,6 +5770,8 @@ class $es extends S {
5770
@override
5771
String get add_new_word => "Agregar palabra nueva";
5772
@override
5773
+ String get exchange_new_template => "Nueva plantilla";
5774
+ @override
5775
String get digit_pin => "-dígito PIN";
5776
@override
5777
String get first_wallet_text => "Impresionante billetera para Monero";
@@ -6363,6 +6382,8 @@ class $nl extends S {
6382
@override
6383
String get add_new_word => "Nieuw woord toevoegen";
6384
@override
6385
+ String get exchange_new_template => "Nieuwe sjabloon";
6386
+ @override
6387
String get digit_pin => "-cijferige PIN";
6388
@override
6389
String get first_wallet_text => "Geweldige portemonnee fvoor Monero";
@@ -6973,6 +6994,8 @@ class $zh extends S {
6994
@override
6995
String get add_new_word => "添加新词";
6996
@override
6997
+ String get exchange_new_template => "新範本";
6998
+ @override
6999
String get digit_pin => "数字别针";
7000
@override
7001
String get first_wallet_text => "很棒的钱包 对于 Monero";
lib/src/screens/exchange/exchange_page.dart
+8
-516
@@ -1,31 +1,15 @@
1
import 'dart:ui';
2
-import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart';
3
-import 'package:cake_wallet/src/domain/exchange/exchange_template.dart';
4
-import 'package:cake_wallet/src/widgets/template_tile.dart';
5
-import 'package:dotted_border/dotted_border.dart';
2
import 'package:flutter/cupertino.dart';
3
import 'package:flutter/material.dart';
8
-import 'package:flutter_mobx/flutter_mobx.dart';
9
-import 'package:mobx/mobx.dart';
4
import 'package:provider/provider.dart';
5
import 'package:cake_wallet/palette.dart';
12
-import 'package:cake_wallet/routes.dart';
6
import 'package:cake_wallet/generated/i18n.dart';
14
-import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
15
-import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart';
16
-import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_exchange_provider.dart';
17
-import 'package:cake_wallet/src/stores/exchange/exchange_trade_state.dart';
18
-import 'package:cake_wallet/src/stores/exchange/limits_state.dart';
7
import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
8
import 'package:cake_wallet/src/stores/exchange/exchange_store.dart';
9
import 'package:cake_wallet/src/screens/base_page.dart';
22
-import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.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/top_panel.dart';
26
-import 'package:cake_wallet/src/screens/exchange/widgets/provider_picker.dart';
10
import 'package:cake_wallet/src/stores/exchange_template/exchange_template_store.dart';
28
-import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
11
+import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker.dart';
12
+import 'package:cake_wallet/src/screens/exchange/widgets/base_exchange_widget.dart';
13
14
class ExchangePage extends BasePage {
15
@override
@@ -34,46 +18,11 @@ class ExchangePage extends BasePage {
18
@override
19
Color get backgroundColor => PaletteDark.walletCardSubAddressField;
20
37
- final Image arrowBottom =
38
- Image.asset('assets/images/arrow_bottom_purple_icon.png', color: Colors.white, height: 6);
39
-
21
@override
22
Widget middle(BuildContext context) {
23
final exchangeStore = Provider.of<ExchangeStore>(context);
24
44
- return FlatButton(
45
- onPressed: () => _presentProviderPicker(context),
46
- highlightColor: Colors.transparent,
47
- splashColor: Colors.transparent,
48
- child: Row(
49
- mainAxisSize: MainAxisSize.min,
50
- crossAxisAlignment: CrossAxisAlignment.start,
51
- children: <Widget>[
52
- Column(
53
- crossAxisAlignment: CrossAxisAlignment.center,
54
- mainAxisSize: MainAxisSize.min,
55
- children: <Widget>[
56
- Text(S.of(context).exchange,
57
- style: TextStyle(
58
- fontSize: 16.0,
59
- fontWeight: FontWeight.w400,
60
- color: Colors.white)),
61
- Observer(
62
- builder: (_) => Text('${exchangeStore.provider.title}',
63
- style: TextStyle(
64
- fontSize: 10.0,
65
- fontWeight: FontWeight.w400,
66
- color:PaletteDark.walletCardText)))
67
- ],
68
- ),
69
- SizedBox(width: 5),
70
- Padding(
71
- padding: EdgeInsets.only(top: 8),
72
- child: arrowBottom,
73
- )
74
- ],
75
- )
76
- );
25
+ return PresentProviderPicker(exchangeStore: exchangeStore);
26
}
27
28
@override
@@ -99,37 +48,6 @@ class ExchangePage extends BasePage {
48
49
@override
50
Widget body(BuildContext context) => ExchangeForm();
102
-
103
- void _presentProviderPicker(BuildContext context) {
104
- final exchangeStore = Provider.of<ExchangeStore>(context);
105
- final items = exchangeStore.providersForCurrentPair();
106
- final selectedItem = items.indexOf(exchangeStore.provider);
107
- final images = List<Image>();
108
-
109
- for (ExchangeProvider provider in items) {
110
- switch (provider.description) {
111
- case ExchangeProviderDescription.xmrto:
112
- images.add(Image.asset('assets/images/xmr_btc.png'));
113
- break;
114
- case ExchangeProviderDescription.changeNow:
115
- images.add(Image.asset('assets/images/change_now.png'));
116
- break;
117
- case ExchangeProviderDescription.morphToken:
118
- images.add(Image.asset('assets/images/morph_icon.png'));
119
- break;
120
- }
121
- }
122
-
123
- showDialog<void>(
124
- builder: (_) => ProviderPicker(
125
- items: items,
126
- images: images,
127
- selectedAtIndex: selectedItem,
128
- title: S.of(context).change_exchange_provider,
129
- onItemSelected: (ExchangeProvider provider) =>
130
- exchangeStore.changeProvider(provider: provider)),
131
- context: context);
132
- }
51
}
52
53
class ExchangeForm extends StatefulWidget {
@@ -138,21 +56,6 @@ class ExchangeForm extends StatefulWidget {
56
}
57
58
class ExchangeFormState extends State<ExchangeForm> {
141
- final depositKey = GlobalKey<ExchangeCardState>();
142
- final receiveKey = GlobalKey<ExchangeCardState>();
143
- final _formKey = GlobalKey<FormState>();
144
- var _isReactionsSet = false;
145
-
146
- final Image arrowBottomPurple = Image.asset(
147
- 'assets/images/arrow_bottom_purple_icon.png',
148
- color: Colors.white,
149
- height: 8,
150
- );
151
- final Image arrowBottomCakeGreen = Image.asset(
152
- 'assets/images/arrow_bottom_cake_green.png',
153
- color: Colors.white,
154
- height: 8,
155
- );
59
60
@override
61
Widget build(BuildContext context) {
@@ -160,422 +63,11 @@ class ExchangeFormState extends State<ExchangeForm> {
63
final walletStore = Provider.of<WalletStore>(context);
64
final exchangeTemplateStore = Provider.of<ExchangeTemplateStore>(context);
65
163
- final depositWalletName =
164
- exchangeStore.depositCurrency == CryptoCurrency.xmr
165
- ? walletStore.name
166
- : null;
167
- final receiveWalletName =
168
- exchangeStore.receiveCurrency == CryptoCurrency.xmr
169
- ? walletStore.name
170
- : null;
171
-
172
- WidgetsBinding.instance.addPostFrameCallback(
173
- (_) => _setReactions(context, exchangeStore, walletStore));
174
-
175
- return Container(
176
- color: PaletteDark.historyPanel,
177
- child: Form(
178
- key: _formKey,
179
- child: ScrollableWithBottomSection(
180
- contentPadding: EdgeInsets.only(bottom: 24),
181
- content: Column(
182
- children: <Widget>[
183
- TopPanel(
184
- color: PaletteDark.menuList,
185
- edgeInsets: EdgeInsets.only(bottom: 24),
186
- widget: Column(
187
- children: <Widget>[
188
- TopPanel(
189
- color: PaletteDark.walletCardSubAddressField,
190
- widget: Observer(
191
- builder: (_) => ExchangeCard(
192
- key: depositKey,
193
- title: S.of(context).you_will_send,
194
- initialCurrency: exchangeStore.depositCurrency,
195
- initialWalletName: depositWalletName,
196
- initialAddress:
197
- exchangeStore.depositCurrency == walletStore.type
198
- ? walletStore.address
199
- : exchangeStore.depositAddress,
200
- initialIsAmountEditable: true,
201
- initialIsAddressEditable: true,
202
- isAmountEstimated: false,
203
- currencies: CryptoCurrency.all,
204
- onCurrencySelected: (currency) =>
205
- exchangeStore.changeDepositCurrency(currency: currency),
206
- imageArrow: arrowBottomPurple,
207
- currencyButtonColor: PaletteDark.walletCardSubAddressField,
208
- addressButtonsColor: PaletteDark.menuList,
209
- currencyValueValidator: (value) {
210
- exchangeStore.validateCryptoCurrency(value);
211
- return exchangeStore.errorMessage;
212
- },
213
- addressTextFieldValidator: (value) {
214
- exchangeStore.validateAddress(value,
215
- cryptoCurrency: exchangeStore.depositCurrency);
216
- return exchangeStore.errorMessage;
217
- },
218
- ),
219
- )
220
- ),
221
- Padding(
222
- padding: EdgeInsets.only(top: 32, left: 24, right: 24),
223
- child: Observer(
224
- builder: (_) => ExchangeCard(
225
- key: receiveKey,
226
- title: S.of(context).you_will_get,
227
- initialCurrency: exchangeStore.receiveCurrency,
228
- initialWalletName: receiveWalletName,
229
- initialAddress:
230
- exchangeStore.receiveCurrency == walletStore.type
231
- ? walletStore.address
232
- : exchangeStore.receiveAddress,
233
- initialIsAmountEditable: false,
234
- initialIsAddressEditable: true,
235
- isAmountEstimated: true,
236
- currencies: CryptoCurrency.all,
237
- onCurrencySelected: (currency) => exchangeStore
238
- .changeReceiveCurrency(currency: currency),
239
- imageArrow: arrowBottomCakeGreen,
240
- currencyButtonColor: PaletteDark.menuList,
241
- currencyValueValidator: (value) {
242
- exchangeStore.validateCryptoCurrency(value);
243
- return exchangeStore.errorMessage;
244
- },
245
- addressTextFieldValidator: (value) {
246
- exchangeStore.validateAddress(value,
247
- cryptoCurrency: exchangeStore.receiveCurrency);
248
- return exchangeStore.errorMessage;
249
- },
250
- )),
251
- )
252
- ],
253
- )
254
- ),
255
- Padding(
256
- padding: EdgeInsets.only(
257
- top: 32,
258
- left: 24,
259
- bottom: 24
260
- ),
261
- child: Row(
262
- mainAxisAlignment: MainAxisAlignment.start,
263
- children: <Widget>[
264
- Text(
265
- S.of(context).send_templates,
266
- style: TextStyle(
267
- fontSize: 18,
268
- fontWeight: FontWeight.w600,
269
- color: PaletteDark.walletCardText
270
- ),
271
- )
272
- ],
273
- ),
274
- ),
275
- Container(
276
- height: 40,
277
- width: double.infinity,
278
- padding: EdgeInsets.only(left: 24),
279
- child: Observer(
280
- builder: (_) {
281
- final itemCount = exchangeTemplateStore.templates.length + 1;
282
-
283
- return ListView.builder(
284
- scrollDirection: Axis.horizontal,
285
- itemCount: itemCount,
286
- itemBuilder: (context, index) {
287
-
288
- if (index == 0) {
289
- return GestureDetector(
290
- onTap: () => Navigator.of(context)
291
- .pushNamed(Routes.exchangeTemplate),
292
- child: Container(
293
- padding: EdgeInsets.only(right: 10),
294
- child: DottedBorder(
295
- borderType: BorderType.RRect,
296
- dashPattern: [8, 4],
297
- color: PaletteDark.menuList,
298
- strokeWidth: 2,
299
- radius: Radius.circular(20),
300
- child: Container(
301
- height: 40,
302
- width: 75,
303
- padding: EdgeInsets.only(left: 10, right: 10),
304
- alignment: Alignment.center,
305
- decoration: BoxDecoration(
306
- borderRadius: BorderRadius.all(Radius.circular(20)),
307
- color: Colors.transparent,
308
- ),
309
- child: Text(
310
- S.of(context).send_new,
311
- style: TextStyle(
312
- fontSize: 14,
313
- fontWeight: FontWeight.w600,
314
- color: PaletteDark.walletCardText
315
- ),
316
- ),
317
- )
318
- ),
319
- ),
320
- );
321
- }
322
-
323
- index -= 1;
324
-
325
- final template = exchangeTemplateStore.templates[index];
326
-
327
- return TemplateTile(
328
- amount: template.amount,
329
- from: template.depositCurrency,
330
- to: template.receiveCurrency,
331
- onTap: () {
332
- applyTemplate(exchangeStore, template);
333
- }
334
- );
335
- }
336
- );
337
- }
338
- ),
339
- )
340
- ],
341
- ),
342
- bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
343
- bottomSection: Column(children: <Widget>[
344
- Padding(
345
- padding: EdgeInsets.only(bottom: 15),
346
- child: Observer(builder: (_) {
347
- final description =
348
- exchangeStore.provider is XMRTOExchangeProvider
349
- ? S.of(context).amount_is_guaranteed
350
- : S.of(context).amount_is_estimate;
351
- return Center(
352
- child: Text(
353
- description,
354
- style: TextStyle(
355
- color: PaletteDark.walletCardText,
356
- fontSize: 12
357
- ),
358
- ),
359
- );
360
- }),
361
- ),
362
- Observer(
363
- builder: (_) => LoadingPrimaryButton(
364
- text: S.of(context).exchange,
365
- onPressed: () {
366
- if (_formKey.currentState.validate()) {
367
- exchangeStore.createTrade();
368
- }
369
- },
370
- color: Colors.blue,
371
- textColor: Colors.white,
372
- isLoading: exchangeStore.tradeState is TradeIsCreating,
373
- )),
374
- ]),
375
- )),
66
+ return BaseExchangeWidget(
67
+ exchangeStore: exchangeStore,
68
+ walletStore: walletStore,
69
+ exchangeTemplateStore: exchangeTemplateStore,
70
+ isTemplate: false
71
);
72
}
378
-
379
- void applyTemplate(ExchangeStore store, ExchangeTemplate template) {
380
- store.changeDepositCurrency(currency: CryptoCurrency.fromString(template.depositCurrency));
381
- store.changeReceiveCurrency(currency: CryptoCurrency.fromString(template.receiveCurrency));
382
-
383
- switch (template.provider) {
384
- case 'XMR.TO':
385
- store.changeProvider(provider: store.providerList[0]);
386
- break;
387
- case 'ChangeNOW':
388
- store.changeProvider(provider: store.providerList[1]);
389
- break;
390
- case 'MorphToken':
391
- store.changeProvider(provider: store.providerList[2]);
392
- break;
393
- }
394
-
395
- store.changeDepositAmount(amount: template.amount);
396
- store.depositAddress = template.depositAddress;
397
- store.receiveAddress = template.receiveAddress;
398
- }
399
-
400
- void _setReactions(
401
- BuildContext context, ExchangeStore store, WalletStore walletStore) {
402
- if (_isReactionsSet) {
403
- return;
404
- }
405
-
406
- final depositAddressController = depositKey.currentState.addressController;
407
- final depositAmountController = depositKey.currentState.amountController;
408
- final receiveAddressController = receiveKey.currentState.addressController;
409
- final receiveAmountController = receiveKey.currentState.amountController;
410
- final limitsState = store.limitsState;
411
-
412
- if (limitsState is LimitsLoadedSuccessfully) {
413
- final min = limitsState.limits.min != null
414
- ? limitsState.limits.min.toString()
415
- : null;
416
- final max = limitsState.limits.max != null
417
- ? limitsState.limits.max.toString()
418
- : null;
419
- final key = depositKey;
420
- key.currentState.changeLimits(min: min, max: max);
421
- }
422
-
423
- _onCurrencyChange(store.receiveCurrency, walletStore, receiveKey);
424
- _onCurrencyChange(store.depositCurrency, walletStore, depositKey);
425
-
426
- reaction(
427
- (_) => walletStore.name,
428
- (String _) => _onWalletNameChange(
429
- walletStore, store.receiveCurrency, receiveKey));
430
-
431
- reaction(
432
- (_) => walletStore.name,
433
- (String _) => _onWalletNameChange(
434
- walletStore, store.depositCurrency, depositKey));
435
-
436
- reaction(
437
- (_) => store.receiveCurrency,
438
- (CryptoCurrency currency) =>
439
- _onCurrencyChange(currency, walletStore, receiveKey));
440
-
441
- reaction(
442
- (_) => store.depositCurrency,
443
- (CryptoCurrency currency) =>
444
- _onCurrencyChange(currency, walletStore, depositKey));
445
-
446
- reaction((_) => store.depositAmount, (String amount) {
447
- if (depositKey.currentState.amountController.text != amount) {
448
- depositKey.currentState.amountController.text = amount;
449
- }
450
- });
451
-
452
- reaction((_) => store.depositAddress, (String address) {
453
- if (depositKey.currentState.addressController.text != address) {
454
- depositKey.currentState.addressController.text = address;
455
- }
456
- });
457
-
458
- reaction((_) => store.receiveAmount, (String amount) {
459
- if (receiveKey.currentState.amountController.text !=
460
- store.receiveAmount) {
461
- receiveKey.currentState.amountController.text = amount;
462
- }
463
- });
464
-
465
- reaction((_) => store.receiveAddress, (String address) {
466
- if (receiveKey.currentState.addressController.text != address) {
467
- receiveKey.currentState.addressController.text = address;
468
- }
469
- });
470
-
471
- reaction((_) => store.provider, (ExchangeProvider provider) {
472
- receiveKey.currentState.isAddressEditable(isEditable: true);
473
- receiveKey.currentState.isAmountEditable(isEditable: false);
474
- depositKey.currentState.isAddressEditable(isEditable: true);
475
- depositKey.currentState.isAmountEditable(isEditable: true);
476
-
477
- receiveKey.currentState.changeIsAmountEstimated(true);
478
- });
479
-
480
- reaction((_) => store.tradeState, (ExchangeTradeState state) {
481
- if (state is TradeIsCreatedFailure) {
482
- WidgetsBinding.instance.addPostFrameCallback((_) {
483
- showDialog<void>(
484
- context: context,
485
- builder: (BuildContext context) {
486
- return AlertWithOneAction(
487
- alertTitle: S.of(context).error,
488
- alertContent: state.error,
489
- buttonText: S.of(context).ok,
490
- buttonAction: () => Navigator.of(context).pop()
491
- );
492
- });
493
- });
494
- }
495
- if (state is TradeIsCreatedSuccessfully) {
496
- Navigator.of(context)
497
- .pushNamed(Routes.exchangeConfirm, arguments: state.trade);
498
- }
499
- });
500
-
501
- reaction((_) => store.limitsState, (LimitsState state) {
502
- String min;
503
- String max;
504
-
505
- if (state is LimitsLoadedSuccessfully) {
506
- min = state.limits.min != null ? state.limits.min.toString() : null;
507
- max = state.limits.max != null ? state.limits.max.toString() : null;
508
- }
509
-
510
- if (state is LimitsLoadedFailure) {
511
- min = '0';
512
- max = '0';
513
- }
514
-
515
- if (state is LimitsIsLoading) {
516
- min = '...';
517
- max = '...';
518
- }
519
-
520
- depositKey.currentState.changeLimits(min: min, max: max);
521
- receiveKey.currentState.changeLimits(min: null, max: null);
522
- });
523
-
524
- depositAddressController.addListener(
525
- () => store.depositAddress = depositAddressController.text);
526
-
527
- depositAmountController.addListener(() {
528
- if (depositAmountController.text != store.depositAmount) {
529
- store.changeDepositAmount(amount: depositAmountController.text);
530
- }
531
- });
532
-
533
- receiveAddressController.addListener(
534
- () => store.receiveAddress = receiveAddressController.text);
535
-
536
- receiveAmountController.addListener(() {
537
- if (receiveAmountController.text != store.receiveAmount) {
538
- store.changeReceiveAmount(amount: receiveAmountController.text);
539
- }
540
- });
541
-
542
- reaction((_) => walletStore.address, (String address) {
543
- if (store.depositCurrency == CryptoCurrency.xmr) {
544
- depositKey.currentState.changeAddress(address: address);
545
- }
546
-
547
- if (store.receiveCurrency == CryptoCurrency.xmr) {
548
- receiveKey.currentState.changeAddress(address: address);
549
- }
550
- });
551
-
552
- _isReactionsSet = true;
553
- }
554
-
555
- void _onCurrencyChange(CryptoCurrency currency, WalletStore walletStore,
556
- GlobalKey<ExchangeCardState> key) {
557
- final isCurrentTypeWallet = currency == walletStore.type;
558
-
559
- key.currentState.changeSelectedCurrency(currency);
560
- key.currentState
561
- .changeWalletName(isCurrentTypeWallet ? walletStore.name : null);
562
-
563
- key.currentState
564
- .changeAddress(address: isCurrentTypeWallet ? walletStore.address : '');
565
-
566
- key.currentState.changeAmount(amount: '');
567
- }
568
-
569
- void _onWalletNameChange(WalletStore walletStore, CryptoCurrency currency,
570
- GlobalKey<ExchangeCardState> key) {
571
- final isCurrentTypeWallet = currency == walletStore.type;
572
-
573
- if (isCurrentTypeWallet) {
574
- key.currentState.changeWalletName(walletStore.name);
575
- key.currentState.addressController.text = walletStore.address;
576
- } else if (key.currentState.addressController.text == walletStore.address) {
577
- key.currentState.changeWalletName(null);
578
- key.currentState.addressController.text = null;
579
- }
580
- }
73
}
lib/src/screens/exchange/exchange_template_page.dart
+10
-406
@@ -1,106 +1,28 @@
1
import 'dart:ui';
2
-import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart';
2
import 'package:flutter/cupertino.dart';
3
import 'package:flutter/material.dart';
5
-import 'package:flutter_mobx/flutter_mobx.dart';
6
-import 'package:mobx/mobx.dart';
4
import 'package:provider/provider.dart';
5
import 'package:cake_wallet/palette.dart';
9
-import 'package:cake_wallet/routes.dart';
10
-import 'package:cake_wallet/generated/i18n.dart';
11
-import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
12
-import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart';
13
-import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_exchange_provider.dart';
14
-import 'package:cake_wallet/src/stores/exchange/exchange_trade_state.dart';
15
-import 'package:cake_wallet/src/stores/exchange/limits_state.dart';
6
import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
7
import 'package:cake_wallet/src/stores/exchange/exchange_store.dart';
8
import 'package:cake_wallet/src/screens/base_page.dart';
19
-import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart';
20
-import 'package:cake_wallet/src/widgets/primary_button.dart';
21
-import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
22
-import 'package:cake_wallet/src/widgets/top_panel.dart';
23
-import 'package:cake_wallet/src/screens/exchange/widgets/provider_picker.dart';
9
+import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker.dart';
10
import 'package:cake_wallet/src/stores/exchange_template/exchange_template_store.dart';
11
+import 'package:cake_wallet/src/screens/exchange/widgets/base_exchange_widget.dart';
12
+import 'package:cake_wallet/generated/i18n.dart';
13
14
class ExchangeTemplatePage extends BasePage {
15
@override
28
- String get title => 'New template';
16
+ String get title => S.current.exchange_new_template;
17
18
@override
19
Color get backgroundColor => PaletteDark.walletCardSubAddressField;
20
33
- final Image arrowBottom =
34
- Image.asset('assets/images/arrow_bottom_purple_icon.png', color: Colors.white, height: 6);
35
-
21
@override
22
Widget trailing(BuildContext context) {
23
final exchangeStore = Provider.of<ExchangeStore>(context);
24
40
- return FlatButton(
41
- onPressed: () => _presentProviderPicker(context),
42
- highlightColor: Colors.transparent,
43
- splashColor: Colors.transparent,
44
- child: Row(
45
- mainAxisSize: MainAxisSize.min,
46
- crossAxisAlignment: CrossAxisAlignment.start,
47
- children: <Widget>[
48
- Column(
49
- crossAxisAlignment: CrossAxisAlignment.center,
50
- mainAxisSize: MainAxisSize.min,
51
- children: <Widget>[
52
- Text(S.of(context).exchange,
53
- style: TextStyle(
54
- fontSize: 16.0,
55
- fontWeight: FontWeight.w400,
56
- color: Colors.white)),
57
- Observer(
58
- builder: (_) => Text('${exchangeStore.provider.title}',
59
- style: TextStyle(
60
- fontSize: 10.0,
61
- fontWeight: FontWeight.w400,
62
- color:PaletteDark.walletCardText)))
63
- ],
64
- ),
65
- SizedBox(width: 5),
66
- Padding(
67
- padding: EdgeInsets.only(top: 8),
68
- child: arrowBottom,
69
- )
70
- ],
71
- )
72
- );
73
- }
74
-
75
- void _presentProviderPicker(BuildContext context) {
76
- final exchangeStore = Provider.of<ExchangeStore>(context);
77
- final items = exchangeStore.providersForCurrentPair();
78
- final selectedItem = items.indexOf(exchangeStore.provider);
79
- final images = List<Image>();
80
-
81
- for (ExchangeProvider provider in items) {
82
- switch (provider.description) {
83
- case ExchangeProviderDescription.xmrto:
84
- images.add(Image.asset('assets/images/xmr_btc.png'));
85
- break;
86
- case ExchangeProviderDescription.changeNow:
87
- images.add(Image.asset('assets/images/change_now.png'));
88
- break;
89
- case ExchangeProviderDescription.morphToken:
90
- images.add(Image.asset('assets/images/morph_icon.png'));
91
- break;
92
- }
93
- }
94
-
95
- showDialog<void>(
96
- builder: (_) => ProviderPicker(
97
- items: items,
98
- images: images,
99
- selectedAtIndex: selectedItem,
100
- title: S.of(context).change_exchange_provider,
101
- onItemSelected: (ExchangeProvider provider) =>
102
- exchangeStore.changeProvider(provider: provider)),
103
- context: context);
25
+ return PresentProviderPicker(exchangeStore: exchangeStore);
26
}
27
28
@override
@@ -113,21 +35,6 @@ class ExchangeTemplateForm extends StatefulWidget{
35
}
36
37
class ExchangeTemplateFormState extends State<ExchangeTemplateForm> {
116
- final depositKey = GlobalKey<ExchangeCardState>();
117
- final receiveKey = GlobalKey<ExchangeCardState>();
118
- final _formKey = GlobalKey<FormState>();
119
- var _isReactionsSet = false;
120
-
121
- final Image arrowBottomPurple = Image.asset(
122
- 'assets/images/arrow_bottom_purple_icon.png',
123
- color: Colors.white,
124
- height: 8,
125
- );
126
- final Image arrowBottomCakeGreen = Image.asset(
127
- 'assets/images/arrow_bottom_cake_green.png',
128
- color: Colors.white,
129
- height: 8,
130
- );
38
39
@override
40
Widget build(BuildContext context) {
@@ -135,314 +42,11 @@ class ExchangeTemplateFormState extends State<ExchangeTemplateForm> {
42
final walletStore = Provider.of<WalletStore>(context);
43
final exchangeTemplateStore = Provider.of<ExchangeTemplateStore>(context);
44
138
- final depositWalletName =
139
- exchangeStore.depositCurrency == CryptoCurrency.xmr
140
- ? walletStore.name
141
- : null;
142
- final receiveWalletName =
143
- exchangeStore.receiveCurrency == CryptoCurrency.xmr
144
- ? walletStore.name
145
- : null;
146
-
147
- WidgetsBinding.instance.addPostFrameCallback(
148
- (_) => _setReactions(context, exchangeStore, walletStore));
149
-
150
- return Container(
151
- color: PaletteDark.historyPanel,
152
- child: Form(
153
- key: _formKey,
154
- child: ScrollableWithBottomSection(
155
- contentPadding: EdgeInsets.only(bottom: 24),
156
- content: Column(
157
- children: <Widget>[
158
- TopPanel(
159
- color: PaletteDark.menuList,
160
- edgeInsets: EdgeInsets.only(bottom: 24),
161
- widget: Column(
162
- children: <Widget>[
163
- TopPanel(
164
- color: PaletteDark.walletCardSubAddressField,
165
- widget: Observer(
166
- builder: (_) => ExchangeCard(
167
- key: depositKey,
168
- title: S.of(context).you_will_send,
169
- initialCurrency: exchangeStore.depositCurrency,
170
- initialWalletName: depositWalletName,
171
- initialAddress:
172
- exchangeStore.depositCurrency == walletStore.type
173
- ? walletStore.address
174
- : null,
175
- initialIsAmountEditable: true,
176
- initialIsAddressEditable: true,
177
- isAmountEstimated: false,
178
- currencies: CryptoCurrency.all,
179
- onCurrencySelected: (currency) =>
180
- exchangeStore.changeDepositCurrency(currency: currency),
181
- imageArrow: arrowBottomPurple,
182
- currencyButtonColor: PaletteDark.walletCardSubAddressField,
183
- addressButtonsColor: PaletteDark.menuList,
184
- currencyValueValidator: (value) {
185
- exchangeStore.validateCryptoCurrency(value);
186
- return exchangeStore.errorMessage;
187
- },
188
- addressTextFieldValidator: (value) {
189
- exchangeStore.validateAddress(value,
190
- cryptoCurrency: exchangeStore.depositCurrency);
191
- return exchangeStore.errorMessage;
192
- },
193
- ),
194
- )
195
- ),
196
- Padding(
197
- padding: EdgeInsets.only(top: 32, left: 24, right: 24),
198
- child: Observer(
199
- builder: (_) => ExchangeCard(
200
- key: receiveKey,
201
- title: S.of(context).you_will_get,
202
- initialCurrency: exchangeStore.receiveCurrency,
203
- initialWalletName: receiveWalletName,
204
- initialAddress:
205
- exchangeStore.receiveCurrency == walletStore.type
206
- ? walletStore.address
207
- : null,
208
- initialIsAmountEditable: false,
209
- initialIsAddressEditable: true,
210
- isAmountEstimated: true,
211
- currencies: CryptoCurrency.all,
212
- onCurrencySelected: (currency) => exchangeStore
213
- .changeReceiveCurrency(currency: currency),
214
- imageArrow: arrowBottomCakeGreen,
215
- currencyButtonColor: PaletteDark.menuList,
216
- currencyValueValidator: (value) {
217
- exchangeStore.validateCryptoCurrency(value);
218
- return exchangeStore.errorMessage;
219
- },
220
- addressTextFieldValidator: (value) {
221
- exchangeStore.validateAddress(value,
222
- cryptoCurrency: exchangeStore.receiveCurrency);
223
- return exchangeStore.errorMessage;
224
- },
225
- )),
226
- )
227
- ],
228
- )
229
- ),
230
- ],
231
- ),
232
- bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
233
- bottomSection: Column(children: <Widget>[
234
- Padding(
235
- padding: EdgeInsets.only(bottom: 15),
236
- child: Observer(builder: (_) {
237
- final description =
238
- exchangeStore.provider is XMRTOExchangeProvider
239
- ? S.of(context).amount_is_guaranteed
240
- : S.of(context).amount_is_estimate;
241
- return Center(
242
- child: Text(
243
- description,
244
- style: TextStyle(
245
- color: PaletteDark.walletCardText,
246
- fontSize: 12
247
- ),
248
- ),
249
- );
250
- }),
251
- ),
252
- PrimaryButton(
253
- onPressed: () {
254
- if (_formKey.currentState.validate()) {
255
- exchangeTemplateStore.addTemplate(
256
- amount: exchangeStore.depositAmount,
257
- depositCurrency: exchangeStore.depositCurrency.toString(),
258
- receiveCurrency: exchangeStore.receiveCurrency.toString(),
259
- provider: exchangeStore.provider.toString(),
260
- depositAddress: exchangeStore.depositAddress,
261
- receiveAddress: exchangeStore.receiveAddress
262
- );
263
- exchangeTemplateStore.update();
264
- Navigator.of(context).pop();
265
- }
266
- },
267
- text: S.of(context).save,
268
- color: Colors.green,
269
- textColor: Colors.white
270
- ),
271
- ]),
272
- )),
45
+ return BaseExchangeWidget(
46
+ exchangeStore: exchangeStore,
47
+ walletStore: walletStore,
48
+ exchangeTemplateStore: exchangeTemplateStore,
49
+ isTemplate: true
50
);
51
}
275
-
276
- void _setReactions(
277
- BuildContext context, ExchangeStore store, WalletStore walletStore) {
278
- if (_isReactionsSet) {
279
- return;
280
- }
281
-
282
- final depositAddressController = depositKey.currentState.addressController;
283
- final depositAmountController = depositKey.currentState.amountController;
284
- final receiveAddressController = receiveKey.currentState.addressController;
285
- final receiveAmountController = receiveKey.currentState.amountController;
286
- final limitsState = store.limitsState;
287
-
288
- if (limitsState is LimitsLoadedSuccessfully) {
289
- final min = limitsState.limits.min != null
290
- ? limitsState.limits.min.toString()
291
- : null;
292
- final max = limitsState.limits.max != null
293
- ? limitsState.limits.max.toString()
294
- : null;
295
- final key = depositKey;
296
- key.currentState.changeLimits(min: min, max: max);
297
- }
298
-
299
- _onCurrencyChange(store.receiveCurrency, walletStore, receiveKey);
300
- _onCurrencyChange(store.depositCurrency, walletStore, depositKey);
301
-
302
- reaction(
303
- (_) => walletStore.name,
304
- (String _) => _onWalletNameChange(
305
- walletStore, store.receiveCurrency, receiveKey));
306
-
307
- reaction(
308
- (_) => walletStore.name,
309
- (String _) => _onWalletNameChange(
310
- walletStore, store.depositCurrency, depositKey));
311
-
312
- reaction(
313
- (_) => store.receiveCurrency,
314
- (CryptoCurrency currency) =>
315
- _onCurrencyChange(currency, walletStore, receiveKey));
316
-
317
- reaction(
318
- (_) => store.depositCurrency,
319
- (CryptoCurrency currency) =>
320
- _onCurrencyChange(currency, walletStore, depositKey));
321
-
322
- reaction((_) => store.depositAmount, (String amount) {
323
- if (depositKey.currentState.amountController.text != amount) {
324
- depositKey.currentState.amountController.text = amount;
325
- }
326
- });
327
-
328
- reaction((_) => store.receiveAmount, (String amount) {
329
- if (receiveKey.currentState.amountController.text !=
330
- store.receiveAmount) {
331
- receiveKey.currentState.amountController.text = amount;
332
- }
333
- });
334
-
335
- reaction((_) => store.provider, (ExchangeProvider provider) {
336
- receiveKey.currentState.isAddressEditable(isEditable: true);
337
- receiveKey.currentState.isAmountEditable(isEditable: false);
338
- depositKey.currentState.isAddressEditable(isEditable: true);
339
- depositKey.currentState.isAmountEditable(isEditable: true);
340
-
341
- receiveKey.currentState.changeIsAmountEstimated(true);
342
- });
343
-
344
- reaction((_) => store.tradeState, (ExchangeTradeState state) {
345
- if (state is TradeIsCreatedFailure) {
346
- WidgetsBinding.instance.addPostFrameCallback((_) {
347
- showDialog<void>(
348
- context: context,
349
- builder: (BuildContext context) {
350
- return AlertDialog(
351
- title: Text(S.of(context).error),
352
- content: Text(state.error),
353
- actions: <Widget>[
354
- FlatButton(
355
- child: Text(S.of(context).ok),
356
- onPressed: () => Navigator.of(context).pop())
357
- ],
358
- );
359
- });
360
- });
361
- }
362
- if (state is TradeIsCreatedSuccessfully) {
363
- Navigator.of(context)
364
- .pushNamed(Routes.exchangeConfirm, arguments: state.trade);
365
- }
366
- });
367
-
368
- reaction((_) => store.limitsState, (LimitsState state) {
369
- String min;
370
- String max;
371
-
372
- if (state is LimitsLoadedSuccessfully) {
373
- min = state.limits.min != null ? state.limits.min.toString() : null;
374
- max = state.limits.max != null ? state.limits.max.toString() : null;
375
- }
376
-
377
- if (state is LimitsLoadedFailure) {
378
- min = '0';
379
- max = '0';
380
- }
381
-
382
- if (state is LimitsIsLoading) {
383
- min = '...';
384
- max = '...';
385
- }
386
-
387
- depositKey.currentState.changeLimits(min: min, max: max);
388
- receiveKey.currentState.changeLimits(min: null, max: null);
389
- });
390
-
391
- depositAddressController.addListener(
392
- () => store.depositAddress = depositAddressController.text);
393
-
394
- depositAmountController.addListener(() {
395
- if (depositAmountController.text != store.depositAmount) {
396
- store.changeDepositAmount(amount: depositAmountController.text);
397
- }
398
- });
399
-
400
- receiveAddressController.addListener(
401
- () => store.receiveAddress = receiveAddressController.text);
402
-
403
- receiveAmountController.addListener(() {
404
- if (receiveAmountController.text != store.receiveAmount) {
405
- store.changeReceiveAmount(amount: receiveAmountController.text);
406
- }
407
- });
408
-
409
- reaction((_) => walletStore.address, (String address) {
410
- if (store.depositCurrency == CryptoCurrency.xmr) {
411
- depositKey.currentState.changeAddress(address: address);
412
- }
413
-
414
- if (store.receiveCurrency == CryptoCurrency.xmr) {
415
- receiveKey.currentState.changeAddress(address: address);
416
- }
417
- });
418
-
419
- _isReactionsSet = true;
420
- }
421
-
422
- void _onCurrencyChange(CryptoCurrency currency, WalletStore walletStore,
423
- GlobalKey<ExchangeCardState> key) {
424
- final isCurrentTypeWallet = currency == walletStore.type;
425
-
426
- key.currentState.changeSelectedCurrency(currency);
427
- key.currentState
428
- .changeWalletName(isCurrentTypeWallet ? walletStore.name : null);
429
-
430
- key.currentState
431
- .changeAddress(address: isCurrentTypeWallet ? walletStore.address : '');
432
-
433
- key.currentState.changeAmount(amount: '');
434
- }
435
-
436
- void _onWalletNameChange(WalletStore walletStore, CryptoCurrency currency,
437
- GlobalKey<ExchangeCardState> key) {
438
- final isCurrentTypeWallet = currency == walletStore.type;
439
-
440
- if (isCurrentTypeWallet) {
441
- key.currentState.changeWalletName(walletStore.name);
442
- key.currentState.addressController.text = walletStore.address;
443
- } else if (key.currentState.addressController.text == walletStore.address) {
444
- key.currentState.changeWalletName(null);
445
- key.currentState.addressController.text = null;
446
- }
447
- }
52
}
\ No newline at end of file
lib/src/screens/exchange/widgets/base_exchange_widget.dart
new
+521
@@ -0,0 +1,521 @@
1
+import 'dart:ui';
2
+import 'package:cake_wallet/src/domain/exchange/exchange_template.dart';
3
+import 'package:cake_wallet/src/widgets/template_tile.dart';
4
+import 'package:dotted_border/dotted_border.dart';
5
+import 'package:flutter/cupertino.dart';
6
+import 'package:flutter/material.dart';
7
+import 'package:flutter_mobx/flutter_mobx.dart';
8
+import 'package:mobx/mobx.dart';
9
+import 'package:cake_wallet/palette.dart';
10
+import 'package:cake_wallet/routes.dart';
11
+import 'package:cake_wallet/generated/i18n.dart';
12
+import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
13
+import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_exchange_provider.dart';
14
+import 'package:cake_wallet/src/stores/exchange/exchange_trade_state.dart';
15
+import 'package:cake_wallet/src/stores/exchange/limits_state.dart';
16
+import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
17
+import 'package:cake_wallet/src/stores/exchange/exchange_store.dart';
18
+import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart';
19
+import 'package:cake_wallet/src/widgets/primary_button.dart';
20
+import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
21
+import 'package:cake_wallet/src/widgets/top_panel.dart';
22
+import 'package:cake_wallet/src/stores/exchange_template/exchange_template_store.dart';
23
+import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
24
+
25
+class BaseExchangeWidget extends StatefulWidget {
26
+ BaseExchangeWidget({
27
+ @ required this.exchangeStore,
28
+ @ required this.walletStore,
29
+ @ required this.exchangeTemplateStore,
30
+ @ required this.isTemplate,
31
+ });
32
+
33
+ final ExchangeStore exchangeStore;
34
+ final WalletStore walletStore;
35
+ final ExchangeTemplateStore exchangeTemplateStore;
36
+ final bool isTemplate;
37
+
38
+ @override
39
+ BaseExchangeWidgetState createState() =>
40
+ BaseExchangeWidgetState(
41
+ exchangeStore: exchangeStore,
42
+ walletStore: walletStore,
43
+ exchangeTemplateStore: exchangeTemplateStore,
44
+ isTemplate: isTemplate
45
+ );
46
+}
47
+
48
+class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
49
+ BaseExchangeWidgetState({
50
+ @ required this.exchangeStore,
51
+ @ required this.walletStore,
52
+ @ required this.exchangeTemplateStore,
53
+ @ required this.isTemplate,
54
+ });
55
+
56
+ final ExchangeStore exchangeStore;
57
+ final WalletStore walletStore;
58
+ final ExchangeTemplateStore exchangeTemplateStore;
59
+ final bool isTemplate;
60
+
61
+ final depositKey = GlobalKey<ExchangeCardState>();
62
+ final receiveKey = GlobalKey<ExchangeCardState>();
63
+ final _formKey = GlobalKey<FormState>();
64
+ var _isReactionsSet = false;
65
+
66
+ final Image arrowBottomPurple = Image.asset(
67
+ 'assets/images/arrow_bottom_purple_icon.png',
68
+ color: Colors.white,
69
+ height: 8,
70
+ );
71
+ final Image arrowBottomCakeGreen = Image.asset(
72
+ 'assets/images/arrow_bottom_cake_green.png',
73
+ color: Colors.white,
74
+ height: 8,
75
+ );
76
+
77
+ @override
78
+ Widget build(BuildContext context) {
79
+
80
+ final depositWalletName =
81
+ exchangeStore.depositCurrency == CryptoCurrency.xmr
82
+ ? walletStore.name
83
+ : null;
84
+ final receiveWalletName =
85
+ exchangeStore.receiveCurrency == CryptoCurrency.xmr
86
+ ? walletStore.name
87
+ : null;
88
+
89
+ WidgetsBinding.instance.addPostFrameCallback(
90
+ (_) => _setReactions(context, exchangeStore, walletStore));
91
+
92
+ return Container(
93
+ color: PaletteDark.historyPanel,
94
+ child: Form(
95
+ key: _formKey,
96
+ child: ScrollableWithBottomSection(
97
+ contentPadding: EdgeInsets.only(bottom: 24),
98
+ content: Column(
99
+ children: <Widget>[
100
+ TopPanel(
101
+ color: PaletteDark.menuList,
102
+ edgeInsets: EdgeInsets.only(bottom: 24),
103
+ widget: Column(
104
+ children: <Widget>[
105
+ TopPanel(
106
+ color: PaletteDark.walletCardSubAddressField,
107
+ widget: Observer(
108
+ builder: (_) => ExchangeCard(
109
+ key: depositKey,
110
+ title: S.of(context).you_will_send,
111
+ initialCurrency: exchangeStore.depositCurrency,
112
+ initialWalletName: depositWalletName,
113
+ initialAddress:
114
+ exchangeStore.depositCurrency == walletStore.type
115
+ ? walletStore.address
116
+ : exchangeStore.depositAddress,
117
+ initialIsAmountEditable: true,
118
+ initialIsAddressEditable: exchangeStore.isDepositAddressEnabled,
119
+ isAmountEstimated: false,
120
+ currencies: CryptoCurrency.all,
121
+ onCurrencySelected: (currency) =>
122
+ exchangeStore.changeDepositCurrency(currency: currency),
123
+ imageArrow: arrowBottomPurple,
124
+ currencyButtonColor: PaletteDark.walletCardSubAddressField,
125
+ addressButtonsColor: PaletteDark.menuList,
126
+ currencyValueValidator: (value) {
127
+ exchangeStore.validateCryptoCurrency(value);
128
+ return exchangeStore.errorMessage;
129
+ },
130
+ addressTextFieldValidator: (value) {
131
+ exchangeStore.validateAddress(value,
132
+ cryptoCurrency: exchangeStore.depositCurrency);
133
+ return exchangeStore.errorMessage;
134
+ },
135
+ ),
136
+ )
137
+ ),
138
+ Padding(
139
+ padding: EdgeInsets.only(top: 32, left: 24, right: 24),
140
+ child: Observer(
141
+ builder: (_) => ExchangeCard(
142
+ key: receiveKey,
143
+ title: S.of(context).you_will_get,
144
+ initialCurrency: exchangeStore.receiveCurrency,
145
+ initialWalletName: receiveWalletName,
146
+ initialAddress:
147
+ exchangeStore.receiveCurrency == walletStore.type
148
+ ? walletStore.address
149
+ : exchangeStore.receiveAddress,
150
+ initialIsAmountEditable: false,
151
+ initialIsAddressEditable: exchangeStore.isReceiveAddressEnabled,
152
+ isAmountEstimated: true,
153
+ currencies: CryptoCurrency.all,
154
+ onCurrencySelected: (currency) => exchangeStore
155
+ .changeReceiveCurrency(currency: currency),
156
+ imageArrow: arrowBottomCakeGreen,
157
+ currencyButtonColor: PaletteDark.menuList,
158
+ currencyValueValidator: (value) {
159
+ exchangeStore.validateCryptoCurrency(value);
160
+ return exchangeStore.errorMessage;
161
+ },
162
+ addressTextFieldValidator: (value) {
163
+ exchangeStore.validateAddress(value,
164
+ cryptoCurrency: exchangeStore.receiveCurrency);
165
+ return exchangeStore.errorMessage;
166
+ },
167
+ )),
168
+ )
169
+ ],
170
+ )
171
+ ),
172
+ isTemplate
173
+ ? Offstage()
174
+ : Padding(
175
+ padding: EdgeInsets.only(
176
+ top: 32,
177
+ left: 24,
178
+ bottom: 24
179
+ ),
180
+ child: Row(
181
+ mainAxisAlignment: MainAxisAlignment.start,
182
+ children: <Widget>[
183
+ Text(
184
+ S.of(context).send_templates,
185
+ style: TextStyle(
186
+ fontSize: 18,
187
+ fontWeight: FontWeight.w600,
188
+ color: PaletteDark.walletCardText
189
+ ),
190
+ )
191
+ ],
192
+ ),
193
+ ),
194
+ isTemplate
195
+ ? Offstage()
196
+ : Container(
197
+ height: 40,
198
+ width: double.infinity,
199
+ padding: EdgeInsets.only(left: 24),
200
+ child: Observer(
201
+ builder: (_) {
202
+ final itemCount = exchangeTemplateStore.templates.length + 1;
203
+
204
+ return ListView.builder(
205
+ scrollDirection: Axis.horizontal,
206
+ itemCount: itemCount,
207
+ itemBuilder: (context, index) {
208
+
209
+ if (index == 0) {
210
+ return GestureDetector(
211
+ onTap: () => Navigator.of(context)
212
+ .pushNamed(Routes.exchangeTemplate),
213
+ child: Container(
214
+ padding: EdgeInsets.only(right: 10),
215
+ child: DottedBorder(
216
+ borderType: BorderType.RRect,
217
+ dashPattern: [8, 4],
218
+ color: PaletteDark.menuList,
219
+ strokeWidth: 2,
220
+ radius: Radius.circular(20),
221
+ child: Container(
222
+ height: 40,
223
+ width: 75,
224
+ padding: EdgeInsets.only(left: 10, right: 10),
225
+ alignment: Alignment.center,
226
+ decoration: BoxDecoration(
227
+ borderRadius: BorderRadius.all(Radius.circular(20)),
228
+ color: Colors.transparent,
229
+ ),
230
+ child: Text(
231
+ S.of(context).send_new,
232
+ style: TextStyle(
233
+ fontSize: 14,
234
+ fontWeight: FontWeight.w600,
235
+ color: PaletteDark.walletCardText
236
+ ),
237
+ ),
238
+ )
239
+ ),
240
+ ),
241
+ );
242
+ }
243
+
244
+ index -= 1;
245
+
246
+ final template = exchangeTemplateStore.templates[index];
247
+
248
+ return TemplateTile(
249
+ amount: template.amount,
250
+ from: template.depositCurrency,
251
+ to: template.receiveCurrency,
252
+ onTap: () {
253
+ applyTemplate(exchangeStore, template);
254
+ }
255
+ );
256
+ }
257
+ );
258
+ }
259
+ ),
260
+ )
261
+ ],
262
+ ),
263
+ bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
264
+ bottomSection: Column(children: <Widget>[
265
+ Padding(
266
+ padding: EdgeInsets.only(bottom: 15),
267
+ child: Observer(builder: (_) {
268
+ final description =
269
+ exchangeStore.provider is XMRTOExchangeProvider
270
+ ? S.of(context).amount_is_guaranteed
271
+ : S.of(context).amount_is_estimate;
272
+ return Center(
273
+ child: Text(
274
+ description,
275
+ style: TextStyle(
276
+ color: PaletteDark.walletCardText,
277
+ fontSize: 12
278
+ ),
279
+ ),
280
+ );
281
+ }),
282
+ ),
283
+ isTemplate
284
+ ? PrimaryButton(
285
+ onPressed: () {
286
+ if (_formKey.currentState.validate()) {
287
+ exchangeTemplateStore.addTemplate(
288
+ amount: exchangeStore.depositAmount,
289
+ depositCurrency: exchangeStore.depositCurrency.toString(),
290
+ receiveCurrency: exchangeStore.receiveCurrency.toString(),
291
+ provider: exchangeStore.provider.toString(),
292
+ depositAddress: exchangeStore.depositAddress,
293
+ receiveAddress: exchangeStore.receiveAddress
294
+ );
295
+ exchangeTemplateStore.update();
296
+ Navigator.of(context).pop();
297
+ }
298
+ },
299
+ text: S.of(context).save,
300
+ color: Colors.green,
301
+ textColor: Colors.white
302
+ )
303
+ : Observer(
304
+ builder: (_) => LoadingPrimaryButton(
305
+ text: S.of(context).exchange,
306
+ onPressed: () {
307
+ if (_formKey.currentState.validate()) {
308
+ exchangeStore.createTrade();
309
+ }
310
+ },
311
+ color: Colors.blue,
312
+ textColor: Colors.white,
313
+ isLoading: exchangeStore.tradeState is TradeIsCreating,
314
+ )),
315
+ ]),
316
+ )),
317
+ );
318
+ }
319
+
320
+ void applyTemplate(ExchangeStore store, ExchangeTemplate template) {
321
+ store.changeDepositCurrency(currency: CryptoCurrency.fromString(template.depositCurrency));
322
+ store.changeReceiveCurrency(currency: CryptoCurrency.fromString(template.receiveCurrency));
323
+
324
+ switch (template.provider) {
325
+ case 'XMR.TO':
326
+ store.changeProvider(provider: store.providerList[0]);
327
+ break;
328
+ case 'ChangeNOW':
329
+ store.changeProvider(provider: store.providerList[1]);
330
+ break;
331
+ case 'MorphToken':
332
+ store.changeProvider(provider: store.providerList[2]);
333
+ break;
334
+ }
335
+
336
+ store.changeDepositAmount(amount: template.amount);
337
+ store.depositAddress = template.depositAddress;
338
+ store.receiveAddress = template.receiveAddress;
339
+ }
340
+
341
+ void _setReactions(
342
+ BuildContext context, ExchangeStore store, WalletStore walletStore) {
343
+ if (_isReactionsSet) {
344
+ return;
345
+ }
346
+
347
+ final depositAddressController = depositKey.currentState.addressController;
348
+ final depositAmountController = depositKey.currentState.amountController;
349
+ final receiveAddressController = receiveKey.currentState.addressController;
350
+ final receiveAmountController = receiveKey.currentState.amountController;
351
+ final limitsState = store.limitsState;
352
+
353
+ if (limitsState is LimitsLoadedSuccessfully) {
354
+ final min = limitsState.limits.min != null
355
+ ? limitsState.limits.min.toString()
356
+ : null;
357
+ final max = limitsState.limits.max != null
358
+ ? limitsState.limits.max.toString()
359
+ : null;
360
+ final key = depositKey;
361
+ key.currentState.changeLimits(min: min, max: max);
362
+ }
363
+
364
+ _onCurrencyChange(store.receiveCurrency, walletStore, receiveKey);
365
+ _onCurrencyChange(store.depositCurrency, walletStore, depositKey);
366
+
367
+ reaction(
368
+ (_) => walletStore.name,
369
+ (String _) => _onWalletNameChange(
370
+ walletStore, store.receiveCurrency, receiveKey));
371
+
372
+ reaction(
373
+ (_) => walletStore.name,
374
+ (String _) => _onWalletNameChange(
375
+ walletStore, store.depositCurrency, depositKey));
376
+
377
+ reaction(
378
+ (_) => store.receiveCurrency,
379
+ (CryptoCurrency currency) =>
380
+ _onCurrencyChange(currency, walletStore, receiveKey));
381
+
382
+ reaction(
383
+ (_) => store.depositCurrency,
384
+ (CryptoCurrency currency) =>
385
+ _onCurrencyChange(currency, walletStore, depositKey));
386
+
387
+ reaction((_) => store.depositAmount, (String amount) {
388
+ if (depositKey.currentState.amountController.text != amount) {
389
+ depositKey.currentState.amountController.text = amount;
390
+ }
391
+ });
392
+
393
+ reaction((_) => store.depositAddress, (String address) {
394
+ if (depositKey.currentState.addressController.text != address) {
395
+ depositKey.currentState.addressController.text = address;
396
+ }
397
+ });
398
+
399
+ reaction((_) => store.isDepositAddressEnabled, (bool isEnabled) {
400
+ depositKey.currentState.isAddressEditable(isEditable: isEnabled);
401
+ });
402
+
403
+ reaction((_) => store.receiveAmount, (String amount) {
404
+ if (receiveKey.currentState.amountController.text !=
405
+ store.receiveAmount) {
406
+ receiveKey.currentState.amountController.text = amount;
407
+ }
408
+ });
409
+
410
+ reaction((_) => store.receiveAddress, (String address) {
411
+ if (receiveKey.currentState.addressController.text != address) {
412
+ receiveKey.currentState.addressController.text = address;
413
+ }
414
+ });
415
+
416
+ reaction((_) => store.isReceiveAddressEnabled, (bool isEnabled) {
417
+ receiveKey.currentState.isAddressEditable(isEditable: isEnabled);
418
+ });
419
+
420
+ reaction((_) => store.tradeState, (ExchangeTradeState state) {
421
+ if (state is TradeIsCreatedFailure) {
422
+ WidgetsBinding.instance.addPostFrameCallback((_) {
423
+ showDialog<void>(
424
+ context: context,
425
+ builder: (BuildContext context) {
426
+ return AlertWithOneAction(
427
+ alertTitle: S.of(context).error,
428
+ alertContent: state.error,
429
+ buttonText: S.of(context).ok,
430
+ buttonAction: () => Navigator.of(context).pop()
431
+ );
432
+ });
433
+ });
434
+ }
435
+ if (state is TradeIsCreatedSuccessfully) {
436
+ Navigator.of(context)
437
+ .pushNamed(Routes.exchangeConfirm, arguments: state.trade);
438
+ }
439
+ });
440
+
441
+ reaction((_) => store.limitsState, (LimitsState state) {
442
+ String min;
443
+ String max;
444
+
445
+ if (state is LimitsLoadedSuccessfully) {
446
+ min = state.limits.min != null ? state.limits.min.toString() : null;
447
+ max = state.limits.max != null ? state.limits.max.toString() : null;
448
+ }
449
+
450
+ if (state is LimitsLoadedFailure) {
451
+ min = '0';
452
+ max = '0';
453
+ }
454
+
455
+ if (state is LimitsIsLoading) {
456
+ min = '...';
457
+ max = '...';
458
+ }
459
+
460
+ depositKey.currentState.changeLimits(min: min, max: max);
461
+ receiveKey.currentState.changeLimits(min: null, max: null);
462
+ });
463
+
464
+ depositAddressController.addListener(
465
+ () => store.depositAddress = depositAddressController.text);
466
+
467
+ depositAmountController.addListener(() {
468
+ if (depositAmountController.text != store.depositAmount) {
469
+ store.changeDepositAmount(amount: depositAmountController.text);
470
+ }
471
+ });
472
+
473
+ receiveAddressController.addListener(
474
+ () => store.receiveAddress = receiveAddressController.text);
475
+
476
+ receiveAmountController.addListener(() {
477
+ if (receiveAmountController.text != store.receiveAmount) {
478
+ store.changeReceiveAmount(amount: receiveAmountController.text);
479
+ }
480
+ });
481
+
482
+ reaction((_) => walletStore.address, (String address) {
483
+ if (store.depositCurrency == CryptoCurrency.xmr) {
484
+ depositKey.currentState.changeAddress(address: address);
485
+ }
486
+
487
+ if (store.receiveCurrency == CryptoCurrency.xmr) {
488
+ receiveKey.currentState.changeAddress(address: address);
489
+ }
490
+ });
491
+
492
+ _isReactionsSet = true;
493
+ }
494
+
495
+ void _onCurrencyChange(CryptoCurrency currency, WalletStore walletStore,
496
+ GlobalKey<ExchangeCardState> key) {
497
+ final isCurrentTypeWallet = currency == walletStore.type;
498
+
499
+ key.currentState.changeSelectedCurrency(currency);
500
+ key.currentState
501
+ .changeWalletName(isCurrentTypeWallet ? walletStore.name : null);
502
+
503
+ key.currentState
504
+ .changeAddress(address: isCurrentTypeWallet ? walletStore.address : '');
505
+
506
+ key.currentState.changeAmount(amount: '');
507
+ }
508
+
509
+ void _onWalletNameChange(WalletStore walletStore, CryptoCurrency currency,
510
+ GlobalKey<ExchangeCardState> key) {
511
+ final isCurrentTypeWallet = currency == walletStore.type;
512
+
513
+ if (isCurrentTypeWallet) {
514
+ key.currentState.changeWalletName(walletStore.name);
515
+ key.currentState.addressController.text = walletStore.address;
516
+ } else if (key.currentState.addressController.text == walletStore.address) {
517
+ key.currentState.changeWalletName(null);
518
+ key.currentState.addressController.text = null;
519
+ }
520
+ }
521
+}
\ No newline at end of file
lib/src/screens/exchange/widgets/present_provider_picker.dart
new
+84
@@ -0,0 +1,84 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:cake_wallet/src/stores/exchange/exchange_store.dart';
3
+import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart';
4
+import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart';
5
+import 'package:cake_wallet/src/screens/exchange/widgets/provider_picker.dart';
6
+import 'package:flutter_mobx/flutter_mobx.dart';
7
+import 'package:cake_wallet/generated/i18n.dart';
8
+import 'package:cake_wallet/palette.dart';
9
+
10
+class PresentProviderPicker extends StatelessWidget {
11
+ PresentProviderPicker({@required this.exchangeStore});
12
+
13
+ final ExchangeStore exchangeStore;
14
+
15
+ final Image arrowBottom =
16
+ Image.asset('assets/images/arrow_bottom_purple_icon.png', color: Colors.white, height: 6);
17
+
18
+ @override
19
+ Widget build(BuildContext context) {
20
+ return FlatButton(
21
+ onPressed: () => _presentProviderPicker(context),
22
+ highlightColor: Colors.transparent,
23
+ splashColor: Colors.transparent,
24
+ child: Row(
25
+ mainAxisSize: MainAxisSize.min,
26
+ crossAxisAlignment: CrossAxisAlignment.start,
27
+ children: <Widget>[
28
+ Column(
29
+ crossAxisAlignment: CrossAxisAlignment.center,
30
+ mainAxisSize: MainAxisSize.min,
31
+ children: <Widget>[
32
+ Text(S.of(context).exchange,
33
+ style: TextStyle(
34
+ fontSize: 16.0,
35
+ fontWeight: FontWeight.w400,
36
+ color: Colors.white)),
37
+ Observer(
38
+ builder: (_) => Text('${exchangeStore.provider.title}',
39
+ style: TextStyle(
40
+ fontSize: 10.0,
41
+ fontWeight: FontWeight.w400,
42
+ color:PaletteDark.walletCardText)))
43
+ ],
44
+ ),
45
+ SizedBox(width: 5),
46
+ Padding(
47
+ padding: EdgeInsets.only(top: 8),
48
+ child: arrowBottom,
49
+ )
50
+ ],
51
+ )
52
+ );
53
+ }
54
+
55
+ void _presentProviderPicker(BuildContext context) {
56
+ final items = exchangeStore.providersForCurrentPair();
57
+ final selectedItem = items.indexOf(exchangeStore.provider);
58
+ final images = List<Image>();
59
+
60
+ for (ExchangeProvider provider in items) {
61
+ switch (provider.description) {
62
+ case ExchangeProviderDescription.xmrto:
63
+ images.add(Image.asset('assets/images/xmr_btc.png'));
64
+ break;
65
+ case ExchangeProviderDescription.changeNow:
66
+ images.add(Image.asset('assets/images/change_now.png'));
67
+ break;
68
+ case ExchangeProviderDescription.morphToken:
69
+ images.add(Image.asset('assets/images/morph_icon.png'));
70
+ break;
71
+ }
72
+ }
73
+
74
+ showDialog<void>(
75
+ builder: (_) => ProviderPicker(
76
+ items: items,
77
+ images: images,
78
+ selectedAtIndex: selectedItem,
79
+ title: S.of(context).change_exchange_provider,
80
+ onItemSelected: (ExchangeProvider provider) =>
81
+ exchangeStore.changeProvider(provider: provider)),
82
+ context: context);
83
+ }
84
+}
\ No newline at end of file
lib/src/stores/exchange/exchange_store.dart
+17
-4
@@ -33,6 +33,8 @@ abstract class ExchangeStoreBase with Store {
33
provider = initialProvider;
34
depositCurrency = initialDepositCurrency;
35
receiveCurrency = initialReceiveCurrency;
36
+ isDepositAddressEnabled = !(depositCurrency == walletStore.type);
37
+ isReceiveAddressEnabled = !(receiveCurrency == walletStore.type);
38
depositAmount = '';
39
receiveAmount = '';
40
depositAddress = '';
@@ -73,6 +75,12 @@ abstract class ExchangeStoreBase with Store {
75
@observable
76
String receiveAddress;
77
78
+ @observable
79
+ bool isDepositAddressEnabled;
80
+
81
+ @observable
82
+ bool isReceiveAddressEnabled;
83
+
84
@observable
85
bool isValid;
86
@@ -99,12 +107,16 @@ abstract class ExchangeStoreBase with Store {
107
void changeDepositCurrency({CryptoCurrency currency}) {
108
depositCurrency = currency;
109
_onPairChange();
110
+ isDepositAddressEnabled = !(depositCurrency == walletStore.type);
111
+ isReceiveAddressEnabled = !(receiveCurrency == walletStore.type);
112
}
113
114
@action
115
void changeReceiveCurrency({CryptoCurrency currency}) {
116
receiveCurrency = currency;
117
_onPairChange();
118
+ isDepositAddressEnabled = !(depositCurrency == walletStore.type);
119
+ isReceiveAddressEnabled = !(receiveCurrency == walletStore.type);
120
}
121
122
@action
@@ -224,12 +236,13 @@ abstract class ExchangeStoreBase with Store {
236
void reset() {
237
depositAmount = '';
238
receiveAmount = '';
227
- depositAddress = '';
228
- receiveAddress = '';
229
- provider = XMRTOExchangeProvider();
239
depositCurrency = CryptoCurrency.xmr;
240
receiveCurrency = CryptoCurrency.btc;
232
- loadLimits();
241
+ depositAddress = depositCurrency == walletStore.type ? walletStore.address : '';
242
+ receiveAddress = receiveCurrency == walletStore.type ? walletStore.address : '';
243
+ isDepositAddressEnabled = !(depositCurrency == walletStore.type);
244
+ isReceiveAddressEnabled = !(receiveCurrency == walletStore.type);
245
+ _onPairChange();
246
}
247
248
List<ExchangeProvider> providersForCurrentPair() {
lib/src/widgets/address_text_field.dart
+5
@@ -124,6 +124,11 @@ class AddressTextField extends StatelessWidget {
124
color: PaletteDark.walletCardSubAddressField,
125
width: 1.0))
126
: InputBorder.none,
127
+ disabledBorder: isBorderExist
128
+ ? UnderlineInputBorder(
129
+ borderSide:
130
+ BorderSide(color: PaletteDark.walletCardSubAddressField, width: 1.0))
131
+ : InputBorder.none,
132
enabledBorder: isBorderExist
133
? UnderlineInputBorder(
134
borderSide:
res/values/strings_de.arb
+1
@@ -96,6 +96,7 @@
96
"expired" : "Abgelaufen",
97
"time" : "${minutes}m ${seconds}s",
98
"send_xmr" : "Senden XMR",
99
+ "exchange_new_template" : "Neue Vorlage",
100
101
"faq" : "FAQ",
102
res/values/strings_en.arb
+1
@@ -96,6 +96,7 @@
96
"expired" : "Expired",
97
"time" : "${minutes}m ${seconds}s",
98
"send_xmr" : "Send XMR",
99
+ "exchange_new_template" : "New template",
100
101
"faq" : "FAQ",
102
res/values/strings_es.arb
+1
@@ -96,6 +96,7 @@
96
"expired" : "Muerto",
97
"time" : "${minutes}m ${seconds}s",
98
"send_xmr" : "Enviar XMR",
99
+ "exchange_new_template" : "Nueva plantilla",
100
101
"faq" : "FAQ",
102
res/values/strings_hi.arb
+1
@@ -96,6 +96,7 @@
96
"expired" : "समय सीमा समाप्त",
97
"time" : "${minutes}m ${seconds}s",
98
"send_xmr" : "संदेश XMR",
99
+ "exchange_new_template" : "नया टेम्पलेट",
100
101
"faq" : "FAQ",
102
res/values/strings_ja.arb
+1
@@ -96,6 +96,7 @@
96
"expired" : "期限切れ",
97
"time" : "${minutes}m ${seconds}s",
98
"send_xmr" : "送る XMR",
99
+ "exchange_new_template" : "新しいテンプレート",
100
101
"faq" : "FAQ",
102
res/values/strings_ko.arb
+1
@@ -96,6 +96,7 @@
96
"expired" : "만료",
97
"time" : "${minutes}m ${seconds}s",
98
"send_xmr" : "보내다 XMR",
99
+ "exchange_new_template" : "새 템플릿",
100
101
"faq" : "FAQ",
102
res/values/strings_nl.arb
+1
@@ -96,6 +96,7 @@
96
"expired" : "Verlopen",
97
"time" : "${minutes}m ${seconds}s",
98
"send_xmr" : "Sturen XMR",
99
+ "exchange_new_template" : "Nieuwe sjabloon",
100
101
"faq" : "FAQ",
102
res/values/strings_pl.arb
+1
@@ -96,6 +96,7 @@
96
"expired" : "Przedawniony",
97
"time" : "${minutes}m ${seconds}s",
98
"send_xmr" : "Wysłać XMR",
99
+ "exchange_new_template" : "Nowy szablon",
100
101
"faq" : "FAQ",
102
res/values/strings_pt.arb
+1
@@ -96,6 +96,7 @@
96
"expired" : "Expirada",
97
"time" : "${minutes}m ${seconds}s",
98
"send_xmr" : "Enviar XMR",
99
+ "exchange_new_template" : "Novo modelo",
100
101
"faq" : "FAQ",
102
res/values/strings_ru.arb
+1
@@ -96,6 +96,7 @@
96
"expired" : "Истекает",
97
"time" : "${minutes}мин ${seconds}сек",
98
"send_xmr" : "Отправить XMR",
99
+ "exchange_new_template" : "Новый шаблон",
100
101
"faq" : "FAQ",
102
res/values/strings_uk.arb
+1
@@ -96,6 +96,7 @@
96
"expired" : "Закінчується",
97
"time" : "${minutes}хв ${seconds}сек",
98
"send_xmr" : "Відправити XMR",
99
+ "exchange_new_template" : "Новий шаблон",
100
101
"faq" : "FAQ",
102
res/values/strings_zh.arb
+1
@@ -96,6 +96,7 @@
96
"expired" : "已过期",
97
"time" : "${minutes}m ${seconds}s",
98
"send_xmr" : "发送 XMR",
99
+ "exchange_new_template" : "新範本",
100
101
"faq" : "FAQ",
102