Merge branch 'main' into CAKE-192-implement-unstoppable-domain

This commit is contained in:
M
2021-06-08 15:09:03 +03:00
90 changed files with 2028 additions and 478 deletions
@@ -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 {
+12
View File
@@ -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;
}
+12
View File
@@ -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';
}
+27
View File
@@ -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);
}
+21
View File
@@ -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;
}
}
}
+28
View File
@@ -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;
}
}
+144
View File
@@ -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 + '&currencyCode=' +
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;
}
}
+10 -1
View File
@@ -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);
}
+163
View File
@@ -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
View File
@@ -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)));
+1 -1
View File
@@ -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);
+1
View File
@@ -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;
}
}
+1 -1
View File
@@ -71,7 +71,7 @@ String walletTypeToDisplayName(WalletType type) {
case WalletType.bitcoin:
return 'Bitcoin (Electrum)';
case WalletType.litecoin:
return 'Litecoin';
return 'Litecoin (Electrum)';
default:
return '';
}
-8
View File
@@ -1,8 +0,0 @@
class WyreException implements Exception {
WyreException(this.description);
String description;
@override
String toString() => description;
}
-113
View File
@@ -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);
}
}
+14
View File
@@ -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
View File
@@ -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';
+4 -1
View File
@@ -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;
+5
View File
@@ -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();
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
});
}
}
+288
View File
@@ -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
),
),
)
],
)
)
);
}
}
+10 -33
View File
@@ -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 {
});
});
}
}
+6 -1
View File
@@ -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 {
});
});
}
}
+6 -4
View File
@@ -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 -1
View File
@@ -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';
+12 -1
View File
@@ -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
View File
@@ -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;
}
}
+29
View File
@@ -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;
}
}
+110
View File
@@ -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 {
+64 -28
View File
@@ -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}')
);
}
}
+9
View File
@@ -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',
+2 -2
View File
@@ -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;
-34
View File
@@ -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());
}
}
}