Merge branch 'main' into deploy

This commit is contained in:
M
2021-03-24 12:22:42 +02:00
37 changed files with 789 additions and 44 deletions
+41 -2
View File
@@ -4,8 +4,10 @@ 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/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';
@@ -22,6 +24,7 @@ import 'package:cake_wallet/src/screens/faq/faq_page.dart';
import 'package:cake_wallet/src/screens/new_wallet/new_wallet_type_page.dart';
import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart';
import 'package:cake_wallet/src/screens/nodes/nodes_list_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/rescan/rescan_page.dart';
import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
@@ -39,6 +42,8 @@ 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';
import 'package:cake_wallet/store/settings_store.dart';
@@ -63,6 +68,7 @@ import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cake_wallet/view_model/order_details_view_model.dart';
import 'package:cake_wallet/view_model/rescan_view_model.dart';
import 'package:cake_wallet/view_model/restore_from_backup_view_model.dart';
import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
@@ -83,6 +89,7 @@ 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';
@@ -116,6 +123,7 @@ Box<Trade> _tradesSource;
Box<Template> _templates;
Box<ExchangeTemplate> _exchangeTemplates;
Box<TransactionDescription> _transactionDescriptionBox;
Box<Order> _ordersSource;
Future setup(
{Box<WalletInfo> walletInfoSource,
@@ -124,7 +132,8 @@ Future setup(
Box<Trade> tradesSource,
Box<Template> templates,
Box<ExchangeTemplate> exchangeTemplates,
Box<TransactionDescription> transactionDescriptionBox}) async {
Box<TransactionDescription> transactionDescriptionBox,
Box<Order> ordersSource}) async {
_walletInfoSource = walletInfoSource;
_nodeSource = nodeSource;
_contactSource = contactSource;
@@ -132,6 +141,7 @@ Future setup(
_templates = templates;
_exchangeTemplates = exchangeTemplates;
_transactionDescriptionBox = transactionDescriptionBox;
_ordersSource = ordersSource;
if (!_isSetupFinished) {
getIt.registerSingletonAsync<SharedPreferences>(
@@ -162,6 +172,8 @@ Future setup(
nodeListStore: getIt.get<NodeListStore>()));
getIt.registerSingleton<TradesStore>(TradesStore(
tradesSource: _tradesSource, settingsStore: getIt.get<SettingsStore>()));
getIt.registerSingleton<OrdersStore>(OrdersStore(
ordersSource: _ordersSource, settingsStore: getIt.get<SettingsStore>()));
getIt.registerSingleton<TradeFilterStore>(TradeFilterStore());
getIt.registerSingleton<TransactionFilterStore>(TransactionFilterStore());
getIt.registerSingleton<FiatConversionStore>(FiatConversionStore());
@@ -225,7 +237,10 @@ Future setup(
tradesStore: getIt.get<TradesStore>(),
tradeFilterStore: getIt.get<TradeFilterStore>(),
transactionFilterStore: getIt.get<TransactionFilterStore>(),
settingsStore: settingsStore));
settingsStore: settingsStore,
ordersSource: _ordersSource,
ordersStore: getIt.get<OrdersStore>(),
wyreViewModel: getIt.get<WyreViewModel>()));
getIt.registerFactory<AuthService>(() => AuthService(
secureStorage: getIt.get<FlutterSecureStorage>(),
@@ -517,6 +532,30 @@ Future setup(
getIt.registerFactoryParam<TradeDetailsPage, Trade, void>((Trade trade, _) =>
TradeDetailsPage(getIt.get<TradeDetailsViewModel>(param1: trade)));
getIt.registerFactory(() {
final wallet = getIt.get<AppStore>().wallet;
return WyreService(walletType: wallet.type, walletAddress: wallet.address);
});
getIt.registerFactory(() {
final wallet = getIt.get<AppStore>().wallet;
return WyreViewModel(ordersSource, getIt.get<OrdersStore>(),
walletId: wallet.id, address: wallet.address, type: wallet.type,
wyreService: getIt.get<WyreService>());
});
getIt.registerFactoryParam<WyrePage, String, void>((String url, _) =>
WyrePage(getIt.get<WyreViewModel>(),
ordersStore: getIt.get<OrdersStore>(), url: url));
getIt.registerFactoryParam<OrderDetailsViewModel, Order, void>(
(order, _) => OrderDetailsViewModel(
wyreViewModel: getIt.get<WyreViewModel>(),
orderForDetails: order));
getIt.registerFactoryParam<OrderDetailsPage, Order, void>((Order order, _) =>
OrderDetailsPage(getIt.get<OrderDetailsViewModel>(param1: order)));
getIt.registerFactory(() => SupportViewModel());
getIt.registerFactory(() => SupportPage(getIt.get<SupportViewModel>()));
+55
View File
@@ -0,0 +1,55 @@
import 'package:hive/hive.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/entities/format_amount.dart';
part 'order.g.dart';
@HiveType(typeId: Order.typeId)
class Order extends HiveObject {
Order(
{this.id,
this.transferId,
this.from,
this.to,
TradeState state,
this.createdAt,
this.amount,
this.receiveAddress,
this.walletId})
: stateRaw = state?.raw;
static const typeId = 8;
static const boxName = 'Orders';
static const boxKey = 'ordersBoxKey';
@HiveField(0)
String id;
@HiveField(1)
String transferId;
@HiveField(2)
String from;
@HiveField(3)
String to;
@HiveField(4)
String stateRaw;
TradeState get state => TradeState.deserialize(raw: stateRaw);
@HiveField(5)
DateTime createdAt;
@HiveField(6)
String amount;
@HiveField(7)
String receiveAddress;
@HiveField(8)
String walletId;
String amountFormatted() => formatAmount(amount);
}
+8
View File
@@ -0,0 +1,8 @@
class WyreException implements Exception {
WyreException(this.description);
String description;
@override
String toString() => description;
}
+111
View File
@@ -0,0 +1,111 @@
import 'dart:convert';
import 'package:cake_wallet/entities/wyre_exception.dart';
import 'package:cake_wallet/exchange/trade_state.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.walletType,
@required this.walletAddress,
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 WalletType walletType;
final String walletAddress;
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()
);
}
}
+12 -12
View File
@@ -368,7 +368,7 @@ class S implements WidgetsLocalizations {
String error_text_maximum_limit(String provider, String max, String currency) => "Trade for ${provider} is not created. Amount is more then maximum: ${max} ${currency}";
String error_text_minimal_limit(String provider, String min, String currency) => "Trade for ${provider} is not created. Amount is less then minimal: ${min} ${currency}";
String exchange_result_confirm(String fetchingLabel, String from, String walletName) => "By pressing confirm, you will be sending ${fetchingLabel} ${from} from your wallet called ${walletName} to the address shown below. Or you can send from your external wallet to the below address/QR code.\n\nPlease press confirm to continue or go back to change the amounts.";
String exchange_result_description(String fetchingLabel, String from) => "Please send ${fetchingLabel} ${from} to the address shown on the next page.";
String exchange_result_description(String fetchingLabel, String from) => "You must send a minimum of ${fetchingLabel} ${from} to the address shown on the next page. If you send an amount lower than ${fetchingLabel} ${from} it may not get converted and it may not be refunded.";
String failed_authentication(String state_error) => "Failed authentication. ${state_error}";
String max_value(String value, String currency) => "Max: ${value} ${currency}";
String min_value(String value, String currency) => "Min: ${value} ${currency}";
@@ -1132,7 +1132,7 @@ class $de extends S {
@override
String error_text_limits_loading_failed(String provider) => "Handel für ${provider} wird nicht erstellt. Das Laden der Limits ist fehlgeschlagen";
@override
String exchange_result_description(String fetchingLabel, String from) => "Bitte senden ${fetchingLabel} ${from} an die auf der nächsten Seite angegebene Adresse.'";
String exchange_result_description(String fetchingLabel, String from) => "Sie müssen mindestens ${fetchingLabel} ${from} an die auf der nächsten Seite angegebene Adresse senden. Wenn Sie einen Betrag unter ${fetchingLabel} ${from} senden, wird dieser möglicherweise nicht konvertiert und möglicherweise nicht erstattet.";
@override
String commit_transaction_amount_fee(String amount, String fee) => "Transaktion festschreiben\nMenge: ${amount}\nGebühr: ${fee}";
@override
@@ -1896,7 +1896,7 @@ class $hi extends S {
@override
String error_text_limits_loading_failed(String provider) => "व्यापार ${provider} के लिए नहीं बनाया गया है। लोडिंग की सीमाएं विफल रहीं";
@override
String exchange_result_description(String fetchingLabel, String from) => "कृपया भेजें ${fetchingLabel} ${from} अगले पृष्ठ पर दिखाए गए पते पर";
String exchange_result_description(String fetchingLabel, String from) => "आपको अगले पृष्ठ पर दिखाए गए पते पर न्यूनतम ${fetchingLabel} ${from} भेजना होगा। यदि आप ${fetchingLabel} ${from} से कम राशि भेजते हैं तो यह परिवर्तित नहीं हो सकती है और इसे वापस नहीं किया जा सकता है।";
@override
String commit_transaction_amount_fee(String amount, String fee) => "लेन-देन करें\nरकम: ${amount}\nशुल्क: ${fee}";
@override
@@ -2660,7 +2660,7 @@ class $ru extends S {
@override
String error_text_limits_loading_failed(String provider) => "Сделка для ${provider} не создана. Ошибка загрузки лимитов";
@override
String exchange_result_description(String fetchingLabel, String from) => "Пожалуйста отправьте ${fetchingLabel} ${from} на адрес, указанный на следующей странице.";
String exchange_result_description(String fetchingLabel, String from) => "Вы должны отправить минимум ${fetchingLabel} ${from} на адрес, указанный на следующей странице. Если вы отправите сумму менее ${fetchingLabel} ${from}, то она может быть не конвертирована и не возвращена.";
@override
String commit_transaction_amount_fee(String amount, String fee) => "Подтвердить транзакцию \nСумма: ${amount}\nКомиссия: ${fee}";
@override
@@ -3424,7 +3424,7 @@ class $ko extends S {
@override
String error_text_limits_loading_failed(String provider) => "거래 ${provider} 가 생성되지 않습니다. 로딩 실패";
@override
String exchange_result_description(String fetchingLabel, String from) => "보내주세요 ${fetchingLabel} ${from} 다음 페이지에 표시된 주소로.";
String exchange_result_description(String fetchingLabel, String from) => "다음 페이지에 표시된 주소로 최소 ${fetchingLabel} ${from} 를 보내야합니다. ${fetchingLabel} ${from} 보다 적은 금액을 보내면 변환되지 않고 환불되지 않을 수 있습니다.";
@override
String commit_transaction_amount_fee(String amount, String fee) => "커밋 거래\n양: ${amount}\n보수: ${fee}";
@override
@@ -4188,7 +4188,7 @@ class $pt extends S {
@override
String error_text_limits_loading_failed(String provider) => "A troca por ${provider} não é criada. Falha no carregamento dos limites";
@override
String exchange_result_description(String fetchingLabel, String from) => "Por favor, envie ${fetchingLabel} ${from} para o endereço mostrado na próxima página.";
String exchange_result_description(String fetchingLabel, String from) => "Você deve enviar no mínimo ${fetchingLabel} ${from} para o endereço mostrado na próxima página. Se você enviar um valor inferior a ${fetchingLabel} ${from}, ele pode não ser convertido e pode não ser reembolsado.";
@override
String commit_transaction_amount_fee(String amount, String fee) => "Confirmar transação\nQuantia: ${amount}\nTaxa: ${fee}";
@override
@@ -4952,7 +4952,7 @@ class $uk extends S {
@override
String error_text_limits_loading_failed(String provider) => "Операція для ${provider} не створена. Помилка завантаження лімітів";
@override
String exchange_result_description(String fetchingLabel, String from) => "Будь ласка, відправте ${fetchingLabel} ${from} на адресу, вказану на наступній сторінці.";
String exchange_result_description(String fetchingLabel, String from) => "Ви повинні надіслати мінімум ${fetchingLabel} ${from} на адресу, вказану на наступній сторінці. Якщо ви надішлете суму меншу за ${fetchingLabel} ${from}, то вона може бути не конвертованою і не поверненою.";
@override
String commit_transaction_amount_fee(String amount, String fee) => "Підтвердити транзакцію \nСума: ${amount}\nКомісія: ${fee}";
@override
@@ -5716,7 +5716,7 @@ class $ja extends S {
@override
String error_text_limits_loading_failed(String provider) => "${provider} の取引は作成されません。 制限の読み込みに失敗しました";
@override
String exchange_result_description(String fetchingLabel, String from) => "送信してください ${fetchingLabel} ${from} 次のページに表示されているアドレスに.";
String exchange_result_description(String fetchingLabel, String from) => "次のページに示されているアドレスに最低 ${fetchingLabel} ${from} を送信する必要があります。 ${fetchingLabel} ${from} 未満の金額を送信すると、変換されず、返金されない場合があります。";
@override
String commit_transaction_amount_fee(String amount, String fee) => "トランザクションをコミット\n量: ${amount}\n費用: ${fee}";
@override
@@ -6484,7 +6484,7 @@ class $pl extends S {
@override
String error_text_limits_loading_failed(String provider) => "Wymiana dla ${provider} nie została utworzona. Ładowanie limitów nie powiodło się";
@override
String exchange_result_description(String fetchingLabel, String from) => "Proszę wyślij ${fetchingLabel} ${from} pod adres podany na następnej stronie.";
String exchange_result_description(String fetchingLabel, String from) => "Musisz wysłać co najmniej ${fetchingLabel} ${from} na adres podany na następnej stronie. Jeśli wyślesz kwotę niższą niż ${fetchingLabel} ${from}, może ona nie zostać przeliczona i może nie zostać zwrócona.";
@override
String commit_transaction_amount_fee(String amount, String fee) => "Zatwierdź transakcję\nIlość: ${amount}\nOpłata: ${fee}";
@override
@@ -7248,7 +7248,7 @@ class $es extends S {
@override
String error_text_limits_loading_failed(String provider) => "El comercio por ${provider} no se crea. Límites de carga fallidos";
@override
String exchange_result_description(String fetchingLabel, String from) => "Envíe ${fetchingLabel} ${from} a la dirección que se muestra en la página siguiente.";
String exchange_result_description(String fetchingLabel, String from) => "Debe enviar un mínimo de ${fetchingLabel} ${from} a la dirección que se muestra en la página siguiente. Si envía una cantidad inferior a ${fetchingLabel} ${from}, es posible que no se convierta y no se reembolse.";
@override
String commit_transaction_amount_fee(String amount, String fee) => "Confirmar transacción\nCantidad: ${amount}\nCuota: ${fee}";
@override
@@ -8012,7 +8012,7 @@ class $nl extends S {
@override
String error_text_limits_loading_failed(String provider) => "Ruil voor ${provider} is niet gemaakt. Beperkingen laden mislukt";
@override
String exchange_result_description(String fetchingLabel, String from) => "Zend alstublieft ${fetchingLabel} ${from} naar het adres op de volgende pagina.";
String exchange_result_description(String fetchingLabel, String from) => "U moet minimaal ${fetchingLabel} ${from} verzenden naar het adres dat op de volgende pagina wordt weergegeven. Als u een bedrag verzendt dat lager is dan ${fetchingLabel} ${from}, wordt het mogelijk niet omgezet en wordt het mogelijk niet terugbetaald.";
@override
String commit_transaction_amount_fee(String amount, String fee) => "Verricht transactie\nBedrag: ${amount}\nhonorarium: ${fee}";
@override
@@ -8776,7 +8776,7 @@ class $zh extends S {
@override
String error_text_limits_loading_failed(String provider) => "未創建 ${provider} 交易。 限制加載失敗";
@override
String exchange_result_description(String fetchingLabel, String from) => "请发送 ${fetchingLabel} ${from} 到下一頁顯示的地址.";
String exchange_result_description(String fetchingLabel, String from) => "您必須至少發送 ${fetchingLabel} ${from} 到下一頁上顯示的地址。 如果您發送的金額少於 ${fetchingLabel} ${from},則可能無法轉換,因此無法退還。";
@override
String commit_transaction_amount_fee(String amount, String fee) => "提交交易\n量: ${amount}\nFee: ${fee}";
@override
+13 -1
View File
@@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/order.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hive/hive.dart';
@@ -69,11 +70,17 @@ Future<void> main() async {
Hive.registerAdapter(ExchangeTemplateAdapter());
}
if (!Hive.isAdapterRegistered(Order.typeId)) {
Hive.registerAdapter(OrderAdapter());
}
final secureStorage = FlutterSecureStorage();
final transactionDescriptionsBoxKey = await getEncryptionKey(
secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
final tradesBoxKey = await getEncryptionKey(
secureStorage: secureStorage, forKey: Trade.boxKey);
final ordersBoxKey = await getEncryptionKey(
secureStorage: secureStorage, forKey: Order.boxKey);
final contacts = await Hive.openBox<Contact>(Contact.boxName);
final nodes = await Hive.openBox<Node>(Node.boxName);
final transactionDescriptions = await Hive.openBox<TransactionDescription>(
@@ -81,6 +88,8 @@ Future<void> main() async {
encryptionKey: transactionDescriptionsBoxKey);
final trades =
await Hive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey);
final orders =
await Hive.openBox<Order>(Order.boxName, encryptionKey: ordersBoxKey);
final walletInfoSource = await Hive.openBox<WalletInfo>(WalletInfo.boxName);
final templates = await Hive.openBox<Template>(Template.boxName);
final exchangeTemplates =
@@ -91,6 +100,7 @@ Future<void> main() async {
walletInfoSource: walletInfoSource,
contactSource: contacts,
tradesSource: trades,
ordersSource: orders,
// fiatConvertationService: fiatConvertationService,
templates: templates,
exchangeTemplates: exchangeTemplates,
@@ -118,6 +128,7 @@ Future<void> initialSetup(
@required Box<WalletInfo> walletInfoSource,
@required Box<Contact> contactSource,
@required Box<Trade> tradesSource,
@required Box<Order> ordersSource,
// @required FiatConvertationService fiatConvertationService,
@required Box<Template> templates,
@required Box<ExchangeTemplate> exchangeTemplates,
@@ -139,7 +150,8 @@ Future<void> initialSetup(
tradesSource: tradesSource,
templates: templates,
exchangeTemplates: exchangeTemplates,
transactionDescriptionBox: transactionDescriptions);
transactionDescriptionBox: transactionDescriptions,
ordersSource: ordersSource);
await bootstrap(navigatorKey);
monero_wallet.onStartup();
}
+13
View File
@@ -1,12 +1,15 @@
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/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/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/monero_account_list/account_list_item.dart';
import 'package:flutter/cupertino.dart';
@@ -285,6 +288,16 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) =>
getIt.get<TradeDetailsPage>(param1: settings.arguments as Trade));
case Routes.orderDetails:
return MaterialPageRoute<void>(
builder: (_) =>
getIt.get<OrderDetailsPage>(param1: settings.arguments as Order));
case Routes.wyre:
return MaterialPageRoute<void>(
builder: (_) =>
getIt.get<WyrePage>(param1: settings.arguments as String));
case Routes.restoreWalletFromSeedDetails:
final args = settings.arguments as List;
final walletRestorationFromSeedVM =
+2
View File
@@ -52,4 +52,6 @@ class Routes {
static const editBackupPassword = '/edit_backup_passowrd';
static const restoreFromBackup = '/restore_from_backup';
static const support = '/support';
static const orderDetails = '/order_details';
static const wyre = '/wyre';
}
+39 -3
View File
@@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/wallet_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/themes/theme_base.dart';
@@ -12,7 +13,9 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/address_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
class DashboardPage extends BasePage {
DashboardPage({
@@ -81,7 +84,7 @@ class DashboardPage extends BasePage {
final exchangeImage = Image.asset('assets/images/transfer.png',
height: 24.27, width: 22.25,
color: Theme.of(context).accentTextTheme.display3.backgroundColor);
final receiveImage = Image.asset('assets/images/download.png',
final buyImage = Image.asset('assets/images/coins.png',
height: 22.24, width: 24,
color: Theme.of(context).accentTextTheme.display3.backgroundColor);
_setEffects();
@@ -111,7 +114,7 @@ class DashboardPage extends BasePage {
)),
Container(
padding: EdgeInsets.only(left: 45, right: 45, bottom: 24),
child: Row(
child: Observer(builder: (_) => Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
ActionButton(
@@ -122,8 +125,41 @@ class DashboardPage extends BasePage {
image: exchangeImage,
title: S.of(context).exchange,
route: Routes.exchange),
if (walletViewModel.type == WalletType.bitcoin) 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 {
try {
walletViewModel.isRunningWebView = true;
final url =
await walletViewModel.wyreViewModel.wyreUrl;
await Navigator.of(context)
.pushNamed(Routes.wyre, arguments: url);
walletViewModel.isRunningWebView = false;
} catch(e) {
print(e.toString());
walletViewModel.isRunningWebView = false;
}
})
],
)),
],
),
)),
)
],
));
@@ -3,14 +3,16 @@ import 'package:flutter/material.dart';
class ActionButton extends StatelessWidget {
ActionButton(
{@required this.image,
@required this.title,
@required this.route,
this.alignment = Alignment.center});
@required this.title,
this.route,
this.onClick,
this.alignment = Alignment.center});
final Image image;
final String title;
final String route;
final Alignment alignment;
final void Function() onClick;
@override
Widget build(BuildContext context) {
@@ -23,8 +25,10 @@ class ActionButton extends StatelessWidget {
children: <Widget>[
GestureDetector(
onTap: () {
if (route.isNotEmpty) {
if (route?.isNotEmpty ?? false) {
Navigator.of(context, rootNavigator: true).pushNamed(route);
} else {
onClick?.call();
}
},
child: Container(
@@ -48,4 +52,4 @@ class ActionButton extends StatelessWidget {
),
);
}
}
}
@@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
class OrderRow extends StatelessWidget {
OrderRow({
@required this.onTap,
this.from,
this.to,
this.createdAtFormattedDate,
this.formattedAmount});
final VoidCallback onTap;
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) {
return InkWell(
onTap: onTap,
child: Container(
padding: EdgeInsets.fromLTRB(24, 8, 24, 8),
color: Colors.transparent,
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
wyreImage,
SizedBox(width: 12),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text('$from → $to',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).accentTextTheme.
display3.backgroundColor
)),
formattedAmount != null
? Text(formattedAmount + ' ' + to,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).accentTextTheme.
display3.backgroundColor
))
: Container()
]),
SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(createdAtFormattedDate,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).textTheme
.overline.backgroundColor))
])
],
)
)
],
),
));
}
}
@@ -1,3 +1,5 @@
import 'package:cake_wallet/src/screens/dashboard/widgets/order_row.dart';
import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -75,6 +77,21 @@ class TransactionsPage extends StatelessWidget {
);
}
if (item is OrderListItem) {
final order = item.order;
return OrderRow(
onTap: () => Navigator.of(context).pushNamed(
Routes.orderDetails,
arguments: order),
from: order.from,
to: order.to,
createdAtFormattedDate:
DateFormat('HH:mm').format(order.createdAt),
formattedAmount: item.orderFormattedAmount,
);
}
return Container(
color: Colors.transparent,
height: 1);
+2 -2
View File
@@ -272,7 +272,7 @@ class ExchangePage extends BasePage {
],
),
),
if (exchangeViewModel.isReceiveAmountEditable) Padding(
/*if (exchangeViewModel.isReceiveAmountEditable) Padding(
padding: EdgeInsets.only(top: 12, left: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
@@ -286,7 +286,7 @@ class ExchangePage extends BasePage {
),
],
)
),
),*/
Padding(
padding: EdgeInsets.only(top: 30, left: 24, bottom: 24),
child: Row(
@@ -0,0 +1,47 @@
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/view_model/order_details_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/standart_list_row.dart';
import 'package:cake_wallet/src/screens/trade_details/track_trade_list_item.dart';
class OrderDetailsPage extends BasePage {
OrderDetailsPage(this.orderDetailsViewModel);
@override
String get title => 'Order Details';
final OrderDetailsViewModel orderDetailsViewModel;
@override
Widget body(BuildContext context) {
return Observer(builder: (_) {
return SectionStandardList(
sectionCount: 1,
itemCounter: (int _) => orderDetailsViewModel.items.length,
itemBuilder: (_, __, index) {
final item = orderDetailsViewModel.items[index];
if (item is TrackTradeListItem) {
return GestureDetector(
onTap: item.onTap,
child: StandartListRow(
title: '${item.title}', value: '${item.value}'));
} else {
return GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: '${item.value}'));
showBar<void>(context, S.of(context).copied_to_clipboard);
},
child: StandartListRow(
title: '${item.title}', value: '${item.value}'));
}
});
});
}
}
+97
View File
@@ -0,0 +1,97 @@
import 'dart:async';
import 'dart:io';
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:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WyrePage extends BasePage {
WyrePage(this.wyreViewModel,
{@required this.ordersStore, @required this.url});
final OrdersStore ordersStore;
final String url;
final WyreViewModel wyreViewModel;
@override
String get title => S.current.buy;
@override
Color get backgroundDarkColor => Colors.white;
@override
Color get titleColor => Palette.darkBlueCraiola;
@override
Widget body(BuildContext context) =>
WyrePageBody(wyreViewModel, ordersStore: ordersStore, url: url);
}
class WyrePageBody extends StatefulWidget {
WyrePageBody(this.wyreViewModel, {this.ordersStore, this.url});
final OrdersStore ordersStore;
final String url;
final WyreViewModel wyreViewModel;
@override
WyrePageBodyState createState() => WyrePageBodyState();
}
class WyrePageBodyState extends State<WyrePageBody> {
String orderId;
WebViewController _webViewController;
GlobalKey _webViewkey;
Timer _timer;
bool _isSaving;
@override
void initState() {
super.initState();
_webViewkey = GlobalKey();
_isSaving = false;
widget.ordersStore.orderId = '';
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
_timer?.cancel();
_timer = Timer.periodic(Duration(seconds: 1), (timer) async {
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);
}
});
}
@override
Widget build(BuildContext context) {
return WebView(
key: _webViewkey,
initialUrl: widget.url,
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController controller) =>
setState(() => _webViewController = controller));
}
}
+45
View File
@@ -0,0 +1,45 @@
import 'dart:async';
import 'package:cake_wallet/entities/order.dart';
import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/store/settings_store.dart';
part 'orders_store.g.dart';
class OrdersStore = OrdersStoreBase with _$OrdersStore;
abstract class OrdersStoreBase with Store {
OrdersStoreBase({this.ordersSource, this.settingsStore}) {
orders = <OrderListItem>[];
orderId = '';
_onOrdersChanged =
ordersSource.watch().listen((_) async => await updateOrderList());
updateOrderList();
}
Box<Order> ordersSource;
StreamSubscription<BoxEvent> _onOrdersChanged;
SettingsStore settingsStore;
@observable
List<OrderListItem> orders;
@observable
Order order;
@observable
String orderId;
@action
void setOrder(Order order) => this.order = order;
@action
Future updateOrderList() async => orders =
ordersSource.values.map((order) => OrderListItem(
order: order,
displayMode: settingsStore.balanceDisplayMode)).toList();
}
@@ -1,7 +1,12 @@
import 'dart:convert';
import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
import 'package:cake_wallet/entities/balance.dart';
import 'package:cake_wallet/entities/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_history.dart';
@@ -14,13 +19,20 @@ import 'package:cake_wallet/entities/transaction_info.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/store/dashboard/orders_store.dart';
import 'package:cake_wallet/utils/mobx.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/filter_item.dart';
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/dashboard/action_list_display_mode.dart';
import 'package:cake_wallet/view_model/wyre_view_model.dart';
import 'package:crypto/crypto.dart';
import 'package:flutter/services.dart';
import 'package:hive/hive.dart';
import 'package:http/http.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/entities/sync_status.dart';
@@ -31,6 +43,8 @@ import 'package:cake_wallet/store/dashboard/trades_store.dart';
import 'package:cake_wallet/store/dashboard/trade_filter_store.dart';
import 'package:cake_wallet/store/dashboard/transaction_filter_store.dart';
import 'package:cake_wallet/view_model/dashboard/formatted_item_list.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:convert/convert.dart';
part 'dashboard_view_model.g.dart';
@@ -43,7 +57,10 @@ abstract class DashboardViewModelBase with Store {
this.tradesStore,
this.tradeFilterStore,
this.transactionFilterStore,
this.settingsStore}) {
this.settingsStore,
this.ordersSource,
this.ordersStore,
this.wyreViewModel}) {
filterItems = {
S.current.transactions: [
FilterItem(
@@ -78,6 +95,8 @@ abstract class DashboardViewModelBase with Store {
]
};
isRunningWebView = false;
name = appStore.wallet?.name;
wallet ??= appStore.wallet;
type = wallet.type;
@@ -143,6 +162,9 @@ abstract class DashboardViewModelBase with Store {
@observable
String subname;
@observable
bool isRunningWebView;
@computed
String get address => wallet.address;
@@ -173,6 +195,11 @@ abstract class DashboardViewModelBase with Store {
.where((trade) => trade.trade.walletId == wallet.id)
.toList();
@computed
List<OrderListItem> get orders => ordersStore.orders
.where((item) => item.order.walletId == wallet.id)
.toList();
@computed
double get price => balanceViewModel.price;
@@ -182,6 +209,7 @@ abstract class DashboardViewModelBase with Store {
_items.addAll(transactionFilterStore.filtered(transactions: transactions));
_items.addAll(tradeFilterStore.filtered(trades: trades, wallet: wallet));
_items.addAll(orders);
return formattedItemsList(_items);
}
@@ -191,6 +219,8 @@ abstract class DashboardViewModelBase with Store {
bool get hasRescan => wallet.type == WalletType.monero;
Box<Order> ordersSource;
BalanceViewModel balanceViewModel;
AppStore appStore;
@@ -199,10 +229,14 @@ abstract class DashboardViewModelBase with Store {
TradesStore tradesStore;
OrdersStore ordersStore;
TradeFilterStore tradeFilterStore;
TransactionFilterStore transactionFilterStore;
WyreViewModel wyreViewModel;
Map<String, List<FilterItem>> filterItems;
bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled;
@@ -285,4 +319,6 @@ abstract class DashboardViewModelBase with Store {
balanceViewModel: balanceViewModel,
settingsStore: appStore.settingsStore)));
}
}
@@ -0,0 +1,21 @@
import 'package:cake_wallet/entities/order.dart';
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart';
class OrderListItem extends ActionListItem {
OrderListItem({this.order, this.displayMode});
final Order order;
final BalanceDisplayMode displayMode;
String get orderFormattedAmount {
return order.amount != null
? displayMode == BalanceDisplayMode.hiddenBalance
? '---'
: order.amountFormatted()
: order.amount;
}
@override
DateTime get date => order.createdAt;
}
@@ -405,12 +405,13 @@ abstract class ExchangeViewModelBase with Store {
}
void _defineIsReceiveAmountEditable() {
if ((provider is ChangeNowExchangeProvider)
/*if ((provider is ChangeNowExchangeProvider)
&&(depositCurrency == CryptoCurrency.xmr)
&&(receiveCurrency == CryptoCurrency.btc)) {
isReceiveAmountEditable = true;
} else {
isReceiveAmountEditable = false;
}
}*/
isReceiveAmountEditable = false;
}
}
@@ -0,0 +1,85 @@
import 'dart:async';
import 'package:cake_wallet/entities/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';
part 'order_details_view_model.g.dart';
class OrderDetailsViewModel = OrderDetailsViewModelBase
with _$OrderDetailsViewModel;
abstract class OrderDetailsViewModelBase with Store {
OrderDetailsViewModelBase({this.wyreViewModel, Order orderForDetails}) {
order = orderForDetails;
items = ObservableList<StandartListItem>();
_updateItems();
_updateOrder();
_timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateOrder());
}
@observable
Order order;
@observable
ObservableList<StandartListItem> items;
WyreViewModel wyreViewModel;
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();
} catch (e) {
print(e.toString());
}
}
void _updateItems() {
final dateFormat = DateFormatter.withCurrentLocal();
final buildURL =
wyreViewModel.trackUrl + '${order.transferId}';
items?.clear();
items.addAll([
StandartListItem(
title: 'Transfer ID',
value: order.transferId),
StandartListItem(
title: S.current.trade_details_state,
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}')
]);
}
}
+42
View File
@@ -0,0 +1,42 @@
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/entities/wallet_type.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.walletId, @required this.address, @required this.type,
@required this.wyreService});
Future<String> get wyreUrl => wyreService.getWyreUrl();
String get trackUrl => wyreService.trackUrl;
final Box<Order> ordersSource;
final OrdersStore ordersStore;
final String walletId;
final WalletType type;
final String address;
final WyreService wyreService;
Future<void> saveOrder(String orderId) async {
try {
final order = await wyreService.findOrderById(orderId);
order.receiveAddress = address;
order.walletId = walletId;
await ordersSource.add(order);
ordersStore.setOrder(order);
} catch (e) {
print(e.toString());
}
}
}