mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-18 05:51:34 +02:00
Merge branch 'main' into CAKE-192-implement-unstoppable-domain
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
class BitcoinMnemonicIsIncorrectException implements Exception {
|
||||
@override
|
||||
String toString() =>
|
||||
'Bitcoin mnemonic has incorrect format. Mnemonic should contain 24 words separated by space.';
|
||||
'Bitcoin mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ abstract class ElectrumTransactionHistoryBase
|
||||
|
||||
@override
|
||||
void addMany(Map<String, ElectrumTransactionInfo> transactions) =>
|
||||
this.transactions.addAll(transactions);
|
||||
transactions.forEach((_, tx) => _updateOrInsert(tx));
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class BuyAmount {
|
||||
BuyAmount({
|
||||
@required this.sourceAmount,
|
||||
@required this.destAmount,
|
||||
this.minAmount = 0});
|
||||
|
||||
final double sourceAmount;
|
||||
final double destAmount;
|
||||
final int minAmount;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
||||
|
||||
class BuyException implements Exception {
|
||||
BuyException({@required this.description, @required this.text});
|
||||
|
||||
final BuyProviderDescription description;
|
||||
final String text;
|
||||
|
||||
@override
|
||||
String toString() => '${description.title}: $text';
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'package:cake_wallet/buy/buy_amount.dart';
|
||||
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||
|
||||
abstract class BuyProvider {
|
||||
BuyProvider({this.wallet, this.isTestEnvironment});
|
||||
|
||||
final WalletBase wallet;
|
||||
final bool isTestEnvironment;
|
||||
|
||||
String get title;
|
||||
BuyProviderDescription get description;
|
||||
String get trackUrl;
|
||||
|
||||
WalletType get walletType => wallet.type;
|
||||
String get walletAddress => wallet.address;
|
||||
String get walletId => wallet.id;
|
||||
|
||||
@override
|
||||
String toString() => title;
|
||||
|
||||
Future<String> requestUrl(String amount, String sourceCurrency);
|
||||
Future<Order> findOrderById(String id);
|
||||
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:cake_wallet/entities/enumerable_item.dart';
|
||||
|
||||
class BuyProviderDescription extends EnumerableItem<int>
|
||||
with Serializable<int> {
|
||||
const BuyProviderDescription({String title, int raw})
|
||||
: super(title: title, raw: raw);
|
||||
|
||||
static const wyre = BuyProviderDescription(title: 'Wyre', raw: 0);
|
||||
static const moonPay = BuyProviderDescription(title: 'MoonPay', raw: 1);
|
||||
|
||||
static BuyProviderDescription deserialize({int raw}) {
|
||||
switch (raw) {
|
||||
case 0:
|
||||
return wyre;
|
||||
case 1:
|
||||
return moonPay;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
||||
|
||||
Image getBuyProviderIcon(BuyProviderDescription providerDescription,
|
||||
{bool isWhiteIconColor = false}) {
|
||||
|
||||
final _wyreIcon =
|
||||
Image.asset('assets/images/wyre-icon.png', width: 36, height: 36);
|
||||
final _moonPayWhiteIcon =
|
||||
Image.asset('assets/images/moonpay-icon.png', color: Colors.white,
|
||||
width: 36, height: 34);
|
||||
final _moonPayBlackIcon =
|
||||
Image.asset('assets/images/moonpay-icon.png', color: Colors.black,
|
||||
width: 36, height: 34);
|
||||
|
||||
if (providerDescription != null) {
|
||||
switch (providerDescription) {
|
||||
case BuyProviderDescription.wyre:
|
||||
return _wyreIcon;
|
||||
case BuyProviderDescription.moonPay:
|
||||
return isWhiteIconColor ? _moonPayWhiteIcon : _moonPayBlackIcon;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import 'dart:convert';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:cake_wallet/buy/buy_exception.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:cake_wallet/buy/buy_amount.dart';
|
||||
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||
import 'package:cake_wallet/exchange/trade_state.dart';
|
||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||
|
||||
class MoonPayBuyProvider extends BuyProvider {
|
||||
MoonPayBuyProvider({WalletBase wallet, bool isTestEnvironment = false})
|
||||
: super(wallet: wallet, isTestEnvironment: isTestEnvironment) {
|
||||
baseUrl = isTestEnvironment ? _baseTestUrl : _baseProductUrl;
|
||||
}
|
||||
|
||||
static const _baseTestUrl = 'https://buy-staging.moonpay.com';
|
||||
static const _baseProductUrl = 'https://buy.moonpay.com';
|
||||
static const _apiUrl = 'https://api.moonpay.com';
|
||||
static const _currenciesSuffix = '/v3/currencies';
|
||||
static const _quoteSuffix = '/buy_quote';
|
||||
static const _transactionsSuffix = '/v1/transactions';
|
||||
static const _ipAddressSuffix = '/v4/ip_address';
|
||||
static const _apiKey = secrets.moonPayApiKey;
|
||||
static const _secretKey = secrets.moonPaySecretKey;
|
||||
|
||||
@override
|
||||
String get title => 'MoonPay';
|
||||
|
||||
@override
|
||||
BuyProviderDescription get description => BuyProviderDescription.moonPay;
|
||||
|
||||
String get currencyCode =>
|
||||
walletTypeToCryptoCurrency(walletType).title.toLowerCase();
|
||||
|
||||
@override
|
||||
String get trackUrl => baseUrl + '/transaction_receipt?transactionId=';
|
||||
|
||||
String baseUrl;
|
||||
|
||||
@override
|
||||
Future<String> requestUrl(String amount, String sourceCurrency) async {
|
||||
final enabledPaymentMethods =
|
||||
'credit_debit_card%2Capple_pay%2Cgoogle_pay%2Csamsung_pay'
|
||||
'%2Csepa_bank_transfer%2Cgbp_bank_transfer%2Cgbp_open_banking_payment';
|
||||
|
||||
final suffix = '?apiKey=' + _apiKey + '¤cyCode=' +
|
||||
currencyCode + '&enabledPaymentMethods=' + enabledPaymentMethods +
|
||||
'&walletAddress=' + walletAddress +
|
||||
'&baseCurrencyCode=' + sourceCurrency.toLowerCase() +
|
||||
'&baseCurrencyAmount=' + amount + '&lockAmount=true' +
|
||||
'&showAllCurrencies=false' + '&showWalletAddressForm=false';
|
||||
|
||||
final originalUrl = baseUrl + suffix;
|
||||
|
||||
final messageBytes = utf8.encode(suffix);
|
||||
final key = utf8.encode(_secretKey);
|
||||
final hmac = Hmac(sha256, key);
|
||||
final digest = hmac.convert(messageBytes);
|
||||
final signature = base64.encode(digest.bytes);
|
||||
final urlWithSignature = originalUrl +
|
||||
'&signature=${Uri.encodeComponent(signature)}';
|
||||
|
||||
return isTestEnvironment ? originalUrl : urlWithSignature;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) async {
|
||||
final url = _apiUrl + _currenciesSuffix + '/$currencyCode' +
|
||||
_quoteSuffix + '/?apiKey=' + _apiKey +
|
||||
'&baseCurrencyAmount=' + amount +
|
||||
'&baseCurrencyCode=' + sourceCurrency.toLowerCase();
|
||||
|
||||
final response = await get(url);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw BuyException(
|
||||
description: description,
|
||||
text: 'Quote is not found!');
|
||||
}
|
||||
|
||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||
final sourceAmount = responseJSON['totalAmount'] as double;
|
||||
final destAmount = responseJSON['quoteCurrencyAmount'] as double;
|
||||
final minSourceAmount = responseJSON['baseCurrency']['minAmount'] as int;
|
||||
|
||||
return BuyAmount(
|
||||
sourceAmount: sourceAmount,
|
||||
destAmount: destAmount,
|
||||
minAmount: minSourceAmount);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Order> findOrderById(String id) async {
|
||||
final url = _apiUrl + _transactionsSuffix + '/$id' +
|
||||
'?apiKey=' + _apiKey;
|
||||
|
||||
final response = await get(url);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw BuyException(
|
||||
description: description,
|
||||
text: 'Transaction $id is not found!');
|
||||
}
|
||||
|
||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||
final status = responseJSON['status'] as String;
|
||||
final state = TradeState.deserialize(raw: status);
|
||||
final createdAtRaw = responseJSON['createdAt'] as String;
|
||||
final createdAt = DateTime.parse(createdAtRaw).toLocal();
|
||||
final amount = responseJSON['quoteCurrencyAmount'] as double;
|
||||
|
||||
return Order(
|
||||
id: id,
|
||||
provider: description,
|
||||
transferId: id,
|
||||
state: state,
|
||||
createdAt: createdAt,
|
||||
amount: amount.toString(),
|
||||
receiveAddress: walletAddress,
|
||||
walletId: walletId
|
||||
);
|
||||
}
|
||||
|
||||
static Future<bool> onEnabled() async {
|
||||
final url = _apiUrl + _ipAddressSuffix + '?apiKey=' + _apiKey;
|
||||
var isBuyEnable = false;
|
||||
|
||||
final response = await get(url);
|
||||
|
||||
try {
|
||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||
isBuyEnable = responseJSON['isBuyAllowed'] as bool;
|
||||
} catch (e) {
|
||||
isBuyEnable = false;
|
||||
print(e.toString());
|
||||
}
|
||||
|
||||
return isBuyEnable;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:cake_wallet/exchange/trade_state.dart';
|
||||
import 'package:cake_wallet/entities/format_amount.dart';
|
||||
@@ -8,6 +9,7 @@ part 'order.g.dart';
|
||||
class Order extends HiveObject {
|
||||
Order(
|
||||
{this.id,
|
||||
BuyProviderDescription provider,
|
||||
this.transferId,
|
||||
this.from,
|
||||
this.to,
|
||||
@@ -16,7 +18,8 @@ class Order extends HiveObject {
|
||||
this.amount,
|
||||
this.receiveAddress,
|
||||
this.walletId})
|
||||
: stateRaw = state?.raw;
|
||||
: providerRaw = provider?.raw,
|
||||
stateRaw = state?.raw;
|
||||
|
||||
static const typeId = 8;
|
||||
static const boxName = 'Orders';
|
||||
@@ -51,5 +54,11 @@ class Order extends HiveObject {
|
||||
@HiveField(8)
|
||||
String walletId;
|
||||
|
||||
@HiveField(9)
|
||||
int providerRaw;
|
||||
|
||||
BuyProviderDescription get provider =>
|
||||
BuyProviderDescription.deserialize(raw: providerRaw);
|
||||
|
||||
String amountFormatted() => formatAmount(amount);
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import 'dart:convert';
|
||||
import 'package:cake_wallet/buy/buy_exception.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:cake_wallet/buy/buy_amount.dart';
|
||||
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||
import 'package:cake_wallet/exchange/trade_state.dart';
|
||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||
|
||||
class WyreBuyProvider extends BuyProvider {
|
||||
WyreBuyProvider({WalletBase wallet, bool isTestEnvironment = false})
|
||||
: super(wallet: wallet, isTestEnvironment: isTestEnvironment) {
|
||||
baseApiUrl = isTestEnvironment
|
||||
? _baseTestApiUrl
|
||||
: _baseProductApiUrl;
|
||||
}
|
||||
|
||||
static const _baseTestApiUrl = 'https://api.testwyre.com';
|
||||
static const _baseProductApiUrl = 'https://api.sendwyre.com';
|
||||
static const _trackTestUrl = 'https://dash.testwyre.com/track/';
|
||||
static const _trackProductUrl = 'https://dash.sendwyre.com/track/';
|
||||
static const _ordersSuffix = '/v3/orders';
|
||||
static const _reserveSuffix = '/reserve';
|
||||
static const _quoteSuffix = '/quote/partner';
|
||||
static const _timeStampSuffix = '?timestamp=';
|
||||
static const _transferSuffix = '/v2/transfer/';
|
||||
static const _trackSuffix = '/track';
|
||||
static const _countryCode = 'US';
|
||||
static const _secretKey = secrets.wyreSecretKey;
|
||||
static const _accountId = secrets.wyreAccountId;
|
||||
|
||||
@override
|
||||
String get title => 'Wyre';
|
||||
|
||||
@override
|
||||
BuyProviderDescription get description => BuyProviderDescription.wyre;
|
||||
|
||||
@override
|
||||
String get trackUrl => isTestEnvironment
|
||||
? _trackTestUrl
|
||||
: _trackProductUrl;
|
||||
|
||||
String baseApiUrl;
|
||||
|
||||
@override
|
||||
Future<String> requestUrl(String amount, String sourceCurrency) async {
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch.toString();
|
||||
final url = baseApiUrl + _ordersSuffix + _reserveSuffix +
|
||||
_timeStampSuffix + timestamp;
|
||||
final body = {
|
||||
'amount': amount,
|
||||
'sourceCurrency': sourceCurrency,
|
||||
'destCurrency': walletTypeToCryptoCurrency(walletType).title,
|
||||
'dest': walletTypeToString(walletType).toLowerCase() + ':' + walletAddress,
|
||||
'referrerAccountId': _accountId,
|
||||
'lockFields': ['amount', 'sourceCurrency', 'destCurrency', 'dest']
|
||||
};
|
||||
|
||||
final response = await post(url,
|
||||
headers: {
|
||||
'Authorization': 'Bearer $_secretKey',
|
||||
'Content-Type': 'application/json',
|
||||
'cache-control': 'no-cache'
|
||||
},
|
||||
body: json.encode(body));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw BuyException(
|
||||
description: description,
|
||||
text: 'Url $url is not found!');
|
||||
}
|
||||
|
||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||
final urlFromResponse = responseJSON['url'] as String;
|
||||
return urlFromResponse;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) async {
|
||||
final quoteUrl = _baseProductApiUrl + _ordersSuffix + _quoteSuffix;
|
||||
final body = {
|
||||
'amount': amount,
|
||||
'sourceCurrency': sourceCurrency,
|
||||
'destCurrency': walletTypeToCryptoCurrency(walletType).title,
|
||||
'dest': walletTypeToString(walletType).toLowerCase() + ':' + walletAddress,
|
||||
'accountId': _accountId,
|
||||
'country': _countryCode
|
||||
};
|
||||
|
||||
final response = await post(quoteUrl,
|
||||
headers: {
|
||||
'Authorization': 'Bearer $_secretKey',
|
||||
'Content-Type': 'application/json',
|
||||
'cache-control': 'no-cache'
|
||||
},
|
||||
body: json.encode(body));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw BuyException(
|
||||
description: description,
|
||||
text: 'Quote is not found!');
|
||||
}
|
||||
|
||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||
final sourceAmount = responseJSON['sourceAmount'] as double;
|
||||
final destAmount = responseJSON['destAmount'] as double;
|
||||
|
||||
return BuyAmount(sourceAmount: sourceAmount, destAmount: destAmount);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Order> findOrderById(String id) async {
|
||||
final orderUrl = baseApiUrl + _ordersSuffix + '/$id';
|
||||
final orderResponse = await get(orderUrl);
|
||||
|
||||
if (orderResponse.statusCode != 200) {
|
||||
throw BuyException(
|
||||
description: description,
|
||||
text: 'Order $id is not found!');
|
||||
}
|
||||
|
||||
final orderResponseJSON =
|
||||
json.decode(orderResponse.body) as Map<String, dynamic>;
|
||||
final transferId = orderResponseJSON['transferId'] as String;
|
||||
final from = orderResponseJSON['sourceCurrency'] as String;
|
||||
final to = orderResponseJSON['destCurrency'] as String;
|
||||
final status = orderResponseJSON['status'] as String;
|
||||
final state = TradeState.deserialize(raw: status.toLowerCase());
|
||||
final createdAtRaw = orderResponseJSON['createdAt'] as int;
|
||||
final createdAt =
|
||||
DateTime.fromMillisecondsSinceEpoch(createdAtRaw).toLocal();
|
||||
|
||||
final transferUrl =
|
||||
baseApiUrl + _transferSuffix + transferId + _trackSuffix;
|
||||
final transferResponse = await get(transferUrl);
|
||||
|
||||
if (transferResponse.statusCode != 200) {
|
||||
throw BuyException(
|
||||
description: description,
|
||||
text: 'Transfer $transferId is not found!');
|
||||
}
|
||||
|
||||
final transferResponseJSON =
|
||||
json.decode(transferResponse.body) as Map<String, dynamic>;
|
||||
final amount = transferResponseJSON['destAmount'] as double;
|
||||
|
||||
return Order(
|
||||
id: id,
|
||||
provider: description,
|
||||
transferId: transferId,
|
||||
from: from,
|
||||
to: to,
|
||||
state: state,
|
||||
createdAt: createdAt,
|
||||
amount: amount.toString(),
|
||||
receiveAddress: walletAddress,
|
||||
walletId: walletId
|
||||
);
|
||||
}
|
||||
}
|
||||
+43
-21
@@ -4,11 +4,9 @@ import 'package:cake_wallet/core/backup_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_service.dart';
|
||||
import 'package:cake_wallet/entities/biometric_auth.dart';
|
||||
import 'package:cake_wallet/entities/contact_record.dart';
|
||||
import 'package:cake_wallet/entities/load_current_wallet.dart';
|
||||
import 'package:cake_wallet/entities/order.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
import 'package:cake_wallet/entities/transaction_info.dart';
|
||||
import 'package:cake_wallet/entities/wyre_service.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet_service.dart';
|
||||
import 'package:cake_wallet/entities/contact.dart';
|
||||
import 'package:cake_wallet/entities/node.dart';
|
||||
@@ -16,7 +14,8 @@ import 'package:cake_wallet/exchange/trade.dart';
|
||||
import 'package:cake_wallet/reactions/on_authentication_state_change.dart';
|
||||
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
|
||||
import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
|
||||
|
||||
import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
|
||||
import 'package:cake_wallet/src/screens/buy/pre_order_page.dart';
|
||||
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/contact/contact_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart';
|
||||
@@ -43,7 +42,6 @@ import 'package:cake_wallet/src/screens/transaction_details/transaction_details_
|
||||
import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wyre/wyre_page.dart';
|
||||
import 'package:cake_wallet/store/dashboard/orders_store.dart';
|
||||
import 'package:cake_wallet/store/node_list_store.dart';
|
||||
import 'package:cake_wallet/store/secret_store.dart';
|
||||
@@ -62,6 +60,8 @@ import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.d
|
||||
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
|
||||
import 'package:cake_wallet/store/wallet_list_store.dart';
|
||||
import 'package:cake_wallet/view_model/backup_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_amount_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
|
||||
@@ -90,7 +90,6 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_restore_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wyre_view_model.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
@@ -113,6 +112,7 @@ import 'package:cake_wallet/store/templates/exchange_template_store.dart';
|
||||
import 'package:cake_wallet/entities/template.dart';
|
||||
import 'package:cake_wallet/exchange/exchange_template.dart';
|
||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||
import 'package:cake_wallet/entities/push_notifications_service.dart';
|
||||
|
||||
final getIt = GetIt.instance;
|
||||
|
||||
@@ -152,6 +152,7 @@ Future setup(
|
||||
final isBitcoinBuyEnabled = (secrets.wyreSecretKey?.isNotEmpty ?? false) &&
|
||||
(secrets.wyreApiKey?.isNotEmpty ?? false) &&
|
||||
(secrets.wyreAccountId?.isNotEmpty ?? false);
|
||||
|
||||
final settingsStore = await SettingsStoreBase.load(
|
||||
nodeSource: _nodeSource, isBitcoinBuyEnabled: isBitcoinBuyEnabled);
|
||||
|
||||
@@ -232,6 +233,9 @@ Future setup(
|
||||
settingsStore: getIt.get<SettingsStore>(),
|
||||
fiatConvertationStore: getIt.get<FiatConversionStore>()));
|
||||
|
||||
|
||||
getIt.registerFactory(() => PushNotificationsService());
|
||||
|
||||
getIt.registerFactory(() => DashboardViewModel(
|
||||
balanceViewModel: getIt.get<BalanceViewModel>(),
|
||||
appStore: getIt.get<AppStore>(),
|
||||
@@ -239,9 +243,8 @@ Future setup(
|
||||
tradeFilterStore: getIt.get<TradeFilterStore>(),
|
||||
transactionFilterStore: getIt.get<TransactionFilterStore>(),
|
||||
settingsStore: settingsStore,
|
||||
ordersSource: _ordersSource,
|
||||
ordersStore: getIt.get<OrdersStore>(),
|
||||
wyreViewModel: getIt.get<WyreViewModel>()));
|
||||
pushNotificationsService: getIt.get<PushNotificationsService>()));
|
||||
|
||||
getIt.registerFactory<AuthService>(() => AuthService(
|
||||
secureStorage: getIt.get<FlutterSecureStorage>(),
|
||||
@@ -487,10 +490,14 @@ Future setup(
|
||||
|
||||
getIt
|
||||
.registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>(
|
||||
(TransactionInfo transactionInfo, _) => TransactionDetailsViewModel(
|
||||
transactionInfo: transactionInfo,
|
||||
transactionDescriptionBox: _transactionDescriptionBox,
|
||||
settingsStore: getIt.get<SettingsStore>()));
|
||||
(TransactionInfo transactionInfo, _) {
|
||||
final wallet = getIt.get<AppStore>().wallet;
|
||||
return TransactionDetailsViewModel(
|
||||
transactionInfo: transactionInfo,
|
||||
transactionDescriptionBox: _transactionDescriptionBox,
|
||||
wallet: wallet,
|
||||
settingsStore: getIt.get<SettingsStore>());
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<TransactionDetailsPage, TransactionInfo, void>(
|
||||
(TransactionInfo transactionInfo, _) => TransactionDetailsPage(
|
||||
@@ -537,22 +544,37 @@ Future setup(
|
||||
getIt.registerFactoryParam<TradeDetailsPage, Trade, void>((Trade trade, _) =>
|
||||
TradeDetailsPage(getIt.get<TradeDetailsViewModel>(param1: trade)));
|
||||
|
||||
getIt.registerFactory(() => BuyAmountViewModel());
|
||||
|
||||
getIt.registerFactory(() {
|
||||
return WyreService(appStore: getIt.get<AppStore>());
|
||||
final wallet = getIt.get<AppStore>().wallet;
|
||||
|
||||
return BuyViewModel(_ordersSource, getIt.get<OrdersStore>(),
|
||||
getIt.get<SettingsStore>(), getIt.get<BuyAmountViewModel>(),
|
||||
wallet: wallet);
|
||||
});
|
||||
|
||||
getIt.registerFactory(() {
|
||||
return WyreViewModel(ordersSource, getIt.get<OrdersStore>(),
|
||||
wyreService: getIt.get<WyreService>());
|
||||
return PreOrderPage(buyViewModel: getIt.get<BuyViewModel>());
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<WyrePage, String, void>((String url, _) =>
|
||||
WyrePage(getIt.get<WyreViewModel>(),
|
||||
ordersStore: getIt.get<OrdersStore>(), url: url));
|
||||
getIt.registerFactoryParam<BuyWebViewPage, List, void>(
|
||||
(List args, _) {
|
||||
final url = args.first as String;
|
||||
final buyViewModel = args[1] as BuyViewModel;
|
||||
|
||||
getIt.registerFactoryParam<OrderDetailsViewModel, Order, void>((order, _) =>
|
||||
OrderDetailsViewModel(
|
||||
wyreViewModel: getIt.get<WyreViewModel>(), orderForDetails: order));
|
||||
return BuyWebViewPage(buyViewModel: buyViewModel,
|
||||
ordersStore: getIt.get<OrdersStore>(), url: url);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<OrderDetailsViewModel, Order, void>(
|
||||
(order, _) {
|
||||
final wallet = getIt.get<AppStore>().wallet;
|
||||
|
||||
return OrderDetailsViewModel(
|
||||
wallet: wallet,
|
||||
orderForDetails: order);
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<OrderDetailsPage, Order, void>((Order order, _) =>
|
||||
OrderDetailsPage(getIt.get<OrderDetailsViewModel>(param1: order)));
|
||||
|
||||
@@ -7,7 +7,7 @@ class BiometricAuth {
|
||||
|
||||
Future<bool> isAuthenticated() async {
|
||||
try {
|
||||
return await _localAuth.authenticateWithBiometrics(
|
||||
return await _localAuth.authenticate(
|
||||
localizedReason: S.current.biometric_auth_reason,
|
||||
useErrorDialogs: true,
|
||||
stickyAuth: false);
|
||||
|
||||
@@ -19,4 +19,5 @@ class PreferencesKey {
|
||||
'current_default_settings_migration_version';
|
||||
static const moneroTransactionPriority = 'current_fee_priority_monero';
|
||||
static const bitcoinTransactionPriority = 'current_fee_priority_bitcoin';
|
||||
static const shouldShowReceiveWarning = 'should_show_receive_warning';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import 'dart:io';
|
||||
import 'package:cake_wallet/main.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
|
||||
class PushNotificationsService {
|
||||
PushNotificationsService._();
|
||||
|
||||
factory PushNotificationsService() => _instance;
|
||||
|
||||
static final PushNotificationsService _instance = PushNotificationsService._();
|
||||
static Future<dynamic> _onBackgroundMessage(Map<String, dynamic> message) async {}
|
||||
final _firebaseMessaging = FirebaseMessaging();
|
||||
bool _initialized = false;
|
||||
|
||||
Future<void> init() async {
|
||||
if (_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
_firebaseMessaging.requestNotificationPermissions();
|
||||
_firebaseMessaging.configure(
|
||||
onMessage: (message) async {
|
||||
Map<dynamic, dynamic> alert = <dynamic, dynamic>{};
|
||||
String msg = '';
|
||||
String title = '';
|
||||
|
||||
if (Platform.isIOS) {
|
||||
alert = message['aps']['alert'] as Map<dynamic, dynamic> ?? <dynamic, dynamic>{};
|
||||
msg = alert['body'] as String ?? '';
|
||||
title = alert['title'] as String ?? '';
|
||||
}
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
msg = message['notification']['body'] as String ?? '';
|
||||
title = message['notification']['title'] as String ?? '';
|
||||
}
|
||||
|
||||
await showBar<void>(navigatorKey.currentContext, msg, titleText: title, duration: null);
|
||||
},
|
||||
onBackgroundMessage: _onBackgroundMessage);
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ String walletTypeToDisplayName(WalletType type) {
|
||||
case WalletType.bitcoin:
|
||||
return 'Bitcoin (Electrum)';
|
||||
case WalletType.litecoin:
|
||||
return 'Litecoin';
|
||||
return 'Litecoin (Electrum)';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
class WyreException implements Exception {
|
||||
WyreException(this.description);
|
||||
|
||||
String description;
|
||||
|
||||
@override
|
||||
String toString() => description;
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'package:cake_wallet/entities/wyre_exception.dart';
|
||||
import 'package:cake_wallet/exchange/trade_state.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||
import 'package:cake_wallet/entities/order.dart';
|
||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||
|
||||
class WyreService {
|
||||
WyreService({@required this.appStore, this.isTestEnvironment = false}) {
|
||||
baseApiUrl = isTestEnvironment ? _baseTestApiUrl : _baseProductApiUrl;
|
||||
trackUrl = isTestEnvironment ? _trackTestUrl : _trackProductUrl;
|
||||
}
|
||||
|
||||
static const _baseTestApiUrl = 'https://api.testwyre.com';
|
||||
static const _baseProductApiUrl = 'https://api.sendwyre.com';
|
||||
static const _trackTestUrl = 'https://dash.testwyre.com/track/';
|
||||
static const _trackProductUrl = 'https://dash.sendwyre.com/track/';
|
||||
static const _ordersSuffix = '/v3/orders';
|
||||
static const _reserveSuffix = '/reserve';
|
||||
static const _timeStampSuffix = '?timestamp=';
|
||||
static const _transferSuffix = '/v2/transfer/';
|
||||
static const _trackSuffix = '/track';
|
||||
|
||||
final bool isTestEnvironment;
|
||||
final AppStore appStore;
|
||||
|
||||
WalletType get walletType => appStore.wallet.type;
|
||||
String get walletAddress => appStore.wallet.address;
|
||||
String get walletId => appStore.wallet.id;
|
||||
|
||||
String baseApiUrl;
|
||||
String trackUrl;
|
||||
|
||||
Future<String> getWyreUrl() async {
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch.toString();
|
||||
final url = baseApiUrl +
|
||||
_ordersSuffix +
|
||||
_reserveSuffix +
|
||||
_timeStampSuffix +
|
||||
timestamp;
|
||||
final secretKey = secrets.wyreSecretKey;
|
||||
final accountId = secrets.wyreAccountId;
|
||||
final body = {
|
||||
'destCurrency': walletTypeToCryptoCurrency(walletType).title,
|
||||
'dest':
|
||||
walletTypeToString(walletType).toLowerCase() + ':' + walletAddress,
|
||||
'referrerAccountId': accountId,
|
||||
'lockFields': ['destCurrency', 'dest']
|
||||
};
|
||||
|
||||
final response = await post(url,
|
||||
headers: {
|
||||
'Authorization': 'Bearer $secretKey',
|
||||
'Content-Type': 'application/json',
|
||||
'cache-control': 'no-cache'
|
||||
},
|
||||
body: json.encode(body));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw WyreException('Url $url is not found!');
|
||||
}
|
||||
|
||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||
final urlFromResponse = responseJSON['url'] as String;
|
||||
return urlFromResponse;
|
||||
}
|
||||
|
||||
Future<Order> findOrderById(String id) async {
|
||||
final orderUrl = baseApiUrl + _ordersSuffix + '/$id';
|
||||
final orderResponse = await get(orderUrl);
|
||||
|
||||
if (orderResponse.statusCode != 200) {
|
||||
throw WyreException('Order $id is not found!');
|
||||
}
|
||||
|
||||
final orderResponseJSON =
|
||||
json.decode(orderResponse.body) as Map<String, dynamic>;
|
||||
final transferId = orderResponseJSON['transferId'] as String;
|
||||
final from = orderResponseJSON['sourceCurrency'] as String;
|
||||
final to = orderResponseJSON['destCurrency'] as String;
|
||||
final status = orderResponseJSON['status'] as String;
|
||||
final state = TradeState.deserialize(raw: status.toLowerCase());
|
||||
final createdAtRaw = orderResponseJSON['createdAt'] as int;
|
||||
final createdAt =
|
||||
DateTime.fromMillisecondsSinceEpoch(createdAtRaw).toLocal();
|
||||
|
||||
final transferUrl =
|
||||
baseApiUrl + _transferSuffix + transferId + _trackSuffix;
|
||||
final transferResponse = await get(transferUrl);
|
||||
|
||||
if (transferResponse.statusCode != 200) {
|
||||
throw WyreException('Transfer $transferId is not found!');
|
||||
}
|
||||
|
||||
final transferResponseJSON =
|
||||
json.decode(transferResponse.body) as Map<String, dynamic>;
|
||||
final amount = transferResponseJSON['destAmount'] as double;
|
||||
|
||||
return Order(
|
||||
id: id,
|
||||
transferId: transferId,
|
||||
from: from,
|
||||
to: to,
|
||||
state: state,
|
||||
createdAt: createdAt,
|
||||
amount: amount.toString(),
|
||||
receiveAddress: walletAddress,
|
||||
walletId: walletId);
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,12 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
|
||||
static const finished = TradeState(raw: 'finished', title: 'Finished');
|
||||
static const waiting = TradeState(raw: 'waiting', title: 'Waiting');
|
||||
static const processing = TradeState(raw: 'processing', title: 'Processing');
|
||||
static const waitingPayment =
|
||||
TradeState(raw: 'waitingPayment', title: 'Waiting payment');
|
||||
static const waitingAuthorization =
|
||||
TradeState(raw: 'waitingAuthorization', title: 'Waiting authorization');
|
||||
static const failed = TradeState(raw: 'failed', title: 'Failed');
|
||||
static const completed = TradeState(raw: 'completed', title: 'Completed');
|
||||
|
||||
static TradeState deserialize({String raw}) {
|
||||
switch (raw) {
|
||||
@@ -62,6 +68,14 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
|
||||
return waiting;
|
||||
case 'processing':
|
||||
return processing;
|
||||
case 'waitingPayment':
|
||||
return waitingPayment;
|
||||
case 'waitingAuthorization':
|
||||
return waitingAuthorization;
|
||||
case 'failed':
|
||||
return failed;
|
||||
case 'completed':
|
||||
return completed;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import 'package:cake_wallet/entities/language_service.dart';
|
||||
import 'package:cake_wallet/entities/order.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'package:cw_monero/transaction_history.dart';
|
||||
|
||||
class MoneroTransactionInfo extends TransactionInfo {
|
||||
MoneroTransactionInfo(this.id, this.height, this.direction, this.date,
|
||||
this.isPending, this.amount, this.accountIndex, this.fee);
|
||||
this.isPending, this.amount, this.accountIndex, this.addressIndex, this.fee);
|
||||
|
||||
MoneroTransactionInfo.fromMap(Map map)
|
||||
: id = (map['hash'] ?? '') as String,
|
||||
@@ -21,6 +21,7 @@ class MoneroTransactionInfo extends TransactionInfo {
|
||||
isPending = parseBoolFromString(map['isPending'] as String),
|
||||
amount = map['amount'] as int,
|
||||
accountIndex = int.parse(map['accountIndex'] as String),
|
||||
addressIndex = map['addressIndex'] as int,
|
||||
key = getTxKey((map['hash'] ?? '') as String),
|
||||
fee = map['fee'] as int ?? 0;
|
||||
|
||||
@@ -33,6 +34,7 @@ class MoneroTransactionInfo extends TransactionInfo {
|
||||
isPending = row.isPending != 0,
|
||||
amount = row.getAmount(),
|
||||
accountIndex = row.subaddrAccount,
|
||||
addressIndex = row.subaddrIndex,
|
||||
key = getTxKey(row.getHash()),
|
||||
fee = row.fee;
|
||||
|
||||
@@ -44,6 +46,7 @@ class MoneroTransactionInfo extends TransactionInfo {
|
||||
final bool isPending;
|
||||
final int amount;
|
||||
final int fee;
|
||||
final int addressIndex;
|
||||
String recipientAddress;
|
||||
String key;
|
||||
|
||||
|
||||
@@ -271,6 +271,11 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||
await walletInfo.save();
|
||||
}
|
||||
|
||||
String getTransactionAddress(int accountIndex, int addressIndex) =>
|
||||
monero_wallet.getAddress(
|
||||
accountIndex: accountIndex,
|
||||
addressIndex: addressIndex);
|
||||
|
||||
@override
|
||||
Future<Map<String, MoneroTransactionInfo>> fetchTransactions() async {
|
||||
monero_transaction_history.refreshTransactions();
|
||||
|
||||
@@ -23,6 +23,7 @@ class Palette {
|
||||
static const Color persianRed = Color.fromRGBO(206, 55, 55, 1.0);
|
||||
static const Color blueCraiola = Color.fromRGBO(69, 110, 255, 1.0);
|
||||
static const Color blueGreyCraiola = Color.fromRGBO(106, 177, 207, 1.0);
|
||||
static const Color greyBlueCraiola = Color.fromRGBO(116, 139, 219, 1.0);
|
||||
static const Color darkBlueCraiola = Color.fromRGBO(53, 86, 136, 1.0);
|
||||
static const Color pinkFlamingo = Color.fromRGBO(240, 60, 243, 1.0);
|
||||
static const Color redHat = Color.fromRGBO(209, 68, 37, 1.0);
|
||||
|
||||
+13
-4
@@ -1,16 +1,18 @@
|
||||
import 'package:cake_wallet/entities/contact_record.dart';
|
||||
import 'package:cake_wallet/entities/order.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
|
||||
import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
|
||||
import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
|
||||
import 'package:cake_wallet/src/screens/buy/pre_order_page.dart';
|
||||
import 'package:cake_wallet/src/screens/order_details/order_details_page.dart';
|
||||
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
|
||||
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
|
||||
import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
|
||||
import 'package:cake_wallet/src/screens/support/support_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wyre/wyre_page.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -299,10 +301,17 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
builder: (_) =>
|
||||
getIt.get<OrderDetailsPage>(param1: settings.arguments as Order));
|
||||
|
||||
case Routes.wyre:
|
||||
case Routes.preOrder:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) =>
|
||||
getIt.get<WyrePage>(param1: settings.arguments as String));
|
||||
getIt.get<PreOrderPage>());
|
||||
|
||||
case Routes.buyWebView:
|
||||
final args = settings.arguments as List;
|
||||
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) =>
|
||||
getIt.get<BuyWebViewPage>(param1: args));
|
||||
|
||||
case Routes.restoreWalletFromSeedDetails:
|
||||
final args = settings.arguments as List;
|
||||
|
||||
+2
-1
@@ -53,5 +53,6 @@ class Routes {
|
||||
static const restoreFromBackup = '/restore_from_backup';
|
||||
static const support = '/support';
|
||||
static const orderDetails = '/order_details';
|
||||
static const wyre = '/wyre';
|
||||
static const preOrder = '/pre_order';
|
||||
static const buyWebView = '/buy_web_view';
|
||||
}
|
||||
@@ -1,20 +1,23 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/wyre/wyre_buy_provider.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/store/dashboard/orders_store.dart';
|
||||
import 'package:cake_wallet/view_model/wyre_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
class WyrePage extends BasePage {
|
||||
WyrePage(this.wyreViewModel,
|
||||
{@required this.ordersStore, @required this.url});
|
||||
class BuyWebViewPage extends BasePage {
|
||||
BuyWebViewPage({@required this.buyViewModel,
|
||||
@required this.ordersStore, @required this.url});
|
||||
|
||||
final OrdersStore ordersStore;
|
||||
final String url;
|
||||
final WyreViewModel wyreViewModel;
|
||||
final BuyViewModel buyViewModel;
|
||||
|
||||
@override
|
||||
String get title => S.current.buy;
|
||||
@@ -27,26 +30,27 @@ class WyrePage extends BasePage {
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) =>
|
||||
WyrePageBody(wyreViewModel, ordersStore: ordersStore, url: url);
|
||||
BuyWebViewPageBody(buyViewModel, ordersStore: ordersStore, url: url);
|
||||
}
|
||||
|
||||
class WyrePageBody extends StatefulWidget {
|
||||
WyrePageBody(this.wyreViewModel, {this.ordersStore, this.url});
|
||||
class BuyWebViewPageBody extends StatefulWidget {
|
||||
BuyWebViewPageBody(this.buyViewModel, {this.ordersStore, this.url});
|
||||
|
||||
final OrdersStore ordersStore;
|
||||
final String url;
|
||||
final WyreViewModel wyreViewModel;
|
||||
final BuyViewModel buyViewModel;
|
||||
|
||||
@override
|
||||
WyrePageBodyState createState() => WyrePageBodyState();
|
||||
BuyWebViewPageBodyState createState() => BuyWebViewPageBodyState();
|
||||
}
|
||||
|
||||
class WyrePageBodyState extends State<WyrePageBody> {
|
||||
class BuyWebViewPageBodyState extends State<BuyWebViewPageBody> {
|
||||
String orderId;
|
||||
WebViewController _webViewController;
|
||||
GlobalKey _webViewkey;
|
||||
Timer _timer;
|
||||
bool _isSaving;
|
||||
BuyProvider _provider;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -54,35 +58,17 @@ class WyrePageBodyState extends State<WyrePageBody> {
|
||||
_webViewkey = GlobalKey();
|
||||
_isSaving = false;
|
||||
widget.ordersStore.orderId = '';
|
||||
_provider = widget.buyViewModel.selectedProvider;
|
||||
|
||||
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
|
||||
|
||||
_timer?.cancel();
|
||||
_timer = Timer.periodic(Duration(seconds: 1), (timer) async {
|
||||
if (_provider is WyreBuyProvider) {
|
||||
_saveOrder(keyword: 'completed', splitSymbol: '/');
|
||||
}
|
||||
|
||||
try {
|
||||
if (_webViewController == null || _isSaving) {
|
||||
return;
|
||||
}
|
||||
|
||||
final url = await _webViewController.currentUrl();
|
||||
|
||||
if (url.contains('completed')) {
|
||||
final urlParts = url.split('/');
|
||||
orderId = urlParts.last;
|
||||
widget.ordersStore.orderId = orderId;
|
||||
|
||||
if (orderId.isNotEmpty) {
|
||||
_isSaving = true;
|
||||
await widget.wyreViewModel.saveOrder(orderId);
|
||||
timer.cancel();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
_isSaving = false;
|
||||
print(e);
|
||||
}
|
||||
});
|
||||
if (_provider is MoonPayBuyProvider) {
|
||||
_saveOrder(keyword: 'transactionId', splitSymbol: '=');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -94,4 +80,33 @@ class WyrePageBodyState extends State<WyrePageBody> {
|
||||
onWebViewCreated: (WebViewController controller) =>
|
||||
setState(() => _webViewController = controller));
|
||||
}
|
||||
|
||||
void _saveOrder({String keyword, String splitSymbol}) {
|
||||
_timer?.cancel();
|
||||
_timer = Timer.periodic(Duration(seconds: 1), (timer) async {
|
||||
|
||||
try {
|
||||
if (_webViewController == null || _isSaving) {
|
||||
return;
|
||||
}
|
||||
|
||||
final url = await _webViewController.currentUrl();
|
||||
|
||||
if (url.contains(keyword)) {
|
||||
final urlParts = url.split(splitSymbol);
|
||||
orderId = urlParts.last;
|
||||
widget.ordersStore.orderId = orderId;
|
||||
|
||||
if (orderId.isNotEmpty) {
|
||||
_isSaving = true;
|
||||
await widget.buyViewModel.saveOrder(orderId);
|
||||
timer.cancel();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
_isSaving = false;
|
||||
print(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
import 'dart:ui';
|
||||
import 'package:cake_wallet/buy/buy_amount.dart';
|
||||
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
|
||||
import 'package:cake_wallet/src/screens/buy/widgets/buy_list_item.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_view_model.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/trail_button.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class PreOrderPage extends BasePage {
|
||||
PreOrderPage({@required this.buyViewModel})
|
||||
: _amountFocus = FocusNode(),
|
||||
_amountController = TextEditingController() {
|
||||
|
||||
_amountController.addListener(() {
|
||||
final amount = _amountController.text;
|
||||
|
||||
if (amount != buyViewModel.buyAmountViewModel.amount) {
|
||||
buyViewModel.buyAmountViewModel.amount = amount;
|
||||
buyViewModel.selectedProvider = null;
|
||||
}
|
||||
});
|
||||
|
||||
reaction((_) => buyViewModel.buyAmountViewModel.amount, (String amount) {
|
||||
if (_amountController.text != amount) {
|
||||
_amountController.text = amount;
|
||||
}
|
||||
if (amount.isEmpty) {
|
||||
buyViewModel.selectedProvider = null;
|
||||
buyViewModel.isShowProviderButtons = false;
|
||||
} else {
|
||||
buyViewModel.isShowProviderButtons = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static const _amountPattern = '^([0-9]+([.\,][0-9]{0,2})?|[.\,][0-9]{1,2})\$';
|
||||
|
||||
final BuyViewModel buyViewModel;
|
||||
final FocusNode _amountFocus;
|
||||
final TextEditingController _amountController;
|
||||
|
||||
@override
|
||||
String get title => S.current.buy_bitcoin;
|
||||
|
||||
@override
|
||||
Color get titleColor => Colors.white;
|
||||
|
||||
@override
|
||||
bool get resizeToAvoidBottomInset => false;
|
||||
|
||||
@override
|
||||
bool get extendBodyBehindAppBar => true;
|
||||
|
||||
@override
|
||||
AppBarStyle get appBarStyle => AppBarStyle.transparent;
|
||||
|
||||
@override
|
||||
Widget trailing(context) => TrailButton(
|
||||
caption: S.of(context).clear,
|
||||
onPressed: () {
|
||||
buyViewModel.reset();
|
||||
});
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return KeyboardActions(
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context).accentTextTheme.body2
|
||||
.backgroundColor,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
focusNode: _amountFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
),
|
||||
]),
|
||||
child: Container(
|
||||
height: 0,
|
||||
color: Theme.of(context).backgroundColor,
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.only(bottom: 24),
|
||||
content: Observer(builder: (_) => Column(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(24),
|
||||
bottomRight: Radius.circular(24)),
|
||||
gradient: LinearGradient(colors: [
|
||||
Theme.of(context).primaryTextTheme.subhead.color,
|
||||
Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.subhead
|
||||
.decorationColor,
|
||||
], begin: Alignment.topLeft, end: Alignment.bottomRight),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 100, bottom: 65),
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 210,
|
||||
child: BaseTextFormField(
|
||||
focusNode: _amountFocus,
|
||||
controller: _amountController,
|
||||
keyboardType:
|
||||
TextInputType.numberWithOptions(
|
||||
signed: false, decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter
|
||||
.allow(RegExp(_amountPattern))
|
||||
],
|
||||
prefixIcon: Padding(
|
||||
padding: EdgeInsets.only(top: 2),
|
||||
child:
|
||||
Text(buyViewModel.fiatCurrency.title + ': ',
|
||||
style: TextStyle(
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
)),
|
||||
),
|
||||
hintText: '0.00',
|
||||
borderColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.body2
|
||||
.decorationColor,
|
||||
borderWidth: 0.5,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 36,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white),
|
||||
placeholderTextStyle: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.headline
|
||||
.decorationColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 36),
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
if (buyViewModel.isShowProviderButtons) Padding(
|
||||
padding: EdgeInsets.only(top: 38, bottom: 18),
|
||||
child: Text(
|
||||
S.of(context).buy_with + ':',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryTextTheme.title.color,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold
|
||||
),
|
||||
)
|
||||
),
|
||||
if (buyViewModel.isShowProviderButtons)
|
||||
...buyViewModel.items.map(
|
||||
(item) => Observer(builder: (_) =>
|
||||
FutureBuilder<BuyAmount>(
|
||||
future: item.buyAmount,
|
||||
builder: (context, AsyncSnapshot<BuyAmount> snapshot) {
|
||||
double sourceAmount;
|
||||
double destAmount;
|
||||
int minAmount;
|
||||
|
||||
if (snapshot.hasData) {
|
||||
sourceAmount = snapshot.data.sourceAmount;
|
||||
destAmount = snapshot.data.destAmount;
|
||||
minAmount = snapshot.data.minAmount;
|
||||
} else {
|
||||
sourceAmount = 0.0;
|
||||
destAmount = 0.0;
|
||||
minAmount = 0;
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding:
|
||||
EdgeInsets.only(left: 15, top: 20, right: 15),
|
||||
child: Observer(builder: (_) {
|
||||
return BuyListItem(
|
||||
selectedProvider:
|
||||
buyViewModel.selectedProvider,
|
||||
provider: item.provider,
|
||||
sourceAmount: sourceAmount,
|
||||
sourceCurrency: buyViewModel.fiatCurrency,
|
||||
destAmount: destAmount,
|
||||
destCurrency: buyViewModel.cryptoCurrency,
|
||||
onTap: ((buyViewModel.doubleAmount != 0.0)
|
||||
&& (snapshot.hasData)) ? () =>
|
||||
onSelectBuyProvider(
|
||||
context: context,
|
||||
provider: item.provider,
|
||||
sourceAmount: sourceAmount,
|
||||
minAmount: minAmount
|
||||
) : null
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
))
|
||||
)
|
||||
],
|
||||
)),
|
||||
bottomSectionPadding:
|
||||
EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
bottomSection: Observer(builder: (_) {
|
||||
return LoadingPrimaryButton(
|
||||
onPressed: () => onPresentProvider(context: context),
|
||||
text: buyViewModel.selectedProvider == null
|
||||
? S.of(context).buy
|
||||
: S.of(context).buy_with +
|
||||
' ${buyViewModel.selectedProvider
|
||||
.description.title}',
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
isLoading: buyViewModel.isRunning,
|
||||
isDisabled: (buyViewModel.selectedProvider == null) ||
|
||||
buyViewModel.isDisabled
|
||||
);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void onSelectBuyProvider({BuildContext context, BuyProvider provider,
|
||||
double sourceAmount, int minAmount}) {
|
||||
|
||||
if ((provider is MoonPayBuyProvider)&&
|
||||
(buyViewModel.buyAmountViewModel.doubleAmount < minAmount)) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: 'MoonPay',
|
||||
alertContent: S.of(context).moonpay_alert_text(
|
||||
minAmount.toString(),
|
||||
buyViewModel.fiatCurrency.toString()),
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
return;
|
||||
}
|
||||
buyViewModel.selectedProvider = provider;
|
||||
sourceAmount > 0
|
||||
? buyViewModel.isDisabled = false
|
||||
: buyViewModel.isDisabled = true;
|
||||
}
|
||||
|
||||
Future<void> onPresentProvider({BuildContext context}) async {
|
||||
if (buyViewModel.isRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
buyViewModel.isRunning = true;
|
||||
final url = await buyViewModel.fetchUrl();
|
||||
|
||||
if (url.isNotEmpty) {
|
||||
if (buyViewModel.selectedProvider is MoonPayBuyProvider) {
|
||||
if (await canLaunch(url)) await launch(url);
|
||||
} else {
|
||||
await Navigator.of(context)
|
||||
.pushNamed(Routes.buyWebView,
|
||||
arguments: [url, buyViewModel]);
|
||||
}
|
||||
}
|
||||
|
||||
buyViewModel.reset();
|
||||
buyViewModel.isRunning = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/get_buy_provider_icon.dart';
|
||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BuyListItem extends StatelessWidget {
|
||||
BuyListItem({
|
||||
@required this.selectedProvider,
|
||||
@required this.provider,
|
||||
@required this.sourceAmount,
|
||||
@required this.sourceCurrency,
|
||||
@required this.destAmount,
|
||||
@required this.destCurrency,
|
||||
@required this.onTap
|
||||
});
|
||||
|
||||
final BuyProvider selectedProvider;
|
||||
final BuyProvider provider;
|
||||
final double sourceAmount;
|
||||
final FiatCurrency sourceCurrency;
|
||||
final double destAmount;
|
||||
final CryptoCurrency destCurrency;
|
||||
final void Function() onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isSelected = selectedProvider?.description == provider.description;
|
||||
|
||||
final providerIcon = getBuyProviderIcon(provider.description,
|
||||
isWhiteIconColor: isSelected);
|
||||
|
||||
final backgroundColor = isSelected
|
||||
? Palette.greyBlueCraiola
|
||||
: Palette.shadowWhite;
|
||||
|
||||
final primaryTextColor = isSelected
|
||||
? Colors.white
|
||||
: Palette.darkGray;
|
||||
|
||||
final secondaryTextColor = isSelected
|
||||
? Colors.white
|
||||
: Palette.darkBlueCraiola;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => onTap?.call(),
|
||||
child: Container(
|
||||
height: 102,
|
||||
padding: EdgeInsets.only(
|
||||
left: 20,
|
||||
//top: 33,
|
||||
right: 20
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(25)),
|
||||
color: backgroundColor
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 33,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (providerIcon != null) Padding(
|
||||
padding: EdgeInsets.only(right: 10),
|
||||
child: providerIcon
|
||||
),
|
||||
Text(
|
||||
provider.description.title,
|
||||
style: TextStyle(
|
||||
color: secondaryTextColor,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Text(
|
||||
'${destAmount?.toString()} ${destCurrency.title}',
|
||||
style: TextStyle(
|
||||
color: secondaryTextColor,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
Positioned(
|
||||
top: 65,
|
||||
right: 0,
|
||||
child: Text(
|
||||
'${sourceAmount?.toString()} ${sourceCurrency.title}',
|
||||
style: TextStyle(
|
||||
color: primaryTextColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -132,29 +132,12 @@ class DashboardPage extends BasePage {
|
||||
image: exchangeImage,
|
||||
title: S.of(context).exchange,
|
||||
route: Routes.exchange),
|
||||
Observer(
|
||||
builder: (_) => Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
if (walletViewModel.isRunningWebView)
|
||||
Positioned(
|
||||
top: -5,
|
||||
child: SpinKitRing(
|
||||
color: Theme.of(context).buttonColor,
|
||||
lineWidth: 3,
|
||||
size: 70.0,
|
||||
),
|
||||
),
|
||||
ActionButton(
|
||||
image: buyImage,
|
||||
title: S.of(context).buy,
|
||||
onClick: walletViewModel.isRunningWebView
|
||||
? null
|
||||
: () async =>
|
||||
await _onClickBuyButton(context))
|
||||
],
|
||||
)),
|
||||
ActionButton(
|
||||
image: buyImage,
|
||||
title: S.of(context).buy,
|
||||
onClick: () async =>
|
||||
await _onClickBuyButton(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
@@ -167,7 +150,8 @@ class DashboardPage extends BasePage {
|
||||
return;
|
||||
}
|
||||
|
||||
pages.add(AddressPage(addressListViewModel: addressListViewModel));
|
||||
pages.add(AddressPage(addressListViewModel: addressListViewModel,
|
||||
walletViewModel: walletViewModel));
|
||||
pages.add(BalancePage(dashboardViewModel: walletViewModel));
|
||||
pages.add(TransactionsPage(dashboardViewModel: walletViewModel));
|
||||
|
||||
@@ -183,7 +167,7 @@ class DashboardPage extends BasePage {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(context).pre_seed_title,
|
||||
alertContent:
|
||||
S.of(context).outdated_electrum_wallet_desceription,
|
||||
S.of(context).outdated_electrum_wallet_description,
|
||||
buttonText: S.of(context).understand,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
@@ -197,14 +181,7 @@ class DashboardPage extends BasePage {
|
||||
|
||||
switch (walletType) {
|
||||
case WalletType.bitcoin:
|
||||
try {
|
||||
walletViewModel.isRunningWebView = true;
|
||||
final url = await walletViewModel.wyreViewModel.wyreUrl;
|
||||
await Navigator.of(context).pushNamed(Routes.wyre, arguments: url);
|
||||
walletViewModel.isRunningWebView = false;
|
||||
} catch (_) {
|
||||
walletViewModel.isRunningWebView = false;
|
||||
}
|
||||
Navigator.of(context).pushNamed(Routes.preOrder);
|
||||
break;
|
||||
default:
|
||||
await showPopUp<void>(
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart';
|
||||
@@ -7,17 +13,43 @@ import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
class AddressPage extends StatelessWidget {
|
||||
AddressPage({@required this.addressListViewModel})
|
||||
AddressPage({@required this.addressListViewModel,
|
||||
this.walletViewModel})
|
||||
: _cryptoAmountFocus = FocusNode();
|
||||
|
||||
final WalletAddressListViewModel addressListViewModel;
|
||||
final DashboardViewModel walletViewModel;
|
||||
|
||||
final FocusNode _cryptoAmountFocus;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
autorun((_) async {
|
||||
if (!walletViewModel.isOutdatedElectrumWallet
|
||||
|| !walletViewModel.settingsStore.shouldShowReceiveWarning) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Future<void>.delayed(Duration(seconds: 1));
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: S.of(context).pre_seed_title,
|
||||
alertContent: S.of(context).outdated_electrum_wallet_receive_warning,
|
||||
leftButtonText: S.of(context).understand,
|
||||
actionLeftButton: () => Navigator.of(context).pop(),
|
||||
rightButtonText: S.of(context).do_not_show_me,
|
||||
actionRightButton: () {
|
||||
walletViewModel.settingsStore.setShouldShowReceiveWarning(false);
|
||||
Navigator.of(context).pop();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return KeyboardActions(
|
||||
autoScroll: false,
|
||||
disableScroll: true,
|
||||
@@ -42,7 +74,8 @@ class AddressPage extends StatelessWidget {
|
||||
child: Observer(builder: (_) => QRWidget(
|
||||
addressListViewModel: addressListViewModel,
|
||||
amountTextFieldFocusNode: _cryptoAmountFocus,
|
||||
isAmountFieldShow: !addressListViewModel.hasAccounts))
|
||||
isAmountFieldShow: !addressListViewModel.hasAccounts,
|
||||
isBright: walletViewModel.settingsStore.currentTheme.type == ThemeType.bright))
|
||||
),
|
||||
Observer(builder: (_) {
|
||||
return addressListViewModel.hasAddressList
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
|
||||
|
||||
@@ -1,22 +1,33 @@
|
||||
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
||||
import 'package:cake_wallet/buy/get_buy_provider_icon.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
|
||||
class OrderRow extends StatelessWidget {
|
||||
OrderRow({
|
||||
@required this.onTap,
|
||||
@required this.provider,
|
||||
this.from,
|
||||
this.to,
|
||||
this.createdAtFormattedDate,
|
||||
this.formattedAmount});
|
||||
final VoidCallback onTap;
|
||||
final BuyProviderDescription provider;
|
||||
final String from;
|
||||
final String to;
|
||||
final String createdAtFormattedDate;
|
||||
final String formattedAmount;
|
||||
final wyreImage =
|
||||
Image.asset('assets/images/wyre-icon.png', width: 36, height: 36);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentTheme = getIt.get<SettingsStore>().currentTheme;
|
||||
final isWhiteIconColor = currentTheme.type != ThemeType.light;
|
||||
|
||||
final providerIcon = getBuyProviderIcon(provider,
|
||||
isWhiteIconColor: isWhiteIconColor);
|
||||
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
@@ -26,8 +37,10 @@ class OrderRow extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
wyreImage,
|
||||
SizedBox(width: 12),
|
||||
if (providerIcon != null) Padding(
|
||||
padding: EdgeInsets.only(right: 12),
|
||||
child: providerIcon,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
||||
@@ -84,6 +84,7 @@ class TransactionsPage extends StatelessWidget {
|
||||
onTap: () => Navigator.of(context).pushNamed(
|
||||
Routes.orderDetails,
|
||||
arguments: order),
|
||||
provider: order.provider,
|
||||
from: order.from,
|
||||
to: order.to,
|
||||
createdAtFormattedDate:
|
||||
|
||||
@@ -175,7 +175,7 @@ class ExchangeTemplatePage extends BasePage {
|
||||
initialIsAddressEditable:
|
||||
exchangeViewModel.isReceiveAddressEnabled,
|
||||
isAmountEstimated: true,
|
||||
currencies: CryptoCurrency.all,
|
||||
currencies: exchangeViewModel.receiveCurrencies,
|
||||
onCurrencySelected: (currency) =>
|
||||
exchangeViewModel.changeReceiveCurrency(
|
||||
currency: currency),
|
||||
|
||||
@@ -45,6 +45,7 @@ class CurrencyPickerState extends State<CurrencyPicker> {
|
||||
color: Palette.darkBlueCraiola,
|
||||
);
|
||||
final int crossAxisCount = 3;
|
||||
final int maxNumberItemsInAlert = 12;
|
||||
final int itemsCount;
|
||||
final double backgroundHeight = 280;
|
||||
final double thumbHeight = 72;
|
||||
@@ -148,6 +149,7 @@ class CurrencyPickerState extends State<CurrencyPicker> {
|
||||
);
|
||||
})
|
||||
),
|
||||
if (itemsCount > maxNumberItemsInAlert)
|
||||
CakeScrollbar(
|
||||
backgroundHeight: backgroundHeight,
|
||||
thumbHeight: thumbHeight,
|
||||
|
||||
@@ -98,6 +98,12 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
|
||||
showInformation(widget.exchangeTradeViewModel, context);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
widget.exchangeTradeViewModel.timer?.cancel();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final copyImage = Image.asset('assets/images/copy_content.png',
|
||||
|
||||
@@ -19,7 +19,33 @@ class OrderDetailsPage extends BasePage {
|
||||
final OrderDetailsViewModel orderDetailsViewModel;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
Widget body(BuildContext context) =>
|
||||
OrderDetailsPageBody(orderDetailsViewModel);
|
||||
}
|
||||
|
||||
class OrderDetailsPageBody extends StatefulWidget {
|
||||
OrderDetailsPageBody(this.orderDetailsViewModel);
|
||||
|
||||
final OrderDetailsViewModel orderDetailsViewModel;
|
||||
|
||||
@override
|
||||
OrderDetailsPageBodyState createState() =>
|
||||
OrderDetailsPageBodyState(orderDetailsViewModel);
|
||||
}
|
||||
|
||||
class OrderDetailsPageBodyState extends State<OrderDetailsPageBody> {
|
||||
OrderDetailsPageBodyState(this.orderDetailsViewModel);
|
||||
|
||||
final OrderDetailsViewModel orderDetailsViewModel;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
orderDetailsViewModel.timer?.cancel();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(builder: (_) {
|
||||
return SectionStandardList(
|
||||
sectionCount: 1,
|
||||
@@ -44,4 +70,5 @@ class OrderDetailsPage extends BasePage {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
@@ -34,6 +35,9 @@ class ReceivePage extends BasePage {
|
||||
@override
|
||||
Color get backgroundDarkColor => Colors.transparent;
|
||||
|
||||
@override
|
||||
bool get resizeToAvoidBottomInset => false;
|
||||
|
||||
final FocusNode _cryptoAmountFocus;
|
||||
|
||||
@override
|
||||
@@ -124,7 +128,8 @@ class ReceivePage extends BasePage {
|
||||
child: QRWidget(
|
||||
addressListViewModel: addressListViewModel,
|
||||
isAmountFieldShow: true,
|
||||
amountTextFieldFocusNode: _cryptoAmountFocus),
|
||||
amountTextFieldFocusNode: _cryptoAmountFocus,
|
||||
isBright: currentTheme.type == ThemeType.bright),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => ListView.separated(
|
||||
|
||||
@@ -13,7 +13,8 @@ class QRWidget extends StatelessWidget {
|
||||
QRWidget(
|
||||
{@required this.addressListViewModel,
|
||||
this.isAmountFieldShow = false,
|
||||
this.amountTextFieldFocusNode})
|
||||
this.amountTextFieldFocusNode,
|
||||
this.isBright})
|
||||
: amountController = TextEditingController(),
|
||||
_formKey = GlobalKey<FormState>() {
|
||||
amountController.addListener(() => addressListViewModel.amount =
|
||||
@@ -25,6 +26,7 @@ class QRWidget extends StatelessWidget {
|
||||
final TextEditingController amountController;
|
||||
final FocusNode amountTextFieldFocusNode;
|
||||
final GlobalKey<FormState> _formKey;
|
||||
final bool isBright;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -55,7 +57,7 @@ class QRWidget extends StatelessWidget {
|
||||
),
|
||||
child: QrImage(
|
||||
data: addressListViewModel.uri.toString(),
|
||||
backgroundColor: Colors.transparent,
|
||||
backgroundColor: isBright ? Colors.black : Colors.transparent,
|
||||
foregroundColor: Theme.of(context).accentTextTheme.
|
||||
display3.backgroundColor,
|
||||
),
|
||||
|
||||
@@ -19,7 +19,33 @@ class TradeDetailsPage extends BasePage {
|
||||
final TradeDetailsViewModel tradeDetailsViewModel;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
Widget body(BuildContext context) =>
|
||||
TradeDetailsPageBody(tradeDetailsViewModel);
|
||||
}
|
||||
|
||||
class TradeDetailsPageBody extends StatefulWidget {
|
||||
TradeDetailsPageBody(this.tradeDetailsViewModel);
|
||||
|
||||
final TradeDetailsViewModel tradeDetailsViewModel;
|
||||
|
||||
@override
|
||||
TradeDetailsPageBodyState createState() =>
|
||||
TradeDetailsPageBodyState(tradeDetailsViewModel);
|
||||
}
|
||||
|
||||
class TradeDetailsPageBodyState extends State<TradeDetailsPageBody> {
|
||||
TradeDetailsPageBodyState(this.tradeDetailsViewModel);
|
||||
|
||||
final TradeDetailsViewModel tradeDetailsViewModel;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
tradeDetailsViewModel.timer?.cancel();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(builder: (_) {
|
||||
return SectionStandardList(
|
||||
sectionCount: 1,
|
||||
@@ -44,4 +70,5 @@ class TradeDetailsPage extends BasePage {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ class BaseTextFormField extends StatelessWidget {
|
||||
this.placeholderTextStyle,
|
||||
this.maxLength,
|
||||
this.focusNode,
|
||||
this.initialValue});
|
||||
this.initialValue,
|
||||
this.borderWidth = 1.0});
|
||||
|
||||
final TextEditingController controller;
|
||||
final TextInputType keyboardType;
|
||||
@@ -52,6 +53,7 @@ class BaseTextFormField extends StatelessWidget {
|
||||
final bool readOnly;
|
||||
final bool enableInteractiveSelection;
|
||||
final String initialValue;
|
||||
final double borderWidth;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -88,17 +90,17 @@ class BaseTextFormField extends StatelessWidget {
|
||||
borderSide: BorderSide(
|
||||
color: borderColor ??
|
||||
Theme.of(context).primaryTextTheme.title.backgroundColor,
|
||||
width: 1.0)),
|
||||
width: borderWidth)),
|
||||
disabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: borderColor ??
|
||||
Theme.of(context).primaryTextTheme.title.backgroundColor,
|
||||
width: 1.0)),
|
||||
width: borderWidth)),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: borderColor ??
|
||||
Theme.of(context).primaryTextTheme.title.backgroundColor,
|
||||
width: 1.0))),
|
||||
width: borderWidth))),
|
||||
validator: validator,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'package:cake_wallet/entities/order.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
@@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:package_info/package_info.dart';
|
||||
import 'package:devicelocale/devicelocale.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
@@ -18,6 +17,7 @@ import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/entities/node.dart';
|
||||
import 'package:cake_wallet/entities/monero_transaction_priority.dart';
|
||||
import 'package:cake_wallet/entities/action_list_display_mode.dart';
|
||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||
|
||||
part 'settings_store.g.dart';
|
||||
|
||||
@@ -147,6 +147,12 @@ abstract class SettingsStoreBase with Store {
|
||||
|
||||
bool isBitcoinBuyEnabled;
|
||||
|
||||
bool get shouldShowReceiveWarning =>
|
||||
_sharedPreferences.getBool(PreferencesKey.shouldShowReceiveWarning) ?? true;
|
||||
|
||||
Future<void> setShouldShowReceiveWarning(bool value) async =>
|
||||
_sharedPreferences.setBool(PreferencesKey.shouldShowReceiveWarning, value);
|
||||
|
||||
static Future<SettingsStore> load(
|
||||
{@required Box<Node> nodeSource,
|
||||
@required bool isBitcoinBuyEnabled,
|
||||
@@ -242,8 +248,13 @@ abstract class SettingsStoreBase with Store {
|
||||
BitcoinTransactionPriority.medium,
|
||||
BalanceDisplayMode initialBalanceDisplayMode =
|
||||
BalanceDisplayMode.availableBalance}) async {
|
||||
final isBitcoinBuyEnabled = (secrets.wyreSecretKey?.isNotEmpty ?? false) &&
|
||||
(secrets.wyreApiKey?.isNotEmpty ?? false) &&
|
||||
(secrets.wyreAccountId?.isNotEmpty ?? false);
|
||||
|
||||
final settings = await SettingsStoreBase.load(
|
||||
nodeSource: nodeSource,
|
||||
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
|
||||
initialBalanceDisplayMode: initialBalanceDisplayMode,
|
||||
initialFiatCurrency: initialFiatCurrency,
|
||||
initialMoneroTransactionPriority: initialMoneroTransactionPriority,
|
||||
|
||||
+12
-4
@@ -2,8 +2,11 @@ import 'package:flushbar/flushbar.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Future<T> showBar<T>(BuildContext context, String text,
|
||||
{bool isDark = false, Duration duration = const Duration(seconds: 1), bool isDismissible = true}) {
|
||||
Future<T> showBar<T>(BuildContext context, String messageText,
|
||||
{bool isDark = false,
|
||||
Duration duration = const Duration(seconds: 1),
|
||||
bool isDismissible = true,
|
||||
String titleText}) {
|
||||
final bar = Flushbar<T>(
|
||||
boxShadows: [
|
||||
BoxShadow(
|
||||
@@ -14,9 +17,14 @@ Future<T> showBar<T>(BuildContext context, String text,
|
||||
backgroundColor: isDark ? Colors.black : Colors.white,
|
||||
borderRadius: 35,
|
||||
margin: EdgeInsets.all(50),
|
||||
messageText: Text(text,
|
||||
titleText: titleText != null
|
||||
? Text(titleText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: isDark ? Colors.white : Colors.black, fontWeight: FontWeight.bold, fontSize: 24.0))
|
||||
: null,
|
||||
messageText: Text(messageText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: isDark ? Colors.white : Colors.black)),
|
||||
style: TextStyle(color: isDark ? Colors.white : Colors.black, fontSize: 16)),
|
||||
duration: duration,
|
||||
isDismissible: isDismissible,
|
||||
flushbarPosition: FlushbarPosition.TOP,
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
|
||||
part 'buy_amount_view_model.g.dart';
|
||||
|
||||
class BuyAmountViewModel = BuyAmountViewModelBase with _$BuyAmountViewModel;
|
||||
|
||||
abstract class BuyAmountViewModelBase with Store {
|
||||
BuyAmountViewModelBase() : amount = '';
|
||||
|
||||
@observable
|
||||
String amount;
|
||||
|
||||
FiatCurrency get fiatCurrency => FiatCurrency.usd;
|
||||
|
||||
@computed
|
||||
double get doubleAmount {
|
||||
double _amount;
|
||||
|
||||
try {
|
||||
_amount = double.parse(amount.replaceAll(',', '.')) ?? 0.0;
|
||||
} catch (e) {
|
||||
_amount = 0.0;
|
||||
}
|
||||
|
||||
return _amount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:cake_wallet/buy/buy_amount.dart';
|
||||
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_amount_view_model.dart';
|
||||
|
||||
class BuyItem {
|
||||
BuyItem({this.provider, this.buyAmountViewModel});
|
||||
|
||||
final BuyProvider provider;
|
||||
final BuyAmountViewModel buyAmountViewModel;
|
||||
|
||||
double get amount => buyAmountViewModel.doubleAmount;
|
||||
|
||||
FiatCurrency get fiatCurrency => buyAmountViewModel.fiatCurrency;
|
||||
|
||||
Future<BuyAmount> get buyAmount async {
|
||||
BuyAmount _buyAmount;
|
||||
|
||||
try {
|
||||
_buyAmount = await provider
|
||||
.calculateAmount(amount?.toString(), fiatCurrency.title);
|
||||
} catch (e) {
|
||||
_buyAmount = BuyAmount(sourceAmount: 0.0, destAmount: 0.0);
|
||||
print(e.toString());
|
||||
}
|
||||
|
||||
return _buyAmount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/wyre/wyre_buy_provider.dart';
|
||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/view_model/buy/buy_item.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:cake_wallet/store/dashboard/orders_store.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'buy_amount_view_model.dart';
|
||||
|
||||
part 'buy_view_model.g.dart';
|
||||
|
||||
class BuyViewModel = BuyViewModelBase with _$BuyViewModel;
|
||||
|
||||
abstract class BuyViewModelBase with Store {
|
||||
BuyViewModelBase(this.ordersSource, this.ordersStore, this.settingsStore,
|
||||
this.buyAmountViewModel, {@required this.wallet}) {
|
||||
|
||||
_fetchBuyItems();
|
||||
|
||||
isRunning = false;
|
||||
isDisabled = true;
|
||||
isShowProviderButtons = false;
|
||||
}
|
||||
|
||||
final Box<Order> ordersSource;
|
||||
final OrdersStore ordersStore;
|
||||
final SettingsStore settingsStore;
|
||||
final BuyAmountViewModel buyAmountViewModel;
|
||||
final WalletBase wallet;
|
||||
|
||||
@observable
|
||||
BuyProvider selectedProvider;
|
||||
|
||||
@observable
|
||||
List<BuyItem> items;
|
||||
|
||||
@observable
|
||||
bool isRunning;
|
||||
|
||||
@observable
|
||||
bool isDisabled;
|
||||
|
||||
@observable
|
||||
bool isShowProviderButtons;
|
||||
|
||||
WalletType get type => wallet.type;
|
||||
|
||||
double get doubleAmount => buyAmountViewModel.doubleAmount;
|
||||
|
||||
FiatCurrency get fiatCurrency => buyAmountViewModel.fiatCurrency;
|
||||
|
||||
CryptoCurrency get cryptoCurrency => walletTypeToCryptoCurrency(type);
|
||||
|
||||
Future <String> fetchUrl() async {
|
||||
String _url = '';
|
||||
|
||||
try {
|
||||
_url = await selectedProvider
|
||||
?.requestUrl(doubleAmount?.toString(), fiatCurrency.title);
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
|
||||
return _url;
|
||||
}
|
||||
|
||||
Future<void> saveOrder(String orderId) async {
|
||||
try {
|
||||
final order = await selectedProvider?.findOrderById(orderId);
|
||||
order.from = fiatCurrency.title;
|
||||
order.to = cryptoCurrency.title;
|
||||
await ordersSource.add(order);
|
||||
ordersStore.setOrder(order);
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
buyAmountViewModel.amount = '';
|
||||
selectedProvider = null;
|
||||
}
|
||||
|
||||
Future<void> _fetchBuyItems() async {
|
||||
final List<BuyProvider> _providerList = [WyreBuyProvider(wallet: wallet)];
|
||||
|
||||
var isMoonPayEnabled = false;
|
||||
try {
|
||||
isMoonPayEnabled = await MoonPayBuyProvider.onEnabled();
|
||||
} catch (e) {
|
||||
isMoonPayEnabled = false;
|
||||
print(e.toString());
|
||||
}
|
||||
|
||||
if (isMoonPayEnabled) {
|
||||
_providerList.add(MoonPayBuyProvider(wallet: wallet));
|
||||
}
|
||||
|
||||
items = _providerList.map((provider) =>
|
||||
BuyItem(provider: provider, buyAmountViewModel: buyAmountViewModel))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import 'package:cake_wallet/core/transaction_history.dart';
|
||||
import 'package:cake_wallet/entities/balance.dart';
|
||||
import 'package:cake_wallet/entities/order.dart';
|
||||
import 'package:cake_wallet/entities/push_notifications_service.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:cake_wallet/entities/transaction_history.dart';
|
||||
import 'package:cake_wallet/exchange/trade_state.dart';
|
||||
import 'package:cake_wallet/monero/account.dart';
|
||||
import 'package:cake_wallet/monero/monero_balance.dart';
|
||||
import 'package:cake_wallet/monero/monero_transaction_info.dart';
|
||||
@@ -17,7 +20,9 @@ import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/wyre_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/action_list_display_mode.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
@@ -42,9 +47,9 @@ abstract class DashboardViewModelBase with Store {
|
||||
this.tradeFilterStore,
|
||||
this.transactionFilterStore,
|
||||
this.settingsStore,
|
||||
this.ordersSource,
|
||||
this.ordersStore,
|
||||
this.wyreViewModel}) {
|
||||
PushNotificationsService pushNotificationsService}) {
|
||||
_pushNotificationsService = pushNotificationsService;
|
||||
filterItems = {
|
||||
S.current.transactions: [
|
||||
FilterItem(
|
||||
@@ -79,8 +84,6 @@ abstract class DashboardViewModelBase with Store {
|
||||
]
|
||||
};
|
||||
|
||||
isRunningWebView = false;
|
||||
|
||||
name = appStore.wallet?.name;
|
||||
wallet ??= appStore.wallet;
|
||||
type = wallet.type;
|
||||
@@ -133,6 +136,8 @@ abstract class DashboardViewModelBase with Store {
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
Future.delayed(Duration(seconds: 2), () => _pushNotificationsService.init());
|
||||
}
|
||||
|
||||
@observable
|
||||
@@ -147,9 +152,6 @@ abstract class DashboardViewModelBase with Store {
|
||||
@observable
|
||||
String subname;
|
||||
|
||||
@observable
|
||||
bool isRunningWebView;
|
||||
|
||||
@computed
|
||||
String get address => wallet.address;
|
||||
|
||||
@@ -205,8 +207,6 @@ abstract class DashboardViewModelBase with Store {
|
||||
|
||||
bool get hasRescan => wallet.type == WalletType.monero;
|
||||
|
||||
Box<Order> ordersSource;
|
||||
|
||||
BalanceViewModel balanceViewModel;
|
||||
|
||||
AppStore appStore;
|
||||
@@ -221,24 +221,24 @@ abstract class DashboardViewModelBase with Store {
|
||||
|
||||
TransactionFilterStore transactionFilterStore;
|
||||
|
||||
WyreViewModel wyreViewModel;
|
||||
|
||||
Map<String, List<FilterItem>> filterItems;
|
||||
|
||||
bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled;
|
||||
|
||||
PushNotificationsService _pushNotificationsService;
|
||||
|
||||
ReactionDisposer _onMoneroAccountChangeReaction;
|
||||
|
||||
ReactionDisposer _onMoneroBalanceChangeReaction;
|
||||
|
||||
@observable
|
||||
bool isOutdatedElectrumWallet;
|
||||
|
||||
Future<void> reconnect() async {
|
||||
final node = appStore.settingsStore.getCurrentNode(wallet.type);
|
||||
await wallet.connectToNode(node: node);
|
||||
}
|
||||
|
||||
@observable
|
||||
bool isOutdatedElectrumWallet;
|
||||
|
||||
@action
|
||||
void _onWalletChange(
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:cake_wallet/entities/order.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
|
||||
import 'package:cake_wallet/entities/balance_display_mode.dart';
|
||||
|
||||
@@ -45,7 +45,7 @@ abstract class ExchangeTradeViewModelBase with Store {
|
||||
|
||||
_updateTrade();
|
||||
|
||||
_timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateTrade());
|
||||
timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateTrade());
|
||||
}
|
||||
|
||||
final WalletBase wallet;
|
||||
@@ -71,7 +71,7 @@ abstract class ExchangeTradeViewModelBase with Store {
|
||||
|
||||
ExchangeProvider _provider;
|
||||
|
||||
Timer _timer;
|
||||
Timer timer;
|
||||
|
||||
@action
|
||||
Future confirmSending() async {
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import 'dart:async';
|
||||
import 'package:cake_wallet/entities/order.dart';
|
||||
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
||||
import 'package:cake_wallet/buy/order.dart';
|
||||
import 'package:cake_wallet/utils/date_formatter.dart';
|
||||
import 'package:cake_wallet/view_model/wyre_view_model.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/trade_details/track_trade_list_item.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/wyre/wyre_buy_provider.dart';
|
||||
|
||||
part 'order_details_view_model.g.dart';
|
||||
|
||||
@@ -14,16 +18,27 @@ class OrderDetailsViewModel = OrderDetailsViewModelBase
|
||||
with _$OrderDetailsViewModel;
|
||||
|
||||
abstract class OrderDetailsViewModelBase with Store {
|
||||
OrderDetailsViewModelBase({this.wyreViewModel, Order orderForDetails}) {
|
||||
OrderDetailsViewModelBase({WalletBase wallet, Order orderForDetails}) {
|
||||
order = orderForDetails;
|
||||
|
||||
if (order.provider != null) {
|
||||
switch (order.provider) {
|
||||
case BuyProviderDescription.wyre:
|
||||
_provider = WyreBuyProvider(wallet: wallet);
|
||||
break;
|
||||
case BuyProviderDescription.moonPay:
|
||||
_provider = MoonPayBuyProvider(wallet: wallet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
items = ObservableList<StandartListItem>();
|
||||
|
||||
_updateItems();
|
||||
|
||||
_updateOrder();
|
||||
|
||||
_timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateOrder());
|
||||
timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateOrder());
|
||||
}
|
||||
|
||||
@observable
|
||||
@@ -32,21 +47,25 @@ abstract class OrderDetailsViewModelBase with Store {
|
||||
@observable
|
||||
ObservableList<StandartListItem> items;
|
||||
|
||||
WyreViewModel wyreViewModel;
|
||||
BuyProvider _provider;
|
||||
|
||||
Timer _timer;
|
||||
Timer timer;
|
||||
|
||||
@action
|
||||
Future<void> _updateOrder() async {
|
||||
try {
|
||||
final updatedOrder =
|
||||
await wyreViewModel.wyreService.findOrderById(order.id);
|
||||
|
||||
updatedOrder.receiveAddress = order.receiveAddress;
|
||||
updatedOrder.walletId = order.walletId;
|
||||
order = updatedOrder;
|
||||
|
||||
_updateItems();
|
||||
if (_provider != null) {
|
||||
final updatedOrder = await _provider.findOrderById(order.id);
|
||||
updatedOrder.from = order.from;
|
||||
updatedOrder.to = order.to;
|
||||
updatedOrder.receiveAddress = order.receiveAddress;
|
||||
updatedOrder.walletId = order.walletId;
|
||||
if (order.provider != null) {
|
||||
updatedOrder.providerRaw = order.provider.raw;
|
||||
}
|
||||
order = updatedOrder;
|
||||
_updateItems();
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
@@ -54,8 +73,6 @@ abstract class OrderDetailsViewModelBase with Store {
|
||||
|
||||
void _updateItems() {
|
||||
final dateFormat = DateFormatter.withCurrentLocal();
|
||||
final buildURL =
|
||||
wyreViewModel.trackUrl + '${order.transferId}';
|
||||
|
||||
items?.clear();
|
||||
|
||||
@@ -68,18 +85,37 @@ abstract class OrderDetailsViewModelBase with Store {
|
||||
value: order.state != null
|
||||
? order.state.toString()
|
||||
: S.current.trade_details_fetching),
|
||||
TrackTradeListItem(
|
||||
title: 'Track',
|
||||
value: buildURL,
|
||||
onTap: () {
|
||||
launch(buildURL);
|
||||
}),
|
||||
StandartListItem(
|
||||
title: S.current.trade_details_created_at,
|
||||
value: dateFormat.format(order.createdAt).toString()),
|
||||
StandartListItem(
|
||||
title: S.current.trade_details_pair,
|
||||
value: '${order.from} → ${order.to}')
|
||||
]);
|
||||
|
||||
if (order.provider != null) {
|
||||
items.add(
|
||||
StandartListItem(
|
||||
title: 'Buy provider',
|
||||
value: order.provider.title)
|
||||
);
|
||||
}
|
||||
|
||||
if (_provider?.trackUrl?.isNotEmpty ?? false) {
|
||||
final buildURL = _provider.trackUrl + '${order.transferId}';
|
||||
items.add(
|
||||
TrackTradeListItem(
|
||||
title: 'Track',
|
||||
value: buildURL,
|
||||
onTap: () => launch(buildURL)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
items.add(
|
||||
StandartListItem(
|
||||
title: S.current.trade_details_created_at,
|
||||
value: dateFormat.format(order.createdAt).toString())
|
||||
);
|
||||
|
||||
items.add(
|
||||
StandartListItem(
|
||||
title: S.current.trade_details_pair,
|
||||
value: '${order.from} → ${order.to}')
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,15 @@ abstract class SupportViewModelBase with Store {
|
||||
title: 'Email',
|
||||
linkTitle: 'support@cakewallet.com',
|
||||
link: 'mailto:support@cakewallet.com'),
|
||||
LinkListItem(
|
||||
title: 'Website',
|
||||
linkTitle: 'cakewallet.com',
|
||||
link: 'https://cakewallet.com'),
|
||||
LinkListItem(
|
||||
title: 'GitHub',
|
||||
icon: 'assets/images/github.png',
|
||||
linkTitle: S.current.apk_update,
|
||||
link: 'https://github.com/cake-tech/cake_wallet/releases'),
|
||||
LinkListItem(
|
||||
title: 'Telegram',
|
||||
icon: 'assets/images/Telegram.png',
|
||||
|
||||
@@ -39,7 +39,7 @@ abstract class TradeDetailsViewModelBase with Store {
|
||||
|
||||
_updateTrade();
|
||||
|
||||
_timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateTrade());
|
||||
timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateTrade());
|
||||
}
|
||||
|
||||
final Box<Trade> trades;
|
||||
@@ -52,7 +52,7 @@ abstract class TradeDetailsViewModelBase with Store {
|
||||
|
||||
ExchangeProvider _provider;
|
||||
|
||||
Timer _timer;
|
||||
Timer timer;
|
||||
|
||||
@action
|
||||
Future<void> _updateTrade() async {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart';
|
||||
import 'package:cake_wallet/core/wallet_base.dart';
|
||||
import 'package:cake_wallet/entities/transaction_info.dart';
|
||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||
import 'package:cake_wallet/monero/monero_transaction_info.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart';
|
||||
import 'package:cake_wallet/entities/transaction_direction.dart';
|
||||
import 'package:cake_wallet/utils/date_formatter.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
@@ -23,6 +26,7 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
TransactionDetailsViewModelBase(
|
||||
{this.transactionInfo,
|
||||
this.transactionDescriptionBox,
|
||||
this.wallet,
|
||||
this.settingsStore})
|
||||
: items = [] {
|
||||
showRecipientAddress = settingsStore?.shouldSaveRecipientAddress ?? false;
|
||||
@@ -51,6 +55,25 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
StandartListItem(title: S.current.transaction_key, value: tx.key));
|
||||
}
|
||||
|
||||
if ((tx.direction == TransactionDirection.incoming)&&
|
||||
(wallet is MoneroWallet)) {
|
||||
try {
|
||||
final accountIndex = tx.accountIndex;
|
||||
final addressIndex = tx.addressIndex;
|
||||
final _wallet = wallet as MoneroWallet;
|
||||
final address =
|
||||
_wallet.getTransactionAddress(accountIndex, addressIndex);
|
||||
if (address?.isNotEmpty ?? false) {
|
||||
_items.add(
|
||||
StandartListItem(
|
||||
title: S.current.transaction_details_recipient_address,
|
||||
value: address));
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
items.addAll(_items);
|
||||
}
|
||||
|
||||
@@ -124,6 +147,7 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
final TransactionInfo transactionInfo;
|
||||
final Box<TransactionDescription> transactionDescriptionBox;
|
||||
final SettingsStore settingsStore;
|
||||
final WalletBase wallet;
|
||||
|
||||
final List<TransactionDetailsListItem> items;
|
||||
bool showRecipientAddress;
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import 'package:cake_wallet/entities/wyre_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:cake_wallet/entities/order.dart';
|
||||
import 'package:cake_wallet/store/dashboard/orders_store.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'wyre_view_model.g.dart';
|
||||
|
||||
class WyreViewModel = WyreViewModelBase with _$WyreViewModel;
|
||||
|
||||
abstract class WyreViewModelBase with Store {
|
||||
WyreViewModelBase(this.ordersSource, this.ordersStore,
|
||||
{@required this.wyreService});
|
||||
|
||||
Future<String> get wyreUrl => wyreService.getWyreUrl();
|
||||
|
||||
String get trackUrl => wyreService.trackUrl;
|
||||
|
||||
final Box<Order> ordersSource;
|
||||
final OrdersStore ordersStore;
|
||||
|
||||
final WyreService wyreService;
|
||||
|
||||
Future<void> saveOrder(String orderId) async {
|
||||
try {
|
||||
final order = await wyreService.findOrderById(orderId);
|
||||
await ordersSource.add(order);
|
||||
ordersStore.setOrder(order);
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user