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

This commit is contained in:
Oleksandr Sobol 2020-07-31 18:29:21 +03:00
parent fe9deecd03
commit 33209c3cd6
56 changed files with 856 additions and 524 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 939 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 735 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

BIN
assets/images/eye_menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 B

BIN
assets/images/key_menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

View file

@ -3,10 +3,11 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
class AmountValidator extends TextValidator {
AmountValidator({WalletType type})
AmountValidator({WalletType type, bool isAutovalidate = false})
: super(
errorMessage: S.current.error_text_amount,
pattern: _pattern(type),
isAutovalidate: isAutovalidate,
minLength: 0,
maxLength: 0);

View file

@ -16,18 +16,20 @@ class TextValidator extends Validator<String> {
this.maxLength,
this.pattern,
this.length,
this.isAutovalidate = false,
String errorMessage})
: super(errorMessage: errorMessage);
final int minLength;
final int maxLength;
final List<int> length;
final bool isAutovalidate;
String pattern;
@override
bool isValid(String value) {
if (value == null || value.isEmpty) {
return false;
return isAutovalidate ? true : false;
}
return value.length > (minLength ?? 0) &&

View file

@ -10,6 +10,8 @@ import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
import 'package:cake_wallet/src/screens/send/send_template_page.dart';
import 'package:cake_wallet/src/screens/settings/settings.dart';
import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
import 'package:cake_wallet/store/contact_list_store.dart';
import 'package:cake_wallet/store/node_list_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
@ -42,6 +44,7 @@ import 'package:cake_wallet/view_model/settings/settings_view_model.dart';
import 'package:cake_wallet/view_model/wallet_keys_view_model.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
import 'package:flutter/foundation.dart';
import 'package:get_it/get_it.dart';
import 'package:hive/hive.dart';
@ -299,4 +302,17 @@ Future setup(
getIt.registerFactory(
() => NodeCreateOrEditPage(getIt.get<NodeCreateOrEditViewModel>()));
getIt.registerFactory(() =>
ExchangeViewModel(
wallet: getIt.get<AppStore>().wallet,
exchangeTemplateStore: getIt.get<ExchangeTemplateStore>(),
trades: tradesSource
));
getIt.registerFactory(() =>
ExchangePage(getIt.get<ExchangeViewModel>()));
getIt.registerFactory(() =>
ExchangeTemplatePage(getIt.get<ExchangeViewModel>()));
}

View file

@ -135,6 +135,7 @@ class S implements WidgetsLocalizations {
String get reconnect => "Reconnect";
String get reconnect_alert_text => "Are you sure to reconnect?";
String get reconnection => "Reconnection";
String get refund_address => "Refund address";
String get remove => "Remove";
String get remove_node => "Remove node";
String get remove_node_message => "Are you sure that you want to remove selected node?";
@ -372,6 +373,8 @@ class $de extends S {
@override
String get trade_state_underpaid => "Unterbezahlt";
@override
String get refund_address => "Rückerstattungsadresse";
@override
String get welcome => "Willkommen zu";
@override
String get share_address => "Adresse teilen ";
@ -998,6 +1001,8 @@ class $hi extends S {
@override
String get trade_state_underpaid => "के तहत भुगतान किया";
@override
String get refund_address => "वापसी का पता";
@override
String get welcome => "स्वागत हे सेवा मेरे";
@override
String get share_address => "पता साझा करें";
@ -1624,6 +1629,8 @@ class $ru extends S {
@override
String get trade_state_underpaid => "Недоплаченная";
@override
String get refund_address => "Адрес возврата";
@override
String get welcome => "Приветствуем в";
@override
String get share_address => "Поделиться адресом";
@ -2250,6 +2257,8 @@ class $ko extends S {
@override
String get trade_state_underpaid => "미지급";
@override
String get refund_address => "환불 주소";
@override
String get welcome => "환영 에";
@override
String get share_address => "주소 공유";
@ -2876,6 +2885,8 @@ class $pt extends S {
@override
String get trade_state_underpaid => "Parcialmente paga";
@override
String get refund_address => "Endereço de reembolso";
@override
String get welcome => "Bem-vindo ao";
@override
String get share_address => "Compartilhar endereço";
@ -3502,6 +3513,8 @@ class $uk extends S {
@override
String get trade_state_underpaid => "Недоплачена";
@override
String get refund_address => "Адреса повернення коштів";
@override
String get welcome => "Вітаємо в";
@override
String get share_address => "Поділитися адресою";
@ -4128,6 +4141,8 @@ class $ja extends S {
@override
String get trade_state_underpaid => "支払不足";
@override
String get refund_address => "払い戻し住所";
@override
String get welcome => "ようこそ に";
@override
String get share_address => "住所を共有する";
@ -4758,6 +4773,8 @@ class $pl extends S {
@override
String get trade_state_underpaid => "Niedopłacone";
@override
String get refund_address => "Adres zwrotu";
@override
String get welcome => "Witamy w";
@override
String get share_address => "Udostępnij adres";
@ -5384,6 +5401,8 @@ class $es extends S {
@override
String get trade_state_underpaid => "Poco pagado";
@override
String get refund_address => "Dirección de reembolso";
@override
String get welcome => "Bienvenido";
@override
String get share_address => "Compartir dirección";
@ -6010,6 +6029,8 @@ class $nl extends S {
@override
String get trade_state_underpaid => "Slecht betaald";
@override
String get refund_address => "Adres voor terugbetaling";
@override
String get welcome => "Welkom bij";
@override
String get share_address => "Deel adres";
@ -6636,6 +6657,8 @@ class $zh extends S {
@override
String get trade_state_underpaid => "支付不足";
@override
String get refund_address => "退款地址";
@override
String get welcome => "歡迎來到";
@override
String get share_address => "分享地址";

View file

@ -23,8 +23,8 @@ class PaletteDark {
static const Color lightDistantBlue = Color.fromRGBO(81, 96, 147, 1.0); // borderCardColor
static const Color gray = Color.fromRGBO(140, 153, 201, 1.0); // walletCardText
static const Color violetBlue = Color.fromRGBO(51, 63, 104, 1.0); // walletCardAddressField
static const Color moderateBlue = Color.fromRGBO(63, 77, 122, 1.0); // walletCardSubAddressField
static const Color darkNightBlue = Color.fromRGBO(33, 43, 73, 1.0); // historyPanel
//static const Color moderateBlue = Color.fromRGBO(63, 77, 122, 1.0); // walletCardSubAddressField
//static const Color darkNightBlue = Color.fromRGBO(33, 43, 73, 1.0); // historyPanel
static const Color pigeonBlue = Color.fromRGBO(91, 112, 146, 1.0); // historyPanelText
static const Color moderateNightBlue = Color.fromRGBO(39, 53, 96, 1.0); // historyPanelButton
static const Color headerNightBlue = Color.fromRGBO(41, 52, 84, 1.0); // menuHeader
@ -46,6 +46,12 @@ class PaletteDark {
static const Color lightBlueGrey = Color.fromRGBO(125, 141, 183, 1.0);
static const Color lightVioletBlue = Color.fromRGBO(56, 71, 109, 1.0);
static const Color darkVioletBlue = Color.fromRGBO(49, 60, 96, 1.0);
static const Color wildVioletBlue = Color.fromRGBO(45, 60, 97, 1.0);
static const Color darkNightBlue = Color.fromRGBO(33, 45, 76, 1.0);
static const Color blueGrey = Color.fromRGBO(87, 98, 138, 1.0);
static const Color moderateBlue = Color.fromRGBO(60, 73, 118, 1.0);
static const Color deepPurpleBlue = Color.fromRGBO(19, 29, 56, 1.0);
static const Color lightOceanBlue = Color.fromRGBO(30, 42, 73, 1.0);
// FIXME: Rename.
static const Color eee = Color.fromRGBO(236, 239, 245, 1.0);

View file

@ -411,45 +411,12 @@ class Router {
walletRestorationFromSeedVM: walletRestorationFromSeedVM));
case Routes.exchange:
return MaterialPageRoute<void>(
builder: (_) => MultiProvider(providers: [
Provider(create: (_) {
final xmrtoprovider = XMRTOExchangeProvider();
return ExchangeStore(
initialProvider: xmrtoprovider,
initialDepositCurrency: CryptoCurrency.xmr,
initialReceiveCurrency: CryptoCurrency.btc,
trades: trades,
providerList: [
xmrtoprovider,
ChangeNowExchangeProvider(),
MorphTokenExchangeProvider(trades: trades)
],
walletStore: walletStore);
}),
], child: ExchangePage()));
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<ExchangePage>());
case Routes.exchangeTemplate:
return MaterialPageRoute<void>(
builder: (_) => Provider(
create: (_) {
final xmrtoprovider = XMRTOExchangeProvider();
return ExchangeStore(
initialProvider: xmrtoprovider,
initialDepositCurrency: CryptoCurrency.xmr,
initialReceiveCurrency: CryptoCurrency.btc,
trades: trades,
providerList: [
xmrtoprovider,
ChangeNowExchangeProvider(),
MorphTokenExchangeProvider(trades: trades)
],
walletStore: walletStore);
},
child: ExchangeTemplatePage(),
));
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<ExchangeTemplatePage>());
case Routes.settings:
return MaterialPageRoute<void>(

View file

@ -19,10 +19,14 @@ abstract class BasePage extends StatelessWidget {
bool get resizeToAvoidBottomPadding => true;
Widget get endDrawer => null;
AppBarStyle get appBarStyle => AppBarStyle.regular;
Widget Function(BuildContext, Widget) get rootWrapper => null;
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final _backArrowImage = Image.asset('assets/images/back_arrow.png',
color: Colors.white);
final _backArrowImageDarkTheme =
@ -32,6 +36,8 @@ abstract class BasePage extends StatelessWidget {
final _closeButtonImageDarkTheme =
Image.asset('assets/images/close_button_dark_theme.png');
void onOpenEndDrawer() => _scaffoldKey.currentState.openEndDrawer();
void onClose(BuildContext context) => Navigator.of(context).pop();
Widget leading(BuildContext context) {
@ -123,9 +129,11 @@ abstract class BasePage extends StatelessWidget {
final _isDarkTheme = _themeChanger.getTheme() == Themes.darkTheme;
final root = Scaffold(
key: _scaffoldKey,
backgroundColor:
_isDarkTheme ? backgroundDarkColor : backgroundLightColor,
resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
endDrawer: endDrawer,
appBar: appBar(context),
body: body(context), //SafeArea(child: ),
floatingActionButton: floatingActionButton(context));

View file

@ -23,6 +23,12 @@ class DashboardPage extends BasePage {
@override
Color get backgroundDarkColor => PaletteDark.backgroundColor;
@override
Widget get endDrawer => MenuWidget(
name: walletViewModel.name,
subname: walletViewModel.subname,
type: walletViewModel.type);
@override
Widget middle(BuildContext context) {
return SyncIndicator(dashboardViewModel: walletViewModel);
@ -40,14 +46,7 @@ class DashboardPage extends BasePage {
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
padding: EdgeInsets.all(0),
onPressed: () async {
await showDialog<void>(
builder: (_) => MenuWidget(
name: walletViewModel.name,
subname: walletViewModel.subname,
type: walletViewModel.type),
context: context);
},
onPressed: () => onOpenEndDrawer(),
child: menuButton
)
);

View file

@ -20,13 +20,13 @@ class WalletMenu {
];
final List<Image> images = [
Image.asset('assets/images/reconnect.png'),
Image.asset('assets/images/wallet.png'),
Image.asset('assets/images/nodes.png'),
Image.asset('assets/images/eye.png'),
Image.asset('assets/images/key.png'),
Image.asset('assets/images/open_book.png'),
Image.asset('assets/images/settings.png'),
Image.asset('assets/images/reconnect_menu.png', height: 16, width: 16),
Image.asset('assets/images/wallet_menu.png', height: 16, width: 16),
Image.asset('assets/images/nodes_menu.png', height: 16, width: 16),
Image.asset('assets/images/eye_menu.png', height: 16, width: 16),
Image.asset('assets/images/key_menu.png', height: 16, width: 16),
Image.asset('assets/images/open_book_menu.png', height: 16, width: 16),
Image.asset('assets/images/settings_menu.png', height: 16, width: 16),
];
final BuildContext context;

View file

@ -1,4 +1,5 @@
import 'dart:ui';
import 'package:cake_wallet/palette.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
import 'package:cake_wallet/src/screens/dashboard/wallet_menu.dart';
@ -15,7 +16,7 @@ class MenuWidget extends StatefulWidget {
}
class MenuWidgetState extends State<MenuWidget> {
final moneroIcon = Image.asset('assets/images/monero.png');
final moneroIcon = Image.asset('assets/images/monero_menu.png');
final bitcoinIcon = Image.asset('assets/images/bitcoin.png');
final largeScreen = 731;
@ -36,10 +37,10 @@ class MenuWidgetState extends State<MenuWidget> {
screenHeight = 0;
opacity = 0;
headerHeight = 120;
headerHeight = 125;
tileHeight = 75;
fromTopEdge = 50;
fromBottomEdge = 30;
fromBottomEdge = 21;//30;
super.initState();
WidgetsBinding.instance.addPostFrameCallback(afterLayout);
@ -66,189 +67,139 @@ class MenuWidgetState extends State<MenuWidget> {
@override
Widget build(BuildContext context) {
final walletMenu = WalletMenu(context);
// final walletStore = Provider.of<WalletStore>(context);
final itemCount = walletMenu.items.length;
return Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
return SafeArea(
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 24),
child: Container(
height: 60,
width: 4,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(2)),
color: Theme.of(context).hintColor),
borderRadius: BorderRadius.all(Radius.circular(2)),
color: Theme.of(context).hintColor),
)),
SizedBox(width: 12),
Expanded(
child: GestureDetector(
onTap: () => null,
child: Container(
width: menuWidth,
height: double.infinity,
decoration: BoxDecoration(
SizedBox(width: 12),
Expanded(
child: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(24),
bottomLeft: Radius.circular(24)),
color: Theme.of(context).primaryTextTheme.display1.color),
child: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(24),
bottomLeft: Radius.circular(24)),
child: ListView.separated(
itemBuilder: (_, index) {
if (index == 0) {
return Container(
height: headerHeight,
padding: EdgeInsets.only(
left: 24,
top: fromTopEdge,
right: 24,
bottom: fromBottomEdge),
decoration: BoxDecoration(
borderRadius:
BorderRadius.only(topLeft: Radius.circular(24)),
color: Theme.of(context)
.primaryTextTheme
.display2
.color),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
_iconFor(type: widget.type),
SizedBox(width: 16),
Expanded(
child: Container(
height: 40,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: widget.subname != null
? MainAxisAlignment.spaceBetween
: MainAxisAlignment.center,
children: <Widget>[
Text(
widget.name,
style: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.title
.color,
decoration: TextDecoration.none,
fontFamily: 'Avenir Next',
fontSize: 20,
fontWeight: FontWeight.bold),
),
if (widget.subname != null)
Text(
widget.subname,
style: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.caption
.color,
decoration: TextDecoration.none,
fontFamily: 'Avenir Next',
fontSize: 12),
)
],
),
))
],
child: Container(
width: menuWidth,
height: double.infinity,
color: PaletteDark.deepPurpleBlue,
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Container(
height: headerHeight,
padding: EdgeInsets.only(
left: 24,
top: fromTopEdge,
right: 24,
bottom: fromBottomEdge),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
_iconFor(type: widget.type),
SizedBox(width: 12),
Expanded(
child: Container(
height: 42,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: widget.subname != null
? MainAxisAlignment.spaceBetween
: MainAxisAlignment.center,
children: <Widget>[
Text(
widget.name,
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold),
),
if (widget.subname != null)
Text(
widget.subname,
style: TextStyle(
color: PaletteDark.darkCyanBlue,
fontWeight: FontWeight.w500,
fontSize: 12),
)
],
),
))
],
),
),
);
}
Container(
height: 1,
color: PaletteDark.lightOceanBlue,
),
ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (_, index) {
index -= 1;
final item = walletMenu.items[index];
final image = walletMenu.images[index] ?? Offstage();
final item = walletMenu.items[index];
final image = walletMenu.images[index] ?? Offstage();
final isLastTile = index == itemCount - 1;
return GestureDetector(
onTap: () {
Navigator.of(context).pop();
walletMenu.action(index);
},
child: index == itemCount - 1
? Container(
height: headerHeight,
padding: EdgeInsets.only(
left: 24,
right: 24,
top: fromBottomEdge,
bottom: fromTopEdge),
alignment: Alignment.topLeft,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24)),
color: Theme.of(context)
.primaryTextTheme
.display1
.color,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
image,
SizedBox(width: 16),
Expanded(
child: Text(
item,
style: TextStyle(
decoration: TextDecoration.none,
color: Theme.of(context)
.primaryTextTheme
.title
.color,
fontFamily: 'Avenir Next',
fontSize: 20,
fontWeight: FontWeight.bold),
))
],
),
)
: Container(
height: tileHeight,
padding: EdgeInsets.only(left: 24, right: 24),
color: Theme.of(context)
.primaryTextTheme
.display1
.color,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
image,
SizedBox(width: 16),
Expanded(
child: Text(
item,
style: TextStyle(
decoration: TextDecoration.none,
color: Theme.of(context)
.primaryTextTheme
.title
.color,
fontFamily: 'Avenir Next',
fontSize: 20,
fontWeight: FontWeight.bold),
))
],
),
return GestureDetector(
onTap: () {
Navigator.of(context).pop();
walletMenu.action(index);
},
child: Container(
height: isLastTile
? headerHeight
: tileHeight,
padding: isLastTile
? EdgeInsets.only(
left: 24,
right: 24,
top: fromBottomEdge,
bottom: fromTopEdge)
: EdgeInsets.only(left: 24, right: 24),
alignment: isLastTile ? Alignment.topLeft : null,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
image,
SizedBox(width: 16),
Expanded(
child: Text(
item,
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold),
))
],
),
)
);
},
separatorBuilder: (_, index) => Container(
height: 1,
color: PaletteDark.lightOceanBlue,
),
);
},
separatorBuilder: (_, index) => Container(
height: 1,
color: Theme.of(context).dividerColor,
),
itemCount: itemCount + 1),
),
),
))
],
itemCount: itemCount)
],
),
),
),
)
)
],
)
);
}

