mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-18 05:51:34 +02:00
Merge branch 'flutter-upgrade' of https://github.com/cake-tech/cake_wallet into CW-182-allow-trusted-nodes
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 [];
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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>()));
|
||||
|
||||
|
||||
@@ -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
@@ -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 ?? '';
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ class CWHaven extends Haven {
|
||||
|
||||
@override
|
||||
TransactionPriority getDefaultTransactionPriority() {
|
||||
return MoneroTransactionPriority.slow;
|
||||
return MoneroTransactionPriority.automatic;
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
+2
-2
@@ -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),
|
||||
)))));
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ class CWMonero extends Monero {
|
||||
|
||||
@override
|
||||
TransactionPriority getDefaultTransactionPriority() {
|
||||
return MoneroTransactionPriority.slow;
|
||||
return MoneroTransactionPriority.automatic;
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
+5
-1
@@ -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(
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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>(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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,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();
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user