Merge branch 'flutter-upgrade' of https://github.com/cake-tech/cake_wallet into CW-182-allow-trusted-nodes

This commit is contained in:
OmarHatem
2022-11-03 21:34:20 +02:00
96 changed files with 1383 additions and 671 deletions
+2 -2
View File
@@ -17,8 +17,8 @@ class AnyPayPaymentInstruction {
return AnyPayPaymentInstruction(
type: obj['type'] as String,
requiredFeeRate: obj['requiredFeeRate'] as int,
txKey: obj['tx_key'] as bool,
txHash: obj['tx_hash'] as bool,
txKey: obj['tx_key'] as bool? ?? false,
txHash: obj['tx_hash'] as bool? ?? false,
outputs: outputs);
}
+4
View File
@@ -112,6 +112,10 @@ class CWBitcoin extends Bitcoin {
int formatterStringDoubleToBitcoinAmount(String amount)
=> stringDoubleToBitcoinAmount(amount);
@override
String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate)
=> (priority as BitcoinTransactionPriority).labelWithRate(rate);
@override
List<Unspent> getUnspents(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
+62 -26
View File
@@ -17,39 +17,25 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.ada:
return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$'
'|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$';
case CryptoCurrency.ape:
return '0x[0-9a-zA-Z]';
case CryptoCurrency.avaxc:
return '0x[0-9a-zA-Z]';
case CryptoCurrency.bch:
return '[0-9a-zA-Z]';
case CryptoCurrency.bnb:
return '[0-9a-zA-Z]';
case CryptoCurrency.btc:
return '^1[0-9a-zA-Z]{32}\$|^1[0-9a-zA-Z]{33}\$|^3[0-9a-zA-Z]{32}\$'
'|^3[0-9a-zA-Z]{33}\$|^bc1[0-9a-zA-Z]{39}\$|^bc1[0-9a-zA-Z]{59}\$';
case CryptoCurrency.dai:
return '[0-9a-zA-Z]';
case CryptoCurrency.dash:
return '[0-9a-zA-Z]';
case CryptoCurrency.eos:
return '[0-9a-zA-Z]';
case CryptoCurrency.eth:
return '0x[0-9a-zA-Z]';
case CryptoCurrency.ltc:
return '[0-9a-zA-Z]';
case CryptoCurrency.nano:
return '[0-9a-zA-Z_]';
case CryptoCurrency.trx:
return '[0-9a-zA-Z]';
case CryptoCurrency.usdc:
case CryptoCurrency.usdcpoly:
case CryptoCurrency.husd:
case CryptoCurrency.ape:
case CryptoCurrency.avaxc:
case CryptoCurrency.eth:
case CryptoCurrency.mana:
case CryptoCurrency.matic:
case CryptoCurrency.maticpoly:
case CryptoCurrency.mkr:
case CryptoCurrency.oxt:
case CryptoCurrency.paxg:
case CryptoCurrency.uni:
return '0x[0-9a-zA-Z]';
case CryptoCurrency.usdt:
return '[0-9a-zA-Z]';
case CryptoCurrency.usdterc20:
return '[0-9a-zA-Z]';
case CryptoCurrency.xlm:
return '[0-9a-zA-Z]';
case CryptoCurrency.xrp:
return '^[0-9a-zA-Z]{34}\$|^X[0-9a-zA-Z]{46}\$';
case CryptoCurrency.xhv:
@@ -67,6 +53,16 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.xnok:
case CryptoCurrency.xnzd:
case CryptoCurrency.xusd:
case CryptoCurrency.usdt:
case CryptoCurrency.usdterc20:
case CryptoCurrency.xlm:
case CryptoCurrency.trx:
case CryptoCurrency.dai:
case CryptoCurrency.dash:
case CryptoCurrency.eos:
case CryptoCurrency.ltc:
case CryptoCurrency.bch:
case CryptoCurrency.bnb:
return '[0-9a-zA-Z]';
case CryptoCurrency.hbar:
return '[0-9a-zA-Z.]';
@@ -74,6 +70,22 @@ class AddressValidator extends TextValidator {
return '^zs[0-9a-zA-Z]{75}';
case CryptoCurrency.zec:
return '^t1[0-9a-zA-Z]{33}\$|^t3[0-9a-zA-Z]{33}\$';
case CryptoCurrency.dcr:
return 'D[ksecS]([0-9a-zA-Z])+';
case CryptoCurrency.rvn:
return '[Rr]([1-9a-km-zA-HJ-NP-Z]){33}';
case CryptoCurrency.near:
return '[0-9a-f]{64}';
case CryptoCurrency.rune:
return 'thor1[0-9a-z]{38}';
case CryptoCurrency.scrt:
return 'secret1[0-9a-z]{38}';
case CryptoCurrency.stx:
return 'S[MP][0-9a-zA-Z]+';
case CryptoCurrency.kmd:
return 'R[0-9a-zA-Z]{33}';
case CryptoCurrency.pivx:
return 'D([1-9a-km-zA-HJ-NP-Z]){33}';
default:
return '[0-9a-zA-Z]';
}
@@ -160,6 +172,30 @@ class AddressValidator extends TextValidator {
return null;
case CryptoCurrency.zec:
return null;
case CryptoCurrency.kmd:
case CryptoCurrency.pivx:
case CryptoCurrency.rvn:
return [34];
case CryptoCurrency.dcr:
return [35];
case CryptoCurrency.stx:
return [40, 41, 42];
case CryptoCurrency.usdcpoly:
case CryptoCurrency.husd:
case CryptoCurrency.mana:
case CryptoCurrency.matic:
case CryptoCurrency.maticpoly:
case CryptoCurrency.mkr:
case CryptoCurrency.oxt:
case CryptoCurrency.paxg:
case CryptoCurrency.uni:
return [42];
case CryptoCurrency.rune:
return [43];
case CryptoCurrency.scrt:
return [45];
case CryptoCurrency.near:
return [64];
default:
return [];
}
-137
View File
@@ -1,137 +0,0 @@
import 'package:intl/intl.dart';
import 'package:cw_core/crypto_currency.dart';
class AmountConverter {
static const _moneroAmountLength = 12;
static const _moneroAmountDivider = 1000000000000;
static const _litecoinAmountDivider = 100000000;
static const _ethereumAmountDivider = 1000000000000000000;
static const _dashAmountDivider = 100000000;
static const _bitcoinCashAmountDivider = 100000000;
static const _bitcoinAmountDivider = 100000000;
static const _bitcoinAmountLength = 8;
static final _bitcoinAmountFormat = NumberFormat()
..maximumFractionDigits = _bitcoinAmountLength
..minimumFractionDigits = 1;
static final _moneroAmountFormat = NumberFormat()
..maximumFractionDigits = _moneroAmountLength
..minimumFractionDigits = 1;
static double amountIntToDouble(CryptoCurrency cryptoCurrency, int amount) {
switch (cryptoCurrency) {
case CryptoCurrency.xmr:
return _moneroAmountToDouble(amount);
case CryptoCurrency.btc:
return _bitcoinAmountToDouble(amount);
case CryptoCurrency.bch:
return _bitcoinCashAmountToDouble(amount);
case CryptoCurrency.dash:
return _dashAmountToDouble(amount);
case CryptoCurrency.eth:
return _ethereumAmountToDouble(amount);
case CryptoCurrency.ltc:
return _litecoinAmountToDouble(amount);
case CryptoCurrency.xhv:
case CryptoCurrency.xag:
case CryptoCurrency.xau:
case CryptoCurrency.xaud:
case CryptoCurrency.xbtc:
case CryptoCurrency.xcad:
case CryptoCurrency.xchf:
case CryptoCurrency.xcny:
case CryptoCurrency.xeur:
case CryptoCurrency.xgbp:
case CryptoCurrency.xjpy:
case CryptoCurrency.xnok:
case CryptoCurrency.xnzd:
case CryptoCurrency.xusd:
return _moneroAmountToDouble(amount);
default:
return 0.0;
}
}
static int amountStringToInt(CryptoCurrency cryptoCurrency, String amount) {
switch (cryptoCurrency) {
case CryptoCurrency.xmr:
return _moneroParseAmount(amount);
case CryptoCurrency.xhv:
case CryptoCurrency.xag:
case CryptoCurrency.xau:
case CryptoCurrency.xaud:
case CryptoCurrency.xbtc:
case CryptoCurrency.xcad:
case CryptoCurrency.xchf:
case CryptoCurrency.xcny:
case CryptoCurrency.xeur:
case CryptoCurrency.xgbp:
case CryptoCurrency.xjpy:
case CryptoCurrency.xnok:
case CryptoCurrency.xnzd:
case CryptoCurrency.xusd:
return _moneroParseAmount(amount);
default:
return 0;
}
}
static String amountIntToString(CryptoCurrency cryptoCurrency, int amount) {
switch (cryptoCurrency) {
case CryptoCurrency.xmr:
return _moneroAmountToString(amount);
case CryptoCurrency.btc:
return _bitcoinAmountToString(amount);
case CryptoCurrency.xhv:
case CryptoCurrency.xag:
case CryptoCurrency.xau:
case CryptoCurrency.xaud:
case CryptoCurrency.xbtc:
case CryptoCurrency.xcad:
case CryptoCurrency.xchf:
case CryptoCurrency.xcny:
case CryptoCurrency.xeur:
case CryptoCurrency.xgbp:
case CryptoCurrency.xjpy:
case CryptoCurrency.xnok:
case CryptoCurrency.xnzd:
case CryptoCurrency.xusd:
return _moneroAmountToString(amount);
default:
return '';
}
}
static double cryptoAmountToDouble({required num amount, required num divider}) =>
amount / divider;
static String _moneroAmountToString(int amount) => _moneroAmountFormat.format(
cryptoAmountToDouble(amount: amount, divider: _moneroAmountDivider));
static double _moneroAmountToDouble(int amount) =>
cryptoAmountToDouble(amount: amount, divider: _moneroAmountDivider);
static int _moneroParseAmount(String amount) =>
_moneroAmountFormat.parse(amount).toInt();
static String _bitcoinAmountToString(int amount) =>
_bitcoinAmountFormat.format(
cryptoAmountToDouble(amount: amount, divider: _bitcoinAmountDivider));
static double _bitcoinAmountToDouble(int amount) =>
cryptoAmountToDouble(amount: amount, divider: _bitcoinAmountDivider);
static int _doubleToBitcoinAmount(double amount) =>
(amount * _bitcoinAmountDivider).toInt();
static double _bitcoinCashAmountToDouble(int amount) =>
cryptoAmountToDouble(amount: amount, divider: _bitcoinCashAmountDivider);
static double _dashAmountToDouble(int amount) =>
cryptoAmountToDouble(amount: amount, divider: _dashAmountDivider);
static double _ethereumAmountToDouble(num amount) =>
cryptoAmountToDouble(amount: amount, divider: _ethereumAmountDivider);
static double _litecoinAmountToDouble(int amount) =>
cryptoAmountToDouble(amount: amount, divider: _litecoinAmountDivider);
}
+112 -43
View File
@@ -16,6 +16,7 @@ import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/wallet_types.g.dart';
import 'package:cake_backup/backup.dart' as cake_backup;
class BackupService {
BackupService(this._flutterSecureStorage, this._walletInfoSource,
@@ -23,9 +24,10 @@ class BackupService {
: _cipher = Cryptography.instance.chacha20Poly1305Aead(),
_correctWallets = <WalletInfo>[];
static const currentVersion = _v1;
static const currentVersion = _v2;
static const _v1 = 1;
static const _v2 = 2;
final Cipher _cipher;
final FlutterSecureStorage _flutterSecureStorage;
@@ -37,13 +39,16 @@ class BackupService {
Future<void> importBackup(Uint8List data, String password,
{String nonce = secrets.backupSalt}) async {
final version = getVersion(data);
final backupBytes = data.toList()..removeAt(0);
final backupData = Uint8List.fromList(backupBytes);
switch (version) {
case _v1:
final backupBytes = data.toList()..removeAt(0);
final backupData = Uint8List.fromList(backupBytes);
await _importBackupV1(backupData, password, nonce: nonce);
break;
case _v2:
await _importBackupV2(data, password);
break;
default:
break;
}
@@ -54,20 +59,26 @@ class BackupService {
switch (version) {
case _v1:
return await _exportBackupV1(password, nonce: nonce);
case _v2:
return await _exportBackupV2(password);
default:
throw Exception('Incorrect version: $version for exportBackup');
}
}
@Deprecated('Use v2 instead')
Future<Uint8List> _exportBackupV1(String password,
{String nonce = secrets.backupSalt}) async {
{String nonce = secrets.backupSalt}) async
=> throw Exception('Deprecated. Export for backups v1 is deprecated. Please use export v2.');
Future<Uint8List> _exportBackupV2(String password) async {
final zipEncoder = ZipFileEncoder();
final appDir = await getApplicationDocumentsDirectory();
final now = DateTime.now();
final tmpDir = Directory('${appDir.path}/~_BACKUP_TMP');
final archivePath = '${tmpDir.path}/backup_${now.toString()}.zip';
final fileEntities = appDir.listSync(recursive: false);
final keychainDump = await _exportKeychainDump(password, nonce: nonce);
final keychainDump = await _exportKeychainDumpV2(password);
final preferencesDump = await _exportPreferencesJSON();
final preferencesDumpFile = File('${tmpDir.path}/~_preferences_dump_TMP');
final keychainDumpFile = File('${tmpDir.path}/~_keychain_dump_TMP');
@@ -98,15 +109,13 @@ class BackupService {
final content = File(archivePath).readAsBytesSync();
tmpDir.deleteSync(recursive: true);
final encryptedData = await _encrypt(content, password, nonce);
return setVersion(encryptedData, currentVersion);
return await _encryptV2(content, password);
}
Future<void> _importBackupV1(Uint8List data, String password,
{required String nonce}) async {
final appDir = await getApplicationDocumentsDirectory();
final decryptedData = await _decrypt(data, password, nonce);
final decryptedData = await _decryptV1(data, password, nonce);
final zip = ZipDecoder().decodeBytes(decryptedData);
zip.files.forEach((file) {
@@ -123,7 +132,30 @@ class BackupService {
});
await _verifyWallets();
await _importKeychainDump(password, nonce: nonce);
await _importKeychainDumpV1(password, nonce: nonce);
await _importPreferencesDump();
}
Future<void> _importBackupV2(Uint8List data, String password) async {
final appDir = await getApplicationDocumentsDirectory();
final decryptedData = await _decryptV2(data, password);
final zip = ZipDecoder().decodeBytes(decryptedData);
zip.files.forEach((file) {
final filename = file.name;
if (file.isFile) {
final content = file.content as List<int>;
File('${appDir.path}/' + filename)
..createSync(recursive: true)
..writeAsBytesSync(content);
} else {
Directory('${appDir.path}/' + filename)..create(recursive: true);
}
});
await _verifyWallets();
await _importKeychainDumpV2(password);
await _importPreferencesDump();
}
@@ -258,12 +290,12 @@ class BackupService {
await preferencesFile.delete();
}
Future<void> _importKeychainDump(String password,
Future<void> _importKeychainDumpV1(String password,
{required String nonce,
String keychainSalt = secrets.backupKeychainSalt}) async {
final appDir = await getApplicationDocumentsDirectory();
final keychainDumpFile = File('${appDir.path}/~_keychain_dump');
final decryptedKeychainDumpFileData = await _decrypt(
final decryptedKeychainDumpFileData = await _decryptV1(
keychainDumpFile.readAsBytesSync(), '$keychainSalt$password', nonce);
final keychainJSON = json.decode(utf8.decode(decryptedKeychainDumpFileData))
as Map<String, dynamic>;
@@ -288,6 +320,35 @@ class BackupService {
keychainDumpFile.deleteSync();
}
Future<void> _importKeychainDumpV2(String password,
{String keychainSalt = secrets.backupKeychainSalt}) async {
final appDir = await getApplicationDocumentsDirectory();
final keychainDumpFile = File('${appDir.path}/~_keychain_dump');
final decryptedKeychainDumpFileData = await _decryptV2(
keychainDumpFile.readAsBytesSync(), '$keychainSalt$password');
final keychainJSON = json.decode(utf8.decode(decryptedKeychainDumpFileData))
as Map<String, dynamic>;
final keychainWalletsInfo = keychainJSON['wallets'] as List;
final decodedPin = keychainJSON['pin'] as String;
final pinCodeKey = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
final backupPasswordKey =
generateStoreKeyFor(key: SecretStoreKey.backupPassword);
final backupPassword = keychainJSON[backupPasswordKey] as String;
await _flutterSecureStorage.write(
key: backupPasswordKey, value: backupPassword);
keychainWalletsInfo.forEach((dynamic rawInfo) async {
final info = rawInfo as Map<String, dynamic>;
await importWalletKeychainInfo(info);
});
await _flutterSecureStorage.write(
key: pinCodeKey, value: encodedPinCode(pin: decodedPin));
keychainDumpFile.deleteSync();
}
Future<void> importWalletKeychainInfo(Map<String, dynamic> info) async {
final name = info['name'] as String;
final password = info['password'] as String;
@@ -295,9 +356,14 @@ class BackupService {
await _keyService.saveWalletPassword(walletName: name, password: password);
}
Future<Uint8List> _exportKeychainDump(String password,
@Deprecated('Use v2 instead')
Future<Uint8List> _exportKeychainDumpV1(String password,
{required String nonce,
String keychainSalt = secrets.backupKeychainSalt}) async {
String keychainSalt = secrets.backupKeychainSalt}) async
=> throw Exception('Deprecated');
Future<Uint8List> _exportKeychainDumpV2(String password,
{String keychainSalt = secrets.backupKeychainSalt}) async {
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
final encodedPin = await _flutterSecureStorage.read(key: key);
final decodedPin = decodedPinCode(pin: encodedPin!);
@@ -319,49 +385,48 @@ class BackupService {
'wallets': wallets,
backupPasswordKey: backupPassword
}));
final encrypted = await _encrypt(
Uint8List.fromList(data), '$keychainSalt$password', nonce);
final encrypted = await _encryptV2(
Uint8List.fromList(data), '$keychainSalt$password');
return encrypted;
}
Future<String> _exportPreferencesJSON() async {
// FIX-ME: Force unwrap
final preferences = <String, dynamic>{
PreferencesKey.currentWalletName:
_sharedPreferences.getString(PreferencesKey.currentWalletName)!,
_sharedPreferences.getString(PreferencesKey.currentWalletName),
PreferencesKey.currentNodeIdKey:
_sharedPreferences.getInt(PreferencesKey.currentNodeIdKey)!,
_sharedPreferences.getInt(PreferencesKey.currentNodeIdKey),
PreferencesKey.currentBalanceDisplayModeKey: _sharedPreferences
.getInt(PreferencesKey.currentBalanceDisplayModeKey)!,
.getInt(PreferencesKey.currentBalanceDisplayModeKey),
PreferencesKey.currentWalletType:
_sharedPreferences.getInt(PreferencesKey.currentWalletType)!,
_sharedPreferences.getInt(PreferencesKey.currentWalletType),
PreferencesKey.currentFiatCurrencyKey:
_sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey)!,
_sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey),
PreferencesKey.shouldSaveRecipientAddressKey: _sharedPreferences
.getBool(PreferencesKey.shouldSaveRecipientAddressKey)!,
.getBool(PreferencesKey.shouldSaveRecipientAddressKey),
PreferencesKey.isDarkThemeLegacy:
_sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy)!,
_sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy),
PreferencesKey.currentPinLength:
_sharedPreferences.getInt(PreferencesKey.currentPinLength)!,
_sharedPreferences.getInt(PreferencesKey.currentPinLength),
PreferencesKey.currentTransactionPriorityKeyLegacy: _sharedPreferences
.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy)!,
.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy),
PreferencesKey.allowBiometricalAuthenticationKey: _sharedPreferences
.getBool(PreferencesKey.allowBiometricalAuthenticationKey)!,
.getBool(PreferencesKey.allowBiometricalAuthenticationKey),
PreferencesKey.currentBitcoinElectrumSererIdKey: _sharedPreferences
.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey)!,
.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey),
PreferencesKey.currentLanguageCode:
_sharedPreferences.getString(PreferencesKey.currentLanguageCode)!,
_sharedPreferences.getString(PreferencesKey.currentLanguageCode),
PreferencesKey.displayActionListModeKey:
_sharedPreferences.getInt(PreferencesKey.displayActionListModeKey)!,
_sharedPreferences.getInt(PreferencesKey.displayActionListModeKey),
PreferencesKey.currentTheme:
_sharedPreferences.getInt(PreferencesKey.currentTheme)!,
_sharedPreferences.getInt(PreferencesKey.currentTheme),
PreferencesKey.currentDefaultSettingsMigrationVersion: _sharedPreferences
.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion)!,
.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion),
PreferencesKey.bitcoinTransactionPriority:
_sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority)!,
_sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority),
PreferencesKey.moneroTransactionPriority:
_sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!,
_sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority),
};
return json.encode(preferences);
@@ -374,16 +439,12 @@ class BackupService {
return Uint8List.fromList(bytes);
}
Future<Uint8List> _encrypt(
Uint8List data, String secretKeySource, String nonceBase64) async {
final secretKeyHash = await Cryptography.instance.sha256().hash(utf8.encode(secretKeySource));
final secretKey = SecretKey(secretKeyHash.bytes);
final nonce = base64.decode(nonceBase64).toList();
final box = await _cipher.encrypt(data.toList(), secretKey: secretKey, nonce: nonce);
return Uint8List.fromList(box.cipherText);
}
@Deprecated('Use v2 instead')
Future<Uint8List> _encryptV1(
Uint8List data, String secretKeySource, String nonceBase64) async
=> throw Exception('Deprecated');
Future<Uint8List> _decrypt(
Future<Uint8List> _decryptV1(
Uint8List data, String secretKeySource, String nonceBase64, {int macLength = 16}) async {
final secretKeyHash = await Cryptography.instance.sha256().hash(utf8.encode(secretKeySource));
final secretKey = SecretKey(secretKeyHash.bytes);
@@ -395,4 +456,12 @@ class BackupService {
final plainData = await _cipher.decrypt(box, secretKey: secretKey);
return Uint8List.fromList(plainData);
}
Future<Uint8List> _encryptV2(
Uint8List data, String passphrase) async
=> cake_backup.encrypt(passphrase, data, version: _v2);
Future<Uint8List> _decryptV2(
Uint8List data, String passphrase) async
=> cake_backup.decrypt(passphrase, data);
}
+13 -4
View File
@@ -4,6 +4,7 @@ import 'package:cake_wallet/entities/wake_lock.dart';
import 'package:cake_wallet/ionia/ionia_anypay.dart';
import 'package:cake_wallet/ionia/ionia_gift_card.dart';
import 'package:cake_wallet/ionia/ionia_tip.dart';
import 'package:cake_wallet/src/screens/buy/onramper_page.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_custom_redeem_page.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_gift_card_detail_page.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_more_options_page.dart';
@@ -419,16 +420,16 @@ Future setup(
getIt.get<MoneroAccountEditOrCreateViewModel>()));*/
getIt.registerFactoryParam<MoneroAccountEditOrCreateViewModel,
AccountListItem, void>(
(AccountListItem account, _) => MoneroAccountEditOrCreateViewModel(
AccountListItem?, void>(
(AccountListItem? account, _) => MoneroAccountEditOrCreateViewModel(
monero!.getAccountList(getIt.get<AppStore>().wallet!),
haven?.getAccountList(getIt.get<AppStore>().wallet!),
wallet: getIt.get<AppStore>().wallet!,
accountListItem: account));
getIt.registerFactoryParam<MoneroAccountEditOrCreatePage, AccountListItem,
getIt.registerFactoryParam<MoneroAccountEditOrCreatePage, AccountListItem?,
void>(
(AccountListItem account, _) => MoneroAccountEditOrCreatePage(
(AccountListItem? account, _) => MoneroAccountEditOrCreatePage(
moneroAccountCreationViewModel:
getIt.get<MoneroAccountEditOrCreateViewModel>(param1: account)));
@@ -482,6 +483,10 @@ Future setup(
getIt.registerFactory(
() => NodeCreateOrEditPage(getIt.get<NodeCreateOrEditViewModel>()));
getIt.registerFactory(() => OnRamperPage(
settingsStore: getIt.get<AppStore>().settingsStore,
wallet: getIt.get<AppStore>().wallet!));
getIt.registerFactory(() => ExchangeViewModel(
getIt.get<AppStore>().wallet!,
_tradesSource,
@@ -489,6 +494,7 @@ Future setup(
getIt.get<TradesStore>(),
getIt.get<AppStore>().settingsStore,
getIt.get<SharedPreferences>(),
getIt.get<SettingsViewModel>(),
));
getIt.registerFactory(() => ExchangeTradeViewModel(
@@ -587,6 +593,9 @@ Future setup(
getIt.registerFactory(() => BackupPage(getIt.get<BackupViewModel>()));
getIt.registerFactory(
() => EditBackupPasswordViewModel(getIt.get<FlutterSecureStorage>(), getIt.get<SecretStore>()));
getIt.registerFactory(
() => EditBackupPasswordPage(getIt.get<EditBackupPasswordViewModel>()));
+9 -11
View File
@@ -1,17 +1,15 @@
// import 'package:barcode_scan/barcode_scan.dart';
import 'package:barcode_scan2/barcode_scan2.dart';
var isQrScannerShown = false;
Future<String> presentQRScanner() async {
isQrScannerShown = true;
// FIX-ME: BarcodeScanner
throw Exception('Unimplemented');
// try {
// final result = await BarcodeScanner.scan();
// isQrScannerShown = false;
// return result.rawContent;
// } catch (e) {
// isQrScannerShown = false;
// rethrow;
// }
try {
final result = await BarcodeScanner.scan();
isQrScannerShown = false;
return result.rawContent;
} catch (e) {
isQrScannerShown = false;
rethrow;
}
}
+28 -14
View File
@@ -5,36 +5,50 @@ part 'template.g.dart';
@HiveType(typeId: Template.typeId)
class Template extends HiveObject {
Template({
required this.name,
required this.isCurrencySelected,
required this.address,
required this.cryptoCurrency,
required this.amount,
required this.fiatCurrency,
required this.amountFiat});
required this.nameRaw,
required this.isCurrencySelectedRaw,
required this.addressRaw,
required this.cryptoCurrencyRaw,
required this.amountRaw,
required this.fiatCurrencyRaw,
required this.amountFiatRaw});
static const typeId = 6;
static const boxName = 'Template';
@HiveField(0)
String name;
String? nameRaw;
@HiveField(1)
String address;
String? addressRaw;
@HiveField(2)
String cryptoCurrency;
String? cryptoCurrencyRaw;
@HiveField(3)
String amount;
String? amountRaw;
@HiveField(4)
String fiatCurrency;
String? fiatCurrencyRaw;
@HiveField(5)
bool isCurrencySelected;
bool? isCurrencySelectedRaw;
@HiveField(6)
String amountFiat;
String? amountFiatRaw;
bool get isCurrencySelected => isCurrencySelectedRaw ?? false;
String get fiatCurrency => fiatCurrencyRaw ?? '';
String get amountFiat => amountFiatRaw ?? '';
String get name => nameRaw ?? '';
String get address => addressRaw ?? '';
String get cryptoCurrency => cryptoCurrencyRaw ?? '';
String get amount => amountRaw ?? '';
}
+24 -12
View File
@@ -5,32 +5,44 @@ part 'exchange_template.g.dart';
@HiveType(typeId: ExchangeTemplate.typeId)
class ExchangeTemplate extends HiveObject {
ExchangeTemplate({
required this.amount,
required this.depositCurrency,
required this.receiveCurrency,
required this.provider,
required this.depositAddress,
required this.receiveAddress
required this.amountRaw,
required this.depositCurrencyRaw,
required this.receiveCurrencyRaw,
required this.providerRaw,
required this.depositAddressRaw,
required this.receiveAddressRaw
});
static const typeId = 7;
static const boxName = 'ExchangeTemplate';
@HiveField(0)
String amount;
String? amountRaw;
@HiveField(1)
String depositCurrency;
String? depositCurrencyRaw;
@HiveField(2)
String receiveCurrency;
String? receiveCurrencyRaw;
@HiveField(3)
String provider;
String? providerRaw;
@HiveField(4)
String depositAddress;
String? depositAddressRaw;
@HiveField(5)
String receiveAddress;
String? receiveAddressRaw;
String get amount => amountRaw ?? '';
String get depositCurrency => depositCurrencyRaw ?? '';
String get receiveCurrency => receiveCurrencyRaw ?? '';
String get provider => providerRaw ?? '';
String get depositAddress => depositAddressRaw ?? '';
String get receiveAddress => receiveAddressRaw ?? '';
}
@@ -1,5 +1,5 @@
import 'dart:convert';
import 'package:cake_wallet/core/amount_converter.dart';
import 'package:cw_core/amount_converter.dart';
import 'package:hive/hive.dart';
import 'package:cake_wallet/exchange/trade_not_found_exeption.dart';
import 'package:flutter/foundation.dart';
@@ -16,16 +16,7 @@ import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
class SideShiftExchangeProvider extends ExchangeProvider {
SideShiftExchangeProvider()
: super(
pairList: CryptoCurrency.all
.where((i) => i != CryptoCurrency.xhv)
.map((i) => CryptoCurrency.all
.where((i) => i != CryptoCurrency.xhv)
.map((k) => ExchangePair(from: i, to: k, reverse: true))
.where((c) => c != null))
.expand((i) => i)
.toList());
SideShiftExchangeProvider() : super(pairList: _supportedPairs());
static const affiliateId = secrets.sideShiftAffiliateId;
static const apiBaseUrl = 'https://sideshift.ai/api';
@@ -34,6 +25,35 @@ class SideShiftExchangeProvider extends ExchangeProvider {
static const quotePath = '/v1/quotes';
static const permissionPath = '/v1/permissions';
static const List<CryptoCurrency> _notSupported = [
CryptoCurrency.xhv,
CryptoCurrency.dcr,
CryptoCurrency.husd,
CryptoCurrency.kmd,
CryptoCurrency.mkr,
CryptoCurrency.near,
CryptoCurrency.oxt,
CryptoCurrency.paxg,
CryptoCurrency.pivx,
CryptoCurrency.rune,
CryptoCurrency.rvn,
CryptoCurrency.scrt,
CryptoCurrency.stx,
];
static List<ExchangePair> _supportedPairs() {
final supportedCurrencies = CryptoCurrency.all
.where((element) => !_notSupported.contains(element))
.toList();
return supportedCurrencies
.map((i) => supportedCurrencies
.map((k) => ExchangePair(from: i, to: k, reverse: true))
.where((c) => c != null))
.expand((i) => i)
.toList();
}
@override
ExchangeProviderDescription get description =>
ExchangeProviderDescription.sideShift;
@@ -270,6 +290,14 @@ class SideShiftExchangeProvider extends ExchangeProvider {
return currency.tag!.toLowerCase();
case CryptoCurrency.usdterc20:
return 'usdtErc20';
case CryptoCurrency.usdttrc20:
return 'usdtTrc20';
case CryptoCurrency.usdcpoly:
return 'usdcpolygon';
case CryptoCurrency.usdcsol:
return 'usdcsol';
case CryptoCurrency.maticpoly:
return 'polygon';
default:
return currency.title.toLowerCase();
}
@@ -17,8 +17,11 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
SimpleSwapExchangeProvider()
: super(
pairList: CryptoCurrency.all
.map((i) =>
CryptoCurrency.all.map((k) => ExchangePair(from: i, to: k, reverse: true)).where((c) => c != null))
.where((i) => i != CryptoCurrency.zaddr)
.map((i) => CryptoCurrency.all
.where((i) => i != CryptoCurrency.zaddr)
.map((k) => ExchangePair(from: i, to: k, reverse: true))
.where((c) => c != null))
.expand((i) => i)
.toList());
@@ -32,7 +35,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
@override
ExchangeProviderDescription get description =>
ExchangeProviderDescription.simpleSwap;
@override
Future<double> calculateAmount(
{required CryptoCurrency from,
@@ -56,9 +59,8 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
final uri = Uri.https(apiAuthority, getEstimatePath, params);
final response = await get(uri);
if (response.body == null) return 0.00;
if (response.body == null || response.body == "null") return 0.00;
final data = json.decode(response.body) as String;
return double.parse(data);
} catch (_) {
return 0.00;
@@ -76,7 +78,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
@override
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
final _request = request as SimpleSwapRequest;
final headers = {
final headers = {
'Content-Type': 'application/json'};
final params = <String, String>{
'api_key': apiKey,
@@ -108,7 +110,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
final id = responseJSON['id'] as String;
final inputAddress = responseJSON['address_from'] as String;
final settleAddress = responseJSON['user_refund_address'] as String;
final extraId = responseJSON['extra_id_from'] as String?;
return Trade(
id: id,
provider: description,
@@ -116,6 +118,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
to: _request.to,
inputAddress: inputAddress,
refundAddress: settleAddress,
extraId: extraId,
state: TradeState.created,
amount: _request.amount,
createdAt: DateTime.now(),
@@ -151,8 +154,8 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final min = responseJSON['min'] != null ? double.tryParse(responseJSON['min'] as String) : null;
final max = responseJSON['max'] != null ? double.parse(responseJSON['max'] as String) : null;
final min = double.tryParse(responseJSON['min'] as String? ?? '');
final max = double.tryParse(responseJSON['max'] as String? ?? '');
return Limits(min: min, max: max);
}
@@ -185,6 +188,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
final to = CryptoCurrency.fromString(toCurrency);
final inputAddress = responseJSON['address_from'] as String;
final expectedSendAmount = responseJSON['expected_amount'].toString();
final extraId = responseJSON['extra_id_from'] as String?;
final status = responseJSON['status'] as String;
final state = TradeState.deserialize(raw: status);
@@ -192,6 +196,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
id: id,
from: from,
to: to,
extraId: extraId,
provider: description,
inputAddress: inputAddress,
amount: expectedSendAmount,
@@ -217,7 +222,13 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
case CryptoCurrency.bnb:
return currency.tag!.toLowerCase();
case CryptoCurrency.usdterc20:
return 'usdterc';
return 'usdterc20';
case CryptoCurrency.usdttrc20:
return 'usdttrc20';
case CryptoCurrency.usdcpoly:
return 'usdcpoly';
case CryptoCurrency.usdcsol:
return 'usdcspl';
default:
return currency.title.toLowerCase();
}
+1 -1
View File
@@ -166,7 +166,7 @@ class CWHaven extends Haven {
@override
TransactionPriority getDefaultTransactionPriority() {
return MoneroTransactionPriority.slow;
return MoneroTransactionPriority.automatic;
}
@override
+2 -2
View File
@@ -130,7 +130,7 @@ Future<void> main() async {
secureStorage: secureStorage,
initialMigrationVersion: 17);
runApp(App());
} catch (e) {
} catch (e, stacktrace) {
runApp(MaterialApp(
debugShowCheckedModeBanner: true,
home: Scaffold(
@@ -138,7 +138,7 @@ Future<void> main() async {
margin:
EdgeInsets.only(top: 50, left: 20, right: 20, bottom: 20),
child: Text(
'Error:\n${e.toString()}',
'Error:\n${e.toString()}\nStacktrace: $stacktrace',
style: TextStyle(fontSize: 22),
)))));
}
+1 -1
View File
@@ -167,7 +167,7 @@ class CWMonero extends Monero {
@override
TransactionPriority getDefaultTransactionPriority() {
return MoneroTransactionPriority.slow;
return MoneroTransactionPriority.automatic;
}
@override
+5 -1
View File
@@ -3,6 +3,7 @@ import 'package:cake_wallet/buy/order.dart';
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
import 'package:cake_wallet/src/screens/buy/onramper_page.dart';
import 'package:cake_wallet/src/screens/buy/pre_order_page.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_cards_page.dart';
import 'package:cake_wallet/src/screens/ionia/cards/ionia_account_page.dart';
@@ -293,7 +294,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.accountCreation:
return CupertinoPageRoute<String>(
builder: (_) => getIt.get<MoneroAccountEditOrCreatePage>(
param1: settings.arguments as AccountListItem));
param1: settings.arguments as AccountListItem?));
case Routes.addressBook:
return MaterialPageRoute<void>(
@@ -471,6 +472,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
param1: paymentInfo,
param2: commitedInfo));
case Routes.onramperPage:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<OnRamperPage>());
default:
return MaterialPageRoute<void>(
builder: (_) => Scaffold(
+1
View File
@@ -76,4 +76,5 @@ class Routes {
static const ioniaPaymentStatusPage = '/ionia_payment_status_page';
static const ioniaMoreOptionsPage = '/ionia_more_options_page';
static const ioniaCustomRedeemPage = '/ionia_custom_redeem_page';
static const onramperPage = '/onramper';
}
+39 -38
View File
@@ -1,5 +1,5 @@
import 'package:another_flushbar/flushbar.dart';
import 'package:cake_wallet/utils/show_bar.dart';
// import 'package:flushbar/flushbar.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
@@ -31,9 +31,8 @@ class AuthPageState extends State<AuthPage> {
final _backArrowImageDarkTheme =
Image.asset('assets/images/close_button.png');
ReactionDisposer? _reaction;
// FIX-ME: replace Flushbar
// Flushbar<void>? _authBar;
// Flushbar<void>? _progressBar;
Flushbar<void>? _authBar;
Flushbar<void>? _progressBar;
@override
void initState() {
@@ -48,39 +47,34 @@ class AuthPageState extends State<AuthPage> {
if (state is IsExecutingState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
// FIX-ME: Changes related to flutter upgreade.
// Could be incorrect value for duration of auth bar
// _authBar =
// createBar<void>(S.of(context).authentication, duration: Duration())
// ..show(context);
// null duration to make it indefinite until its disposed
_authBar =
createBar<void>(S.of(context).authentication, duration: null)
..show(context);
});
}
if (state is FailureState) {
print('X');
print(state.error);
WidgetsBinding.instance.addPostFrameCallback((_) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
_pinCodeKey.currentState?.clear();
// _authBar?.dismiss();
dismissFlushBar(_authBar);
showBar<void>(
context, S.of(context).failed_authentication(state.error));
if (widget.onAuthenticationFinished != null) {
widget.onAuthenticationFinished(false, this);
}
widget.onAuthenticationFinished(false, this);
});
}
if (state is AuthenticationBanned) {
WidgetsBinding.instance.addPostFrameCallback((_) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
_pinCodeKey.currentState?.clear();
// _authBar?.dismiss();
dismissFlushBar(_authBar);
showBar<void>(
context, S.of(context).failed_authentication(state.error));
if (widget.onAuthenticationFinished != null) {
widget.onAuthenticationFinished(false, this);
}
widget.onAuthenticationFinished(false, this);
});
}
});
@@ -102,26 +96,32 @@ class AuthPageState extends State<AuthPage> {
}
void changeProcessText(String text) {
// _authBar?.dismiss();
// FIX-ME: Changes related to flutter upgreade.
// Could be incorrect value for duration of auth bar
// _progressBar = createBar<void>(text, duration: Duration())
// ..show(_key.currentContext);
dismissFlushBar(_authBar);
_progressBar = createBar<void>(text, duration: null)
..show(_key.currentContext!);
}
void hideProgressText() {
// _progressBar?.dismiss();
// _progressBar = null;
dismissFlushBar(_progressBar);
_progressBar = null;
}
void close() {
Future<void> close({String? route}) async {
if (_key.currentContext == null) {
throw Exception('Key context is null. Should be not happened');
}
// _authBar?.dismiss();
// _progressBar?.dismiss();
Navigator.of(_key.currentContext!).pop();
WidgetsBinding.instance.addPostFrameCallback((_) {
dismissFlushBar(_authBar);
dismissFlushBar(_progressBar);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (route != null) {
Navigator.of(_key.currentContext!).pushReplacementNamed(route);
} else {
Navigator.of(_key.currentContext!).pop();
}
});
});
}
@override
@@ -135,14 +135,9 @@ class AuthPageState extends State<AuthPage> {
child: SizedBox(
height: 37,
width: 37,
child: ButtonTheme(
minWidth: double.minPositive,
child: TextButton(
//highlightColor: Colors.transparent,
//splashColor: Colors.transparent,
//padding: EdgeInsets.all(0),
onPressed: () => Navigator.of(context).pop(),
child: _backArrowImageDarkTheme),
child: InkWell(
onTap: () => Navigator.of(context).pop(),
child: _backArrowImageDarkTheme,
),
))
: Container(),
@@ -152,4 +147,10 @@ class AuthPageState extends State<AuthPage> {
body: PinCode((pin, _) => widget.authViewModel.auth(password: pin),
(_) => null, widget.authViewModel.pinLength, false, _pinCodeKey));
}
void dismissFlushBar(Flushbar<dynamic>? bar) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
await bar?.dismiss();
});
}
}
+20 -10
View File
@@ -1,10 +1,10 @@
import 'dart:io';
import 'package:cake_wallet/palette.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
// import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:share_plus/share_plus.dart';
import 'package:cross_file/cross_file.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
@@ -103,12 +103,14 @@ class BackupPage extends BasePage {
Navigator.of(dialogContext).pop();
final backup = await backupViewModelBase.exportBackup();
if (backup == null) {
return;
}
if (Platform.isAndroid) {
onExportAndroid(context, backup!);
onExportAndroid(context, backup);
} else {
// FIX-ME: Share esys_flutter_share.dart
// await Share.file(S.of(context).backup_file, backup.name,
// backup.content, 'application/*');
await share(backup);
}
},
actionLeftButton: () => Navigator.of(dialogContext).pop());
@@ -136,12 +138,20 @@ class BackupPage extends BasePage {
backup.name, backup.content);
Navigator.of(dialogContext).pop();
},
actionLeftButton: () {
actionLeftButton: () async {
Navigator.of(dialogContext).pop();
// FIX-ME: Share esys_flutter_share.dart
// Share.file(S.of(context).backup_file, backup.name,
// backup.content, 'application/*');
await share(backup);
});
});
}
Future<void> share(BackupExportFile backup) async {
const mimeType = 'application/*';
final path = await backupViewModelBase.saveBackupFileLocally(backup);
await Share.shareXFiles(<XFile>[XFile(
path,
name: backup.name,
mimeType: mimeType)]);
await backupViewModelBase.removeBackupFileLocally(backup);
}
}
+86
View File
@@ -0,0 +1,86 @@
import 'dart:io';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
class OnRamperPage extends BasePage {
OnRamperPage({
required this.settingsStore,
required this.wallet});
final SettingsStore settingsStore;
final WalletBase wallet;
@override
String get title => S.current.buy;
@override
Widget body(BuildContext context) {
final darkMode = Theme.of(context).brightness == Brightness.dark;
return OnRamperPageBody(
settingsStore: settingsStore,
wallet: wallet,
darkMode: darkMode,
backgroundColor: darkMode
? backgroundDarkColor
: backgroundLightColor,
supportSell: false,
supportSwap: false);
}
}
class OnRamperPageBody extends StatefulWidget {
OnRamperPageBody({
required this.settingsStore,
required this.wallet,
required this.darkMode,
required this.supportSell,
required this.supportSwap,
required this.backgroundColor});
static const baseUrl = 'widget.onramper.com';
final SettingsStore settingsStore;
final WalletBase wallet;
final Color backgroundColor;
final bool darkMode;
final bool supportSell;
final bool supportSwap;
Uri get uri
=> Uri.https(
baseUrl,
'',
<String, dynamic>{
'apiKey': secrets.onramperApiKey,
'defaultCrypto': wallet.currency.title,
'defaultFiat': settingsStore.fiatCurrency.title,
'wallets': '${wallet.currency.title}:${wallet.walletAddresses.address}',
'darkMode': darkMode.toString(),
'supportSell': supportSell.toString(),
'supportSwap': supportSwap.toString()});
@override
OnRamperPageBodyState createState() => OnRamperPageBodyState();
}
class OnRamperPageBodyState extends State<OnRamperPageBody> {
OnRamperPageBodyState();
@override
void initState() {
super.initState();
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
}
@override
Widget build(BuildContext context) {
return WebView(
initialUrl: widget.uri.toString(),
backgroundColor: widget.backgroundColor,
javascriptMode: JavascriptMode.unrestricted);
}
}
+10 -11
View File
@@ -80,7 +80,7 @@ class PreOrderPage extends BasePage {
return KeyboardActions(
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).accentTextTheme!.bodyText1!
keyboardBarColor: Theme.of(context).accentTextTheme.bodyText1!
.backgroundColor!,
nextFocus: false,
actions: [
@@ -102,10 +102,9 @@ class PreOrderPage extends BasePage {
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24)),
gradient: LinearGradient(colors: [
Theme.of(context).primaryTextTheme!.subtitle1!.color!,
Theme.of(context).primaryTextTheme.subtitle1!.color!,
Theme.of(context)
.primaryTextTheme!
.subtitle1!
.primaryTextTheme.subtitle1!
.decorationColor!,
], begin: Alignment.topLeft, end: Alignment.bottomRight),
),
@@ -161,11 +160,11 @@ class PreOrderPage extends BasePage {
),
),
hintText: '0.00',
borderColor: Theme.of(context).primaryTextTheme!.bodyText1!.decorationColor!,
borderColor: Theme.of(context).primaryTextTheme.bodyText1!.decorationColor!,
borderWidth: 0.5,
textStyle: TextStyle(fontSize: 36, fontWeight: FontWeight.w500, color: Colors.white),
placeholderTextStyle: TextStyle(
color: Theme.of(context).primaryTextTheme!.headline5!.decorationColor!,
color: Theme.of(context).primaryTextTheme.headline5!.decorationColor!,
fontWeight: FontWeight.w500,
fontSize: 36,
),
@@ -180,7 +179,7 @@ class PreOrderPage extends BasePage {
S.of(context).buy_with + ':',
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).primaryTextTheme!.headline6!.color!,
color: Theme.of(context).primaryTextTheme.headline6!.color!,
fontSize: 18,
fontWeight: FontWeight.bold
),
@@ -197,11 +196,11 @@ class PreOrderPage extends BasePage {
double achAmount;
int minAmount;
if (snapshot.hasData) {
if (snapshot.hasData && snapshot.data != null) {
sourceAmount = snapshot.data!.sourceAmount;
destAmount = snapshot.data!.destAmount;
minAmount = snapshot.data!.minAmount;
achAmount = snapshot.data!.achSourceAmount!;
achAmount = snapshot.data!.achSourceAmount ?? 0;
} else {
sourceAmount = 0.0;
destAmount = 0.0;
@@ -215,7 +214,7 @@ class PreOrderPage extends BasePage {
child: Observer(builder: (_) {
return BuyListItem(
selectedProvider:
buyViewModel.selectedProvider!,
buyViewModel.selectedProvider,
provider: item.provider,
sourceAmount: sourceAmount,
sourceCurrency: buyViewModel.fiatCurrency,
@@ -247,7 +246,7 @@ class PreOrderPage extends BasePage {
? S.of(context).buy
: S.of(context).buy_with +
' ${buyViewModel.selectedProvider!.description.title}',
color: Theme.of(context).accentTextTheme!.bodyText1!.color!,
color: Theme.of(context).accentTextTheme.bodyText1!.color!,
textColor: Colors.white,
isLoading: buyViewModel.isRunning,
isDisabled: (buyViewModel.selectedProvider == null) ||
@@ -18,7 +18,7 @@ class BuyListItem extends StatelessWidget {
this.onTap
});
final BuyProvider selectedProvider;
final BuyProvider? selectedProvider;
final BuyProvider provider;
final double sourceAmount;
final FiatCurrency sourceCurrency;
+44 -37
View File
@@ -1,4 +1,5 @@
import 'package:cake_wallet/entities/contact_base.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
@@ -34,12 +35,12 @@ class ContactListPage extends BasePage {
height: 32.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).accentTextTheme!.caption!.color!),
color: Theme.of(context).accentTextTheme.caption!.color!),
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Icon(Icons.add,
color: Theme.of(context).primaryTextTheme!.headline6!.color!,
color: Theme.of(context).primaryTextTheme.headline6!.color!,
size: 22.0),
ButtonTheme(
minWidth: 32.0,
@@ -66,9 +67,9 @@ class ContactListPage extends BasePage {
return CollapsibleSectionList(
context: context,
sectionCount: 2,
themeColor: Theme.of(context).primaryTextTheme!.headline6!.color!,
themeColor: Theme.of(context).primaryTextTheme.headline6!.color!,
dividerThemeColor:
Theme.of(context).primaryTextTheme!.caption!.decorationColor!,
Theme.of(context).primaryTextTheme.caption!.decorationColor!,
sectionTitleBuilder: (_, int sectionIndex) {
var title = 'Contacts';
@@ -91,37 +92,13 @@ class ContactListPage extends BasePage {
final contact = contactListViewModel.contacts[index];
final content = generateRaw(context, contact);
// FIX-ME: Slidable
return content;
// return !isEditable
// ? content
// : Slidable(
// key: Key('${contact.key}'),
// actionPane: SlidableDrawerActionPane(),
// child: content,
// secondaryActions: <Widget>[
// IconSlideAction(
// caption: S.of(context).edit,
// color: Colors.blue,
// icon: Icons.edit,
// onTap: () async => await Navigator.of(context)
// .pushNamed(Routes.addressBookAddContact,
// arguments: contact),
// ),
// IconSlideAction(
// caption: S.of(context).delete,
// color: Colors.red,
// icon: CupertinoIcons.delete,
// onTap: () async {
// final isDelete =
// await showAlertDialog(context) ?? false;
// if (isDelete) {
// await contactListViewModel.delete(contact);
// }
// },
// ),
// ]);
return !isEditable
? content
: Slidable(
key: Key('${contact.key}'),
endActionPane: _actionPane(context, contact),
child: content,
);
},
);
},
@@ -141,7 +118,7 @@ class ContactListPage extends BasePage {
final isCopied = await showNameAndAddressDialog(
context, contact.name, contact.address);
if (isCopied != null && isCopied) {
if (isCopied) {
await Clipboard.setData(ClipboardData(text: contact.address));
await showBar<void>(context, S.of(context).copied_to_clipboard);
}
@@ -165,7 +142,7 @@ class ContactListPage extends BasePage {
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: Theme.of(context).primaryTextTheme!.headline6!.color!),
color: Theme.of(context).primaryTextTheme.headline6!.color!),
),
)
)
@@ -266,4 +243,34 @@ class ContactListPage extends BasePage {
actionLeftButton: () => Navigator.of(context).pop(false));
}) ?? false;
}
ActionPane _actionPane(BuildContext context, ContactRecord contact) => ActionPane(
motion: const ScrollMotion(),
extentRatio: 0.4,
children: [
SlidableAction(
onPressed: (_) async => await Navigator.of(context)
.pushNamed(Routes.addressBookAddContact,
arguments: contact),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
icon: Icons.edit,
label: S.of(context).edit,
),
SlidableAction(
onPressed: (_) async {
final isDelete =
await showAlertDialog(context);
if (isDelete) {
await contactListViewModel.delete(contact);
}
},
backgroundColor: Colors.red,
foregroundColor: Colors.white,
icon: CupertinoIcons.delete,
label: S.of(context).delete,
),
],
);
}
@@ -271,10 +271,10 @@ class DashboardPage extends BasePage {
switch (walletType) {
case WalletType.bitcoin:
Navigator.of(context).pushNamed(Routes.preOrder);
Navigator.of(context).pushNamed(Routes.onramperPage);
break;
case WalletType.litecoin:
Navigator.of(context).pushNamed(Routes.preOrder);
Navigator.of(context).pushNamed(Routes.onramperPage);
break;
default:
await showPopUp<void>(
+16 -2
View File
@@ -42,8 +42,7 @@ class WalletMenu {
Navigator.of(context).pushNamed(Routes.auth,
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
if (isAuthenticatedSuccessfully) {
auth.close();
Navigator.of(auth.context).pushNamed(Routes.showKeys);
auth.close(route: Routes.showKeys);
}
});
}),
@@ -52,6 +51,21 @@ class WalletMenu {
image: Image.asset('assets/images/open_book_menu.png',
height: 16, width: 16),
handler: () => Navigator.of(context).pushNamed(Routes.addressBook)),
WalletMenuItem(
title: S.current.backup,
image: Image.asset('assets/images/restore_wallet.png',
height: 16,
width: 16,
color: Palette.darkBlue),
handler: () {
Navigator.of(context).pushNamed(
Routes.auth,
arguments: (bool isAuthenticatedSuccessfully, AuthPageState auth) {
if (isAuthenticatedSuccessfully) {
auth.close(route:Routes.backup);
}
});
}),
WalletMenuItem(
title: S.current.settings_title,
image: Image.asset('assets/images/settings_menu.png',
@@ -15,6 +15,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:mobx/mobx.dart';
import 'package:share_plus/share_plus.dart';
class AddressPage extends BasePage {
AddressPage({
@@ -85,6 +86,26 @@ class AddressPage extends BasePage {
], begin: Alignment.topRight, end: Alignment.bottomLeft)),
child: scaffold);
@override
Widget? trailing(BuildContext context) {
final shareImage =
Image.asset('assets/images/share.png',
color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!);
return !addressListViewModel.hasAddressList ? Material(
color: Colors.transparent,
child: IconButton(
padding: EdgeInsets.zero,
constraints: BoxConstraints(),
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
iconSize: 25,
onPressed: () => Share.share(addressListViewModel.address.address),
icon: shareImage,
),
) : null;
}
@override
Widget body(BuildContext context) {
autorun((_) async {
+29 -3
View File
@@ -111,6 +111,10 @@ class ExchangePage extends BasePage {
WidgetsBinding.instance
.addPostFrameCallback((_) => _setReactions(context, exchangeViewModel));
if (exchangeViewModel.isLowFee) {
_showFeeAlert(context);
}
return KeyboardActions(
disableScroll: true,
config: KeyboardActionsConfig(
@@ -179,6 +183,7 @@ class ExchangePage extends BasePage {
padding: EdgeInsets.fromLTRB(24, 100, 24, 32),
child: Observer(
builder: (_) => ExchangeCard(
onDispose: disposeBestRateSync,
hasAllAmount: exchangeViewModel.hasAllAmount,
allAmount: exchangeViewModel.hasAllAmount
? () => exchangeViewModel
@@ -265,6 +270,7 @@ class ExchangePage extends BasePage {
EdgeInsets.only(top: 29, left: 24, right: 24),
child: Observer(
builder: (_) => ExchangeCard(
onDispose: disposeBestRateSync,
amountFocusNode: _receiveAmountFocus,
addressFocusNode: _receiveAddressFocus,
key: receiveKey,
@@ -743,13 +749,13 @@ class ExchangePage extends BasePage {
if (_receiveAmountFocus.hasFocus) {
exchangeViewModel.isFixedRateMode = true;
}
exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text);
// exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text);
});
_depositAmountFocus.addListener(() {
exchangeViewModel.isFixedRateMode = false;
exchangeViewModel.changeDepositAmount(
amount: depositAmountController.text);
// exchangeViewModel.changeDepositAmount(
// amount: depositAmountController.text);
});
_isReactionsSet = true;
@@ -791,4 +797,24 @@ class ExchangePage extends BasePage {
final address = await extractAddressFromParsed(context, parsedAddress);
return address;
}
void _showFeeAlert(BuildContext context) async {
await Future<void>.delayed(Duration(seconds: 1));
final confirmed = await showPopUp<bool>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle: S.of(context).low_fee,
alertContent: S.of(context).low_fee_alert,
leftButtonText: S.of(context).ignor,
rightButtonText: S.of(context).use_suggested,
actionLeftButton: () => Navigator.of(context).pop(false),
actionRightButton: () => Navigator.of(context).pop(true));
}) ?? false;
if (confirmed) {
exchangeViewModel.setDefaultTransactionPriority();
}
}
void disposeBestRateSync() => exchangeViewModel.bestRateSync?.cancel();
}
@@ -6,6 +6,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart';
import 'package:cake_wallet/palette.dart';
import 'currency_picker_widget.dart';
class CurrencyPicker extends StatefulWidget {
@@ -104,7 +105,7 @@ class CurrencyPickerState extends State<CurrencyPicker> {
Padding(
padding: const EdgeInsets.all(16),
child: TextFormField(
style: TextStyle(color: Theme.of(context).primaryTextTheme!.headline6!.color!),
style: TextStyle(color: Palette.darkBlueCraiola),
decoration: InputDecoration(
hintText: widget.hintText,
prefixIcon: Image.asset("assets/images/search_icon.png"),
@@ -37,7 +37,8 @@ class ExchangeCard extends StatefulWidget {
this.addressFocusNode,
this.allAmount,
this.onPushPasteButton,
this.onPushAddressBookButton})
this.onPushAddressBookButton,
this.onDispose})
: super(key: key);
final List<CryptoCurrency> currencies;
@@ -63,6 +64,7 @@ class ExchangeCard extends StatefulWidget {
final VoidCallback? allAmount;
final void Function(BuildContext context)? onPushPasteButton;
final void Function(BuildContext context)? onPushAddressBookButton;
final Function()? onDispose;
@override
ExchangeCardState createState() => ExchangeCardState();
@@ -106,6 +108,13 @@ class ExchangeCardState extends State<ExchangeCard> {
super.initState();
}
@override
void dispose() {
widget.onDispose?.call();
super.dispose();
}
void changeLimits({String? min, String? max}) {
setState(() {
_min = min;
@@ -20,9 +20,12 @@ class PresentProviderPicker extends StatelessWidget {
return TextButton(
onPressed: () => _presentProviderPicker(context),
// FIX-ME: Style
//highlightColor: Colors.transparent,
//splashColor: Colors.transparent,
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
splashFactory: NoSplash.splashFactory,
foregroundColor: MaterialStateProperty.all(Colors.transparent),
overlayColor: MaterialStateProperty.all(Colors.transparent),
),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
@@ -46,7 +49,7 @@ class PresentProviderPicker extends StatelessWidget {
style: TextStyle(
fontSize: 10.0,
fontWeight: FontWeight.w500,
color: Theme.of(context).textTheme!.headline5!.color!)))
color: Theme.of(context).textTheme.headline5!.color!)))
],
),
SizedBox(width: 5),
@@ -1,15 +1,6 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/di.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
// import 'package:flushbar/flushbar.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
@@ -67,8 +58,6 @@ class WalletTypeFormState extends State<WalletTypeForm> {
WalletType? selected;
List<WalletType> types;
// FIX-ME: Replace Flushbar
// Flushbar<void>? _progressBar;
@override
void initState() {
@@ -97,7 +86,7 @@ class WalletTypeFormState extends State<WalletTypeForm> {
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).primaryTextTheme!.headline6!.color!),
color: Theme.of(context).primaryTextTheme.headline6!.color!),
),
),
...types.map((type) => Padding(
@@ -114,7 +103,7 @@ class WalletTypeFormState extends State<WalletTypeForm> {
bottomSection: PrimaryButton(
onPressed: () => onTypeSelected(),
text: S.of(context).seed_language_next,
color: Theme.of(context).accentTextTheme!.bodyText1!.color!,
color: Theme.of(context).accentTextTheme.bodyText1!.color!,
textColor: Colors.white,
isDisabled: selected == null,
),
+44 -36
View File
@@ -1,4 +1,5 @@
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/node.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -26,7 +27,7 @@ class NodeListPage extends BasePage {
height: 32,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(16)),
color: Theme.of(context).accentTextTheme!.caption!.color!),
color: Theme.of(context).accentTextTheme.caption!.color!),
child: ButtonTheme(
minWidth: double.minPositive,
child: TextButton(
@@ -85,7 +86,7 @@ class NodeListPage extends BasePage {
final node = nodeListViewModel.nodes[index];
final isSelected =
node.keyIndex == nodeListViewModel.currentNode?.keyIndex;
node.keyIndex == nodeListViewModel.currentNode.keyIndex;
final nodeListRow = NodeListRow(
title: node.uriRaw,
isSelected: isSelected,
@@ -114,45 +115,52 @@ class NodeListPage extends BasePage {
});
});
});
// FIX-ME: Slidable
// final dismissibleRow = Slidable(
// key: Key('${node.keyIndex}'),
// actionPane: SlidableDrawerActionPane(),
// child: nodeListRow,
// secondaryActions: <Widget>[
// IconSlideAction(
// caption: S.of(context).delete,
// color: Colors.red,
// icon: CupertinoIcons.delete,
// onTap: () async {
// final confirmed = await showPopUp<bool>(
// context: context,
// builder: (BuildContext context) {
// return AlertWithTwoActions(
// alertTitle: S.of(context).remove_node,
// alertContent:
// S.of(context).remove_node_message,
// rightButtonText: S.of(context).remove,
// leftButtonText: S.of(context).cancel,
// actionRightButton: () =>
// Navigator.pop(context, true),
// actionLeftButton: () =>
// Navigator.pop(context, false));
// }) ??
// false;
// if (confirmed) {
// await nodeListViewModel.delete(node);
// }
// },
// ),
// ]);
final dismissibleRow = Slidable(
key: Key('${node.keyIndex}'),
startActionPane: _actionPane(context, node),
endActionPane: _actionPane(context, node),
child: nodeListRow,
);
return nodeListRow;
// return isSelected ? nodeListRow : dismissibleRow;
return isSelected ? nodeListRow : dismissibleRow;
});
},
),
);
}
ActionPane _actionPane(BuildContext context, Node node) => ActionPane(
motion: const ScrollMotion(),
extentRatio: 0.3,
children: [
SlidableAction(
onPressed: (context) async {
final confirmed = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).remove_node,
alertContent:
S.of(context).remove_node_message,
rightButtonText: S.of(context).remove,
leftButtonText: S.of(context).cancel,
actionRightButton: () =>
Navigator.pop(context, true),
actionLeftButton: () =>
Navigator.pop(context, false));
}) ??
false;
if (confirmed) {
await nodeListViewModel.delete(node);
}
},
backgroundColor: Colors.red,
foregroundColor: Colors.white,
icon: CupertinoIcons.delete,
label: S.of(context).delete,
),
],
);
}
+7 -10
View File
@@ -1,7 +1,6 @@
import 'package:cake_wallet/utils/show_bar.dart';
// import 'package:flushbar/flushbar.dart';
import 'package:another_flushbar/flushbar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/generated/i18n.dart';
class PinCodeWidget extends StatefulWidget {
@@ -40,8 +39,7 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
String pin;
String title;
double _aspectRatio;
// FIX-ME: Replace Flushbar
// Flushbar<void>? _progressBar;
Flushbar<void>? _progressBar;
int currentPinLength() => pin.length;
@@ -91,19 +89,18 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
void changeProcessText(String text) {
hideProgressText();
// FIX-ME: Empty Duration,
// _progressBar = createBar<void>(text, duration: Duration())
// ..show(_key.currentContext);
_progressBar = createBar<void>(text, duration: null)
..show(_key.currentContext!);
}
void close() {
// _progressBar?.dismiss();
_progressBar?.dismiss();
Navigator.of(_key.currentContext!).pop();
}
void hideProgressText() {
// _progressBar?.dismiss();
// _progressBar = null;
_progressBar?.dismiss();
_progressBar = null;
}
@override
+12 -18
View File
@@ -6,7 +6,7 @@ import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
// import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:share_plus/share_plus.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/di.dart';
@@ -92,23 +92,17 @@ class ReceivePage extends BasePage {
Image.asset('assets/images/share.png',
color: Theme.of(context).accentTextTheme!.headline2!.backgroundColor!);
return SizedBox(
height: 20.0,
width: 20.0,
child: ButtonTheme(
minWidth: double.minPositive,
child: TextButton(
// FIX-ME: Style
//highlightColor: Colors.transparent,
//splashColor: Colors.transparent,
//padding: EdgeInsets.all(0),
onPressed: () {
// FIX-ME: Share esys_flutter_share.dart
// Share.text(S.current.share_address,
// addressListViewModel.address.address, 'text/plain')
},
child: shareImage),
),
return Material(
color: Colors.transparent,
child: IconButton(
padding: EdgeInsets.zero,
constraints: BoxConstraints(),
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
iconSize: 25,
onPressed: () => Share.share(addressListViewModel.address.address),
icon: shareImage
)
);
}
@@ -57,6 +57,7 @@ class AddressCell extends StatelessWidget {
final Widget cell = InkWell(
onTap: () => onTap?.call(address),
child: Container(
width: double.infinity,
color: backgroundColor,
padding: EdgeInsets.only(left: 24, right: 24, top: 28, bottom: 28),
child: Text(
@@ -69,20 +70,25 @@ class AddressCell extends StatelessWidget {
),
),
));
// FIX-ME: Slidable
return cell;
// return Container(
// color: backgroundColor,
// child: Slidable(
// key: Key(address),
// actionPane: SlidableDrawerActionPane(),
// child: cell,
// secondaryActions: <Widget>[
// IconSlideAction(
// caption: S.of(context).edit,
// color: Colors.blue,
// icon: Icons.edit,
// onTap: () => onEdit?.call())
// ]));
return Slidable(
key: Key(address),
startActionPane: _actionPane(context),
endActionPane: _actionPane(context),
child: cell,
);
}
ActionPane _actionPane(BuildContext context) => ActionPane(
motion: const ScrollMotion(),
extentRatio: 0.3,
children: [
SlidableAction(
onPressed: (_) => onEdit?.call(),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
icon: Icons.edit,
label: S.of(context).edit,
),
],
);
}
+3 -8
View File
@@ -6,7 +6,7 @@ import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:share_plus/share_plus.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
@@ -159,13 +159,8 @@ class WalletSeedPage extends BasePage {
child: Container(
padding: EdgeInsets.only(right: 8.0),
child: PrimaryButton(
onPressed: () {
// FIX-ME: Share esys_flutter_share
// Share.text(
// S.of(context).seed_share,
// walletSeedViewModel.seed,
// 'text/plain')
},
onPressed: () =>
Share.share(walletSeedViewModel.seed),
text: S.of(context).save,
color: Colors.green,
textColor: Colors.white),
@@ -20,7 +20,7 @@ class SettingsLinkProviderCell extends StandardListRow {
@override
Widget? buildLeading(BuildContext context) =>
icon != null ? Image.asset(icon!, color: iconColor, height: 30, width: 30) : null;
icon != null ? Image.asset(icon!, color: iconColor, height: 24, width: 24) : null;
@override
Widget buildTrailing(BuildContext context) => Text(linkTitle,
@@ -1,3 +1,4 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
@@ -18,7 +19,36 @@ class WalletKeysPage extends BasePage {
@override
Widget body(BuildContext context) {
return Container(
return Column(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Container(
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.0),
color: Theme.of(context).accentTextTheme!.caption!.color!,
),
child: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: AutoSizeText(
S.of(context).do_not_share_warning_text.toUpperCase(),
textAlign: TextAlign.center,
maxLines: 4,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.red)),
),
),
),
),
),
Expanded(
flex: 7,
child: Container(
padding: EdgeInsets.only(top: 20.0, bottom: 20.0),
child: Observer(
builder: (_) {
@@ -48,6 +78,6 @@ class WalletKeysPage extends BasePage {
);
});
},
));
)))]);
}
}
@@ -3,7 +3,7 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
// import 'package:flushbar/flushbar.dart';
import 'package:another_flushbar/flushbar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -49,7 +49,7 @@ class WalletListBodyState extends State<WalletListBody> {
Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
final scrollController = ScrollController();
final double tileHeight = 60;
// Flushbar<void>? _progressBar;
Flushbar<void>? _progressBar;
@override
Widget build(BuildContext context) {
@@ -156,22 +156,14 @@ class WalletListBodyState extends State<WalletListBody> {
),
));
// FIX-ME: Slidable for current
return row;
// return wallet.isCurrent
// ? row
// : Slidable(
// key: Key('${wallet.key}'),
// actionPane: SlidableDrawerActionPane(),
// child: row,
// secondaryActions: <Widget>[
// IconSlideAction(
// caption: S.of(context).delete,
// color: Colors.red,
// icon: CupertinoIcons.delete,
// onTap: () async => _removeWallet(wallet),
// )
// ]);
return wallet.isCurrent
? row
: Slidable(
key: Key('${wallet.key}'),
startActionPane: _actionPane(wallet),
endActionPane: _actionPane(wallet),
child: row,
);
}),
),
),
@@ -240,7 +232,9 @@ class WalletListBodyState extends State<WalletListBody> {
await widget.walletListViewModel.loadWallet(wallet);
auth.hideProgressText();
auth.close();
Navigator.of(context).pop();
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.of(context).pop();
});
} catch (e) {
auth.changeProcessText(S
.of(context)
@@ -256,14 +250,34 @@ class WalletListBodyState extends State<WalletListBody> {
return;
}
try {
auth.changeProcessText(
S.of(context).wallet_list_removing_wallet(wallet.name));
await widget.walletListViewModel.remove(wallet);
} catch (e) {
auth.changeProcessText(S
.of(context)
.wallet_list_failed_to_remove(wallet.name, e.toString()));
bool confirmed = false;
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).delete_wallet,
alertContent: S.of(context).delete_wallet_confirm_message(wallet.name),
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).delete,
actionLeftButton: () => Navigator.of(context).pop(),
actionRightButton: () {
confirmed = true;
Navigator.of(context).pop();
},
);
});
if (confirmed) {
try {
auth.changeProcessText(
S.of(context).wallet_list_removing_wallet(wallet.name));
await widget.walletListViewModel.remove(wallet);
} catch (e) {
auth.changeProcessText(S
.of(context)
.wallet_list_failed_to_remove(wallet.name, e.toString()));
}
}
auth.close();
@@ -271,12 +285,25 @@ class WalletListBodyState extends State<WalletListBody> {
}
void changeProcessText(String text) {
// FIX-ME: Duration
// _progressBar = createBar<void>(text, duration: Duration())..show(context);
_progressBar = createBar<void>(text, duration: null)..show(context);
}
void hideProgressText() {
// _progressBar?.dismiss();
// _progressBar = null;
_progressBar?.dismiss();
_progressBar = null;
}
ActionPane _actionPane(WalletListItem wallet) => ActionPane(
motion: const ScrollMotion(),
extentRatio: 0.3,
children: [
SlidableAction(
onPressed: (_) => _removeWallet(wallet),
backgroundColor: Colors.red,
foregroundColor: Colors.white,
icon: CupertinoIcons.delete,
label: S.of(context).delete,
),
],
);
}
@@ -24,7 +24,7 @@ abstract class ExchangeTemplateBase with Store {
templates.replaceRange(0, templates.length, templateSource.values.toList());
@action
Future addTemplate({
Future<void> addTemplate({
required String amount,
required String depositCurrency,
required String receiveCurrency,
@@ -32,15 +32,15 @@ abstract class ExchangeTemplateBase with Store {
required String depositAddress,
required String receiveAddress}) async {
final template = ExchangeTemplate(
amount: amount,
depositCurrency: depositCurrency,
receiveCurrency: receiveCurrency,
provider: provider,
depositAddress: depositAddress,
receiveAddress: receiveAddress);
amountRaw: amount,
depositCurrencyRaw: depositCurrency,
receiveCurrencyRaw: receiveCurrency,
providerRaw: provider,
depositAddressRaw: depositAddress,
receiveAddressRaw: receiveAddress);
await templateSource.add(template);
}
@action
Future remove({required ExchangeTemplate template}) async => await template.delete();
Future<void> remove({required ExchangeTemplate template}) async => await template.delete();
}
+9 -9
View File
@@ -23,7 +23,7 @@ abstract class SendTemplateBase with Store {
templates.replaceRange(0, templates.length, templateSource.values.toList());
@action
Future addTemplate({
Future<void> addTemplate({
required String name,
required bool isCurrencySelected,
required String address,
@@ -32,16 +32,16 @@ abstract class SendTemplateBase with Store {
required String amount,
required String amountFiat}) async {
final template = Template(
name: name,
isCurrencySelected: isCurrencySelected,
address: address,
cryptoCurrency: cryptoCurrency,
fiatCurrency: fiatCurrency,
amount: amount,
amountFiat: amountFiat);
nameRaw: name,
isCurrencySelectedRaw: isCurrencySelected,
addressRaw: address,
cryptoCurrencyRaw: cryptoCurrency,
fiatCurrencyRaw: fiatCurrency,
amountRaw: amount,
amountFiatRaw: amountFiat);
await templateSource.add(template);
}
@action
Future remove({required Template template}) async => await template.delete();
Future<void> remove({required Template template}) async => await template.delete();
}
+64 -51
View File
@@ -1,58 +1,71 @@
// import 'package:flushbar/flushbar.dart';
import 'package:flutter/cupertino.dart';
import 'package:another_flushbar/flushbar.dart';
import 'package:flutter/material.dart';
Future<T?> showBar<T>(BuildContext context, String messageText,
{bool isDark = false,
Duration duration = const Duration(seconds: 1),
bool isDismissible = true,
String? titleText}) async {
// FIX-ME: Unimplemented Flushbar
// final bar = Flushbar<T>(
// boxShadows: [
// BoxShadow(
// color: Colors.black.withOpacity(0.09),
// blurRadius: 8,
// offset: Offset(0, 2))
// ],
// backgroundColor: isDark ? Colors.black : Colors.white,
// borderRadius: 35,
// margin: EdgeInsets.all(50),
// titleText: titleText != null
// ? Text(titleText,
// textAlign: TextAlign.center,
// style: TextStyle(color: isDark ? Colors.white : Colors.black, fontWeight: FontWeight.bold, fontSize: 24.0))
// : null,
// messageText: Text(messageText,
// textAlign: TextAlign.center,
// style: TextStyle(color: isDark ? Colors.white : Colors.black, fontSize: 16)),
// duration: duration,
// isDismissible: isDismissible,
// flushbarPosition: FlushbarPosition.TOP,
// flushbarStyle: FlushbarStyle.FLOATING);
Duration? duration = const Duration(seconds: 1), // pass explicitly by null to make the duration indefinite
bool isDismissible = true,
String? titleText}) async {
final bar = Flushbar<T>(
boxShadows: [
BoxShadow(
color: Colors.black.withOpacity(0.09),
blurRadius: 8,
offset: Offset(0, 2),
)
],
backgroundColor: isDark ? Colors.black : Colors.white,
borderRadius: BorderRadius.circular(35),
margin: EdgeInsets.all(50),
titleText: titleText != null
? Text(
titleText,
textAlign: TextAlign.center,
style: TextStyle(
color: isDark ? Colors.white : Colors.black,
fontWeight: FontWeight.bold,
fontSize: 24.0,
),
)
: null,
messageText: Text(
messageText,
textAlign: TextAlign.center,
style: TextStyle(
color: isDark ? Colors.white : Colors.black,
fontSize: 16,
),
),
duration: duration,
isDismissible: isDismissible,
flushbarPosition: FlushbarPosition.TOP,
flushbarStyle: FlushbarStyle.FLOATING,
);
// return bar.show(context);
return null;
return bar.show(context);
}
// FIX-ME: Unimplemented Flushbar
// Flushbar<T> createBar<T>(String text,
// {bool isDark = false, Duration duration = const Duration(seconds: 1), bool isDismissible = true}) {
// return Flushbar<T>(
// boxShadows: [
// BoxShadow(
// color: Colors.black.withOpacity(0.09),
// blurRadius: 8,
// offset: Offset(0, 2))
// ],
// backgroundColor: isDark ? Colors.black : Colors.white,
// borderRadius: 35,
// margin: EdgeInsets.all(50),
// messageText: Text(text,
// textAlign: TextAlign.center,
// style: TextStyle(color: isDark ? Colors.white : Colors.black)),
// duration: duration,
// isDismissible: isDismissible,
// flushbarPosition: FlushbarPosition.TOP,
// flushbarStyle: FlushbarStyle.FLOATING);
// }
Flushbar<T> createBar<T>(String text,
{bool isDark = false,
Duration? duration = const Duration(seconds: 1), // pass explicitly by null to make the duration indefinite
bool isDismissible = true}) {
return Flushbar<T>(
boxShadows: [
BoxShadow(
color: Colors.black.withOpacity(0.09),
blurRadius: 8,
offset: Offset(0, 2),
)
],
backgroundColor: isDark ? Colors.black : Colors.white,
borderRadius: BorderRadius.circular(35),
margin: EdgeInsets.all(50),
messageText: Text(text,
textAlign: TextAlign.center,
style: TextStyle(color: isDark ? Colors.white : Colors.black)),
duration: duration,
isDismissible: isDismissible,
flushbarPosition: FlushbarPosition.TOP,
flushbarStyle: FlushbarStyle.FLOATING,
);
}
+5 -2
View File
@@ -1,4 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/view_model/auth_state.dart';
@@ -55,8 +56,10 @@ abstract class AuthViewModelBase with Store {
final isSuccessfulAuthenticated = await _authService.authenticate(password);
if (isSuccessfulAuthenticated) {
state = ExecutedSuccessfullyState();
_failureCounter = 0;
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
state = ExecutedSuccessfullyState();
_failureCounter = 0;
});
} else {
_failureCounter += 1;
+16
View File
@@ -8,6 +8,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:mobx/mobx.dart';
import 'package:intl/intl.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:path_provider/path_provider.dart';
part 'backup_view_model.g.dart';
@@ -71,6 +72,21 @@ abstract class BackupViewModelBase with Store {
}
}
Future<String> saveBackupFileLocally(BackupExportFile backup) async {
final appDir = await getApplicationDocumentsDirectory();
final path = '${appDir.path}/${backup.name}';
final backupFile = File(path);
await backupFile.writeAsBytes(backup.content);
return path;
}
Future<void> removeBackupFileLocally(BackupExportFile backup) async {
final appDir = await getApplicationDocumentsDirectory();
final path = '${appDir.path}/${backup.name}';
final backupFile = File(path);
await backupFile.delete();
}
@action
void showMasterPassword() => isBackupPasswordVisible = true;
+1 -2
View File
@@ -6,7 +6,6 @@ import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/view_model/buy/buy_item.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:cake_wallet/buy/order.dart';
import 'package:cake_wallet/store/dashboard/orders_store.dart';
@@ -103,7 +102,7 @@ abstract class BuyViewModelBase with Store {
print(e.toString());
}
if (isMoonPayEnabled ?? false) {
if (isMoonPayEnabled) {
_providerList.add(MoonPayBuyProvider(wallet: wallet));
}
+181 -144
View File
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:collection';
import 'dart:convert';
@@ -5,6 +6,10 @@ import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.dart';
import 'package:cake_wallet/exchange/sideshift/sideshift_request.dart';
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
import 'package:cake_wallet/view_model/settings/settings_view_model.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_core/monero_transaction_priority.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cake_wallet/exchange/simpleswap/simpleswap_request.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/crypto_currency.dart';
@@ -39,7 +44,7 @@ class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel;
abstract class ExchangeViewModelBase with Store {
ExchangeViewModelBase(this.wallet, this.trades, this._exchangeTemplateStore,
this.tradesStore, this._settingsStore, this.sharedPreferences)
this.tradesStore, this._settingsStore, this.sharedPreferences, this._settingsViewModel)
: _cryptoNumberFormat = NumberFormat(),
isReverse = false,
isFixedRateMode = false,
@@ -59,12 +64,12 @@ abstract class ExchangeViewModelBase with Store {
receiveCurrency = wallet.currency,
depositCurrency = wallet.currency,
providerList = [ChangeNowExchangeProvider(), SideShiftExchangeProvider(), SimpleSwapExchangeProvider()],
selectedProviders = ObservableList<ExchangeProvider>(),
currentTradeAvailableProviders = SplayTreeMap<double, ExchangeProvider>() {
selectedProviders = ObservableList<ExchangeProvider>() {
const excludeDepositCurrencies = [CryptoCurrency.btt, CryptoCurrency.nano];
const excludeReceiveCurrencies = [CryptoCurrency.xlm, CryptoCurrency.xrp,
CryptoCurrency.bnb, CryptoCurrency.btt, CryptoCurrency.nano];
_initialPairBasedOnWallet();
final Map<String, dynamic> exchangeProvidersSelection = json
.decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}") as Map<String, dynamic>;
@@ -76,6 +81,11 @@ abstract class ExchangeViewModelBase with Store {
: (exchangeProvidersSelection[element.title] as bool))
.toList());
_setAvailableProviders();
_calculateBestRate();
bestRateSync = Timer.periodic(Duration(seconds: 10), (timer) => _calculateBestRate());
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
depositAmount = '';
@@ -119,8 +129,15 @@ abstract class ExchangeViewModelBase with Store {
/// Maps in dart are not sorted by default
/// SplayTreeMap is a map sorted by keys
/// will use it to sort available providers
/// depending on the amount they yield for the current trade
SplayTreeMap<double, ExchangeProvider> currentTradeAvailableProviders;
/// based on the rate they yield for the current trade
///
///
/// initialize with descending comparator
/// since we want largest rate first
final SplayTreeMap<double, ExchangeProvider> _sortedAvailableProviders =
SplayTreeMap<double, ExchangeProvider>((double a, double b) => b.compareTo(a));
final List<ExchangeProvider> _tradeAvailableProviders = [];
@observable
ObservableList<ExchangeProvider> selectedProviders;
@@ -179,6 +196,20 @@ abstract class ExchangeViewModelBase with Store {
bool get isMoneroWallet => wallet.type == WalletType.monero;
bool get isLowFee {
switch (wallet.type) {
case WalletType.monero:
case WalletType.haven:
return _settingsViewModel.transactionPriority == MoneroTransactionPriority.slow;
case WalletType.bitcoin:
return _settingsViewModel.transactionPriority == BitcoinTransactionPriority.slow;
case WalletType.litecoin:
return _settingsViewModel.transactionPriority == LitecoinTransactionPriority.slow;
default:
return false;
}
}
List<CryptoCurrency> receiveCurrencies;
List<CryptoCurrency> depositCurrencies;
@@ -191,6 +222,12 @@ abstract class ExchangeViewModelBase with Store {
final SettingsStore _settingsStore;
final SettingsViewModel _settingsViewModel;
double _bestRate = 0.0;
late Timer bestRateSync;
@action
void changeDepositCurrency({required CryptoCurrency currency}) {
depositCurrency = currency;
@@ -210,68 +247,11 @@ abstract class ExchangeViewModelBase with Store {
}
@action
void changeReceiveAmount({required String amount}) {
Future<void> changeReceiveAmount({required String amount}) async {
receiveAmount = amount;
isReverse = true;
if (amount == null || amount.isEmpty) {
depositAmount = '';
receiveAmount = '';
return;
}
final _enteredAmount = double.parse(amount.replaceAll(',', '.')) ?? 0;
currentTradeAvailableProviders.clear();
for (var provider in selectedProviders) {
/// if this provider is not valid for the current pair, skip it
if (!providersForCurrentPair().contains(provider)) {
continue;
}
provider
.calculateAmount(
from: receiveCurrency,
to: depositCurrency,
amount: _enteredAmount,
isFixedRateMode: isFixedRateMode,
isReceiveAmount: true)
.then((amount) {
final from = isFixedRateMode
? receiveCurrency
: depositCurrency;
final to = isFixedRateMode
? depositCurrency
: receiveCurrency;
provider.fetchLimits(
from: from,
to: to,
isFixedRateMode: isFixedRateMode,
).then((limits) {
/// if the entered amount doesn't exceed the limits of this provider
if ((limits?.max ?? double.maxFinite) >= _enteredAmount
&& (limits?.min ?? 0) <= _enteredAmount) {
/// add this provider as its valid for this trade
/// will be sorted ascending already since
/// we seek the least deposit amount
currentTradeAvailableProviders[amount] = provider;
}
return amount;
}).then((amount) => depositAmount = _cryptoNumberFormat
.format(amount)
.toString()
.replaceAll(RegExp('\\,'), ''));
});
}
}
@action
void changeDepositAmount({required String amount}) {
depositAmount = amount;
isReverse = false;
if (amount == null || amount.isEmpty) {
if (amount.isEmpty) {
depositAmount = '';
receiveAmount = '';
return;
@@ -279,93 +259,115 @@ abstract class ExchangeViewModelBase with Store {
final _enteredAmount = double.tryParse(amount.replaceAll(',', '.')) ?? 0;
currentTradeAvailableProviders.clear();
for (var provider in selectedProviders) {
/// if this provider is not valid for the current pair, skip it
if (!providersForCurrentPair().contains(provider)) {
continue;
if (_bestRate == 0) {
depositAmount = S.current.fetching;
await _calculateBestRate();
}
depositAmount = _cryptoNumberFormat
.format(_enteredAmount / _bestRate)
.toString()
.replaceAll(RegExp('\\,'), '');
}
@action
Future<void> changeDepositAmount({required String amount}) async {
depositAmount = amount;
isReverse = false;
if (amount.isEmpty) {
depositAmount = '';
receiveAmount = '';
return;
}
final _enteredAmount = double.tryParse(amount.replaceAll(',', '.')) ?? 0;
/// in case the best rate was not calculated yet
if (_bestRate == 0) {
receiveAmount = S.current.fetching;
await _calculateBestRate();
}
receiveAmount = _cryptoNumberFormat
.format(_bestRate * _enteredAmount)
.toString()
.replaceAll(RegExp('\\,'), '');
}
Future<void> _calculateBestRate() async {
final result = await Future.wait<double>(
_tradeAvailableProviders
.map((element) => element.calculateAmount(
from: depositCurrency,
to: receiveCurrency,
amount: 1,
isFixedRateMode: isFixedRateMode,
isReceiveAmount: false))
);
_sortedAvailableProviders.clear();
for (int i=0;i<result.length;i++) {
if (result[i] != 0) {
/// add this provider as its valid for this trade
_sortedAvailableProviders[result[i]] = _tradeAvailableProviders[i];
}
provider
.calculateAmount(
from: depositCurrency,
to: receiveCurrency,
amount: _enteredAmount,
isFixedRateMode: isFixedRateMode,
isReceiveAmount: false)
.then((amount) {
final from = isFixedRateMode
? receiveCurrency
: depositCurrency;
final to = isFixedRateMode
? depositCurrency
: receiveCurrency;
provider.fetchLimits(
from: from,
to: to,
isFixedRateMode: isFixedRateMode,
).then((limits) {
/// if the entered amount doesn't exceed the limits of this provider
if ((limits?.max ?? double.maxFinite) >= _enteredAmount
&& (limits?.min ?? 0) <= _enteredAmount) {
/// add this provider as its valid for this trade
/// subtract from maxFinite so the provider
/// with the largest amount would be sorted ascending
currentTradeAvailableProviders[double.maxFinite - amount] = provider;
}
return amount;
}).then((amount) => receiveAmount =
receiveAmount = _cryptoNumberFormat
.format(amount)
.toString()
.replaceAll(RegExp('\\,'), ''));
});
}
if (_sortedAvailableProviders.isNotEmpty) {
_bestRate = _sortedAvailableProviders.keys.first;
}
}
@action
Future loadLimits() async {
Future<void> loadLimits() async {
if (selectedProviders.isEmpty) {
return;
}
limitsState = LimitsIsLoading();
try {
final from = isFixedRateMode
final from = isFixedRateMode
? receiveCurrency
: depositCurrency;
final to = isFixedRateMode
final to = isFixedRateMode
? depositCurrency
: receiveCurrency;
limits = await selectedProviders.first.fetchLimits(
from: from,
to: to,
isFixedRateMode: isFixedRateMode);
double lowestMin = double.maxFinite;
double? highestMax = 0.0;
/// if the first provider limits is bounded then check with other providers
/// for the highest maximum limit
if (limits.max != null) {
for (int i = 1;i < selectedProviders.length;i++) {
final Limits tempLimits = await selectedProviders[i].fetchLimits(
from: from,
to: to,
isFixedRateMode: isFixedRateMode);
/// set the limits with the maximum provider limit
/// if there is a provider with null max then it's the maximum limit
if ((tempLimits.max ?? double.maxFinite) > limits.max!) {
limits = tempLimits;
}
}
for (var provider in selectedProviders) {
/// if this provider is not valid for the current pair, skip it
if (!providersForCurrentPair().contains(provider)) {
continue;
}
try {
final tempLimits = await provider.fetchLimits(
from: from,
to: to,
isFixedRateMode: isFixedRateMode);
if (tempLimits.min != null && tempLimits.min! < lowestMin) {
lowestMin = tempLimits.min!;
}
if (highestMax != null && (tempLimits.max ?? double.maxFinite) > highestMax) {
highestMax = tempLimits.max;
}
} catch (e) {
continue;
}
}
if (lowestMin < double.maxFinite) {
limits = Limits(min: lowestMin, max: highestMax);
limitsState = LimitsLoadedSuccessfully(limits: limits);
} catch (e) {
limitsState = LimitsLoadedFailure(error: e.toString());
} else {
limitsState = LimitsLoadedFailure(error: 'Limits loading failed');
}
}
@@ -374,7 +376,7 @@ abstract class ExchangeViewModelBase with Store {
TradeRequest? request;
String amount = '';
for (var provider in currentTradeAvailableProviders.values) {
for (var provider in _sortedAvailableProviders.values) {
if (!(await provider.checkIsAvailable())) {
continue;
}
@@ -383,7 +385,7 @@ abstract class ExchangeViewModelBase with Store {
request = SideShiftRequest(
depositMethod: depositCurrency,
settleMethod: receiveCurrency,
depositAmount: depositAmount?.replaceAll(',', '.') ?? '',
depositAmount: depositAmount.replaceAll(',', '.'),
settleAddress: receiveAddress,
refundAddress: depositAddress,
);
@@ -394,7 +396,7 @@ abstract class ExchangeViewModelBase with Store {
request = SimpleSwapRequest(
from: depositCurrency,
to: receiveCurrency,
amount: depositAmount?.replaceAll(',', '.') ?? '',
amount: depositAmount.replaceAll(',', '.'),
address: receiveAddress,
refundAddress: depositAddress,
);
@@ -405,8 +407,8 @@ abstract class ExchangeViewModelBase with Store {
request = XMRTOTradeRequest(
from: depositCurrency,
to: receiveCurrency,
amount: depositAmount?.replaceAll(',', '.') ?? '',
receiveAmount: receiveAmount?.replaceAll(',', '.') ?? '',
amount: depositAmount.replaceAll(',', '.'),
receiveAmount: receiveAmount.replaceAll(',', '.'),
address: receiveAddress,
refundAddress: depositAddress,
isBTCRequest: isReceiveAmountEntered);
@@ -417,8 +419,8 @@ abstract class ExchangeViewModelBase with Store {
request = ChangeNowRequest(
from: depositCurrency,
to: receiveCurrency,
fromAmount: depositAmount?.replaceAll(',', '.') ?? '',
toAmount: receiveAmount?.replaceAll(',', '.') ?? '',
fromAmount: depositAmount.replaceAll(',', '.'),
toAmount: receiveAmount.replaceAll(',', '.'),
refundAddress: depositAddress,
address: receiveAddress,
isReverse: isReverse);
@@ -429,7 +431,7 @@ abstract class ExchangeViewModelBase with Store {
request = MorphTokenRequest(
from: depositCurrency,
to: receiveCurrency,
amount: depositAmount?.replaceAll(',', '.') ?? '',
amount: depositAmount.replaceAll(',', '.'),
refundAddress: depositAddress,
address: receiveAddress);
amount = depositAmount;
@@ -437,7 +439,7 @@ abstract class ExchangeViewModelBase with Store {
amount = amount.replaceAll(',', '.');
if (limitsState is LimitsLoadedSuccessfully && amount != null) {
if (limitsState is LimitsLoadedSuccessfully) {
if (double.parse(amount) < limits.min!) {
continue;
} else if (limits.max != null && double.parse(amount) > limits.max!) {
@@ -527,7 +529,7 @@ abstract class ExchangeViewModelBase with Store {
final providers = providerList
.where((provider) => provider.pairList
.where((pair) =>
pair.from == (from ?? depositCurrency) && pair.to == (to ?? receiveCurrency))
pair.from == from && pair.to == to)
.isNotEmpty)
.toList();
@@ -537,6 +539,10 @@ abstract class ExchangeViewModelBase with Store {
void _onPairChange() {
depositAmount = '';
receiveAmount = '';
loadLimits();
_setAvailableProviders();
_bestRate = 0;
_calculateBestRate();
}
void _initialPairBasedOnWallet() {
@@ -579,11 +585,15 @@ abstract class ExchangeViewModelBase with Store {
@action
void addExchangeProvider(ExchangeProvider provider) {
selectedProviders.add(provider);
if (providersForCurrentPair().contains(provider)) {
_tradeAvailableProviders.add(provider);
}
}
@action
void removeExchangeProvider(ExchangeProvider provider) {
selectedProviders.remove(provider);
_tradeAvailableProviders.remove(provider);
}
@action
@@ -593,13 +603,14 @@ abstract class ExchangeViewModelBase with Store {
isFixedRateMode = false;
_defineIsReceiveAmountEditable();
loadLimits();
_bestRate = 0;
_calculateBestRate();
final Map<String, dynamic> exchangeProvidersSelection = json
.decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}") as Map<String, dynamic>;
exchangeProvidersSelection.updateAll((key, dynamic value) => false);
for (var provider in selectedProviders) {
exchangeProvidersSelection[provider.title] = true;
for (var provider in providerList) {
exchangeProvidersSelection[provider.title] = selectedProviders.contains(provider);
}
sharedPreferences.setString(
@@ -612,4 +623,30 @@ abstract class ExchangeViewModelBase with Store {
final providersForPair = providersForCurrentPair();
return selectedProviders.any((element) => element.isAvailable && providersForPair.contains(element));
}
void _setAvailableProviders() {
_tradeAvailableProviders.clear();
_tradeAvailableProviders.addAll(
selectedProviders
.where((provider) => providersForCurrentPair().contains(provider)));
}
@action
void setDefaultTransactionPriority() {
switch (wallet.type) {
case WalletType.monero:
case WalletType.haven:
_settingsStore.priority[wallet.type] = MoneroTransactionPriority.automatic;
break;
case WalletType.bitcoin:
_settingsStore.priority[wallet.type] = BitcoinTransactionPriority.medium;
break;
case WalletType.litecoin:
_settingsStore.priority[wallet.type] = LitecoinTransactionPriority.medium;
break;
default:
break;
}
}
}
@@ -64,7 +64,7 @@ abstract class RestoreFromBackupViewModelBase with Store {
} catch (e) {
var msg = e.toString();
if (msg == 'Message authentication code (MAC) is invalid') {
if (msg.toLowerCase().contains("message authentication code (mac)")) {
msg = 'Incorrect backup password';
}
+1 -2
View File
@@ -1,7 +1,6 @@
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
@@ -276,7 +275,7 @@ abstract class SendViewModelBase with Store {
if (isElectrumWallet) {
final rate = bitcoin!.getFeeRate(wallet, _priority);
return '${(priority as BitcoinTransactionPriority).labelWithRate(rate)}';
return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate);
}
return priority.toString();
@@ -1,7 +1,6 @@
import 'package:cake_wallet/entities/language_service.dart';
import 'package:cake_wallet/store/yat/yat_store.dart';
import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:flutter/cupertino.dart';
import 'package:mobx/mobx.dart';
import 'package:package_info/package_info.dart';
@@ -137,7 +136,7 @@ abstract class SettingsViewModelBase with Store {
if (wallet.type == WalletType.bitcoin
|| wallet.type == WalletType.litecoin) {
final rate = bitcoin!.getFeeRate(wallet, _priority);
return '${(priority as BitcoinTransactionPriority).labelWithRate(rate)}';
return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate);
}
return priority.toString();
+10
View File
@@ -53,6 +53,16 @@ abstract class SupportViewModelBase with Store {
icon: 'assets/images/change_now.png',
linkTitle: 'support@changenow.io',
link: 'mailto:support@changenow.io'),
LinkListItem(
title: 'SideShift',
icon: 'assets/images/sideshift.png',
linkTitle: S.current.help,
link: 'https://help.sideshift.ai/en/'),
LinkListItem(
title: 'SimpleSwap',
icon: 'assets/images/simpleSwap.png',
linkTitle: 'support@simpleswap.io',
link: 'mailto:support@simpleswap.io'),
if (!isMoneroOnly) ... [
LinkListItem(
title: 'Wyre',