View file

@ -1,66 +1,40 @@
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
import 'package:cake_wallet/src/stores/exchange/exchange_store.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/stores/exchange_template/exchange_template_store.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/base_exchange_widget.dart';
import 'package:cake_wallet/src/widgets/trail_button.dart';
import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
class ExchangePage extends BasePage {
ExchangePage(this.exchangeViewModel);
final ExchangeViewModel exchangeViewModel;
@override
String get title => S.current.exchange;
@override
Color get backgroundLightColor => Palette.darkLavender;
Color get backgroundLightColor => PaletteDark.wildVioletBlue;
@override
Color get backgroundDarkColor => PaletteDark.moderateBlue;
Color get backgroundDarkColor => PaletteDark.wildVioletBlue;
@override
Widget middle(BuildContext context) {
final exchangeStore = Provider.of<ExchangeStore>(context);
return PresentProviderPicker(exchangeStore: exchangeStore);
}
Widget middle(BuildContext context) =>
PresentProviderPicker(exchangeViewModel: exchangeViewModel);
@override
Widget trailing(BuildContext context) {
final exchangeStore = Provider.of<ExchangeStore>(context);
return TrailButton(
caption: S.of(context).reset,
onPressed: () => exchangeStore.reset()
);
}
Widget trailing(BuildContext context) =>
TrailButton(
caption: S.of(context).reset,
onPressed: () => exchangeViewModel.reset()
);
@override
Widget body(BuildContext context) => ExchangeForm();
}
class ExchangeForm extends StatefulWidget {
@override
State<StatefulWidget> createState() => ExchangeFormState();
}
class ExchangeFormState extends State<ExchangeForm> {
@override
Widget build(BuildContext context) {
final exchangeStore = Provider.of<ExchangeStore>(context);
final walletStore = Provider.of<WalletStore>(context);
final exchangeTemplateStore = Provider.of<ExchangeTemplateStore>(context);
return BaseExchangeWidget(
exchangeStore: exchangeStore,
walletStore: walletStore,
exchangeTemplateStore: exchangeTemplateStore,
isTemplate: false
);
}
Widget body(BuildContext context) =>
BaseExchangeWidget(exchangeViewModel: exchangeViewModel);
}

