mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 19:49:22 +00:00
CW 68 exchange automatic rate selector (#472)
* Add 'Exchange provider picker' Save user selections * Save user's exchange providers selection * Add text for selected providers availability * Fix selected providers not updating * Load limits based on highest maximum in the selected providers * Change received and deposit amount to be the best value from the selected providers * Add provider name next to Trade ID Set selected provider based on amount calculated * Grey out providers who doesn't support selected currency pair * Fix disabled providers * Add Provider logo in Confirm Screen * Only choose a provider if it satisfies its limits * Fix amount validation * Fix typo in error message * Add a queue of possible exchange providers sorted by the best rate to try next if one failed * Fix string locale typo * Add Localization for other languages * Add Placeholder text when there are no providers selected * Check Exchange provider availability before creating a trade * Fix "Fixed Rate" changing unconditionally * Enable "convert to" field regardless of the provider * Remove "Choose one" from providers picker * Merge Master * Fix Conflicts with master * Add missing isEnabled field in simple swap provider
This commit is contained in:
parent
92458e2f4b
commit
c50eeee58b
30 changed files with 627 additions and 251 deletions
|
@ -485,7 +485,9 @@ Future setup(
|
||||||
_tradesSource,
|
_tradesSource,
|
||||||
getIt.get<ExchangeTemplateStore>(),
|
getIt.get<ExchangeTemplateStore>(),
|
||||||
getIt.get<TradesStore>(),
|
getIt.get<TradesStore>(),
|
||||||
getIt.get<AppStore>().settingsStore));
|
getIt.get<AppStore>().settingsStore,
|
||||||
|
getIt.get<SharedPreferences>(),
|
||||||
|
));
|
||||||
|
|
||||||
getIt.registerFactory(() => ExchangeTradeViewModel(
|
getIt.registerFactory(() => ExchangeTradeViewModel(
|
||||||
wallet: getIt.get<AppStore>().wallet,
|
wallet: getIt.get<AppStore>().wallet,
|
||||||
|
|
|
@ -26,4 +26,6 @@ class PreferencesKey {
|
||||||
|
|
||||||
static String moneroWalletUpdateV1Key(String name)
|
static String moneroWalletUpdateV1Key(String name)
|
||||||
=> '${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';
|
=> '${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';
|
||||||
|
|
||||||
|
static const exchangeProvidersSelection = 'exchange-providers-selection';
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,9 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
|
||||||
@override
|
@override
|
||||||
bool get isAvailable => true;
|
bool get isAvailable => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isEnabled => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ExchangeProviderDescription get description =>
|
ExchangeProviderDescription get description =>
|
||||||
ExchangeProviderDescription.changeNow;
|
ExchangeProviderDescription.changeNow;
|
||||||
|
|
|
@ -13,6 +13,7 @@ abstract class ExchangeProvider {
|
||||||
List<ExchangePair> pairList;
|
List<ExchangePair> pairList;
|
||||||
ExchangeProviderDescription description;
|
ExchangeProviderDescription description;
|
||||||
bool get isAvailable;
|
bool get isAvailable;
|
||||||
|
bool get isEnabled;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => title;
|
String toString() => title;
|
||||||
|
|
|
@ -2,20 +2,23 @@ import 'package:cw_core/enumerable_item.dart';
|
||||||
|
|
||||||
class ExchangeProviderDescription extends EnumerableItem<int>
|
class ExchangeProviderDescription extends EnumerableItem<int>
|
||||||
with Serializable<int> {
|
with Serializable<int> {
|
||||||
const ExchangeProviderDescription({String title, int raw})
|
const ExchangeProviderDescription({String title, int raw, this.horizontalLogo = false, this.image})
|
||||||
: super(title: title, raw: raw);
|
: super(title: title, raw: raw);
|
||||||
|
|
||||||
static const xmrto = ExchangeProviderDescription(title: 'XMR.TO', raw: 0);
|
final bool horizontalLogo;
|
||||||
|
final String image;
|
||||||
|
|
||||||
|
static const xmrto = ExchangeProviderDescription(title: 'XMR.TO', raw: 0, image: 'assets/images/xmrto.png');
|
||||||
static const changeNow =
|
static const changeNow =
|
||||||
ExchangeProviderDescription(title: 'ChangeNOW', raw: 1);
|
ExchangeProviderDescription(title: 'ChangeNOW', raw: 1, image: 'assets/images/changenow.png');
|
||||||
static const morphToken =
|
static const morphToken =
|
||||||
ExchangeProviderDescription(title: 'MorphToken', raw: 2);
|
ExchangeProviderDescription(title: 'MorphToken', raw: 2, image: 'assets/images/morph.png');
|
||||||
|
|
||||||
static const sideShift =
|
static const sideShift =
|
||||||
ExchangeProviderDescription(title: 'SideShift', raw: 3);
|
ExchangeProviderDescription(title: 'SideShift', raw: 3, image: 'assets/images/sideshift.png');
|
||||||
|
|
||||||
static const simpleSwap =
|
static const simpleSwap =
|
||||||
ExchangeProviderDescription(title: 'SimpleSwap', raw: 4);
|
ExchangeProviderDescription(title: 'SimpleSwap', raw: 4, image: 'assets/images/simpleSwap.png');
|
||||||
|
|
||||||
static ExchangeProviderDescription deserialize({int raw}) {
|
static ExchangeProviderDescription deserialize({int raw}) {
|
||||||
switch (raw) {
|
switch (raw) {
|
||||||
|
|
|
@ -63,6 +63,9 @@ class MorphTokenExchangeProvider extends ExchangeProvider {
|
||||||
@override
|
@override
|
||||||
bool get isAvailable => true;
|
bool get isAvailable => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isEnabled => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ExchangeProviderDescription get description =>
|
ExchangeProviderDescription get description =>
|
||||||
ExchangeProviderDescription.morphToken;
|
ExchangeProviderDescription.morphToken;
|
||||||
|
|
|
@ -244,6 +244,9 @@ class SideShiftExchangeProvider extends ExchangeProvider {
|
||||||
@override
|
@override
|
||||||
bool get isAvailable => true;
|
bool get isAvailable => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isEnabled => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get title => 'SideShift';
|
String get title => 'SideShift';
|
||||||
|
|
||||||
|
|
|
@ -197,6 +197,9 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
|
||||||
@override
|
@override
|
||||||
bool get isAvailable => true;
|
bool get isAvailable => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isEnabled => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get title => 'SimpleSwap';
|
String get title => 'SimpleSwap';
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,9 @@ class XMRTOExchangeProvider extends ExchangeProvider {
|
||||||
@override
|
@override
|
||||||
bool get isAvailable => _isAvailable;
|
bool get isAvailable => _isAvailable;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isEnabled => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ExchangeProviderDescription get description =>
|
ExchangeProviderDescription get description =>
|
||||||
ExchangeProviderDescription.xmrto;
|
ExchangeProviderDescription.xmrto;
|
||||||
|
|
|
@ -354,8 +354,12 @@ class ExchangePage extends BasePage {
|
||||||
padding: EdgeInsets.only(bottom: 15),
|
padding: EdgeInsets.only(bottom: 15),
|
||||||
child: Observer(builder: (_) {
|
child: Observer(builder: (_) {
|
||||||
final description = exchangeViewModel.isFixedRateMode
|
final description = exchangeViewModel.isFixedRateMode
|
||||||
|
? exchangeViewModel.isAvailableInSelected
|
||||||
? S.of(context).amount_is_guaranteed
|
? S.of(context).amount_is_guaranteed
|
||||||
: S.of(context).amount_is_estimate;
|
: S.of(context).fixed_pair_not_supported
|
||||||
|
: exchangeViewModel.isAvailableInSelected
|
||||||
|
? S.of(context).amount_is_estimate
|
||||||
|
: S.of(context).variable_pair_not_supported;
|
||||||
return Center(
|
return Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
description,
|
description,
|
||||||
|
@ -399,8 +403,8 @@ class ExchangePage extends BasePage {
|
||||||
},
|
},
|
||||||
color: Theme.of(context).accentTextTheme.body2.color,
|
color: Theme.of(context).accentTextTheme.body2.color,
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
isLoading:
|
isDisabled: exchangeViewModel.selectedProviders.isEmpty,
|
||||||
exchangeViewModel.tradeState is TradeIsCreating)),
|
isLoading: exchangeViewModel.tradeState is TradeIsCreating)),
|
||||||
]),
|
]),
|
||||||
)),
|
)),
|
||||||
));
|
));
|
||||||
|
@ -518,13 +522,6 @@ class ExchangePage extends BasePage {
|
||||||
exchangeViewModel.changeReceiveCurrency(
|
exchangeViewModel.changeReceiveCurrency(
|
||||||
currency: CryptoCurrency.fromString(template.receiveCurrency));
|
currency: CryptoCurrency.fromString(template.receiveCurrency));
|
||||||
|
|
||||||
switch (template.provider) {
|
|
||||||
case 'ChangeNOW':
|
|
||||||
exchangeViewModel.changeProvider(
|
|
||||||
provider: exchangeViewModel.providerList[0]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
exchangeViewModel.changeDepositAmount(amount: template.amount);
|
exchangeViewModel.changeDepositAmount(amount: template.amount);
|
||||||
exchangeViewModel.depositAddress = template.depositAddress;
|
exchangeViewModel.depositAddress = template.depositAddress;
|
||||||
exchangeViewModel.receiveAddress = template.receiveAddress;
|
exchangeViewModel.receiveAddress = template.receiveAddress;
|
||||||
|
@ -744,11 +741,10 @@ class ExchangePage extends BasePage {
|
||||||
});
|
});
|
||||||
|
|
||||||
_receiveAmountFocus.addListener(() {
|
_receiveAmountFocus.addListener(() {
|
||||||
if(receiveAmountController.text.isNotEmpty){
|
if (_receiveAmountFocus.hasFocus) {
|
||||||
exchangeViewModel.isFixedRateMode = true;
|
exchangeViewModel.isFixedRateMode = true;
|
||||||
}
|
}
|
||||||
exchangeViewModel.changeReceiveAmount(
|
exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text);
|
||||||
amount: receiveAmountController.text);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
_depositAmountFocus.addListener(() {
|
_depositAmountFocus.addListener(() {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/check_box_picker.dart';
|
||||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
|
||||||
import 'package:cake_wallet/exchange/exchange_provider.dart';
|
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.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/view_model/exchange/exchange_view_model.dart';
|
||||||
|
|
||||||
class PresentProviderPicker extends StatelessWidget {
|
class PresentProviderPicker extends StatelessWidget {
|
||||||
|
@ -38,11 +36,16 @@ class PresentProviderPicker extends StatelessWidget {
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Colors.white)),
|
color: Colors.white)),
|
||||||
Observer(
|
Observer(
|
||||||
builder: (_) => Text('${exchangeViewModel.provider.title}',
|
builder: (_) => Text(
|
||||||
style: TextStyle(
|
exchangeViewModel.selectedProviders.isEmpty
|
||||||
fontSize: 10.0,
|
? S.of(context).choose_one
|
||||||
fontWeight: FontWeight.w500,
|
: exchangeViewModel.selectedProviders.length > 1
|
||||||
color: Theme.of(context).textTheme.headline.color)))
|
? S.of(context).automatic
|
||||||
|
: exchangeViewModel.selectedProviders.first.title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10.0,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).textTheme.headline.color)))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(width: 5),
|
SizedBox(width: 5),
|
||||||
|
@ -54,41 +57,19 @@ class PresentProviderPicker extends StatelessWidget {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _presentProviderPicker(BuildContext context) {
|
void _presentProviderPicker(BuildContext context) async {
|
||||||
final items = exchangeViewModel.providersForCurrentPair();
|
await showPopUp<void>(
|
||||||
final selectedItem = items.indexOf(exchangeViewModel.provider);
|
builder: (BuildContext popUpContext) => CheckBoxPicker(
|
||||||
final images = <Image>[];
|
items: exchangeViewModel.providerList
|
||||||
String description;
|
.map((e) => CheckBoxItem(
|
||||||
|
e.title,
|
||||||
for (var provider in items) {
|
exchangeViewModel.selectedProviders.contains(e),
|
||||||
switch (provider.description) {
|
isDisabled: !exchangeViewModel.providersForCurrentPair().contains(e),
|
||||||
case ExchangeProviderDescription.xmrto:
|
))
|
||||||
images.add(Image.asset('assets/images/xmr_btc.png'));
|
.toList(),
|
||||||
break;
|
|
||||||
case ExchangeProviderDescription.changeNow:
|
|
||||||
images.add(Image.asset('assets/images/change_now.png'));
|
|
||||||
break;
|
|
||||||
case ExchangeProviderDescription.morphToken:
|
|
||||||
images.add(Image.asset('assets/images/morph_icon.png'));
|
|
||||||
break;
|
|
||||||
case ExchangeProviderDescription.sideShift:
|
|
||||||
images.add(Image.asset('assets/images/sideshift.png', width: 20));
|
|
||||||
break;
|
|
||||||
case ExchangeProviderDescription.simpleSwap:
|
|
||||||
images.add(Image.asset('assets/images/simpleSwap.png', width: 20));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
showPopUp<void>(
|
|
||||||
builder: (BuildContext popUpContext) => Picker(
|
|
||||||
items: items,
|
|
||||||
images: images,
|
|
||||||
selectedAtIndex: selectedItem,
|
|
||||||
title: S.of(context).change_exchange_provider,
|
title: S.of(context).change_exchange_provider,
|
||||||
description: description,
|
onChanged: (int index, bool value) {
|
||||||
onItemSelected: (ExchangeProvider provider) {
|
if (!exchangeViewModel.providerList[index].isAvailable) {
|
||||||
if (!provider.isAvailable) {
|
|
||||||
showPopUp<void>(
|
showPopUp<void>(
|
||||||
builder: (BuildContext popUpContext) => AlertWithOneAction(
|
builder: (BuildContext popUpContext) => AlertWithOneAction(
|
||||||
alertTitle: 'Error',
|
alertTitle: 'Error',
|
||||||
|
@ -98,8 +79,14 @@ class PresentProviderPicker extends StatelessWidget {
|
||||||
context: context);
|
context: context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
exchangeViewModel.changeProvider(provider: provider);
|
if (value) {
|
||||||
|
exchangeViewModel.addExchangeProvider(exchangeViewModel.providerList[index]);
|
||||||
|
} else {
|
||||||
|
exchangeViewModel.removeExchangeProvider(exchangeViewModel.providerList[index]);
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
context: context);
|
context: context);
|
||||||
|
|
||||||
|
exchangeViewModel.saveSelectedProviders();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
||||||
import 'package:cake_wallet/store/dashboard/trades_store.dart';
|
import 'package:cake_wallet/store/dashboard/trades_store.dart';
|
||||||
import 'package:cake_wallet/utils/show_bar.dart';
|
import 'package:cake_wallet/utils/show_bar.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -56,7 +57,7 @@ class ExchangeConfirmPage extends BasePage {
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
S.of(context).trade_id,
|
"${trade.provider.title} ${S.of(context).trade_id}",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12.0,
|
fontSize: 12.0,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
|
@ -101,7 +102,23 @@ class ExchangeConfirmPage extends BasePage {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Flexible(child: Offstage()),
|
Flexible(
|
||||||
|
child: Center(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
(trade.provider.image?.isNotEmpty ?? false)
|
||||||
|
? Image.asset(trade.provider.image, height: 50)
|
||||||
|
: const SizedBox(),
|
||||||
|
if (!trade.provider.horizontalLogo)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
|
child: Text(trade.provider.title),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
|
|
166
lib/src/widgets/check_box_picker.dart
Normal file
166
lib/src/widgets/check_box_picker.dart
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
import 'dart:ui';
|
||||||
|
import 'package:cake_wallet/palette.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/alert_background.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
|
||||||
|
class CheckBoxPicker extends StatefulWidget {
|
||||||
|
CheckBoxPicker({
|
||||||
|
@required this.items,
|
||||||
|
@required this.onChanged,
|
||||||
|
this.title,
|
||||||
|
this.displayItem,
|
||||||
|
this.isSeparated = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<CheckBoxItem> items;
|
||||||
|
final String title;
|
||||||
|
final Widget Function(CheckBoxItem) displayItem;
|
||||||
|
final bool isSeparated;
|
||||||
|
final Function(int, bool) onChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
CheckBoxPickerState createState() => CheckBoxPickerState(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
class CheckBoxPickerState extends State<CheckBoxPicker> {
|
||||||
|
CheckBoxPickerState(this.items);
|
||||||
|
|
||||||
|
final List<CheckBoxItem> items;
|
||||||
|
|
||||||
|
ScrollController controller = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertBackground(
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
if (widget.title?.isNotEmpty ?? false)
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
child: Text(
|
||||||
|
widget.title,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontFamily: 'Lato',
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(left: 24, right: 24, top: 24),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||||
|
child: Container(
|
||||||
|
color: Theme.of(context).accentTextTheme.title.color,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxHeight: MediaQuery.of(context).size.height * 0.65,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
(items?.length ?? 0) > 3
|
||||||
|
? Scrollbar(
|
||||||
|
controller: controller,
|
||||||
|
child: itemsList(),
|
||||||
|
)
|
||||||
|
: itemsList(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
AlertCloseButton(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget itemsList() {
|
||||||
|
return Container(
|
||||||
|
color: Theme.of(context).accentTextTheme.headline6.backgroundColor,
|
||||||
|
child: ListView.separated(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
controller: controller,
|
||||||
|
shrinkWrap: true,
|
||||||
|
separatorBuilder: (context, index) => widget.isSeparated
|
||||||
|
? Divider(
|
||||||
|
color: Theme.of(context).accentTextTheme.title.backgroundColor,
|
||||||
|
height: 1,
|
||||||
|
)
|
||||||
|
: const SizedBox(),
|
||||||
|
itemCount: items == null || items.isEmpty ? 0 : items.length,
|
||||||
|
itemBuilder: (context, index) => buildItem(index),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildItem(int index) {
|
||||||
|
final item = items[index];
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
height: 55,
|
||||||
|
color: Theme.of(context).accentTextTheme.headline6.color,
|
||||||
|
padding: EdgeInsets.only(left: 24, right: 24),
|
||||||
|
child: CheckboxListTile(
|
||||||
|
value: item.value,
|
||||||
|
activeColor: item.value
|
||||||
|
? Palette.blueCraiola
|
||||||
|
: Theme.of(context).accentTextTheme.subhead.decorationColor,
|
||||||
|
checkColor: Colors.white,
|
||||||
|
title: widget.displayItem?.call(item) ??
|
||||||
|
Text(
|
||||||
|
item.title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: 'Lato',
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: item.isDisabled
|
||||||
|
? Colors.grey.withOpacity(0.5)
|
||||||
|
: Theme.of(context).primaryTextTheme.title.color,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: (bool value) {
|
||||||
|
item.value = value;
|
||||||
|
widget.onChanged(index, value);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CheckBoxItem {
|
||||||
|
CheckBoxItem(this.title, this.value, {this.isDisabled = false});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final bool isDisabled;
|
||||||
|
bool value;
|
||||||
|
}
|
|
@ -116,7 +116,7 @@ abstract class ExchangeTradeViewModelBase with Store {
|
||||||
items?.clear();
|
items?.clear();
|
||||||
|
|
||||||
items.add(ExchangeTradeItem(
|
items.add(ExchangeTradeItem(
|
||||||
title: S.current.id, data: '${trade.id}', isCopied: true));
|
title: "${trade.provider.title} ${S.current.id}", data: '${trade.id}', isCopied: true));
|
||||||
|
|
||||||
if (trade.extraId != null) {
|
if (trade.extraId != null) {
|
||||||
final title = trade.from == CryptoCurrency.xrp
|
final title = trade.from == CryptoCurrency.xrp
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
import 'dart:collection';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||||
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
|
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
|
||||||
import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart';
|
import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart';
|
||||||
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
|
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
|
||||||
|
@ -27,6 +31,7 @@ import 'package:cake_wallet/exchange/morphtoken/morphtoken_exchange_provider.dar
|
||||||
import 'package:cake_wallet/exchange/morphtoken/morphtoken_request.dart';
|
import 'package:cake_wallet/exchange/morphtoken/morphtoken_request.dart';
|
||||||
import 'package:cake_wallet/store/templates/exchange_template_store.dart';
|
import 'package:cake_wallet/store/templates/exchange_template_store.dart';
|
||||||
import 'package:cake_wallet/exchange/exchange_template.dart';
|
import 'package:cake_wallet/exchange/exchange_template.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
part 'exchange_view_model.g.dart';
|
part 'exchange_view_model.g.dart';
|
||||||
|
|
||||||
|
@ -34,10 +39,24 @@ class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel;
|
||||||
|
|
||||||
abstract class ExchangeViewModelBase with Store {
|
abstract class ExchangeViewModelBase with Store {
|
||||||
ExchangeViewModelBase(this.wallet, this.trades, this._exchangeTemplateStore,
|
ExchangeViewModelBase(this.wallet, this.trades, this._exchangeTemplateStore,
|
||||||
this.tradesStore, this._settingsStore) {
|
this.tradesStore, this._settingsStore, this.sharedPreferences) {
|
||||||
const excludeDepositCurrencies = [CryptoCurrency.xhv];
|
const excludeDepositCurrencies = [CryptoCurrency.xhv];
|
||||||
const excludeReceiveCurrencies = [CryptoCurrency.xlm, CryptoCurrency.xrp, CryptoCurrency.bnb, CryptoCurrency.xhv];
|
const excludeReceiveCurrencies = [CryptoCurrency.xlm, CryptoCurrency.xrp, CryptoCurrency.bnb, CryptoCurrency.xhv];
|
||||||
providerList = [ChangeNowExchangeProvider(), SideShiftExchangeProvider(), SimpleSwapExchangeProvider()];
|
providerList = [ChangeNowExchangeProvider(), SideShiftExchangeProvider(), SimpleSwapExchangeProvider()];
|
||||||
|
|
||||||
|
currentTradeAvailableProviders = SplayTreeMap<double, ExchangeProvider>();
|
||||||
|
|
||||||
|
final Map<String, dynamic> exchangeProvidersSelection = json
|
||||||
|
.decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}") as Map<String, dynamic>;
|
||||||
|
|
||||||
|
/// if the provider is not in the user settings (user's first time or newly added provider)
|
||||||
|
/// then use its default value decided by us
|
||||||
|
selectedProviders = ObservableList.of(providerList.where(
|
||||||
|
(element) => exchangeProvidersSelection[element.title] == null
|
||||||
|
? element.isEnabled
|
||||||
|
: (exchangeProvidersSelection[element.title] as bool))
|
||||||
|
.toList());
|
||||||
|
|
||||||
_initialPairBasedOnWallet();
|
_initialPairBasedOnWallet();
|
||||||
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
|
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
|
||||||
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
|
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
|
||||||
|
@ -48,7 +67,7 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
? wallet.walletAddresses.address : '';
|
? wallet.walletAddresses.address : '';
|
||||||
limitsState = LimitsInitialState();
|
limitsState = LimitsInitialState();
|
||||||
tradeState = ExchangeTradeStateInitial();
|
tradeState = ExchangeTradeStateInitial();
|
||||||
_cryptoNumberFormat = NumberFormat()..maximumFractionDigits = 12;
|
_cryptoNumberFormat = NumberFormat()..maximumFractionDigits = wallet.type == WalletType.bitcoin ? 8 : 12;
|
||||||
provider = providersForCurrentPair().first;
|
provider = providersForCurrentPair().first;
|
||||||
final initialProvider = provider;
|
final initialProvider = provider;
|
||||||
provider.checkIsAvailable().then((bool isAvailable) {
|
provider.checkIsAvailable().then((bool isAvailable) {
|
||||||
|
@ -79,10 +98,20 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
final Box<Trade> trades;
|
final Box<Trade> trades;
|
||||||
final ExchangeTemplateStore _exchangeTemplateStore;
|
final ExchangeTemplateStore _exchangeTemplateStore;
|
||||||
final TradesStore tradesStore;
|
final TradesStore tradesStore;
|
||||||
|
final SharedPreferences sharedPreferences;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
ExchangeProvider provider;
|
ExchangeProvider provider;
|
||||||
|
|
||||||
|
/// Maps in dart are not sorted by default
|
||||||
|
/// SplayTreeMap is a map sorted by keys
|
||||||
|
/// will use it to sort available providers
|
||||||
|
/// depending on the amount they yield for the current trade
|
||||||
|
SplayTreeMap<double, ExchangeProvider> currentTradeAvailableProviders;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
ObservableList<ExchangeProvider> selectedProviders;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
List<ExchangeProvider> providerList;
|
List<ExchangeProvider> providerList;
|
||||||
|
|
||||||
|
@ -147,17 +176,7 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
|
|
||||||
NumberFormat _cryptoNumberFormat;
|
NumberFormat _cryptoNumberFormat;
|
||||||
|
|
||||||
SettingsStore _settingsStore;
|
final SettingsStore _settingsStore;
|
||||||
|
|
||||||
@action
|
|
||||||
void changeProvider({ExchangeProvider provider}) {
|
|
||||||
this.provider = provider;
|
|
||||||
depositAmount = '';
|
|
||||||
receiveAmount = '';
|
|
||||||
isFixedRateMode = false;
|
|
||||||
_defineIsReceiveAmountEditable();
|
|
||||||
loadLimits();
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void changeDepositCurrency({CryptoCurrency currency}) {
|
void changeDepositCurrency({CryptoCurrency currency}) {
|
||||||
|
@ -188,20 +207,46 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final _amount = double.parse(amount.replaceAll(',', '.')) ?? 0;
|
final _enteredAmount = double.parse(amount.replaceAll(',', '.')) ?? 0;
|
||||||
|
|
||||||
provider
|
currentTradeAvailableProviders.clear();
|
||||||
.calculateAmount(
|
for (var provider in selectedProviders) {
|
||||||
from: receiveCurrency,
|
provider
|
||||||
to: depositCurrency,
|
.calculateAmount(
|
||||||
amount: _amount,
|
from: receiveCurrency,
|
||||||
isFixedRateMode: isFixedRateMode,
|
to: depositCurrency,
|
||||||
isReceiveAmount: true)
|
amount: _enteredAmount,
|
||||||
.then((amount) => _cryptoNumberFormat
|
isFixedRateMode: isFixedRateMode,
|
||||||
|
isReceiveAmount: true)
|
||||||
|
.then((amount) {
|
||||||
|
|
||||||
|
final from = isFixedRateMode
|
||||||
|
? receiveCurrency
|
||||||
|
: depositCurrency;
|
||||||
|
final to = isFixedRateMode
|
||||||
|
? depositCurrency
|
||||||
|
: receiveCurrency;
|
||||||
|
|
||||||
|
provider.fetchLimits(
|
||||||
|
from: from,
|
||||||
|
to: to,
|
||||||
|
isFixedRateMode: isFixedRateMode,
|
||||||
|
).then((limits) {
|
||||||
|
/// if the entered amount doesn't exceed the limits of this provider
|
||||||
|
if ((limits.max ?? double.maxFinite) >= _enteredAmount
|
||||||
|
&& (limits.min ?? 0) <= _enteredAmount) {
|
||||||
|
/// add this provider as its valid for this trade
|
||||||
|
/// will be sorted ascending already since
|
||||||
|
/// we seek the least deposit amount
|
||||||
|
currentTradeAvailableProviders[amount] = provider;
|
||||||
|
}
|
||||||
|
return amount;
|
||||||
|
}).then((amount) => depositAmount = _cryptoNumberFormat
|
||||||
.format(amount)
|
.format(amount)
|
||||||
.toString()
|
.toString()
|
||||||
.replaceAll(RegExp('\\,'), ''))
|
.replaceAll(RegExp('\\,'), ''));
|
||||||
.then((amount) => depositAmount = amount);
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -215,23 +260,56 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final _amount = double.parse(amount.replaceAll(',', '.')) ?? 0;
|
final _enteredAmount = double.tryParse(amount.replaceAll(',', '.')) ?? 0;
|
||||||
provider
|
|
||||||
.calculateAmount(
|
currentTradeAvailableProviders.clear();
|
||||||
from: depositCurrency,
|
for (var provider in selectedProviders) {
|
||||||
to: receiveCurrency,
|
provider
|
||||||
amount: _amount,
|
.calculateAmount(
|
||||||
isFixedRateMode: isFixedRateMode,
|
from: depositCurrency,
|
||||||
isReceiveAmount: false)
|
to: receiveCurrency,
|
||||||
.then((amount) => _cryptoNumberFormat
|
amount: _enteredAmount,
|
||||||
|
isFixedRateMode: isFixedRateMode,
|
||||||
|
isReceiveAmount: false)
|
||||||
|
.then((amount) {
|
||||||
|
|
||||||
|
final from = isFixedRateMode
|
||||||
|
? receiveCurrency
|
||||||
|
: depositCurrency;
|
||||||
|
final to = isFixedRateMode
|
||||||
|
? depositCurrency
|
||||||
|
: receiveCurrency;
|
||||||
|
|
||||||
|
provider.fetchLimits(
|
||||||
|
from: from,
|
||||||
|
to: to,
|
||||||
|
isFixedRateMode: isFixedRateMode,
|
||||||
|
).then((limits) {
|
||||||
|
|
||||||
|
/// if the entered amount doesn't exceed the limits of this provider
|
||||||
|
if ((limits.max ?? double.maxFinite) >= _enteredAmount
|
||||||
|
&& (limits.min ?? 0) <= _enteredAmount) {
|
||||||
|
/// add this provider as its valid for this trade
|
||||||
|
/// subtract from maxFinite so the provider
|
||||||
|
/// with the largest amount would be sorted ascending
|
||||||
|
currentTradeAvailableProviders[double.maxFinite - amount] = provider;
|
||||||
|
}
|
||||||
|
return amount;
|
||||||
|
}).then((amount) => receiveAmount =
|
||||||
|
receiveAmount = _cryptoNumberFormat
|
||||||
.format(amount)
|
.format(amount)
|
||||||
.toString()
|
.toString()
|
||||||
.replaceAll(RegExp('\\,'), ''))
|
.replaceAll(RegExp('\\,'), ''));
|
||||||
.then((amount) => receiveAmount = amount);
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future loadLimits() async {
|
Future loadLimits() async {
|
||||||
|
if (selectedProviders.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
limitsState = LimitsIsLoading();
|
limitsState = LimitsIsLoading();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -241,10 +319,29 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
final to = isFixedRateMode
|
final to = isFixedRateMode
|
||||||
? depositCurrency
|
? depositCurrency
|
||||||
: receiveCurrency;
|
: receiveCurrency;
|
||||||
limits = await provider.fetchLimits(
|
|
||||||
|
limits = await selectedProviders.first.fetchLimits(
|
||||||
from: from,
|
from: from,
|
||||||
to: to,
|
to: to,
|
||||||
isFixedRateMode: isFixedRateMode);
|
isFixedRateMode: isFixedRateMode);
|
||||||
|
|
||||||
|
/// if the first provider limits is bounded then check with other providers
|
||||||
|
/// for the highest maximum limit
|
||||||
|
if (limits.max != null) {
|
||||||
|
for (int i = 1;i < selectedProviders.length;i++) {
|
||||||
|
final Limits tempLimits = await selectedProviders[i].fetchLimits(
|
||||||
|
from: from,
|
||||||
|
to: to,
|
||||||
|
isFixedRateMode: isFixedRateMode);
|
||||||
|
|
||||||
|
/// set the limits with the maximum provider limit
|
||||||
|
/// if there is a provider with null max then it's the maximum limit
|
||||||
|
if ((tempLimits.max ?? double.maxFinite) > limits.max) {
|
||||||
|
limits = tempLimits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
limitsState = LimitsLoadedSuccessfully(limits: limits);
|
limitsState = LimitsLoadedSuccessfully(limits: limits);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
limitsState = LimitsLoadedFailure(error: e.toString());
|
limitsState = LimitsLoadedFailure(error: e.toString());
|
||||||
|
@ -255,102 +352,97 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
Future createTrade() async {
|
Future createTrade() async {
|
||||||
TradeRequest request;
|
TradeRequest request;
|
||||||
String amount;
|
String amount;
|
||||||
CryptoCurrency currency;
|
|
||||||
|
|
||||||
if (provider is SideShiftExchangeProvider) {
|
for (var provider in currentTradeAvailableProviders.values) {
|
||||||
request = SideShiftRequest(
|
if (!(await provider.checkIsAvailable())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider is SideShiftExchangeProvider) {
|
||||||
|
request = SideShiftRequest(
|
||||||
depositMethod: depositCurrency,
|
depositMethod: depositCurrency,
|
||||||
settleMethod: receiveCurrency,
|
settleMethod: receiveCurrency,
|
||||||
depositAmount: depositAmount?.replaceAll(',', '.'),
|
depositAmount: depositAmount?.replaceAll(',', '.'),
|
||||||
settleAddress: receiveAddress,
|
settleAddress: receiveAddress,
|
||||||
refundAddress: depositAddress,
|
refundAddress: depositAddress,
|
||||||
);
|
);
|
||||||
amount = depositAmount;
|
amount = depositAmount;
|
||||||
currency = depositCurrency;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (provider is SimpleSwapExchangeProvider) {
|
if (provider is SimpleSwapExchangeProvider) {
|
||||||
request = SimpleSwapRequest(
|
request = SimpleSwapRequest(
|
||||||
from: depositCurrency,
|
from: depositCurrency,
|
||||||
to: receiveCurrency,
|
to: receiveCurrency,
|
||||||
amount: depositAmount?.replaceAll(',', '.'),
|
amount: depositAmount?.replaceAll(',', '.'),
|
||||||
address: receiveAddress,
|
address: receiveAddress,
|
||||||
refundAddress: depositAddress,
|
refundAddress: depositAddress,
|
||||||
);
|
);
|
||||||
amount = depositAmount;
|
amount = depositAmount;
|
||||||
currency = depositCurrency;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (provider is XMRTOExchangeProvider) {
|
if (provider is XMRTOExchangeProvider) {
|
||||||
request = XMRTOTradeRequest(
|
request = XMRTOTradeRequest(
|
||||||
from: depositCurrency,
|
from: depositCurrency,
|
||||||
to: receiveCurrency,
|
to: receiveCurrency,
|
||||||
amount: depositAmount?.replaceAll(',', '.'),
|
amount: depositAmount?.replaceAll(',', '.'),
|
||||||
receiveAmount: receiveAmount?.replaceAll(',', '.'),
|
receiveAmount: receiveAmount?.replaceAll(',', '.'),
|
||||||
address: receiveAddress,
|
address: receiveAddress,
|
||||||
refundAddress: depositAddress,
|
refundAddress: depositAddress,
|
||||||
isBTCRequest: isReceiveAmountEntered);
|
isBTCRequest: isReceiveAmountEntered);
|
||||||
amount = depositAmount;
|
amount = depositAmount;
|
||||||
currency = depositCurrency;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (provider is ChangeNowExchangeProvider) {
|
if (provider is ChangeNowExchangeProvider) {
|
||||||
request = ChangeNowRequest(
|
request = ChangeNowRequest(
|
||||||
from: depositCurrency,
|
from: depositCurrency,
|
||||||
to: receiveCurrency,
|
to: receiveCurrency,
|
||||||
fromAmount: depositAmount?.replaceAll(',', '.'),
|
fromAmount: depositAmount?.replaceAll(',', '.'),
|
||||||
toAmount: receiveAmount?.replaceAll(',', '.'),
|
toAmount: receiveAmount?.replaceAll(',', '.'),
|
||||||
refundAddress: depositAddress,
|
refundAddress: depositAddress,
|
||||||
address: receiveAddress,
|
address: receiveAddress,
|
||||||
isReverse: isReverse);
|
isReverse: isReverse);
|
||||||
amount = isReverse ? receiveAmount : depositAmount;
|
amount = isReverse ? receiveAmount : depositAmount;
|
||||||
currency = depositCurrency;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (provider is MorphTokenExchangeProvider) {
|
if (provider is MorphTokenExchangeProvider) {
|
||||||
request = MorphTokenRequest(
|
request = MorphTokenRequest(
|
||||||
from: depositCurrency,
|
from: depositCurrency,
|
||||||
to: receiveCurrency,
|
to: receiveCurrency,
|
||||||
amount: depositAmount?.replaceAll(',', '.'),
|
amount: depositAmount?.replaceAll(',', '.'),
|
||||||
refundAddress: depositAddress,
|
refundAddress: depositAddress,
|
||||||
address: receiveAddress);
|
address: receiveAddress);
|
||||||
amount = depositAmount;
|
amount = depositAmount;
|
||||||
currency = depositCurrency;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
amount = amount.replaceAll(',', '.');
|
amount = amount.replaceAll(',', '.');
|
||||||
|
|
||||||
if (limitsState is LimitsLoadedSuccessfully && amount != null) {
|
if (limitsState is LimitsLoadedSuccessfully && amount != null) {
|
||||||
if (double.parse(amount) < limits.min) {
|
if (double.parse(amount) < limits.min) {
|
||||||
tradeState = TradeIsCreatedFailure(
|
continue;
|
||||||
title: provider.title,
|
} else if (limits.max != null && double.parse(amount) > limits.max) {
|
||||||
error: S.current.error_text_minimal_limit('${provider.description}',
|
continue;
|
||||||
'${limits.min}', currency.toString()));
|
} else {
|
||||||
} else if (limits.max != null && double.parse(amount) > limits.max) {
|
try {
|
||||||
tradeState = TradeIsCreatedFailure(
|
tradeState = TradeIsCreating();
|
||||||
title: provider.title,
|
final trade = await provider.createTrade(
|
||||||
error: S.current.error_text_maximum_limit('${provider.description}',
|
request: request, isFixedRateMode: isFixedRateMode);
|
||||||
'${limits.max}', currency.toString()));
|
trade.walletId = wallet.id;
|
||||||
} else {
|
tradesStore.setTrade(trade);
|
||||||
try {
|
await trades.add(trade);
|
||||||
tradeState = TradeIsCreating();
|
tradeState = TradeIsCreatedSuccessfully(trade: trade);
|
||||||
final trade = await provider.createTrade(
|
/// return after the first successful trade
|
||||||
request: request, isFixedRateMode: isFixedRateMode);
|
return;
|
||||||
trade.walletId = wallet.id;
|
} catch (e) {
|
||||||
tradesStore.setTrade(trade);
|
continue;
|
||||||
await trades.add(trade);
|
}
|
||||||
tradeState = TradeIsCreatedSuccessfully(trade: trade);
|
|
||||||
} catch (e) {
|
|
||||||
tradeState =
|
|
||||||
TradeIsCreatedFailure(title: provider.title, error: e.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
tradeState = TradeIsCreatedFailure(
|
|
||||||
title: provider.title,
|
|
||||||
error: S.current
|
|
||||||
.error_text_limits_loading_failed('${provider.description}'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// if the code reached here then none of the providers succeeded
|
||||||
|
tradeState = TradeIsCreatedFailure(
|
||||||
|
title: S.current.trade_not_created,
|
||||||
|
error: S.current.none_of_selected_providers_can_exchange);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -414,7 +506,7 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
final providers = providerList
|
final providers = providerList
|
||||||
.where((provider) => provider.pairList
|
.where((provider) => provider.pairList
|
||||||
.where((pair) =>
|
.where((pair) =>
|
||||||
pair.from == depositCurrency && pair.to == receiveCurrency)
|
pair.from == (from ?? depositCurrency) && pair.to == (to ?? receiveCurrency))
|
||||||
.isNotEmpty)
|
.isNotEmpty)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
@ -422,27 +514,8 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onPairChange() {
|
void _onPairChange() {
|
||||||
final isPairExist = provider.pairList
|
depositAmount = '';
|
||||||
.where((pair) =>
|
receiveAmount = '';
|
||||||
pair.from == depositCurrency && pair.to == receiveCurrency)
|
|
||||||
.isNotEmpty;
|
|
||||||
|
|
||||||
if (isPairExist) {
|
|
||||||
final provider =
|
|
||||||
_providerForPair(from: depositCurrency, to: receiveCurrency);
|
|
||||||
|
|
||||||
if (provider != null) {
|
|
||||||
changeProvider(provider: provider);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
depositAmount = '';
|
|
||||||
receiveAmount = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ExchangeProvider _providerForPair({CryptoCurrency from, CryptoCurrency to}) {
|
|
||||||
final providers = _providersForPair(from: from, to: to);
|
|
||||||
return providers.isNotEmpty ? providers[0] : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _initialPairBasedOnWallet() {
|
void _initialPairBasedOnWallet() {
|
||||||
|
@ -473,6 +546,45 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
isReceiveAmountEditable = false;
|
isReceiveAmountEditable = false;
|
||||||
}*/
|
}*/
|
||||||
//isReceiveAmountEditable = false;
|
//isReceiveAmountEditable = false;
|
||||||
isReceiveAmountEditable = provider is ChangeNowExchangeProvider || provider is SimpleSwapExchangeProvider;
|
// isReceiveAmountEditable = selectedProviders.any((provider) => provider is ChangeNowExchangeProvider);
|
||||||
|
// isReceiveAmountEditable = provider is ChangeNowExchangeProvider || provider is SimpleSwapExchangeProvider;
|
||||||
|
isReceiveAmountEditable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void addExchangeProvider(ExchangeProvider provider) {
|
||||||
|
selectedProviders.add(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void removeExchangeProvider(ExchangeProvider provider) {
|
||||||
|
selectedProviders.remove(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void saveSelectedProviders() {
|
||||||
|
depositAmount = '';
|
||||||
|
receiveAmount = '';
|
||||||
|
isFixedRateMode = false;
|
||||||
|
_defineIsReceiveAmountEditable();
|
||||||
|
loadLimits();
|
||||||
|
|
||||||
|
final Map<String, dynamic> exchangeProvidersSelection = json
|
||||||
|
.decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}") as Map<String, dynamic>;
|
||||||
|
|
||||||
|
exchangeProvidersSelection.updateAll((key, dynamic value) => false);
|
||||||
|
for (var provider in selectedProviders) {
|
||||||
|
exchangeProvidersSelection[provider.title] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedPreferences.setString(
|
||||||
|
PreferencesKey.exchangeProvidersSelection,
|
||||||
|
json.encode(exchangeProvidersSelection),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isAvailableInSelected {
|
||||||
|
final providersForPair = providersForCurrentPair();
|
||||||
|
return selectedProviders.any((element) => element.isAvailable && providersForPair.contains(element));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -366,7 +366,7 @@
|
||||||
|
|
||||||
|
|
||||||
"trade_for_not_created" : "Handel für ${title} wird nicht erstellt.",
|
"trade_for_not_created" : "Handel für ${title} wird nicht erstellt.",
|
||||||
"trade_not_created" : "Handel nicht erstellt.",
|
"trade_not_created" : "Handel nicht erstellt",
|
||||||
"trade_id_not_found" : "Handel ${tradeId} von ${title} nicht gefunden.",
|
"trade_id_not_found" : "Handel ${tradeId} von ${title} nicht gefunden.",
|
||||||
"trade_not_found" : "Handel nicht gefunden.",
|
"trade_not_found" : "Handel nicht gefunden.",
|
||||||
|
|
||||||
|
@ -586,7 +586,7 @@
|
||||||
"use_card_info_two": "Tegoeden worden omgezet naar USD wanneer ze op de prepaid-rekening staan, niet in digitale valuta.",
|
"use_card_info_two": "Tegoeden worden omgezet naar USD wanneer ze op de prepaid-rekening staan, niet in digitale valuta.",
|
||||||
"use_card_info_three": "Gebruik de digitale kaart online of met contactloze betaalmethoden.",
|
"use_card_info_three": "Gebruik de digitale kaart online of met contactloze betaalmethoden.",
|
||||||
"optionally_order_card": "Optioneel een fysieke kaart bestellen.",
|
"optionally_order_card": "Optioneel een fysieke kaart bestellen.",
|
||||||
"hide_details" : "Details verbergen",
|
"hide_details" : "Details verbergen",
|
||||||
"show_details" : "Toon details",
|
"show_details" : "Toon details",
|
||||||
"upto": "tot ${value}",
|
"upto": "tot ${value}",
|
||||||
"discount": "Bespaar ${value}%",
|
"discount": "Bespaar ${value}%",
|
||||||
|
@ -634,5 +634,10 @@
|
||||||
"contact_support": "Support kontaktieren",
|
"contact_support": "Support kontaktieren",
|
||||||
"gift_cards_unavailable": "Geschenkkarten können derzeit nur über Monero, Bitcoin und Litecoin erworben werden",
|
"gift_cards_unavailable": "Geschenkkarten können derzeit nur über Monero, Bitcoin und Litecoin erworben werden",
|
||||||
"introducing_cake_pay": "Einführung von Cake Pay!",
|
"introducing_cake_pay": "Einführung von Cake Pay!",
|
||||||
"cake_pay_learn_more": "Karten sofort in der App kaufen und einlösen!\nWischen Sie nach rechts, um mehr zu erfahren!"
|
"cake_pay_learn_more": "Karten sofort in der App kaufen und einlösen!\nWischen Sie nach rechts, um mehr zu erfahren!",
|
||||||
|
"automatic": "Automatisch",
|
||||||
|
"fixed_pair_not_supported": "Dieses feste Paar wird von den ausgewählten Vermittlungsstellen nicht unterstützt",
|
||||||
|
"variable_pair_not_supported": "Dieses Variablenpaar wird von den ausgewählten Börsen nicht unterstützt",
|
||||||
|
"none_of_selected_providers_can_exchange": "Keiner der ausgewählten Anbieter kann diesen Austausch vornehmen",
|
||||||
|
"choose_one": "Wähle ein"
|
||||||
}
|
}
|
||||||
|
|
|
@ -366,7 +366,7 @@
|
||||||
|
|
||||||
|
|
||||||
"trade_for_not_created" : "Trade for ${title} is not created.",
|
"trade_for_not_created" : "Trade for ${title} is not created.",
|
||||||
"trade_not_created" : "Trade not created.",
|
"trade_not_created" : "Trade not created",
|
||||||
"trade_id_not_found" : "Trade ${tradeId} of ${title} not found.",
|
"trade_id_not_found" : "Trade ${tradeId} of ${title} not found.",
|
||||||
"trade_not_found" : "Trade not found.",
|
"trade_not_found" : "Trade not found.",
|
||||||
|
|
||||||
|
@ -634,5 +634,10 @@
|
||||||
"contact_support": "Contact Support",
|
"contact_support": "Contact Support",
|
||||||
"gift_cards_unavailable": "Gift cards are available for purchase only with Monero, Bitcoin, and Litecoin at this time",
|
"gift_cards_unavailable": "Gift cards are available for purchase only with Monero, Bitcoin, and Litecoin at this time",
|
||||||
"introducing_cake_pay": "Introducing Cake Pay!",
|
"introducing_cake_pay": "Introducing Cake Pay!",
|
||||||
"cake_pay_learn_more": "Instantly purchase and redeem cards in the app!\nSwipe right to learn more!"
|
"cake_pay_learn_more": "Instantly purchase and redeem cards in the app!\nSwipe right to learn more!",
|
||||||
|
"automatic": "Automatic",
|
||||||
|
"fixed_pair_not_supported": "This fixed pair is not supported with the selected exchanges",
|
||||||
|
"variable_pair_not_supported": "This variable pair is not supported with the selected exchanges",
|
||||||
|
"none_of_selected_providers_can_exchange": "None of the selected providers can make this exchange",
|
||||||
|
"choose_one": "Choose one"
|
||||||
}
|
}
|
||||||
|
|
|
@ -366,7 +366,7 @@
|
||||||
|
|
||||||
|
|
||||||
"trade_for_not_created" : "Comercio por ${title} no se crea.",
|
"trade_for_not_created" : "Comercio por ${title} no se crea.",
|
||||||
"trade_not_created" : "Comercio no se crea.",
|
"trade_not_created" : "Comercio no se crea",
|
||||||
"trade_id_not_found" : "Comercio ${tradeId} de ${title} no encontrado.",
|
"trade_id_not_found" : "Comercio ${tradeId} de ${title} no encontrado.",
|
||||||
"trade_not_found" : "Comercio no encontrado.",
|
"trade_not_found" : "Comercio no encontrado.",
|
||||||
|
|
||||||
|
@ -634,5 +634,10 @@
|
||||||
"contact_support": "Contactar con Soporte",
|
"contact_support": "Contactar con Soporte",
|
||||||
"gift_cards_unavailable": "Las tarjetas de regalo están disponibles para comprar solo a través de Monero, Bitcoin y Litecoin en este momento",
|
"gift_cards_unavailable": "Las tarjetas de regalo están disponibles para comprar solo a través de Monero, Bitcoin y Litecoin en este momento",
|
||||||
"introducing_cake_pay": "¡Presentamos Cake Pay!",
|
"introducing_cake_pay": "¡Presentamos Cake Pay!",
|
||||||
"cake_pay_learn_more": "¡Compre y canjee tarjetas al instante en la aplicación!\n¡Desliza hacia la derecha para obtener más información!"
|
"cake_pay_learn_more": "¡Compre y canjee tarjetas al instante en la aplicación!\n¡Desliza hacia la derecha para obtener más información!",
|
||||||
|
"automatic": "Automático",
|
||||||
|
"fixed_pair_not_supported": "Este par fijo no es compatible con los intercambios seleccionados",
|
||||||
|
"variable_pair_not_supported": "Este par de variables no es compatible con los intercambios seleccionados",
|
||||||
|
"none_of_selected_providers_can_exchange": "Ninguno de los proveedores seleccionados puede realizar este intercambio",
|
||||||
|
"choose_one": "Elige uno"
|
||||||
}
|
}
|
||||||
|
|
|
@ -364,7 +364,7 @@
|
||||||
|
|
||||||
|
|
||||||
"trade_for_not_created" : "L'échange pour ${title} n'est pas créé.",
|
"trade_for_not_created" : "L'échange pour ${title} n'est pas créé.",
|
||||||
"trade_not_created" : "Échange non créé.",
|
"trade_not_created" : "Échange non créé",
|
||||||
"trade_id_not_found" : "Échange ${tradeId} de ${title} introuvable.",
|
"trade_id_not_found" : "Échange ${tradeId} de ${title} introuvable.",
|
||||||
"trade_not_found" : "Échange introuvable.",
|
"trade_not_found" : "Échange introuvable.",
|
||||||
|
|
||||||
|
@ -632,5 +632,10 @@
|
||||||
"contact_support": "Contacter l'assistance",
|
"contact_support": "Contacter l'assistance",
|
||||||
"gift_cards_unavailable": "Les cartes-cadeaux ne sont disponibles à l'achat que via Monero, Bitcoin et Litecoin pour le moment",
|
"gift_cards_unavailable": "Les cartes-cadeaux ne sont disponibles à l'achat que via Monero, Bitcoin et Litecoin pour le moment",
|
||||||
"introducing_cake_pay": "Présentation de Cake Pay!",
|
"introducing_cake_pay": "Présentation de Cake Pay!",
|
||||||
"cake_pay_learn_more": "Achetez et échangez instantanément des cartes dans l'application !\nBalayez vers la droite pour en savoir plus !"
|
"cake_pay_learn_more": "Achetez et échangez instantanément des cartes dans l'application !\nBalayez vers la droite pour en savoir plus !",
|
||||||
|
"automatic": "Automatique",
|
||||||
|
"fixed_pair_not_supported": "Cette paire fixe n'est pas prise en charge avec les échanges sélectionnés",
|
||||||
|
"variable_pair_not_supported": "Cette paire de variables n'est pas prise en charge avec les échanges sélectionnés",
|
||||||
|
"none_of_selected_providers_can_exchange": "Aucun des prestataires sélectionnés ne peut effectuer cet échange",
|
||||||
|
"choose_one": "Choisissez-en un"
|
||||||
}
|
}
|
||||||
|
|
|
@ -366,7 +366,7 @@
|
||||||
|
|
||||||
|
|
||||||
"trade_for_not_created" : "के लिए व्यापार ${title} निर्मित नहीं है.",
|
"trade_for_not_created" : "के लिए व्यापार ${title} निर्मित नहीं है.",
|
||||||
"trade_not_created" : "व्यापार नहीं बनाया गया.",
|
"trade_not_created" : "व्यापार नहीं बनाया गया",
|
||||||
"trade_id_not_found" : "व्यापार ${tradeId} of ${title} नहीं मिला.",
|
"trade_id_not_found" : "व्यापार ${tradeId} of ${title} नहीं मिला.",
|
||||||
"trade_not_found" : "व्यापार नहीं मिला",
|
"trade_not_found" : "व्यापार नहीं मिला",
|
||||||
|
|
||||||
|
@ -634,5 +634,10 @@
|
||||||
"contact_support": "सहायता से संपर्क करें",
|
"contact_support": "सहायता से संपर्क करें",
|
||||||
"gift_cards_unavailable": "उपहार कार्ड इस समय केवल मोनेरो, बिटकॉइन और लिटकोइन के माध्यम से खरीदने के लिए उपलब्ध हैं",
|
"gift_cards_unavailable": "उपहार कार्ड इस समय केवल मोनेरो, बिटकॉइन और लिटकोइन के माध्यम से खरीदने के लिए उपलब्ध हैं",
|
||||||
"introducing_cake_pay": "परिचय Cake Pay!",
|
"introducing_cake_pay": "परिचय Cake Pay!",
|
||||||
"cake_pay_learn_more": "ऐप में तुरंत कार्ड खरीदें और रिडीम करें!\nअधिक जानने के लिए दाएं स्वाइप करें!"
|
"cake_pay_learn_more": "ऐप में तुरंत कार्ड खरीदें और रिडीम करें!\nअधिक जानने के लिए दाएं स्वाइप करें!",
|
||||||
|
"automatic": "स्वचालित",
|
||||||
|
"fixed_pair_not_supported": "यह निश्चित जोड़ी चयनित एक्सचेंजों के साथ समर्थित नहीं है",
|
||||||
|
"variable_pair_not_supported": "यह परिवर्तनीय जोड़ी चयनित एक्सचेंजों के साथ समर्थित नहीं है",
|
||||||
|
"none_of_selected_providers_can_exchange": "चयनित प्रदाताओं में से कोई भी इस एक्सचेंज को नहीं बना सकता",
|
||||||
|
"choose_one": "एक का चयन"
|
||||||
}
|
}
|
||||||
|
|
|
@ -366,7 +366,7 @@
|
||||||
|
|
||||||
|
|
||||||
"trade_for_not_created" : "Razmjena za ${title} nije izrađena.",
|
"trade_for_not_created" : "Razmjena za ${title} nije izrađena.",
|
||||||
"trade_not_created" : "Razmjena nije izrađena.",
|
"trade_not_created" : "Razmjena nije izrađena",
|
||||||
"trade_id_not_found" : "Razmjena ${tradeId} za ${title} nije pronađena.",
|
"trade_id_not_found" : "Razmjena ${tradeId} za ${title} nije pronađena.",
|
||||||
"trade_not_found" : "Razmjena nije pronađena.",
|
"trade_not_found" : "Razmjena nije pronađena.",
|
||||||
|
|
||||||
|
@ -634,5 +634,10 @@
|
||||||
"contact_support": "Kontaktirajte podršku",
|
"contact_support": "Kontaktirajte podršku",
|
||||||
"gift_cards_unavailable": "Poklon kartice trenutno su dostupne za kupnju samo putem Monera, Bitcoina i Litecoina",
|
"gift_cards_unavailable": "Poklon kartice trenutno su dostupne za kupnju samo putem Monera, Bitcoina i Litecoina",
|
||||||
"introducing_cake_pay": "Predstavljamo Cake Pay!",
|
"introducing_cake_pay": "Predstavljamo Cake Pay!",
|
||||||
"cake_pay_learn_more": "Odmah kupite i iskoristite kartice u aplikaciji!\nPrijeđite prstom udesno da biste saznali više!"
|
"cake_pay_learn_more": "Odmah kupite i iskoristite kartice u aplikaciji!\nPrijeđite prstom udesno da biste saznali više!",
|
||||||
|
"automatic": "Automatski",
|
||||||
|
"fixed_pair_not_supported": "Ovaj fiksni par nije podržan s odabranim burzama",
|
||||||
|
"variable_pair_not_supported": "Ovaj par varijabli nije podržan s odabranim burzama",
|
||||||
|
"none_of_selected_providers_can_exchange": "Niti jedan od odabranih pružatelja usluga ne može izvršiti ovu razmjenu",
|
||||||
|
"choose_one": "Izaberi jedan"
|
||||||
}
|
}
|
||||||
|
|
|
@ -366,7 +366,7 @@
|
||||||
|
|
||||||
|
|
||||||
"trade_for_not_created" : "Lo scambio per ${title} non è stato creato.",
|
"trade_for_not_created" : "Lo scambio per ${title} non è stato creato.",
|
||||||
"trade_not_created" : "Scambio non creato.",
|
"trade_not_created" : "Scambio non creato",
|
||||||
"trade_id_not_found" : "Scambio ${tradeId} di ${title} not trovato.",
|
"trade_id_not_found" : "Scambio ${tradeId} di ${title} not trovato.",
|
||||||
"trade_not_found" : "Scambio non trovato.",
|
"trade_not_found" : "Scambio non trovato.",
|
||||||
|
|
||||||
|
@ -634,5 +634,10 @@
|
||||||
"contact_support": "Contatta l'assistenza",
|
"contact_support": "Contatta l'assistenza",
|
||||||
"gift_cards_unavailable": "Le carte regalo sono disponibili per l'acquisto solo tramite Monero, Bitcoin e Litecoin in questo momento",
|
"gift_cards_unavailable": "Le carte regalo sono disponibili per l'acquisto solo tramite Monero, Bitcoin e Litecoin in questo momento",
|
||||||
"introducing_cake_pay": "Presentazione di Cake Pay!",
|
"introducing_cake_pay": "Presentazione di Cake Pay!",
|
||||||
"cake_pay_learn_more": "Acquista e riscatta istantaneamente le carte nell'app!\nScorri verso destra per saperne di più!"
|
"cake_pay_learn_more": "Acquista e riscatta istantaneamente le carte nell'app!\nScorri verso destra per saperne di più!",
|
||||||
|
"automatic": "Automatico",
|
||||||
|
"fixed_pair_not_supported": "Questa coppia fissa non è supportata con gli scambi selezionati",
|
||||||
|
"variable_pair_not_supported": "Questa coppia di variabili non è supportata con gli scambi selezionati",
|
||||||
|
"none_of_selected_providers_can_exchange": "Nessuno dei fornitori selezionati può effettuare questo scambio",
|
||||||
|
"choose_one": "Scegline uno"
|
||||||
}
|
}
|
||||||
|
|
|
@ -612,21 +612,21 @@
|
||||||
"cards": "カード",
|
"cards": "カード",
|
||||||
"active": "アクティブ",
|
"active": "アクティブ",
|
||||||
"redeemed": "償還",
|
"redeemed": "償還",
|
||||||
"gift_card_balance_note": "残高が残っているギフトカードがここに表示されます",
|
"gift_card_balance_note": "残高が残っているギフトカードがここに表示されます",
|
||||||
"gift_card_redeemed_note": "利用したギフトカードがここに表示されます",
|
"gift_card_redeemed_note": "利用したギフトカードがここに表示されます",
|
||||||
"logout": "ログアウト",
|
"logout": "ログアウト",
|
||||||
"add_tip": "ヒントを追加",
|
"add_tip": "ヒントを追加",
|
||||||
"percentageOf": "of ${amount}",
|
"percentageOf": "of ${amount}",
|
||||||
"is_percentage": "is",
|
"is_percentage": "is",
|
||||||
"search_category": "検索カテゴリ",
|
"search_category": "検索カテゴリ",
|
||||||
"mark_as_redeemed": "償還済みとしてマーク",
|
"mark_as_redeemed": "償還済みとしてマーク",
|
||||||
"more_options": "その他のオプション",
|
"more_options": "その他のオプション",
|
||||||
"awaiting_payment_confirmation": "支払い確認を待っています",
|
"awaiting_payment_confirmation": "支払い確認を待っています",
|
||||||
"transaction_sent_notice": "1分経っても画面が進まない場合は、ブロックエクスプローラーとメールアドレスを確認してください。",
|
"transaction_sent_notice": "1分経っても画面が進まない場合は、ブロックエクスプローラーとメールアドレスを確認してください。",
|
||||||
"agree": "同意する",
|
"agree": "同意する",
|
||||||
"in_store": "インストア",
|
"in_store": "インストア",
|
||||||
"generated_gift_card": "ギフトカードの生成",
|
"generated_gift_card": "ギフトカードの生成",
|
||||||
"payment_was_received": "お支払いを受け取りました。",
|
"payment_was_received": "お支払いを受け取りました。",
|
||||||
"proceed_after_one_minute": "1分経っても画面が進まない場合は、メールを確認してください。",
|
"proceed_after_one_minute": "1分経っても画面が進まない場合は、メールを確認してください。",
|
||||||
"order_id": "注文ID",
|
"order_id": "注文ID",
|
||||||
"gift_card_is_generated": "ギフトカードが生成されます",
|
"gift_card_is_generated": "ギフトカードが生成されます",
|
||||||
|
@ -634,5 +634,10 @@
|
||||||
"contact_support": "サポートに連絡する",
|
"contact_support": "サポートに連絡する",
|
||||||
"gift_cards_unavailable": "現時点では、ギフトカードはMonero、Bitcoin、Litecoinからのみ購入できます。",
|
"gift_cards_unavailable": "現時点では、ギフトカードはMonero、Bitcoin、Litecoinからのみ購入できます。",
|
||||||
"introducing_cake_pay": "序章Cake Pay!",
|
"introducing_cake_pay": "序章Cake Pay!",
|
||||||
"cake_pay_learn_more": "アプリですぐにカードを購入して引き換えましょう!\n右にスワイプして詳細をご覧ください。"
|
"cake_pay_learn_more": "アプリですぐにカードを購入して引き換えましょう!\n右にスワイプして詳細をご覧ください。",
|
||||||
|
"automatic": "自動",
|
||||||
|
"fixed_pair_not_supported": "この固定ペアは、選択したエクスチェンジではサポートされていません",
|
||||||
|
"variable_pair_not_supported": "この変数ペアは、選択した取引所ではサポートされていません",
|
||||||
|
"none_of_selected_providers_can_exchange": "選択したプロバイダーはいずれもこの交換を行うことができません",
|
||||||
|
"choose_one": "1 つ選択してください"
|
||||||
}
|
}
|
||||||
|
|
|
@ -366,7 +366,7 @@
|
||||||
|
|
||||||
|
|
||||||
"trade_for_not_created" : "거래 ${title} 생성되지 않습니다.",
|
"trade_for_not_created" : "거래 ${title} 생성되지 않습니다.",
|
||||||
"trade_not_created" : "거래가 생성되지 않았습니다.",
|
"trade_not_created" : "거래가 생성되지 않았습니다",
|
||||||
"trade_id_not_found" : "무역 ${tradeId} 의 ${title} 찾을 수 없습니다.",
|
"trade_id_not_found" : "무역 ${tradeId} 의 ${title} 찾을 수 없습니다.",
|
||||||
"trade_not_found" : "거래를 찾을 수 없습니다.",
|
"trade_not_found" : "거래를 찾을 수 없습니다.",
|
||||||
|
|
||||||
|
@ -634,5 +634,10 @@
|
||||||
"contact_support": "지원팀에 문의",
|
"contact_support": "지원팀에 문의",
|
||||||
"gift_cards_unavailable": "기프트 카드는 현재 Monero, Bitcoin 및 Litecoin을 통해서만 구매할 수 있습니다.",
|
"gift_cards_unavailable": "기프트 카드는 현재 Monero, Bitcoin 및 Litecoin을 통해서만 구매할 수 있습니다.",
|
||||||
"introducing_cake_pay": "소개 Cake Pay!",
|
"introducing_cake_pay": "소개 Cake Pay!",
|
||||||
"cake_pay_learn_more": "앱에서 즉시 카드를 구매하고 사용하세요!\n자세히 알아보려면 오른쪽으로 스와이프하세요!"
|
"cake_pay_learn_more": "앱에서 즉시 카드를 구매하고 사용하세요!\n자세히 알아보려면 오른쪽으로 스와이프하세요!",
|
||||||
|
"automatic": "자동적 인",
|
||||||
|
"fixed_pair_not_supported": "이 고정 쌍은 선택한 교환에서 지원되지 않습니다.",
|
||||||
|
"variable_pair_not_supported": "이 변수 쌍은 선택한 교환에서 지원되지 않습니다.",
|
||||||
|
"none_of_selected_providers_can_exchange": "선택한 공급자 중 누구도 이 교환을 할 수 없습니다.",
|
||||||
|
"choose_one": "하나 선택"
|
||||||
}
|
}
|
||||||
|
|
|
@ -366,7 +366,7 @@
|
||||||
|
|
||||||
|
|
||||||
"trade_for_not_created" : "Ruilen voor ${title} is niet gemaakt.",
|
"trade_for_not_created" : "Ruilen voor ${title} is niet gemaakt.",
|
||||||
"trade_not_created" : "Handel niet gecreëerd.",
|
"trade_not_created" : "Handel niet gecreëerd",
|
||||||
"trade_id_not_found" : "Handel ${tradeId} van ${title} niet gevonden.",
|
"trade_id_not_found" : "Handel ${tradeId} van ${title} niet gevonden.",
|
||||||
"trade_not_found" : "Handel niet gevonden.",
|
"trade_not_found" : "Handel niet gevonden.",
|
||||||
|
|
||||||
|
@ -533,7 +533,7 @@
|
||||||
"search_language": "Zoektaal",
|
"search_language": "Zoektaal",
|
||||||
"search_currency": "Zoek valuta",
|
"search_currency": "Zoek valuta",
|
||||||
"new_template" : "Nieuwe sjabloon",
|
"new_template" : "Nieuwe sjabloon",
|
||||||
"electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work",
|
"electrum_address_disclaimer": "We genereren nieuwe adressen elke keer dat u er een gebruikt, maar eerdere adressen blijven werken",
|
||||||
"wallet_name_exists": "Portemonnee met die naam bestaat al",
|
"wallet_name_exists": "Portemonnee met die naam bestaat al",
|
||||||
"market_place": "Marktplaats",
|
"market_place": "Marktplaats",
|
||||||
"cake_pay_title": "Cake Pay-cadeaubonnen",
|
"cake_pay_title": "Cake Pay-cadeaubonnen",
|
||||||
|
@ -634,5 +634,10 @@
|
||||||
"contact_support": "Contact opnemen met ondersteuning",
|
"contact_support": "Contact opnemen met ondersteuning",
|
||||||
"gift_cards_unavailable": "Cadeaubonnen kunnen momenteel alleen worden gekocht via Monero, Bitcoin en Litecoin",
|
"gift_cards_unavailable": "Cadeaubonnen kunnen momenteel alleen worden gekocht via Monero, Bitcoin en Litecoin",
|
||||||
"introducing_cake_pay": "Introductie van Cake Pay!",
|
"introducing_cake_pay": "Introductie van Cake Pay!",
|
||||||
"cake_pay_learn_more": "Koop en wissel direct kaarten in de app!\nSwipe naar rechts voor meer informatie!"
|
"cake_pay_learn_more": "Koop en wissel direct kaarten in de app!\nSwipe naar rechts voor meer informatie!",
|
||||||
|
"automatic": "automatisch",
|
||||||
|
"fixed_pair_not_supported": "Dit vaste paar wordt niet ondersteund bij de geselecteerde exchanges",
|
||||||
|
"variable_pair_not_supported": "Dit variabelenpaar wordt niet ondersteund met de geselecteerde uitwisselingen",
|
||||||
|
"none_of_selected_providers_can_exchange": "Geen van de geselecteerde providers kan deze uitwisseling maken",
|
||||||
|
"choose_one": "Kies er een"
|
||||||
}
|
}
|
||||||
|
|
|
@ -366,7 +366,7 @@
|
||||||
|
|
||||||
|
|
||||||
"trade_for_not_created" : "Zamienić się za ${title} nie jest tworzony.",
|
"trade_for_not_created" : "Zamienić się za ${title} nie jest tworzony.",
|
||||||
"trade_not_created" : "Handel nie utworzony.",
|
"trade_not_created" : "Handel nie utworzony",
|
||||||
"trade_id_not_found" : "Handel ${tradeId} of ${title} nie znaleziono.",
|
"trade_id_not_found" : "Handel ${tradeId} of ${title} nie znaleziono.",
|
||||||
"trade_not_found" : "Nie znaleziono handlu.",
|
"trade_not_found" : "Nie znaleziono handlu.",
|
||||||
|
|
||||||
|
@ -634,5 +634,10 @@
|
||||||
"contact_support": "Skontaktuj się z pomocą techniczną",
|
"contact_support": "Skontaktuj się z pomocą techniczną",
|
||||||
"gift_cards_unavailable": "Karty podarunkowe można obecnie kupić tylko za pośrednictwem Monero, Bitcoin i Litecoin",
|
"gift_cards_unavailable": "Karty podarunkowe można obecnie kupić tylko za pośrednictwem Monero, Bitcoin i Litecoin",
|
||||||
"introducing_cake_pay": "Przedstawiamy Ciasto Pay!",
|
"introducing_cake_pay": "Przedstawiamy Ciasto Pay!",
|
||||||
"cake_pay_learn_more": "Natychmiast kupuj i realizuj karty w aplikacji!\nPrzesuń w prawo, aby dowiedzieć się więcej!"
|
"cake_pay_learn_more": "Natychmiast kupuj i realizuj karty w aplikacji!\nPrzesuń w prawo, aby dowiedzieć się więcej!",
|
||||||
|
"automatic": "Automatyczny",
|
||||||
|
"fixed_pair_not_supported": "Ta stała para nie jest obsługiwana na wybranych giełdach",
|
||||||
|
"variable_pair_not_supported": "Ta para zmiennych nie jest obsługiwana na wybranych giełdach",
|
||||||
|
"none_of_selected_providers_can_exchange": "Żaden z wybranych dostawców nie może dokonać tej wymiany",
|
||||||
|
"choose_one": "Wybierz jeden"
|
||||||
}
|
}
|
||||||
|
|
|
@ -366,7 +366,7 @@
|
||||||
|
|
||||||
|
|
||||||
"trade_for_not_created" : "A troca por ${title} não foi criada.",
|
"trade_for_not_created" : "A troca por ${title} não foi criada.",
|
||||||
"trade_not_created" : "Troca não criada.",
|
"trade_not_created" : "Troca não criada",
|
||||||
"trade_id_not_found" : "A troca ${tradeId} de ${title} não foi encontrada.",
|
"trade_id_not_found" : "A troca ${tradeId} de ${title} não foi encontrada.",
|
||||||
"trade_not_found" : "Troca não encontrada.",
|
"trade_not_found" : "Troca não encontrada.",
|
||||||
|
|
||||||
|
@ -634,5 +634,10 @@
|
||||||
"contact_support": "Contatar Suporte",
|
"contact_support": "Contatar Suporte",
|
||||||
"gift_cards_unavailable": "Os cartões-presente estão disponíveis para compra apenas através do Monero, Bitcoin e Litecoin no momento",
|
"gift_cards_unavailable": "Os cartões-presente estão disponíveis para compra apenas através do Monero, Bitcoin e Litecoin no momento",
|
||||||
"introducing_cake_pay": "Apresentando o Cake Pay!",
|
"introducing_cake_pay": "Apresentando o Cake Pay!",
|
||||||
"cake_pay_learn_more": "Compre e resgate cartões instantaneamente no aplicativo!\nDeslize para a direita para saber mais!"
|
"cake_pay_learn_more": "Compre e resgate cartões instantaneamente no aplicativo!\nDeslize para a direita para saber mais!",
|
||||||
|
"automatic": "Automático",
|
||||||
|
"fixed_pair_not_supported": "Este par fixo não é compatível com as exchanges selecionadas",
|
||||||
|
"variable_pair_not_supported": "Este par de variáveis não é compatível com as trocas selecionadas",
|
||||||
|
"none_of_selected_providers_can_exchange": "Nenhum dos provedores selecionados pode fazer esta troca",
|
||||||
|
"choose_one": "Escolha um"
|
||||||
}
|
}
|
||||||
|
|
|
@ -366,7 +366,7 @@
|
||||||
|
|
||||||
|
|
||||||
"trade_for_not_created" : "Сделка для ${title} не создана.",
|
"trade_for_not_created" : "Сделка для ${title} не создана.",
|
||||||
"trade_not_created" : "Сделка не создана.",
|
"trade_not_created" : "Сделка не создана",
|
||||||
"trade_id_not_found" : "Сделка ${tradeId} ${title} не найдена.",
|
"trade_id_not_found" : "Сделка ${tradeId} ${title} не найдена.",
|
||||||
"trade_not_found" : "Trade not found.",
|
"trade_not_found" : "Trade not found.",
|
||||||
|
|
||||||
|
@ -634,5 +634,10 @@
|
||||||
"contact_support": "Связаться со службой поддержки",
|
"contact_support": "Связаться со службой поддержки",
|
||||||
"gift_cards_unavailable": "В настоящее время подарочные карты можно приобрести только через Monero, Bitcoin и Litecoin.",
|
"gift_cards_unavailable": "В настоящее время подарочные карты можно приобрести только через Monero, Bitcoin и Litecoin.",
|
||||||
"introducing_cake_pay": "Представляем Cake Pay!",
|
"introducing_cake_pay": "Представляем Cake Pay!",
|
||||||
"cake_pay_learn_more": "Мгновенно покупайте и погашайте карты в приложении!\nПроведите вправо, чтобы узнать больше!"
|
"cake_pay_learn_more": "Мгновенно покупайте и погашайте карты в приложении!\nПроведите вправо, чтобы узнать больше!",
|
||||||
|
"automatic": "автоматический",
|
||||||
|
"fixed_pair_not_supported": "Эта фиксированная пара не поддерживается выбранными биржами.",
|
||||||
|
"variable_pair_not_supported": "Эта пара переменных не поддерживается выбранными биржами.",
|
||||||
|
"none_of_selected_providers_can_exchange": "Ни один из выбранных провайдеров не может совершить этот обмен",
|
||||||
|
"choose_one": "Выбери один"
|
||||||
}
|
}
|
||||||
|
|
|
@ -365,7 +365,7 @@
|
||||||
|
|
||||||
|
|
||||||
"trade_for_not_created" : "Операція для ${title} не створена.",
|
"trade_for_not_created" : "Операція для ${title} не створена.",
|
||||||
"trade_not_created" : "Операція не створена.",
|
"trade_not_created" : "Операція не створена",
|
||||||
"trade_id_not_found" : "Операція ${tradeId} ${title} не знайдена.",
|
"trade_id_not_found" : "Операція ${tradeId} ${title} не знайдена.",
|
||||||
"trade_not_found" : "Операція не знайдена.",
|
"trade_not_found" : "Операція не знайдена.",
|
||||||
|
|
||||||
|
@ -633,5 +633,10 @@
|
||||||
"contact_support": "Звернутися до служби підтримки",
|
"contact_support": "Звернутися до служби підтримки",
|
||||||
"gift_cards_unavailable": "Наразі подарункові картки можна придбати лише через Monero, Bitcoin і Litecoin",
|
"gift_cards_unavailable": "Наразі подарункові картки можна придбати лише через Monero, Bitcoin і Litecoin",
|
||||||
"introducing_cake_pay": "Представляємо Cake Pay!",
|
"introducing_cake_pay": "Представляємо Cake Pay!",
|
||||||
"cake_pay_learn_more": "Миттєва купівля та погашення карток в додатку!\nПроведіть праворуч, щоб дізнатися більше!"
|
"cake_pay_learn_more": "Миттєва купівля та погашення карток в додатку!\nПроведіть праворуч, щоб дізнатися більше!",
|
||||||
|
"automatic": "Автоматичний",
|
||||||
|
"fixed_pair_not_supported": "Ця фіксована пара не підтримується вибраними біржами",
|
||||||
|
"variable_pair_not_supported": "Ця пара змінних не підтримується вибраними біржами",
|
||||||
|
"none_of_selected_providers_can_exchange": "Жоден із вибраних провайдерів не може здійснити цей обмін",
|
||||||
|
"choose_one": "Вибери один"
|
||||||
}
|
}
|
||||||
|
|
|
@ -366,7 +366,7 @@
|
||||||
|
|
||||||
|
|
||||||
"trade_for_not_created" : "交易 ${title} 未创建.",
|
"trade_for_not_created" : "交易 ${title} 未创建.",
|
||||||
"trade_not_created" : "未建立交易.",
|
"trade_not_created" : "未建立交易",
|
||||||
"trade_id_not_found" : "交易方式 ${tradeId} 的 ${title} 未找到.",
|
"trade_id_not_found" : "交易方式 ${tradeId} 的 ${title} 未找到.",
|
||||||
"trade_not_found" : "找不到交易.",
|
"trade_not_found" : "找不到交易.",
|
||||||
|
|
||||||
|
@ -632,5 +632,10 @@
|
||||||
"contact_support": "联系支持",
|
"contact_support": "联系支持",
|
||||||
"gift_cards_unavailable": "目前只能通过门罗币、比特币和莱特币购买礼品卡",
|
"gift_cards_unavailable": "目前只能通过门罗币、比特币和莱特币购买礼品卡",
|
||||||
"introducing_cake_pay": "介绍 Cake Pay!",
|
"introducing_cake_pay": "介绍 Cake Pay!",
|
||||||
"cake_pay_learn_more": "立即在应用程序中购买和兑换卡!\n向右滑动了解更多!"
|
"cake_pay_learn_more": "立即在应用程序中购买和兑换卡!\n向右滑动了解更多!",
|
||||||
|
"automatic": "自动的",
|
||||||
|
"fixed_pair_not_supported": "所选交易所不支持此固定货币对",
|
||||||
|
"variable_pair_not_supported": "所选交易所不支持此变量对",
|
||||||
|
"none_of_selected_providers_can_exchange": "选定的供应商都不能进行此交换",
|
||||||
|
"choose_one": "选一个"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue