CAKE-20 | updated exchange and exchange template pages; created exchange_view_model and applied to exchange and exchange template pages; applied new design to menu widget; changed text validator and amount validator

Oleksandr Sobol committed Jul 31, 2020 at 18:29 UTC 33209c3cd621cfbc4e7b28b00b193f3019f53d76
56 files changed +857 -525
assets/images/2.0x/eye_menu.png
Binary files /dev/null and b/assets/images/2.0x/eye_menu.png differ
assets/images/2.0x/key_menu.png
Binary files /dev/null and b/assets/images/2.0x/key_menu.png differ
assets/images/2.0x/monero_menu.png
Binary files /dev/null and b/assets/images/2.0x/monero_menu.png differ
assets/images/2.0x/nodes_menu.png
Binary files /dev/null and b/assets/images/2.0x/nodes_menu.png differ
assets/images/2.0x/open_book_menu.png
Binary files /dev/null and b/assets/images/2.0x/open_book_menu.png differ
assets/images/2.0x/reconnect_menu.png
Binary files /dev/null and b/assets/images/2.0x/reconnect_menu.png differ
assets/images/2.0x/settings_menu.png
Binary files /dev/null and b/assets/images/2.0x/settings_menu.png differ
assets/images/2.0x/wallet_menu.png
Binary files /dev/null and b/assets/images/2.0x/wallet_menu.png differ
assets/images/3.0x/eye_menu.png
Binary files /dev/null and b/assets/images/3.0x/eye_menu.png differ
assets/images/3.0x/key_menu.png
Binary files /dev/null and b/assets/images/3.0x/key_menu.png differ
assets/images/3.0x/monero_menu.png
Binary files /dev/null and b/assets/images/3.0x/monero_menu.png differ
assets/images/3.0x/nodes_menu.png
Binary files /dev/null and b/assets/images/3.0x/nodes_menu.png differ
assets/images/3.0x/open_book_menu.png
Binary files /dev/null and b/assets/images/3.0x/open_book_menu.png differ
assets/images/3.0x/reconnect_menu.png
Binary files /dev/null and b/assets/images/3.0x/reconnect_menu.png differ
assets/images/3.0x/settings_menu.png
Binary files /dev/null and b/assets/images/3.0x/settings_menu.png differ
assets/images/3.0x/wallet_menu.png
Binary files /dev/null and b/assets/images/3.0x/wallet_menu.png differ
assets/images/eye_menu.png
Binary files /dev/null and b/assets/images/eye_menu.png differ
assets/images/key_menu.png
Binary files /dev/null and b/assets/images/key_menu.png differ
assets/images/monero_menu.png
Binary files /dev/null and b/assets/images/monero_menu.png differ
assets/images/nodes_menu.png
Binary files /dev/null and b/assets/images/nodes_menu.png differ
assets/images/open_book_menu.png
Binary files /dev/null and b/assets/images/open_book_menu.png differ
assets/images/reconnect_menu.png
Binary files /dev/null and b/assets/images/reconnect_menu.png differ
assets/images/settings_menu.png
Binary files /dev/null and b/assets/images/settings_menu.png differ
assets/images/wallet_menu.png
Binary files /dev/null and b/assets/images/wallet_menu.png differ
lib/core/amount_validator.dart
+2 -1
@@ -3,10 +3,11 @@ import 'package:cake_wallet/generated/i18n.dart';
3 import 'package:cake_wallet/src/domain/common/wallet_type.dart';
4
5 class AmountValidator extends TextValidator {
6 - AmountValidator({WalletType type})
6 + AmountValidator({WalletType type, bool isAutovalidate = false})
7 : super(
8 errorMessage: S.current.error_text_amount,
9 pattern: _pattern(type),
10 + isAutovalidate: isAutovalidate,
11 minLength: 0,
12 maxLength: 0);
13
lib/core/validator.dart
+3 -1
@@ -16,18 +16,20 @@ class TextValidator extends Validator<String> {
16 this.maxLength,
17 this.pattern,
18 this.length,
19 + this.isAutovalidate = false,
20 String errorMessage})
21 : super(errorMessage: errorMessage);
22
23 final int minLength;
24 final int maxLength;
25 final List<int> length;
26 + final bool isAutovalidate;
27 String pattern;
28
29 @override
30 bool isValid(String value) {
31 if (value == null || value.isEmpty) {
30 - return false;
32 + return isAutovalidate ? true : false;
33 }
34
35 return value.length > (minLength ?? 0) &&
lib/di.dart
+16
@@ -10,6 +10,8 @@ import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
10 import 'package:cake_wallet/src/screens/send/send_template_page.dart';
11 import 'package:cake_wallet/src/screens/settings/settings.dart';
12 import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
13 +import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
14 +import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
15 import 'package:cake_wallet/store/contact_list_store.dart';
16 import 'package:cake_wallet/store/node_list_store.dart';
17 import 'package:cake_wallet/store/settings_store.dart';
@@ -42,6 +44,7 @@ import 'package:cake_wallet/view_model/settings/settings_view_model.dart';
44 import 'package:cake_wallet/view_model/wallet_keys_view_model.dart';
45 import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
46 import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
47 +import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
48 import 'package:flutter/foundation.dart';
49 import 'package:get_it/get_it.dart';
50 import 'package:hive/hive.dart';
@@ -299,4 +302,17 @@ Future setup(
302
303 getIt.registerFactory(
304 () => NodeCreateOrEditPage(getIt.get<NodeCreateOrEditViewModel>()));
305 +
306 + getIt.registerFactory(() =>
307 + ExchangeViewModel(
308 + wallet: getIt.get<AppStore>().wallet,
309 + exchangeTemplateStore: getIt.get<ExchangeTemplateStore>(),
310 + trades: tradesSource
311 + ));
312 +
313 + getIt.registerFactory(() =>
314 + ExchangePage(getIt.get<ExchangeViewModel>()));
315 +
316 + getIt.registerFactory(() =>
317 + ExchangeTemplatePage(getIt.get<ExchangeViewModel>()));
318 }
lib/generated/i18n.dart
+23
@@ -135,6 +135,7 @@ class S implements WidgetsLocalizations {
135 String get reconnect => "Reconnect";
136 String get reconnect_alert_text => "Are you sure to reconnect?";
137 String get reconnection => "Reconnection";
138 + String get refund_address => "Refund address";
139 String get remove => "Remove";
140 String get remove_node => "Remove node";
141 String get remove_node_message => "Are you sure that you want to remove selected node?";
@@ -372,6 +373,8 @@ class $de extends S {
373 @override
374 String get trade_state_underpaid => "Unterbezahlt";
375 @override
376 + String get refund_address => "Rückerstattungsadresse";
377 + @override
378 String get welcome => "Willkommen zu";
379 @override
380 String get share_address => "Adresse teilen ";
@@ -998,6 +1001,8 @@ class $hi extends S {
1001 @override
1002 String get trade_state_underpaid => "के तहत भुगतान किया";
1003 @override
1004 + String get refund_address => "वापसी का पता";
1005 + @override
1006 String get welcome => "स्वागत हे सेवा मेरे";
1007 @override
1008 String get share_address => "पता साझा करें";
@@ -1624,6 +1629,8 @@ class $ru extends S {
1629 @override
1630 String get trade_state_underpaid => "Недоплаченная";
1631 @override
1632 + String get refund_address => "Адрес возврата";
1633 + @override
1634 String get welcome => "Приветствуем в";
1635 @override
1636 String get share_address => "Поделиться адресом";
@@ -2250,6 +2257,8 @@ class $ko extends S {
2257 @override
2258 String get trade_state_underpaid => "미지급";
2259 @override
2260 + String get refund_address => "환불 주소";
2261 + @override
2262 String get welcome => "환영 에";
2263 @override
2264 String get share_address => "주소 공유";
@@ -2876,6 +2885,8 @@ class $pt extends S {
2885 @override
2886 String get trade_state_underpaid => "Parcialmente paga";
2887 @override
2888 + String get refund_address => "Endereço de reembolso";
2889 + @override
2890 String get welcome => "Bem-vindo ao";
2891 @override
2892 String get share_address => "Compartilhar endereço";
@@ -3502,6 +3513,8 @@ class $uk extends S {
3513 @override
3514 String get trade_state_underpaid => "Недоплачена";
3515 @override
3516 + String get refund_address => "Адреса повернення коштів";
3517 + @override
3518 String get welcome => "Вітаємо в";
3519 @override
3520 String get share_address => "Поділитися адресою";
@@ -4128,6 +4141,8 @@ class $ja extends S {
4141 @override
4142 String get trade_state_underpaid => "支払不足";
4143 @override
4144 + String get refund_address => "払い戻し住所";
4145 + @override
4146 String get welcome => "ようこそ に";
4147 @override
4148 String get share_address => "住所を共有する";
@@ -4758,6 +4773,8 @@ class $pl extends S {
4773 @override
4774 String get trade_state_underpaid => "Niedopłacone";
4775 @override
4776 + String get refund_address => "Adres zwrotu";
4777 + @override
4778 String get welcome => "Witamy w";
4779 @override
4780 String get share_address => "Udostępnij adres";
@@ -5384,6 +5401,8 @@ class $es extends S {
5401 @override
5402 String get trade_state_underpaid => "Poco pagado";
5403 @override
5404 + String get refund_address => "Dirección de reembolso";
5405 + @override
5406 String get welcome => "Bienvenido";
5407 @override
5408 String get share_address => "Compartir dirección";
@@ -6010,6 +6029,8 @@ class $nl extends S {
6029 @override
6030 String get trade_state_underpaid => "Slecht betaald";
6031 @override
6032 + String get refund_address => "Adres voor terugbetaling";
6033 + @override
6034 String get welcome => "Welkom bij";
6035 @override
6036 String get share_address => "Deel adres";
@@ -6636,6 +6657,8 @@ class $zh extends S {
6657 @override
6658 String get trade_state_underpaid => "支付不足";
6659 @override
6660 + String get refund_address => "退款地址";
6661 + @override
6662 String get welcome => "歡迎來到";
6663 @override
6664 String get share_address => "分享地址";
lib/palette.dart
+8 -2
@@ -23,8 +23,8 @@ class PaletteDark {
23 static const Color lightDistantBlue = Color.fromRGBO(81, 96, 147, 1.0); // borderCardColor
24 static const Color gray = Color.fromRGBO(140, 153, 201, 1.0); // walletCardText
25 static const Color violetBlue = Color.fromRGBO(51, 63, 104, 1.0); // walletCardAddressField
26 - static const Color moderateBlue = Color.fromRGBO(63, 77, 122, 1.0); // walletCardSubAddressField
27 - static const Color darkNightBlue = Color.fromRGBO(33, 43, 73, 1.0); // historyPanel
26 + //static const Color moderateBlue = Color.fromRGBO(63, 77, 122, 1.0); // walletCardSubAddressField
27 + //static const Color darkNightBlue = Color.fromRGBO(33, 43, 73, 1.0); // historyPanel
28 static const Color pigeonBlue = Color.fromRGBO(91, 112, 146, 1.0); // historyPanelText
29 static const Color moderateNightBlue = Color.fromRGBO(39, 53, 96, 1.0); // historyPanelButton
30 static const Color headerNightBlue = Color.fromRGBO(41, 52, 84, 1.0); // menuHeader
@@ -46,6 +46,12 @@ class PaletteDark {
46 static const Color lightBlueGrey = Color.fromRGBO(125, 141, 183, 1.0);
47 static const Color lightVioletBlue = Color.fromRGBO(56, 71, 109, 1.0);
48 static const Color darkVioletBlue = Color.fromRGBO(49, 60, 96, 1.0);
49 + static const Color wildVioletBlue = Color.fromRGBO(45, 60, 97, 1.0);
50 + static const Color darkNightBlue = Color.fromRGBO(33, 45, 76, 1.0);
51 + static const Color blueGrey = Color.fromRGBO(87, 98, 138, 1.0);
52 + static const Color moderateBlue = Color.fromRGBO(60, 73, 118, 1.0);
53 + static const Color deepPurpleBlue = Color.fromRGBO(19, 29, 56, 1.0);
54 + static const Color lightOceanBlue = Color.fromRGBO(30, 42, 73, 1.0);
55
56 // FIXME: Rename.
57 static const Color eee = Color.fromRGBO(236, 239, 245, 1.0);
lib/router.dart
+4 -37
@@ -411,45 +411,12 @@ class Router {
411 walletRestorationFromSeedVM: walletRestorationFromSeedVM));
412
413 case Routes.exchange:
414 - return MaterialPageRoute<void>(
415 - builder: (_) => MultiProvider(providers: [
416 - Provider(create: (_) {
417 - final xmrtoprovider = XMRTOExchangeProvider();
418 -
419 - return ExchangeStore(
420 - initialProvider: xmrtoprovider,
421 - initialDepositCurrency: CryptoCurrency.xmr,
422 - initialReceiveCurrency: CryptoCurrency.btc,
423 - trades: trades,
424 - providerList: [
425 - xmrtoprovider,
426 - ChangeNowExchangeProvider(),
427 - MorphTokenExchangeProvider(trades: trades)
428 - ],
429 - walletStore: walletStore);
430 - }),
431 - ], child: ExchangePage()));
414 + return CupertinoPageRoute<void>(
415 + builder: (_) => getIt.get<ExchangePage>());
416
417 case Routes.exchangeTemplate:
434 - return MaterialPageRoute<void>(
435 - builder: (_) => Provider(
436 - create: (_) {
437 - final xmrtoprovider = XMRTOExchangeProvider();
438 -
439 - return ExchangeStore(
440 - initialProvider: xmrtoprovider,
441 - initialDepositCurrency: CryptoCurrency.xmr,
442 - initialReceiveCurrency: CryptoCurrency.btc,
443 - trades: trades,
444 - providerList: [
445 - xmrtoprovider,
446 - ChangeNowExchangeProvider(),
447 - MorphTokenExchangeProvider(trades: trades)
448 - ],
449 - walletStore: walletStore);
450 - },
451 - child: ExchangeTemplatePage(),
452 - ));
418 + return CupertinoPageRoute<void>(
419 + builder: (_) => getIt.get<ExchangeTemplatePage>());
420
421 case Routes.settings:
422 return MaterialPageRoute<void>(
lib/src/screens/base_page.dart
+8
@@ -19,10 +19,14 @@ abstract class BasePage extends StatelessWidget {
19
20 bool get resizeToAvoidBottomPadding => true;
21
22 + Widget get endDrawer => null;
23 +
24 AppBarStyle get appBarStyle => AppBarStyle.regular;
25
26 Widget Function(BuildContext, Widget) get rootWrapper => null;
27
28 + final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
29 +
30 final _backArrowImage = Image.asset('assets/images/back_arrow.png',
31 color: Colors.white);
32 final _backArrowImageDarkTheme =
@@ -32,6 +36,8 @@ abstract class BasePage extends StatelessWidget {
36 final _closeButtonImageDarkTheme =
37 Image.asset('assets/images/close_button_dark_theme.png');
38
39 + void onOpenEndDrawer() => _scaffoldKey.currentState.openEndDrawer();
40 +
41 void onClose(BuildContext context) => Navigator.of(context).pop();
42
43 Widget leading(BuildContext context) {
@@ -123,9 +129,11 @@ abstract class BasePage extends StatelessWidget {
129 final _isDarkTheme = _themeChanger.getTheme() == Themes.darkTheme;
130
131 final root = Scaffold(
132 + key: _scaffoldKey,
133 backgroundColor:
134 _isDarkTheme ? backgroundDarkColor : backgroundLightColor,
135 resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
136 + endDrawer: endDrawer,
137 appBar: appBar(context),
138 body: body(context), //SafeArea(child: ),
139 floatingActionButton: floatingActionButton(context));
lib/src/screens/dashboard/dashboard_page.dart
+7 -8
@@ -23,6 +23,12 @@ class DashboardPage extends BasePage {
23 @override
24 Color get backgroundDarkColor => PaletteDark.backgroundColor;
25
26 + @override
27 + Widget get endDrawer => MenuWidget(
28 + name: walletViewModel.name,
29 + subname: walletViewModel.subname,
30 + type: walletViewModel.type);
31 +
32 @override
33 Widget middle(BuildContext context) {
34 return SyncIndicator(dashboardViewModel: walletViewModel);
@@ -40,14 +46,7 @@ class DashboardPage extends BasePage {
46 highlightColor: Colors.transparent,
47 splashColor: Colors.transparent,
48 padding: EdgeInsets.all(0),
43 - onPressed: () async {
44 - await showDialog<void>(
45 - builder: (_) => MenuWidget(
46 - name: walletViewModel.name,
47 - subname: walletViewModel.subname,
48 - type: walletViewModel.type),
49 - context: context);
50 - },
49 + onPressed: () => onOpenEndDrawer(),
50 child: menuButton
51 )
52 );
lib/src/screens/dashboard/wallet_menu.dart
+7 -7
@@ -20,13 +20,13 @@ class WalletMenu {
20 ];
21
22 final List<Image> images = [
23 - Image.asset('assets/images/reconnect.png'),
24 - Image.asset('assets/images/wallet.png'),
25 - Image.asset('assets/images/nodes.png'),
26 - Image.asset('assets/images/eye.png'),
27 - Image.asset('assets/images/key.png'),
28 - Image.asset('assets/images/open_book.png'),
29 - Image.asset('assets/images/settings.png'),
23 + Image.asset('assets/images/reconnect_menu.png', height: 16, width: 16),
24 + Image.asset('assets/images/wallet_menu.png', height: 16, width: 16),
25 + Image.asset('assets/images/nodes_menu.png', height: 16, width: 16),
26 + Image.asset('assets/images/eye_menu.png', height: 16, width: 16),
27 + Image.asset('assets/images/key_menu.png', height: 16, width: 16),
28 + Image.asset('assets/images/open_book_menu.png', height: 16, width: 16),
29 + Image.asset('assets/images/settings_menu.png', height: 16, width: 16),
30 ];
31
32 final BuildContext context;
lib/src/screens/dashboard/widgets/menu_widget.dart
+123 -172
@@ -1,4 +1,5 @@
1 import 'dart:ui';
2 +import 'package:cake_wallet/palette.dart';
3 import 'package:flutter/material.dart';
4 import 'package:cake_wallet/src/domain/common/wallet_type.dart';
5 import 'package:cake_wallet/src/screens/dashboard/wallet_menu.dart';
@@ -15,7 +16,7 @@ class MenuWidget extends StatefulWidget {
16 }
17
18 class MenuWidgetState extends State<MenuWidget> {
18 - final moneroIcon = Image.asset('assets/images/monero.png');
19 + final moneroIcon = Image.asset('assets/images/monero_menu.png');
20 final bitcoinIcon = Image.asset('assets/images/bitcoin.png');
21 final largeScreen = 731;
22
@@ -36,10 +37,10 @@ class MenuWidgetState extends State<MenuWidget> {
37 screenHeight = 0;
38 opacity = 0;
39
39 - headerHeight = 120;
40 + headerHeight = 125;
41 tileHeight = 75;
42 fromTopEdge = 50;
42 - fromBottomEdge = 30;
43 + fromBottomEdge = 21;//30;
44
45 super.initState();
46 WidgetsBinding.instance.addPostFrameCallback(afterLayout);
@@ -66,189 +67,139 @@ class MenuWidgetState extends State<MenuWidget> {
67 @override
68 Widget build(BuildContext context) {
69 final walletMenu = WalletMenu(context);
69 -// final walletStore = Provider.of<WalletStore>(context);
70 final itemCount = walletMenu.items.length;
71
72 - return Row(
73 - mainAxisSize: MainAxisSize.max,
74 - crossAxisAlignment: CrossAxisAlignment.center,
75 - children: <Widget>[
76 - Padding(
72 + return SafeArea(
73 + child: Row(
74 + mainAxisSize: MainAxisSize.max,
75 + crossAxisAlignment: CrossAxisAlignment.center,
76 + children: <Widget>[
77 + Padding(
78 padding: EdgeInsets.only(left: 24),
79 child: Container(
80 height: 60,
81 width: 4,
82 decoration: BoxDecoration(
82 - borderRadius: BorderRadius.all(Radius.circular(2)),
83 - color: Theme.of(context).hintColor),
83 + borderRadius: BorderRadius.all(Radius.circular(2)),
84 + color: Theme.of(context).hintColor),
85 )),
85 - SizedBox(width: 12),
86 - Expanded(
87 - child: GestureDetector(
88 - onTap: () => null,
89 - child: Container(
90 - width: menuWidth,
91 - height: double.infinity,
92 - decoration: BoxDecoration(
86 + SizedBox(width: 12),
87 + Expanded(
88 + child: ClipRRect(
89 borderRadius: BorderRadius.only(
94 - topLeft: Radius.circular(24),
95 - bottomLeft: Radius.circular(24)),
96 - color: Theme.of(context).primaryTextTheme.display1.color),
97 - child: ClipRRect(
98 - borderRadius: BorderRadius.only(
90 topLeft: Radius.circular(24),
91 bottomLeft: Radius.circular(24)),
101 - child: ListView.separated(
102 - itemBuilder: (_, index) {
103 - if (index == 0) {
104 - return Container(
105 - height: headerHeight,
106 - padding: EdgeInsets.only(
107 - left: 24,
108 - top: fromTopEdge,
109 - right: 24,
110 - bottom: fromBottomEdge),
111 - decoration: BoxDecoration(
112 - borderRadius:
113 - BorderRadius.only(topLeft: Radius.circular(24)),
114 - color: Theme.of(context)
115 - .primaryTextTheme
116 - .display2
117 - .color),
118 - child: Row(
119 - mainAxisAlignment: MainAxisAlignment.start,
120 - children: <Widget>[
121 - _iconFor(type: widget.type),
122 - SizedBox(width: 16),
123 - Expanded(
92 + child: Container(
93 + width: menuWidth,
94 + height: double.infinity,
95 + color: PaletteDark.deepPurpleBlue,
96 + child: SingleChildScrollView(
97 + child: Column(
98 + children: <Widget>[
99 + Container(
100 + height: headerHeight,
101 + padding: EdgeInsets.only(
102 + left: 24,
103 + top: fromTopEdge,
104 + right: 24,
105 + bottom: fromBottomEdge),
106 + child: Row(
107 + mainAxisAlignment: MainAxisAlignment.start,
108 + children: <Widget>[
109 + _iconFor(type: widget.type),
110 + SizedBox(width: 12),
111 + Expanded(
112 + child: Container(
113 + height: 42,
114 + child: Column(
115 + crossAxisAlignment: CrossAxisAlignment.start,
116 + mainAxisAlignment: widget.subname != null
117 + ? MainAxisAlignment.spaceBetween
118 + : MainAxisAlignment.center,
119 + children: <Widget>[
120 + Text(
121 + widget.name,
122 + style: TextStyle(
123 + color: Colors.white,
124 + fontSize: 16,
125 + fontWeight: FontWeight.bold),
126 + ),
127 + if (widget.subname != null)
128 + Text(
129 + widget.subname,
130 + style: TextStyle(
131 + color: PaletteDark.darkCyanBlue,
132 + fontWeight: FontWeight.w500,
133 + fontSize: 12),
134 + )
135 + ],
136 + ),
137 + ))
138 + ],
139 + ),
140 + ),
141 + Container(
142 + height: 1,
143 + color: PaletteDark.lightOceanBlue,
144 + ),
145 + ListView.separated(
146 + shrinkWrap: true,
147 + physics: NeverScrollableScrollPhysics(),
148 + itemBuilder: (_, index) {
149 +
150 + final item = walletMenu.items[index];
151 + final image = walletMenu.images[index] ?? Offstage();
152 + final isLastTile = index == itemCount - 1;
153 +
154 + return GestureDetector(
155 + onTap: () {
156 + Navigator.of(context).pop();
157 + walletMenu.action(index);
158 + },
159 child: Container(
125 - height: 40,
126 - child: Column(
127 - crossAxisAlignment: CrossAxisAlignment.start,
128 - mainAxisAlignment: widget.subname != null
129 - ? MainAxisAlignment.spaceBetween
130 - : MainAxisAlignment.center,
131 - children: <Widget>[
132 - Text(
133 - widget.name,
134 - style: TextStyle(
135 - color: Theme.of(context)
136 - .primaryTextTheme
137 - .title
138 - .color,
139 - decoration: TextDecoration.none,
140 - fontFamily: 'Avenir Next',
141 - fontSize: 20,
142 - fontWeight: FontWeight.bold),
160 + height: isLastTile
161 + ? headerHeight
162 + : tileHeight,
163 + padding: isLastTile
164 + ? EdgeInsets.only(
165 + left: 24,
166 + right: 24,
167 + top: fromBottomEdge,
168 + bottom: fromTopEdge)
169 + : EdgeInsets.only(left: 24, right: 24),
170 + alignment: isLastTile ? Alignment.topLeft : null,
171 + child: Row(
172 + mainAxisAlignment: MainAxisAlignment.start,
173 + crossAxisAlignment: CrossAxisAlignment.center,
174 + children: <Widget>[
175 + image,
176 + SizedBox(width: 16),
177 + Expanded(
178 + child: Text(
179 + item,
180 + style: TextStyle(
181 + color: Colors.white,
182 + fontSize: 16,
183 + fontWeight: FontWeight.bold),
184 + ))
185 + ],
186 ),
144 - if (widget.subname != null)
145 - Text(
146 - widget.subname,
147 - style: TextStyle(
148 - color: Theme.of(context)
149 - .primaryTextTheme
150 - .caption
151 - .color,
152 - decoration: TextDecoration.none,
153 - fontFamily: 'Avenir Next',
154 - fontSize: 12),
155 - )
156 - ],
157 - ),
158 - ))
159 - ],
160 - ),
161 - );
162 - }
163 -
164 - index -= 1;
165 - final item = walletMenu.items[index];
166 - final image = walletMenu.images[index] ?? Offstage();
167 -
168 - return GestureDetector(
169 - onTap: () {
170 - Navigator.of(context).pop();
171 - walletMenu.action(index);
172 - },
173 - child: index == itemCount - 1
174 - ? Container(
175 - height: headerHeight,
176 - padding: EdgeInsets.only(
177 - left: 24,
178 - right: 24,
179 - top: fromBottomEdge,
180 - bottom: fromTopEdge),
181 - alignment: Alignment.topLeft,
182 - decoration: BoxDecoration(
183 - borderRadius: BorderRadius.only(
184 - bottomLeft: Radius.circular(24)),
185 - color: Theme.of(context)
186 - .primaryTextTheme
187 - .display1
188 - .color,
189 - ),
190 - child: Row(
191 - mainAxisAlignment: MainAxisAlignment.start,
192 - crossAxisAlignment: CrossAxisAlignment.center,
193 - children: <Widget>[
194 - image,
195 - SizedBox(width: 16),
196 - Expanded(
197 - child: Text(
198 - item,
199 - style: TextStyle(
200 - decoration: TextDecoration.none,
201 - color: Theme.of(context)
202 - .primaryTextTheme
203 - .title
204 - .color,
205 - fontFamily: 'Avenir Next',
206 - fontSize: 20,
207 - fontWeight: FontWeight.bold),
208 - ))
209 - ],
210 - ),
211 - )
212 - : Container(
213 - height: tileHeight,
214 - padding: EdgeInsets.only(left: 24, right: 24),
215 - color: Theme.of(context)
216 - .primaryTextTheme
217 - .display1
218 - .color,
219 - child: Row(
220 - mainAxisAlignment: MainAxisAlignment.start,
221 - crossAxisAlignment: CrossAxisAlignment.center,
222 - children: <Widget>[
223 - image,
224 - SizedBox(width: 16),
225 - Expanded(
226 - child: Text(
227 - item,
228 - style: TextStyle(
229 - decoration: TextDecoration.none,
230 - color: Theme.of(context)
231 - .primaryTextTheme
232 - .title
233 - .color,
234 - fontFamily: 'Avenir Next',
235 - fontSize: 20,
236 - fontWeight: FontWeight.bold),
237 - ))
238 - ],
239 - ),
187 + )
188 + );
189 + },
190 + separatorBuilder: (_, index) => Container(
191 + height: 1,
192 + color: PaletteDark.lightOceanBlue,
193 ),
241 - );
242 - },
243 - separatorBuilder: (_, index) => Container(
244 - height: 1,
245 - color: Theme.of(context).dividerColor,
246 - ),
247 - itemCount: itemCount + 1),
248 - ),
249 - ),
250 - ))
251 - ],
194 + itemCount: itemCount)
195 + ],
196 + ),
197 + ),
198 + ),
199 + )
200 + )
201 + ],
202 + )
203 );
204 }
205
lib/src/screens/exchange/exchange_page.dart
+15 -41
@@ -1,66 +1,40 @@
1 import 'dart:ui';
2 import 'package:flutter/cupertino.dart';
3 import 'package:flutter/material.dart';
4 -import 'package:provider/provider.dart';
4 import 'package:cake_wallet/palette.dart';
5 import 'package:cake_wallet/generated/i18n.dart';
7 -import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
8 -import 'package:cake_wallet/src/stores/exchange/exchange_store.dart';
6 import 'package:cake_wallet/src/screens/base_page.dart';
10 -import 'package:cake_wallet/src/stores/exchange_template/exchange_template_store.dart';
7 import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker.dart';
8 import 'package:cake_wallet/src/screens/exchange/widgets/base_exchange_widget.dart';
9 import 'package:cake_wallet/src/widgets/trail_button.dart';
10 +import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
11
12 class ExchangePage extends BasePage {
16 - @override
17 - String get title => S.current.exchange;
13 + ExchangePage(this.exchangeViewModel);
14
19 - @override
20 - Color get backgroundLightColor => Palette.darkLavender;
15 + final ExchangeViewModel exchangeViewModel;
16
17 @override
23 - Color get backgroundDarkColor => PaletteDark.moderateBlue;
18 + String get title => S.current.exchange;
19
20 @override
26 - Widget middle(BuildContext context) {
27 - final exchangeStore = Provider.of<ExchangeStore>(context);
28 -
29 - return PresentProviderPicker(exchangeStore: exchangeStore);
30 - }
21 + Color get backgroundLightColor => PaletteDark.wildVioletBlue;
22
23 @override
33 - Widget trailing(BuildContext context) {
34 - final exchangeStore = Provider.of<ExchangeStore>(context);
35 -
36 - return TrailButton(
37 - caption: S.of(context).reset,
38 - onPressed: () => exchangeStore.reset()
39 - );
40 - }
24 + Color get backgroundDarkColor => PaletteDark.wildVioletBlue;
25
26 @override
43 - Widget body(BuildContext context) => ExchangeForm();
44 -}
27 + Widget middle(BuildContext context) =>
28 + PresentProviderPicker(exchangeViewModel: exchangeViewModel);
29
46 -class ExchangeForm extends StatefulWidget {
30 @override
48 - State<StatefulWidget> createState() => ExchangeFormState();
49 -}
50 -
51 -class ExchangeFormState extends State<ExchangeForm> {
31 + Widget trailing(BuildContext context) =>
32 + TrailButton(
33 + caption: S.of(context).reset,
34 + onPressed: () => exchangeViewModel.reset()
35 + );
36
37 @override
54 - Widget build(BuildContext context) {
55 - final exchangeStore = Provider.of<ExchangeStore>(context);
56 - final walletStore = Provider.of<WalletStore>(context);
57 - final exchangeTemplateStore = Provider.of<ExchangeTemplateStore>(context);
58 -
59 - return BaseExchangeWidget(
60 - exchangeStore: exchangeStore,
61 - walletStore: walletStore,
62 - exchangeTemplateStore: exchangeTemplateStore,
63 - isTemplate: false
64 - );
65 - }
38 + Widget body(BuildContext context) =>
39 + BaseExchangeWidget(exchangeViewModel: exchangeViewModel);
40 }
lib/src/screens/exchange/exchange_template_page.dart
+10 -33
@@ -1,55 +1,32 @@
1 import 'dart:ui';
2 import 'package:flutter/cupertino.dart';
3 import 'package:flutter/material.dart';
4 -import 'package:provider/provider.dart';
4 import 'package:cake_wallet/palette.dart';
6 -import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
7 -import 'package:cake_wallet/src/stores/exchange/exchange_store.dart';
5 import 'package:cake_wallet/src/screens/base_page.dart';
6 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';
7 import 'package:cake_wallet/src/screens/exchange/widgets/base_exchange_widget.dart';
8 import 'package:cake_wallet/generated/i18n.dart';
9 +import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
10
11 class ExchangeTemplatePage extends BasePage {
15 - @override
16 - String get title => S.current.exchange_new_template;
12 + ExchangeTemplatePage(this.exchangeViewModel);
13
18 - @override
19 - Color get backgroundLightColor => Palette.darkLavender;
14 + final ExchangeViewModel exchangeViewModel;
15
16 @override
22 - Color get backgroundDarkColor => PaletteDark.moderateBlue;
17 + String get title => S.current.exchange_new_template;
18
19 @override
25 - Widget trailing(BuildContext context) {
26 - final exchangeStore = Provider.of<ExchangeStore>(context);
27 -
28 - return PresentProviderPicker(exchangeStore: exchangeStore);
29 - }
20 + Color get backgroundLightColor => PaletteDark.wildVioletBlue;
21
22 @override
32 - Widget body(BuildContext context) => ExchangeTemplateForm();
33 -}
23 + Color get backgroundDarkColor => PaletteDark.wildVioletBlue;
24
35 -class ExchangeTemplateForm extends StatefulWidget{
25 @override
37 - ExchangeTemplateFormState createState() => ExchangeTemplateFormState();
38 -}
39 -
40 -class ExchangeTemplateFormState extends State<ExchangeTemplateForm> {
26 + Widget trailing(BuildContext context) =>
27 + PresentProviderPicker(exchangeViewModel: exchangeViewModel);
28
29 @override
43 - Widget build(BuildContext context) {
44 - final exchangeStore = Provider.of<ExchangeStore>(context);
45 - final walletStore = Provider.of<WalletStore>(context);
46 - final exchangeTemplateStore = Provider.of<ExchangeTemplateStore>(context);
47 -
48 - return BaseExchangeWidget(
49 - exchangeStore: exchangeStore,
50 - walletStore: walletStore,
51 - exchangeTemplateStore: exchangeTemplateStore,
52 - isTemplate: true
53 - );
54 - }
30 + Widget body(BuildContext context) =>
31 + BaseExchangeWidget(exchangeViewModel: exchangeViewModel, isTemplate: true);
32 }
\ No newline at end of file
lib/src/screens/exchange/widgets/base_exchange_widget.dart
+197 -185
@@ -1,5 +1,7 @@
1 import 'dart:ui';
2 +import 'package:cake_wallet/palette.dart';
3 import 'package:cake_wallet/src/domain/exchange/exchange_template.dart';
4 +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
5 import 'package:cake_wallet/src/widgets/template_tile.dart';
6 import 'package:dotted_border/dotted_border.dart';
7 import 'package:flutter/cupertino.dart';
@@ -12,49 +14,37 @@ import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
14 import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_exchange_provider.dart';
15 import 'package:cake_wallet/src/stores/exchange/exchange_trade_state.dart';
16 import 'package:cake_wallet/src/stores/exchange/limits_state.dart';
15 -import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
16 -import 'package:cake_wallet/src/stores/exchange/exchange_store.dart';
17 import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart';
18 import 'package:cake_wallet/src/widgets/primary_button.dart';
19 import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
20 import 'package:cake_wallet/src/widgets/top_panel.dart';
21 -import 'package:cake_wallet/src/stores/exchange_template/exchange_template_store.dart';
21 import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
22 +import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
23
24 class BaseExchangeWidget extends StatefulWidget {
25 BaseExchangeWidget({
26 - @ required this.exchangeStore,
27 - @ required this.walletStore,
28 - @ required this.exchangeTemplateStore,
29 - @ required this.isTemplate,
26 + @ required this.exchangeViewModel,
27 + this.isTemplate = false,
28 });
29
32 - final ExchangeStore exchangeStore;
33 - final WalletStore walletStore;
34 - final ExchangeTemplateStore exchangeTemplateStore;
30 + final ExchangeViewModel exchangeViewModel;
31 final bool isTemplate;
32
33 @override
34 BaseExchangeWidgetState createState() =>
35 BaseExchangeWidgetState(
40 - exchangeStore: exchangeStore,
41 - walletStore: walletStore,
42 - exchangeTemplateStore: exchangeTemplateStore,
36 + exchangeViewModel: exchangeViewModel,
37 isTemplate: isTemplate
38 );
39 }
40
41 class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
42 BaseExchangeWidgetState({
49 - @ required this.exchangeStore,
50 - @ required this.walletStore,
51 - @ required this.exchangeTemplateStore,
43 + @ required this.exchangeViewModel,
44 @ required this.isTemplate,
45 });
46
55 - final ExchangeStore exchangeStore;
56 - final WalletStore walletStore;
57 - final ExchangeTemplateStore exchangeTemplateStore;
47 + final ExchangeViewModel exchangeViewModel;
48 final bool isTemplate;
49
50 final depositKey = GlobalKey<ExchangeCardState>();
@@ -64,31 +54,31 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
54
55 @override
56 Widget build(BuildContext context) {
67 - final Image arrowBottomPurple = Image.asset(
57 + final arrowBottomPurple = Image.asset(
58 'assets/images/arrow_bottom_purple_icon.png',
69 - color: Theme.of(context).primaryTextTheme.title.color,
59 + color: Colors.white,
60 height: 8,
61 );
72 - final Image arrowBottomCakeGreen = Image.asset(
62 + final arrowBottomCakeGreen = Image.asset(
63 'assets/images/arrow_bottom_cake_green.png',
74 - color: Theme.of(context).primaryTextTheme.title.color,
64 + color: Colors.white,
65 height: 8,
66 );
67
68 final depositWalletName =
79 - exchangeStore.depositCurrency == CryptoCurrency.xmr
80 - ? walletStore.name
69 + exchangeViewModel.depositCurrency == CryptoCurrency.xmr
70 + ? exchangeViewModel.wallet.name
71 : null;
72 final receiveWalletName =
83 - exchangeStore.receiveCurrency == CryptoCurrency.xmr
84 - ? walletStore.name
73 + exchangeViewModel.receiveCurrency == CryptoCurrency.xmr
74 + ? exchangeViewModel.wallet.name
75 : null;
76
77 WidgetsBinding.instance.addPostFrameCallback(
88 - (_) => _setReactions(context, exchangeStore, walletStore));
78 + (_) => _setReactions(context, exchangeViewModel));
79
80 return Container(
91 - color: Theme.of(context).backgroundColor,
81 + color: PaletteDark.backgroundColor,
82 child: Form(
83 key: _formKey,
84 child: ScrollableWithBottomSection(
@@ -96,73 +86,60 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
86 content: Column(
87 children: <Widget>[
88 TopPanel(
99 - color: Theme.of(context).accentTextTheme.title.backgroundColor,
100 - edgeInsets: EdgeInsets.only(bottom: 24),
89 + color: PaletteDark.darkNightBlue,
90 + edgeInsets: EdgeInsets.only(bottom: 32),
91 widget: Column(
92 children: <Widget>[
93 TopPanel(
104 - color: Theme.of(context).accentTextTheme.title.color,
94 + edgeInsets: EdgeInsets.fromLTRB(24, 29, 24, 32),
95 + color: PaletteDark.wildVioletBlue,
96 widget: Observer(
97 builder: (_) => ExchangeCard(
98 key: depositKey,
99 title: S.of(context).you_will_send,
109 - initialCurrency: exchangeStore.depositCurrency,
100 + initialCurrency: exchangeViewModel.depositCurrency,
101 initialWalletName: depositWalletName,
102 initialAddress:
112 - exchangeStore.depositCurrency == walletStore.type
113 - ? walletStore.address
114 - : exchangeStore.depositAddress,
103 + exchangeViewModel.depositCurrency == exchangeViewModel.wallet.currency
104 + ? exchangeViewModel.wallet.address
105 + : exchangeViewModel.depositAddress,
106 initialIsAmountEditable: true,
116 - initialIsAddressEditable: exchangeStore.isDepositAddressEnabled,
107 + initialIsAddressEditable: exchangeViewModel.isDepositAddressEnabled,
108 isAmountEstimated: false,
109 currencies: CryptoCurrency.all,
110 onCurrencySelected: (currency) =>
120 - exchangeStore.changeDepositCurrency(currency: currency),
111 + exchangeViewModel.changeDepositCurrency(currency: currency),
112 imageArrow: arrowBottomPurple,
122 - currencyButtonColor: Theme.of(context).accentTextTheme.title.color,
123 - addressButtonsColor: Theme.of(context).accentTextTheme.title.backgroundColor,
124 - currencyValueValidator: (value) {
125 - exchangeStore.validateCryptoCurrency(value);
126 - return exchangeStore.errorMessage;
127 - },
128 - addressTextFieldValidator: (value) {
129 - exchangeStore.validateAddress(value,
130 - cryptoCurrency: exchangeStore.depositCurrency);
131 - return exchangeStore.errorMessage;
132 - },
113 + currencyButtonColor: PaletteDark.wildVioletBlue,
114 + addressButtonsColor: PaletteDark.moderateBlue,
115 + currencyValueValidator: exchangeViewModel.amountValidator,
116 + addressTextFieldValidator: exchangeViewModel.addressValidator,
117 ),
118 )
119 ),
120 Padding(
137 - padding: EdgeInsets.only(top: 32, left: 24, right: 24),
121 + padding: EdgeInsets.only(top: 29, left: 24, right: 24),
122 child: Observer(
123 builder: (_) => ExchangeCard(
124 key: receiveKey,
125 title: S.of(context).you_will_get,
142 - initialCurrency: exchangeStore.receiveCurrency,
126 + initialCurrency: exchangeViewModel.receiveCurrency,
127 initialWalletName: receiveWalletName,
128 initialAddress:
145 - exchangeStore.receiveCurrency == walletStore.type
146 - ? walletStore.address
147 - : exchangeStore.receiveAddress,
129 + exchangeViewModel.receiveCurrency == exchangeViewModel.wallet.currency
130 + ? exchangeViewModel.wallet.address
131 + : exchangeViewModel.receiveAddress,
132 initialIsAmountEditable: false,
149 - initialIsAddressEditable: exchangeStore.isReceiveAddressEnabled,
133 + initialIsAddressEditable: exchangeViewModel.isReceiveAddressEnabled,
134 isAmountEstimated: true,
135 currencies: CryptoCurrency.all,
152 - onCurrencySelected: (currency) => exchangeStore
136 + onCurrencySelected: (currency) => exchangeViewModel
137 .changeReceiveCurrency(currency: currency),
138 imageArrow: arrowBottomCakeGreen,
155 - currencyButtonColor: Theme.of(context).accentTextTheme.title.backgroundColor,
156 - addressButtonsColor: Theme.of(context).accentTextTheme.title.color,
157 - currencyValueValidator: (value) {
158 - exchangeStore.validateCryptoCurrency(value);
159 - return exchangeStore.errorMessage;
160 - },
161 - addressTextFieldValidator: (value) {
162 - exchangeStore.validateAddress(value,
163 - cryptoCurrency: exchangeStore.receiveCurrency);
164 - return exchangeStore.errorMessage;
165 - },
139 + currencyButtonColor: PaletteDark.darkNightBlue,
140 + addressButtonsColor: PaletteDark.moderateBlue,
141 + currencyValueValidator: exchangeViewModel.amountValidator,
142 + addressTextFieldValidator: exchangeViewModel.addressValidator,
143 )),
144 )
145 ],
@@ -172,7 +149,7 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
149 ? Offstage()
150 : Padding(
151 padding: EdgeInsets.only(
175 - top: 32,
152 + top: 30,
153 left: 24,
154 bottom: 24
155 ),
@@ -184,7 +161,7 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
161 style: TextStyle(
162 fontSize: 18,
163 fontWeight: FontWeight.w600,
187 - color: Theme.of(context).primaryTextTheme.caption.color
164 + color: PaletteDark.darkCyanBlue
165 ),
166 )
167 ],
@@ -196,66 +173,90 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
173 height: 40,
174 width: double.infinity,
175 padding: EdgeInsets.only(left: 24),
199 - child: Observer(
200 - builder: (_) {
201 - final itemCount = exchangeTemplateStore.templates.length + 1;
202 -
203 - return ListView.builder(
204 - scrollDirection: Axis.horizontal,
205 - itemCount: itemCount,
206 - itemBuilder: (context, index) {
207 -
208 - if (index == 0) {
209 - return GestureDetector(
210 - onTap: () => Navigator.of(context)
211 - .pushNamed(Routes.exchangeTemplate),
212 - child: Container(
213 - padding: EdgeInsets.only(right: 10),
214 - child: DottedBorder(
215 - borderType: BorderType.RRect,
216 - dashPattern: [8, 4],
217 - color: Theme.of(context).accentTextTheme.title.backgroundColor,
218 - strokeWidth: 2,
219 - radius: Radius.circular(20),
220 - child: Container(
221 - height: 40,
222 - width: 75,
223 - padding: EdgeInsets.only(left: 10, right: 10),
224 - alignment: Alignment.center,
225 - decoration: BoxDecoration(
226 - borderRadius: BorderRadius.all(Radius.circular(20)),
227 - color: Colors.transparent,
228 - ),
229 - child: Text(
230 - S.of(context).send_new,
231 - style: TextStyle(
232 - fontSize: 14,
233 - fontWeight: FontWeight.w600,
234 - color: Theme.of(context).primaryTextTheme.caption.color
235 - ),
236 - ),
237 - )
176 + child: SingleChildScrollView(
177 + scrollDirection: Axis.horizontal,
178 + child: Row(
179 + children: <Widget>[
180 + GestureDetector(
181 + onTap: () => Navigator.of(context)
182 + .pushNamed(Routes.exchangeTemplate),
183 + child: Container(
184 + padding: EdgeInsets.only(left: 1, right: 10),
185 + child: DottedBorder(
186 + borderType: BorderType.RRect,
187 + dashPattern: [6, 4],
188 + color: PaletteDark.darkCyanBlue,
189 + strokeWidth: 2,
190 + radius: Radius.circular(20),
191 + child: Container(
192 + height: 34,
193 + width: 75,
194 + padding: EdgeInsets.only(left: 10, right: 10),
195 + alignment: Alignment.center,
196 + decoration: BoxDecoration(
197 + borderRadius: BorderRadius.all(Radius.circular(20)),
198 + color: Colors.transparent,
199 + ),
200 + child: Text(
201 + S.of(context).send_new,
202 + style: TextStyle(
203 + fontSize: 14,
204 + fontWeight: FontWeight.w600,
205 + color: PaletteDark.darkCyanBlue
206 ),
207 ),
240 - );
241 - }
242 -
243 - index -= 1;
244 -
245 - final template = exchangeTemplateStore.templates[index];
246 -
247 - return TemplateTile(
248 - amount: template.amount,
249 - from: template.depositCurrency,
250 - to: template.receiveCurrency,
251 - onTap: () {
252 - applyTemplate(exchangeStore, template);
208 + )
209 + ),
210 + ),
211 + ),
212 + Observer(
213 + builder: (_) {
214 + final templates = exchangeViewModel.templates;
215 + final itemCount = exchangeViewModel.templates.length;
216 +
217 + return ListView.builder(
218 + scrollDirection: Axis.horizontal,
219 + shrinkWrap: true,
220 + physics: NeverScrollableScrollPhysics(),
221 + itemCount: itemCount,
222 + itemBuilder: (context, index) {
223 + final template = templates[index];
224 +
225 + return TemplateTile(
226 + key: UniqueKey(),
227 + amount: template.amount,
228 + from: template.depositCurrency,
229 + to: template.receiveCurrency,
230 + onTap: () {
231 + applyTemplate(exchangeViewModel, template);
232 + },
233 + onRemove: () {
234 + showDialog<void>(
235 + context: context,
236 + builder: (dialogContext) {
237 + return AlertWithTwoActions(
238 + alertTitle: S.of(context).template,
239 + alertContent: S.of(context).confirm_delete_template,
240 + leftButtonText: S.of(context).delete,
241 + rightButtonText: S.of(context).cancel,
242 + actionLeftButton: () {
243 + Navigator.of(dialogContext).pop();
244 + exchangeViewModel.exchangeTemplateStore.remove(template: template);
245 + exchangeViewModel.exchangeTemplateStore.update();
246 + },
247 + actionRightButton: () => Navigator.of(dialogContext).pop()
248 + );
249 + }
250 + );
251 + },
252 + );
253 }
254 );
255 }
256 - );
257 - }
258 - ),
256 + ),
257 + ],
258 + )
259 + )
260 )
261 ],
262 ),
@@ -265,14 +266,15 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
266 padding: EdgeInsets.only(bottom: 15),
267 child: Observer(builder: (_) {
268 final description =
268 - exchangeStore.provider is XMRTOExchangeProvider
269 + exchangeViewModel.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(
275 - color: Theme.of(context).primaryTextTheme.caption.color,
276 + color: PaletteDark.darkCyanBlue,
277 + fontWeight: FontWeight.w500,
278 fontSize: 12
279 ),
280 ),
@@ -283,15 +285,15 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
285 ? PrimaryButton(
286 onPressed: () {
287 if (_formKey.currentState.validate()) {
286 - exchangeTemplateStore.addTemplate(
287 - amount: exchangeStore.depositAmount,
288 - depositCurrency: exchangeStore.depositCurrency.toString(),
289 - receiveCurrency: exchangeStore.receiveCurrency.toString(),
290 - provider: exchangeStore.provider.toString(),
291 - depositAddress: exchangeStore.depositAddress,
292 - receiveAddress: exchangeStore.receiveAddress
288 + exchangeViewModel.exchangeTemplateStore.addTemplate(
289 + amount: exchangeViewModel.depositAmount,
290 + depositCurrency: exchangeViewModel.depositCurrency.toString(),
291 + receiveCurrency: exchangeViewModel.receiveCurrency.toString(),
292 + provider: exchangeViewModel.provider.toString(),
293 + depositAddress: exchangeViewModel.depositAddress,
294 + receiveAddress: exchangeViewModel.receiveAddress
295 );
294 - exchangeTemplateStore.update();
296 + exchangeViewModel.exchangeTemplateStore.update();
297 Navigator.of(context).pop();
298 }
299 },
@@ -304,41 +306,47 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
306 text: S.of(context).exchange,
307 onPressed: () {
308 if (_formKey.currentState.validate()) {
307 - exchangeStore.createTrade();
309 + exchangeViewModel.createTrade();
310 }
311 },
312 color: Colors.blue,
313 textColor: Colors.white,
312 - isLoading: exchangeStore.tradeState is TradeIsCreating,
314 + isLoading: exchangeViewModel.tradeState is TradeIsCreating,
315 )),
316 ]),
317 )),
318 );
319 }
320
319 - void applyTemplate(ExchangeStore store, ExchangeTemplate template) {
320 - store.changeDepositCurrency(currency: CryptoCurrency.fromString(template.depositCurrency));
321 - store.changeReceiveCurrency(currency: CryptoCurrency.fromString(template.receiveCurrency));
321 + void applyTemplate(ExchangeViewModel exchangeViewModel,
322 + ExchangeTemplate template) {
323 + exchangeViewModel.changeDepositCurrency(
324 + currency: CryptoCurrency.fromString(template.depositCurrency));
325 + exchangeViewModel.changeReceiveCurrency(
326 + currency: CryptoCurrency.fromString(template.receiveCurrency));
327
328 switch (template.provider) {
329 case 'XMR.TO':
325 - store.changeProvider(provider: store.providerList[0]);
330 + exchangeViewModel.changeProvider(
331 + provider: exchangeViewModel.providerList[0]);
332 break;
333 case 'ChangeNOW':
328 - store.changeProvider(provider: store.providerList[1]);
334 + exchangeViewModel.changeProvider(
335 + provider: exchangeViewModel.providerList[1]);
336 break;
337 case 'MorphToken':
331 - store.changeProvider(provider: store.providerList[2]);
338 + exchangeViewModel.changeProvider(
339 + provider: exchangeViewModel.providerList[2]);
340 break;
341 }
342
335 - store.changeDepositAmount(amount: template.amount);
336 - store.depositAddress = template.depositAddress;
337 - store.receiveAddress = template.receiveAddress;
343 + exchangeViewModel.changeDepositAmount(amount: template.amount);
344 + exchangeViewModel.depositAddress = template.depositAddress;
345 + exchangeViewModel.receiveAddress = template.receiveAddress;
346 }
347
348 void _setReactions(
341 - BuildContext context, ExchangeStore store, WalletStore walletStore) {
349 + BuildContext context, ExchangeViewModel exchangeViewModel) {
350 if (_isReactionsSet) {
351 return;
352 }
@@ -347,7 +355,7 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
355 final depositAmountController = depositKey.currentState.amountController;
356 final receiveAddressController = receiveKey.currentState.addressController;
357 final receiveAmountController = receiveKey.currentState.amountController;
350 - final limitsState = store.limitsState;
358 + final limitsState = exchangeViewModel.limitsState;
359
360 if (limitsState is LimitsLoadedSuccessfully) {
361 final min = limitsState.limits.min != null
@@ -360,63 +368,62 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
368 key.currentState.changeLimits(min: min, max: max);
369 }
370
363 - _onCurrencyChange(store.receiveCurrency, walletStore, receiveKey);
364 - _onCurrencyChange(store.depositCurrency, walletStore, depositKey);
371 + _onCurrencyChange(exchangeViewModel.receiveCurrency, exchangeViewModel, receiveKey);
372 + _onCurrencyChange(exchangeViewModel.depositCurrency, exchangeViewModel, depositKey);
373
374 reaction(
367 - (_) => walletStore.name,
375 + (_) => exchangeViewModel.wallet.name,
376 (String _) => _onWalletNameChange(
369 - walletStore, store.receiveCurrency, receiveKey));
377 + exchangeViewModel, exchangeViewModel.receiveCurrency, receiveKey));
378
379 reaction(
372 - (_) => walletStore.name,
380 + (_) => exchangeViewModel.wallet.name,
381 (String _) => _onWalletNameChange(
374 - walletStore, store.depositCurrency, depositKey));
382 + exchangeViewModel, exchangeViewModel.depositCurrency, depositKey));
383
384 reaction(
377 - (_) => store.receiveCurrency,
385 + (_) => exchangeViewModel.receiveCurrency,
386 (CryptoCurrency currency) =>
379 - _onCurrencyChange(currency, walletStore, receiveKey));
387 + _onCurrencyChange(currency, exchangeViewModel, receiveKey));
388
389 reaction(
382 - (_) => store.depositCurrency,
390 + (_) => exchangeViewModel.depositCurrency,
391 (CryptoCurrency currency) =>
384 - _onCurrencyChange(currency, walletStore, depositKey));
392 + _onCurrencyChange(currency, exchangeViewModel, depositKey));
393
386 - reaction((_) => store.depositAmount, (String amount) {
394 + reaction((_) => exchangeViewModel.depositAmount, (String amount) {
395 if (depositKey.currentState.amountController.text != amount) {
396 depositKey.currentState.amountController.text = amount;
397 }
398 });
399
392 - reaction((_) => store.depositAddress, (String address) {
400 + reaction((_) => exchangeViewModel.depositAddress, (String address) {
401 if (depositKey.currentState.addressController.text != address) {
402 depositKey.currentState.addressController.text = address;
403 }
404 });
405
398 - reaction((_) => store.isDepositAddressEnabled, (bool isEnabled) {
406 + reaction((_) => exchangeViewModel.isDepositAddressEnabled, (bool isEnabled) {
407 depositKey.currentState.isAddressEditable(isEditable: isEnabled);
408 });
409
402 - reaction((_) => store.receiveAmount, (String amount) {
403 - if (receiveKey.currentState.amountController.text !=
404 - store.receiveAmount) {
410 + reaction((_) => exchangeViewModel.receiveAmount, (String amount) {
411 + if (receiveKey.currentState.amountController.text != amount) {
412 receiveKey.currentState.amountController.text = amount;
413 }
414 });
415
409 - reaction((_) => store.receiveAddress, (String address) {
416 + reaction((_) => exchangeViewModel.receiveAddress, (String address) {
417 if (receiveKey.currentState.addressController.text != address) {
418 receiveKey.currentState.addressController.text = address;
419 }
420 });
421
415 - reaction((_) => store.isReceiveAddressEnabled, (bool isEnabled) {
422 + reaction((_) => exchangeViewModel.isReceiveAddressEnabled, (bool isEnabled) {
423 receiveKey.currentState.isAddressEditable(isEditable: isEnabled);
424 });
425
419 - reaction((_) => store.tradeState, (ExchangeTradeState state) {
426 + reaction((_) => exchangeViewModel.tradeState, (ExchangeTradeState state) {
427 if (state is TradeIsCreatedFailure) {
428 WidgetsBinding.instance.addPostFrameCallback((_) {
429 showDialog<void>(
@@ -437,7 +444,7 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
444 }
445 });
446
440 - reaction((_) => store.limitsState, (LimitsState state) {
447 + reaction((_) => exchangeViewModel.limitsState, (LimitsState state) {
448 String min;
449 String max;
450
@@ -461,29 +468,29 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
468 });
469
470 depositAddressController.addListener(
464 - () => store.depositAddress = depositAddressController.text);
471 + () => exchangeViewModel.depositAddress = depositAddressController.text);
472
473 depositAmountController.addListener(() {
467 - if (depositAmountController.text != store.depositAmount) {
468 - store.changeDepositAmount(amount: depositAmountController.text);
474 + if (depositAmountController.text != exchangeViewModel.depositAmount) {
475 + exchangeViewModel.changeDepositAmount(amount: depositAmountController.text);
476 }
477 });
478
479 receiveAddressController.addListener(
473 - () => store.receiveAddress = receiveAddressController.text);
480 + () => exchangeViewModel.receiveAddress = receiveAddressController.text);
481
482 receiveAmountController.addListener(() {
476 - if (receiveAmountController.text != store.receiveAmount) {
477 - store.changeReceiveAmount(amount: receiveAmountController.text);
483 + if (receiveAmountController.text != exchangeViewModel.receiveAmount) {
484 + exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text);
485 }
486 });
487
481 - reaction((_) => walletStore.address, (String address) {
482 - if (store.depositCurrency == CryptoCurrency.xmr) {
488 + reaction((_) => exchangeViewModel.wallet.address, (String address) {
489 + if (exchangeViewModel.depositCurrency == CryptoCurrency.xmr) {
490 depositKey.currentState.changeAddress(address: address);
491 }
492
486 - if (store.receiveCurrency == CryptoCurrency.xmr) {
493 + if (exchangeViewModel.receiveCurrency == CryptoCurrency.xmr) {
494 receiveKey.currentState.changeAddress(address: address);
495 }
496 });
@@ -491,28 +498,33 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
498 _isReactionsSet = true;
499 }
500
494 - void _onCurrencyChange(CryptoCurrency currency, WalletStore walletStore,
501 + void _onCurrencyChange(CryptoCurrency currency,
502 + ExchangeViewModel exchangeViewModel,
503 GlobalKey<ExchangeCardState> key) {
496 - final isCurrentTypeWallet = currency == walletStore.type;
504 + final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency;
505
506 key.currentState.changeSelectedCurrency(currency);
507 key.currentState
500 - .changeWalletName(isCurrentTypeWallet ? walletStore.name : null);
508 + .changeWalletName(isCurrentTypeWallet
509 + ? exchangeViewModel.wallet.name : null);
510
511 key.currentState
503 - .changeAddress(address: isCurrentTypeWallet ? walletStore.address : '');
512 + .changeAddress(address: isCurrentTypeWallet
513 + ? exchangeViewModel.wallet.address : '');
514
515 key.currentState.changeAmount(amount: '');
516 }
517
508 - void _onWalletNameChange(WalletStore walletStore, CryptoCurrency currency,
518 + void _onWalletNameChange(ExchangeViewModel exchangeViewModel,
519 + CryptoCurrency currency,
520 GlobalKey<ExchangeCardState> key) {
510 - final isCurrentTypeWallet = currency == walletStore.type;
521 + final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency;
522
523 if (isCurrentTypeWallet) {
513 - key.currentState.changeWalletName(walletStore.name);
514 - key.currentState.addressController.text = walletStore.address;
515 - } else if (key.currentState.addressController.text == walletStore.address) {
524 + key.currentState.changeWalletName(exchangeViewModel.wallet.name);
525 + key.currentState.addressController.text = exchangeViewModel.wallet.address;
526 + } else if (key.currentState.addressController.text ==
527 + exchangeViewModel.wallet.address) {
528 key.currentState.changeWalletName(null);
529 key.currentState.addressController.text = null;
530 }
lib/src/screens/exchange/widgets/exchange_card.dart
+90 -20
@@ -5,6 +5,7 @@ import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
5 import 'package:cake_wallet/src/widgets/address_text_field.dart';
6 import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
7 import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
8 +import 'package:cake_wallet/palette.dart';
9
10 class ExchangeCard extends StatefulWidget {
11 ExchangeCard(
@@ -57,6 +58,10 @@ class ExchangeCardState extends State<ExchangeCard> {
58 bool _isAddressEditable;
59 bool _isAmountEstimated;
60
61 + final copyImage = Image.asset('assets/images/copy_content.png',
62 + height: 16, width: 16,
63 + color: Colors.white);
64 +
65 @override
66 void initState() {
67 _title = widget.title;
@@ -114,6 +119,7 @@ class ExchangeCardState extends State<ExchangeCard> {
119 width: double.infinity,
120 color: Colors.transparent,
121 child: Column(
122 + crossAxisAlignment: CrossAxisAlignment.start,
123 children: <Widget>[
124 Row(
125 mainAxisAlignment: MainAxisAlignment.start,
@@ -123,13 +129,13 @@ class ExchangeCardState extends State<ExchangeCard> {
129 style: TextStyle(
130 fontSize: 18,
131 fontWeight: FontWeight.w600,
126 - color: Theme.of(context).primaryTextTheme.caption.color
132 + color: PaletteDark.lightBlueGrey
133 ),
134 )
135 ],
136 ),
137 Padding(
132 - padding: EdgeInsets.only(top: 10),
138 + padding: EdgeInsets.only(top: 20),
139 child: Stack(
140 children: <Widget>[
141 BaseTextFormField(
@@ -143,7 +149,20 @@ class ExchangeCardState extends State<ExchangeCard> {
149 RegExp('[\\-|\\ |\\,]'))
150 ],
151 hintText: '0.0000',
146 - validator: widget.currencyValueValidator
152 + borderColor: PaletteDark.blueGrey,
153 + textStyle: TextStyle(
154 + fontSize: 16,
155 + fontWeight: FontWeight.w600,
156 + color: Colors.white
157 + ),
158 + placeholderTextStyle: TextStyle(
159 + fontSize: 16,
160 + fontWeight: FontWeight.w600,
161 + color: PaletteDark.lightBlueGrey
162 + ),
163 + validator: _isAmountEditable
164 + ? widget.currencyValueValidator
165 + : null
166 ),
167 Positioned(
168 top: 8,
@@ -163,7 +182,7 @@ class ExchangeCardState extends State<ExchangeCard> {
182 style: TextStyle(
183 fontWeight: FontWeight.w600,
184 fontSize: 16,
166 - color: Theme.of(context).primaryTextTheme.title.color)),
185 + color: Colors.white)),
186 Padding(
187 padding: EdgeInsets.only(left: 5),
188 child: widget.imageArrow,
@@ -181,45 +200,96 @@ class ExchangeCardState extends State<ExchangeCard> {
200 mainAxisAlignment: MainAxisAlignment.start,
201 children: <Widget>[
202 _min != null
184 - ? Text(
203 + ? Text(
204 S.of(context).min_value(
205 _min, _selectedCurrency.toString()),
206 style: TextStyle(
207 fontSize: 10,
208 height: 1.2,
190 - color: Theme.of(context).primaryTextTheme.caption.color),
209 + color: PaletteDark.lightBlueGrey),
210 )
192 - : Offstage(),
211 + : Offstage(),
212 _min != null ? SizedBox(width: 10) : Offstage(),
213 _max != null
195 - ? Text(
214 + ? Text(
215 S.of(context).max_value(
216 _max, _selectedCurrency.toString()),
217 style: TextStyle(
218 fontSize: 10,
219 height: 1.2,
201 - color: Theme.of(context).primaryTextTheme.caption.color))
202 - : Offstage(),
220 + color: PaletteDark.lightBlueGrey))
221 + : Offstage(),
222 ]),
223 ),
224 Padding(
206 - padding: EdgeInsets.only(top: 10),
207 - child: AddressTextField(
225 + padding: EdgeInsets.only(top: 20),
226 + child: Text(
227 + _isAddressEditable
228 + ? S.of(context).widgets_address
229 + : S.of(context).refund_address,
230 + style: TextStyle(
231 + fontSize: 14,
232 + fontWeight: FontWeight.w500,
233 + color: PaletteDark.lightBlueGrey
234 + ),
235 + )
236 + ),
237 + _isAddressEditable
238 + ? AddressTextField(
239 controller: addressController,
209 - isActive: _isAddressEditable,
210 - options: _isAddressEditable
211 - ? _walletName != null
212 - ? []
213 - : [
240 + options: [
241 + AddressTextFieldOption.paste,
242 AddressTextFieldOption.qrCode,
243 AddressTextFieldOption.addressBook,
216 - ]
217 - : [],
244 + ],
245 + placeholder: '',
246 isBorderExist: false,
247 + textStyle: TextStyle(
248 + fontSize: 16,
249 + fontWeight: FontWeight.w600,
250 + color: Colors.white),
251 buttonColor: widget.addressButtonsColor,
252 validator: widget.addressTextFieldValidator,
221 - ),
253 )
254 + : Padding(
255 + padding: EdgeInsets.only(top: 10),
256 + child: Builder(
257 + builder: (context) => GestureDetector(
258 + onTap: () {
259 + Clipboard.setData(ClipboardData(
260 + text: addressController.text));
261 + Scaffold.of(context).showSnackBar(SnackBar(
262 + content: Text(
263 + S.of(context).copied_to_clipboard,
264 + style: TextStyle(color: Colors.white),
265 + ),
266 + backgroundColor: Colors.green,
267 + duration: Duration(milliseconds: 500),
268 + ));
269 + },
270 + child: Row(
271 + mainAxisSize: MainAxisSize.max,
272 + children: <Widget>[
273 + Expanded(
274 + child: Text(
275 + addressController.text,
276 + maxLines: 1,
277 + overflow: TextOverflow.ellipsis,
278 + style: TextStyle(
279 + fontSize: 16,
280 + fontWeight: FontWeight.w600,
281 + color: Colors.white),
282 + ),
283 + ),
284 + Padding(
285 + padding: EdgeInsets.only(left: 16),
286 + child: copyImage,
287 + )
288 + ],
289 + ),
290 + )
291 + ),
292 + ),
293 ]),
294 );
295 }
lib/src/screens/exchange/widgets/present_provider_picker.dart
+17 -16
@@ -1,21 +1,22 @@
1 import 'package:flutter/material.dart';
2 -import 'package:cake_wallet/src/stores/exchange/exchange_store.dart';
2 import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart';
3 import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart';
4 import 'package:flutter_mobx/flutter_mobx.dart';
5 import 'package:cake_wallet/generated/i18n.dart';
6 import 'package:cake_wallet/src/widgets/picker.dart';
7 +import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
8 +import 'package:cake_wallet/palette.dart';
9
10 class PresentProviderPicker extends StatelessWidget {
10 - PresentProviderPicker({@required this.exchangeStore});
11 + PresentProviderPicker({@required this.exchangeViewModel});
12
12 - final ExchangeStore exchangeStore;
13 + final ExchangeViewModel exchangeViewModel;
14
15 @override
16 Widget build(BuildContext context) {
16 - final Image arrowBottom =
17 + final arrowBottom =
18 Image.asset('assets/images/arrow_bottom_purple_icon.png',
18 - color: Theme.of(context).primaryTextTheme.title.color,
19 + color: Colors.white,
20 height: 6);
21
22 return FlatButton(
@@ -33,19 +34,19 @@ class PresentProviderPicker extends StatelessWidget {
34 Text(S.of(context).exchange,
35 style: TextStyle(
36 fontSize: 16.0,
36 - fontWeight: FontWeight.w400,
37 - color: Theme.of(context).primaryTextTheme.title.color)),
37 + fontWeight: FontWeight.w600,
38 + color: Colors.white)),
39 Observer(
39 - builder: (_) => Text('${exchangeStore.provider.title}',
40 + builder: (_) => Text('${exchangeViewModel.provider.title}',
41 style: TextStyle(
42 fontSize: 10.0,
42 - fontWeight: FontWeight.w400,
43 - color: Theme.of(context).primaryTextTheme.caption.color)))
43 + fontWeight: FontWeight.w500,
44 + color: PaletteDark.lightBlueGrey)))
45 ],
46 ),
47 SizedBox(width: 5),
48 Padding(
48 - padding: EdgeInsets.only(top: 8),
49 + padding: EdgeInsets.only(top: 12),
50 child: arrowBottom,
51 )
52 ],
@@ -54,11 +55,11 @@ class PresentProviderPicker extends StatelessWidget {
55 }
56
57 void _presentProviderPicker(BuildContext context) {
57 - final items = exchangeStore.providersForCurrentPair();
58 - final selectedItem = items.indexOf(exchangeStore.provider);
59 - final images = List<Image>();
58 + final items = exchangeViewModel.providersForCurrentPair();
59 + final selectedItem = items.indexOf(exchangeViewModel.provider);
60 + final images = <Image>[];
61
61 - for (ExchangeProvider provider in items) {
62 + for (var provider in items) {
63 switch (provider.description) {
64 case ExchangeProviderDescription.xmrto:
65 images.add(Image.asset('assets/images/xmr_btc.png'));
@@ -79,7 +80,7 @@ class PresentProviderPicker extends StatelessWidget {
80 selectedAtIndex: selectedItem,
81 title: S.of(context).change_exchange_provider,
82 onItemSelected: (ExchangeProvider provider) =>
82 - exchangeStore.changeProvider(provider: provider)),
83 + exchangeViewModel.changeProvider(provider: provider)),
84 context: context);
85 }
86 }
\ No newline at end of file
lib/src/screens/receive/receive_page.dart
+1 -1
@@ -125,7 +125,7 @@ class ReceivePage extends BasePage {
125 .headline5
126 .color
127 .withOpacity(0.4),
128 - validator: AmountValidator(),
128 + validator: AmountValidator(isAutovalidate: true),
129 autovalidate: true,
130 placeholderTextStyle: TextStyle(
131 color: Theme.of(context)
lib/src/widgets/base_text_form_field.dart
+4
@@ -72,6 +72,10 @@ class BaseTextFormField extends StatelessWidget {
72 borderSide: BorderSide(
73 color: borderColor ?? Theme.of(context).dividerColor,
74 width: 1.0)),
75 + disabledBorder: UnderlineInputBorder(
76 + borderSide: BorderSide(
77 + color: borderColor ?? Theme.of(context).dividerColor,
78 + width: 1.0)),
79 enabledBorder: UnderlineInputBorder(
80 borderSide: BorderSide(
81 color: borderColor ?? Theme.of(context).dividerColor,
lib/src/widgets/nav_bar.dart
+2
@@ -71,6 +71,8 @@ class NavBar extends StatelessWidget implements ObstructingPreferredSizeWidget {
71 EdgeInsetsDirectional.only(bottom: _paddingBottom, top: paddingTop),
72 child: CupertinoNavigationBar(
73 leading: leading,
74 + automaticallyImplyLeading: false,
75 + automaticallyImplyMiddle: false,
76 middle: middle,
77 trailing: trailing,
78 backgroundColor: backgroundColor,
lib/src/widgets/trail_button.dart
+2 -1
@@ -1,4 +1,5 @@
1 import 'package:flutter/material.dart';
2 +import 'package:cake_wallet/palette.dart';
3
4 class TrailButton extends StatelessWidget {
5 TrailButton({
@@ -21,7 +22,7 @@ class TrailButton extends StatelessWidget {
22 child: Text(
23 caption,
24 style: TextStyle(
24 - color: Theme.of(context).primaryTextTheme.caption.color,
25 + color: PaletteDark.lightBlueGrey,
26 fontWeight: FontWeight.w500,
27 fontSize: 14),
28 ),
lib/view_model/exchange/exchange_view_model.dart new
+306
@@ -0,0 +1,306 @@
1 +import 'package:cake_wallet/core/address_validator.dart';
2 +import 'package:cake_wallet/core/amount_validator.dart';
3 +import 'package:cake_wallet/core/template_validator.dart';
4 +import 'package:cake_wallet/core/validator.dart';
5 +import 'package:cake_wallet/core/wallet_base.dart';
6 +import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
7 +import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart';
8 +import 'package:cake_wallet/src/domain/exchange/limits.dart';
9 +import 'package:cake_wallet/src/domain/exchange/trade.dart';
10 +import 'package:cake_wallet/src/stores/exchange/limits_state.dart';
11 +import 'package:intl/intl.dart';
12 +import 'package:mobx/mobx.dart';
13 +import 'package:cake_wallet/generated/i18n.dart';
14 +import 'package:hive/hive.dart';
15 +import 'package:cake_wallet/src/stores/exchange/exchange_trade_state.dart';
16 +import 'package:cake_wallet/src/domain/exchange/changenow/changenow_exchange_provider.dart';
17 +import 'package:cake_wallet/src/domain/exchange/changenow/changenow_request.dart';
18 +import 'package:cake_wallet/src/domain/exchange/trade_request.dart';
19 +import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_exchange_provider.dart';
20 +import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_trade_request.dart';
21 +import 'package:cake_wallet/src/domain/exchange/morphtoken/morphtoken_exchange_provider.dart';
22 +import 'package:cake_wallet/src/domain/exchange/morphtoken/morphtoken_request.dart';
23 +import 'package:cake_wallet/store/templates/exchange_template_store.dart';
24 +import 'package:cake_wallet/src/domain/exchange/exchange_template.dart';
25 +
26 +part 'exchange_view_model.g.dart';
27 +
28 +class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel;
29 +
30 +abstract class ExchangeViewModelBase with Store {
31 + ExchangeViewModelBase({this.wallet, this.trades, this.exchangeTemplateStore}) {
32 + providerList = [
33 + XMRTOExchangeProvider(),
34 + ChangeNowExchangeProvider(),
35 + MorphTokenExchangeProvider(trades: trades)
36 + ];
37 +
38 + provider = providerList[ 0 ];
39 +
40 + depositCurrency = CryptoCurrency.xmr;
41 + receiveCurrency = CryptoCurrency.btc;
42 + isDepositAddressEnabled = !(depositCurrency == wallet.currency);
43 + isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
44 + depositAmount = '';
45 + receiveAmount = '';
46 + depositAddress = '';
47 + receiveAddress = '';
48 + limitsState = LimitsInitialState();
49 + tradeState = ExchangeTradeStateInitial();
50 + _cryptoNumberFormat = NumberFormat()..maximumFractionDigits = 12;
51 + loadLimits();
52 + }
53 +
54 + final WalletBase wallet;
55 + final Box<Trade> trades;
56 + final ExchangeTemplateStore exchangeTemplateStore;
57 +
58 + @observable
59 + ExchangeProvider provider;
60 +
61 + @observable
62 + List<ExchangeProvider> providerList;
63 +
64 + @observable
65 + CryptoCurrency depositCurrency;
66 +
67 + @observable
68 + CryptoCurrency receiveCurrency;
69 +
70 + @observable
71 + LimitsState limitsState;
72 +
73 + @observable
74 + ExchangeTradeState tradeState;
75 +
76 + @observable
77 + String depositAmount;
78 +
79 + @observable
80 + String receiveAmount;
81 +
82 + @observable
83 + String depositAddress;
84 +
85 + @observable
86 + String receiveAddress;
87 +
88 + @observable
89 + bool isDepositAddressEnabled;
90 +
91 + @observable
92 + bool isReceiveAddressEnabled;
93 +
94 + @observable
95 + bool isValid;
96 +
97 + @observable
98 + String errorMessage;
99 +
100 + Limits limits;
101 +
102 + NumberFormat _cryptoNumberFormat;
103 +
104 + Validator get amountValidator => AmountValidator(type: wallet.type);
105 +
106 + Validator get addressValidator => AddressValidator(type: wallet.currency);
107 +
108 + Validator get templateValidator => TemplateValidator();
109 +
110 + @computed
111 + ObservableList<ExchangeTemplate> get templates =>
112 + exchangeTemplateStore.templates;
113 +
114 + @action
115 + void changeProvider({ExchangeProvider provider}) {
116 + this.provider = provider;
117 + depositAmount = '';
118 + receiveAmount = '';
119 + loadLimits();
120 + }
121 +
122 + @action
123 + void changeDepositCurrency({CryptoCurrency currency}) {
124 + depositCurrency = currency;
125 + _onPairChange();
126 + isDepositAddressEnabled = !(depositCurrency == wallet.currency);
127 + isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
128 + }
129 +
130 + @action
131 + void changeReceiveCurrency({CryptoCurrency currency}) {
132 + receiveCurrency = currency;
133 + _onPairChange();
134 + isDepositAddressEnabled = !(depositCurrency == wallet.currency);
135 + isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
136 + }
137 +
138 + @action
139 + void changeReceiveAmount({String amount}) {
140 + receiveAmount = amount;
141 +
142 + if (amount == null || amount.isEmpty) {
143 + depositAmount = '';
144 + receiveAmount = '';
145 + return;
146 + }
147 +
148 + final _amount = double.parse(amount) ?? 0;
149 +
150 + provider
151 + .calculateAmount(
152 + from: depositCurrency, to: receiveCurrency, amount: _amount)
153 + .then((amount) => _cryptoNumberFormat.format(amount).toString().replaceAll(RegExp("\\,"), ""))
154 + .then((amount) => depositAmount = amount);
155 + }
156 +
157 + @action
158 + void changeDepositAmount({String amount}) {
159 + depositAmount = amount;
160 +
161 + if (amount == null || amount.isEmpty) {
162 + depositAmount = '';
163 + receiveAmount = '';
164 + return;
165 + }
166 +
167 + final _amount = double.parse(amount);
168 + provider
169 + .calculateAmount(
170 + from: depositCurrency, to: receiveCurrency, amount: _amount)
171 + .then((amount) => _cryptoNumberFormat.format(amount).toString().replaceAll(RegExp("\\,"), ""))
172 + .then((amount) => receiveAmount = amount);
173 + }
174 +
175 + @action
176 + Future loadLimits() async {
177 + limitsState = LimitsIsLoading();
178 +
179 + try {
180 + limits = await provider.fetchLimits(
181 + from: depositCurrency, to: receiveCurrency);
182 + limitsState = LimitsLoadedSuccessfully(limits: limits);
183 + } catch (e) {
184 + limitsState = LimitsLoadedFailure(error: e.toString());
185 + }
186 + }
187 +
188 + @action
189 + Future createTrade() async {
190 + TradeRequest request;
191 + String amount;
192 + CryptoCurrency currency;
193 +
194 + if (provider is XMRTOExchangeProvider) {
195 + request = XMRTOTradeRequest(
196 + from: depositCurrency,
197 + to: receiveCurrency,
198 + amount: depositAmount,
199 + address: receiveAddress,
200 + refundAddress: depositAddress);
201 + amount = depositAmount;
202 + currency = depositCurrency;
203 + }
204 +
205 + if (provider is ChangeNowExchangeProvider) {
206 + request = ChangeNowRequest(
207 + from: depositCurrency,
208 + to: receiveCurrency,
209 + amount: depositAmount,
210 + refundAddress: depositAddress,
211 + address: receiveAddress);
212 + amount = depositAmount;
213 + currency = depositCurrency;
214 + }
215 +
216 + if (provider is MorphTokenExchangeProvider) {
217 + request = MorphTokenRequest(
218 + from: depositCurrency,
219 + to: receiveCurrency,
220 + amount: depositAmount,
221 + refundAddress: depositAddress,
222 + address: receiveAddress);
223 + amount = depositAmount;
224 + currency = depositCurrency;
225 + }
226 +
227 + if (limitsState is LimitsLoadedSuccessfully && amount != null) {
228 + if (double.parse(amount) < limits.min) {
229 + tradeState = TradeIsCreatedFailure(error: S.current.error_text_minimal_limit('${provider.description}',
230 + '${limits.min}', currency.toString()));
231 + } else if (limits.max != null && double.parse(amount) > limits.max) {
232 + tradeState = TradeIsCreatedFailure(error: S.current.error_text_maximum_limit('${provider.description}',
233 + '${limits.max}', currency.toString()));
234 + } else {
235 + try {
236 + tradeState = TradeIsCreating();
237 + final trade = await provider.createTrade(request: request);
238 + trade.walletId = wallet.id;
239 + await trades.add(trade);
240 + tradeState = TradeIsCreatedSuccessfully(trade: trade);
241 + } catch (e) {
242 + tradeState = TradeIsCreatedFailure(error: e.toString());
243 + }
244 + }
245 + } else {
246 + tradeState = TradeIsCreatedFailure(error: S.current.error_text_limits_loading_failed("${provider.description}"));
247 + }
248 +
249 + }
250 +
251 + @action
252 + void reset() {
253 + depositAmount = '';
254 + receiveAmount = '';
255 + depositCurrency = CryptoCurrency.xmr;
256 + receiveCurrency = CryptoCurrency.btc;
257 + depositAddress = depositCurrency == wallet.currency ? wallet.address : '';
258 + receiveAddress = receiveCurrency == wallet.currency ? wallet.address : '';
259 + isDepositAddressEnabled = !(depositCurrency == wallet.currency);
260 + isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
261 + _onPairChange();
262 + }
263 +
264 + List<ExchangeProvider> providersForCurrentPair() {
265 + return _providersForPair(from: depositCurrency, to: receiveCurrency);
266 + }
267 +
268 + List<ExchangeProvider> _providersForPair(
269 + {CryptoCurrency from, CryptoCurrency to}) {
270 + final providers = providerList
271 + .where((provider) => provider.pairList
272 + .where((pair) =>
273 + pair.from == depositCurrency && pair.to == receiveCurrency)
274 + .isNotEmpty)
275 + .toList();
276 +
277 + return providers;
278 + }
279 +
280 + void _onPairChange() {
281 + final isPairExist = provider.pairList
282 + .where((pair) =>
283 + pair.from == depositCurrency && pair.to == receiveCurrency)
284 + .isNotEmpty;
285 +
286 + if (!isPairExist) {
287 + final provider =
288 + _providerForPair(from: depositCurrency, to: receiveCurrency);
289 +
290 + if (provider != null) {
291 + changeProvider(provider: provider);
292 + }
293 + }
294 +
295 + depositAmount = '';
296 + receiveAmount = '';
297 +
298 + loadLimits();
299 + }
300 +
301 + ExchangeProvider _providerForPair({CryptoCurrency from, CryptoCurrency to}) {
302 + final providers = _providersForPair(from: from, to: to);
303 + return providers.isNotEmpty ? providers[0] : null;
304 + }
305 +
306 +}
\ No newline at end of file
res/values/strings_de.arb
+1
@@ -61,6 +61,7 @@
61
62 "exchange" : "Austausch",
63 "clear" : "klar",
64 + "refund_address" : "Rückerstattungsadresse",
65 "change_exchange_provider" : "Wechseln Sie den Exchange-Anbieter",
66 "you_will_send" : "Du wirst senden",
67 "you_will_get" : "Sie erhalten",
res/values/strings_en.arb
+1
@@ -61,6 +61,7 @@
61
62 "exchange" : "Exchange",
63 "clear" : "Clear",
64 + "refund_address" : "Refund address",
65 "change_exchange_provider" : "Change Exchange Provider",
66 "you_will_send" : "You will send",
67 "you_will_get" : "You will get",
res/values/strings_es.arb
+1
@@ -61,6 +61,7 @@
61
62 "exchange" : "Intercambiar",
63 "clear" : "Claro",
64 + "refund_address" : "Dirección de reembolso",
65 "change_exchange_provider" : "Cambiar proveedor de intercambio",
66 "you_will_send" : "Enviarás",
67 "you_will_get" : "Conseguirás",
res/values/strings_hi.arb
+1
@@ -61,6 +61,7 @@
61
62 "exchange" : "अदला बदली",
63 "clear" : "स्पष्ट",
64 + "refund_address" : "वापसी का पता",
65 "change_exchange_provider" : "एक्सचेंज प्रदाता बदलें",
66 "you_will_send" : "तुम भेजोगे",
67 "you_will_get" : "आपको मिल जायेगा",
res/values/strings_ja.arb
+1
@@ -61,6 +61,7 @@
61
62 "exchange" : "交換する",
63 "clear" : "クリア",
64 + "refund_address" : "払い戻し住所",
65 "change_exchange_provider" : "Exchangeプロバイダーの変更",
66 "you_will_send" : "送ります",
67 "you_will_get" : "あなたが取得します",
res/values/strings_ko.arb
+1
@@ -61,6 +61,7 @@
61
62 "exchange" : "교환",
63 "clear" : "명확한",
64 + "refund_address" : "환불 주소",
65 "change_exchange_provider" : "교환 공급자 변경",
66 "you_will_send" : "보내드립니다",
67 "you_will_get" : "당신은 얻을 것이다",
res/values/strings_nl.arb
+1
@@ -61,6 +61,7 @@
61
62 "exchange" : "Uitwisseling",
63 "clear" : "Duidelijk",
64 + "refund_address" : "Adres voor terugbetaling",
65 "change_exchange_provider" : "Wijzig Exchange Provider",
66 "you_will_send" : "Je zal versturen",
67 "you_will_get" : "Je zult krijgen",
res/values/strings_pl.arb
+1
@@ -61,6 +61,7 @@
61
62 "exchange" : "Wymieniać się",
63 "clear" : "Wyczyść",
64 + "refund_address" : "Adres zwrotu",
65 "change_exchange_provider" : "Zmień dostawcę programu Exchange",
66 "you_will_send" : "Wyślesz",
67 "you_will_get" : "Dostaniesz",
res/values/strings_pt.arb
+1
@@ -61,6 +61,7 @@
61
62 "exchange" : "Trocar",
63 "clear" : "Limpar",
64 + "refund_address" : "Endereço de reembolso",
65 "change_exchange_provider" : "Alterar o provedor de troca",
66 "you_will_send" : "Você enviará",
67 "you_will_get" : "Você receberá",
res/values/strings_ru.arb
+1
@@ -61,6 +61,7 @@
61
62 "exchange" : "Обмен",
63 "clear" : "Очистить",
64 + "refund_address" : "Адрес возврата",
65 "change_exchange_provider" : "Изменить провайдера обмена",
66 "you_will_send" : "Вы отправите",
67 "you_will_get" : "Вы получите",
res/values/strings_uk.arb
+1
@@ -61,6 +61,7 @@
61
62 "exchange" : "Обмін",
63 "clear" : "Очистити",
64 + "refund_address" : "Адреса повернення коштів",
65 "change_exchange_provider" : "Змінити провайдера обміну",
66 "you_will_send" : "Ви відправите",
67 "you_will_get" : "Ви отримаєте",
res/values/strings_zh.arb
+1
@@ -61,6 +61,7 @@
61
62 "exchange" : "交换",
63 "clear" : "明确",
64 + "refund_address" : "退款地址",
65 "change_exchange_provider" : "更改交易所提供商",
66 "you_will_send" : "您将发送",
67 "you_will_get" : "你会得到",