View file

@ -1,55 +1,32 @@
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
import 'package:cake_wallet/src/stores/exchange/exchange_store.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker.dart';
import 'package:cake_wallet/src/stores/exchange_template/exchange_template_store.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/base_exchange_widget.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
class ExchangeTemplatePage extends BasePage {
ExchangeTemplatePage(this.exchangeViewModel);
final ExchangeViewModel exchangeViewModel;
@override
String get title => S.current.exchange_new_template;
@override
Color get backgroundLightColor => Palette.darkLavender;
Color get backgroundLightColor => PaletteDark.wildVioletBlue;
@override
Color get backgroundDarkColor => PaletteDark.moderateBlue;
Color get backgroundDarkColor => PaletteDark.wildVioletBlue;
@override
Widget trailing(BuildContext context) {
final exchangeStore = Provider.of<ExchangeStore>(context);
return PresentProviderPicker(exchangeStore: exchangeStore);
}
Widget trailing(BuildContext context) =>
PresentProviderPicker(exchangeViewModel: exchangeViewModel);
@override
Widget body(BuildContext context) => ExchangeTemplateForm();
}
class ExchangeTemplateForm extends StatefulWidget{
@override
ExchangeTemplateFormState createState() => ExchangeTemplateFormState();
}
class ExchangeTemplateFormState extends State<ExchangeTemplateForm> {
@override
Widget build(BuildContext context) {
final exchangeStore = Provider.of<ExchangeStore>(context);
final walletStore = Provider.of<WalletStore>(context);
final exchangeTemplateStore = Provider.of<ExchangeTemplateStore>(context);
return BaseExchangeWidget(
exchangeStore: exchangeStore,
walletStore: walletStore,
exchangeTemplateStore: exchangeTemplateStore,
isTemplate: true
);
}
Widget body(BuildContext context) =>
BaseExchangeWidget(exchangeViewModel: exchangeViewModel, isTemplate: true);
}

View file

