mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
Merge remote-tracking branch 'origin/dev' into CW-1529-Improved-Seed-UI
# Conflicts: # lib/new-ui/pages/home_page.dart # res/values/strings_ar.arb # res/values/strings_bg.arb # res/values/strings_cs.arb # res/values/strings_es.arb # res/values/strings_fa.arb # res/values/strings_fr.arb # res/values/strings_gn.arb # res/values/strings_ha.arb # res/values/strings_hi.arb # res/values/strings_hr.arb # res/values/strings_hy.arb # res/values/strings_id.arb # res/values/strings_it.arb # res/values/strings_ja.arb # res/values/strings_ko.arb # res/values/strings_my.arb # res/values/strings_nl.arb # res/values/strings_pl.arb # res/values/strings_pt.arb # res/values/strings_pt_BR.arb # res/values/strings_ru.arb # res/values/strings_th.arb # res/values/strings_tl.arb # res/values/strings_tr.arb # res/values/strings_uk.arb # res/values/strings_ur.arb # res/values/strings_vi.arb # res/values/strings_yo.arb # res/values/strings_zh.arb
This commit is contained in:
@@ -5,6 +5,7 @@ import 'package:cake_wallet/entities/calculate_fiat_amount.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/entities/provider_types.dart';
|
||||
import 'package:cake_wallet/exchange/limits.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
||||
enum ProviderRecommendation { bestRate, lowKyc, successRate }
|
||||
@@ -13,11 +14,11 @@ extension RecommendationTitle on ProviderRecommendation {
|
||||
String get title {
|
||||
switch (this) {
|
||||
case ProviderRecommendation.bestRate:
|
||||
return 'BEST RATE';
|
||||
return S.current.best_rate;
|
||||
case ProviderRecommendation.lowKyc:
|
||||
return 'LOW KYC';
|
||||
return S.current.low_kyc;
|
||||
case ProviderRecommendation.successRate:
|
||||
return 'HIGHEST SUCCESS RATE';
|
||||
return S.current.highest_success_rate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+21
-11
@@ -115,7 +115,7 @@ extension PaymentTypeTitle on PaymentType {
|
||||
case PaymentType.creditCard:
|
||||
case PaymentType.debitCard:
|
||||
case PaymentType.yellowCardBankTransfer:
|
||||
return 'assets/images/card.svg';
|
||||
return 'assets/new-ui/buy_payment_methods/debit_card.svg';
|
||||
case PaymentType.bankTransfer:
|
||||
return 'assets/images/bank_light.svg';
|
||||
case PaymentType.skrill:
|
||||
@@ -132,17 +132,21 @@ extension PaymentTypeTitle on PaymentType {
|
||||
String? get darkIconPath {
|
||||
switch (this) {
|
||||
case PaymentType.all:
|
||||
return 'assets/images/usd_round_dark.svg';
|
||||
return 'assets/new-ui/buy_payment_methods/all_methods.svg';
|
||||
case PaymentType.creditCard:
|
||||
case PaymentType.debitCard:
|
||||
case PaymentType.yellowCardBankTransfer:
|
||||
return 'assets/images/card_dark.svg';
|
||||
return 'assets/new-ui/buy_payment_methods/debit_card.svg';
|
||||
case PaymentType.bankTransfer:
|
||||
return 'assets/images/bank_dark.svg';
|
||||
return 'assets/new-ui/buy_payment_methods/bank_transfer.svg';
|
||||
case PaymentType.skrill:
|
||||
return 'assets/images/skrill.svg';
|
||||
case PaymentType.applePay:
|
||||
return 'assets/images/apple_pay_round_dark.svg';
|
||||
return 'assets/new-ui/buy_payment_methods/apple_pay.svg';
|
||||
case PaymentType.googlePay:
|
||||
return "assets/new-ui/buy_payment_methods/google_pay.svg";
|
||||
case PaymentType.paypal:
|
||||
return "assets/new-ui/buy_payment_methods/paypal.svg";
|
||||
case PaymentType.revolutPay:
|
||||
return 'assets/images/revolut_dark.svg';
|
||||
default:
|
||||
@@ -150,6 +154,12 @@ extension PaymentTypeTitle on PaymentType {
|
||||
}
|
||||
}
|
||||
|
||||
bool get isMonochromeIcon => [
|
||||
"assets/new-ui/buy_payment_methods/all_methods.svg",
|
||||
"assets/new-ui/buy_payment_methods/debit_card.svg",
|
||||
"assets/new-ui/buy_payment_methods/bank_transfer.svg"
|
||||
].contains(darkIconPath);
|
||||
|
||||
String? get description {
|
||||
switch (this) {
|
||||
default:
|
||||
@@ -190,7 +200,7 @@ class PaymentMethod extends SelectableOption {
|
||||
return PaymentMethod(
|
||||
paymentMethodType: PaymentType.all,
|
||||
customTitle: 'All Payment Methods',
|
||||
customIconPath: 'assets/images/dollar_coin.svg');
|
||||
customIconPath: 'assets/new-ui/buy_payment_methods/all_methods.svg');
|
||||
}
|
||||
|
||||
factory PaymentMethod.fromOnramperJson(Map<String, dynamic> json) {
|
||||
@@ -199,7 +209,7 @@ class PaymentMethod extends SelectableOption {
|
||||
paymentMethodType: type ?? PaymentType.unknown,
|
||||
customPaymentMethodType: json['paymentTypeId'] as String?,
|
||||
customTitle: json['name'] as String? ?? 'Unknown',
|
||||
customIconPath: json['icon'] as String? ?? 'assets/images/card.png',
|
||||
customIconPath: json['icon'] as String? ?? 'assets/new-ui/buy_payment_methods/debit_card.svg',
|
||||
customDescription: json['description'] as String?);
|
||||
}
|
||||
|
||||
@@ -207,14 +217,14 @@ class PaymentMethod extends SelectableOption {
|
||||
return PaymentMethod(
|
||||
paymentMethodType: paymentType,
|
||||
customTitle: paymentMethod,
|
||||
customIconPath: 'assets/images/card.png');
|
||||
customIconPath: 'assets/new-ui/buy_payment_methods/debit_card.svg');
|
||||
}
|
||||
|
||||
factory PaymentMethod.fromMoonPayJson(Map<String, dynamic> json, PaymentType paymentType) {
|
||||
return PaymentMethod(
|
||||
paymentMethodType: paymentType,
|
||||
customTitle: json['paymentMethod'] as String,
|
||||
customIconPath: 'assets/images/card.png');
|
||||
customIconPath: 'assets/new-ui/buy_payment_methods/debit_card.svg');
|
||||
}
|
||||
|
||||
factory PaymentMethod.fromMeldJson(Map<String, dynamic> json) {
|
||||
@@ -223,7 +233,7 @@ class PaymentMethod extends SelectableOption {
|
||||
return PaymentMethod(
|
||||
paymentMethodType: type ?? PaymentType.unknown,
|
||||
customTitle: json['name'] as String? ?? 'Unknown',
|
||||
customIconPath: logos['dark'] as String? ?? 'assets/images/card.png',
|
||||
customIconPath: logos['dark'] as String? ?? 'assets/new-ui/buy_payment_methods/debit_card.svg',
|
||||
customDescription: json['description'] as String?);
|
||||
}
|
||||
|
||||
@@ -232,7 +242,7 @@ class PaymentMethod extends SelectableOption {
|
||||
return PaymentMethod(
|
||||
paymentMethodType: type ?? PaymentType.unknown,
|
||||
customTitle: json['payment_method'] as String? ?? 'Unknown',
|
||||
customIconPath: 'assets/images/card.png',
|
||||
customIconPath: 'assets/new-ui/buy_payment_methods/debit_card.svg',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
@@ -13,13 +14,19 @@ import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/payjoin_transaction_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:collection/collection.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class CsvExportService {
|
||||
CsvExportService({required this.transactionDescriptionBox});
|
||||
|
||||
final Box<TransactionDescription> transactionDescriptionBox;
|
||||
|
||||
static const _columns = [
|
||||
'record_type',
|
||||
'date_time',
|
||||
@@ -42,23 +49,46 @@ class CsvExportService {
|
||||
|
||||
static const _utf8Bom = '';
|
||||
|
||||
static const _formulaTriggers = {'=', '+', '-', '@', '\t', '\r'};
|
||||
|
||||
static final _plainNumber = RegExp(r'^-?\d+(\.\d+)?$');
|
||||
|
||||
String buildCsvContent(List<ActionListItem> items) {
|
||||
final buf = StringBuffer();
|
||||
buf.write(_utf8Bom);
|
||||
buf.writeln(_columns.join(','));
|
||||
|
||||
final primaryAddress = _primaryAddressOf(items);
|
||||
|
||||
for (final item in items) {
|
||||
if (item is DateSectionItem) continue;
|
||||
|
||||
final row = _buildRow(item);
|
||||
final row = _buildRow(item, primaryAddress);
|
||||
if (row != null) buf.writeln(row);
|
||||
}
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
String? _buildRow(ActionListItem item) {
|
||||
if (item is TransactionListItem) return _transactionRow(item);
|
||||
String _primaryAddressOf(List<ActionListItem> items) {
|
||||
for (final item in items) {
|
||||
if (item is TransactionListItem) {
|
||||
return item.balanceViewModel.wallet.walletAddresses.primaryAddress;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
String _noteFor(TransactionListItem item, String primaryAddress) {
|
||||
final txHash = item.transaction.txHash;
|
||||
final descriptionKey = '${txHash}_$primaryAddress';
|
||||
final description = transactionDescriptionBox.values
|
||||
.firstWhereOrNull((val) => val.id == descriptionKey || val.id == txHash);
|
||||
return description?.note ?? '';
|
||||
}
|
||||
|
||||
String? _buildRow(ActionListItem item, String primaryAddress) {
|
||||
if (item is TransactionListItem) return _transactionRow(item, primaryAddress);
|
||||
if (item is TradeListItem) return _tradeRow(item);
|
||||
if (item is OrderListItem) return _orderRow(item);
|
||||
if (item is AnonpayTransactionListItem) return _anonpayRow(item);
|
||||
@@ -66,7 +96,7 @@ class CsvExportService {
|
||||
return null;
|
||||
}
|
||||
|
||||
String _transactionRow(TransactionListItem item) {
|
||||
String _transactionRow(TransactionListItem item, String primaryAddress) {
|
||||
final tx = item.transaction;
|
||||
final type = tx.direction == TransactionDirection.incoming ? 'incoming' : 'outgoing';
|
||||
final status = tx.isPending ? 'pending' : 'confirmed';
|
||||
@@ -88,7 +118,7 @@ class CsvExportService {
|
||||
tx.id,
|
||||
address,
|
||||
status,
|
||||
'',
|
||||
_noteFor(item, primaryAddress),
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
@@ -200,10 +230,20 @@ class CsvExportService {
|
||||
String _row(List<String> fields) => fields.map(escapeField).join(',');
|
||||
|
||||
String escapeField(String field) {
|
||||
if (field.contains(',') || field.contains('"') || field.contains('\n')) {
|
||||
return '"${field.replaceAll('"', '""')}"';
|
||||
final value = _neutralizeFormula(field);
|
||||
if (value.contains(',') ||
|
||||
value.contains('"') ||
|
||||
value.contains('\n') ||
|
||||
value.contains('\r')) {
|
||||
return '"${value.replaceAll('"', '""')}"';
|
||||
}
|
||||
return field;
|
||||
return value;
|
||||
}
|
||||
|
||||
String _neutralizeFormula(String field) {
|
||||
if (field.isEmpty || !_formulaTriggers.contains(field[0])) return field;
|
||||
if (_plainNumber.hasMatch(field)) return field;
|
||||
return "'$field";
|
||||
}
|
||||
|
||||
String _isoDate(DateTime dt) => dt.toUtc().toIso8601String();
|
||||
|
||||
+20
-1
@@ -22,6 +22,7 @@ import 'package:cake_wallet/cake_pay/src/services/cake_pay_api.dart';
|
||||
import 'package:cake_wallet/cake_pay/src/services/cake_pay_service.dart';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/core/backup_service_v3.dart';
|
||||
import 'package:cake_wallet/core/csv_export_service.dart';
|
||||
import 'package:cake_wallet/core/key_service.dart';
|
||||
import 'package:cake_wallet/core/new_wallet_arguments.dart';
|
||||
import 'package:cake_wallet/core/new_wallet_type_arguments.dart';
|
||||
@@ -56,6 +57,9 @@ import 'package:cake_wallet/new-ui/new_dashboard.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/about_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/account_customizer.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/bridge/bridge_amount_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/bridge/bridge_network_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/bridge/bridge_receiving_wallet_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/buy_sell/buy_sell_amount_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/coin_control_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/addresses_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/home_page.dart';
|
||||
@@ -67,6 +71,7 @@ import "package:cake_wallet/new-ui/pages/seed/pre_seed_page.dart";
|
||||
import "package:cake_wallet/new-ui/pages/seed/show_keys_disclaimer_page.dart";
|
||||
import 'package:cake_wallet/new-ui/viewmodels/lightning_username/lightning_username_bloc.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/addresses_page/address_label_input.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/transaction_details_modal.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_label_modal.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/swap_page.dart';
|
||||
@@ -599,6 +604,13 @@ Future<void> setup({
|
||||
getIt.registerFactory<AccountCreationModal>(() => AccountCreationModal(
|
||||
accountEditOrCreateViewModel: getIt.get<MoneroAccountEditOrCreateViewModel>()));
|
||||
|
||||
getIt.registerFactoryParam<AccountCustomizer, DashboardViewModel, void>(
|
||||
(dashboardViewModel, _) => AccountCustomizer(
|
||||
accountListViewModel: getIt.get<MoneroAccountListViewModel>(),
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactory<LightningUsernameBloc>(
|
||||
() => LightningUsernameBloc(getIt.get<AppStore>().wallet!));
|
||||
|
||||
@@ -1408,6 +1420,9 @@ Future<void> setup({
|
||||
param1: derivations,
|
||||
)));
|
||||
|
||||
getIt.registerFactory<CsvExportService>(
|
||||
() => CsvExportService(transactionDescriptionBox: _transactionDescriptionBox));
|
||||
|
||||
getIt.registerFactoryParam<TransactionDetailsViewModel, List<dynamic>, void>((params, _) {
|
||||
final transactionInfo = params[0] as TransactionInfo;
|
||||
final canReplaceByFee = params[1] as bool? ?? false;
|
||||
@@ -1493,7 +1508,11 @@ Future<void> setup({
|
||||
|
||||
getIt.registerFactory(() => BuyAmountViewModel());
|
||||
|
||||
getIt.registerFactory(() => BuySellViewModel(getIt.get<AppStore>()));
|
||||
getIt.registerFactoryParam<BuySellViewModel, BuySellPageMode, void>((mode, _) => BuySellViewModel(
|
||||
mode: mode, getIt.get<AppStore>(), fiatConversionStore: getIt.get<FiatConversionStore>()));
|
||||
|
||||
getIt.registerFactoryParam<NewBuySellAmountPage, BuySellPageMode, void>((mode, _) =>
|
||||
NewBuySellAmountPage(buySellViewModel: getIt.get<BuySellViewModel>(param1: mode)));
|
||||
|
||||
getIt.registerFactory(
|
||||
() => BuySellPage(getIt.get<BuySellViewModel>(), getIt.get<AddressResolverService>()));
|
||||
|
||||
@@ -8,8 +8,9 @@ String calculateFiatAmount({double? price, String? cryptoAmount, bool raw = fals
|
||||
cryptoAmount = cryptoAmount.sanitized();
|
||||
|
||||
final _amount = double.tryParse(cryptoAmount);
|
||||
if (_amount == null || _amount.isNaN) return '0.00';
|
||||
if (_amount == null || !_amount.isFinite) return '0.00';
|
||||
final _result = price * _amount;
|
||||
if (!_result.isFinite) return '0.00';
|
||||
final result = _result < 0 ? _result * -1 : _result;
|
||||
|
||||
if (result == 0.0) {
|
||||
|
||||
@@ -7,6 +7,10 @@ String calculateFiatAmountRaw({required double cryptoAmount, double? price}) {
|
||||
|
||||
final result = price * cryptoAmount;
|
||||
|
||||
if (!result.isFinite) {
|
||||
return '0.00';
|
||||
}
|
||||
|
||||
if (result == 0.0) {
|
||||
return '0.00';
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ const moneroWorldNodeUri = '.moneroworld.com';
|
||||
const decredDefaultUri = "default-spv-nodes";
|
||||
const dogecoinDefaultNodeUri = 'dogecoin.stackwallet.com:50022';
|
||||
const baseDefaultNodeUri = 'base-rpc.publicnode.com';
|
||||
const arbitrumDefaultNodeUri = 'arbitrum.nownodes.io';
|
||||
const arbitrumDefaultNodeUri = 'arbitrum-one-rpc.publicnode.com';
|
||||
const bscDefaultNodeUri = 'bsc-dataseed.bnbchain.org';
|
||||
const zcashDefaultNodeUri = 'zec.rocks:443';
|
||||
|
||||
|
||||
+148
-64
@@ -1,12 +1,13 @@
|
||||
import 'package:cw_core/amount/money.dart';
|
||||
import 'package:cw_core/currency.dart';
|
||||
import 'package:cw_core/enumerable_item.dart';
|
||||
import "package:cw_core/amount/money.dart";
|
||||
import "package:cw_core/currency.dart";
|
||||
import "package:cw_core/enumerable_item.dart";
|
||||
|
||||
class FiatCurrency extends EnumerableItem<String> with Serializable<String> implements Currency {
|
||||
const FiatCurrency({
|
||||
required this.symbol,
|
||||
required this.countryCode,
|
||||
required this.fullName,
|
||||
required this.emoji,
|
||||
this.decimals = 2,
|
||||
}) : super(title: symbol, raw: symbol);
|
||||
|
||||
@@ -21,6 +22,8 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> impl
|
||||
@override
|
||||
final String symbol;
|
||||
|
||||
final String emoji;
|
||||
|
||||
static List<FiatCurrency> get all => _all.values.toList();
|
||||
|
||||
static List<FiatCurrency> get currenciesAvailableToBuyWith => [
|
||||
@@ -68,67 +71,148 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> impl
|
||||
kes,
|
||||
];
|
||||
|
||||
static const amd = FiatCurrency(symbol: 'AMD', countryCode: "arm", fullName: "Armenian Dram");
|
||||
static const ars = FiatCurrency(symbol: 'ARS', countryCode: "arg", fullName: "Argentine Peso");
|
||||
static const aud = FiatCurrency(symbol: 'AUD', countryCode: "aus", fullName: "Australian Dollar");
|
||||
static const bdt = FiatCurrency(symbol: 'BDT', countryCode: "bgd", fullName: "Bangladeshi Taka");
|
||||
static const bgn = FiatCurrency(symbol: 'BGN', countryCode: "bgr", fullName: "Bulgarian Lev");
|
||||
static const brl = FiatCurrency(symbol: 'BRL', countryCode: "bra", fullName: "Brazilian Real");
|
||||
static const cad = FiatCurrency(symbol: 'CAD', countryCode: "cad", fullName: "Canadian Dollar");
|
||||
static const chf = FiatCurrency(symbol: 'CHF', countryCode: "che", fullName: "Swiss Franc");
|
||||
static const clp = FiatCurrency(symbol: 'CLP', countryCode: "chl", fullName: "Chilean Peso");
|
||||
static const cny = FiatCurrency(symbol: 'CNY', countryCode: "chn", fullName: "Chinese Yuan");
|
||||
static const cop = FiatCurrency(symbol: 'COP', countryCode: "col", fullName: "Colombian Peso");
|
||||
static const czk = FiatCurrency(symbol: 'CZK', countryCode: "czk", fullName: "Czech Koruna");
|
||||
static const dkk = FiatCurrency(symbol: 'DKK', countryCode: "dnk", fullName: "Danish Krone");
|
||||
static const egp = FiatCurrency(symbol: 'EGP', countryCode: "egy", fullName: "Egyptian Pound");
|
||||
static const eur = FiatCurrency(symbol: 'EUR', countryCode: "eur", fullName: "Euro");
|
||||
static const gbp = FiatCurrency(symbol: 'GBP', countryCode: "gbr", fullName: "Pound Sterling");
|
||||
static const ghs = FiatCurrency(symbol: 'GHS', countryCode: "gha", fullName: "Ghanaian Cedi");
|
||||
static const gtq =
|
||||
FiatCurrency(symbol: 'GTQ', countryCode: "gtm", fullName: "Guatemalan Quetzal");
|
||||
static const hkd = FiatCurrency(symbol: 'HKD', countryCode: "hkg", fullName: "Hong Kong Dollar");
|
||||
static const hrk = FiatCurrency(symbol: 'HRK', countryCode: "hrv", fullName: "Croatian Kuna");
|
||||
static const huf = FiatCurrency(symbol: 'HUF', countryCode: "hun", fullName: "Hungarian Forint");
|
||||
static const idr = FiatCurrency(symbol: 'IDR', countryCode: "idn", fullName: "Indonesian Rupiah");
|
||||
static const ils =
|
||||
FiatCurrency(symbol: 'ILS', countryCode: "isr", fullName: "Israeli New Shekel");
|
||||
static const inr = FiatCurrency(symbol: 'INR', countryCode: "ind", fullName: "Indian Rupee");
|
||||
static const irr = FiatCurrency(symbol: 'IRR', countryCode: "irn", fullName: "Iranian Rial");
|
||||
static const isk =
|
||||
FiatCurrency(symbol: 'ISK', countryCode: "isl", fullName: "Icelandic Krona Króna");
|
||||
static const jpy = FiatCurrency(symbol: 'JPY', countryCode: "jpn", fullName: "Japanese Yen");
|
||||
static const krw = FiatCurrency(symbol: 'KRW', countryCode: "kor", fullName: "South Korean Won");
|
||||
static const mad = FiatCurrency(symbol: 'MAD', countryCode: "mar", fullName: "Moroccan Dirham");
|
||||
static const mxn = FiatCurrency(symbol: 'MXN', countryCode: "mex", fullName: "Mexican Peso");
|
||||
static const myr = FiatCurrency(symbol: 'MYR', countryCode: "mys", fullName: "Malaysian Ringgit");
|
||||
static const ngn = FiatCurrency(symbol: 'NGN', countryCode: "nga", fullName: "Nigerian Naira");
|
||||
static const nok = FiatCurrency(symbol: 'NOK', countryCode: "nor", fullName: "Norwegian Krone");
|
||||
static const nzd =
|
||||
FiatCurrency(symbol: 'NZD', countryCode: "nzl", fullName: "New Zealand Dollar");
|
||||
static const php = FiatCurrency(symbol: 'PHP', countryCode: "phl", fullName: "Philippine Peso");
|
||||
static const pkr = FiatCurrency(symbol: 'PKR', countryCode: "pak", fullName: "Pakistani Rupee");
|
||||
static const pln =
|
||||
FiatCurrency(symbol: 'PLN', countryCode: "pol", fullName: "Poland Zloty złoty");
|
||||
static const ron = FiatCurrency(symbol: 'RON', countryCode: "rou", fullName: "Romanian Leu");
|
||||
static const rub = FiatCurrency(symbol: 'RUB', countryCode: "rus", fullName: "Russian Ruble");
|
||||
static const sar = FiatCurrency(symbol: 'SAR', countryCode: "sau", fullName: "Saudi Riyal");
|
||||
static const sek = FiatCurrency(symbol: 'SEK', countryCode: "swe", fullName: "Swedish Krona");
|
||||
static const sgd = FiatCurrency(symbol: 'SGD', countryCode: "sgp", fullName: "Singapore Dollar");
|
||||
static const thb =
|
||||
FiatCurrency(symbol: 'THB', countryCode: "tha", fullName: "New Thaiwan Dollar");
|
||||
static const twd = FiatCurrency(symbol: 'TWD', countryCode: "twn", fullName: "Thai Baht");
|
||||
static const uah = FiatCurrency(symbol: 'UAH', countryCode: "ukr", fullName: "Ukrainian Hryvnia");
|
||||
static const usd =
|
||||
FiatCurrency(symbol: 'USD', countryCode: "usa", fullName: "United States Dollar");
|
||||
static const vef =
|
||||
FiatCurrency(symbol: 'VEF', countryCode: "ven", fullName: "Venezuelan Bolivar Bolívar");
|
||||
static const vnd =
|
||||
FiatCurrency(symbol: 'VND', countryCode: "vnm", fullName: "Vietnamese Dong đồng");
|
||||
static const zar =
|
||||
FiatCurrency(symbol: 'ZAR', countryCode: "saf", fullName: "South African Rand");
|
||||
static const tur = FiatCurrency(symbol: 'TRY', countryCode: "tur", fullName: "Turkish Lira");
|
||||
static const kes = FiatCurrency(symbol: 'KES', countryCode: "ken", fullName: "Kenyan Shillings");
|
||||
static const amd =
|
||||
FiatCurrency(symbol: "AMD", countryCode: "arm", fullName: "Armenian Dram", emoji: "🇦🇲");
|
||||
static const ars =
|
||||
FiatCurrency(symbol: "ARS", countryCode: "arg", fullName: "Argentine Peso", emoji: "🇦🇷");
|
||||
static const aud =
|
||||
FiatCurrency(symbol: "AUD", countryCode: "aus", fullName: "Australian Dollar", emoji: "🇦🇺");
|
||||
static const bdt =
|
||||
FiatCurrency(symbol: "BDT", countryCode: "bgd", fullName: "Bangladeshi Taka", emoji: "🇧🇩");
|
||||
static const bgn =
|
||||
FiatCurrency(symbol: "BGN", countryCode: "bgr", fullName: "Bulgarian Lev", emoji: "🇧🇬");
|
||||
static const brl =
|
||||
FiatCurrency(symbol: "BRL", countryCode: "bra", fullName: "Brazilian Real", emoji: "🇧🇷");
|
||||
static const cad =
|
||||
FiatCurrency(symbol: "CAD", countryCode: "cad", fullName: "Canadian Dollar", emoji: "🇨🇦");
|
||||
static const chf =
|
||||
FiatCurrency(symbol: "CHF", countryCode: "che", fullName: "Swiss Franc", emoji: "🇨🇭");
|
||||
static const clp =
|
||||
FiatCurrency(symbol: "CLP", countryCode: "chl", fullName: "Chilean Peso", emoji: "🇨🇱");
|
||||
static const cny =
|
||||
FiatCurrency(symbol: "CNY", countryCode: "chn", fullName: "Chinese Yuan", emoji: "🇨🇳");
|
||||
static const cop =
|
||||
FiatCurrency(symbol: "COP", countryCode: "col", fullName: "Colombian Peso", emoji: "🇨🇴");
|
||||
static const czk =
|
||||
FiatCurrency(symbol: "CZK", countryCode: "czk", fullName: "Czech Koruna", emoji: "🇨🇿");
|
||||
static const dkk =
|
||||
FiatCurrency(symbol: "DKK", countryCode: "dnk", fullName: "Danish Krone", emoji: "🇩🇰");
|
||||
static const egp =
|
||||
FiatCurrency(symbol: "EGP", countryCode: "egy", fullName: "Egyptian Pound", emoji: "🇪🇬");
|
||||
static const eur =
|
||||
FiatCurrency(symbol: "EUR", countryCode: "eur", fullName: "Euro", emoji: "🇪🇺");
|
||||
static const gbp =
|
||||
FiatCurrency(symbol: "GBP", countryCode: "gbr", fullName: "Pound Sterling", emoji: "🇬🇧");
|
||||
static const ghs =
|
||||
FiatCurrency(symbol: "GHS", countryCode: "gha", fullName: "Ghanaian Cedi", emoji: "🇬🇭");
|
||||
static const gtq = FiatCurrency(
|
||||
symbol: "GTQ",
|
||||
countryCode: "gtm",
|
||||
fullName: "Guatemalan Quetzal",
|
||||
emoji: "🇬🇹",
|
||||
);
|
||||
static const hkd =
|
||||
FiatCurrency(symbol: "HKD", countryCode: "hkg", fullName: "Hong Kong Dollar", emoji: "🇭🇰");
|
||||
static const hrk =
|
||||
FiatCurrency(symbol: "HRK", countryCode: "hrv", fullName: "Croatian Kuna", emoji: "🇭🇷");
|
||||
static const huf =
|
||||
FiatCurrency(symbol: "HUF", countryCode: "hun", fullName: "Hungarian Forint", emoji: "🇭🇺");
|
||||
static const idr =
|
||||
FiatCurrency(symbol: "IDR", countryCode: "idn", fullName: "Indonesian Rupiah", emoji: "🇮🇩");
|
||||
static const ils = FiatCurrency(
|
||||
symbol: "ILS",
|
||||
countryCode: "isr",
|
||||
fullName: "Israeli New Shekel",
|
||||
emoji: "🇮🇱",
|
||||
);
|
||||
static const inr =
|
||||
FiatCurrency(symbol: "INR", countryCode: "ind", fullName: "Indian Rupee", emoji: "🇮🇳");
|
||||
static const irr =
|
||||
FiatCurrency(symbol: "IRR", countryCode: "irn", fullName: "Iranian Rial", emoji: "🇮🇷");
|
||||
static const isk = FiatCurrency(
|
||||
symbol: "ISK",
|
||||
countryCode: "isl",
|
||||
fullName: "Icelandic Krona Króna",
|
||||
emoji: "🇮🇸",
|
||||
);
|
||||
static const jpy =
|
||||
FiatCurrency(symbol: "JPY", countryCode: "jpn", fullName: "Japanese Yen", emoji: "🇯🇵");
|
||||
static const krw =
|
||||
FiatCurrency(symbol: "KRW", countryCode: "kor", fullName: "South Korean Won", emoji: "🇰🇷");
|
||||
static const mad =
|
||||
FiatCurrency(symbol: "MAD", countryCode: "mar", fullName: "Moroccan Dirham", emoji: "🇲🇦");
|
||||
static const mxn =
|
||||
FiatCurrency(symbol: "MXN", countryCode: "mex", fullName: "Mexican Peso", emoji: "🇲🇽");
|
||||
static const myr =
|
||||
FiatCurrency(symbol: "MYR", countryCode: "mys", fullName: "Malaysian Ringgit", emoji: "🇲🇾");
|
||||
static const ngn =
|
||||
FiatCurrency(symbol: "NGN", countryCode: "nga", fullName: "Nigerian Naira", emoji: "🇳🇬");
|
||||
static const nok =
|
||||
FiatCurrency(symbol: "NOK", countryCode: "nor", fullName: "Norwegian Krone", emoji: "🇳🇴");
|
||||
static const nzd = FiatCurrency(
|
||||
symbol: "NZD",
|
||||
countryCode: "nzl",
|
||||
fullName: "New Zealand Dollar",
|
||||
emoji: "🇳🇿",
|
||||
);
|
||||
static const php =
|
||||
FiatCurrency(symbol: "PHP", countryCode: "phl", fullName: "Philippine Peso", emoji: "🇵🇭");
|
||||
static const pkr =
|
||||
FiatCurrency(symbol: "PKR", countryCode: "pak", fullName: "Pakistani Rupee", emoji: "🇵🇰");
|
||||
static const pln = FiatCurrency(
|
||||
symbol: "PLN",
|
||||
countryCode: "pol",
|
||||
fullName: "Poland Zloty złoty",
|
||||
emoji: "🇵🇱",
|
||||
);
|
||||
static const ron =
|
||||
FiatCurrency(symbol: "RON", countryCode: "rou", fullName: "Romanian Leu", emoji: "🇷🇴");
|
||||
static const rub =
|
||||
FiatCurrency(symbol: "RUB", countryCode: "rus", fullName: "Russian Ruble", emoji: "🇷🇺");
|
||||
static const sar =
|
||||
FiatCurrency(symbol: "SAR", countryCode: "sau", fullName: "Saudi Riyal", emoji: "🇸🇦");
|
||||
static const sek =
|
||||
FiatCurrency(symbol: "SEK", countryCode: "swe", fullName: "Swedish Krona", emoji: "🇸🇪");
|
||||
static const sgd =
|
||||
FiatCurrency(symbol: "SGD", countryCode: "sgp", fullName: "Singapore Dollar", emoji: "🇸🇬");
|
||||
static const thb = FiatCurrency(
|
||||
symbol: "THB",
|
||||
countryCode: "tha",
|
||||
fullName: "Thai Baht",
|
||||
emoji: "🇹🇭",
|
||||
);
|
||||
static const twd =
|
||||
FiatCurrency(symbol: "TWD", countryCode: "twn", fullName: "New Thaiwan Dollar", emoji: "🇹🇼");
|
||||
static const uah =
|
||||
FiatCurrency(symbol: "UAH", countryCode: "ukr", fullName: "Ukrainian Hryvnia", emoji: "🇺🇦");
|
||||
static const usd = FiatCurrency(
|
||||
symbol: "USD",
|
||||
countryCode: "usa",
|
||||
fullName: "United States Dollar",
|
||||
emoji: "🇺🇸",
|
||||
);
|
||||
static const vef = FiatCurrency(
|
||||
symbol: "VEF",
|
||||
countryCode: "ven",
|
||||
fullName: "Venezuelan Bolivar Bolívar",
|
||||
emoji: "🇻🇪",
|
||||
);
|
||||
static const vnd = FiatCurrency(
|
||||
symbol: "VND",
|
||||
countryCode: "vnm",
|
||||
fullName: "Vietnamese Dong đồng",
|
||||
emoji: "🇻🇳",
|
||||
);
|
||||
static const zar = FiatCurrency(
|
||||
symbol: "ZAR",
|
||||
countryCode: "saf",
|
||||
fullName: "South African Rand",
|
||||
emoji: "🇿🇦",
|
||||
);
|
||||
static const tur =
|
||||
FiatCurrency(symbol: "TRY", countryCode: "tur", fullName: "Turkish Lira", emoji: "🇹🇷");
|
||||
static const kes =
|
||||
FiatCurrency(symbol: "KES", countryCode: "ken", fullName: "Kenyan Shillings", emoji: "🇰🇪");
|
||||
|
||||
static final _all = {
|
||||
FiatCurrency.amd.raw: FiatCurrency.amd,
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import 'package:cake_wallet/generated/locales.dart';
|
||||
import 'package:devicelocale/devicelocale.dart';
|
||||
import "package:flutter/widgets.dart";
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
// Flutter's Material delegates match on the language subtag, so "pt_BR" must be split into two
|
||||
Locale localeFromLanguageCode(String code) {
|
||||
final parts = code.split("_");
|
||||
|
||||
return parts.length == 2 ? Locale(parts[0], parts[1]) : Locale(code);
|
||||
}
|
||||
|
||||
class LanguageService {
|
||||
static const Map<String, String> supportedLocales = {
|
||||
'en': 'English',
|
||||
@@ -85,12 +93,16 @@ class LanguageService {
|
||||
|
||||
static Future<String> localeDetection() async {
|
||||
try {
|
||||
var locale = await Devicelocale.currentLocale ?? '';
|
||||
locale = Intl.shortLocale(locale);
|
||||
|
||||
final locale = Intl.canonicalizedLocale(await Devicelocale.currentLocale ?? "");
|
||||
if (list.keys.contains(locale)) {
|
||||
return locale;
|
||||
}
|
||||
|
||||
final language = Intl.shortLocale(locale);
|
||||
if (list.keys.contains(language)) {
|
||||
return language;
|
||||
}
|
||||
|
||||
return LanguageService.defaultLocale;
|
||||
} catch (_) {
|
||||
return LanguageService.defaultLocale;
|
||||
|
||||
@@ -22,6 +22,8 @@ class ListItemRegularRow extends ListItem {
|
||||
this.leadingIconSize,
|
||||
this.badgeIconSize,
|
||||
this.iconColor,
|
||||
this.secondaryLabel,
|
||||
this.subtitleColor,
|
||||
});
|
||||
|
||||
final String? subtitle;
|
||||
@@ -31,10 +33,12 @@ class ListItemRegularRow extends ListItem {
|
||||
final String? badgeIconPath;
|
||||
final String? copyableText;
|
||||
final VoidCallback? onTap;
|
||||
final String? secondaryLabel;
|
||||
final bool showArrow;
|
||||
final Widget? bottomWidget;
|
||||
final Widget? trailingWidget;
|
||||
final bool truncateTrailingText;
|
||||
final Color? subtitleColor;
|
||||
final Color? foregroundColor;
|
||||
final double? trailingIconSize;
|
||||
final Widget? leadingIconErrorWidget;
|
||||
|
||||
+3
-1
@@ -65,6 +65,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_daemon/flutter_daemon.dart';
|
||||
import "package:flutter_localizations/flutter_localizations.dart";
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:quick_actions/quick_actions.dart';
|
||||
@@ -329,6 +330,7 @@ Future<void> initialSetup({
|
||||
required int initialMigrationVersion,
|
||||
}) async {
|
||||
LanguageService.loadLocaleList();
|
||||
await GlobalMaterialLocalizations.delegate.load(const Locale("en"));
|
||||
await defaultSettingsMigration(
|
||||
secureStorage: secureStorage,
|
||||
version: initialMigrationVersion,
|
||||
@@ -426,7 +428,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
|
||||
themeMode: themeMode,
|
||||
localizationsDelegates: localizationDelegates,
|
||||
supportedLocales: S.delegate.supportedLocales,
|
||||
locale: Locale(appStore.settingsStore.languageCode),
|
||||
locale: localeFromLanguageCode(appStore.settingsStore.languageCode),
|
||||
onGenerateRoute: (settings) => Router.createRoute(settings),
|
||||
initialRoute: initialRoute,
|
||||
scrollBehavior: AppScrollBehavior(),
|
||||
|
||||
@@ -18,6 +18,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../view_model/dashboard/dashboard_view_model.dart';
|
||||
|
||||
class NewDashboard extends StatefulWidget {
|
||||
@@ -54,7 +55,11 @@ class _NewDashboardState extends State<NewDashboard> {
|
||||
});
|
||||
});
|
||||
|
||||
Future.delayed(Duration(milliseconds: 300)).then((_) => _showChangelog(context));
|
||||
Future.delayed(Duration(milliseconds: 300)).then((_) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_showChangelog(context);
|
||||
});
|
||||
});
|
||||
_showVulnerableSeedsPopup(context);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:ui';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/card_customizer.dart';
|
||||
import 'package:cake_wallet/new-ui/viewmodels/card_customizer/card_customizer_bloc.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/cards/balance_card.dart';
|
||||
@@ -24,7 +23,6 @@ import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
|
||||
class AccountCustomizerListItem {
|
||||
@@ -38,13 +36,9 @@ class AccountCustomizerListItem {
|
||||
|
||||
class AccountCustomizer extends StatefulWidget {
|
||||
const AccountCustomizer(
|
||||
{super.key,
|
||||
required this.accountListViewModel,
|
||||
required this.accountEditOrCreateViewModel,
|
||||
required this.dashboardViewModel});
|
||||
{super.key, required this.accountListViewModel, required this.dashboardViewModel});
|
||||
|
||||
final MoneroAccountListViewModel accountListViewModel;
|
||||
final MoneroAccountEditOrCreateViewModel accountEditOrCreateViewModel;
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
|
||||
@override
|
||||
@@ -62,7 +56,7 @@ class _AccountCustomizerState extends State<AccountCustomizer> {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
loadCards();
|
||||
final activeId = monero!.getCurrentAccount(widget.dashboardViewModel.wallet).id;
|
||||
final activeId = widget.accountListViewModel.selected.id;
|
||||
for (int i = 0; i < _items.length - 1; i++) {
|
||||
if (_items[i].accountListItem.id == activeId) {
|
||||
final lastIndex = _items.length - 1;
|
||||
|
||||
@@ -0,0 +1,537 @@
|
||||
import "dart:async";
|
||||
|
||||
import "package:cake_wallet/buy/payment_method.dart";
|
||||
import "package:cake_wallet/buy/sell_buy_states.dart";
|
||||
import "package:cake_wallet/entities/fiat_currency.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/new-ui/pages/buy_sell/buy_sell_provider_page.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_args.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_sheet.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/currency_picker/fiat_currency_picker_sheet.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/floating_amount_input.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/money/currency_symbol_text.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/money/money_text.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/new_primary_button.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/token_chain_display.dart";
|
||||
import "package:cake_wallet/src/widgets/alert_with_one_action.dart";
|
||||
import "package:cake_wallet/src/widgets/cake_image_widget.dart";
|
||||
import "package:cake_wallet/themes/core/theme_extension.dart";
|
||||
import "package:cake_wallet/utils/show_pop_up.dart";
|
||||
import "package:cake_wallet/view_model/buy/buy_sell_view_model.dart";
|
||||
import "package:cw_core/amount/money.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:cw_core/currency.dart";
|
||||
import "package:flutter/cupertino.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_mobx/flutter_mobx.dart";
|
||||
import "package:mobx/mobx.dart";
|
||||
|
||||
class NewBuySellAmountPage extends StatefulWidget {
|
||||
const NewBuySellAmountPage({required this.buySellViewModel, super.key});
|
||||
|
||||
final BuySellViewModel buySellViewModel;
|
||||
|
||||
@override
|
||||
State<NewBuySellAmountPage> createState() => _NewBuySellAmountPageState();
|
||||
}
|
||||
|
||||
class _NewBuySellAmountPageState extends State<NewBuySellAmountPage> {
|
||||
bool _customAmountMode = false;
|
||||
bool _isLoadingPaymentMethods = false;
|
||||
final customInputController = TextEditingController();
|
||||
final customInputFocusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
customInputController.dispose();
|
||||
customInputFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// this is a hack for the "up to" display to show the actual upper limit.
|
||||
// limits are loaded alongside quotes, and quotes for some providers only load properly with an amount and payment method selected.
|
||||
// it'll be refactored soon anyway, i guess.
|
||||
widget.buySellViewModel.changeFiatAmount(amount: widget.buySellViewModel.defaultAmounts[3]);
|
||||
widget.buySellViewModel.calculateBestRate();
|
||||
when(
|
||||
(_) => widget.buySellViewModel.paymentMethodState is PaymentMethodLoaded,
|
||||
() => widget.buySellViewModel.selectedPaymentMethod = widget.buySellViewModel.paymentMethods
|
||||
.firstWhere((item) => item.paymentMethodType == PaymentType.all),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(18)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).colorScheme.surface,
|
||||
Theme.of(context).colorScheme.surfaceDim,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
Observer(
|
||||
builder: (_) => ModalTopBar(
|
||||
title: _pageTitle,
|
||||
bottomText: !_customAmountMode || widget.buySellViewModel.maxFiatAmount == null
|
||||
? null
|
||||
: "${S.of(context).up_to} ~${widget.buySellViewModel.maxFiatAmount} ${widget.buySellViewModel.fiatCurrency.title}",
|
||||
leadingIcon: const Icon(Icons.close),
|
||||
leadingSemanticLabel: S.of(context).close,
|
||||
onLeadingPressed: Navigator.of(context, rootNavigator: true).pop,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Observer(
|
||||
builder: (_) => AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: _customAmountMode
|
||||
? BuySellCustomAmountInput(
|
||||
fiatCurrency: widget.buySellViewModel.fiatCurrency,
|
||||
cryptoCurrency: widget.buySellViewModel.cryptoCurrency,
|
||||
cryptoAmount: widget.buySellViewModel.cryptoAmount,
|
||||
isLoading: _isLoadingPaymentMethods,
|
||||
hasCurrencySelector: widget.buySellViewModel.hasMultipleCurrencies,
|
||||
onCurrencySelectorPressed: () => selectCryptoCurrency(context),
|
||||
controller: customInputController,
|
||||
focusNode: customInputFocusNode,
|
||||
onContinuePressed: () {
|
||||
navigateToProviders(context);
|
||||
},
|
||||
onChanged: (amount) {
|
||||
if (widget.buySellViewModel.mode == BuySellPageMode.sell) {
|
||||
widget.buySellViewModel.setCryptoAmountFromFiat(amount);
|
||||
} else {
|
||||
widget.buySellViewModel.changeFiatAmount(amount: amount);
|
||||
}
|
||||
},
|
||||
)
|
||||
: BuySellDefaultAmountSelector(
|
||||
key: const ValueKey(0),
|
||||
defaultAmounts: widget.buySellViewModel.defaultAmounts,
|
||||
fiatCurrency: widget.buySellViewModel.fiatCurrency,
|
||||
currentAmount: widget.buySellViewModel.fiatAmount,
|
||||
hasCurrencySelector: widget.buySellViewModel.hasMultipleCurrencies,
|
||||
onCurrencySelectorPressed: () => selectCryptoCurrency(context),
|
||||
onFiatSelectorPressed: () => selectFiatCurrency(context),
|
||||
cryptoCurrency: widget.buySellViewModel.cryptoCurrency,
|
||||
isLoading: _isLoadingPaymentMethods,
|
||||
mode: widget.buySellViewModel.mode,
|
||||
onSelected: (amount) async {
|
||||
if (amount == null) {
|
||||
// this resets the rate and prevents showing 0 usd = 0.something btc
|
||||
await widget.buySellViewModel.changeFiatAmount(amount: "");
|
||||
setState(() {
|
||||
_customAmountMode = true;
|
||||
});
|
||||
customInputFocusNode.requestFocus();
|
||||
} else {
|
||||
if (widget.buySellViewModel.mode == BuySellPageMode.sell) {
|
||||
await widget.buySellViewModel.setCryptoAmountFromFiat(amount);
|
||||
} else {
|
||||
await widget.buySellViewModel.changeFiatAmount(amount: amount);
|
||||
}
|
||||
if (context.mounted) {
|
||||
await navigateToProviders(context);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
void selectCryptoCurrency(BuildContext context) => CurrencyPickerSheet.show(
|
||||
context: context,
|
||||
args: CurrencyPickerArgs(
|
||||
items: widget.buySellViewModel.activeWalletCurrencies.toList(),
|
||||
onSelected: (item) => widget.buySellViewModel.changeCryptoCurrency(currency: item),
|
||||
symbolResolver: widget.buySellViewModel.amountParsingProxy.getCryptoSymbol,
|
||||
),
|
||||
);
|
||||
|
||||
void selectFiatCurrency(BuildContext context) => FiatCurrencyPickerSheet.show(
|
||||
context: context,
|
||||
selected: widget.buySellViewModel.fiatCurrency,
|
||||
onSelected: (item) => widget.buySellViewModel.changeFiatCurrency(currency: item),
|
||||
);
|
||||
|
||||
String get _pageTitle => widget.buySellViewModel.mode == BuySellPageMode.buy
|
||||
? S.current.buy
|
||||
: S.current.sell +
|
||||
((widget.buySellViewModel.cryptoCurrencies.length == 1)
|
||||
? " ${widget.buySellViewModel.cryptoCurrencies.first.fullName}"
|
||||
: "");
|
||||
|
||||
Future<void> navigateToProviders(BuildContext context) async {
|
||||
if (_isLoadingPaymentMethods) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setState(() {
|
||||
_isLoadingPaymentMethods = true;
|
||||
});
|
||||
|
||||
await asyncWhen(
|
||||
(_) => [PaymentMethodLoaded, PaymentMethodFailed]
|
||||
.contains(widget.buySellViewModel.paymentMethodState.runtimeType),
|
||||
);
|
||||
|
||||
if (widget.buySellViewModel.paymentMethodState is PaymentMethodFailed) {
|
||||
if (context.mounted) {
|
||||
await showPopUp(
|
||||
context: context,
|
||||
builder: (context) => AlertWithOneAction(
|
||||
alertTitle: S.of(context).failed_to_load_payment_methods,
|
||||
alertContent: S.of(context).please_try_again_later,
|
||||
buttonText: "OK",
|
||||
buttonAction: Navigator.of(context).pop,
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
widget.buySellViewModel.selectedPaymentMethod = widget.buySellViewModel.paymentMethods
|
||||
.firstWhere((item) => item.paymentMethodType == PaymentType.all);
|
||||
|
||||
// unawaited because BuySellProviderPage has a nice little "loading rates..." ui thingie
|
||||
unawaited(widget.buySellViewModel.calculateBestRate());
|
||||
|
||||
final page = BuySellProviderPage(buySellViewModel: widget.buySellViewModel);
|
||||
if (context.mounted) {
|
||||
unawaited(
|
||||
Navigator.of(context).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => Material(color: Colors.transparent, child: page),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoadingPaymentMethods = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BuySellCustomAmountInput extends StatelessWidget {
|
||||
const BuySellCustomAmountInput({
|
||||
required this.fiatCurrency,
|
||||
required this.cryptoCurrency,
|
||||
required this.cryptoAmount,
|
||||
required this.controller,
|
||||
required this.onContinuePressed,
|
||||
required this.isLoading,
|
||||
required this.onChanged,
|
||||
required this.focusNode,
|
||||
required this.hasCurrencySelector,
|
||||
required this.onCurrencySelectorPressed,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final FiatCurrency fiatCurrency;
|
||||
final CryptoCurrency cryptoCurrency;
|
||||
final String cryptoAmount;
|
||||
final TextEditingController controller;
|
||||
final FocusNode focusNode;
|
||||
final bool isLoading;
|
||||
final bool hasCurrencySelector;
|
||||
final VoidCallback onCurrencySelectorPressed;
|
||||
final VoidCallback onContinuePressed;
|
||||
final Function(String) onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Padding(
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const SizedBox.shrink(),
|
||||
Column(
|
||||
spacing: 8,
|
||||
children: [
|
||||
FloatingAmountInput(
|
||||
currency: fiatCurrency,
|
||||
focusNode: focusNode,
|
||||
controller: controller,
|
||||
onChanged: onChanged,
|
||||
),
|
||||
Opacity(
|
||||
opacity: cryptoAmount.isEmpty ? 0 : 1,
|
||||
child: Text(
|
||||
"≈ ${cryptoAmount} ${cryptoCurrency.symbol}",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(18),
|
||||
child: NewPrimaryButton(
|
||||
disabled: controller.text.isEmpty,
|
||||
onPressed: onContinuePressed,
|
||||
isLoading: isLoading,
|
||||
text: S.of(context).continue_text,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class BuySellDefaultAmountSelector extends StatelessWidget {
|
||||
const BuySellDefaultAmountSelector({
|
||||
required this.onFiatSelectorPressed,
|
||||
required this.defaultAmounts,
|
||||
required this.fiatCurrency,
|
||||
required this.mode,
|
||||
required this.onSelected,
|
||||
required this.isLoading,
|
||||
required this.hasCurrencySelector,
|
||||
required this.onCurrencySelectorPressed,
|
||||
required this.cryptoCurrency,
|
||||
super.key,
|
||||
this.currentAmount,
|
||||
});
|
||||
|
||||
final List<String> defaultAmounts;
|
||||
final String? currentAmount;
|
||||
final bool isLoading;
|
||||
final bool hasCurrencySelector;
|
||||
final VoidCallback onCurrencySelectorPressed;
|
||||
final VoidCallback onFiatSelectorPressed;
|
||||
final CryptoCurrency cryptoCurrency;
|
||||
final FiatCurrency fiatCurrency;
|
||||
final BuySellPageMode mode;
|
||||
final Function(String?) onSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18),
|
||||
child: Column(
|
||||
spacing: 24,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (hasCurrencySelector)
|
||||
BuySellCurrencyPickerPill(
|
||||
title: mode == BuySellPageMode.sell ? S.of(context).sell : S.of(context).buy,
|
||||
curr: cryptoCurrency,
|
||||
onTap: onCurrencySelectorPressed,
|
||||
),
|
||||
BuySellCurrencyPickerPill(
|
||||
title: mode == BuySellPageMode.sell
|
||||
? S.of(context).receive
|
||||
: S.of(context).purchase_with,
|
||||
curr: fiatCurrency,
|
||||
onTap: onFiatSelectorPressed,
|
||||
),
|
||||
Text(
|
||||
mode == BuySellPageMode.sell
|
||||
? S.of(context).choose_amount_to_sell
|
||||
: S.of(context).choose_amount_to_buy,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
GridView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
// +1 for "custom" option
|
||||
itemCount: defaultAmounts.length + 1,
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 16,
|
||||
mainAxisExtent: 105,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final String? item = index == defaultAmounts.length ? null : defaultAmounts[index];
|
||||
|
||||
return BuySellAmountPill(
|
||||
isLoading: isLoading && item == currentAmount,
|
||||
amount: item == null ? null : Money.parse(item, fiatCurrency),
|
||||
onTap: () => onSelected(item),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class BuySellAmountPill extends StatelessWidget {
|
||||
const BuySellAmountPill({required this.onTap, required this.isLoading, super.key, this.amount});
|
||||
|
||||
final Money? amount;
|
||||
final VoidCallback onTap;
|
||||
final bool isLoading;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(9999999999),
|
||||
border: Border.all(
|
||||
width: 1,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
context.customColors.cardGradientColorPrimary,
|
||||
context.customColors.cardGradientColorSecondary,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(9999999999),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(9999999999),
|
||||
onTap: onTap,
|
||||
child: isLoading
|
||||
? const CupertinoActivityIndicator()
|
||||
: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 4,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (amount != null)
|
||||
MoneyText(
|
||||
amount!,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
|
||||
showSymbol: false,
|
||||
),
|
||||
Text(
|
||||
amount?.currency.symbol ?? S.of(context).custom,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: amount == null
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (amount == null)
|
||||
Text(
|
||||
S.of(context).enter_amount,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class BuySellCurrencyPickerPill extends StatelessWidget {
|
||||
const BuySellCurrencyPickerPill(
|
||||
{required this.title, required this.curr, required this.onTap, super.key});
|
||||
|
||||
final String title;
|
||||
final Currency curr;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 12,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
width: MediaQuery.sizeOf(context).width,
|
||||
height: 62,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
if (curr is CryptoCurrency)
|
||||
TokenChainDisplay(size: 30, asset: curr as CryptoCurrency)
|
||||
else if (curr is FiatCurrency)
|
||||
SizedBox(
|
||||
width: 30,
|
||||
child: Text(
|
||||
(curr as FiatCurrency).emoji,
|
||||
style: const TextStyle(fontSize: 24),
|
||||
)),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 2,
|
||||
children: [
|
||||
Text(
|
||||
curr.fullName ?? curr.symbol,
|
||||
),
|
||||
CurrencySymbolText(
|
||||
curr,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
RotatedBox(
|
||||
quarterTurns: 2,
|
||||
child: CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/dropdown_arrow.svg",
|
||||
width: 8,
|
||||
height: 8,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.primary, BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import "package:cake_wallet/buy/payment_method.dart";
|
||||
import "package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/new-ui/pages/buy_sell/buy_sell_redirecting_page.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/new_primary_button.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
|
||||
import "package:cake_wallet/src/widgets/cake_image_widget.dart";
|
||||
import "package:cake_wallet/src/widgets/new_list_row/new_list_section.dart";
|
||||
import "package:cake_wallet/view_model/buy/buy_sell_view_model.dart";
|
||||
import "package:flutter/cupertino.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_mobx/flutter_mobx.dart";
|
||||
|
||||
class BuySellConfirmationPage extends StatelessWidget {
|
||||
const BuySellConfirmationPage({required this.buySellViewModel, super.key});
|
||||
|
||||
final BuySellViewModel buySellViewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(18)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).colorScheme.surface,
|
||||
Theme.of(context).colorScheme.surfaceDim,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: _pageTitle,
|
||||
leadingIcon: const Icon(Icons.arrow_back_ios_new),
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
leadingSemanticLabel: S.of(context).seed_alert_back,
|
||||
),
|
||||
Expanded(
|
||||
child: Observer(
|
||||
builder: (_) => Column(
|
||||
spacing: 24,
|
||||
children: [
|
||||
Column(
|
||||
spacing: 4,
|
||||
children: [
|
||||
Text(
|
||||
"${buySellViewModel.fiatAmount} ${buySellViewModel.fiatCurrency}",
|
||||
style: const TextStyle(fontSize: 32),
|
||||
),
|
||||
if (buySellViewModel.amountForQuote(buySellViewModel.selectedQuote!) !=
|
||||
null)
|
||||
Text(
|
||||
"≈ ${buySellViewModel.amountForQuote(buySellViewModel.selectedQuote!)?.toStringWithSymbol(fractionalDigits: 8)}",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18),
|
||||
child: NewListSections(
|
||||
sections: {
|
||||
"": [
|
||||
ListItemRegularRow(
|
||||
keyValue: "provider",
|
||||
label: S.of(context).provider,
|
||||
trailingWidget: Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: Theme.of(context).brightness == Brightness.light
|
||||
? buySellViewModel.selectedQuote!.lightIconPath
|
||||
: buySellViewModel.selectedQuote!.darkIconPath,
|
||||
width: 24,
|
||||
height: 24,
|
||||
),
|
||||
Text(
|
||||
buySellViewModel.selectedQuote!.rampName ??
|
||||
buySellViewModel.selectedQuote!.provider.title,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (buySellViewModel.selectedQuote!.paymentType.title?.isNotEmpty ??
|
||||
false)
|
||||
ListItemRegularRow(
|
||||
showArrow: false,
|
||||
keyValue: "payment method",
|
||||
label: S.of(context).payment_method,
|
||||
trailingText: buySellViewModel.selectedQuote!.paymentType.title,
|
||||
),
|
||||
ListItemRegularRow(
|
||||
showArrow: false,
|
||||
keyValue: "rate",
|
||||
label: S.of(context).rate,
|
||||
trailingText: buySellViewModel.selectedQuote!.topLeftSubTitle,
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(18),
|
||||
child: NewPrimaryButton(
|
||||
onPressed: () => confirm(context),
|
||||
text: S.of(context).proceed,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
String get _pageTitle =>
|
||||
"${buySellViewModel.mode == BuySellPageMode.buy ? S.current.buy : S.current.sell} ${buySellViewModel.cryptoCurrency.fullName ?? ""}";
|
||||
|
||||
void confirm(BuildContext context) {
|
||||
final page = BuySellRedirectingPage(buySellViewModel: buySellViewModel);
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
Navigator.of(context, rootNavigator: true).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => Material(
|
||||
child: page,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import "dart:async";
|
||||
|
||||
import "package:cake_wallet/buy/payment_method.dart";
|
||||
import "package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
|
||||
import "package:cake_wallet/src/widgets/new_list_row/new_list_section.dart";
|
||||
import "package:cake_wallet/view_model/buy/buy_sell_view_model.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_mobx/flutter_mobx.dart";
|
||||
|
||||
class BuySellPaymentMethodPage extends StatelessWidget {
|
||||
const BuySellPaymentMethodPage({required this.buySellViewModel, super.key});
|
||||
|
||||
final BuySellViewModel buySellViewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(18)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).colorScheme.surface,
|
||||
Theme.of(context).colorScheme.surfaceDim,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: S.of(context).payment_method,
|
||||
leadingIcon: const Icon(Icons.arrow_back_ios_new),
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
leadingSemanticLabel: S.of(context).seed_alert_back,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18),
|
||||
child: Observer(
|
||||
builder: (_) => NewListSections(
|
||||
sections: {
|
||||
"": buySellViewModel.paymentMethods
|
||||
.map(
|
||||
(item) => ListItemRegularRow(
|
||||
keyValue: item.title,
|
||||
label: item.title,
|
||||
showArrow: false,
|
||||
iconPath: Theme.of(context).brightness == Brightness.light
|
||||
? item.lightIconPath
|
||||
: item.darkIconPath,
|
||||
iconColor: item.paymentMethodType.isMonochromeIcon
|
||||
? Theme.of(context).colorScheme.onSurfaceVariant
|
||||
: null,
|
||||
trailingWidget: buySellViewModel.selectedPaymentMethod == item
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
size: 16,
|
||||
)
|
||||
: null,
|
||||
onTap: () {
|
||||
buySellViewModel.changeOption(item);
|
||||
unawaited(buySellViewModel.calculateBestRate());
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
import "package:cake_wallet/buy/buy_quote.dart";
|
||||
import "package:cake_wallet/buy/sell_buy_states.dart";
|
||||
import "package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart";
|
||||
import "package:cake_wallet/entities/new_ui_entities/list_item/list_item_dropdown.dart";
|
||||
import "package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/new-ui/pages/buy_sell/buy_sell_confirmation_page.dart";
|
||||
import "package:cake_wallet/new-ui/pages/buy_sell/buy_sell_payment_method_page.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/money/money_text.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
|
||||
import "package:cake_wallet/src/widgets/new_list_row/new_list_section.dart";
|
||||
import "package:cake_wallet/view_model/buy/buy_sell_view_model.dart";
|
||||
import "package:flutter/cupertino.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_mobx/flutter_mobx.dart";
|
||||
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
|
||||
|
||||
class BuySellProviderPage extends StatefulWidget {
|
||||
const BuySellProviderPage({required this.buySellViewModel, super.key});
|
||||
|
||||
final BuySellViewModel buySellViewModel;
|
||||
|
||||
@override
|
||||
State<BuySellProviderPage> createState() => _BuySellProviderPageState();
|
||||
}
|
||||
|
||||
class _BuySellProviderPageState extends State<BuySellProviderPage> {
|
||||
bool _allProvidersExpanded = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(18)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).colorScheme.surface,
|
||||
Theme.of(context).colorScheme.surfaceDim,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: _pageTitle,
|
||||
leadingIcon: const Icon(Icons.arrow_back_ios_new),
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
leadingSemanticLabel: S.of(context).seed_alert_back,
|
||||
),
|
||||
Expanded(
|
||||
child: Observer(
|
||||
builder: (_) {
|
||||
if (widget.buySellViewModel.buySellQuotState is BuySellQuotFailed) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 24,
|
||||
children: [
|
||||
const Icon(Icons.warning_amber_outlined, size: 48),
|
||||
Column(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).could_not_load_quotes,
|
||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
|
||||
),
|
||||
Text(
|
||||
(widget.buySellViewModel.buySellQuotState as BuySellQuotFailed)
|
||||
.errorMessage ??
|
||||
S.of(context).please_try_again_later,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.buySellViewModel.buySellQuotState is BuySellQuotLoading) {
|
||||
return Center(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 8,
|
||||
children: [
|
||||
const CupertinoActivityIndicator(),
|
||||
Text(
|
||||
S.of(context).loading_rates,
|
||||
style:
|
||||
TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
controller: ModalScrollController.of(context),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18),
|
||||
child: NewListSections(
|
||||
showHeader: true,
|
||||
sections: {
|
||||
"": [
|
||||
ListItemRegularRow(
|
||||
keyValue: "payment method",
|
||||
label: S.of(context).payment_method,
|
||||
showArrow: true,
|
||||
onTap: () {
|
||||
final page = BuySellPaymentMethodPage(
|
||||
buySellViewModel: widget.buySellViewModel,
|
||||
);
|
||||
Navigator.of(context).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => Material(
|
||||
color: Colors.transparent,
|
||||
child: page,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
trailingText: widget.buySellViewModel.selectedPaymentMethod?.title,
|
||||
),
|
||||
],
|
||||
S.of(context).available_providers: [
|
||||
...widget.buySellViewModel.sortedRecommendedQuotes.map(quoteListItem),
|
||||
if (widget.buySellViewModel.sortedQuotes.isNotEmpty)
|
||||
ListItemDropdown(
|
||||
keyValue: "more options",
|
||||
label: S.of(context).more_options,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_allProvidersExpanded = !_allProvidersExpanded;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_allProvidersExpanded)
|
||||
...widget.buySellViewModel.sortedQuotes.map(quoteListItem),
|
||||
],
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
String get _pageTitle =>
|
||||
"${widget.buySellViewModel.mode == BuySellPageMode.buy ? S.current.buy : S.current.sell} ${widget.buySellViewModel.cryptoCurrency.fullName ?? ""}";
|
||||
|
||||
ListItem quoteListItem(Quote quote) {
|
||||
final fiatAmount = widget.buySellViewModel.fiatAmountForQuote(quote);
|
||||
return ListItemRegularRow(
|
||||
keyValue: quote.provider.title,
|
||||
label: quote.rampName ?? quote.provider.title,
|
||||
secondaryLabel: quote.rampName != null ? quote.provider.title : null,
|
||||
subtitle: quote.badges.isEmpty ? null : quote.badges.join(" - "),
|
||||
subtitleColor: Theme.of(context).colorScheme.primary,
|
||||
iconPath: Theme.of(context).brightness == Brightness.light
|
||||
? quote.lightIconPath
|
||||
: quote.darkIconPath,
|
||||
onTap: () {
|
||||
widget.buySellViewModel.changeOption(quote);
|
||||
navigateToConfirmation(context);
|
||||
},
|
||||
leadingIconSize: 32,
|
||||
trailingWidget: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 4,
|
||||
children: [
|
||||
MoneyText.optional(widget.buySellViewModel.amountForQuote(quote)),
|
||||
if (fiatAmount != null)
|
||||
Text(
|
||||
"= ${fiatAmount.toStringWithSymbol(fractionalDigits: 2, trimZeros: false)}",
|
||||
style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void navigateToConfirmation(BuildContext context) {
|
||||
final page = BuySellConfirmationPage(buySellViewModel: widget.buySellViewModel);
|
||||
Navigator.of(context).push(
|
||||
CupertinoPageRoute(
|
||||
builder: (context) => Material(
|
||||
color: Colors.transparent,
|
||||
child: page,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
import "package:cake_wallet/buy/sell_buy_states.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/new_primary_button.dart";
|
||||
import "package:cake_wallet/src/widgets/cake_image_widget.dart";
|
||||
import "package:cake_wallet/view_model/buy/buy_sell_view_model.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_mobx/flutter_mobx.dart";
|
||||
|
||||
class BuySellRedirectingPage extends StatefulWidget {
|
||||
const BuySellRedirectingPage({required this.buySellViewModel, super.key});
|
||||
|
||||
final BuySellViewModel buySellViewModel;
|
||||
|
||||
@override
|
||||
State<BuySellRedirectingPage> createState() => _BuySellRedirectingPageState();
|
||||
}
|
||||
|
||||
class _BuySellRedirectingPageState extends State<BuySellRedirectingPage> {
|
||||
bool _hasRedirected = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Future.delayed(const Duration(seconds: 2)).then((_) {
|
||||
WidgetsBinding.instance
|
||||
.addPostFrameCallback((_) => widget.buySellViewModel.launchTrade(context));
|
||||
setState(() {
|
||||
_hasRedirected = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => PopScope(
|
||||
canPop: false,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(18)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).colorScheme.surface,
|
||||
Theme.of(context).colorScheme.surfaceDim,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: Observer(
|
||||
builder: (_) {
|
||||
final showExitButton =
|
||||
_hasRedirected || widget.buySellViewModel.buySellQuotState is BuySellQuotFailed;
|
||||
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const SizedBox.shrink(),
|
||||
Observer(
|
||||
builder: (_) {
|
||||
if (widget.buySellViewModel.buySellQuotState is BuySellQuotFailed) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 24,
|
||||
children: [
|
||||
const Icon(Icons.warning_amber_outlined, size: 48),
|
||||
Column(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).could_not_proceed_with_trade,
|
||||
style:
|
||||
const TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
|
||||
),
|
||||
Text(
|
||||
(widget.buySellViewModel.buySellQuotState as BuySellQuotFailed)
|
||||
.errorMessage ??
|
||||
S.of(context).please_try_again_later,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 24,
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: Theme.of(context).brightness == Brightness.light
|
||||
? widget.buySellViewModel.selectedQuote!.lightIconPath
|
||||
: widget.buySellViewModel.selectedQuote!.darkIconPath,
|
||||
width: 64,
|
||||
height: 64,
|
||||
),
|
||||
Column(
|
||||
spacing: 10,
|
||||
children: [
|
||||
Text(
|
||||
"${S.of(context).connecting_you_to} ${widget.buySellViewModel.selectedQuote!.provider.title}...",
|
||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
|
||||
),
|
||||
Text(
|
||||
"${widget.buySellViewModel.fiatAmount} ${widget.buySellViewModel.fiatCurrency} → ${widget.buySellViewModel.amountForQuote(widget.buySellViewModel.selectedQuote!)?.toStringWithSymbol(fractionalDigits: 8)}",
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
showExitButton
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(18),
|
||||
child: NewPrimaryButton(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
text: S.of(context).close,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -17,11 +17,9 @@ import 'package:cake_wallet/new-ui/widgets/coins_page/mweb_ad.dart';
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/seed_backup_reminder_card.dart";
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/top_bar.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/unconfirmed_balance_widget.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/wallet_info.dart';
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/zcash_migration_modal.dart";
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -137,6 +135,7 @@ class _NewHomePageState extends State<NewHomePage> with RouteAware {
|
||||
spacing: 24.0,
|
||||
children: [
|
||||
TopBar(
|
||||
key: ValueKey(widget.dashboardViewModel.wallet.id),
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
lightningMode: _lightningMode,
|
||||
onLightningSwitchPress: () {
|
||||
@@ -144,8 +143,8 @@ class _NewHomePageState extends State<NewHomePage> with RouteAware {
|
||||
_lightningMode = !_lightningMode;
|
||||
});
|
||||
},
|
||||
onSettingsButtonPress: () {
|
||||
CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
onSettingsButtonPress: () async {
|
||||
await CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withAlpha(85),
|
||||
builder: (context) => FractionallySizedBox(
|
||||
@@ -155,74 +154,81 @@ class _NewHomePageState extends State<NewHomePage> with RouteAware {
|
||||
authService: getIt.get<AuthService>(),
|
||||
))),
|
||||
);
|
||||
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
_setAccountViewModel();
|
||||
await widget.dashboardViewModel.loadCardDesigns();
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => WalletInfoBar(
|
||||
lightningMode: _lightningMode,
|
||||
hardwareWalletType: widget.dashboardViewModel.wallet.hardwareWalletType,
|
||||
name: widget.dashboardViewModel.wallet.name,
|
||||
hasCustomize: accountListViewModel != null,
|
||||
onCustomizeButtonTap: openAccountCustomizer),
|
||||
),
|
||||
Column(
|
||||
spacing: 20,
|
||||
children: [
|
||||
Observer(
|
||||
builder: (_) => CardsView(
|
||||
key: ValueKey(widget.dashboardViewModel.wallet.name),
|
||||
onCustomizeTapped: openCardCustomizer,
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
accountListViewModel: accountListViewModel,
|
||||
onCompactModeBackgroundCardsTapped: openAccountCustomizer,
|
||||
lightningMode: _lightningMode,
|
||||
),
|
||||
),
|
||||
Observer(builder: (_) {
|
||||
return AnimatedSize(
|
||||
duration: Duration(milliseconds: 150),
|
||||
curve: Curves.easeInOutCubic,
|
||||
child: (widget.dashboardViewModel.shouldShowBalanceHiddenMessage)
|
||||
? Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 12,
|
||||
width: double.infinity,
|
||||
),
|
||||
Text(
|
||||
S.of(context).long_press_show_balance,
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
)
|
||||
],
|
||||
)
|
||||
: SizedBox(width: double.infinity),
|
||||
);
|
||||
}),
|
||||
UnconfirmedBalanceWidget(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
),
|
||||
],
|
||||
),
|
||||
Observer(
|
||||
builder: (_) {
|
||||
return Column(
|
||||
Column(
|
||||
children: [
|
||||
CoinActionRow(
|
||||
lightningMode: _lightningMode,
|
||||
showSwap: widget.dashboardViewModel.isEnabledSwapAction,
|
||||
walletType: widget.dashboardViewModel.wallet.type,
|
||||
Observer(
|
||||
builder: (_) => CardsView(
|
||||
key: ValueKey(widget.dashboardViewModel.wallet.name),
|
||||
onCustomizeTapped: openCardCustomizer,
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
accountListViewModel: accountListViewModel,
|
||||
onCompactModeBackgroundCardsTapped: openAccountCustomizer,
|
||||
lightningMode: _lightningMode,
|
||||
),
|
||||
),
|
||||
MwebAd(
|
||||
Observer(builder: (_) {
|
||||
return AnimatedSize(
|
||||
duration: Duration(milliseconds: 150),
|
||||
curve: Curves.easeInOutCubic,
|
||||
child: (widget.dashboardViewModel.shouldShowBalanceHiddenMessage)
|
||||
? Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 12,
|
||||
width: double.infinity,
|
||||
),
|
||||
Text(
|
||||
S.of(context).long_press_show_balance,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant),
|
||||
)
|
||||
],
|
||||
)
|
||||
: SizedBox(width: double.infinity),
|
||||
);
|
||||
}),
|
||||
UnconfirmedBalanceWidget(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
),
|
||||
SeedBackupReminderCard(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
onTap: openSeedBackupReminder,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
Observer(
|
||||
builder: (_) {
|
||||
return Column(
|
||||
children: [
|
||||
CoinActionRow(
|
||||
lightningMode: _lightningMode,
|
||||
showSwap: widget.dashboardViewModel.isEnabledSwapAction,
|
||||
walletType: widget.dashboardViewModel.wallet.type,
|
||||
),
|
||||
MwebAd(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
),
|
||||
SeedBackupReminderCard(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
onTap: openSeedBackupReminder,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -271,7 +277,12 @@ class _NewHomePageState extends State<NewHomePage> with RouteAware {
|
||||
);
|
||||
}
|
||||
|
||||
void openAccountCustomizer() async {
|
||||
Future<void> openAccountCustomizer() async {
|
||||
final accountList = accountListViewModel;
|
||||
if (accountList == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
await CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
barrierColor: Colors.black.withAlpha(60),
|
||||
context: context,
|
||||
@@ -280,15 +291,15 @@ class _NewHomePageState extends State<NewHomePage> with RouteAware {
|
||||
parentContext: context,
|
||||
heightMode: ModalHeightModes.fullScreen,
|
||||
rootPage: Material(
|
||||
child: AccountCustomizer(
|
||||
accountListViewModel: accountListViewModel!,
|
||||
accountEditOrCreateViewModel: getIt.get<MoneroAccountEditOrCreateViewModel>(),
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
)),
|
||||
child: AccountCustomizer(
|
||||
accountListViewModel: accountList,
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
widget.dashboardViewModel.loadCardDesigns();
|
||||
await widget.dashboardViewModel.loadCardDesigns();
|
||||
}
|
||||
|
||||
void openCardCustomizer() async {
|
||||
|
||||
@@ -25,6 +25,8 @@ bool _hasMweb(DashboardViewModel vm) => vm.hasMweb;
|
||||
|
||||
bool _hasWalletConnect(DashboardViewModel vm) => vm.hasWalletConnect;
|
||||
|
||||
bool _hasAccounts(DashboardViewModel vm) => vm.balanceViewModel.hasAccounts;
|
||||
|
||||
bool _requiresKeyImageSync(DashboardViewModel vm) =>
|
||||
vm.wallet.type == WalletType.monero &&
|
||||
[HardwareWalletType.cupcake, HardwareWalletType.trezor].contains(vm.wallet.hardwareWalletType);
|
||||
@@ -38,12 +40,14 @@ class SettingsListItem {
|
||||
this.use2fa = _falseFunc,
|
||||
this.condition = _trueFunc,
|
||||
this.routeArgs,
|
||||
this.routeArgsBuilder,
|
||||
});
|
||||
|
||||
final String iconPath;
|
||||
final String title;
|
||||
final String route;
|
||||
final Object? routeArgs;
|
||||
final Object? Function(DashboardViewModel)? routeArgsBuilder;
|
||||
final bool requireAuth;
|
||||
final bool Function(DashboardViewModel) use2fa;
|
||||
final bool Function(DashboardViewModel) condition;
|
||||
@@ -58,6 +62,13 @@ class SettingsSectionData {
|
||||
|
||||
static SettingsSectionData walletSettings =
|
||||
SettingsSectionData(S.current.wallet_settings, "assets/new-ui/wallet-setting.svg", [
|
||||
SettingsListItem(
|
||||
"assets/new-ui/settings_row_icons/accounts.svg",
|
||||
S.current.accounts,
|
||||
Routes.accountCustomizer,
|
||||
condition: _hasAccounts,
|
||||
routeArgsBuilder: (vm) => vm,
|
||||
),
|
||||
SettingsListItem(
|
||||
"assets/new-ui/settings_row_icons/nodes.svg", S.current.nodes, Routes.manageNodes),
|
||||
SettingsListItem(
|
||||
@@ -145,7 +156,11 @@ class SettingsMainPage extends StatelessWidget {
|
||||
conditionToDetermineIfToUse2FA: item.use2fa(dashboardViewModel),
|
||||
route: item.route);
|
||||
} else {
|
||||
Navigator.of(context).pushNamed(item.route, arguments: item.routeArgs);
|
||||
Navigator.of(context).pushNamed(
|
||||
item.route,
|
||||
arguments:
|
||||
item.routeArgsBuilder?.call(dashboardViewModel) ?? item.routeArgs,
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
import "package:cake_wallet/di.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/main.dart";
|
||||
import "package:cake_wallet/new-ui/modal_navigator.dart";
|
||||
import "package:cake_wallet/new-ui/pages/buy_sell/buy_sell_amount_page.dart";
|
||||
import "package:cake_wallet/src/widgets/cake_image_widget.dart";
|
||||
import "package:cake_wallet/themes/core/theme_extension.dart";
|
||||
import "package:cake_wallet/view_model/buy/buy_sell_view_model.dart";
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
class BuySellSelectorModal extends StatelessWidget {
|
||||
const BuySellSelectorModal({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(18)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).colorScheme.surface,
|
||||
Theme.of(context).colorScheme.surfaceDim,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: Column(
|
||||
spacing: 24,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox.shrink(),
|
||||
Text(
|
||||
S.of(context).buy_or_sell,
|
||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
|
||||
),
|
||||
Text(
|
||||
S.of(context).buy_or_sell_desc,
|
||||
style:
|
||||
TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18),
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
BuySellSelectorModalButton(
|
||||
title: S.of(context).buy_crypto,
|
||||
description: S.of(context).buy_crypto_desc,
|
||||
iconPath: "assets/new-ui/plus.svg",
|
||||
onTap: () => openBuySellPage(context, BuySellPageMode.buy),
|
||||
),
|
||||
BuySellSelectorModalButton(
|
||||
title: S.of(context).sell_crypto,
|
||||
description: S.of(context).sell_crypto_desc,
|
||||
iconPath: "assets/new-ui/sell.svg",
|
||||
onTap: () => openBuySellPage(context, BuySellPageMode.sell),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
void openBuySellPage(BuildContext context, BuySellPageMode mode) {
|
||||
Navigator.of(context).pop();
|
||||
final page = getIt.get<NewBuySellAmountPage>(param1: mode);
|
||||
showModalBottomSheet(
|
||||
useSafeArea: true,
|
||||
isScrollControlled: true,
|
||||
context: navigatorKey.currentContext!,
|
||||
builder: (modalContext) => ModalNavigator(
|
||||
rootPage: page,
|
||||
parentContext: modalContext,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BuySellSelectorModalButton extends StatelessWidget {
|
||||
const BuySellSelectorModalButton({
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.iconPath,
|
||||
required this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String description;
|
||||
final String iconPath;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
width: 1,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
context.customColors.cardGradientColorPrimary,
|
||||
context.customColors.cardGradientColorSecondary,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Row(
|
||||
spacing: 20,
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: iconPath,
|
||||
width: 55,
|
||||
height: 55,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
Column(
|
||||
spacing: 8,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 16),
|
||||
),
|
||||
Text(
|
||||
description,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -26,7 +26,12 @@ class AssetsTopBar extends StatelessWidget {
|
||||
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 32.0, bottom: 0.0, left: 12.0, right: 18.0),
|
||||
padding: const EdgeInsets.only(
|
||||
top: 20,
|
||||
bottom: 0,
|
||||
left: 12,
|
||||
right: 18,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:cake_wallet/core/csv_export_service.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/history_filters_page.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/assets_history/history_section.dart';
|
||||
@@ -33,7 +34,7 @@ class HistoryModal extends StatelessWidget {
|
||||
),
|
||||
trailingSemanticLabel: S.of(context).export_csv,
|
||||
onTrailingPressed: () =>
|
||||
CsvExportService().exportToCsv(dashboardViewModel.items, context),
|
||||
getIt.get<CsvExportService>().exportToCsv(dashboardViewModel.items, context),
|
||||
),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
|
||||
@@ -20,7 +20,7 @@ class HistoryTopBar extends StatelessWidget {
|
||||
onTap: onTap,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, top: 24),
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, top: 20),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:cake_wallet/entities/bitcoin_amount_display_mode.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/modal_navigator.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/send_page.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/utils/feature_flag.dart';
|
||||
import 'package:cake_wallet/utils/payment_request.dart';
|
||||
@@ -204,7 +205,10 @@ class _CardsViewState extends State<CardsView> {
|
||||
label: S.current.buy,
|
||||
icon: Icons.arrow_forward_ios_rounded,
|
||||
iconSize: 12,
|
||||
onTap: () => Navigator.of(context).pushNamed(Routes.buySellPage),
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
context: context, builder: (context) => BuySellSelectorModal());
|
||||
},
|
||||
)
|
||||
]
|
||||
: [];
|
||||
|
||||
@@ -30,64 +30,59 @@ class LightningSwitcher extends StatelessWidget {
|
||||
shape:
|
||||
RoundedSuperellipseBorder(borderRadius: BorderRadiusGeometry.circular(900.0)),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer),
|
||||
width: 70,
|
||||
width: 63,
|
||||
height: 36,
|
||||
padding: EdgeInsets.symmetric(vertical: 2),
|
||||
padding: const EdgeInsets.all(4.5),
|
||||
child: Stack(
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
alignment: Alignment.centerRight,
|
||||
margin: EdgeInsets.only(left: lightningMode ? 36 : 2),
|
||||
duration: Duration(milliseconds: 250),
|
||||
margin: EdgeInsets.only(left: lightningMode ? 27 : 0),
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeOutCubic,
|
||||
width: 32,
|
||||
height: 32,
|
||||
// height: double.infinity,
|
||||
width: 27,
|
||||
height: 27,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(9999990.0)),
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
Container(
|
||||
child: Row(
|
||||
spacing: 2.0,
|
||||
children: [
|
||||
SizedBox(),
|
||||
AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 150),
|
||||
transitionBuilder: (child, animation) =>
|
||||
FadeTransition(opacity: animation, child: child),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: 'assets/new-ui/switcher-bitcoin.svg',
|
||||
key: ValueKey(lightningMode),
|
||||
width: 32,
|
||||
height: 32,
|
||||
colorFilter: ColorFilter.mode(
|
||||
lightningMode
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.surfaceContainer,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
transitionBuilder: (child, animation) =>
|
||||
FadeTransition(opacity: animation, child: child),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/switcher-bitcoin.svg",
|
||||
key: ValueKey(lightningMode),
|
||||
width: 27,
|
||||
height: 27,
|
||||
colorFilter: ColorFilter.mode(
|
||||
lightningMode
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.surfaceContainer,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 150),
|
||||
transitionBuilder: (child, animation) =>
|
||||
FadeTransition(opacity: animation, child: child),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: 'assets/new-ui/switcher-lightning.svg',
|
||||
key: ValueKey(lightningMode),
|
||||
width: 32,
|
||||
height: 32,
|
||||
colorFilter: ColorFilter.mode(
|
||||
lightningMode
|
||||
? Theme.of(context).colorScheme.surfaceContainer
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
transitionBuilder: (child, animation) =>
|
||||
FadeTransition(opacity: animation, child: child),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/switcher-lightning.svg",
|
||||
key: ValueKey(lightningMode),
|
||||
width: 27,
|
||||
height: 27,
|
||||
colorFilter: ColorFilter.mode(
|
||||
lightningMode
|
||||
? Theme.of(context).colorScheme.surfaceContainer
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import "package:cake_wallet/themes/core/theme_extension.dart";
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import "package:flutter/cupertino.dart";
|
||||
@@ -11,11 +12,11 @@ import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
|
||||
class SyncBar extends StatelessWidget {
|
||||
SyncBar({
|
||||
super.key,
|
||||
const SyncBar({
|
||||
required this.dashboardViewModel,
|
||||
required this.isSyncHeavy,
|
||||
required this.showSyncedMessage,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
@@ -46,106 +47,126 @@ class SyncBar extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Observer(
|
||||
builder: (_) {
|
||||
final status = dashboardViewModel.status;
|
||||
final Widget? icon = _getIcon(context, status.runtimeType);
|
||||
builder: (_) {
|
||||
final status = dashboardViewModel.status;
|
||||
if (!_showFullBar) {
|
||||
return _buildCompactBar(context);
|
||||
}
|
||||
|
||||
return Expanded(
|
||||
child: Stack(
|
||||
alignment: Alignment.centerLeft,
|
||||
children: [
|
||||
if (!_showFullBar()) _buildCompactBar(context),
|
||||
if (_showFullBar())
|
||||
// A single node: the localized status text (plus any active
|
||||
// Tor/MWEB/Silent Payments badge) is the label, and the hint says
|
||||
// where tapping leads. Everything inside is redundant with it.
|
||||
Semantics(
|
||||
button: true,
|
||||
label: _statusSemanticsLabel(context, status),
|
||||
hint: S.of(context).manage_nodes,
|
||||
onTap: () => _openNodeManagement(context),
|
||||
child: ExcludeSemantics(
|
||||
child: GestureDetector(
|
||||
onTap: () => _openNodeManagement(context),
|
||||
child: AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 100),
|
||||
child: Container(
|
||||
key: ValueKey(status.runtimeType),
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(9999),
|
||||
border: _getBorder(context, status.runtimeType),
|
||||
color: _getBackgroundColor(context, status.runtimeType),
|
||||
),
|
||||
child: Row(
|
||||
spacing: 10,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
if (icon != null) icon,
|
||||
// if (dashboardViewModel.silentPaymentsScanningActive &&
|
||||
// progressStatuses.contains(status.runtimeType)) ...[
|
||||
// Text(
|
||||
// "${(status.progress() * 100).toInt()}%",
|
||||
// style: TextStyle(fontSize: 12, color: Color(0xFFEFBA5E)),
|
||||
// ),
|
||||
// Text(
|
||||
// "·",
|
||||
// style: TextStyle(fontSize: 12),
|
||||
// )
|
||||
// ],
|
||||
Text(
|
||||
syncStatusTitle(status,
|
||||
dashboardViewModel.settingsStore.syncStatusDisplayMode),
|
||||
style: _getTextStyle(context, status.runtimeType),
|
||||
final Widget? icon = _getIcon(context, status.runtimeType);
|
||||
final statusTitle = _statusTitle(context, status);
|
||||
final silentPaymentsProgress = _silentPaymentsProgress(status);
|
||||
final barHeight = silentPaymentsProgress == null ? 36.0 : 40.0;
|
||||
|
||||
// A single node: the localized status text (plus any active
|
||||
// Tor/MWEB/Silent Payments badge) is the label, and the hint says
|
||||
// where tapping leads. Everything inside is redundant with it.
|
||||
return Semantics(
|
||||
button: true,
|
||||
label: _statusSemanticsLabel(context, status, statusTitle),
|
||||
value: silentPaymentsProgress,
|
||||
hint: S.of(context).manage_nodes,
|
||||
onTap: () => _openNodeManagement(context),
|
||||
child: ExcludeSemantics(
|
||||
child: GestureDetector(
|
||||
onTap: () => _openNodeManagement(context),
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
child: OverflowBox(
|
||||
key: ValueKey(status.runtimeType),
|
||||
alignment: Alignment.center,
|
||||
minHeight: barHeight,
|
||||
maxHeight: barHeight,
|
||||
child: Container(
|
||||
height: barHeight,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(9999),
|
||||
border: _getBorder(context, status.runtimeType),
|
||||
color: _getBackgroundColor(context, status.runtimeType),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
spacing: 8,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
if (icon != null) icon,
|
||||
if (silentPaymentsProgress != null) ...[
|
||||
Text(
|
||||
silentPaymentsProgress,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: context.customColors.warningOutlineColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
"·",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
Flexible(
|
||||
child: Text(
|
||||
statusTitle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: _getTextStyle(context, status.runtimeType),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
void _openNodeManagement(BuildContext context) =>
|
||||
CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withAlpha(85),
|
||||
builder: (context) => FractionallySizedBox(
|
||||
child: Material(
|
||||
child: getIt.get<ManageNodesPage>(param1: false),
|
||||
)));
|
||||
void _openNodeManagement(BuildContext context) {
|
||||
CupertinoScaffold.showCupertinoModalBottomSheet(
|
||||
context: context,
|
||||
barrierColor: Colors.black.withAlpha(85),
|
||||
builder: (context) => FractionallySizedBox(
|
||||
child: Material(
|
||||
child: getIt.get<ManageNodesPage>(param1: false),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Compact mode shows sync state with a pulsing dot (and a Tor glyph) only, so
|
||||
/// the whole row needs a text equivalent.
|
||||
Widget _buildCompactBar(BuildContext context) {
|
||||
final row = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
spacing: 6,
|
||||
children: [
|
||||
if (dashboardViewModel.isTorEnabled)
|
||||
CakeImageWidget(
|
||||
const CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/tor.svg",
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
if (_showDot()) const CupertinoActivityIndicator(radius: 8),
|
||||
if(_showLightSyncCheck()) const Icon(Icons.check, color: syncedColor, size: 18),
|
||||
],
|
||||
if (_showLightSyncCheck()) const Icon(Icons.check, color: syncedColor, size: 18),
|
||||
],
|
||||
);
|
||||
|
||||
final label = _joinLabels([
|
||||
if (dashboardViewModel.isTorEnabled) S.of(context).tor_connection,
|
||||
if (_showDot()) S.of(context).synchronizing,
|
||||
if (_showLightSyncCheck()) S.of(context).sync_synced,
|
||||
]);
|
||||
|
||||
if (label.isEmpty) return row;
|
||||
if (label.isEmpty) {
|
||||
return row;
|
||||
}
|
||||
|
||||
return Semantics(label: label, child: ExcludeSemantics(child: row));
|
||||
}
|
||||
@@ -153,11 +174,17 @@ class SyncBar extends StatelessWidget {
|
||||
bool get _isShowingSyncedMessage =>
|
||||
showSyncedMessage && dashboardViewModel.status.runtimeType == SyncedSyncStatus;
|
||||
|
||||
String _statusSemanticsLabel(BuildContext context, SyncStatus status) {
|
||||
String _statusTitle(BuildContext context, SyncStatus status) {
|
||||
final title = syncStatusTitle(status, dashboardViewModel.settingsStore.syncStatusDisplayMode);
|
||||
|
||||
return title.isNotEmpty ? title : S.of(context).synchronizing;
|
||||
}
|
||||
|
||||
String _statusSemanticsLabel(BuildContext context, SyncStatus status, String statusTitle) {
|
||||
final isFailure = failStatuses.contains(status.runtimeType);
|
||||
|
||||
return _joinLabels([
|
||||
syncStatusTitle(status, dashboardViewModel.settingsStore.syncStatusDisplayMode),
|
||||
statusTitle,
|
||||
if (!isFailure && dashboardViewModel.isTorEnabled) S.of(context).tor_connection,
|
||||
if (!isFailure && dashboardViewModel.hasMweb) S.of(context).litecoin_mweb,
|
||||
if (!isFailure && dashboardViewModel.hasSilentPayments) S.of(context).silent_payments,
|
||||
@@ -166,6 +193,16 @@ class SyncBar extends StatelessWidget {
|
||||
|
||||
String _joinLabels(List<String> parts) => parts.where((part) => part.isNotEmpty).join(", ");
|
||||
|
||||
String? _silentPaymentsProgress(SyncStatus status) {
|
||||
if (!dashboardViewModel.hasSilentPayments ||
|
||||
!dashboardViewModel.silentPaymentsScanningActive ||
|
||||
status is! SyncingSyncStatus) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return "${(status.progress() * 100).toInt()}%";
|
||||
}
|
||||
|
||||
Color? _getBackgroundColor(BuildContext context, Type status) {
|
||||
if (failStatuses.contains(status)) {
|
||||
return Theme.of(context).colorScheme.errorContainer.withAlpha(64);
|
||||
@@ -192,14 +229,12 @@ class SyncBar extends StatelessWidget {
|
||||
color = Theme.of(context).colorScheme.onSurfaceVariant;
|
||||
}
|
||||
|
||||
return TextStyle(
|
||||
fontSize: 12, fontWeight: FontWeight.w400, color: color);
|
||||
return TextStyle(fontSize: 12, fontWeight: FontWeight.w400, color: color);
|
||||
}
|
||||
|
||||
Widget? _getIcon(BuildContext context, Type status) {
|
||||
|
||||
if(status == SyncedSyncStatus) {
|
||||
return Icon(Icons.check, color: syncedColor, size: 12);
|
||||
if (status == SyncedSyncStatus) {
|
||||
return const Icon(Icons.check, color: syncedColor, size: 12);
|
||||
}
|
||||
|
||||
if (status == LostConnectionSyncStatus) {
|
||||
@@ -219,22 +254,35 @@ class SyncBar extends StatelessWidget {
|
||||
final List<Widget> children = [];
|
||||
|
||||
if (dashboardViewModel.isTorEnabled) {
|
||||
children.add(CakeImageWidget(
|
||||
children.add(
|
||||
const CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/tor_sync.svg",
|
||||
colorFilter: ColorFilter.mode(Color(0xFF8A38F5), BlendMode.srcIn)));
|
||||
colorFilter: ColorFilter.mode(Color(0xFF8A38F5), BlendMode.srcIn),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (dashboardViewModel.hasMweb) {
|
||||
children.add(CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/mweb_sync.svg",
|
||||
colorFilter:
|
||||
ColorFilter.mode(Theme.of(context).colorScheme.onSurfaceVariant, BlendMode.srcIn),
|
||||
));
|
||||
children.add(
|
||||
CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/mweb_sync.svg",
|
||||
colorFilter:
|
||||
ColorFilter.mode(Theme.of(context).colorScheme.onSurfaceVariant, BlendMode.srcIn),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (dashboardViewModel.hasSilentPayments) {
|
||||
children.add(CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/silent_sync.svg",
|
||||
colorFilter: ColorFilter.mode(Color(0xFFEFBA5E), BlendMode.srcIn),
|
||||
));
|
||||
children.add(
|
||||
CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/silent_sync.svg",
|
||||
width: 16,
|
||||
height: 16,
|
||||
colorFilter: ColorFilter.mode(context.customColors.warningOutlineColor, BlendMode.srcIn),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (children.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Row(
|
||||
@@ -243,15 +291,16 @@ class SyncBar extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
bool _showFullBar() {
|
||||
if (dashboardViewModel.status.runtimeType == SyncedSyncStatus)
|
||||
bool get _showFullBar {
|
||||
if (dashboardViewModel.status.runtimeType == SyncedSyncStatus) {
|
||||
return isSyncHeavy && _isShowingSyncedMessage;
|
||||
}
|
||||
|
||||
return isSyncHeavy || failStatuses.contains(dashboardViewModel.status.runtimeType);
|
||||
}
|
||||
|
||||
bool _showDot() =>
|
||||
!isSyncHeavy && progressStatuses.contains(dashboardViewModel.status.runtimeType);
|
||||
|
||||
bool _showLightSyncCheck() =>
|
||||
!isSyncHeavy && _isShowingSyncedMessage;
|
||||
bool _showLightSyncCheck() => !isSyncHeavy && _isShowingSyncedMessage;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/chain_icon.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/lightning_switcher.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/sync_bar.dart';
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/wallet_info.dart";
|
||||
import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
@@ -15,11 +16,11 @@ import 'package:mobx/mobx.dart';
|
||||
|
||||
class TopBar extends StatefulWidget {
|
||||
const TopBar({
|
||||
super.key,
|
||||
required this.lightningMode,
|
||||
required this.onLightningSwitchPress,
|
||||
required this.dashboardViewModel,
|
||||
required this.onSettingsButtonPress,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final bool lightningMode;
|
||||
@@ -36,25 +37,59 @@ class _TopBarState extends State<TopBar> {
|
||||
|
||||
bool showSyncedMessage = false;
|
||||
Timer? syncedMessageTimer;
|
||||
late final ReactionDisposer? _statusReactionDisposer;
|
||||
ReactionDisposer? _statusReactionDisposer;
|
||||
|
||||
bool get replacesWalletName {
|
||||
final status = widget.dashboardViewModel.status.runtimeType;
|
||||
if (status == SyncedSyncStatus) {
|
||||
return showSyncedMessage;
|
||||
}
|
||||
|
||||
return widget.dashboardViewModel.isSyncHeavy ||
|
||||
SyncBar.progressStatuses.contains(status) ||
|
||||
SyncBar.failStatuses.contains(status);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_bindStatusReaction();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant TopBar oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
if (identical(oldWidget.dashboardViewModel, widget.dashboardViewModel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
syncedMessageTimer?.cancel();
|
||||
syncedMessageTimer = null;
|
||||
showSyncedMessage = false;
|
||||
_statusReactionDisposer?.call();
|
||||
_bindStatusReaction();
|
||||
}
|
||||
|
||||
void _bindStatusReaction() {
|
||||
_statusReactionDisposer = reaction(
|
||||
(_) => widget.dashboardViewModel.status.runtimeType,
|
||||
(status) {
|
||||
syncedMessageTimer?.cancel();
|
||||
syncedMessageTimer = null;
|
||||
|
||||
if (status == SyncedSyncStatus) {
|
||||
if(mounted) {
|
||||
if (mounted) {
|
||||
setState(() => showSyncedMessage = true);
|
||||
}
|
||||
syncedMessageTimer =
|
||||
Timer(syncedMessageDuration, () => setState(() => showSyncedMessage = false));
|
||||
syncedMessageTimer = Timer(syncedMessageDuration, () {
|
||||
syncedMessageTimer = null;
|
||||
if (mounted) {
|
||||
setState(() => showSyncedMessage = false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if(mounted) {
|
||||
if (mounted) {
|
||||
setState(() => showSyncedMessage = false);
|
||||
}
|
||||
}
|
||||
@@ -65,51 +100,90 @@ class _TopBarState extends State<TopBar> {
|
||||
@override
|
||||
void dispose() {
|
||||
syncedMessageTimer?.cancel();
|
||||
_statusReactionDisposer?.reaction.dispose();
|
||||
syncedMessageTimer = null;
|
||||
_statusReactionDisposer?.call();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: 10, left: 18, right: 18, top: 10 + _additionalTopPadding(context)),
|
||||
child: Observer(
|
||||
builder: (_) => Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
(widget.dashboardViewModel.hasLightning)
|
||||
? LightningSwitcher(
|
||||
lightningMode: widget.lightningMode,
|
||||
onLightningSwitchPress: widget.onLightningSwitchPress,
|
||||
)
|
||||
: ChainIcon(
|
||||
iconPath: widget.dashboardViewModel.wallet.currency.flatIconPath ?? "",
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
isSyncHeavy: widget.dashboardViewModel.isSyncHeavy,
|
||||
showSyncedMessage: showSyncedMessage),
|
||||
SyncBar(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
isSyncHeavy: widget.dashboardViewModel.isSyncHeavy,
|
||||
showSyncedMessage: showSyncedMessage,
|
||||
),
|
||||
ModernButton.svg(
|
||||
iconColor: Theme.of(context).colorScheme.primary,
|
||||
size: 36,
|
||||
onPressed: () {
|
||||
HapticFeedback.mediumImpact();
|
||||
widget.onSettingsButtonPress();
|
||||
},
|
||||
svgPath: "assets/new-ui/top-settings.svg",
|
||||
semanticLabel: S.of(context).settings_title,
|
||||
),
|
||||
],
|
||||
padding: EdgeInsets.only(left: 18, right: 18, top: 10 + _additionalTopPadding(context)),
|
||||
child: Observer(
|
||||
builder: (_) => Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
widget.dashboardViewModel.hasLightning
|
||||
? LightningSwitcher(
|
||||
lightningMode: widget.lightningMode,
|
||||
onLightningSwitchPress: widget.onLightningSwitchPress,
|
||||
)
|
||||
: ChainIcon(
|
||||
iconPath: widget.dashboardViewModel.wallet.currency.flatIconPath ?? "",
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
isSyncHeavy: widget.dashboardViewModel.isSyncHeavy,
|
||||
showSyncedMessage: showSyncedMessage,
|
||||
),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
reverseDuration: Duration.zero,
|
||||
layoutBuilder: (currentChild, previousChildren) => Stack(
|
||||
alignment: Alignment.centerLeft,
|
||||
children: [
|
||||
...previousChildren,
|
||||
if (currentChild != null) currentChild,
|
||||
],
|
||||
),
|
||||
child: replacesWalletName
|
||||
? SyncBar(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
isSyncHeavy: widget.dashboardViewModel.isSyncHeavy,
|
||||
showSyncedMessage: showSyncedMessage,
|
||||
)
|
||||
: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: WalletInfoBar(
|
||||
name: widget.dashboardViewModel.wallet.name,
|
||||
hardwareWalletType:
|
||||
widget.dashboardViewModel.wallet.hardwareWalletType,
|
||||
),
|
||||
),
|
||||
if (widget.dashboardViewModel.isTorEnabled) ...[
|
||||
const SizedBox(width: 6),
|
||||
SyncBar(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
isSyncHeavy: widget.dashboardViewModel.isSyncHeavy,
|
||||
showSyncedMessage: showSyncedMessage,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
ModernButton.svg(
|
||||
iconColor: Theme.of(context).colorScheme.primary,
|
||||
size: 36,
|
||||
onPressed: () {
|
||||
HapticFeedback.mediumImpact();
|
||||
widget.onSettingsButtonPress();
|
||||
},
|
||||
svgPath: "assets/new-ui/top-settings.svg",
|
||||
semanticLabel: S.of(context).settings_title,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
|
||||
//FIXME remove after this gets fixed flutter-side
|
||||
double _additionalTopPadding(BuildContext context) {
|
||||
if (Platform.isIOS && MediaQuery.of(context).viewPadding.top < 12) return 24;
|
||||
if (Platform.isIOS && MediaQuery.of(context).viewPadding.top < 12) {
|
||||
return 24;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,102 +1,67 @@
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class WalletInfoBar extends StatelessWidget {
|
||||
const WalletInfoBar(
|
||||
{super.key,
|
||||
required this.lightningMode,
|
||||
required this.name,
|
||||
required this.hardwareWalletType,
|
||||
required this.onCustomizeButtonTap,
|
||||
required this.hasCustomize});
|
||||
const WalletInfoBar({
|
||||
required this.name,
|
||||
required this.hardwareWalletType,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final bool lightningMode;
|
||||
final String name;
|
||||
final HardwareWalletType? hardwareWalletType;
|
||||
final bool hasCustomize;
|
||||
final VoidCallback onCustomizeButtonTap;
|
||||
|
||||
void _openAccountCustomizer() {
|
||||
if (hasCustomize) {
|
||||
onCustomizeButtonTap();
|
||||
HapticFeedback.mediumImpact();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// The row, the hardware-wallet glyph and the inner accounts button are one
|
||||
// control for a screen reader: a single labeled node opening the customizer.
|
||||
final semanticsLabel =
|
||||
hardwareWalletType == null ? name : "$name, ${S.of(context).hardware_wallet}";
|
||||
|
||||
final row = GestureDetector(
|
||||
onTap: _openAccountCustomizer,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 150),
|
||||
transitionBuilder: (child, animation) {
|
||||
return SizeTransition(
|
||||
return Semantics(
|
||||
label: semanticsLabel,
|
||||
child: ExcludeSemantics(
|
||||
child: Row(
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
transitionBuilder: (child, animation) => SizeTransition(
|
||||
axis: Axis.horizontal,
|
||||
sizeFactor: animation,
|
||||
child: FadeTransition(opacity: animation, child: child),
|
||||
);
|
||||
},
|
||||
child: hardwareWalletIcon == null
|
||||
? const SizedBox.shrink(key: ValueKey("empty"))
|
||||
: Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: hardwareWalletIcon!,
|
||||
key: ValueKey("hardware_wallet_icon"),
|
||||
width: 24,
|
||||
height: 24,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
child: hardwareWalletIcon == null
|
||||
? const SizedBox.shrink(key: ValueKey("empty"))
|
||||
: Padding(
|
||||
padding: const EdgeInsets.only(right: 4),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: hardwareWalletIcon!,
|
||||
key: const ValueKey("hardware_wallet_icon"),
|
||||
width: 24,
|
||||
height: 24,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
name,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge
|
||||
?.copyWith(color: Theme.of(context).colorScheme.onSurface),
|
||||
),
|
||||
if (hasCustomize) ...[
|
||||
SizedBox(width: 8),
|
||||
ModernButton.svg(
|
||||
size: 24,
|
||||
onPressed: _openAccountCustomizer,
|
||||
svgPath: "assets/new-ui/icon-accounts.svg",
|
||||
semanticLabel: S.of(context).wallet_accounts,
|
||||
)
|
||||
]
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
name,
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(color: Theme.of(context).colorScheme.onSurface),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (!hasCustomize) {
|
||||
return Semantics(label: semanticsLabel, child: ExcludeSemantics(child: row));
|
||||
}
|
||||
|
||||
return Semantics(
|
||||
button: true,
|
||||
label: semanticsLabel,
|
||||
hint: S.of(context).wallet_accounts,
|
||||
onTap: _openAccountCustomizer,
|
||||
child: ExcludeSemantics(child: row),
|
||||
);
|
||||
}
|
||||
|
||||
String? get hardwareWalletIcon {
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import "package:cake_wallet/new-ui/widgets/money/currency_symbol_text.dart";
|
||||
import "package:cw_core/currency.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter/services.dart";
|
||||
|
||||
class FloatingAmountInput extends StatefulWidget {
|
||||
const FloatingAmountInput({
|
||||
required this.currency,
|
||||
required this.controller,
|
||||
super.key,
|
||||
this.focusNode,
|
||||
this.inputFormatters,
|
||||
this.onChanged,
|
||||
this.validator,
|
||||
});
|
||||
|
||||
final Currency currency;
|
||||
final TextEditingController controller;
|
||||
final FocusNode? focusNode;
|
||||
final List<TextInputFormatter>? inputFormatters;
|
||||
final Function(String)? onChanged;
|
||||
final FormFieldValidator<String>? validator;
|
||||
|
||||
@override
|
||||
State<FloatingAmountInput> createState() => _FloatingAmountInputState();
|
||||
}
|
||||
|
||||
class _FloatingAmountInputState extends State<FloatingAmountInput> {
|
||||
bool _amountFocused = false;
|
||||
late FocusNode focusNode = widget.focusNode ?? FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
focusNode.addListener(() => setState(() => _amountFocused = focusNode.hasFocus));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (focusNode != widget.focusNode) {
|
||||
focusNode.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Center(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
children: [
|
||||
IntrinsicWidth(
|
||||
child: TextFormField(
|
||||
controller: widget.controller,
|
||||
focusNode: focusNode,
|
||||
maxLines: 1,
|
||||
onChanged: widget.onChanged,
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
validator: widget.validator,
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
signed: false,
|
||||
decimal: true,
|
||||
),
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
FilteringTextInputFormatter.allow(
|
||||
RegExp(r"^\d*[.,]?\d*$"),
|
||||
),
|
||||
],
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
isCollapsed: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
fillColor: Colors.transparent,
|
||||
hoverColor: Colors.transparent,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
hintText: _amountFocused || widget.controller.text.isNotEmpty ? null : "0.00",
|
||||
hintStyle: Theme.of(context).textTheme.displayMedium?.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
style: Theme.of(context).textTheme.displayMedium?.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 45,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
CurrencySymbolText(
|
||||
widget.currency,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.displayMedium?.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 45,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ class ModalTopBar extends StatelessWidget {
|
||||
this.leadingIcon,
|
||||
this.trailingIcon,
|
||||
this.padding,
|
||||
this.bottomText,
|
||||
this.leadingWidget,
|
||||
this.trailingWidget,
|
||||
this.leadingSemanticLabel,
|
||||
@@ -27,6 +28,7 @@ class ModalTopBar extends StatelessWidget {
|
||||
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final String? bottomText;
|
||||
final VoidCallback onLeadingPressed;
|
||||
final VoidCallback onTrailingPressed;
|
||||
final Widget? leadingIcon;
|
||||
@@ -50,32 +52,44 @@ class ModalTopBar extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasBottomText = bottomText != null && bottomText!.isNotEmpty;
|
||||
return Padding(
|
||||
padding: padding ?? EdgeInsets.all(18),
|
||||
child: Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
Positioned(
|
||||
top: 6,
|
||||
top: hasBottomText ? -4 : 6,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 4,
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Semantics(
|
||||
Column(
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Semantics(
|
||||
key: ValueKey(title),
|
||||
header: title.isNotEmpty,
|
||||
// Android reads the heading from headingLevel since the
|
||||
header: title.isNotEmpty,
|
||||
// Android reads the heading from headingLevel since the
|
||||
// Flutter 3.41 engine; header: alone only covers iOS.
|
||||
headingLevel: title.isNotEmpty ? 1 : null,
|
||||
child: Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
style: TextStyle(
|
||||
fontSize: hasBottomText ? 16 : 18, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (hasBottomText)
|
||||
Text(
|
||||
bottomText!,
|
||||
style: TextStyle(
|
||||
fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (subtitle != null && subtitle!.isNotEmpty)
|
||||
Text(subtitle!,
|
||||
style: Theme.of(context)
|
||||
|
||||
@@ -214,7 +214,7 @@ class SendTransactionDetails extends StatelessWidget {
|
||||
sendViewModel.balance, sendViewModel.selectedCryptoCurrency) ??
|
||||
zero;
|
||||
|
||||
return sendViewModel.selectedCryptoCurrency.tryParseAmount(o.cryptoAmount) ?? zero;
|
||||
return o.cryptoAmountMoney;
|
||||
}, sendViewModel.selectedCryptoCurrency))
|
||||
: sendViewModel.amountParsingProxy.asDisplayString(transaction.amount);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
|
||||
import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
|
||||
import 'package:cw_core/amount/amount_sanitizer.dart';
|
||||
import 'package:cw_core/amount/money.dart';
|
||||
import 'package:cw_core/crypto_amount_format.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
@@ -176,9 +177,11 @@ class SwapTransactionDetails extends StatelessWidget {
|
||||
iconPath: exchangeViewModel.depositCurrency.iconPath ?? "",
|
||||
badgeIconPath: _resolveChainBadgePath(exchangeViewModel.depositCurrency),
|
||||
trailingText: exchangeViewModel.amountParsingProxy
|
||||
.asDisplayStringWithSymbol(exchangeViewModel.depositCurrency
|
||||
.tryParseAmount(exchangeTradeViewModel.trade.amount) ??
|
||||
Money.zero(exchangeViewModel.depositCurrency)),
|
||||
.asDisplayStringWithSymbol(
|
||||
exchangeViewModel.depositCurrency.tryParseAmount(
|
||||
exchangeTradeViewModel.trade.amount.sanitized(),
|
||||
) ??
|
||||
Money.zero(exchangeViewModel.depositCurrency)),
|
||||
),
|
||||
if (exchangeTradeViewModel.sendViewModel.pendingTransaction != null)
|
||||
ListItemRegularRow(
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import "package:cake_wallet/src/widgets/cake_image_widget.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
class TokenChainDisplay extends StatelessWidget {
|
||||
const TokenChainDisplay({required this.size, required this.asset, super.key});
|
||||
|
||||
final CryptoCurrency asset;
|
||||
final double size;
|
||||
|
||||
static const double _chainIconSizeFactor = 0.35;
|
||||
|
||||
double get _chainIconSize => size * _chainIconSizeFactor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => SizedBox(
|
||||
width: size+(_chainIconSize/2),
|
||||
height: size+(_chainIconSize/2),
|
||||
child: Stack(
|
||||
children: [
|
||||
CakeImageWidget(
|
||||
imageUrl: asset.iconPath ?? "",
|
||||
width: size,
|
||||
),
|
||||
if ((asset.chainIconPath ?? "").isNotEmpty)
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Container(
|
||||
decoration: ShapeDecoration(
|
||||
shape: RoundedSuperellipseBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
side: const BorderSide(color: Colors.black),
|
||||
),
|
||||
color: Colors.white,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(2),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: asset.chainIconPath,
|
||||
width: _chainIconSize,
|
||||
height: _chainIconSize,
|
||||
colorFilter: const ColorFilter.mode(Colors.black, BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
+12
-2
@@ -4,10 +4,12 @@ import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
|
||||
import "package:cake_wallet/core/auth_service.dart";
|
||||
import 'package:cake_wallet/core/new_wallet_arguments.dart';
|
||||
import 'package:cake_wallet/new-ui/new_dashboard.dart';
|
||||
import "package:cake_wallet/new-ui/pages/account_customizer.dart";
|
||||
import 'package:cake_wallet/new-ui/pages/about_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/bridge/bridge_history_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/bridge/bridge_network_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/bridge/bridge_receiving_wallet_page.dart';
|
||||
import "package:cake_wallet/new-ui/pages/buy_sell/buy_sell_amount_page.dart";
|
||||
import 'package:cake_wallet/new-ui/pages/coin_control_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/addresses_page.dart';
|
||||
import 'package:cake_wallet/new-ui/pages/lightning_username_page.dart';
|
||||
@@ -15,6 +17,7 @@ import "package:cake_wallet/new-ui/pages/seed/pre_seed_page.dart";
|
||||
import "package:cake_wallet/new-ui/pages/seed/show_keys_disclaimer_page.dart";
|
||||
import "package:cake_wallet/new-ui/pages/receive_page.dart";
|
||||
import 'package:cake_wallet/new-ui/pages/send_page.dart';
|
||||
import "package:cake_wallet/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart";
|
||||
import 'package:cake_wallet/new-ui/widgets/hardware_wallet/sync_key_images_sheet.dart';
|
||||
import 'package:cake_wallet/order/order.dart';
|
||||
import 'package:cake_wallet/core/new_wallet_type_arguments.dart';
|
||||
@@ -145,6 +148,7 @@ import 'package:cake_wallet/utils/payment_request.dart';
|
||||
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/bridge/bridge_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/bridge/bridge_history_view_model.dart';
|
||||
import "package:cake_wallet/view_model/buy/buy_sell_view_model.dart";
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import "package:cake_wallet/view_model/dashboard/nft_send_view_model.dart";
|
||||
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
|
||||
@@ -668,6 +672,13 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
builder: (_) => getIt.get<MoneroAccountEditOrCreatePage>(
|
||||
param1: settings.arguments as AccountListItem?));
|
||||
|
||||
case Routes.accountCustomizer:
|
||||
return handleRouteWithPlatformAwareness(
|
||||
(_) => getIt.get<AccountCustomizer>(
|
||||
param1: settings.arguments! as DashboardViewModel,
|
||||
),
|
||||
);
|
||||
|
||||
case Routes.nanoAccountCreation:
|
||||
return CupertinoPageRoute<String>(
|
||||
builder: (_) =>
|
||||
@@ -717,9 +728,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
builder: (_) => getIt.get<OrderDetailsPage>(param1: settings.arguments as Order));
|
||||
|
||||
case Routes.buySellPage:
|
||||
final args = settings.arguments as bool?;
|
||||
return handleRouteWithPlatformAwareness(
|
||||
(context) => getIt.get<BuySellPage>(param1: args),
|
||||
(context) => Material(child: getIt.get<NewBuySellAmountPage>(param1: BuySellPageMode.buy)),
|
||||
);
|
||||
|
||||
case Routes.buyOptionsPage:
|
||||
|
||||
@@ -32,6 +32,7 @@ class Routes {
|
||||
static const login = '/login';
|
||||
static const splash = '/splash';
|
||||
static const accountCreation = '/account_new';
|
||||
static const accountCustomizer = "/account_customizer";
|
||||
static const nanoAccountCreation = '/nano_account_new';
|
||||
static const addressBook = '/address_book';
|
||||
static const pickerAddressBook = '/picker_address_book';
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:cake_wallet/core/address_resolver/address_resolver_service.dart'
|
||||
import 'package:cake_wallet/core/address_validator.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_args.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/currency_picker/currency_picker_sheet.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/currency_picker/fiat_currency_picker_sheet.dart';
|
||||
@@ -410,21 +411,9 @@ class BuySellPage extends BasePage {
|
||||
currencyButtonColor: Colors.transparent,
|
||||
addressButtonsColor: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
borderColor: Theme.of(context).colorScheme.outlineVariant,
|
||||
onPushPasteButton: (context) async {
|
||||
final domain = cryptoCurrencyKey.currentState!.addressController.text;
|
||||
final parsed = await fetchParsedAddress(context, domain, buySellViewModel.cryptoCurrency);
|
||||
if (parsed.isNotEmpty) {
|
||||
buySellViewModel.cryptoCurrencyAddress = parsed;
|
||||
}
|
||||
},
|
||||
onPushAddressBookButton: (context) async {
|
||||
final domain = cryptoCurrencyKey.currentState!.addressController.text;
|
||||
final parsed = await fetchParsedAddress(context, domain, buySellViewModel.cryptoCurrency);
|
||||
if (parsed.isNotEmpty) {
|
||||
buySellViewModel.cryptoCurrencyAddress = parsed;
|
||||
}
|
||||
},
|
||||
fillColor: buySellViewModel.isBuyAction
|
||||
onPushPasteButton: (context) async {},
|
||||
onPushAddressBookButton: (context) async {},
|
||||
fillColor: buySellViewModel.mode == BuySellPageMode.buy
|
||||
? Theme.of(context).colorScheme.surfaceContainer
|
||||
: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||
),
|
||||
@@ -463,7 +452,7 @@ class BuySellPage extends BasePage {
|
||||
addressTextFieldValidator: AddressValidator(type: buySellViewModel.cryptoCurrency),
|
||||
onPushPasteButton: (context) async {},
|
||||
onPushAddressBookButton: (context) async {},
|
||||
fillColor: buySellViewModel.isBuyAction
|
||||
fillColor: buySellViewModel.mode == BuySellPageMode.buy
|
||||
? Theme.of(context).colorScheme.surfaceContainerLow
|
||||
: Theme.of(context).colorScheme.surfaceContainer,
|
||||
useSatoshis: buySellViewModel.useSatoshi,
|
||||
@@ -473,50 +462,14 @@ class BuySellPage extends BasePage {
|
||||
if (responsiveLayoutUtil.shouldRenderMobileUI) {
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
if (buySellViewModel.isBuyAction) {
|
||||
return MobileExchangeCardsSection(
|
||||
firstExchangeCard: fiatExchangeCard,
|
||||
secondExchangeCard: cryptoExchangeCard,
|
||||
onBuyTap: () => null,
|
||||
onSellTap: () =>
|
||||
buySellViewModel.isBuyAction ? buySellViewModel.changeBuySellAction() : null,
|
||||
isBuySellOption: true,
|
||||
);
|
||||
} else {
|
||||
return MobileExchangeCardsSection(
|
||||
firstExchangeCard: cryptoExchangeCard,
|
||||
secondExchangeCard: fiatExchangeCard,
|
||||
onBuyTap: () =>
|
||||
!buySellViewModel.isBuyAction ? buySellViewModel.changeBuySellAction() : null,
|
||||
onSellTap: () => null,
|
||||
isBuySellOption: true,
|
||||
);
|
||||
}
|
||||
return Placeholder();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
if (buySellViewModel.isBuyAction) {
|
||||
return DesktopExchangeCardsSection(
|
||||
firstExchangeCard: fiatExchangeCard,
|
||||
secondExchangeCard: cryptoExchangeCard,
|
||||
onBuyTap: () => null,
|
||||
onSellTap: () =>
|
||||
buySellViewModel.isBuyAction ? buySellViewModel.changeBuySellAction() : null,
|
||||
isBuySellOption: true,
|
||||
);
|
||||
} else {
|
||||
return DesktopExchangeCardsSection(
|
||||
firstExchangeCard: cryptoExchangeCard,
|
||||
secondExchangeCard: fiatExchangeCard,
|
||||
onBuyTap: () =>
|
||||
!buySellViewModel.isBuyAction ? buySellViewModel.changeBuySellAction() : null,
|
||||
onSellTap: () => null,
|
||||
isBuySellOption: true,
|
||||
);
|
||||
}
|
||||
return Placeholder();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/csv_export_service.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/balance_display_mode.dart';
|
||||
import 'package:cake_wallet/order/order_source_description.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/anonpay_transaction_row.dart';
|
||||
@@ -66,7 +67,8 @@ class TransactionsPage extends StatelessWidget {
|
||||
}),
|
||||
HeaderRow(
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
onExportCsv: () => CsvExportService().exportToCsv(dashboardViewModel.items, context),
|
||||
onExportCsv: () =>
|
||||
getIt.get<CsvExportService>().exportToCsv(dashboardViewModel.items, context),
|
||||
key: ValueKey('transactions_page_header_row_key'),
|
||||
),
|
||||
Expanded(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:vector_graphics/vector_graphics.dart';
|
||||
@@ -130,7 +131,10 @@ class CakeImageWidget extends StatelessWidget {
|
||||
/// A caller-supplied [loadingWidget] owns its own semantics; the built-in
|
||||
/// spinner is purely visual and must not become an unnamed focus stop.
|
||||
Widget _buildLoadingWidget() =>
|
||||
loadingWidget ?? const ExcludeSemantics(child: Center(child: CircularProgressIndicator()));
|
||||
loadingWidget ?? ExcludeSemantics(child:SizedBox(
|
||||
height: height,
|
||||
width: width,
|
||||
child: Center(child: CupertinoActivityIndicator())));
|
||||
|
||||
Widget _buildErrorWidget(BuildContext context) {
|
||||
final Widget placeholder = Container(
|
||||
|
||||
@@ -29,31 +29,29 @@ class ListItemDropdownWidget extends StatelessWidget {
|
||||
return ListItemStyleWrapper(
|
||||
isFirstInSection: isFirstInSection,
|
||||
isLastInSection: isLastInSection,
|
||||
onTap: onTap,
|
||||
builder: (context, textStyle, labelStyle) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(label, style: textStyle),
|
||||
Row(
|
||||
children: [
|
||||
if (trailingText != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: Text(
|
||||
trailingText!,
|
||||
style: labelStyle,
|
||||
),
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(label, style: textStyle),
|
||||
Row(
|
||||
children: [
|
||||
if (trailingText != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: Text(
|
||||
trailingText!,
|
||||
style: labelStyle,
|
||||
),
|
||||
Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -23,22 +23,26 @@ class ListItemRegularRowWidget extends StatelessWidget {
|
||||
this.foregroundColor,
|
||||
this.trailingIconSize,
|
||||
this.bottomWidget,
|
||||
this.subtitleColor,
|
||||
this.trailingWidget,
|
||||
this.copyableText,
|
||||
this.leadingIconErrorWidget,
|
||||
this.leadingIconSize,
|
||||
this.badgeIconSize,
|
||||
this.iconColor});
|
||||
this.iconColor,
|
||||
this.secondaryLabel});
|
||||
|
||||
final String keyValue;
|
||||
final String label;
|
||||
final String? subtitle;
|
||||
final String? trailingText;
|
||||
final String? secondaryLabel;
|
||||
final String? iconPath;
|
||||
final String? badgeIconPath;
|
||||
final VoidCallback? onTap;
|
||||
final bool isFirstInSection;
|
||||
final bool isLastInSection;
|
||||
final Color? subtitleColor;
|
||||
final bool showArrow;
|
||||
final String? trailingIconPath;
|
||||
final Widget? bottomWidget;
|
||||
@@ -143,14 +147,28 @@ class ListItemRegularRowWidget extends StatelessWidget {
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
)
|
||||
else
|
||||
Text(label,
|
||||
style: foregroundColor == null
|
||||
? textStyle
|
||||
: textStyle.copyWith(color: foregroundColor)),
|
||||
Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
Text(label,
|
||||
style: foregroundColor == null
|
||||
? textStyle
|
||||
: textStyle.copyWith(color: foregroundColor)),
|
||||
if (secondaryLabel != null)
|
||||
Text(
|
||||
secondaryLabel!,
|
||||
style: textStyle.copyWith(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
)
|
||||
],
|
||||
),
|
||||
if (subtitle != null)
|
||||
Text(
|
||||
subtitle!,
|
||||
style: labelStyle.copyWith(fontSize: 12),
|
||||
style: subtitleColor == null
|
||||
? labelStyle.copyWith(fontSize: 12)
|
||||
: labelStyle.copyWith(fontSize: 12, color: subtitleColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -32,30 +32,35 @@ class ListItemSelectorWidget extends StatelessWidget {
|
||||
onTap: onTap,
|
||||
isFirstInSection: isFirstInSection,
|
||||
isLastInSection: isLastInSection,
|
||||
builder: (context, textStyle, labelStyle) {
|
||||
return Row(
|
||||
builder: (context, textStyle, labelStyle) => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(child: Text(label, style: textStyle)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
Text(
|
||||
options[selectedIndex],
|
||||
style: labelStyle,
|
||||
),
|
||||
CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/chooser.svg",
|
||||
colorFilter:
|
||||
ColorFilter.mode(theme.colorScheme.onSurfaceVariant, BlendMode.srcIn),
|
||||
),
|
||||
],
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
options[selectedIndex],
|
||||
style: labelStyle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
CakeImageWidget(
|
||||
imageUrl: "assets/new-ui/chooser.svg",
|
||||
colorFilter:
|
||||
ColorFilter.mode(theme.colorScheme.onSurfaceVariant, BlendMode.srcIn),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,8 @@ class NewListSections extends StatelessWidget {
|
||||
iconPath: item.iconPath,
|
||||
badgeIconPath: item.badgeIconPath,
|
||||
trailingIconPath: item.trailingIconPath,
|
||||
secondaryLabel: item.secondaryLabel,
|
||||
subtitleColor: item.subtitleColor,
|
||||
onTap: tapHandlers[item.keyValue] ?? item.onTap,
|
||||
isFirstInSection: isFirst,
|
||||
isLastInSection: isLast,
|
||||
|
||||
@@ -1378,8 +1378,8 @@ abstract class SettingsStoreBase with Store {
|
||||
pinLength = defaultPinLength;
|
||||
}
|
||||
|
||||
final savedLanguageCode = sharedPreferences.getString(PreferencesKey.currentLanguageCode) ??
|
||||
await LanguageService.localeDetection();
|
||||
final savedLanguageCode =
|
||||
_offeredLanguageCode(sharedPreferences) ?? await LanguageService.localeDetection();
|
||||
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
||||
final bitcoinElectrumServerId =
|
||||
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
|
||||
@@ -1938,7 +1938,12 @@ abstract class SettingsStoreBase with Store {
|
||||
}
|
||||
pinCodeLength = pinLength;
|
||||
|
||||
languageCode = sharedPreferences.getString(PreferencesKey.currentLanguageCode) ?? languageCode;
|
||||
final restoredLanguageCode = _offeredLanguageCode(sharedPreferences);
|
||||
if (restoredLanguageCode == null) {
|
||||
await sharedPreferences.setString(PreferencesKey.currentLanguageCode, languageCode);
|
||||
} else {
|
||||
languageCode = restoredLanguageCode;
|
||||
}
|
||||
shouldShowYatPopup =
|
||||
sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? shouldShowYatPopup;
|
||||
shouldShowDEuroDisclaimer =
|
||||
@@ -2315,6 +2320,12 @@ abstract class SettingsStoreBase with Store {
|
||||
await _sharedPreferences.setString(key, serializedData);
|
||||
}
|
||||
|
||||
static String? _offeredLanguageCode(SharedPreferences sharedPreferences) {
|
||||
final code = sharedPreferences.getString(PreferencesKey.currentLanguageCode);
|
||||
|
||||
return LanguageService.list.containsKey(code) ? code : null;
|
||||
}
|
||||
|
||||
static Future<String?> _getDeviceName() async {
|
||||
String? deviceName = '';
|
||||
final deviceInfoPlugin = DeviceInfoPlugin();
|
||||
|
||||
@@ -118,7 +118,8 @@ class ExceptionHandler {
|
||||
|
||||
if (kDebugMode || kProfileMode) {
|
||||
if (_ignoreError(errorDetails.exception.toString()) ||
|
||||
_ignoreError(errorDetails.stack.toString())) {
|
||||
_ignoreError(errorDetails.stack.toString()) ||
|
||||
_flutterErrorIgnore(errorDetails)) {
|
||||
printV("(BELOW ERROR IS IGNORED AND WILL NOT TRIGGER POPUP IN PROD)");
|
||||
}
|
||||
FlutterError.presentError(errorDetails);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||
import 'package:cake_wallet/buy/buy_quote.dart';
|
||||
@@ -11,21 +12,26 @@ import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/entities/provider_types.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
||||
import 'package:cw_core/amount/money.dart';
|
||||
import 'package:cw_core/crypto_amount_format.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'buy_sell_view_model.g.dart';
|
||||
|
||||
enum BuySellPageMode { buy, sell }
|
||||
|
||||
class BuySellViewModel = BuySellViewModelBase with _$BuySellViewModel;
|
||||
|
||||
abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with Store {
|
||||
BuySellViewModelBase(
|
||||
AppStore appStore,
|
||||
) : _cryptoAmount = '',
|
||||
BuySellViewModelBase(AppStore appStore, {required this.mode, required this.fiatConversionStore})
|
||||
: _cryptoAmount = '',
|
||||
fiatAmount = '',
|
||||
cryptoCurrencyAddress = '',
|
||||
isCryptoCurrencyAddressEnabled = false,
|
||||
@@ -56,6 +62,8 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
||||
|
||||
late Timer bestRateSync;
|
||||
|
||||
final FiatConversionStore fiatConversionStore;
|
||||
|
||||
List<BuyProvider> get availableBuyProviders {
|
||||
final providerTypes = ProvidersHelper.getAvailableBuyProviderTypes();
|
||||
return providerTypes
|
||||
@@ -81,9 +89,27 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
||||
final formattedFiatAmount = double.tryParse(fiatAmount);
|
||||
final formattedCryptoAmount = double.tryParse(_cryptoAmount);
|
||||
|
||||
return isBuyAction
|
||||
return mode == BuySellPageMode.buy
|
||||
? formattedFiatAmount ?? 200.0
|
||||
: formattedCryptoAmount ?? (cryptoCurrency == CryptoCurrency.btc ? 0.001 : 1);
|
||||
: formattedCryptoAmount ?? (cryptoCurrency == CryptoCurrency.btc ? 0.01 : 1);
|
||||
}
|
||||
|
||||
// sets based on the absolute amout (from the fiat/charts api)
|
||||
// works even if you have no rates
|
||||
Future<void> setCryptoAmountFromFiat(String fiatAmount) async {
|
||||
if(fiatAmount.isEmpty) {
|
||||
await changeCryptoAmount(amount: "");
|
||||
return;
|
||||
}
|
||||
|
||||
if(fiatConversionStore.prices[cryptoCurrency] == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
await changeCryptoAmount(
|
||||
amount: (double.parse(fiatAmount) / (fiatConversionStore.prices[cryptoCurrency]!))
|
||||
.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
final AppStore _appStore;
|
||||
@@ -100,8 +126,7 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
||||
@observable
|
||||
List<FiatCurrency> fiatCurrencies;
|
||||
|
||||
@observable
|
||||
bool isBuyAction = true;
|
||||
final BuySellPageMode mode;
|
||||
|
||||
@observable
|
||||
List<BuyProvider> providerList;
|
||||
@@ -149,6 +174,100 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
||||
@observable
|
||||
bool skipIsReadyToTradeReaction = false;
|
||||
|
||||
@computed
|
||||
String? get maxFiatAmount {
|
||||
if ((sortedQuotes.isEmpty && sortedRecommendedQuotes.isEmpty) ||
|
||||
buySellQuotState is! BuySellQuotLoaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final allQuotes = sortedRecommendedQuotes.followedBy(sortedQuotes);
|
||||
|
||||
final maxAmount = allQuotes.fold<double>(0.0, (current, item) {
|
||||
final limitMax = item.limits?.max?.toDouble() ?? 0.0;
|
||||
return max(current, limitMax);
|
||||
});
|
||||
|
||||
return maxAmount.toStringAsFixed(2);
|
||||
}
|
||||
|
||||
Money? amountForQuote(Quote quote) => Money.trySafeParse(
|
||||
((double.tryParse(fiatAmount) ?? 0) / quote.rate).toStringAsFixed(min(20, cryptoCurrency.decimals)),
|
||||
cryptoCurrency);
|
||||
|
||||
Money? fiatAmountForQuote(Quote quote) {
|
||||
if(fiatConversionStore.prices[cryptoCurrency] == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Money.trySafeParse(
|
||||
(fiatConversionStore.prices[cryptoCurrency]! *
|
||||
(double.tryParse(amountForQuote(quote).toString())??0))
|
||||
.toStringAsFixed(2),
|
||||
fiatCurrency);
|
||||
}
|
||||
|
||||
// based on usd values, should have roughly equal worth (was done with ai though so it's subject to correction)
|
||||
static final Map<FiatCurrency, List<String>> _defaultAmountsMap = {
|
||||
FiatCurrency.amd: ["20000", "40000", "200000", "400000", "1000000"],
|
||||
FiatCurrency.ars: ["50000", "100000", "500000", "1000000", "2500000"],
|
||||
FiatCurrency.aud: ["100", "200", "1000", "2000", "5000"],
|
||||
FiatCurrency.bdt: ["5000", "10000", "50000", "100000", "250000"],
|
||||
FiatCurrency.bgn: ["100", "200", "1000", "2000", "5000"],
|
||||
FiatCurrency.brl: ["250", "500", "2500", "5000", "12500"],
|
||||
FiatCurrency.cad: ["50", "100", "500", "1000", "2500"],
|
||||
FiatCurrency.chf: ["50", "100", "500", "1000", "2500"],
|
||||
FiatCurrency.clp: ["50000", "100000", "500000", "1000000", "2500000"],
|
||||
FiatCurrency.cny: ["500", "1000", "5000", "10000", "25000"],
|
||||
FiatCurrency.cop: ["200000", "400000", "2000000", "4000000", "10000000"],
|
||||
FiatCurrency.czk: ["1000", "2000", "10000", "20000", "50000"],
|
||||
FiatCurrency.dkk: ["400", "800", "4000", "8000", "20000"],
|
||||
FiatCurrency.egp: ["2500", "5000", "25000", "50000", "125000"],
|
||||
FiatCurrency.eur: ["50", "100", "500", "1000", "2500"],
|
||||
FiatCurrency.gbp: ["50", "100", "500", "1000", "2500"],
|
||||
FiatCurrency.ghs: ["500", "1000", "5000", "10000", "25000"],
|
||||
FiatCurrency.gtq: ["400", "800", "4000", "8000", "20000"],
|
||||
FiatCurrency.hkd: ["400", "800", "4000", "8000", "20000"],
|
||||
FiatCurrency.hrk: ["400", "800", "4000", "8000", "20000"],
|
||||
FiatCurrency.huf: ["20000", "40000", "200000", "400000", "1000000"],
|
||||
FiatCurrency.idr: ["800000", "1600000", "8000000", "16000000", "40000000"],
|
||||
FiatCurrency.ils: ["200", "400", "2000", "4000", "10000"],
|
||||
FiatCurrency.inr: ["5000", "10000", "50000", "100000", "250000"],
|
||||
FiatCurrency.irr: ["2000000", "4000000", "20000000", "40000000", "100000000"],
|
||||
FiatCurrency.isk: ["7000", "14000", "70000", "140000", "350000"],
|
||||
FiatCurrency.jpy: ["10000", "20000", "100000", "200000", "500000"],
|
||||
FiatCurrency.kes: ["5000", "10000", "50000", "100000", "250000"],
|
||||
FiatCurrency.krw: ["50000", "100000", "500000", "1000000", "2500000"],
|
||||
FiatCurrency.mad: ["500", "1000", "5000", "10000", "25000"],
|
||||
FiatCurrency.mxn: ["1000", "2000", "10000", "20000", "50000"],
|
||||
FiatCurrency.myr: ["250", "500", "2500", "5000", "12500"],
|
||||
FiatCurrency.ngn: ["50000", "100000", "500000", "1000000", "2500000"],
|
||||
FiatCurrency.nok: ["500", "1000", "5000", "10000", "25000"],
|
||||
FiatCurrency.nzd: ["100", "200", "1000", "2000", "5000"],
|
||||
FiatCurrency.php: ["3000", "6000", "30000", "60000", "150000"],
|
||||
FiatCurrency.pkr: ["15000", "30000", "150000", "300000", "750000"],
|
||||
FiatCurrency.pln: ["200", "400", "2000", "4000", "10000"],
|
||||
FiatCurrency.ron: ["250", "500", "2500", "5000", "12500"],
|
||||
FiatCurrency.rub: ["5000", "10000", "50000", "100000", "250000"],
|
||||
FiatCurrency.sar: ["200", "400", "2000", "4000", "10000"],
|
||||
FiatCurrency.sek: ["500", "1000", "5000", "10000", "25000"],
|
||||
FiatCurrency.sgd: ["50", "100", "500", "1000", "2500"],
|
||||
FiatCurrency.thb: ["2000", "4000", "20000", "40000", "100000"],
|
||||
FiatCurrency.tur: ["1500", "3000", "15000", "30000", "75000"],
|
||||
FiatCurrency.twd: ["1500", "3000", "15000", "30000", "75000"],
|
||||
FiatCurrency.uah: ["2000", "4000", "20000", "40000", "100000"],
|
||||
FiatCurrency.usd: ["50", "100", "500", "1000", "2500"],
|
||||
FiatCurrency.vef: ["2000", "4000", "20000", "40000", "100000"],
|
||||
FiatCurrency.vnd: ["1000000", "2000000", "10000000", "20000000", "50000000"],
|
||||
FiatCurrency.zar: ["1000", "2000", "10000", "20000", "50000"],
|
||||
};
|
||||
|
||||
|
||||
// the fallback is just the usd values.
|
||||
// not great but this fallback shouldn't be triggered anyway
|
||||
List<String> get defaultAmounts =>
|
||||
_defaultAmountsMap[fiatCurrency] ?? ["50", "100", "500", "1000", "2500"];
|
||||
|
||||
@computed
|
||||
bool get isReadyToTrade {
|
||||
final hasSelectedQuote = selectedQuote != null;
|
||||
@@ -181,12 +300,6 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
||||
_initialize();
|
||||
}
|
||||
|
||||
@action
|
||||
void changeBuySellAction() {
|
||||
isBuyAction = !isBuyAction;
|
||||
_initialize();
|
||||
}
|
||||
|
||||
@action
|
||||
void changeFiatCurrency({required FiatCurrency currency}) {
|
||||
fiatCurrency = currency;
|
||||
@@ -200,12 +313,18 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
||||
isCryptoCurrencyAddressEnabled = !(cryptoCurrency == wallet.currency);
|
||||
}
|
||||
|
||||
@computed
|
||||
Iterable<CryptoCurrency> get activeWalletCurrencies => wallet.balance.keys;
|
||||
|
||||
@computed
|
||||
bool get hasMultipleCurrencies => activeWalletCurrencies.length > 1;
|
||||
|
||||
@action
|
||||
void changeCryptoCurrencyAddress(String address) => cryptoCurrencyAddress = address;
|
||||
|
||||
@action
|
||||
Future<void> changeFiatAmount({required String amount}) async {
|
||||
fiatAmount = amount;
|
||||
fiatAmount = amount.replaceAll(",", ".");
|
||||
|
||||
if (amount.isEmpty) {
|
||||
fiatAmount = '';
|
||||
@@ -214,16 +333,18 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
||||
}
|
||||
|
||||
if (!isReadyToTrade && !isBuySellQuoteFailed) {
|
||||
_cryptoAmount = S.current.fetching;
|
||||
_cryptoAmount = "...";
|
||||
return;
|
||||
} else if (isBuySellQuoteFailed) {
|
||||
_cryptoAmount = '';
|
||||
return;
|
||||
}
|
||||
|
||||
printV(bestRateQuote);
|
||||
if (bestRateQuote != null) {
|
||||
final enteredAmount = double.tryParse(fiatAmount.replaceAll(',', '.')) ?? 0;
|
||||
final amount = enteredAmount / bestRateQuote!.rate;
|
||||
printV(amount);
|
||||
|
||||
_cryptoAmount = amount.toString().withMaxDecimals(cryptoCurrency.decimals);
|
||||
} else {
|
||||
@@ -338,7 +459,7 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
||||
}
|
||||
|
||||
void _setProviders() =>
|
||||
providerList = isBuyAction ? availableBuyProviders : availableSellProviders;
|
||||
providerList = mode == BuySellPageMode.buy ? availableBuyProviders : availableSellProviders;
|
||||
|
||||
Future<void> _initialize() async {
|
||||
_setProviders();
|
||||
@@ -347,53 +468,52 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
||||
cryptoCurrencyAddress = _getInitialCryptoCurrencyAddress();
|
||||
paymentMethodState = InitialPaymentMethod();
|
||||
buySellQuotState = InitialBuySellQuotState();
|
||||
sortedRecommendedQuotes.clear();
|
||||
sortedQuotes.clear();
|
||||
paymentMethods.clear();
|
||||
await _getAvailablePaymentTypes();
|
||||
await calculateBestRate();
|
||||
}
|
||||
|
||||
String _getInitialCryptoCurrencyAddress() {
|
||||
if (cryptoCurrency == wallet.currency) {
|
||||
if ([CryptoCurrency.zec, CryptoCurrency.btc].contains(cryptoCurrency)) {
|
||||
return wallet.walletAddresses.addressForBuy;
|
||||
}
|
||||
|
||||
return wallet.walletAddresses.address;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
String _getInitialCryptoCurrencyAddress() => wallet.walletAddresses.addressForBuy;
|
||||
|
||||
@action
|
||||
Future<void> _getAvailablePaymentTypes() async {
|
||||
paymentMethodState = PaymentMethodLoading();
|
||||
selectedPaymentMethod = null;
|
||||
final result = await Future.wait(providerList.map((element) =>
|
||||
element.getAvailablePaymentTypes(fiatCurrency.title, cryptoCurrency, isBuyAction).timeout(
|
||||
Duration(seconds: 10),
|
||||
onTimeout: () => [],
|
||||
)));
|
||||
try{
|
||||
paymentMethodState = PaymentMethodLoading();
|
||||
selectedPaymentMethod = null;
|
||||
final result = await Future.wait(providerList.map((element) => element
|
||||
.getAvailablePaymentTypes(fiatCurrency.title, cryptoCurrency, mode == BuySellPageMode.buy)
|
||||
.timeout(
|
||||
Duration(seconds: 10),
|
||||
onTimeout: () => [],
|
||||
)));
|
||||
|
||||
final List<PaymentMethod> tempPaymentMethods = [];
|
||||
final List<PaymentMethod> tempPaymentMethods = [];
|
||||
|
||||
for (var methods in result) {
|
||||
for (var method in methods) {
|
||||
final alreadyExists = tempPaymentMethods.any((m) {
|
||||
return m.paymentMethodType == method.paymentMethodType;
|
||||
});
|
||||
for (var methods in result) {
|
||||
for (var method in methods) {
|
||||
final alreadyExists = tempPaymentMethods.any((m) {
|
||||
return m.paymentMethodType == method.paymentMethodType;
|
||||
});
|
||||
|
||||
if (!alreadyExists) {
|
||||
tempPaymentMethods.add(method);
|
||||
if (!alreadyExists) {
|
||||
tempPaymentMethods.add(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
paymentMethods = ObservableList<PaymentMethod>.of(tempPaymentMethods);
|
||||
paymentMethods = ObservableList<PaymentMethod>.of(tempPaymentMethods);
|
||||
|
||||
if (paymentMethods.isNotEmpty) {
|
||||
paymentMethods.insert(0, PaymentMethod.all());
|
||||
selectedPaymentMethod = paymentMethods.first;
|
||||
selectedPaymentMethod!.isSelected = true;
|
||||
paymentMethodState = PaymentMethodLoaded();
|
||||
} else {
|
||||
if (paymentMethods.isNotEmpty) {
|
||||
paymentMethods.insert(0, PaymentMethod.all());
|
||||
selectedPaymentMethod = paymentMethods.first;
|
||||
selectedPaymentMethod!.isSelected = true;
|
||||
paymentMethodState = PaymentMethodLoaded();
|
||||
} else {
|
||||
paymentMethodState = PaymentMethodFailed();
|
||||
}
|
||||
} catch(e) {
|
||||
paymentMethodState = PaymentMethodFailed();
|
||||
}
|
||||
}
|
||||
@@ -403,17 +523,22 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
||||
buySellQuotState = BuySellQuotLoading();
|
||||
|
||||
final List<BuyProvider> validProviders = providerList.where((provider) {
|
||||
if (isBuyAction) {
|
||||
return provider.supportedCryptoList
|
||||
.any((pair) => pair.from == cryptoCurrency && pair.to == fiatCurrency);
|
||||
if (mode == BuySellPageMode.buy) {
|
||||
return provider.supportedCryptoList.any((pair) =>
|
||||
pair.from.symbol == cryptoCurrency.symbol &&
|
||||
pair.from.tag == cryptoCurrency.tag &&
|
||||
pair.to.symbol == fiatCurrency.symbol);
|
||||
} else {
|
||||
return provider.supportedFiatList
|
||||
.any((pair) => pair.from == fiatCurrency && pair.to == cryptoCurrency);
|
||||
return provider.supportedFiatList.any((pair) =>
|
||||
pair.from.symbol == fiatCurrency.symbol &&
|
||||
pair.to.symbol == cryptoCurrency.symbol &&
|
||||
pair.to.tag == cryptoCurrency.tag);
|
||||
}
|
||||
}).toList();
|
||||
|
||||
if (validProviders.isEmpty) {
|
||||
buySellQuotState = BuySellQuotFailed();
|
||||
buySellQuotState = BuySellQuotFailed(
|
||||
errorMessage: S.current.no_buy_providers(cryptoCurrency.fullName??cryptoCurrency.title));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -423,7 +548,7 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
||||
fiatCurrency: fiatCurrency,
|
||||
amount: amount,
|
||||
paymentType: selectedPaymentMethod?.paymentMethodType,
|
||||
isBuyAction: isBuyAction,
|
||||
isBuyAction: mode == BuySellPageMode.buy,
|
||||
walletAddress: wallet.walletAddresses.address,
|
||||
customPaymentMethodType: selectedPaymentMethod?.customPaymentMethodType,
|
||||
)
|
||||
@@ -441,11 +566,12 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
||||
.toList();
|
||||
|
||||
if (validQuotes.isEmpty) {
|
||||
buySellQuotState = BuySellQuotFailed();
|
||||
buySellQuotState = BuySellQuotFailed(
|
||||
errorMessage: S.current.no_buy_providers(cryptoCurrency.fullName??cryptoCurrency.title));
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBuyAction) {
|
||||
if (mode == BuySellPageMode.buy) {
|
||||
validQuotes.sort((a, b) => b.payout.compareTo(a.payout));
|
||||
} else {
|
||||
validQuotes.sort((a, b) => a.payout.compareTo(b.payout));
|
||||
@@ -486,7 +612,7 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
||||
});
|
||||
|
||||
final Quote effectiveBestRateQuote = sortedRecommendedQuotes.reduce((a, b) {
|
||||
return isBuyAction
|
||||
return mode == BuySellPageMode.buy
|
||||
? a.rate < b.rate
|
||||
? a
|
||||
: b
|
||||
@@ -512,7 +638,7 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
|
||||
context: context,
|
||||
quote: selectedQuote!,
|
||||
amount: amount,
|
||||
isBuyAction: isBuyAction,
|
||||
isBuyAction: mode == BuySellPageMode.buy,
|
||||
cryptoCurrencyAddress: cryptoCurrencyAddress,
|
||||
);
|
||||
} catch (e) {
|
||||
|
||||
@@ -55,7 +55,7 @@ import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/utils/file.dart';
|
||||
import 'package:cw_core/encryption_file_utils.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
@@ -1515,7 +1515,8 @@ abstract class DashboardViewModelBase with Store {
|
||||
if (walletInfo.type == WalletType.bitcoin) {
|
||||
final password = await keyService.getWalletPassword(walletName: walletInfo.name);
|
||||
final path = await pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||
final jsonSource = await read(path: path, password: password);
|
||||
final encryption = encryptionFileUtilsFor(SettingsStoreBase.walletPasswordDirectInput);
|
||||
final jsonSource = await encryption.read(path: path, password: password);
|
||||
final data = json.decode(jsonSource) as Map;
|
||||
final mnemonic = data['mnemonic'] as String?;
|
||||
|
||||
|
||||
@@ -338,10 +338,21 @@ abstract class OutputBase with Store {
|
||||
? CryptoCurrency.btc
|
||||
: cryptoCurrencyHandler();
|
||||
|
||||
final price = _fiatConversationStore.prices[cryptoCurrency];
|
||||
if (price == null || !price.isFinite || price <= 0) {
|
||||
printV('_updateCryptoAmount: invalid fiat rate $price for $cryptoCurrency');
|
||||
cryptoAmount = '';
|
||||
return;
|
||||
}
|
||||
|
||||
final decimals = min(20, cryptoCurrencyHandler().decimals);
|
||||
final crypto = (double.parse(fiatAmount.replaceAll(',', '.')) /
|
||||
_fiatConversationStore.prices[cryptoCurrency]!)
|
||||
.toStringAsFixed(decimals);
|
||||
final rawCrypto = double.parse(fiatAmount.replaceAll(',', '.')) / price;
|
||||
if (!rawCrypto.isFinite) {
|
||||
cryptoAmount = '';
|
||||
return;
|
||||
}
|
||||
|
||||
final crypto = rawCrypto.toStringAsFixed(decimals);
|
||||
|
||||
if (cryptoAmount != crypto) cryptoAmount = crypto;
|
||||
} catch (e) {
|
||||
|
||||
@@ -54,6 +54,7 @@ import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_mod
|
||||
import 'package:cake_wallet/wownero/wownero.dart';
|
||||
import 'package:cake_wallet/zano/zano.dart';
|
||||
import 'package:cake_wallet/zcash/zcash.dart';
|
||||
import 'package:cw_core/amount/amount_sanitizer.dart';
|
||||
import 'package:cw_core/amount/money.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/currency_for_wallet_type.dart';
|
||||
@@ -882,7 +883,11 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
final fee = actualFee > 0 ? actualFee : 0.0005;
|
||||
|
||||
final fromCurrency = trade.from ?? CryptoCurrency.sol;
|
||||
final amount = Money.tryParse(trade.amount, fromCurrency) ?? Money.zero(fromCurrency);
|
||||
final amount = Money.tryParse(
|
||||
trade.amount.sanitized(),
|
||||
fromCurrency,
|
||||
strictParsing: false,
|
||||
) ?? Money.zero(fromCurrency);
|
||||
|
||||
pendingTransaction = await solana!.signAndPrepareJupiterSwapTransaction(
|
||||
wallet,
|
||||
@@ -920,8 +925,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
final bool isTradeTx = trade != null && provider != null;
|
||||
|
||||
if (isTradeTx) {
|
||||
final tradeAmountDouble = double.tryParse(trade.amount) ?? 0.0;
|
||||
if (tradeAmountDouble <= 0) throw Exception('Trade amount must be greater than 0');
|
||||
final tradeAmountMoney = Money.tryParse(
|
||||
trade.amount.sanitized(),
|
||||
trade.from ?? selectedCryptoCurrency,
|
||||
strictParsing: false,
|
||||
);
|
||||
if (tradeAmountMoney == null || tradeAmountMoney.sign <= 0) {
|
||||
throw Exception('Trade amount must be greater than 0');
|
||||
}
|
||||
final tradeAmountDouble = double.tryParse(tradeAmountMoney.toString()) ?? 0.0;
|
||||
|
||||
if (trade.isSendAll == true) {
|
||||
if (provider is NearIntentsExchangeProvider) {
|
||||
@@ -1146,7 +1158,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
final selectedToken = evm!.getERC20Currencies(wallet).firstWhereOrNull(
|
||||
(token) => token.title.toUpperCase() == selectedCryptoCurrency.title.toUpperCase(),
|
||||
);
|
||||
|
||||
wallet.transactionHistory.addOne(evm!.getTransactionInfo(
|
||||
id: pendingTransaction!.evmTxHashFromRawHex!,
|
||||
height: 0,
|
||||
|
||||
Reference in New Issue
Block a user