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,
|
||||
getIt.get<ExchangeTemplateStore>(),
|
||||
getIt.get<TradesStore>(),
|
||||
getIt.get<AppStore>().settingsStore));
|
||||
getIt.get<AppStore>().settingsStore,
|
||||
getIt.get<SharedPreferences>(),
|
||||
));
|
||||
|
||||
getIt.registerFactory(() => ExchangeTradeViewModel(
|
||||
wallet: getIt.get<AppStore>().wallet,
|
||||
|
|
|
@ -26,4 +26,6 @@ class PreferencesKey {
|
|||
|
||||
static String moneroWalletUpdateV1Key(String name)
|
||||
=> '${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';
|
||||
|
||||
static const exchangeProvidersSelection = 'exchange-providers-selection';
|
||||
}
|
||||
|
|
|
@ -39,6 +39,9 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
|
|||
@override
|
||||
bool get isAvailable => true;
|
||||
|
||||
@override
|
||||
bool get isEnabled => true;
|
||||
|
||||
@override
|
||||
ExchangeProviderDescription get description =>
|
||||
ExchangeProviderDescription.changeNow;
|
||||
|
|
|
@ -13,6 +13,7 @@ abstract class ExchangeProvider {
|
|||
List<ExchangePair> pairList;
|
||||
ExchangeProviderDescription description;
|
||||
bool get isAvailable;
|
||||
bool get isEnabled;
|
||||
|
||||
@override
|
||||
String toString() => title;
|
||||
|
|
|
@ -2,20 +2,23 @@ import 'package:cw_core/enumerable_item.dart';
|
|||
|
||||
class ExchangeProviderDescription extends EnumerableItem<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);
|
||||
|
||||
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 =
|
||||
ExchangeProviderDescription(title: 'ChangeNOW', raw: 1);
|
||||
ExchangeProviderDescription(title: 'ChangeNOW', raw: 1, image: 'assets/images/changenow.png');
|
||||
static const morphToken =
|
||||
ExchangeProviderDescription(title: 'MorphToken', raw: 2);
|
||||
ExchangeProviderDescription(title: 'MorphToken', raw: 2, image: 'assets/images/morph.png');
|
||||
|
||||
static const sideShift =
|
||||
ExchangeProviderDescription(title: 'SideShift', raw: 3);
|
||||
|
||||
static const simpleSwap =
|
||||
ExchangeProviderDescription(title: 'SimpleSwap', raw: 4);
|
||||
ExchangeProviderDescription(title: 'SideShift', raw: 3, image: 'assets/images/sideshift.png');
|
||||
|
||||
static const simpleSwap =
|
||||
ExchangeProviderDescription(title: 'SimpleSwap', raw: 4, image: 'assets/images/simpleSwap.png');
|
||||
|
||||
static ExchangeProviderDescription deserialize({int raw}) {
|
||||
switch (raw) {
|
||||
|
|
|
@ -63,6 +63,9 @@ class MorphTokenExchangeProvider extends ExchangeProvider {
|
|||
@override
|
||||
bool get isAvailable => true;
|
||||
|
||||
@override
|
||||
bool get isEnabled => true;
|
||||
|
||||
@override
|
||||
ExchangeProviderDescription get description =>
|
||||
ExchangeProviderDescription.morphToken;
|
||||
|
|
|
@ -244,6 +244,9 @@ class SideShiftExchangeProvider extends ExchangeProvider {
|
|||
@override
|
||||
bool get isAvailable => true;
|
||||
|
||||
@override
|
||||
bool get isEnabled => true;
|
||||
|
||||
@override
|
||||
String get title => 'SideShift';
|
||||
|
||||
|
|
|
@ -197,6 +197,9 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
|
|||
@override
|
||||
bool get isAvailable => true;
|
||||
|
||||
@override
|
||||
bool get isEnabled => true;
|
||||
|
||||
@override
|
||||
String get title => 'SimpleSwap';
|
||||
|
||||
|
|
|
@ -44,6 +44,9 @@ class XMRTOExchangeProvider extends ExchangeProvider {
|
|||
@override
|
||||
bool get isAvailable => _isAvailable;
|
||||
|
||||
@override
|
||||
bool get isEnabled => true;
|
||||
|
||||
@override
|
||||
ExchangeProviderDescription get description =>
|
||||
ExchangeProviderDescription.xmrto;
|
||||
|
|
|
@ -354,8 +354,12 @@ class ExchangePage extends BasePage {
|
|||
padding: EdgeInsets.only(bottom: 15),
|
||||
child: Observer(builder: (_) {
|
||||
final description = exchangeViewModel.isFixedRateMode
|
||||
? exchangeViewModel.isAvailableInSelected
|
||||
? 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(
|
||||
child: Text(
|
||||
description,
|
||||
|
@ -399,8 +403,8 @@ class ExchangePage extends BasePage {
|
|||
},
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
isLoading:
|
||||
exchangeViewModel.tradeState is TradeIsCreating)),
|
||||
isDisabled: exchangeViewModel.selectedProviders.isEmpty,
|
||||
isLoading: exchangeViewModel.tradeState is TradeIsCreating)),
|
||||
]),
|
||||
)),
|
||||
));
|
||||
|
@ -518,13 +522,6 @@ class ExchangePage extends BasePage {
|
|||
exchangeViewModel.changeReceiveCurrency(
|
||||
currency: CryptoCurrency.fromString(template.receiveCurrency));
|
||||
|
||||
switch (template.provider) {
|
||||
case 'ChangeNOW':
|
||||
exchangeViewModel.changeProvider(
|
||||
provider: exchangeViewModel.providerList[0]);
|
||||
break;
|
||||
}
|
||||
|
||||
exchangeViewModel.changeDepositAmount(amount: template.amount);
|
||||
exchangeViewModel.depositAddress = template.depositAddress;
|
||||
exchangeViewModel.receiveAddress = template.receiveAddress;
|
||||
|
@ -744,11 +741,10 @@ class ExchangePage extends BasePage {
|
|||
});
|
||||
|
||||
_receiveAmountFocus.addListener(() {
|
||||
if(receiveAmountController.text.isNotEmpty){
|
||||
exchangeViewModel.isFixedRateMode = true;
|
||||
}
|
||||
exchangeViewModel.changeReceiveAmount(
|
||||
amount: receiveAmountController.text);
|
||||
if (_receiveAmountFocus.hasFocus) {
|
||||
exchangeViewModel.isFixedRateMode = true;
|
||||
}
|
||||
exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text);
|
||||
});
|
||||
|
||||
_depositAmountFocus.addListener(() {
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
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: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:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/picker.dart';
|
||||
import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
|
||||
|
||||
class PresentProviderPicker extends StatelessWidget {
|
||||
|
@ -38,11 +36,16 @@ class PresentProviderPicker extends StatelessWidget {
|
|||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white)),
|
||||
Observer(
|
||||
builder: (_) => Text('${exchangeViewModel.provider.title}',
|
||||
style: TextStyle(
|
||||
fontSize: 10.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).textTheme.headline.color)))
|
||||
builder: (_) => Text(
|
||||
exchangeViewModel.selectedProviders.isEmpty
|
||||
? S.of(context).choose_one
|
||||
: exchangeViewModel.selectedProviders.length > 1
|
||||
? 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),
|
||||
|
@ -54,41 +57,19 @@ class PresentProviderPicker extends StatelessWidget {
|
|||
));
|
||||
}
|
||||
|
||||
void _presentProviderPicker(BuildContext context) {
|
||||
final items = exchangeViewModel.providersForCurrentPair();
|
||||
final selectedItem = items.indexOf(exchangeViewModel.provider);
|
||||
final images = <Image>[];
|
||||
String description;
|
||||
|
||||
for (var provider in items) {
|
||||
switch (provider.description) {
|
||||
case ExchangeProviderDescription.xmrto:
|
||||
images.add(Image.asset('assets/images/xmr_btc.png'));
|
||||
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,
|
||||
void _presentProviderPicker(BuildContext context) async {
|
||||
await showPopUp<void>(
|
||||
builder: (BuildContext popUpContext) => CheckBoxPicker(
|
||||
items: exchangeViewModel.providerList
|
||||
.map((e) => CheckBoxItem(
|
||||
e.title,
|
||||
exchangeViewModel.selectedProviders.contains(e),
|
||||
isDisabled: !exchangeViewModel.providersForCurrentPair().contains(e),
|
||||
))
|
||||
.toList(),
|
||||
title: S.of(context).change_exchange_provider,
|
||||
description: description,
|
||||
onItemSelected: (ExchangeProvider provider) {
|
||||
if (!provider.isAvailable) {
|
||||
onChanged: (int index, bool value) {
|
||||
if (!exchangeViewModel.providerList[index].isAvailable) {
|
||||
showPopUp<void>(
|
||||
builder: (BuildContext popUpContext) => AlertWithOneAction(
|
||||
alertTitle: 'Error',
|
||||
|
@ -98,8 +79,14 @@ class PresentProviderPicker extends StatelessWidget {
|
|||
context: context);
|
||||
return;
|
||||
}
|
||||
exchangeViewModel.changeProvider(provider: provider);
|
||||
if (value) {
|
||||
exchangeViewModel.addExchangeProvider(exchangeViewModel.providerList[index]);
|
||||
} else {
|
||||
exchangeViewModel.removeExchangeProvider(exchangeViewModel.providerList[index]);
|
||||
}
|
||||
}),
|
||||
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/utils/show_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -56,7 +57,7 @@ class ExchangeConfirmPage extends BasePage {
|
|||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
S.of(context).trade_id,
|
||||
"${trade.provider.title} ${S.of(context).trade_id}",
|
||||
style: TextStyle(
|
||||
fontSize: 12.0,
|
||||
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(
|
||||
|
|
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.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) {
|
||||
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_request.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/store/templates/exchange_template_store.dart';
|
||||
import 'package:cake_wallet/exchange/exchange_template.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
part 'exchange_view_model.g.dart';
|
||||
|
||||
|
@ -34,10 +39,24 @@ class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel;
|
|||
|
||||
abstract class ExchangeViewModelBase with Store {
|
||||
ExchangeViewModelBase(this.wallet, this.trades, this._exchangeTemplateStore,
|
||||
this.tradesStore, this._settingsStore) {
|
||||
this.tradesStore, this._settingsStore, this.sharedPreferences) {
|
||||
const excludeDepositCurrencies = [CryptoCurrency.xhv];
|
||||
const excludeReceiveCurrencies = [CryptoCurrency.xlm, CryptoCurrency.xrp, CryptoCurrency.bnb, CryptoCurrency.xhv];
|
||||
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();
|
||||
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
|
||||
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
|
||||
|
@ -48,7 +67,7 @@ abstract class ExchangeViewModelBase with Store {
|
|||
? wallet.walletAddresses.address : '';
|
||||
limitsState = LimitsInitialState();
|
||||
tradeState = ExchangeTradeStateInitial();
|
||||
_cryptoNumberFormat = NumberFormat()..maximumFractionDigits = 12;
|
||||
_cryptoNumberFormat = NumberFormat()..maximumFractionDigits = wallet.type == WalletType.bitcoin ? 8 : 12;
|
||||
provider = providersForCurrentPair().first;
|
||||
final initialProvider = provider;
|
||||
provider.checkIsAvailable().then((bool isAvailable) {
|
||||
|
@ -79,10 +98,20 @@ abstract class ExchangeViewModelBase with Store {
|
|||
final Box<Trade> trades;
|
||||
final ExchangeTemplateStore _exchangeTemplateStore;
|
||||
final TradesStore tradesStore;
|
||||
final SharedPreferences sharedPreferences;
|
||||
|
||||
@observable
|
||||
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
|
||||
List<ExchangeProvider> providerList;
|
||||
|
||||
|
@ -147,17 +176,7 @@ abstract class ExchangeViewModelBase with Store {
|
|||
|
||||
NumberFormat _cryptoNumberFormat;
|
||||
|
||||
SettingsStore _settingsStore;
|
||||
|
||||
@action
|
||||
void changeProvider({ExchangeProvider provider}) {
|
||||
this.provider = provider;
|
||||
depositAmount = '';
|
||||
receiveAmount = '';
|
||||
isFixedRateMode = false;
|
||||
_defineIsReceiveAmountEditable();
|
||||
loadLimits();
|
||||
}
|
||||
final SettingsStore _settingsStore;
|
||||
|
||||
@action
|
||||
void changeDepositCurrency({CryptoCurrency currency}) {
|
||||
|
@ -188,20 +207,46 @@ abstract class ExchangeViewModelBase with Store {
|
|||
return;
|
||||
}
|
||||
|
||||
final _amount = double.parse(amount.replaceAll(',', '.')) ?? 0;
|
||||
final _enteredAmount = double.parse(amount.replaceAll(',', '.')) ?? 0;
|
||||
|
||||
provider
|
||||
.calculateAmount(
|
||||
from: receiveCurrency,
|
||||
to: depositCurrency,
|
||||
amount: _amount,
|
||||
isFixedRateMode: isFixedRateMode,
|
||||
isReceiveAmount: true)
|
||||
.then((amount) => _cryptoNumberFormat
|
||||
currentTradeAvailableProviders.clear();
|
||||
for (var provider in selectedProviders) {
|
||||
provider
|
||||
.calculateAmount(
|
||||
from: receiveCurrency,
|
||||
to: depositCurrency,
|
||||
amount: _enteredAmount,
|
||||
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)
|
||||
.toString()
|
||||
.replaceAll(RegExp('\\,'), ''))
|
||||
.then((amount) => depositAmount = amount);
|
||||
.replaceAll(RegExp('\\,'), ''));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -215,23 +260,56 @@ abstract class ExchangeViewModelBase with Store {
|
|||
return;
|
||||
}
|
||||
|
||||
final _amount = double.parse(amount.replaceAll(',', '.')) ?? 0;
|
||||
provider
|
||||
.calculateAmount(
|
||||
from: depositCurrency,
|
||||
to: receiveCurrency,
|
||||
amount: _amount,
|
||||
isFixedRateMode: isFixedRateMode,
|
||||
isReceiveAmount: false)
|
||||
.then((amount) => _cryptoNumberFormat
|
||||
final _enteredAmount = double.tryParse(amount.replaceAll(',', '.')) ?? 0;
|
||||
|
||||
currentTradeAvailableProviders.clear();
|
||||
for (var provider in selectedProviders) {
|
||||
provider
|
||||
.calculateAmount(
|
||||
from: depositCurrency,
|
||||
to: receiveCurrency,
|
||||
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)
|
||||
.toString()
|
||||
.replaceAll(RegExp('\\,'), ''))
|
||||
.then((amount) => receiveAmount = amount);
|
||||
.replaceAll(RegExp('\\,'), ''));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future loadLimits() async {
|
||||
if (selectedProviders.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
limitsState = LimitsIsLoading();
|
||||
|
||||
try {
|
||||
|
@ -241,10 +319,29 @@ abstract class ExchangeViewModelBase with Store {
|
|||
final to = isFixedRateMode
|
||||
? depositCurrency
|
||||
: receiveCurrency;
|
||||
limits = await provider.fetchLimits(
|
||||
|
||||
limits = await selectedProviders.first.fetchLimits(
|
||||
from: from,
|
||||
to: to,
|
||||
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);
|
||||
} catch (e) {
|
||||
limitsState = LimitsLoadedFailure(error: e.toString());
|
||||
|
@ -255,102 +352,97 @@ abstract class ExchangeViewModelBase with Store {
|
|||
Future createTrade() async {
|
||||
TradeRequest request;
|
||||
String amount;
|
||||
CryptoCurrency currency;
|
||||
|
||||
if (provider is SideShiftExchangeProvider) {
|
||||
request = SideShiftRequest(
|
||||
for (var provider in currentTradeAvailableProviders.values) {
|
||||
if (!(await provider.checkIsAvailable())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (provider is SideShiftExchangeProvider) {
|
||||
request = SideShiftRequest(
|
||||
depositMethod: depositCurrency,
|
||||
settleMethod: receiveCurrency,
|
||||
depositAmount: depositAmount?.replaceAll(',', '.'),
|
||||
settleAddress: receiveAddress,
|
||||
refundAddress: depositAddress,
|
||||
);
|
||||
amount = depositAmount;
|
||||
currency = depositCurrency;
|
||||
}
|
||||
);
|
||||
amount = depositAmount;
|
||||
}
|
||||
|
||||
if (provider is SimpleSwapExchangeProvider) {
|
||||
request = SimpleSwapRequest(
|
||||
if (provider is SimpleSwapExchangeProvider) {
|
||||
request = SimpleSwapRequest(
|
||||
from: depositCurrency,
|
||||
to: receiveCurrency,
|
||||
amount: depositAmount?.replaceAll(',', '.'),
|
||||
address: receiveAddress,
|
||||
refundAddress: depositAddress,
|
||||
);
|
||||
amount = depositAmount;
|
||||
currency = depositCurrency;
|
||||
}
|
||||
);
|
||||
amount = depositAmount;
|
||||
}
|
||||
|
||||
if (provider is XMRTOExchangeProvider) {
|
||||
request = XMRTOTradeRequest(
|
||||
from: depositCurrency,
|
||||
to: receiveCurrency,
|
||||
amount: depositAmount?.replaceAll(',', '.'),
|
||||
receiveAmount: receiveAmount?.replaceAll(',', '.'),
|
||||
address: receiveAddress,
|
||||
refundAddress: depositAddress,
|
||||
isBTCRequest: isReceiveAmountEntered);
|
||||
amount = depositAmount;
|
||||
currency = depositCurrency;
|
||||
}
|
||||
if (provider is XMRTOExchangeProvider) {
|
||||
request = XMRTOTradeRequest(
|
||||
from: depositCurrency,
|
||||
to: receiveCurrency,
|
||||
amount: depositAmount?.replaceAll(',', '.'),
|
||||
receiveAmount: receiveAmount?.replaceAll(',', '.'),
|
||||
address: receiveAddress,
|
||||
refundAddress: depositAddress,
|
||||
isBTCRequest: isReceiveAmountEntered);
|
||||
amount = depositAmount;
|
||||
}
|
||||
|
||||
if (provider is ChangeNowExchangeProvider) {
|
||||
request = ChangeNowRequest(
|
||||
from: depositCurrency,
|
||||
to: receiveCurrency,
|
||||
fromAmount: depositAmount?.replaceAll(',', '.'),
|
||||
toAmount: receiveAmount?.replaceAll(',', '.'),
|
||||
refundAddress: depositAddress,
|
||||
address: receiveAddress,
|
||||
isReverse: isReverse);
|
||||
amount = isReverse ? receiveAmount : depositAmount;
|
||||
currency = depositCurrency;
|
||||
}
|
||||
if (provider is ChangeNowExchangeProvider) {
|
||||
request = ChangeNowRequest(
|
||||
from: depositCurrency,
|
||||
to: receiveCurrency,
|
||||
fromAmount: depositAmount?.replaceAll(',', '.'),
|
||||
toAmount: receiveAmount?.replaceAll(',', '.'),
|
||||
refundAddress: depositAddress,
|
||||
address: receiveAddress,
|
||||
isReverse: isReverse);
|
||||
amount = isReverse ? receiveAmount : depositAmount;
|
||||
}
|
||||
|
||||
if (provider is MorphTokenExchangeProvider) {
|
||||
request = MorphTokenRequest(
|
||||
from: depositCurrency,
|
||||
to: receiveCurrency,
|
||||
amount: depositAmount?.replaceAll(',', '.'),
|
||||
refundAddress: depositAddress,
|
||||
address: receiveAddress);
|
||||
amount = depositAmount;
|
||||
currency = depositCurrency;
|
||||
}
|
||||
if (provider is MorphTokenExchangeProvider) {
|
||||
request = MorphTokenRequest(
|
||||
from: depositCurrency,
|
||||
to: receiveCurrency,
|
||||
amount: depositAmount?.replaceAll(',', '.'),
|
||||
refundAddress: depositAddress,
|
||||
address: receiveAddress);
|
||||
amount = depositAmount;
|
||||
}
|
||||
|
||||
amount = amount.replaceAll(',', '.');
|
||||
amount = amount.replaceAll(',', '.');
|
||||
|
||||
if (limitsState is LimitsLoadedSuccessfully && amount != null) {
|
||||
if (double.parse(amount) < limits.min) {
|
||||
tradeState = TradeIsCreatedFailure(
|
||||
title: provider.title,
|
||||
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(
|
||||
title: provider.title,
|
||||
error: S.current.error_text_maximum_limit('${provider.description}',
|
||||
'${limits.max}', currency.toString()));
|
||||
} else {
|
||||
try {
|
||||
tradeState = TradeIsCreating();
|
||||
final trade = await provider.createTrade(
|
||||
request: request, isFixedRateMode: isFixedRateMode);
|
||||
trade.walletId = wallet.id;
|
||||
tradesStore.setTrade(trade);
|
||||
await trades.add(trade);
|
||||
tradeState = TradeIsCreatedSuccessfully(trade: trade);
|
||||
} catch (e) {
|
||||
tradeState =
|
||||
TradeIsCreatedFailure(title: provider.title, error: e.toString());
|
||||
if (limitsState is LimitsLoadedSuccessfully && amount != null) {
|
||||
if (double.parse(amount) < limits.min) {
|
||||
continue;
|
||||
} else if (limits.max != null && double.parse(amount) > limits.max) {
|
||||
continue;
|
||||
} else {
|
||||
try {
|
||||
tradeState = TradeIsCreating();
|
||||
final trade = await provider.createTrade(
|
||||
request: request, isFixedRateMode: isFixedRateMode);
|
||||
trade.walletId = wallet.id;
|
||||
tradesStore.setTrade(trade);
|
||||
await trades.add(trade);
|
||||
tradeState = TradeIsCreatedSuccessfully(trade: trade);
|
||||
/// return after the first successful trade
|
||||
return;
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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
|
||||
|
@ -414,7 +506,7 @@ abstract class ExchangeViewModelBase with Store {
|
|||
final providers = providerList
|
||||
.where((provider) => provider.pairList
|
||||
.where((pair) =>
|
||||
pair.from == depositCurrency && pair.to == receiveCurrency)
|
||||
pair.from == (from ?? depositCurrency) && pair.to == (to ?? receiveCurrency))
|
||||
.isNotEmpty)
|
||||
.toList();
|
||||
|
||||
|
@ -422,27 +514,8 @@ abstract class ExchangeViewModelBase with Store {
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
depositAmount = '';
|
||||
receiveAmount = '';
|
||||
}
|
||||
}
|
||||
|
||||
ExchangeProvider _providerForPair({CryptoCurrency from, CryptoCurrency to}) {
|
||||
final providers = _providersForPair(from: from, to: to);
|
||||
return providers.isNotEmpty ? providers[0] : null;
|
||||
depositAmount = '';
|
||||
receiveAmount = '';
|
||||
}
|
||||
|
||||
void _initialPairBasedOnWallet() {
|
||||
|
@ -473,6 +546,45 @@ abstract class ExchangeViewModelBase with Store {
|
|||
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_not_created" : "Handel nicht erstellt.",
|
||||
"trade_not_created" : "Handel nicht erstellt",
|
||||
"trade_id_not_found" : "Handel ${tradeId} von ${title} 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_three": "Gebruik de digitale kaart online of met contactloze betaalmethoden.",
|
||||
"optionally_order_card": "Optioneel een fysieke kaart bestellen.",
|
||||
"hide_details" : "Details verbergen",
|
||||
"hide_details" : "Details verbergen",
|
||||
"show_details" : "Toon details",
|
||||
"upto": "tot ${value}",
|
||||
"discount": "Bespaar ${value}%",
|
||||
|
@ -634,5 +634,10 @@
|
|||
"contact_support": "Support kontaktieren",
|
||||
"gift_cards_unavailable": "Geschenkkarten können derzeit nur über Monero, Bitcoin und Litecoin erworben werden",
|
||||
"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_not_created" : "Trade not created.",
|
||||
"trade_not_created" : "Trade not created",
|
||||
"trade_id_not_found" : "Trade ${tradeId} of ${title} not found.",
|
||||
"trade_not_found" : "Trade not found.",
|
||||
|
||||
|
@ -634,5 +634,10 @@
|
|||
"contact_support": "Contact Support",
|
||||
"gift_cards_unavailable": "Gift cards are available for purchase only with Monero, Bitcoin, and Litecoin at this time",
|
||||
"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_not_created" : "Comercio no se crea.",
|
||||
"trade_not_created" : "Comercio no se crea",
|
||||
"trade_id_not_found" : "Comercio ${tradeId} de ${title} no encontrado.",
|
||||
"trade_not_found" : "Comercio no encontrado.",
|
||||
|
||||
|
@ -634,5 +634,10 @@
|
|||
"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",
|
||||
"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_not_created" : "Échange non créé.",
|
||||
"trade_not_created" : "Échange non créé",
|
||||
"trade_id_not_found" : "Échange ${tradeId} de ${title} introuvable.",
|
||||
"trade_not_found" : "Échange introuvable.",
|
||||
|
||||
|
@ -632,5 +632,10 @@
|
|||
"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",
|
||||
"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_not_created" : "व्यापार नहीं बनाया गया.",
|
||||
"trade_not_created" : "व्यापार नहीं बनाया गया",
|
||||
"trade_id_not_found" : "व्यापार ${tradeId} of ${title} नहीं मिला.",
|
||||
"trade_not_found" : "व्यापार नहीं मिला",
|
||||
|
||||
|
@ -634,5 +634,10 @@
|
|||
"contact_support": "सहायता से संपर्क करें",
|
||||
"gift_cards_unavailable": "उपहार कार्ड इस समय केवल मोनेरो, बिटकॉइन और लिटकोइन के माध्यम से खरीदने के लिए उपलब्ध हैं",
|
||||
"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_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_not_found" : "Razmjena nije pronađena.",
|
||||
|
||||
|
@ -634,5 +634,10 @@
|
|||
"contact_support": "Kontaktirajte podršku",
|
||||
"gift_cards_unavailable": "Poklon kartice trenutno su dostupne za kupnju samo putem Monera, Bitcoina i Litecoina",
|
||||
"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_not_created" : "Scambio non creato.",
|
||||
"trade_not_created" : "Scambio non creato",
|
||||
"trade_id_not_found" : "Scambio ${tradeId} di ${title} not trovato.",
|
||||
"trade_not_found" : "Scambio non trovato.",
|
||||
|
||||
|
@ -634,5 +634,10 @@
|
|||
"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",
|
||||
"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": "カード",
|
||||
"active": "アクティブ",
|
||||
"redeemed": "償還",
|
||||
"gift_card_balance_note": "残高が残っているギフトカードがここに表示されます",
|
||||
"gift_card_redeemed_note": "利用したギフトカードがここに表示されます",
|
||||
"logout": "ログアウト",
|
||||
"add_tip": "ヒントを追加",
|
||||
"percentageOf": "of ${amount}",
|
||||
"is_percentage": "is",
|
||||
"search_category": "検索カテゴリ",
|
||||
"mark_as_redeemed": "償還済みとしてマーク",
|
||||
"more_options": "その他のオプション",
|
||||
"awaiting_payment_confirmation": "支払い確認を待っています",
|
||||
"transaction_sent_notice": "1分経っても画面が進まない場合は、ブロックエクスプローラーとメールアドレスを確認してください。",
|
||||
"agree": "同意する",
|
||||
"in_store": "インストア",
|
||||
"generated_gift_card": "ギフトカードの生成",
|
||||
"payment_was_received": "お支払いを受け取りました。",
|
||||
"gift_card_balance_note": "残高が残っているギフトカードがここに表示されます",
|
||||
"gift_card_redeemed_note": "利用したギフトカードがここに表示されます",
|
||||
"logout": "ログアウト",
|
||||
"add_tip": "ヒントを追加",
|
||||
"percentageOf": "of ${amount}",
|
||||
"is_percentage": "is",
|
||||
"search_category": "検索カテゴリ",
|
||||
"mark_as_redeemed": "償還済みとしてマーク",
|
||||
"more_options": "その他のオプション",
|
||||
"awaiting_payment_confirmation": "支払い確認を待っています",
|
||||
"transaction_sent_notice": "1分経っても画面が進まない場合は、ブロックエクスプローラーとメールアドレスを確認してください。",
|
||||
"agree": "同意する",
|
||||
"in_store": "インストア",
|
||||
"generated_gift_card": "ギフトカードの生成",
|
||||
"payment_was_received": "お支払いを受け取りました。",
|
||||
"proceed_after_one_minute": "1分経っても画面が進まない場合は、メールを確認してください。",
|
||||
"order_id": "注文ID",
|
||||
"gift_card_is_generated": "ギフトカードが生成されます",
|
||||
|
@ -634,5 +634,10 @@
|
|||
"contact_support": "サポートに連絡する",
|
||||
"gift_cards_unavailable": "現時点では、ギフトカードはMonero、Bitcoin、Litecoinからのみ購入できます。",
|
||||
"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_not_created" : "거래가 생성되지 않았습니다.",
|
||||
"trade_not_created" : "거래가 생성되지 않았습니다",
|
||||
"trade_id_not_found" : "무역 ${tradeId} 의 ${title} 찾을 수 없습니다.",
|
||||
"trade_not_found" : "거래를 찾을 수 없습니다.",
|
||||
|
||||
|
@ -634,5 +634,10 @@
|
|||
"contact_support": "지원팀에 문의",
|
||||
"gift_cards_unavailable": "기프트 카드는 현재 Monero, Bitcoin 및 Litecoin을 통해서만 구매할 수 있습니다.",
|
||||
"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_not_created" : "Handel niet gecreëerd.",
|
||||
"trade_not_created" : "Handel niet gecreëerd",
|
||||
"trade_id_not_found" : "Handel ${tradeId} van ${title} niet gevonden.",
|
||||
"trade_not_found" : "Handel niet gevonden.",
|
||||
|
||||
|
@ -533,7 +533,7 @@
|
|||
"search_language": "Zoektaal",
|
||||
"search_currency": "Zoek valuta",
|
||||
"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",
|
||||
"market_place": "Marktplaats",
|
||||
"cake_pay_title": "Cake Pay-cadeaubonnen",
|
||||
|
@ -634,5 +634,10 @@
|
|||
"contact_support": "Contact opnemen met ondersteuning",
|
||||
"gift_cards_unavailable": "Cadeaubonnen kunnen momenteel alleen worden gekocht via Monero, Bitcoin en Litecoin",
|
||||
"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_not_created" : "Handel nie utworzony.",
|
||||
"trade_not_created" : "Handel nie utworzony",
|
||||
"trade_id_not_found" : "Handel ${tradeId} of ${title} nie znaleziono.",
|
||||
"trade_not_found" : "Nie znaleziono handlu.",
|
||||
|
||||
|
@ -634,5 +634,10 @@
|
|||
"contact_support": "Skontaktuj się z pomocą techniczną",
|
||||
"gift_cards_unavailable": "Karty podarunkowe można obecnie kupić tylko za pośrednictwem Monero, Bitcoin i Litecoin",
|
||||
"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_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_not_found" : "Troca não encontrada.",
|
||||
|
||||
|
@ -634,5 +634,10 @@
|
|||
"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",
|
||||
"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_not_created" : "Сделка не создана.",
|
||||
"trade_not_created" : "Сделка не создана",
|
||||
"trade_id_not_found" : "Сделка ${tradeId} ${title} не найдена.",
|
||||
"trade_not_found" : "Trade not found.",
|
||||
|
||||
|
@ -634,5 +634,10 @@
|
|||
"contact_support": "Связаться со службой поддержки",
|
||||
"gift_cards_unavailable": "В настоящее время подарочные карты можно приобрести только через Monero, Bitcoin и Litecoin.",
|
||||
"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_not_created" : "Операція не створена.",
|
||||
"trade_not_created" : "Операція не створена",
|
||||
"trade_id_not_found" : "Операція ${tradeId} ${title} не знайдена.",
|
||||
"trade_not_found" : "Операція не знайдена.",
|
||||
|
||||
|
@ -633,5 +633,10 @@
|
|||
"contact_support": "Звернутися до служби підтримки",
|
||||
"gift_cards_unavailable": "Наразі подарункові картки можна придбати лише через Monero, Bitcoin і Litecoin",
|
||||
"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_not_created" : "未建立交易.",
|
||||
"trade_not_created" : "未建立交易",
|
||||
"trade_id_not_found" : "交易方式 ${tradeId} 的 ${title} 未找到.",
|
||||
"trade_not_found" : "找不到交易.",
|
||||
|
||||
|
@ -632,5 +632,10 @@
|
|||
"contact_support": "联系支持",
|
||||
"gift_cards_unavailable": "目前只能通过门罗币、比特币和莱特币购买礼品卡",
|
||||
"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