@ -1,5 +1,7 @@
import 'dart:ui';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/domain/exchange/exchange_template.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/template_tile.dart';
import 'package:dotted_border/dotted_border.dart';
import 'package:flutter/cupertino.dart';
@ -12,49 +14,37 @@ import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_exchange_provider.dart';
import 'package:cake_wallet/src/stores/exchange/exchange_trade_state.dart';
import 'package:cake_wallet/src/stores/exchange/limits_state.dart';
import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
import 'package:cake_wallet/src/stores/exchange/exchange_store.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/src/widgets/top_panel.dart';
import 'package:cake_wallet/src/stores/exchange_template/exchange_template_store.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
class BaseExchangeWidget extends StatefulWidget {
BaseExchangeWidget({
@ required this.exchangeStore,
@ required this.walletStore,
@ required this.exchangeTemplateStore,
@ required this.isTemplate,
@ required this.exchangeViewModel,
this.isTemplate = false,
});
final ExchangeStore exchangeStore;
final WalletStore walletStore;
final ExchangeTemplateStore exchangeTemplateStore;
final ExchangeViewModel exchangeViewModel;
final bool isTemplate;
@override
BaseExchangeWidgetState createState() =>
BaseExchangeWidgetState(
exchangeStore: exchangeStore,
walletStore: walletStore,
exchangeTemplateStore: exchangeTemplateStore,
exchangeViewModel: exchangeViewModel,
isTemplate: isTemplate
);
}
class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
BaseExchangeWidgetState({
@ required this.exchangeStore,
@ required this.walletStore,
@ required this.exchangeTemplateStore,
@ required this.exchangeViewModel,
@ required this.isTemplate,
});
final ExchangeStore exchangeStore;
final WalletStore walletStore;
final ExchangeTemplateStore exchangeTemplateStore;
final ExchangeViewModel exchangeViewModel;
final bool isTemplate;
final depositKey = GlobalKey<ExchangeCardState>();
@ -64,31 +54,31 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
@override
Widget build(BuildContext context) {
final Image arrowBottomPurple = Image.asset(
final arrowBottomPurple = Image.asset(
'assets/images/arrow_bottom_purple_icon.png',
color: Theme.of(context).primaryTextTheme.title.color,
color: Colors.white,
height: 8,
);
final Image arrowBottomCakeGreen = Image.asset(
final arrowBottomCakeGreen = Image.asset(
'assets/images/arrow_bottom_cake_green.png',
color: Theme.of(context).primaryTextTheme.title.color,
color: Colors.white,
height: 8,
);
final depositWalletName =
exchangeStore.depositCurrency == CryptoCurrency.xmr
? walletStore.name
exchangeViewModel.depositCurrency == CryptoCurrency.xmr
? exchangeViewModel.wallet.name
: null;
final receiveWalletName =
exchangeStore.receiveCurrency == CryptoCurrency.xmr
? walletStore.name
exchangeViewModel.receiveCurrency == CryptoCurrency.xmr
? exchangeViewModel.wallet.name
: null;
WidgetsBinding.instance.addPostFrameCallback(
(_) => _setReactions(context, exchangeStore, walletStore));
(_) => _setReactions(context, exchangeViewModel));
return Container(
color: Theme.of(context).backgroundColor,
color: PaletteDark.backgroundColor,
child: Form(
key: _formKey,
child: ScrollableWithBottomSection(
@ -96,73 +86,60 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
content: Column(
children: <Widget>[
TopPanel(
color: Theme.of(context).accentTextTheme.title.backgroundColor,
edgeInsets: EdgeInsets.only(bottom: 24),
color: PaletteDark.darkNightBlue,
edgeInsets: EdgeInsets.only(bottom: 32),
widget: Column(
children: <Widget>[
TopPanel(
color: Theme.of(context).accentTextTheme.title.color,
edgeInsets: EdgeInsets.fromLTRB(24, 29, 24, 32),
color: PaletteDark.wildVioletBlue,
widget: Observer(
builder: (_) => ExchangeCard(
key: depositKey,
title: S.of(context).you_will_send,
initialCurrency: exchangeStore.depositCurrency,
initialCurrency: exchangeViewModel.depositCurrency,
initialWalletName: depositWalletName,
initialAddress:
exchangeStore.depositCurrency == walletStore.type
? walletStore.address
: exchangeStore.depositAddress,
exchangeViewModel.depositCurrency == exchangeViewModel.wallet.currency
? exchangeViewModel.wallet.address
: exchangeViewModel.depositAddress,
initialIsAmountEditable: true,
initialIsAddressEditable: exchangeStore.isDepositAddressEnabled,
initialIsAddressEditable: exchangeViewModel.isDepositAddressEnabled,
isAmountEstimated: false,
currencies: CryptoCurrency.all,
onCurrencySelected: (currency) =>
exchangeStore.changeDepositCurrency(currency: currency),
exchangeViewModel.changeDepositCurrency(currency: currency),
imageArrow: arrowBottomPurple,
currencyButtonColor: Theme.of(context).accentTextTheme.title.color,
addressButtonsColor: Theme.of(context).accentTextTheme.title.backgroundColor,
currencyValueValidator: (value) {
exchangeStore.validateCryptoCurrency(value);
return exchangeStore.errorMessage;
},
addressTextFieldValidator: (value) {
exchangeStore.validateAddress(value,
cryptoCurrency: exchangeStore.depositCurrency);
return exchangeStore.errorMessage;
},
currencyButtonColor: PaletteDark.wildVioletBlue,
addressButtonsColor: PaletteDark.moderateBlue,
currencyValueValidator: exchangeViewModel.amountValidator,
addressTextFieldValidator: exchangeViewModel.addressValidator,
),
)
),
Padding(
padding: EdgeInsets.only(top: 32, left: 24, right: 24),
padding: EdgeInsets.only(top: 29, left: 24, right: 24),
child: Observer(
builder: (_) => ExchangeCard(
key: receiveKey,
title: S.of(context).you_will_get,
initialCurrency: exchangeStore.receiveCurrency,
initialCurrency: exchangeViewModel.receiveCurrency,
initialWalletName: receiveWalletName,
initialAddress:
exchangeStore.receiveCurrency == walletStore.type
? walletStore.address
: exchangeStore.receiveAddress,
exchangeViewModel.receiveCurrency == exchangeViewModel.wallet.currency
? exchangeViewModel.wallet.address
: exchangeViewModel.receiveAddress,
initialIsAmountEditable: false,
initialIsAddressEditable: exchangeStore.isReceiveAddressEnabled,
initialIsAddressEditable: exchangeViewModel.isReceiveAddressEnabled,
isAmountEstimated: true,
currencies: CryptoCurrency.all,
onCurrencySelected: (currency) => exchangeStore
onCurrencySelected: (currency) => exchangeViewModel
.changeReceiveCurrency(currency: currency),
imageArrow: arrowBottomCakeGreen,
currencyButtonColor: Theme.of(context).accentTextTheme.title.backgroundColor,
addressButtonsColor: Theme.of(context).accentTextTheme.title.color,
currencyValueValidator: (value) {
exchangeStore.validateCryptoCurrency(value);
return exchangeStore.errorMessage;
},
addressTextFieldValidator: (value) {
exchangeStore.validateAddress(value,
cryptoCurrency: exchangeStore.receiveCurrency);
return exchangeStore.errorMessage;
},
currencyButtonColor: PaletteDark.darkNightBlue,
addressButtonsColor: PaletteDark.moderateBlue,
currencyValueValidator: exchangeViewModel.amountValidator,
addressTextFieldValidator: exchangeViewModel.addressValidator,
)),
)
],
@ -172,7 +149,7 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
? Offstage()
: Padding(
padding: EdgeInsets.only(
top: 32,
top: 30,
left: 24,
bottom: 24
),
@ -184,7 +161,7 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Theme.of(context).primaryTextTheme.caption.color
color: PaletteDark.darkCyanBlue
),
)
],
@ -196,66 +173,90 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
height: 40,
width: double.infinity,
padding: EdgeInsets.only(left: 24),
child: Observer(
builder: (_) {
final itemCount = exchangeTemplateStore.templates.length + 1;
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: itemCount,
itemBuilder: (context, index) {
if (index == 0) {
return GestureDetector(
onTap: () => Navigator.of(context)
.pushNamed(Routes.exchangeTemplate),
child: Container(
padding: EdgeInsets.only(right: 10),
child: DottedBorder(
borderType: BorderType.RRect,
dashPattern: [8, 4],
color: Theme.of(context).accentTextTheme.title.backgroundColor,
strokeWidth: 2,
radius: Radius.circular(20),
child: Container(
height: 40,
width: 75,
padding: EdgeInsets.only(left: 10, right: 10),
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: Colors.transparent,
),
child: Text(
S.of(context).send_new,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Theme.of(context).primaryTextTheme.caption.color
),
),
)
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: <Widget>[
GestureDetector(
onTap: () => Navigator.of(context)
.pushNamed(Routes.exchangeTemplate),
child: Container(
padding: EdgeInsets.only(left: 1, right: 10),
child: DottedBorder(
borderType: BorderType.RRect,
dashPattern: [6, 4],
color: PaletteDark.darkCyanBlue,
strokeWidth: 2,
radius: Radius.circular(20),
child: Container(
height: 34,
width: 75,
padding: EdgeInsets.only(left: 10, right: 10),
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: Colors.transparent,
),
child: Text(
S.of(context).send_new,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: PaletteDark.darkCyanBlue
),
),
);
}
)
),
),
),
Observer(
builder: (_) {
final templates = exchangeViewModel.templates;
final itemCount = exchangeViewModel.templates.length;
index -= 1;
return ListView.builder(
scrollDirection: Axis.horizontal,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: itemCount,
itemBuilder: (context, index) {
final template = templates[index];
final template = exchangeTemplateStore.templates[index];
return TemplateTile(
amount: template.amount,
from: template.depositCurrency,
to: template.receiveCurrency,
onTap: () {
applyTemplate(exchangeStore, template);
return TemplateTile(
key: UniqueKey(),
amount: template.amount,
from: template.depositCurrency,
to: template.receiveCurrency,
onTap: () {
applyTemplate(exchangeViewModel, template);
},
onRemove: () {
showDialog<void>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle: S.of(context).template,
alertContent: S.of(context).confirm_delete_template,
leftButtonText: S.of(context).delete,
rightButtonText: S.of(context).cancel,
actionLeftButton: () {
Navigator.of(dialogContext).pop();
exchangeViewModel.exchangeTemplateStore.remove(template: template);
exchangeViewModel.exchangeTemplateStore.update();
},
actionRightButton: () => Navigator.of(dialogContext).pop()
);
}
);
},
);
}
);
}
);
}
),
),
],
)
)
)
],
),
@ -265,14 +266,15 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
padding: EdgeInsets.only(bottom: 15),
child: Observer(builder: (_) {
final description =
exchangeStore.provider is XMRTOExchangeProvider
exchangeViewModel.provider is XMRTOExchangeProvider
? S.of(context).amount_is_guaranteed
: S.of(context).amount_is_estimate;
return Center(
child: Text(
description,
style: TextStyle(
color: Theme.of(context).primaryTextTheme.caption.color,
color: PaletteDark.darkCyanBlue,
fontWeight: FontWeight.w500,
fontSize: 12
),
),
@ -283,15 +285,15 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
? PrimaryButton(
onPressed: () {
if (_formKey.currentState.validate()) {
exchangeTemplateStore.addTemplate(
amount: exchangeStore.depositAmount,
depositCurrency: exchangeStore.depositCurrency.toString(),
receiveCurrency: exchangeStore.receiveCurrency.toString(),
provider: exchangeStore.provider.toString(),
depositAddress: exchangeStore.depositAddress,
receiveAddress: exchangeStore.receiveAddress
exchangeViewModel.exchangeTemplateStore.addTemplate(
amount: exchangeViewModel.depositAmount,
depositCurrency: exchangeViewModel.depositCurrency.toString(),
receiveCurrency: exchangeViewModel.receiveCurrency.toString(),
provider: exchangeViewModel.provider.toString(),
depositAddress: exchangeViewModel.depositAddress,
receiveAddress: exchangeViewModel.receiveAddress
);
exchangeTemplateStore.update();
exchangeViewModel.exchangeTemplateStore.update();
Navigator.of(context).pop();
}
},
@ -304,41 +306,47 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
text: S.of(context).exchange,
onPressed: () {
if (_formKey.currentState.validate()) {
exchangeStore.createTrade();
exchangeViewModel.createTrade();
}
},
color: Colors.blue,
textColor: Colors.white,
isLoading: exchangeStore.tradeState is TradeIsCreating,
isLoading: exchangeViewModel.tradeState is TradeIsCreating,
)),
]),
)),
);
}
void applyTemplate(ExchangeStore store, ExchangeTemplate template) {
store.changeDepositCurrency(currency: CryptoCurrency.fromString(template.depositCurrency));
store.changeReceiveCurrency(currency: CryptoCurrency.fromString(template.receiveCurrency));
void applyTemplate(ExchangeViewModel exchangeViewModel,
ExchangeTemplate template) {
exchangeViewModel.changeDepositCurrency(
currency: CryptoCurrency.fromString(template.depositCurrency));
exchangeViewModel.changeReceiveCurrency(
currency: CryptoCurrency.fromString(template.receiveCurrency));
switch (template.provider) {
case 'XMR.TO':
store.changeProvider(provider: store.providerList[0]);
exchangeViewModel.changeProvider(
provider: exchangeViewModel.providerList[0]);
break;
case 'ChangeNOW':
store.changeProvider(provider: store.providerList[1]);
exchangeViewModel.changeProvider(
provider: exchangeViewModel.providerList[1]);
break;
case 'MorphToken':
store.changeProvider(provider: store.providerList[2]);
exchangeViewModel.changeProvider(
provider: exchangeViewModel.providerList[2]);
break;
}
store.changeDepositAmount(amount: template.amount);
store.depositAddress = template.depositAddress;
store.receiveAddress = template.receiveAddress;
exchangeViewModel.changeDepositAmount(amount: template.amount);
exchangeViewModel.depositAddress = template.depositAddress;
exchangeViewModel.receiveAddress = template.receiveAddress;
}
void _setReactions(
BuildContext context, ExchangeStore store, WalletStore walletStore) {
BuildContext context, ExchangeViewModel exchangeViewModel) {
if (_isReactionsSet) {
return;
}
@ -347,7 +355,7 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
final depositAmountController = depositKey.currentState.amountController;
final receiveAddressController = receiveKey.currentState.addressController;
final receiveAmountController = receiveKey.currentState.amountController;
final limitsState = store.limitsState;
final limitsState = exchangeViewModel.limitsState;
if (limitsState is LimitsLoadedSuccessfully) {
final min = limitsState.limits.min != null
@ -360,63 +368,62 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
key.currentState.changeLimits(min: min, max: max);
}
_onCurrencyChange(store.receiveCurrency, walletStore, receiveKey);
_onCurrencyChange(store.depositCurrency, walletStore, depositKey);
_onCurrencyChange(exchangeViewModel.receiveCurrency, exchangeViewModel, receiveKey);
_onCurrencyChange(exchangeViewModel.depositCurrency, exchangeViewModel, depositKey);
reaction(
(_) => walletStore.name,
(_) => exchangeViewModel.wallet.name,
(String _) => _onWalletNameChange(
walletStore, store.receiveCurrency, receiveKey));
exchangeViewModel, exchangeViewModel.receiveCurrency, receiveKey));
reaction(
(_) => walletStore.name,
(_) => exchangeViewModel.wallet.name,
(String _) => _onWalletNameChange(
walletStore, store.depositCurrency, depositKey));
exchangeViewModel, exchangeViewModel.depositCurrency, depositKey));
reaction(
(_) => store.receiveCurrency,
(_) => exchangeViewModel.receiveCurrency,
(CryptoCurrency currency) =>
_onCurrencyChange(currency, walletStore, receiveKey));
_onCurrencyChange(currency, exchangeViewModel, receiveKey));
reaction(
(_) => store.depositCurrency,
(_) => exchangeViewModel.depositCurrency,
(CryptoCurrency currency) =>
_onCurrencyChange(currency, walletStore, depositKey));
_onCurrencyChange(currency, exchangeViewModel, depositKey));
reaction((_) => store.depositAmount, (String amount) {
reaction((_) => exchangeViewModel.depositAmount, (String amount) {
if (depositKey.currentState.amountController.text != amount) {
depositKey.currentState.amountController.text = amount;
}
});
reaction((_) => store.depositAddress, (String address) {
reaction((_) => exchangeViewModel.depositAddress, (String address) {
if (depositKey.currentState.addressController.text != address) {
depositKey.currentState.addressController.text = address;
}
});
reaction((_) => store.isDepositAddressEnabled, (bool isEnabled) {
reaction((_) => exchangeViewModel.isDepositAddressEnabled, (bool isEnabled) {
depositKey.currentState.isAddressEditable(isEditable: isEnabled);
});
reaction((_) => store.receiveAmount, (String amount) {
if (receiveKey.currentState.amountController.text !=
store.receiveAmount) {
reaction((_) => exchangeViewModel.receiveAmount, (String amount) {
if (receiveKey.currentState.amountController.text != amount) {
receiveKey.currentState.amountController.text = amount;
}
});
reaction((_) => store.receiveAddress, (String address) {
reaction((_) => exchangeViewModel.receiveAddress, (String address) {
if (receiveKey.currentState.addressController.text != address) {
receiveKey.currentState.addressController.text = address;
}
});
reaction((_) => store.isReceiveAddressEnabled, (bool isEnabled) {
reaction((_) => exchangeViewModel.isReceiveAddressEnabled, (bool isEnabled) {
receiveKey.currentState.isAddressEditable(isEditable: isEnabled);
});
reaction((_) => store.tradeState, (ExchangeTradeState state) {
reaction((_) => exchangeViewModel.tradeState, (ExchangeTradeState state) {
if (state is TradeIsCreatedFailure) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showDialog<void>(
@ -437,7 +444,7 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
}
});
reaction((_) => store.limitsState, (LimitsState state) {
reaction((_) => exchangeViewModel.limitsState, (LimitsState state) {
String min;
String max;
@ -461,29 +468,29 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
});
depositAddressController.addListener(
() => store.depositAddress = depositAddressController.text);
() => exchangeViewModel.depositAddress = depositAddressController.text);
depositAmountController.addListener(() {
if (depositAmountController.text != store.depositAmount) {
store.changeDepositAmount(amount: depositAmountController.text);
if (depositAmountController.text != exchangeViewModel.depositAmount) {
exchangeViewModel.changeDepositAmount(amount: depositAmountController.text);
}
});
receiveAddressController.addListener(
() => store.receiveAddress = receiveAddressController.text);
() => exchangeViewModel.receiveAddress = receiveAddressController.text);
receiveAmountController.addListener(() {
if (receiveAmountController.text != store.receiveAmount) {
store.changeReceiveAmount(amount: receiveAmountController.text);
if (receiveAmountController.text != exchangeViewModel.receiveAmount) {
exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text);
}
});
reaction((_) => walletStore.address, (String address) {
if (store.depositCurrency == CryptoCurrency.xmr) {
reaction((_) => exchangeViewModel.wallet.address, (String address) {
if (exchangeViewModel.depositCurrency == CryptoCurrency.xmr) {
depositKey.currentState.changeAddress(address: address);
}
if (store.receiveCurrency == CryptoCurrency.xmr) {
if (exchangeViewModel.receiveCurrency == CryptoCurrency.xmr) {
receiveKey.currentState.changeAddress(address: address);
}
});
@ -491,28 +498,33 @@ class BaseExchangeWidgetState extends State<BaseExchangeWidget> {
_isReactionsSet = true;
}
void _onCurrencyChange(CryptoCurrency currency, WalletStore walletStore,
void _onCurrencyChange(CryptoCurrency currency,
ExchangeViewModel exchangeViewModel,
GlobalKey<ExchangeCardState> key) {
final isCurrentTypeWallet = currency == walletStore.type;
final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency;
key.currentState.changeSelectedCurrency(currency);
key.currentState
.changeWalletName(isCurrentTypeWallet ? walletStore.name : null);
.changeWalletName(isCurrentTypeWallet
? exchangeViewModel.wallet.name : null);
key.currentState
.changeAddress(address: isCurrentTypeWallet ? walletStore.address : '');
.changeAddress(address: isCurrentTypeWallet
? exchangeViewModel.wallet.address : '');
key.currentState.changeAmount(amount: '');
}
void _onWalletNameChange(WalletStore walletStore, CryptoCurrency currency,
void _onWalletNameChange(ExchangeViewModel exchangeViewModel,
CryptoCurrency currency,
GlobalKey<ExchangeCardState> key) {
final isCurrentTypeWallet = currency == walletStore.type;
final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency;
if (isCurrentTypeWallet) {
key.currentState.changeWalletName(walletStore.name);
key.currentState.addressController.text = walletStore.address;
} else if (key.currentState.addressController.text == walletStore.address) {
key.currentState.changeWalletName(exchangeViewModel.wallet.name);
key.currentState.addressController.text = exchangeViewModel.wallet.address;
} else if (key.currentState.addressController.text ==
exchangeViewModel.wallet.address) {
key.currentState.changeWalletName(null);
key.currentState.addressController.text = null;
}

View file

@ -5,6 +5,7 @@ import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
import 'package:cake_wallet/src/widgets/address_text_field.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/palette.dart';
class ExchangeCard extends StatefulWidget {
ExchangeCard(
@ -57,6 +58,10 @@ class ExchangeCardState extends State<ExchangeCard> {
bool _isAddressEditable;
bool _isAmountEstimated;
final copyImage = Image.asset('assets/images/copy_content.png',
height: 16, width: 16,
color: Colors.white);
@override
void initState() {
_title = widget.title;
@ -114,6 +119,7 @@ class ExchangeCardState extends State<ExchangeCard> {
width: double.infinity,
color: Colors.transparent,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
@ -123,13 +129,13 @@ class ExchangeCardState extends State<ExchangeCard> {
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Theme.of(context).primaryTextTheme.caption.color
color: PaletteDark.lightBlueGrey
),
)
],
),
Padding(
padding: EdgeInsets.only(top: 10),
padding: EdgeInsets.only(top: 20),
child: Stack(
children: <Widget>[
BaseTextFormField(
@ -143,7 +149,20 @@ class ExchangeCardState extends State<ExchangeCard> {
RegExp('[\\-|\\ |\\,]'))
],
hintText: '0.0000',
validator: widget.currencyValueValidator
borderColor: PaletteDark.blueGrey,
textStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white
),
placeholderTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: PaletteDark.lightBlueGrey
),
validator: _isAmountEditable
? widget.currencyValueValidator
: null
),
Positioned(
top: 8,
@ -163,7 +182,7 @@ class ExchangeCardState extends State<ExchangeCard> {
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Theme.of(context).primaryTextTheme.title.color)),
color: Colors.white)),
Padding(
padding: EdgeInsets.only(left: 5),
child: widget.imageArrow,
@ -181,45 +200,96 @@ class ExchangeCardState extends State<ExchangeCard> {
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
_min != null
? Text(
? Text(
S.of(context).min_value(
_min, _selectedCurrency.toString()),
style: TextStyle(
fontSize: 10,
height: 1.2,
color: Theme.of(context).primaryTextTheme.caption.color),
color: PaletteDark.lightBlueGrey),
)
: Offstage(),
: Offstage(),
_min != null ? SizedBox(width: 10) : Offstage(),
_max != null
? Text(
? Text(
S.of(context).max_value(
_max, _selectedCurrency.toString()),
style: TextStyle(
fontSize: 10,
height: 1.2,
color: Theme.of(context).primaryTextTheme.caption.color))
: Offstage(),
color: PaletteDark.lightBlueGrey))
: Offstage(),
]),
),
Padding(
padding: EdgeInsets.only(top: 10),
child: AddressTextField(
padding: EdgeInsets.only(top: 20),
child: Text(
_isAddressEditable
? S.of(context).widgets_address
: S.of(context).refund_address,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: PaletteDark.lightBlueGrey
),
)
),
_isAddressEditable
? AddressTextField(
controller: addressController,
isActive: _isAddressEditable,
options: _isAddressEditable
? _walletName != null
? []
: [
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode,
AddressTextFieldOption.addressBook,
]
: [],
],
placeholder: '',
isBorderExist: false,
textStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white),
buttonColor: widget.addressButtonsColor,
validator: widget.addressTextFieldValidator,
),
)
: Padding(
padding: EdgeInsets.only(top: 10),
child: Builder(
builder: (context) => GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(
text: addressController.text));
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(
S.of(context).copied_to_clipboard,
style: TextStyle(color: Colors.white),
),
backgroundColor: Colors.green,
duration: Duration(milliseconds: 500),
));
},
child: Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Text(
addressController.text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white),
),
),
Padding(
padding: EdgeInsets.only(left: 16),
child: copyImage,
)
],
),
)
),
),
]),
);
}

View file

@ -1,21 +1,22 @@
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/stores/exchange/exchange_store.dart';
import 'package:cake_wallet/src/domain/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
import 'package:cake_wallet/palette.dart';
class PresentProviderPicker extends StatelessWidget {
PresentProviderPicker({@required this.exchangeStore});
PresentProviderPicker({@required this.exchangeViewModel});
final ExchangeStore exchangeStore;
final ExchangeViewModel exchangeViewModel;
@override
Widget build(BuildContext context) {
final Image arrowBottom =
final arrowBottom =
Image.asset('assets/images/arrow_bottom_purple_icon.png',
color: Theme.of(context).primaryTextTheme.title.color,
color: Colors.white,
height: 6);
return FlatButton(
@ -33,19 +34,19 @@ class PresentProviderPicker extends StatelessWidget {
Text(S.of(context).exchange,
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.w400,
color: Theme.of(context).primaryTextTheme.title.color)),
fontWeight: FontWeight.w600,
color: Colors.white)),
Observer(
builder: (_) => Text('${exchangeStore.provider.title}',
builder: (_) => Text('${exchangeViewModel.provider.title}',
style: TextStyle(
fontSize: 10.0,
fontWeight: FontWeight.w400,
color: Theme.of(context).primaryTextTheme.caption.color)))
fontWeight: FontWeight.w500,
color: PaletteDark.lightBlueGrey)))
],
),
SizedBox(width: 5),
Padding(
padding: EdgeInsets.only(top: 8),
padding: EdgeInsets.only(top: 12),
child: arrowBottom,
)
],
@ -54,11 +55,11 @@ class PresentProviderPicker extends StatelessWidget {
}
void _presentProviderPicker(BuildContext context) {
final items = exchangeStore.providersForCurrentPair();
final selectedItem = items.indexOf(exchangeStore.provider);
final images = List<Image>();
final items = exchangeViewModel.providersForCurrentPair();
final selectedItem = items.indexOf(exchangeViewModel.provider);
final images = <Image>[];
for (ExchangeProvider provider in items) {
for (var provider in items) {
switch (provider.description) {
case ExchangeProviderDescription.xmrto:
images.add(Image.asset('assets/images/xmr_btc.png'));
@ -79,7 +80,7 @@ class PresentProviderPicker extends StatelessWidget {
selectedAtIndex: selectedItem,
title: S.of(context).change_exchange_provider,
onItemSelected: (ExchangeProvider provider) =>
exchangeStore.changeProvider(provider: provider)),
exchangeViewModel.changeProvider(provider: provider)),
context: context);
}
}

View file

@ -125,7 +125,7 @@ class ReceivePage extends BasePage {
.headline5
.color
.withOpacity(0.4),
validator: AmountValidator(),
validator: AmountValidator(isAutovalidate: true),
autovalidate: true,
placeholderTextStyle: TextStyle(
color: Theme.of(context)

View file

@ -72,6 +72,10 @@ class BaseTextFormField extends StatelessWidget {
borderSide: BorderSide(
color: borderColor ?? Theme.of(context).dividerColor,
width: 1.0)),
disabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: borderColor ?? Theme.of(context).dividerColor,
width: 1.0)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: borderColor ?? Theme.of(context).dividerColor,

View file

@ -71,6 +71,8 @@ class NavBar extends StatelessWidget implements ObstructingPreferredSizeWidget {
EdgeInsetsDirectional.only(bottom: _paddingBottom, top: paddingTop),
child: CupertinoNavigationBar(
leading: leading,
automaticallyImplyLeading: false,
automaticallyImplyMiddle: false,
middle: middle,
trailing: trailing,
backgroundColor: backgroundColor,

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:cake_wallet/palette.dart';
class TrailButton extends StatelessWidget {
TrailButton({
@ -21,7 +22,7 @@ class TrailButton extends StatelessWidget {
child: Text(
caption,
style: TextStyle(
color: Theme.of(context).primaryTextTheme.caption.color,
color: PaletteDark.lightBlueGrey,
fontWeight: FontWeight.w500,
fontSize: 14),
),

View file

@ -0,0 +1,306 @@
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/core/amount_validator.dart';
import 'package:cake_wallet/core/template_validator.dart';
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
import 'package:cake_wallet/src/domain/exchange/exchange_provider.dart';
import 'package:cake_wallet/src/domain/exchange/limits.dart';
import 'package:cake_wallet/src/domain/exchange/trade.dart';
import 'package:cake_wallet/src/stores/exchange/limits_state.dart';
import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:hive/hive.dart';
import 'package:cake_wallet/src/stores/exchange/exchange_trade_state.dart';
import 'package:cake_wallet/src/domain/exchange/changenow/changenow_exchange_provider.dart';
import 'package:cake_wallet/src/domain/exchange/changenow/changenow_request.dart';
import 'package:cake_wallet/src/domain/exchange/trade_request.dart';
import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_exchange_provider.dart';
import 'package:cake_wallet/src/domain/exchange/xmrto/xmrto_trade_request.dart';
import 'package:cake_wallet/src/domain/exchange/morphtoken/morphtoken_exchange_provider.dart';
import 'package:cake_wallet/src/domain/exchange/morphtoken/morphtoken_request.dart';
import 'package:cake_wallet/store/templates/exchange_template_store.dart';
import 'package:cake_wallet/src/domain/exchange/exchange_template.dart';
part 'exchange_view_model.g.dart';
class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel;
abstract class ExchangeViewModelBase with Store {
ExchangeViewModelBase({this.wallet, this.trades, this.exchangeTemplateStore}) {
providerList = [
XMRTOExchangeProvider(),
ChangeNowExchangeProvider(),
MorphTokenExchangeProvider(trades: trades)
];
provider = providerList[ 0 ];
depositCurrency = CryptoCurrency.xmr;
receiveCurrency = CryptoCurrency.btc;
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
depositAmount = '';
receiveAmount = '';
depositAddress = '';
receiveAddress = '';
limitsState = LimitsInitialState();
tradeState = ExchangeTradeStateInitial();
_cryptoNumberFormat = NumberFormat()..maximumFractionDigits = 12;
loadLimits();
}
final WalletBase wallet;
final Box<Trade> trades;
final ExchangeTemplateStore exchangeTemplateStore;
@observable
ExchangeProvider provider;
@observable
List<ExchangeProvider> providerList;
@observable
CryptoCurrency depositCurrency;
@observable
CryptoCurrency receiveCurrency;
@observable
LimitsState limitsState;
@observable
ExchangeTradeState tradeState;
@observable
String depositAmount;
@observable
String receiveAmount;
@observable
String depositAddress;
@observable
String receiveAddress;
@observable
bool isDepositAddressEnabled;
@observable
bool isReceiveAddressEnabled;
@observable
bool isValid;
@observable
String errorMessage;
Limits limits;
NumberFormat _cryptoNumberFormat;
Validator get amountValidator => AmountValidator(type: wallet.type);
Validator get addressValidator => AddressValidator(type: wallet.currency);
Validator get templateValidator => TemplateValidator();
@computed
ObservableList<ExchangeTemplate> get templates =>
exchangeTemplateStore.templates;
@action
void changeProvider({ExchangeProvider provider}) {
this.provider = provider;
depositAmount = '';
receiveAmount = '';
loadLimits();
}
@action
void changeDepositCurrency({CryptoCurrency currency}) {
depositCurrency = currency;
_onPairChange();
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
}
@action
void changeReceiveCurrency({CryptoCurrency currency}) {
receiveCurrency = currency;
_onPairChange();
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
}
@action
void changeReceiveAmount({String amount}) {
receiveAmount = amount;
if (amount == null || amount.isEmpty) {
depositAmount = '';
receiveAmount = '';
return;
}
final _amount = double.parse(amount) ?? 0;
provider
.calculateAmount(
from: depositCurrency, to: receiveCurrency, amount: _amount)
.then((amount) => _cryptoNumberFormat.format(amount).toString().replaceAll(RegExp("\\,"), ""))
.then((amount) => depositAmount = amount);
}
@action
void changeDepositAmount({String amount}) {
depositAmount = amount;
if (amount == null || amount.isEmpty) {
depositAmount = '';
receiveAmount = '';
return;
}
final _amount = double.parse(amount);
provider
.calculateAmount(
from: depositCurrency, to: receiveCurrency, amount: _amount)
.then((amount) => _cryptoNumberFormat.format(amount).toString().replaceAll(RegExp("\\,"), ""))
.then((amount) => receiveAmount = amount);
}
@action
Future loadLimits() async {
limitsState = LimitsIsLoading();
try {
limits = await provider.fetchLimits(
from: depositCurrency, to: receiveCurrency);
limitsState = LimitsLoadedSuccessfully(limits: limits);
} catch (e) {
limitsState = LimitsLoadedFailure(error: e.toString());
}
}
@action
Future createTrade() async {
TradeRequest request;
String amount;
CryptoCurrency currency;
if (provider is XMRTOExchangeProvider) {
request = XMRTOTradeRequest(
from: depositCurrency,
to: receiveCurrency,
amount: depositAmount,
address: receiveAddress,
refundAddress: depositAddress);
amount = depositAmount;
currency = depositCurrency;
}
if (provider is ChangeNowExchangeProvider) {
request = ChangeNowRequest(
from: depositCurrency,
to: receiveCurrency,
amount: depositAmount,
refundAddress: depositAddress,
address: receiveAddress);
amount = depositAmount;
currency = depositCurrency;
}
if (provider is MorphTokenExchangeProvider) {
request = MorphTokenRequest(
from: depositCurrency,
to: receiveCurrency,
amount: depositAmount,
refundAddress: depositAddress,
address: receiveAddress);
amount = depositAmount;
currency = depositCurrency;
}
if (limitsState is LimitsLoadedSuccessfully && amount != null) {
if (double.parse(amount) < limits.min) {
tradeState = TradeIsCreatedFailure(error: S.current.error_text_minimal_limit('${provider.description}',
'${limits.min}', currency.toString()));
} else if (limits.max != null && double.parse(amount) > limits.max) {
tradeState = TradeIsCreatedFailure(error: S.current.error_text_maximum_limit('${provider.description}',
'${limits.max}', currency.toString()));
} else {
try {
tradeState = TradeIsCreating();
final trade = await provider.createTrade(request: request);
trade.walletId = wallet.id;
await trades.add(trade);
tradeState = TradeIsCreatedSuccessfully(trade: trade);
} catch (e) {
tradeState = TradeIsCreatedFailure(error: e.toString());
}
}
} else {
tradeState = TradeIsCreatedFailure(error: S.current.error_text_limits_loading_failed("${provider.description}"));
}
}
@action
void reset() {
depositAmount = '';
receiveAmount = '';
depositCurrency = CryptoCurrency.xmr;
receiveCurrency = CryptoCurrency.btc;
depositAddress = depositCurrency == wallet.currency ? wallet.address : '';
receiveAddress = receiveCurrency == wallet.currency ? wallet.address : '';
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
_onPairChange();
}
List<ExchangeProvider> providersForCurrentPair() {
return _providersForPair(from: depositCurrency, to: receiveCurrency);
}
List<ExchangeProvider> _providersForPair(
{CryptoCurrency from, CryptoCurrency to}) {
final providers = providerList
.where((provider) => provider.pairList
.where((pair) =>
pair.from == depositCurrency && pair.to == receiveCurrency)
.isNotEmpty)
.toList();
return providers;
}
void _onPairChange() {
final isPairExist = provider.pairList
.where((pair) =>
pair.from == depositCurrency && pair.to == receiveCurrency)
.isNotEmpty;
if (!isPairExist) {
final provider =
_providerForPair(from: depositCurrency, to: receiveCurrency);
if (provider != null) {
changeProvider(provider: provider);
}
}
depositAmount = '';
receiveAmount = '';
loadLimits();
}
ExchangeProvider _providerForPair({CryptoCurrency from, CryptoCurrency to}) {
final providers = _providersForPair(from: from, to: to);
return providers.isNotEmpty ? providers[0] : null;
}
}

View file

@ -61,6 +61,7 @@
"exchange" : "Austausch",
"clear" : "klar",
"refund_address" : "Rückerstattungsadresse",
"change_exchange_provider" : "Wechseln Sie den Exchange-Anbieter",
"you_will_send" : "Du wirst senden",
"you_will_get" : "Sie erhalten",

View file

@ -61,6 +61,7 @@
"exchange" : "Exchange",
"clear" : "Clear",
"refund_address" : "Refund address",
"change_exchange_provider" : "Change Exchange Provider",
"you_will_send" : "You will send",
"you_will_get" : "You will get",

View file

@ -61,6 +61,7 @@
"exchange" : "Intercambiar",
"clear" : "Claro",
"refund_address" : "Dirección de reembolso",
"change_exchange_provider" : "Cambiar proveedor de intercambio",
"you_will_send" : "Enviarás",
"you_will_get" : "Conseguirás",

View file

@ -61,6 +61,7 @@
"exchange" : "अदला बदली",
"clear" : "स्पष्ट",
"refund_address" : "वापसी का पता",
"change_exchange_provider" : "एक्सचेंज प्रदाता बदलें",
"you_will_send" : "तुम भेजोगे",
"you_will_get" : "आपको मिल जायेगा",

View file

@ -61,6 +61,7 @@
"exchange" : "交換する",
"clear" : "クリア",
"refund_address" : "払い戻し住所",
"change_exchange_provider" : "Exchangeプロバイダーの変更",
"you_will_send" : "送ります",
"you_will_get" : "あなたが取得します",

View file

@ -61,6 +61,7 @@
"exchange" : "교환",
"clear" : "명확한",
"refund_address" : "환불 주소",
"change_exchange_provider" : "교환 공급자 변경",
"you_will_send" : "보내드립니다",
"you_will_get" : "당신은 얻을 것이다",

View file

@ -61,6 +61,7 @@
"exchange" : "Uitwisseling",
"clear" : "Duidelijk",
"refund_address" : "Adres voor terugbetaling",
"change_exchange_provider" : "Wijzig Exchange Provider",
"you_will_send" : "Je zal versturen",
"you_will_get" : "Je zult krijgen",

View file

@ -61,6 +61,7 @@
"exchange" : "Wymieniać się",
"clear" : "Wyczyść",
"refund_address" : "Adres zwrotu",
"change_exchange_provider" : "Zmień dostawcę programu Exchange",
"you_will_send" : "Wyślesz",
"you_will_get" : "Dostaniesz",

View file

@ -61,6 +61,7 @@
"exchange" : "Trocar",
"clear" : "Limpar",
"refund_address" : "Endereço de reembolso",
"change_exchange_provider" : "Alterar o provedor de troca",
"you_will_send" : "Você enviará",
"you_will_get" : "Você receberá",

View file

@ -61,6 +61,7 @@
"exchange" : "Обмен",
"clear" : "Очистить",
"refund_address" : "Адрес возврата",
"change_exchange_provider" : "Изменить провайдера обмена",
"you_will_send" : "Вы отправите",
"you_will_get" : "Вы получите",

View file

@ -61,6 +61,7 @@
"exchange" : "Обмін",
"clear" : "Очистити",
"refund_address" : "Адреса повернення коштів",
"change_exchange_provider" : "Змінити провайдера обміну",
"you_will_send" : "Ви відправите",
"you_will_get" : "Ви отримаєте",

View file

@ -61,6 +61,7 @@
"exchange" : "交换",
"clear" : "明确",
"refund_address" : "退款地址",
"change_exchange_provider" : "更改交易所提供商",
"you_will_send" : "您将发送",
"you_will_get" : "你会得到",