mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-23 12:09:43 +00:00
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into CW-438-add-nano
This commit is contained in:
commit
607d767523
90 changed files with 2805 additions and 1400 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -8,6 +8,7 @@
|
||||||
.buildlog/
|
.buildlog/
|
||||||
.history
|
.history
|
||||||
.svn/
|
.svn/
|
||||||
|
.fvm/
|
||||||
|
|
||||||
# IntelliJ related
|
# IntelliJ related
|
||||||
*.iml
|
*.iml
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:fullBackupContent="false"
|
android:fullBackupContent="false"
|
||||||
|
android:versionCode="__versionCode__"
|
||||||
|
android:versionName="__versionName__"
|
||||||
android:requestLegacyExternalStorage="true">
|
android:requestLegacyExternalStorage="true">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
org.gradle.jvmargs=-Xmx1536M
|
org.gradle.jvmargs=-Xmx1536M
|
||||||
android.enableR8=true
|
android.enableR8=true
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
10
configure_cake_wallet_android.sh
Normal file
10
configure_cake_wallet_android.sh
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
cd scripts/android
|
||||||
|
source ./app_env.sh cakewallet
|
||||||
|
./app_config.sh
|
||||||
|
cd ../.. && flutter pub get
|
||||||
|
cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
|
cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
|
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
|
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
|
cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
|
flutter packages pub run build_runner build --delete-conflicting-outputs
|
|
@ -1,6 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_ethereum/erc20_balance.dart';
|
import 'package:cw_ethereum/erc20_balance.dart';
|
||||||
|
@ -18,7 +17,6 @@ import 'package:cw_ethereum/.secrets.g.dart' as secrets;
|
||||||
class EthereumClient {
|
class EthereumClient {
|
||||||
final _httpClient = Client();
|
final _httpClient = Client();
|
||||||
Web3Client? _client;
|
Web3Client? _client;
|
||||||
StreamSubscription<Transfer>? subscription;
|
|
||||||
|
|
||||||
bool connect(Node node) {
|
bool connect(Node node) {
|
||||||
try {
|
try {
|
||||||
|
@ -30,42 +28,13 @@ class EthereumClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setListeners(EthereumAddress userAddress, Function(FilterEvent) onNewTransaction) async {
|
void setListeners(EthereumAddress userAddress, Function() onNewTransaction) async {
|
||||||
// final String abi = await rootBundle.loadString("assets/abi_json/erc20_abi.json");
|
// _client?.pendingTransactions().listen((transactionHash) async {
|
||||||
// final contractAbi = ContractAbi.fromJson(abi, "ERC20");
|
// final transaction = await _client!.getTransactionByHash(transactionHash);
|
||||||
//
|
//
|
||||||
// final contract = DeployedContract(
|
// if (transaction.from.hex == userAddress || transaction.to?.hex == userAddress) {
|
||||||
// contractAbi,
|
// onNewTransaction();
|
||||||
// EthereumAddress.fromHex("0xf451659CF5688e31a31fC3316efbcC2339A490Fb"),
|
// }
|
||||||
// );
|
|
||||||
//
|
|
||||||
// final transferEvent = contract.event('Transfer');
|
|
||||||
// // listen for the Transfer event when it's emitted by the contract above
|
|
||||||
// final subscription = _client!
|
|
||||||
// .events(FilterOptions.events(contract: contract, event: transferEvent))
|
|
||||||
// .take(1)
|
|
||||||
// .listen((event) {
|
|
||||||
// final decoded = transferEvent.decodeResults(event.topics ?? [], event.data ?? '');
|
|
||||||
//
|
|
||||||
// final from = decoded[0] as EthereumAddress;
|
|
||||||
// final to = decoded[1] as EthereumAddress;
|
|
||||||
// final value = decoded[2] as BigInt;
|
|
||||||
//
|
|
||||||
// print('$from sent $value MetaCoins to $to');
|
|
||||||
// });
|
|
||||||
|
|
||||||
// final eventFilter = FilterOptions(address: userAddress);
|
|
||||||
//
|
|
||||||
// _client!.events(eventFilter).listen((event) {
|
|
||||||
// print('Address ${event.address} data ${event.data} tx hash ${event.transactionHash}!');
|
|
||||||
// onNewTransaction(event);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// final erc20 = Erc20(client: _client!, address: userAddress);
|
|
||||||
//
|
|
||||||
// subscription = erc20.transferEvents().take(1).listen((event) {
|
|
||||||
// print('${event.from} sent ${event.value} MetaCoins to ${event.to}!');
|
|
||||||
// onNewTransaction(event);
|
|
||||||
// });
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,17 +46,9 @@ class EthereumClient {
|
||||||
return gasPrice.getInWei.toInt();
|
return gasPrice.getInWei.toInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<int>> getEstimatedGasForPriorities() async {
|
Future<int> getEstimatedGas() async {
|
||||||
// TODO: there is no difference, find out why
|
final estimatedGas = await _client!.estimateGas();
|
||||||
// [53000, 53000, 53000]
|
return estimatedGas.toInt();
|
||||||
final result = await Future.wait(EthereumTransactionPriority.all.map(
|
|
||||||
(priority) => _client!.estimateGas(
|
|
||||||
// maxPriorityFeePerGas: EtherAmount.fromUnitAndValue(EtherUnit.gwei, priority.tip),
|
|
||||||
// maxFeePerGas: EtherAmount.fromUnitAndValue(EtherUnit.gwei, priority.tip),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
|
|
||||||
return result.map((e) => e.toInt()).toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<PendingEthereumTransaction> signTransaction({
|
Future<PendingEthereumTransaction> signTransaction({
|
||||||
|
@ -111,20 +72,17 @@ class EthereumClient {
|
||||||
to: EthereumAddress.fromHex(toAddress),
|
to: EthereumAddress.fromHex(toAddress),
|
||||||
maxGas: gas,
|
maxGas: gas,
|
||||||
gasPrice: price,
|
gasPrice: price,
|
||||||
|
maxPriorityFeePerGas: EtherAmount.fromUnitAndValue(EtherUnit.gwei, priority.tip),
|
||||||
value: _isEthereum ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
|
value: _isEthereum ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
|
||||||
);
|
);
|
||||||
|
|
||||||
final signedTransaction = await _client!.signTransaction(privateKey, transaction);
|
final signedTransaction = await _client!.signTransaction(privateKey, transaction);
|
||||||
|
|
||||||
final BigInt estimatedGas;
|
|
||||||
final Function _sendTransaction;
|
final Function _sendTransaction;
|
||||||
|
|
||||||
if (_isEthereum) {
|
if (_isEthereum) {
|
||||||
estimatedGas = BigInt.from(21000);
|
|
||||||
_sendTransaction = () async => await sendTransaction(signedTransaction);
|
_sendTransaction = () async => await sendTransaction(signedTransaction);
|
||||||
} else {
|
} else {
|
||||||
estimatedGas = BigInt.from(50000);
|
|
||||||
|
|
||||||
final erc20 = Erc20(
|
final erc20 = Erc20(
|
||||||
client: _client!,
|
client: _client!,
|
||||||
address: EthereumAddress.fromHex(contractAddress!),
|
address: EthereumAddress.fromHex(contractAddress!),
|
||||||
|
@ -142,7 +100,7 @@ class EthereumClient {
|
||||||
return PendingEthereumTransaction(
|
return PendingEthereumTransaction(
|
||||||
signedTransaction: signedTransaction,
|
signedTransaction: signedTransaction,
|
||||||
amount: amount,
|
amount: amount,
|
||||||
fee: estimatedGas * price.getInWei,
|
fee: BigInt.from(gas) * price.getInWei,
|
||||||
sendTransaction: _sendTransaction,
|
sendTransaction: _sendTransaction,
|
||||||
exponent: exponent,
|
exponent: exponent,
|
||||||
);
|
);
|
||||||
|
@ -193,19 +151,6 @@ I/flutter ( 4474): Gas Used: 53000
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// Future<List<Transaction>> fetchTransactions(String address) async {
|
|
||||||
// get(Uri.https(
|
|
||||||
// "https://api.etherscan.io",
|
|
||||||
// "api/",
|
|
||||||
// {
|
|
||||||
// "module": "account",
|
|
||||||
// "action": "txlist",
|
|
||||||
// "address": address,
|
|
||||||
// "apikey": secrets.,
|
|
||||||
// },
|
|
||||||
// ));
|
|
||||||
// }
|
|
||||||
|
|
||||||
Future<ERC20Balance> fetchERC20Balances(
|
Future<ERC20Balance> fetchERC20Balances(
|
||||||
EthereumAddress userAddress, String contractAddress) async {
|
EthereumAddress userAddress, String contractAddress) async {
|
||||||
final erc20 = Erc20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
|
final erc20 = Erc20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
|
||||||
|
@ -235,7 +180,6 @@ I/flutter ( 4474): Gas Used: 53000
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
subscription?.cancel();
|
|
||||||
_client?.dispose();
|
_client?.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,8 +32,9 @@ abstract class EthereumTransactionHistoryBase
|
||||||
final path = '$dirPath/$transactionsHistoryFileName';
|
final path = '$dirPath/$transactionsHistoryFileName';
|
||||||
final data = json.encode({'transactions': transactions});
|
final data = json.encode({'transactions': transactions});
|
||||||
await writeData(path: path, password: _password, data: data);
|
await writeData(path: path, password: _password, data: data);
|
||||||
} catch (e) {
|
} catch (e, s) {
|
||||||
print('Error while save bitcoin transaction history: ${e.toString()}');
|
print('Error while save ethereum transaction history: ${e.toString()}');
|
||||||
|
print(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +49,9 @@ abstract class EthereumTransactionHistoryBase
|
||||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||||
final path = '$dirPath/$transactionsHistoryFileName';
|
final path = '$dirPath/$transactionsHistoryFileName';
|
||||||
final content = await read(path: path, password: _password);
|
final content = await read(path: path, password: _password);
|
||||||
|
if (content.isEmpty) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
return json.decode(content) as Map<String, dynamic>;
|
return json.decode(content) as Map<String, dynamic>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,9 +48,9 @@ class EthereumTransactionInfo extends TransactionInfo {
|
||||||
return EthereumTransactionInfo(
|
return EthereumTransactionInfo(
|
||||||
id: data['id'] as String,
|
id: data['id'] as String,
|
||||||
height: data['height'] as int,
|
height: data['height'] as int,
|
||||||
ethAmount: data['amount'] as BigInt,
|
ethAmount: BigInt.parse(data['amount']),
|
||||||
exponent: data['exponent'] as int,
|
exponent: data['exponent'] as int,
|
||||||
ethFee: data['fee'] as BigInt,
|
ethFee: BigInt.parse(data['fee']),
|
||||||
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
||||||
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
|
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
|
||||||
isPending: data['isPending'] as bool,
|
isPending: data['isPending'] as bool,
|
||||||
|
@ -62,9 +62,9 @@ class EthereumTransactionInfo extends TransactionInfo {
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
'id': id,
|
'id': id,
|
||||||
'height': height,
|
'height': height,
|
||||||
'amount': ethAmount,
|
'amount': ethAmount.toString(),
|
||||||
'exponent': exponent,
|
'exponent': exponent,
|
||||||
'fee': ethFee,
|
'fee': ethFee.toString(),
|
||||||
'direction': direction.index,
|
'direction': direction.index,
|
||||||
'date': date.millisecondsSinceEpoch,
|
'date': date.millisecondsSinceEpoch,
|
||||||
'isPending': isPending,
|
'isPending': isPending,
|
||||||
|
|
|
@ -8,11 +8,11 @@ class EthereumTransactionPriority extends TransactionPriority {
|
||||||
|
|
||||||
static const List<EthereumTransactionPriority> all = [fast, medium, slow];
|
static const List<EthereumTransactionPriority> all = [fast, medium, slow];
|
||||||
static const EthereumTransactionPriority slow =
|
static const EthereumTransactionPriority slow =
|
||||||
EthereumTransactionPriority(title: 'slow', raw: 0, tip: 50);
|
EthereumTransactionPriority(title: 'slow', raw: 0, tip: 1);
|
||||||
static const EthereumTransactionPriority medium =
|
static const EthereumTransactionPriority medium =
|
||||||
EthereumTransactionPriority(title: 'Medium', raw: 1, tip: 75);
|
EthereumTransactionPriority(title: 'Medium', raw: 1, tip: 2);
|
||||||
static const EthereumTransactionPriority fast =
|
static const EthereumTransactionPriority fast =
|
||||||
EthereumTransactionPriority(title: 'Fast', raw: 2, tip: 100);
|
EthereumTransactionPriority(title: 'Fast', raw: 2, tip: 4);
|
||||||
|
|
||||||
static EthereumTransactionPriority deserialize({required int raw}) {
|
static EthereumTransactionPriority deserialize({required int raw}) {
|
||||||
switch (raw) {
|
switch (raw) {
|
||||||
|
|
|
@ -29,6 +29,7 @@ import 'package:cw_core/erc20_token.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:hex/hex.dart';
|
import 'package:hex/hex.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:web3dart/web3dart.dart';
|
import 'package:web3dart/web3dart.dart';
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
import 'package:bip32/bip32.dart' as bip32;
|
import 'package:bip32/bip32.dart' as bip32;
|
||||||
|
@ -48,7 +49,6 @@ abstract class EthereumWalletBase
|
||||||
}) : syncStatus = NotConnectedSyncStatus(),
|
}) : syncStatus = NotConnectedSyncStatus(),
|
||||||
_password = password,
|
_password = password,
|
||||||
_mnemonic = mnemonic,
|
_mnemonic = mnemonic,
|
||||||
_priorityFees = [],
|
|
||||||
_isTransactionUpdating = false,
|
_isTransactionUpdating = false,
|
||||||
_client = EthereumClient(),
|
_client = EthereumClient(),
|
||||||
walletAddresses = EthereumWalletAddresses(walletInfo),
|
walletAddresses = EthereumWalletAddresses(walletInfo),
|
||||||
|
@ -61,6 +61,8 @@ abstract class EthereumWalletBase
|
||||||
if (!Hive.isAdapterRegistered(Erc20Token.typeId)) {
|
if (!Hive.isAdapterRegistered(Erc20Token.typeId)) {
|
||||||
Hive.registerAdapter(Erc20TokenAdapter());
|
Hive.registerAdapter(Erc20TokenAdapter());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_sharedPrefs.complete(SharedPreferences.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
final String _mnemonic;
|
final String _mnemonic;
|
||||||
|
@ -72,10 +74,13 @@ abstract class EthereumWalletBase
|
||||||
|
|
||||||
late EthereumClient _client;
|
late EthereumClient _client;
|
||||||
|
|
||||||
List<int> _priorityFees;
|
|
||||||
int? _gasPrice;
|
int? _gasPrice;
|
||||||
|
int? _estimatedGas;
|
||||||
bool _isTransactionUpdating;
|
bool _isTransactionUpdating;
|
||||||
|
|
||||||
|
// TODO: remove after integrating our own node and having eth_newPendingTransactionFilter
|
||||||
|
Timer? _transactionsUpdateTimer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
WalletAddresses walletAddresses;
|
WalletAddresses walletAddresses;
|
||||||
|
|
||||||
|
@ -87,6 +92,8 @@ abstract class EthereumWalletBase
|
||||||
@observable
|
@observable
|
||||||
late ObservableMap<CryptoCurrency, ERC20Balance> balance;
|
late ObservableMap<CryptoCurrency, ERC20Balance> balance;
|
||||||
|
|
||||||
|
Completer<SharedPreferences> _sharedPrefs = Completer();
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
erc20TokensBox = await Hive.openBox<Erc20Token>(Erc20Token.boxName);
|
erc20TokensBox = await Hive.openBox<Erc20Token>(Erc20Token.boxName);
|
||||||
await walletAddresses.init();
|
await walletAddresses.init();
|
||||||
|
@ -100,7 +107,9 @@ abstract class EthereumWalletBase
|
||||||
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
|
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
|
||||||
try {
|
try {
|
||||||
if (priority is EthereumTransactionPriority) {
|
if (priority is EthereumTransactionPriority) {
|
||||||
return _gasPrice! * _priorityFees[priority.raw];
|
final priorityFee =
|
||||||
|
EtherAmount.fromUnitAndValue(EtherUnit.gwei, priority.tip).getInWei.toInt();
|
||||||
|
return (_gasPrice! + priorityFee) * (_estimatedGas ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -117,6 +126,7 @@ abstract class EthereumWalletBase
|
||||||
@override
|
@override
|
||||||
void close() {
|
void close() {
|
||||||
_client.stop();
|
_client.stop();
|
||||||
|
_transactionsUpdateTimer?.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -132,7 +142,8 @@ abstract class EthereumWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
_client.setListeners(_privateKey.address, _onNewTransaction);
|
_client.setListeners(_privateKey.address, _onNewTransaction);
|
||||||
_updateBalance();
|
|
||||||
|
_setTransactionUpdateTimer();
|
||||||
|
|
||||||
syncStatus = ConnectedSyncStatus();
|
syncStatus = ConnectedSyncStatus();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -149,7 +160,7 @@ abstract class EthereumWalletBase
|
||||||
BigInt totalAmount = BigInt.zero;
|
BigInt totalAmount = BigInt.zero;
|
||||||
int exponent =
|
int exponent =
|
||||||
_credentials.currency is Erc20Token ? (_credentials.currency as Erc20Token).decimal : 18;
|
_credentials.currency is Erc20Token ? (_credentials.currency as Erc20Token).decimal : 18;
|
||||||
BigInt amountToEthereumMultiplier = BigInt.from(pow(10, exponent));
|
num amountToEthereumMultiplier = pow(10, exponent);
|
||||||
|
|
||||||
if (hasMultiDestination) {
|
if (hasMultiDestination) {
|
||||||
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
|
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
|
||||||
|
@ -158,19 +169,20 @@ abstract class EthereumWalletBase
|
||||||
|
|
||||||
final totalOriginalAmount = EthereumFormatter.parseEthereumAmountToDouble(
|
final totalOriginalAmount = EthereumFormatter.parseEthereumAmountToDouble(
|
||||||
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)));
|
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)));
|
||||||
totalAmount = BigInt.from(totalOriginalAmount) * amountToEthereumMultiplier;
|
totalAmount = BigInt.from(totalOriginalAmount * amountToEthereumMultiplier);
|
||||||
|
|
||||||
if (_erc20Balance.balance < totalAmount) {
|
if (_erc20Balance.balance < totalAmount) {
|
||||||
throw EthereumTransactionCreationException(_credentials.currency);
|
throw EthereumTransactionCreationException(_credentials.currency);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final output = outputs.first;
|
final output = outputs.first;
|
||||||
final BigInt allAmount = _erc20Balance.balance - BigInt.from(feeRate(_credentials.priority!));
|
final BigInt allAmount =
|
||||||
|
_erc20Balance.balance - BigInt.from(calculateEstimatedFee(_credentials.priority!, null));
|
||||||
final totalOriginalAmount =
|
final totalOriginalAmount =
|
||||||
EthereumFormatter.parseEthereumAmountToDouble(output.formattedCryptoAmount ?? 0);
|
EthereumFormatter.parseEthereumAmountToDouble(output.formattedCryptoAmount ?? 0);
|
||||||
totalAmount = output.sendAll
|
totalAmount = output.sendAll
|
||||||
? allAmount
|
? allAmount
|
||||||
: BigInt.from(totalOriginalAmount) * amountToEthereumMultiplier;
|
: BigInt.from(totalOriginalAmount * amountToEthereumMultiplier);
|
||||||
|
|
||||||
if (_erc20Balance.balance < totalAmount) {
|
if (_erc20Balance.balance < totalAmount) {
|
||||||
throw EthereumTransactionCreationException(_credentials.currency);
|
throw EthereumTransactionCreationException(_credentials.currency);
|
||||||
|
@ -181,7 +193,7 @@ abstract class EthereumWalletBase
|
||||||
privateKey: _privateKey,
|
privateKey: _privateKey,
|
||||||
toAddress: _credentials.outputs.first.address,
|
toAddress: _credentials.outputs.first.address,
|
||||||
amount: totalAmount.toString(),
|
amount: totalAmount.toString(),
|
||||||
gas: _priorityFees[_credentials.priority!.raw],
|
gas: _estimatedGas!,
|
||||||
priority: _credentials.priority!,
|
priority: _credentials.priority!,
|
||||||
currency: _credentials.currency,
|
currency: _credentials.currency,
|
||||||
exponent: exponent,
|
exponent: exponent,
|
||||||
|
@ -193,11 +205,15 @@ abstract class EthereumWalletBase
|
||||||
return pendingEthereumTransaction;
|
return pendingEthereumTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateTransactions() async {
|
Future<void> _updateTransactions() async {
|
||||||
try {
|
try {
|
||||||
if (_isTransactionUpdating) {
|
if (_isTransactionUpdating) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
bool isEtherscanEnabled = (await _sharedPrefs.future).getBool("use_etherscan") ?? true;
|
||||||
|
if (!isEtherscanEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_isTransactionUpdating = true;
|
_isTransactionUpdating = true;
|
||||||
final transactions = await fetchTransactions();
|
final transactions = await fetchTransactions();
|
||||||
|
@ -279,14 +295,14 @@ abstract class EthereumWalletBase
|
||||||
try {
|
try {
|
||||||
syncStatus = AttemptingSyncStatus();
|
syncStatus = AttemptingSyncStatus();
|
||||||
await _updateBalance();
|
await _updateBalance();
|
||||||
await updateTransactions();
|
await _updateTransactions();
|
||||||
_gasPrice = await _client.getGasUnitPrice();
|
_gasPrice = await _client.getGasUnitPrice();
|
||||||
_priorityFees = await _client.getEstimatedGasForPriorities();
|
_estimatedGas = await _client.getEstimatedGas();
|
||||||
|
|
||||||
Timer.periodic(
|
Timer.periodic(
|
||||||
const Duration(minutes: 1), (timer) async => _gasPrice = await _client.getGasUnitPrice());
|
const Duration(minutes: 1), (timer) async => _gasPrice = await _client.getGasUnitPrice());
|
||||||
Timer.periodic(const Duration(minutes: 1),
|
Timer.periodic(const Duration(seconds: 10),
|
||||||
(timer) async => _priorityFees = await _client.getEstimatedGasForPriorities());
|
(timer) async => _estimatedGas = await _client.getEstimatedGas());
|
||||||
|
|
||||||
syncStatus = SyncedSyncStatus();
|
syncStatus = SyncedSyncStatus();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -294,18 +310,6 @@ abstract class EthereumWalletBase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int feeRate(TransactionPriority priority) {
|
|
||||||
try {
|
|
||||||
if (priority is EthereumTransactionPriority) {
|
|
||||||
return _priorityFees[priority.raw];
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
} catch (e) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||||
|
|
||||||
String toJSON() => json.encode({
|
String toJSON() => json.encode({
|
||||||
|
@ -414,9 +418,9 @@ abstract class EthereumWalletBase
|
||||||
Future<Erc20Token?> getErc20Token(String contractAddress) async =>
|
Future<Erc20Token?> getErc20Token(String contractAddress) async =>
|
||||||
await _client.getErc20Token(contractAddress);
|
await _client.getErc20Token(contractAddress);
|
||||||
|
|
||||||
void _onNewTransaction(FilterEvent event) {
|
void _onNewTransaction() {
|
||||||
_updateBalance();
|
_updateBalance();
|
||||||
// TODO: Add in transaction history
|
_updateTransactions();
|
||||||
}
|
}
|
||||||
|
|
||||||
void addInitialTokens() {
|
void addInitialTokens() {
|
||||||
|
@ -446,4 +450,24 @@ abstract class EthereumWalletBase
|
||||||
// Delete old name's dir and files
|
// Delete old name's dir and files
|
||||||
await Directory(currentDirPath).delete(recursive: true);
|
await Directory(currentDirPath).delete(recursive: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _setTransactionUpdateTimer() {
|
||||||
|
if (_transactionsUpdateTimer?.isActive ?? false) {
|
||||||
|
_transactionsUpdateTimer!.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
_transactionsUpdateTimer = Timer.periodic(Duration(seconds: 10), (_) {
|
||||||
|
_updateTransactions();
|
||||||
|
_updateBalance();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateEtherscanUsageState(bool isEnabled) {
|
||||||
|
if (isEnabled) {
|
||||||
|
_updateTransactions();
|
||||||
|
_setTransactionUpdateTimer();
|
||||||
|
} else {
|
||||||
|
_transactionsUpdateTimer?.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import 'package:cw_ethereum/ethereum_wallet.dart';
|
||||||
import 'package:cw_ethereum/ethereum_wallet_creation_credentials.dart';
|
import 'package:cw_ethereum/ethereum_wallet_creation_credentials.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
|
class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
|
||||||
EthereumRestoreWalletFromSeedCredentials, EthereumRestoreWalletFromWIFCredentials> {
|
EthereumRestoreWalletFromSeedCredentials, EthereumRestoreWalletFromWIFCredentials> {
|
||||||
|
@ -57,8 +58,12 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> remove(String wallet) async =>
|
Future<void> remove(String wallet) async {
|
||||||
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
|
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
|
||||||
|
final walletInfo = walletInfoSource.values.firstWhereOrNull(
|
||||||
|
(info) => info.id == WalletBase.idFor(wallet, getType()))!;
|
||||||
|
await walletInfoSource.delete(walletInfo.key);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<EthereumWallet> restoreFromKeys(credentials) {
|
Future<EthereumWallet> restoreFromKeys(credentials) {
|
||||||
|
@ -72,7 +77,7 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
|
||||||
throw EthereumMnemonicIsIncorrectException();
|
throw EthereumMnemonicIsIncorrectException();
|
||||||
}
|
}
|
||||||
|
|
||||||
final wallet = await EthereumWallet(
|
final wallet = EthereumWallet(
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
mnemonic: credentials.mnemonic,
|
mnemonic: credentials.mnemonic,
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
|
|
|
@ -19,6 +19,7 @@ dependencies:
|
||||||
ed25519_hd_key: ^2.2.0
|
ed25519_hd_key: ^2.2.0
|
||||||
hex: ^0.2.0
|
hex: ^0.2.0
|
||||||
http: ^0.13.4
|
http: ^0.13.4
|
||||||
|
shared_preferences: ^2.0.15
|
||||||
cw_core:
|
cw_core:
|
||||||
path: ../cw_core
|
path: ../cw_core
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,9 @@ The following are the system requirements to build CakeWallet for your Android d
|
||||||
|
|
||||||
```
|
```
|
||||||
Ubuntu >= 16.04
|
Ubuntu >= 16.04
|
||||||
Android SDK 28
|
Android SDK 29 or higher (better to have the latest one 33)
|
||||||
Android NDK 17c
|
Android NDK 17c
|
||||||
Flutter 2 or above
|
Flutter 3.7.x
|
||||||
```
|
```
|
||||||
|
|
||||||
## Building CakeWallet on Android
|
## Building CakeWallet on Android
|
||||||
|
@ -55,7 +55,7 @@ You may download and install the latest version of Android Studio [here](https:/
|
||||||
|
|
||||||
### 3. Installing Flutter
|
### 3. Installing Flutter
|
||||||
|
|
||||||
Need to install flutter with version `3.x.x`. For this please check section [Install Flutter manually](https://docs.flutter.dev/get-started/install/linux#install-flutter-manually).
|
Need to install flutter with version `3.7.x`. For this please check section [Install Flutter manually](https://docs.flutter.dev/get-started/install/linux#install-flutter-manually).
|
||||||
|
|
||||||
### 4. Verify Installations
|
### 4. Verify Installations
|
||||||
|
|
||||||
|
@ -66,9 +66,9 @@ Verify that the Android toolchain, Flutter, and Android Studio have been correct
|
||||||
The output of this command will appear like this, indicating successful installations. If there are problems with your installation, they **must** be corrected before proceeding.
|
The output of this command will appear like this, indicating successful installations. If there are problems with your installation, they **must** be corrected before proceeding.
|
||||||
```
|
```
|
||||||
Doctor summary (to see all details, run flutter doctor -v):
|
Doctor summary (to see all details, run flutter doctor -v):
|
||||||
[✓] Flutter (Channel stable, 3.x.x, on Linux, locale en_US.UTF-8)
|
[✓] Flutter (Channel stable, 3.7.x, on Linux, locale en_US.UTF-8)
|
||||||
[✓] Android toolchain - develop for Android devices (Android SDK version 28)
|
[✓] Android toolchain - develop for Android devices (Android SDK version 29 or higher)
|
||||||
[✓] Android Studio (version 4.0)
|
[✓] Android Studio (version 4.0 or higher)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. Generate a secure keystore for Android
|
### 5. Generate a secure keystore for Android
|
||||||
|
|
|
@ -25,6 +25,10 @@ class AuthService with Store {
|
||||||
Routes.setupPin,
|
Routes.setupPin,
|
||||||
Routes.setup_2faPage,
|
Routes.setup_2faPage,
|
||||||
Routes.modify2FAPage,
|
Routes.modify2FAPage,
|
||||||
|
Routes.newWallet,
|
||||||
|
Routes.newWalletType,
|
||||||
|
Routes.addressBookAddContact,
|
||||||
|
Routes.restoreOptions,
|
||||||
];
|
];
|
||||||
|
|
||||||
final FlutterSecureStorage secureStorage;
|
final FlutterSecureStorage secureStorage;
|
||||||
|
@ -81,21 +85,26 @@ class AuthService with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> authenticateAction(BuildContext context,
|
Future<void> authenticateAction(BuildContext context,
|
||||||
{Function(bool)? onAuthSuccess, String? route, Object? arguments}) async {
|
{Function(bool)? onAuthSuccess,
|
||||||
|
String? route,
|
||||||
|
Object? arguments,
|
||||||
|
required bool conditionToDetermineIfToUse2FA}) async {
|
||||||
assert(route != null || onAuthSuccess != null,
|
assert(route != null || onAuthSuccess != null,
|
||||||
'Either route or onAuthSuccess param must be passed.');
|
'Either route or onAuthSuccess param must be passed.');
|
||||||
|
|
||||||
if (!requireAuth() && !_alwaysAuthenticateRoutes.contains(route)) {
|
if (!conditionToDetermineIfToUse2FA) {
|
||||||
if (onAuthSuccess != null) {
|
if (!requireAuth() && !_alwaysAuthenticateRoutes.contains(route)) {
|
||||||
onAuthSuccess(true);
|
if (onAuthSuccess != null) {
|
||||||
} else {
|
onAuthSuccess(true);
|
||||||
Navigator.of(context).pushNamed(
|
} else {
|
||||||
route ?? '',
|
Navigator.of(context).pushNamed(
|
||||||
arguments: arguments,
|
route ?? '',
|
||||||
);
|
arguments: arguments,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Navigator.of(context).pushNamed(Routes.auth,
|
Navigator.of(context).pushNamed(Routes.auth,
|
||||||
|
@ -104,7 +113,7 @@ class AuthService with Store {
|
||||||
onAuthSuccess?.call(false);
|
onAuthSuccess?.call(false);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
if (settingsStore.useTOTP2FA) {
|
if (settingsStore.useTOTP2FA && conditionToDetermineIfToUse2FA) {
|
||||||
auth.close(
|
auth.close(
|
||||||
route: Routes.totpAuthCodePage,
|
route: Routes.totpAuthCodePage,
|
||||||
arguments: TotpAuthArgumentsModel(
|
arguments: TotpAuthArgumentsModel(
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
@ -19,8 +20,8 @@ import 'package:cake_wallet/wallet_types.g.dart';
|
||||||
import 'package:cake_backup/backup.dart' as cake_backup;
|
import 'package:cake_backup/backup.dart' as cake_backup;
|
||||||
|
|
||||||
class BackupService {
|
class BackupService {
|
||||||
BackupService(this._flutterSecureStorage, this._walletInfoSource,
|
BackupService(
|
||||||
this._keyService, this._sharedPreferences)
|
this._flutterSecureStorage, this._walletInfoSource, this._keyService, this._sharedPreferences)
|
||||||
: _cipher = Cryptography.instance.chacha20Poly1305Aead(),
|
: _cipher = Cryptography.instance.chacha20Poly1305Aead(),
|
||||||
_correctWallets = <WalletInfo>[];
|
_correctWallets = <WalletInfo>[];
|
||||||
|
|
||||||
|
@ -67,9 +68,8 @@ class BackupService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated('Use v2 instead')
|
@Deprecated('Use v2 instead')
|
||||||
Future<Uint8List> _exportBackupV1(String password,
|
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.');
|
||||||
=> throw Exception('Deprecated. Export for backups v1 is deprecated. Please use export v2.');
|
|
||||||
|
|
||||||
Future<Uint8List> _exportBackupV2(String password) async {
|
Future<Uint8List> _exportBackupV2(String password) async {
|
||||||
final zipEncoder = ZipFileEncoder();
|
final zipEncoder = ZipFileEncoder();
|
||||||
|
@ -112,8 +112,7 @@ class BackupService {
|
||||||
return await _encryptV2(content, password);
|
return await _encryptV2(content, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _importBackupV1(Uint8List data, String password,
|
Future<void> _importBackupV1(Uint8List data, String password, {required String nonce}) async {
|
||||||
{required String nonce}) async {
|
|
||||||
final appDir = await getApplicationDocumentsDirectory();
|
final appDir = await getApplicationDocumentsDirectory();
|
||||||
final decryptedData = await _decryptV1(data, password, nonce);
|
final decryptedData = await _decryptV1(data, password, nonce);
|
||||||
final zip = ZipDecoder().decodeBytes(decryptedData);
|
final zip = ZipDecoder().decodeBytes(decryptedData);
|
||||||
|
@ -161,10 +160,8 @@ class BackupService {
|
||||||
|
|
||||||
Future<void> _verifyWallets() async {
|
Future<void> _verifyWallets() async {
|
||||||
final walletInfoSource = await _reloadHiveWalletInfoBox();
|
final walletInfoSource = await _reloadHiveWalletInfoBox();
|
||||||
_correctWallets = walletInfoSource
|
_correctWallets =
|
||||||
.values
|
walletInfoSource.values.where((info) => availableWalletTypes.contains(info.type)).toList();
|
||||||
.where((info) => availableWalletTypes.contains(info.type))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
if (_correctWallets.isEmpty) {
|
if (_correctWallets.isEmpty) {
|
||||||
throw Exception('Correct wallets not detected');
|
throw Exception('Correct wallets not detected');
|
||||||
|
@ -191,14 +188,12 @@ class BackupService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final data =
|
final data = json.decode(preferencesFile.readAsStringSync()) as Map<String, dynamic>;
|
||||||
json.decode(preferencesFile.readAsStringSync()) as Map<String, dynamic>;
|
|
||||||
String currentWalletName = data[PreferencesKey.currentWalletName] as String;
|
String currentWalletName = data[PreferencesKey.currentWalletName] as String;
|
||||||
int currentWalletType = data[PreferencesKey.currentWalletType] as int;
|
int currentWalletType = data[PreferencesKey.currentWalletType] as int;
|
||||||
|
|
||||||
final isCorrentCurrentWallet = _correctWallets
|
final isCorrentCurrentWallet = _correctWallets
|
||||||
.any((info) => info.name == currentWalletName &&
|
.any((info) => info.name == currentWalletName && info.type.index == currentWalletType);
|
||||||
info.type.index == currentWalletType);
|
|
||||||
|
|
||||||
if (!isCorrentCurrentWallet) {
|
if (!isCorrentCurrentWallet) {
|
||||||
currentWalletName = _correctWallets.first.name;
|
currentWalletName = _correctWallets.first.name;
|
||||||
|
@ -212,110 +207,159 @@ class BackupService {
|
||||||
final isAppSecure = data[PreferencesKey.isAppSecureKey] as bool?;
|
final isAppSecure = data[PreferencesKey.isAppSecureKey] as bool?;
|
||||||
final disableBuy = data[PreferencesKey.disableBuyKey] as bool?;
|
final disableBuy = data[PreferencesKey.disableBuyKey] as bool?;
|
||||||
final disableSell = data[PreferencesKey.disableSellKey] as bool?;
|
final disableSell = data[PreferencesKey.disableSellKey] as bool?;
|
||||||
final currentTransactionPriorityKeyLegacy = data[PreferencesKey.currentTransactionPriorityKeyLegacy] as int?;
|
final currentTransactionPriorityKeyLegacy =
|
||||||
final allowBiometricalAuthentication = data[PreferencesKey.allowBiometricalAuthenticationKey] as bool?;
|
data[PreferencesKey.currentTransactionPriorityKeyLegacy] as int?;
|
||||||
final currentBitcoinElectrumSererId = data[PreferencesKey.currentBitcoinElectrumSererIdKey] as int?;
|
final allowBiometricalAuthentication =
|
||||||
|
data[PreferencesKey.allowBiometricalAuthenticationKey] as bool?;
|
||||||
|
final currentBitcoinElectrumSererId =
|
||||||
|
data[PreferencesKey.currentBitcoinElectrumSererIdKey] as int?;
|
||||||
final currentLanguageCode = data[PreferencesKey.currentLanguageCode] as String?;
|
final currentLanguageCode = data[PreferencesKey.currentLanguageCode] as String?;
|
||||||
final displayActionListMode = data[PreferencesKey.displayActionListModeKey] as int?;
|
final displayActionListMode = data[PreferencesKey.displayActionListModeKey] as int?;
|
||||||
final fiatApiMode = data[PreferencesKey.currentFiatApiModeKey] as int?;
|
final fiatApiMode = data[PreferencesKey.currentFiatApiModeKey] as int?;
|
||||||
final currentPinLength = data[PreferencesKey.currentPinLength] as int?;
|
final currentPinLength = data[PreferencesKey.currentPinLength] as int?;
|
||||||
final currentTheme = data[PreferencesKey.currentTheme] as int?;
|
final currentTheme = data[PreferencesKey.currentTheme] as int?;
|
||||||
final exchangeStatus = data[PreferencesKey.exchangeStatusKey] as int?;
|
final exchangeStatus = data[PreferencesKey.exchangeStatusKey] as int?;
|
||||||
final currentDefaultSettingsMigrationVersion = data[PreferencesKey.currentDefaultSettingsMigrationVersion] as int?;
|
final currentDefaultSettingsMigrationVersion =
|
||||||
|
data[PreferencesKey.currentDefaultSettingsMigrationVersion] as int?;
|
||||||
final moneroTransactionPriority = data[PreferencesKey.moneroTransactionPriority] as int?;
|
final moneroTransactionPriority = data[PreferencesKey.moneroTransactionPriority] as int?;
|
||||||
final bitcoinTransactionPriority = data[PreferencesKey.bitcoinTransactionPriority] as int?;
|
final bitcoinTransactionPriority = data[PreferencesKey.bitcoinTransactionPriority] as int?;
|
||||||
|
final selectedCake2FAPreset = data[PreferencesKey.selectedCake2FAPreset] as int?;
|
||||||
|
final shouldRequireTOTP2FAForAccessingWallet =
|
||||||
|
data[PreferencesKey.shouldRequireTOTP2FAForAccessingWallet] as bool?;
|
||||||
|
final shouldRequireTOTP2FAForSendsToContact =
|
||||||
|
data[PreferencesKey.shouldRequireTOTP2FAForSendsToContact] as bool?;
|
||||||
|
final shouldRequireTOTP2FAForSendsToNonContact =
|
||||||
|
data[PreferencesKey.shouldRequireTOTP2FAForSendsToNonContact] as bool?;
|
||||||
|
final shouldRequireTOTP2FAForSendsToInternalWallets =
|
||||||
|
data[PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets] as bool?;
|
||||||
|
final shouldRequireTOTP2FAForExchangesToInternalWallets =
|
||||||
|
data[PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets] as bool?;
|
||||||
|
final shouldRequireTOTP2FAForAddingContacts =
|
||||||
|
data[PreferencesKey.shouldRequireTOTP2FAForAddingContacts] as bool?;
|
||||||
|
final shouldRequireTOTP2FAForCreatingNewWallets =
|
||||||
|
data[PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets] as bool?;
|
||||||
|
final shouldRequireTOTP2FAForAllSecurityAndBackupSettings =
|
||||||
|
data[PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings] as bool?;
|
||||||
final sortBalanceTokensBy = data[PreferencesKey.sortBalanceBy] as int?;
|
final sortBalanceTokensBy = data[PreferencesKey.sortBalanceBy] as int?;
|
||||||
final pinNativeTokenAtTop = data[PreferencesKey.pinNativeTokenAtTop] as bool?;
|
final pinNativeTokenAtTop = data[PreferencesKey.pinNativeTokenAtTop] as bool?;
|
||||||
|
final useEtherscan = data[PreferencesKey.useEtherscan] as bool?;
|
||||||
|
|
||||||
await _sharedPreferences.setString(PreferencesKey.currentWalletName,
|
await _sharedPreferences.setString(PreferencesKey.currentWalletName, currentWalletName);
|
||||||
currentWalletName);
|
|
||||||
|
|
||||||
if (currentNodeId != null)
|
if (currentNodeId != null)
|
||||||
await _sharedPreferences.setInt(PreferencesKey.currentNodeIdKey,
|
await _sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, currentNodeId);
|
||||||
currentNodeId);
|
|
||||||
|
|
||||||
if (currentBalanceDisplayMode != null)
|
if (currentBalanceDisplayMode != null)
|
||||||
await _sharedPreferences.setInt(PreferencesKey.currentBalanceDisplayModeKey,
|
await _sharedPreferences.setInt(
|
||||||
currentBalanceDisplayMode);
|
PreferencesKey.currentBalanceDisplayModeKey, currentBalanceDisplayMode);
|
||||||
|
|
||||||
await _sharedPreferences.setInt(PreferencesKey.currentWalletType,
|
await _sharedPreferences.setInt(PreferencesKey.currentWalletType, currentWalletType);
|
||||||
currentWalletType);
|
|
||||||
|
|
||||||
if (currentFiatCurrency != null)
|
if (currentFiatCurrency != null)
|
||||||
await _sharedPreferences.setString(PreferencesKey.currentFiatCurrencyKey,
|
await _sharedPreferences.setString(
|
||||||
currentFiatCurrency);
|
PreferencesKey.currentFiatCurrencyKey, currentFiatCurrency);
|
||||||
|
|
||||||
if (shouldSaveRecipientAddress != null)
|
if (shouldSaveRecipientAddress != null)
|
||||||
await _sharedPreferences.setBool(
|
await _sharedPreferences.setBool(
|
||||||
PreferencesKey.shouldSaveRecipientAddressKey,
|
PreferencesKey.shouldSaveRecipientAddressKey, shouldSaveRecipientAddress);
|
||||||
shouldSaveRecipientAddress);
|
|
||||||
|
|
||||||
if (isAppSecure != null)
|
if (isAppSecure != null)
|
||||||
await _sharedPreferences.setBool(
|
await _sharedPreferences.setBool(PreferencesKey.isAppSecureKey, isAppSecure);
|
||||||
PreferencesKey.isAppSecureKey,
|
|
||||||
isAppSecure);
|
|
||||||
|
|
||||||
if (disableBuy != null)
|
if (disableBuy != null)
|
||||||
await _sharedPreferences.setBool(
|
await _sharedPreferences.setBool(PreferencesKey.disableBuyKey, disableBuy);
|
||||||
PreferencesKey.disableBuyKey,
|
|
||||||
disableBuy);
|
|
||||||
|
|
||||||
if (disableSell != null)
|
if (disableSell != null)
|
||||||
await _sharedPreferences.setBool(
|
await _sharedPreferences.setBool(PreferencesKey.disableSellKey, disableSell);
|
||||||
PreferencesKey.disableSellKey,
|
|
||||||
disableSell);
|
|
||||||
|
|
||||||
if (currentTransactionPriorityKeyLegacy != null)
|
if (currentTransactionPriorityKeyLegacy != null)
|
||||||
await _sharedPreferences.setInt(
|
await _sharedPreferences.setInt(
|
||||||
PreferencesKey.currentTransactionPriorityKeyLegacy,
|
PreferencesKey.currentTransactionPriorityKeyLegacy, currentTransactionPriorityKeyLegacy);
|
||||||
currentTransactionPriorityKeyLegacy);
|
|
||||||
|
|
||||||
if (allowBiometricalAuthentication != null)
|
if (allowBiometricalAuthentication != null)
|
||||||
await _sharedPreferences.setBool(
|
await _sharedPreferences.setBool(
|
||||||
PreferencesKey.allowBiometricalAuthenticationKey,
|
PreferencesKey.allowBiometricalAuthenticationKey, allowBiometricalAuthentication);
|
||||||
allowBiometricalAuthentication);
|
|
||||||
|
|
||||||
if (currentBitcoinElectrumSererId != null)
|
if (currentBitcoinElectrumSererId != null)
|
||||||
await _sharedPreferences.setInt(
|
await _sharedPreferences.setInt(
|
||||||
PreferencesKey.currentBitcoinElectrumSererIdKey,
|
PreferencesKey.currentBitcoinElectrumSererIdKey, currentBitcoinElectrumSererId);
|
||||||
currentBitcoinElectrumSererId);
|
|
||||||
|
|
||||||
if (currentLanguageCode != null)
|
if (currentLanguageCode != null)
|
||||||
await _sharedPreferences.setString(PreferencesKey.currentLanguageCode,
|
await _sharedPreferences.setString(PreferencesKey.currentLanguageCode, currentLanguageCode);
|
||||||
currentLanguageCode);
|
|
||||||
|
|
||||||
if (displayActionListMode != null)
|
if (displayActionListMode != null)
|
||||||
await _sharedPreferences.setInt(PreferencesKey.displayActionListModeKey,
|
await _sharedPreferences.setInt(
|
||||||
displayActionListMode);
|
PreferencesKey.displayActionListModeKey, displayActionListMode);
|
||||||
|
|
||||||
if (fiatApiMode != null)
|
if (fiatApiMode != null)
|
||||||
await _sharedPreferences.setInt(PreferencesKey.currentFiatApiModeKey,
|
await _sharedPreferences.setInt(PreferencesKey.currentFiatApiModeKey, fiatApiMode);
|
||||||
fiatApiMode);
|
|
||||||
|
|
||||||
if (currentPinLength != null)
|
if (currentPinLength != null)
|
||||||
await _sharedPreferences.setInt(PreferencesKey.currentPinLength,
|
await _sharedPreferences.setInt(PreferencesKey.currentPinLength, currentPinLength);
|
||||||
currentPinLength);
|
|
||||||
|
|
||||||
if (currentTheme != null)
|
if (currentTheme != null)
|
||||||
await _sharedPreferences.setInt(
|
await _sharedPreferences.setInt(PreferencesKey.currentTheme, currentTheme);
|
||||||
PreferencesKey.currentTheme, currentTheme);
|
|
||||||
|
|
||||||
if (exchangeStatus != null)
|
if (exchangeStatus != null)
|
||||||
await _sharedPreferences.setInt(
|
await _sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, exchangeStatus);
|
||||||
PreferencesKey.exchangeStatusKey, exchangeStatus);
|
|
||||||
|
|
||||||
if (currentDefaultSettingsMigrationVersion != null)
|
if (currentDefaultSettingsMigrationVersion != null)
|
||||||
await _sharedPreferences.setInt(
|
await _sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion,
|
||||||
PreferencesKey.currentDefaultSettingsMigrationVersion,
|
currentDefaultSettingsMigrationVersion);
|
||||||
currentDefaultSettingsMigrationVersion);
|
|
||||||
|
|
||||||
if (moneroTransactionPriority != null)
|
if (moneroTransactionPriority != null)
|
||||||
await _sharedPreferences.setInt(PreferencesKey.moneroTransactionPriority,
|
await _sharedPreferences.setInt(
|
||||||
moneroTransactionPriority);
|
PreferencesKey.moneroTransactionPriority, moneroTransactionPriority);
|
||||||
|
|
||||||
if (bitcoinTransactionPriority != null)
|
if (bitcoinTransactionPriority != null)
|
||||||
await _sharedPreferences.setInt(PreferencesKey.bitcoinTransactionPriority,
|
await _sharedPreferences.setInt(
|
||||||
bitcoinTransactionPriority);
|
PreferencesKey.bitcoinTransactionPriority, bitcoinTransactionPriority);
|
||||||
|
|
||||||
|
if (selectedCake2FAPreset != null)
|
||||||
|
await _sharedPreferences.setInt(PreferencesKey.selectedCake2FAPreset, selectedCake2FAPreset);
|
||||||
|
|
||||||
|
if (shouldRequireTOTP2FAForAccessingWallet != null)
|
||||||
|
await _sharedPreferences.setBool(PreferencesKey.shouldRequireTOTP2FAForAccessingWallet,
|
||||||
|
shouldRequireTOTP2FAForAccessingWallet);
|
||||||
|
|
||||||
|
if (shouldRequireTOTP2FAForSendsToContact != null)
|
||||||
|
await _sharedPreferences.setBool(PreferencesKey.shouldRequireTOTP2FAForSendsToContact,
|
||||||
|
shouldRequireTOTP2FAForSendsToContact);
|
||||||
|
|
||||||
|
if (shouldRequireTOTP2FAForSendsToNonContact != null)
|
||||||
|
await _sharedPreferences.setBool(PreferencesKey.shouldRequireTOTP2FAForSendsToNonContact,
|
||||||
|
shouldRequireTOTP2FAForSendsToNonContact);
|
||||||
|
|
||||||
|
if (shouldRequireTOTP2FAForSendsToInternalWallets != null)
|
||||||
|
await _sharedPreferences.setBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets,
|
||||||
|
shouldRequireTOTP2FAForSendsToInternalWallets);
|
||||||
|
|
||||||
|
if (shouldRequireTOTP2FAForExchangesToInternalWallets != null)
|
||||||
|
await _sharedPreferences.setBool(
|
||||||
|
PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets,
|
||||||
|
shouldRequireTOTP2FAForExchangesToInternalWallets);
|
||||||
|
|
||||||
|
if (shouldRequireTOTP2FAForAddingContacts != null)
|
||||||
|
await _sharedPreferences.setBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts,
|
||||||
|
shouldRequireTOTP2FAForAddingContacts);
|
||||||
|
|
||||||
|
if (shouldRequireTOTP2FAForCreatingNewWallets != null)
|
||||||
|
await _sharedPreferences.setBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||||
|
shouldRequireTOTP2FAForCreatingNewWallets);
|
||||||
|
|
||||||
|
if (shouldRequireTOTP2FAForAllSecurityAndBackupSettings != null)
|
||||||
|
await _sharedPreferences.setBool(
|
||||||
|
PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||||
|
shouldRequireTOTP2FAForAllSecurityAndBackupSettings);
|
||||||
|
|
||||||
|
if (sortBalanceTokensBy != null)
|
||||||
|
await _sharedPreferences.setInt(PreferencesKey.sortBalanceBy, sortBalanceTokensBy);
|
||||||
|
|
||||||
|
if (pinNativeTokenAtTop != null)
|
||||||
|
await _sharedPreferences.setBool(PreferencesKey.pinNativeTokenAtTop, pinNativeTokenAtTop);
|
||||||
|
|
||||||
|
if (useEtherscan != null)
|
||||||
|
await _sharedPreferences.setBool(PreferencesKey.useEtherscan, useEtherscan);
|
||||||
|
|
||||||
if (sortBalanceTokensBy != null)
|
if (sortBalanceTokensBy != null)
|
||||||
await _sharedPreferences.setInt(PreferencesKey.sortBalanceBy, sortBalanceTokensBy);
|
await _sharedPreferences.setInt(PreferencesKey.sortBalanceBy, sortBalanceTokensBy);
|
||||||
|
@ -327,31 +371,27 @@ class BackupService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _importKeychainDumpV1(String password,
|
Future<void> _importKeychainDumpV1(String password,
|
||||||
{required String nonce,
|
{required String nonce, String keychainSalt = secrets.backupKeychainSalt}) async {
|
||||||
String keychainSalt = secrets.backupKeychainSalt}) async {
|
|
||||||
final appDir = await getApplicationDocumentsDirectory();
|
final appDir = await getApplicationDocumentsDirectory();
|
||||||
final keychainDumpFile = File('${appDir.path}/~_keychain_dump');
|
final keychainDumpFile = File('${appDir.path}/~_keychain_dump');
|
||||||
final decryptedKeychainDumpFileData = await _decryptV1(
|
final decryptedKeychainDumpFileData =
|
||||||
keychainDumpFile.readAsBytesSync(), '$keychainSalt$password', nonce);
|
await _decryptV1(keychainDumpFile.readAsBytesSync(), '$keychainSalt$password', nonce);
|
||||||
final keychainJSON = json.decode(utf8.decode(decryptedKeychainDumpFileData))
|
final keychainJSON =
|
||||||
as Map<String, dynamic>;
|
json.decode(utf8.decode(decryptedKeychainDumpFileData)) as Map<String, dynamic>;
|
||||||
final keychainWalletsInfo = keychainJSON['wallets'] as List;
|
final keychainWalletsInfo = keychainJSON['wallets'] as List;
|
||||||
final decodedPin = keychainJSON['pin'] as String;
|
final decodedPin = keychainJSON['pin'] as String;
|
||||||
final pinCodeKey = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
|
final pinCodeKey = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
|
||||||
final backupPasswordKey =
|
final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
|
||||||
generateStoreKeyFor(key: SecretStoreKey.backupPassword);
|
|
||||||
final backupPassword = keychainJSON[backupPasswordKey] as String;
|
final backupPassword = keychainJSON[backupPasswordKey] as String;
|
||||||
|
|
||||||
await _flutterSecureStorage.write(
|
await _flutterSecureStorage.write(key: backupPasswordKey, value: backupPassword);
|
||||||
key: backupPasswordKey, value: backupPassword);
|
|
||||||
|
|
||||||
keychainWalletsInfo.forEach((dynamic rawInfo) async {
|
keychainWalletsInfo.forEach((dynamic rawInfo) async {
|
||||||
final info = rawInfo as Map<String, dynamic>;
|
final info = rawInfo as Map<String, dynamic>;
|
||||||
await importWalletKeychainInfo(info);
|
await importWalletKeychainInfo(info);
|
||||||
});
|
});
|
||||||
|
|
||||||
await _flutterSecureStorage.write(
|
await _flutterSecureStorage.write(key: pinCodeKey, value: encodedPinCode(pin: decodedPin));
|
||||||
key: pinCodeKey, value: encodedPinCode(pin: decodedPin));
|
|
||||||
|
|
||||||
keychainDumpFile.deleteSync();
|
keychainDumpFile.deleteSync();
|
||||||
}
|
}
|
||||||
|
@ -360,27 +400,24 @@ class BackupService {
|
||||||
{String keychainSalt = secrets.backupKeychainSalt}) async {
|
{String keychainSalt = secrets.backupKeychainSalt}) async {
|
||||||
final appDir = await getApplicationDocumentsDirectory();
|
final appDir = await getApplicationDocumentsDirectory();
|
||||||
final keychainDumpFile = File('${appDir.path}/~_keychain_dump');
|
final keychainDumpFile = File('${appDir.path}/~_keychain_dump');
|
||||||
final decryptedKeychainDumpFileData = await _decryptV2(
|
final decryptedKeychainDumpFileData =
|
||||||
keychainDumpFile.readAsBytesSync(), '$keychainSalt$password');
|
await _decryptV2(keychainDumpFile.readAsBytesSync(), '$keychainSalt$password');
|
||||||
final keychainJSON = json.decode(utf8.decode(decryptedKeychainDumpFileData))
|
final keychainJSON =
|
||||||
as Map<String, dynamic>;
|
json.decode(utf8.decode(decryptedKeychainDumpFileData)) as Map<String, dynamic>;
|
||||||
final keychainWalletsInfo = keychainJSON['wallets'] as List;
|
final keychainWalletsInfo = keychainJSON['wallets'] as List;
|
||||||
final decodedPin = keychainJSON['pin'] as String;
|
final decodedPin = keychainJSON['pin'] as String;
|
||||||
final pinCodeKey = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
|
final pinCodeKey = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
|
||||||
final backupPasswordKey =
|
final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
|
||||||
generateStoreKeyFor(key: SecretStoreKey.backupPassword);
|
|
||||||
final backupPassword = keychainJSON[backupPasswordKey] as String;
|
final backupPassword = keychainJSON[backupPasswordKey] as String;
|
||||||
|
|
||||||
await _flutterSecureStorage.write(
|
await _flutterSecureStorage.write(key: backupPasswordKey, value: backupPassword);
|
||||||
key: backupPasswordKey, value: backupPassword);
|
|
||||||
|
|
||||||
keychainWalletsInfo.forEach((dynamic rawInfo) async {
|
keychainWalletsInfo.forEach((dynamic rawInfo) async {
|
||||||
final info = rawInfo as Map<String, dynamic>;
|
final info = rawInfo as Map<String, dynamic>;
|
||||||
await importWalletKeychainInfo(info);
|
await importWalletKeychainInfo(info);
|
||||||
});
|
});
|
||||||
|
|
||||||
await _flutterSecureStorage.write(
|
await _flutterSecureStorage.write(key: pinCodeKey, value: encodedPinCode(pin: decodedPin));
|
||||||
key: pinCodeKey, value: encodedPinCode(pin: decodedPin));
|
|
||||||
|
|
||||||
keychainDumpFile.deleteSync();
|
keychainDumpFile.deleteSync();
|
||||||
}
|
}
|
||||||
|
@ -394,35 +431,26 @@ class BackupService {
|
||||||
|
|
||||||
@Deprecated('Use v2 instead')
|
@Deprecated('Use v2 instead')
|
||||||
Future<Uint8List> _exportKeychainDumpV1(String password,
|
Future<Uint8List> _exportKeychainDumpV1(String password,
|
||||||
{required String nonce,
|
{required String nonce, String keychainSalt = secrets.backupKeychainSalt}) async =>
|
||||||
String keychainSalt = secrets.backupKeychainSalt}) async
|
throw Exception('Deprecated');
|
||||||
=> throw Exception('Deprecated');
|
|
||||||
|
|
||||||
Future<Uint8List> _exportKeychainDumpV2(String password,
|
Future<Uint8List> _exportKeychainDumpV2(String password,
|
||||||
{String keychainSalt = secrets.backupKeychainSalt}) async {
|
{String keychainSalt = secrets.backupKeychainSalt}) async {
|
||||||
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
|
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
|
||||||
final encodedPin = await _flutterSecureStorage.read(key: key);
|
final encodedPin = await _flutterSecureStorage.read(key: key);
|
||||||
final decodedPin = decodedPinCode(pin: encodedPin!);
|
final decodedPin = decodedPinCode(pin: encodedPin!);
|
||||||
final wallets =
|
final wallets = await Future.wait(_walletInfoSource.values.map((walletInfo) async {
|
||||||
await Future.wait(_walletInfoSource.values.map((walletInfo) async {
|
|
||||||
return {
|
return {
|
||||||
'name': walletInfo.name,
|
'name': walletInfo.name,
|
||||||
'type': walletInfo.type.toString(),
|
'type': walletInfo.type.toString(),
|
||||||
'password':
|
'password': await _keyService.getWalletPassword(walletName: walletInfo.name)
|
||||||
await _keyService.getWalletPassword(walletName: walletInfo.name)
|
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
final backupPasswordKey =
|
final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
|
||||||
generateStoreKeyFor(key: SecretStoreKey.backupPassword);
|
final backupPassword = await _flutterSecureStorage.read(key: backupPasswordKey);
|
||||||
final backupPassword =
|
final data = utf8.encode(
|
||||||
await _flutterSecureStorage.read(key: backupPasswordKey);
|
json.encode({'pin': decodedPin, 'wallets': wallets, backupPasswordKey: backupPassword}));
|
||||||
final data = utf8.encode(json.encode({
|
final encrypted = await _encryptV2(Uint8List.fromList(data), '$keychainSalt$password');
|
||||||
'pin': decodedPin,
|
|
||||||
'wallets': wallets,
|
|
||||||
backupPasswordKey: backupPassword
|
|
||||||
}));
|
|
||||||
final encrypted = await _encryptV2(
|
|
||||||
Uint8List.fromList(data), '$keychainSalt$password');
|
|
||||||
|
|
||||||
return encrypted;
|
return encrypted;
|
||||||
}
|
}
|
||||||
|
@ -431,50 +459,63 @@ class BackupService {
|
||||||
final preferences = <String, dynamic>{
|
final preferences = <String, dynamic>{
|
||||||
PreferencesKey.currentWalletName:
|
PreferencesKey.currentWalletName:
|
||||||
_sharedPreferences.getString(PreferencesKey.currentWalletName),
|
_sharedPreferences.getString(PreferencesKey.currentWalletName),
|
||||||
PreferencesKey.currentNodeIdKey:
|
PreferencesKey.currentNodeIdKey: _sharedPreferences.getInt(PreferencesKey.currentNodeIdKey),
|
||||||
_sharedPreferences.getInt(PreferencesKey.currentNodeIdKey),
|
PreferencesKey.currentBalanceDisplayModeKey:
|
||||||
PreferencesKey.currentBalanceDisplayModeKey: _sharedPreferences
|
_sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey),
|
||||||
.getInt(PreferencesKey.currentBalanceDisplayModeKey),
|
PreferencesKey.currentWalletType: _sharedPreferences.getInt(PreferencesKey.currentWalletType),
|
||||||
PreferencesKey.currentWalletType:
|
|
||||||
_sharedPreferences.getInt(PreferencesKey.currentWalletType),
|
|
||||||
PreferencesKey.currentFiatCurrencyKey:
|
PreferencesKey.currentFiatCurrencyKey:
|
||||||
_sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey),
|
_sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey),
|
||||||
PreferencesKey.shouldSaveRecipientAddressKey: _sharedPreferences
|
PreferencesKey.shouldSaveRecipientAddressKey:
|
||||||
.getBool(PreferencesKey.shouldSaveRecipientAddressKey),
|
_sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey),
|
||||||
PreferencesKey.disableBuyKey: _sharedPreferences
|
PreferencesKey.disableBuyKey: _sharedPreferences.getBool(PreferencesKey.disableBuyKey),
|
||||||
.getBool(PreferencesKey.disableBuyKey),
|
PreferencesKey.disableSellKey: _sharedPreferences.getBool(PreferencesKey.disableSellKey),
|
||||||
PreferencesKey.disableSellKey: _sharedPreferences
|
|
||||||
.getBool(PreferencesKey.disableSellKey),
|
|
||||||
PreferencesKey.isDarkThemeLegacy:
|
PreferencesKey.isDarkThemeLegacy:
|
||||||
_sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy),
|
_sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy),
|
||||||
PreferencesKey.currentPinLength:
|
PreferencesKey.currentPinLength: _sharedPreferences.getInt(PreferencesKey.currentPinLength),
|
||||||
_sharedPreferences.getInt(PreferencesKey.currentPinLength),
|
PreferencesKey.currentTransactionPriorityKeyLegacy:
|
||||||
PreferencesKey.currentTransactionPriorityKeyLegacy: _sharedPreferences
|
_sharedPreferences.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy),
|
||||||
.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy),
|
PreferencesKey.allowBiometricalAuthenticationKey:
|
||||||
PreferencesKey.allowBiometricalAuthenticationKey: _sharedPreferences
|
_sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey),
|
||||||
.getBool(PreferencesKey.allowBiometricalAuthenticationKey),
|
PreferencesKey.currentBitcoinElectrumSererIdKey:
|
||||||
PreferencesKey.currentBitcoinElectrumSererIdKey: _sharedPreferences
|
_sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey),
|
||||||
.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey),
|
|
||||||
PreferencesKey.currentLanguageCode:
|
PreferencesKey.currentLanguageCode:
|
||||||
_sharedPreferences.getString(PreferencesKey.currentLanguageCode),
|
_sharedPreferences.getString(PreferencesKey.currentLanguageCode),
|
||||||
PreferencesKey.displayActionListModeKey:
|
PreferencesKey.displayActionListModeKey:
|
||||||
_sharedPreferences.getInt(PreferencesKey.displayActionListModeKey),
|
_sharedPreferences.getInt(PreferencesKey.displayActionListModeKey),
|
||||||
PreferencesKey.currentTheme:
|
PreferencesKey.currentTheme: _sharedPreferences.getInt(PreferencesKey.currentTheme),
|
||||||
_sharedPreferences.getInt(PreferencesKey.currentTheme),
|
PreferencesKey.exchangeStatusKey: _sharedPreferences.getInt(PreferencesKey.exchangeStatusKey),
|
||||||
PreferencesKey.exchangeStatusKey:
|
PreferencesKey.currentDefaultSettingsMigrationVersion:
|
||||||
_sharedPreferences.getInt(PreferencesKey.exchangeStatusKey),
|
_sharedPreferences.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion),
|
||||||
PreferencesKey.currentDefaultSettingsMigrationVersion: _sharedPreferences
|
|
||||||
.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion),
|
|
||||||
PreferencesKey.bitcoinTransactionPriority:
|
PreferencesKey.bitcoinTransactionPriority:
|
||||||
_sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority),
|
_sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority),
|
||||||
PreferencesKey.moneroTransactionPriority:
|
PreferencesKey.moneroTransactionPriority:
|
||||||
_sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority),
|
_sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority),
|
||||||
PreferencesKey.currentFiatApiModeKey:
|
PreferencesKey.currentFiatApiModeKey:
|
||||||
_sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey),
|
_sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey),
|
||||||
|
PreferencesKey.selectedCake2FAPreset:
|
||||||
|
_sharedPreferences.getInt(PreferencesKey.selectedCake2FAPreset),
|
||||||
|
PreferencesKey.shouldRequireTOTP2FAForAccessingWallet:
|
||||||
|
_sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAccessingWallet),
|
||||||
|
PreferencesKey.shouldRequireTOTP2FAForSendsToContact:
|
||||||
|
_sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToContact),
|
||||||
|
PreferencesKey.shouldRequireTOTP2FAForSendsToNonContact:
|
||||||
|
_sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToNonContact),
|
||||||
|
PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets:
|
||||||
|
_sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets),
|
||||||
|
PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets: _sharedPreferences
|
||||||
|
.getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets),
|
||||||
|
PreferencesKey.shouldRequireTOTP2FAForAddingContacts:
|
||||||
|
_sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts),
|
||||||
|
PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets:
|
||||||
|
_sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets),
|
||||||
|
PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings: _sharedPreferences
|
||||||
|
.getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings),
|
||||||
PreferencesKey.sortBalanceBy:
|
PreferencesKey.sortBalanceBy:
|
||||||
_sharedPreferences.getInt(PreferencesKey.sortBalanceBy),
|
_sharedPreferences.getInt(PreferencesKey.sortBalanceBy),
|
||||||
PreferencesKey.pinNativeTokenAtTop:
|
PreferencesKey.pinNativeTokenAtTop:
|
||||||
_sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop),
|
_sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop),
|
||||||
|
PreferencesKey.useEtherscan:
|
||||||
|
_sharedPreferences.getBool(PreferencesKey.useEtherscan),
|
||||||
};
|
};
|
||||||
|
|
||||||
return json.encode(preferences);
|
return json.encode(preferences);
|
||||||
|
@ -488,28 +529,23 @@ class BackupService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated('Use v2 instead')
|
@Deprecated('Use v2 instead')
|
||||||
Future<Uint8List> _encryptV1(
|
Future<Uint8List> _encryptV1(Uint8List data, String secretKeySource, String nonceBase64) async =>
|
||||||
Uint8List data, String secretKeySource, String nonceBase64) async
|
throw Exception('Deprecated');
|
||||||
=> throw Exception('Deprecated');
|
|
||||||
|
|
||||||
Future<Uint8List> _decryptV1(
|
Future<Uint8List> _decryptV1(Uint8List data, String secretKeySource, String nonceBase64,
|
||||||
Uint8List data, String secretKeySource, String nonceBase64, {int macLength = 16}) async {
|
{int macLength = 16}) async {
|
||||||
final secretKeyHash = await Cryptography.instance.sha256().hash(utf8.encode(secretKeySource));
|
final secretKeyHash = await Cryptography.instance.sha256().hash(utf8.encode(secretKeySource));
|
||||||
final secretKey = SecretKey(secretKeyHash.bytes);
|
final secretKey = SecretKey(secretKeyHash.bytes);
|
||||||
final nonce = base64.decode(nonceBase64).toList();
|
final nonce = base64.decode(nonceBase64).toList();
|
||||||
final box = SecretBox(
|
final box = SecretBox(Uint8List.sublistView(data, 0, data.lengthInBytes - macLength).toList(),
|
||||||
Uint8List.sublistView(data, 0, data.lengthInBytes - macLength).toList(),
|
nonce: nonce, mac: Mac(Uint8List.sublistView(data, data.lengthInBytes - macLength)));
|
||||||
nonce: nonce,
|
|
||||||
mac: Mac(Uint8List.sublistView(data, data.lengthInBytes - macLength)));
|
|
||||||
final plainData = await _cipher.decrypt(box, secretKey: secretKey);
|
final plainData = await _cipher.decrypt(box, secretKey: secretKey);
|
||||||
return Uint8List.fromList(plainData);
|
return Uint8List.fromList(plainData);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uint8List> _encryptV2(
|
Future<Uint8List> _encryptV2(Uint8List data, String passphrase) async =>
|
||||||
Uint8List data, String passphrase) async
|
cake_backup.encrypt(passphrase, data, version: _v2);
|
||||||
=> cake_backup.encrypt(passphrase, data, version: _v2);
|
|
||||||
|
|
||||||
Future<Uint8List> _decryptV2(
|
Future<Uint8List> _decryptV2(Uint8List data, String passphrase) async =>
|
||||||
Uint8List data, String passphrase) async
|
cake_backup.decrypt(passphrase, data);
|
||||||
=> cake_backup.decrypt(passphrase, data);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ class SeedValidator extends Validator<MnemonicItem> {
|
||||||
case WalletType.ethereum:
|
case WalletType.ethereum:
|
||||||
return ethereum!.getEthereumWordList(language);
|
return ethereum!.getEthereumWordList(language);
|
||||||
case WalletType.nano:
|
case WalletType.nano:
|
||||||
case WalletType.banano:// the same
|
case WalletType.banano:
|
||||||
return nano!.getNanoWordList(language);
|
return nano!.getNanoWordList(language);
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
|
|
44
lib/di.dart
44
lib/di.dart
|
@ -397,7 +397,9 @@ Future setup({
|
||||||
final authStore = getIt.get<AuthenticationStore>();
|
final authStore = getIt.get<AuthenticationStore>();
|
||||||
final appStore = getIt.get<AppStore>();
|
final appStore = getIt.get<AppStore>();
|
||||||
final useTotp = appStore.settingsStore.useTOTP2FA;
|
final useTotp = appStore.settingsStore.useTOTP2FA;
|
||||||
if (useTotp) {
|
final shouldUseTotp2FAToAccessWallets =
|
||||||
|
appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet;
|
||||||
|
if (useTotp && shouldUseTotp2FAToAccessWallets) {
|
||||||
authPageState.close(
|
authPageState.close(
|
||||||
route: Routes.totpAuthCodePage,
|
route: Routes.totpAuthCodePage,
|
||||||
arguments: TotpAuthArgumentsModel(
|
arguments: TotpAuthArgumentsModel(
|
||||||
|
@ -533,17 +535,22 @@ Future setup({
|
||||||
getIt.get<SendTemplateStore>(),
|
getIt.get<SendTemplateStore>(),
|
||||||
getIt.get<FiatConversionStore>()));
|
getIt.get<FiatConversionStore>()));
|
||||||
|
|
||||||
getIt.registerFactory<SendViewModel>(() => SendViewModel(
|
getIt.registerFactory<SendViewModel>(
|
||||||
|
() => SendViewModel(
|
||||||
getIt.get<AppStore>().wallet!,
|
getIt.get<AppStore>().wallet!,
|
||||||
getIt.get<AppStore>().settingsStore,
|
getIt.get<AppStore>().settingsStore,
|
||||||
getIt.get<SendTemplateViewModel>(),
|
getIt.get<SendTemplateViewModel>(),
|
||||||
getIt.get<FiatConversionStore>(),
|
getIt.get<FiatConversionStore>(),
|
||||||
getIt.get<BalanceViewModel>(),
|
getIt.get<BalanceViewModel>(),
|
||||||
_transactionDescriptionBox));
|
getIt.get<ContactListViewModel>(),
|
||||||
|
_transactionDescriptionBox,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
getIt.registerFactoryParam<SendPage, PaymentRequest?, void>(
|
getIt.registerFactoryParam<SendPage, PaymentRequest?, void>(
|
||||||
(PaymentRequest? initialPaymentRequest, _) => SendPage(
|
(PaymentRequest? initialPaymentRequest, _) => SendPage(
|
||||||
sendViewModel: getIt.get<SendViewModel>(),
|
sendViewModel: getIt.get<SendViewModel>(),
|
||||||
|
authService: getIt.get<AuthService>(),
|
||||||
initialPaymentRequest: initialPaymentRequest,
|
initialPaymentRequest: initialPaymentRequest,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -578,8 +585,8 @@ Future setup({
|
||||||
));
|
));
|
||||||
|
|
||||||
getIt.registerFactoryParam<WalletEditViewModel, WalletListViewModel, void>(
|
getIt.registerFactoryParam<WalletEditViewModel, WalletListViewModel, void>(
|
||||||
(WalletListViewModel walletListViewModel, _) => WalletEditViewModel(
|
(WalletListViewModel walletListViewModel, _) =>
|
||||||
walletListViewModel, getIt.get<WalletLoadingService>()));
|
WalletEditViewModel(walletListViewModel, getIt.get<WalletLoadingService>()));
|
||||||
|
|
||||||
getIt.registerFactoryParam<WalletEditPage, List<dynamic>, void>((args, _) {
|
getIt.registerFactoryParam<WalletEditPage, List<dynamic>, void>((args, _) {
|
||||||
final walletListViewModel = args.first as WalletListViewModel;
|
final walletListViewModel = args.first as WalletListViewModel;
|
||||||
|
@ -591,7 +598,6 @@ Future setup({
|
||||||
editingWallet: editingWallet);
|
editingWallet: editingWallet);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
getIt.registerFactory(() {
|
getIt.registerFactory(() {
|
||||||
final wallet = getIt.get<AppStore>().wallet!;
|
final wallet = getIt.get<AppStore>().wallet!;
|
||||||
|
|
||||||
|
@ -638,7 +644,7 @@ Future setup({
|
||||||
});
|
});
|
||||||
|
|
||||||
getIt.registerFactory(() {
|
getIt.registerFactory(() {
|
||||||
return PrivacySettingsViewModel(getIt.get<SettingsStore>());
|
return PrivacySettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!);
|
||||||
});
|
});
|
||||||
|
|
||||||
getIt.registerFactory(() {
|
getIt.registerFactory(() {
|
||||||
|
@ -662,10 +668,11 @@ Future setup({
|
||||||
(ContactRecord? contact, _) => ContactViewModel(_contactSource, contact: contact));
|
(ContactRecord? contact, _) => ContactViewModel(_contactSource, contact: contact));
|
||||||
|
|
||||||
getIt.registerFactoryParam<ContactListViewModel, CryptoCurrency?, void>(
|
getIt.registerFactoryParam<ContactListViewModel, CryptoCurrency?, void>(
|
||||||
(CryptoCurrency? cur, _) => ContactListViewModel(_contactSource, _walletInfoSource, cur));
|
(CryptoCurrency? cur, _) =>
|
||||||
|
ContactListViewModel(_contactSource, _walletInfoSource, cur, getIt.get<SettingsStore>()));
|
||||||
|
|
||||||
getIt.registerFactoryParam<ContactListPage, CryptoCurrency?, void>(
|
getIt.registerFactoryParam<ContactListPage, CryptoCurrency?, void>((CryptoCurrency? cur, _) =>
|
||||||
(CryptoCurrency? cur, _) => ContactListPage(getIt.get<ContactListViewModel>(param1: cur)));
|
ContactListPage(getIt.get<ContactListViewModel>(param1: cur), getIt.get<AuthService>()));
|
||||||
|
|
||||||
getIt.registerFactoryParam<ContactPage, ContactRecord?, void>(
|
getIt.registerFactoryParam<ContactPage, ContactRecord?, void>(
|
||||||
(ContactRecord? contact, _) => ContactPage(getIt.get<ContactViewModel>(param1: contact)));
|
(ContactRecord? contact, _) => ContactPage(getIt.get<ContactViewModel>(param1: contact)));
|
||||||
|
@ -710,13 +717,13 @@ Future setup({
|
||||||
));
|
));
|
||||||
|
|
||||||
getIt.registerFactory(() => ExchangeViewModel(
|
getIt.registerFactory(() => ExchangeViewModel(
|
||||||
getIt.get<AppStore>().wallet!,
|
getIt.get<AppStore>().wallet!,
|
||||||
_tradesSource,
|
_tradesSource,
|
||||||
getIt.get<ExchangeTemplateStore>(),
|
getIt.get<ExchangeTemplateStore>(),
|
||||||
getIt.get<TradesStore>(),
|
getIt.get<TradesStore>(),
|
||||||
getIt.get<AppStore>().settingsStore,
|
getIt.get<AppStore>().settingsStore,
|
||||||
getIt.get<SharedPreferences>(),
|
getIt.get<SharedPreferences>(),
|
||||||
));
|
getIt.get<ContactListViewModel>()));
|
||||||
|
|
||||||
getIt.registerFactory(() => ExchangeTradeViewModel(
|
getIt.registerFactory(() => ExchangeTradeViewModel(
|
||||||
wallet: getIt.get<AppStore>().wallet!,
|
wallet: getIt.get<AppStore>().wallet!,
|
||||||
|
@ -724,7 +731,8 @@ Future setup({
|
||||||
tradesStore: getIt.get<TradesStore>(),
|
tradesStore: getIt.get<TradesStore>(),
|
||||||
sendViewModel: getIt.get<SendViewModel>()));
|
sendViewModel: getIt.get<SendViewModel>()));
|
||||||
|
|
||||||
getIt.registerFactory(() => ExchangePage(getIt.get<ExchangeViewModel>()));
|
getIt.registerFactory(
|
||||||
|
() => ExchangePage(getIt.get<ExchangeViewModel>(), getIt.get<AuthService>()));
|
||||||
|
|
||||||
getIt.registerFactory(() => ExchangeConfirmPage(tradesStore: getIt.get<TradesStore>()));
|
getIt.registerFactory(() => ExchangeConfirmPage(tradesStore: getIt.get<TradesStore>()));
|
||||||
|
|
||||||
|
|
35
lib/entities/cake_2fa_preset_options.dart
Normal file
35
lib/entities/cake_2fa_preset_options.dart
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import 'package:cw_core/enumerable_item.dart';
|
||||||
|
|
||||||
|
class Cake2FAPresetsOptions extends EnumerableItem<int> with Serializable<int> {
|
||||||
|
const Cake2FAPresetsOptions({required String super.title, required int super.raw});
|
||||||
|
|
||||||
|
static const narrow = Cake2FAPresetsOptions(title: 'Narrow', raw: 0);
|
||||||
|
static const normal = Cake2FAPresetsOptions(title: 'Normal', raw: 1);
|
||||||
|
static const aggressive = Cake2FAPresetsOptions(title: 'Aggressive', raw: 2);
|
||||||
|
|
||||||
|
static Cake2FAPresetsOptions deserialize({required int raw}) {
|
||||||
|
switch (raw) {
|
||||||
|
case 0:
|
||||||
|
return Cake2FAPresetsOptions.narrow;
|
||||||
|
case 1:
|
||||||
|
return Cake2FAPresetsOptions.normal;
|
||||||
|
case 2:
|
||||||
|
return Cake2FAPresetsOptions.aggressive;
|
||||||
|
default:
|
||||||
|
throw Exception(
|
||||||
|
'Incorrect Cake 2FA Preset $raw for Cake2FAPresetOptions deserialize',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum VerboseControlSettings {
|
||||||
|
accessWallet,
|
||||||
|
addingContacts,
|
||||||
|
sendsToContacts,
|
||||||
|
sendsToNonContacts,
|
||||||
|
sendsToInternalWallets,
|
||||||
|
exchangesToInternalWallets,
|
||||||
|
securityAndBackupSettings,
|
||||||
|
creatingNewWallets,
|
||||||
|
}
|
|
@ -472,6 +472,13 @@ Future<void> checkCurrentNodes(Box<Node> nodeSource, SharedPreferences sharedPre
|
||||||
await nodeSource.add(node);
|
await nodeSource.add(node);
|
||||||
await sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int);
|
await sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentEthereumNodeServer == null) {
|
||||||
|
final node = Node(uri: ethereumDefaultNodeUri, type: WalletType.ethereum);
|
||||||
|
await nodeSource.add(node);
|
||||||
|
await sharedPreferences.setInt(
|
||||||
|
PreferencesKey.currentEthereumNodeIdKey, node.key as int);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> resetBitcoinElectrumServer(
|
Future<void> resetBitcoinElectrumServer(
|
||||||
|
@ -511,8 +518,8 @@ Future<void> migrateExchangeStatus(SharedPreferences sharedPreferences) async {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey,
|
await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, isExchangeDisabled
|
||||||
isExchangeDisabled ? ExchangeApiMode.disabled.raw : ExchangeApiMode.enabled.raw);
|
? ExchangeApiMode.disabled.raw : ExchangeApiMode.enabled.raw);
|
||||||
|
|
||||||
await sharedPreferences.remove(PreferencesKey.disableExchangeKey);
|
await sharedPreferences.remove(PreferencesKey.disableExchangeKey);
|
||||||
}
|
}
|
||||||
|
@ -527,7 +534,8 @@ Future<void> addEthereumNodeList({required Box<Node> nodes}) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> changeEthereumCurrentNodeToDefault(
|
Future<void> changeEthereumCurrentNodeToDefault(
|
||||||
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
|
{required SharedPreferences sharedPreferences,
|
||||||
|
required Box<Node> nodes}) async {
|
||||||
final node = getEthereumDefaultNode(nodes: nodes);
|
final node = getEthereumDefaultNode(nodes: nodes);
|
||||||
final nodeId = node?.key as int? ?? 0;
|
final nodeId = node?.key as int? ?? 0;
|
||||||
|
|
||||||
|
|
|
@ -100,6 +100,22 @@ Future<List<Node>> loadDefaultNanoNodes() async {
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<Node>> loadDefaultEthereumNodes() async {
|
||||||
|
final nodesRaw = await rootBundle.loadString('assets/ethereum_server_list.yml');
|
||||||
|
final loadedNodes = loadYaml(nodesRaw) as YamlList;
|
||||||
|
final nodes = <Node>[];
|
||||||
|
|
||||||
|
for (final raw in loadedNodes) {
|
||||||
|
if (raw is Map) {
|
||||||
|
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||||
|
node.type = WalletType.ethereum;
|
||||||
|
nodes.add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
Future resetToDefault(Box<Node> nodeSource) async {
|
Future resetToDefault(Box<Node> nodeSource) async {
|
||||||
final moneroNodes = await loadDefaultNodes();
|
final moneroNodes = await loadDefaultNodes();
|
||||||
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
|
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
|
||||||
|
|
|
@ -43,16 +43,33 @@ class PreferencesKey {
|
||||||
static const lastAppReviewDate = 'last_app_review_date';
|
static const lastAppReviewDate = 'last_app_review_date';
|
||||||
static const sortBalanceBy = 'sort_balance_by';
|
static const sortBalanceBy = 'sort_balance_by';
|
||||||
static const pinNativeTokenAtTop = 'pin_native_token_at_top';
|
static const pinNativeTokenAtTop = 'pin_native_token_at_top';
|
||||||
|
static const useEtherscan = 'use_etherscan';
|
||||||
|
|
||||||
|
static String moneroWalletUpdateV1Key(String name) =>
|
||||||
|
'${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';
|
||||||
static String moneroWalletUpdateV1Key(String name)
|
|
||||||
=> '${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';
|
|
||||||
|
|
||||||
static const exchangeProvidersSelection = 'exchange-providers-selection';
|
static const exchangeProvidersSelection = 'exchange-providers-selection';
|
||||||
static const clearnetDonationLink = 'clearnet_donation_link';
|
static const clearnetDonationLink = 'clearnet_donation_link';
|
||||||
static const onionDonationLink = 'onion_donation_link';
|
static const onionDonationLink = 'onion_donation_link';
|
||||||
static const lastSeenAppVersion = 'last_seen_app_version';
|
static const lastSeenAppVersion = 'last_seen_app_version';
|
||||||
static const shouldShowMarketPlaceInDashboard = 'should_show_marketplace_in_dashboard';
|
static const shouldShowMarketPlaceInDashboard =
|
||||||
|
'should_show_marketplace_in_dashboard';
|
||||||
static const isNewInstall = 'is_new_install';
|
static const isNewInstall = 'is_new_install';
|
||||||
|
static const shouldRequireTOTP2FAForAccessingWallet =
|
||||||
|
'should_require_totp_2fa_for_accessing_wallets';
|
||||||
|
static const shouldRequireTOTP2FAForSendsToContact =
|
||||||
|
'should_require_totp_2fa_for_sends_to_contact';
|
||||||
|
static const shouldRequireTOTP2FAForSendsToNonContact =
|
||||||
|
'should_require_totp_2fa_for_sends_to_non_contact';
|
||||||
|
static const shouldRequireTOTP2FAForSendsToInternalWallets =
|
||||||
|
'should_require_totp_2fa_for_sends_to_internal_wallets';
|
||||||
|
static const shouldRequireTOTP2FAForExchangesToInternalWallets =
|
||||||
|
'should_require_totp_2fa_for_exchanges_to_internal_wallets';
|
||||||
|
static const shouldRequireTOTP2FAForAddingContacts =
|
||||||
|
'should_require_totp_2fa_for_adding_contacts';
|
||||||
|
static const shouldRequireTOTP2FAForCreatingNewWallets =
|
||||||
|
'should_require_totp_2fa_for_creating_new_wallets';
|
||||||
|
static const shouldRequireTOTP2FAForAllSecurityAndBackupSettings =
|
||||||
|
'should_require_totp_2fa_for_all_security_and_backup_settings';
|
||||||
|
static const selectedCake2FAPreset = 'selected_cake_2fa_preset';
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,5 +55,5 @@ class Template extends HiveObject {
|
||||||
|
|
||||||
String get amount => amountRaw ?? '';
|
String get amount => amountRaw ?? '';
|
||||||
|
|
||||||
List<Template>? get additionalRecipients => additionalRecipientsRaw ?? null;
|
List<Template>? get additionalRecipients => additionalRecipientsRaw;
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,12 +35,6 @@ class CWEthereum extends Ethereum {
|
||||||
TransactionPriority deserializeEthereumTransactionPriority(int raw) =>
|
TransactionPriority deserializeEthereumTransactionPriority(int raw) =>
|
||||||
EthereumTransactionPriority.deserialize(raw: raw);
|
EthereumTransactionPriority.deserialize(raw: raw);
|
||||||
|
|
||||||
@override
|
|
||||||
int getEstimatedFee(Object wallet, TransactionPriority priority) {
|
|
||||||
final ethereumWallet = wallet as EthereumWallet;
|
|
||||||
return ethereumWallet.feeRate(priority);
|
|
||||||
}
|
|
||||||
|
|
||||||
Object createEthereumTransactionCredentials(
|
Object createEthereumTransactionCredentials(
|
||||||
List<Output> outputs, {
|
List<Output> outputs, {
|
||||||
required TransactionPriority priority,
|
required TransactionPriority priority,
|
||||||
|
@ -81,10 +75,16 @@ class CWEthereum extends Ethereum {
|
||||||
int formatterEthereumParseAmount(String amount) => EthereumFormatter.parseEthereumAmount(amount);
|
int formatterEthereumParseAmount(String amount) => EthereumFormatter.parseEthereumAmount(amount);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
double formatterEthereumAmountToDouble({required TransactionInfo transaction}) {
|
double formatterEthereumAmountToDouble(
|
||||||
transaction as EthereumTransactionInfo;
|
{TransactionInfo? transaction, BigInt? amount, int exponent = 18}) {
|
||||||
return cryptoAmountToDouble(
|
assert(transaction != null || amount != null);
|
||||||
amount: transaction.amount, divider: BigInt.from(10).pow(transaction.exponent).toInt());
|
|
||||||
|
if (transaction != null) {
|
||||||
|
transaction as EthereumTransactionInfo;
|
||||||
|
return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
|
||||||
|
} else {
|
||||||
|
return (amount!) / BigInt.from(10).pow(exponent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -118,4 +118,9 @@ class CWEthereum extends Ethereum {
|
||||||
return wallet.erc20Currencies
|
return wallet.erc20Currencies
|
||||||
.firstWhere((element) => transaction.tokenSymbol == element.symbol);
|
.firstWhere((element) => transaction.tokenSymbol == element.symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void updateEtherscanUsageState(WalletBase wallet, bool isEnabled) {
|
||||||
|
(wallet as EthereumWallet).updateEtherscanUsageState(isEnabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,13 +34,16 @@ Future<void> startFiatRateUpdate(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appStore.wallet!.type == WalletType.ethereum) {
|
if (appStore.wallet!.type == WalletType.ethereum) {
|
||||||
final currencies = ethereum!.getERC20Currencies(appStore.wallet!);
|
final currencies =
|
||||||
|
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
|
||||||
|
|
||||||
for (final currency in currencies) {
|
for (final currency in currencies) {
|
||||||
fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice(
|
() async {
|
||||||
crypto: currency,
|
fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice(
|
||||||
fiat: settingsStore.fiatCurrency,
|
crypto: currency,
|
||||||
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
fiat: settingsStore.fiatCurrency,
|
||||||
|
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
||||||
|
}.call();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -99,13 +99,16 @@ void startCurrentWalletChangeReaction(AppStore appStore,
|
||||||
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
||||||
|
|
||||||
if (wallet.type == WalletType.ethereum) {
|
if (wallet.type == WalletType.ethereum) {
|
||||||
final currencies = ethereum!.getERC20Currencies(appStore.wallet!);
|
final currencies =
|
||||||
|
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
|
||||||
|
|
||||||
for (final currency in currencies) {
|
for (final currency in currencies) {
|
||||||
fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice(
|
() async {
|
||||||
crypto: currency,
|
fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice(
|
||||||
fiat: settingsStore.fiatCurrency,
|
crypto: currency,
|
||||||
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
fiat: settingsStore.fiatCurrency,
|
||||||
|
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
||||||
|
}.call();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -311,13 +311,17 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||||
|
|
||||||
case Routes.otherSettingsPage:
|
case Routes.otherSettingsPage:
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
fullscreenDialog: true, builder: (_) => getIt.get<OtherSettingsPage>());
|
fullscreenDialog: true,
|
||||||
|
builder: (_) => getIt.get<OtherSettingsPage>());
|
||||||
|
|
||||||
case Routes.newNode:
|
case Routes.newNode:
|
||||||
final args = settings.arguments as Map<String, dynamic>?;
|
final args = settings.arguments as Map<String, dynamic>?;
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
builder: (_) => getIt.get<NodeCreateOrEditPage>(
|
builder: (_) => getIt.get<NodeCreateOrEditPage>(
|
||||||
param1: args?['editingNode'] as Node?, param2: args?['isSelected'] as bool?));
|
param1: args?['editingNode'] as Node?,
|
||||||
|
param2: args?['isSelected'] as bool?));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
case Routes.accountCreation:
|
case Routes.accountCreation:
|
||||||
return CupertinoPageRoute<String>(
|
return CupertinoPageRoute<String>(
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/core/auth_service.dart';
|
||||||
import 'package:cake_wallet/entities/contact_base.dart';
|
import 'package:cake_wallet/entities/contact_base.dart';
|
||||||
import 'package:cake_wallet/entities/contact_record.dart';
|
import 'package:cake_wallet/entities/contact_record.dart';
|
||||||
import 'package:cake_wallet/utils/show_bar.dart';
|
import 'package:cake_wallet/utils/show_bar.dart';
|
||||||
|
@ -15,9 +16,10 @@ import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart
|
||||||
import 'package:cake_wallet/src/widgets/collapsible_standart_list.dart';
|
import 'package:cake_wallet/src/widgets/collapsible_standart_list.dart';
|
||||||
|
|
||||||
class ContactListPage extends BasePage {
|
class ContactListPage extends BasePage {
|
||||||
ContactListPage(this.contactListViewModel);
|
ContactListPage(this.contactListViewModel, this.authService);
|
||||||
|
|
||||||
final ContactListViewModel contactListViewModel;
|
final ContactListViewModel contactListViewModel;
|
||||||
|
final AuthService authService;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get title => S.current.address_book;
|
String get title => S.current.address_book;
|
||||||
|
@ -26,95 +28,99 @@ class ContactListPage extends BasePage {
|
||||||
Widget? trailing(BuildContext context) {
|
Widget? trailing(BuildContext context) {
|
||||||
return MergeSemantics(
|
return MergeSemantics(
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 32.0,
|
width: 32.0,
|
||||||
height: 32.0,
|
height: 32.0,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context).accentTextTheme!.bodySmall!.color!),
|
||||||
.accentTextTheme!
|
child: Stack(
|
||||||
.bodySmall!
|
alignment: Alignment.center,
|
||||||
.color!),
|
children: <Widget>[
|
||||||
child: Stack(
|
Icon(
|
||||||
alignment: Alignment.center,
|
Icons.add,
|
||||||
children: <Widget>[
|
color: Theme.of(context).primaryTextTheme.titleLarge!.color!,
|
||||||
Icon(Icons.add,
|
size: 22.0,
|
||||||
color: Theme.of(context).primaryTextTheme!.titleLarge!.color!,
|
),
|
||||||
size: 22.0),
|
ButtonTheme(
|
||||||
ButtonTheme(
|
minWidth: 32.0,
|
||||||
minWidth: 32.0,
|
height: 32.0,
|
||||||
height: 32.0,
|
child: TextButton(
|
||||||
child: Semantics(
|
// FIX-ME: Style
|
||||||
label: S.of(context).add,
|
//shape: CircleBorder(),
|
||||||
child: TextButton(
|
onPressed: () async {
|
||||||
// FIX-ME: Style
|
if (contactListViewModel
|
||||||
//shape: CircleBorder(),
|
.shouldRequireTOTP2FAForAddingContacts) {
|
||||||
onPressed: () async {
|
authService.authenticateAction(
|
||||||
await Navigator.of(context)
|
context,
|
||||||
.pushNamed(Routes.addressBookAddContact);
|
route: Routes.addressBookAddContact,
|
||||||
},
|
conditionToDetermineIfToUse2FA: contactListViewModel
|
||||||
child: Offstage()),
|
.shouldRequireTOTP2FAForAddingContacts,
|
||||||
),
|
);
|
||||||
)
|
} else {
|
||||||
],
|
await Navigator.of(context)
|
||||||
)),
|
.pushNamed(Routes.addressBookAddContact);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Offstage()),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) {
|
Widget body(BuildContext context) {
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.only(top: 20.0, bottom: 20.0),
|
padding: EdgeInsets.only(top: 20.0, bottom: 20.0),
|
||||||
child: Observer(
|
child: Observer(builder: (_) {
|
||||||
builder: (_) {
|
|
||||||
final contacts = contactListViewModel.contactsToShow;
|
final contacts = contactListViewModel.contactsToShow;
|
||||||
final walletContacts = contactListViewModel.walletContactsToShow;
|
final walletContacts = contactListViewModel.walletContactsToShow;
|
||||||
return CollapsibleSectionList(
|
return CollapsibleSectionList(
|
||||||
context: context,
|
context: context,
|
||||||
sectionCount: 2,
|
sectionCount: 2,
|
||||||
themeColor: Theme.of(context).primaryTextTheme!.titleLarge!.color!,
|
themeColor: Theme.of(context).primaryTextTheme.titleLarge!.color!,
|
||||||
dividerThemeColor:
|
dividerThemeColor:
|
||||||
Theme.of(context).primaryTextTheme!.bodySmall!.decorationColor!,
|
Theme.of(context).primaryTextTheme.bodySmall!.decorationColor!,
|
||||||
sectionTitleBuilder: (_, int sectionIndex) {
|
sectionTitleBuilder: (_, int sectionIndex) {
|
||||||
var title = S.current.contact_list_contacts;
|
var title = S.current.contact_list_contacts;
|
||||||
|
|
||||||
if (sectionIndex == 0) {
|
if (sectionIndex == 0) {
|
||||||
title = S.current.contact_list_wallets;
|
title = S.current.contact_list_wallets;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.only(bottom: 10),
|
padding: EdgeInsets.only(bottom: 10),
|
||||||
child: Text(title, style: TextStyle(fontSize: 36)));
|
child: Text(title, style: TextStyle(fontSize: 36)));
|
||||||
},
|
},
|
||||||
itemCounter: (int sectionIndex) => sectionIndex == 0
|
itemCounter: (int sectionIndex) =>
|
||||||
? walletContacts.length
|
sectionIndex == 0 ? walletContacts.length : contacts.length,
|
||||||
: contacts.length,
|
itemBuilder: (_, sectionIndex, index) {
|
||||||
itemBuilder: (_, sectionIndex, index) {
|
if (sectionIndex == 0) {
|
||||||
if (sectionIndex == 0) {
|
final walletInfo = walletContacts[index];
|
||||||
final walletInfo = walletContacts[index];
|
return generateRaw(context, walletInfo);
|
||||||
return generateRaw(context, walletInfo);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
final contact = contacts[index];
|
final contact = contacts[index];
|
||||||
final content = generateRaw(context, contact);
|
final content = generateRaw(context, contact);
|
||||||
return contactListViewModel.isEditable
|
return contactListViewModel.isEditable
|
||||||
? Slidable(
|
? Slidable(
|
||||||
key: Key('${contact.key}'),
|
key: Key('${contact.key}'),
|
||||||
endActionPane: _actionPane(context, contact),
|
endActionPane: _actionPane(context, contact),
|
||||||
child: content,
|
child: content,
|
||||||
)
|
)
|
||||||
: content;
|
: content;
|
||||||
},
|
},
|
||||||
);})
|
);
|
||||||
);
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget generateRaw(BuildContext context, ContactBase contact) {
|
Widget generateRaw(BuildContext context, ContactBase contact) {
|
||||||
final image = contact.type.iconPath;
|
final image = contact.type.iconPath;
|
||||||
final currencyIcon = image != null ? Image.asset(image, height: 24, width: 24)
|
final currencyIcon = image != null
|
||||||
|
? Image.asset(image, height: 24, width: 24)
|
||||||
: const SizedBox(height: 24, width: 24);
|
: const SizedBox(height: 24, width: 24);
|
||||||
|
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (!contactListViewModel.isEditable) {
|
if (!contactListViewModel.isEditable) {
|
||||||
|
@ -128,30 +134,28 @@ class ContactListPage extends BasePage {
|
||||||
if (isCopied) {
|
if (isCopied) {
|
||||||
await Clipboard.setData(ClipboardData(text: contact.address));
|
await Clipboard.setData(ClipboardData(text: contact.address));
|
||||||
await showBar<void>(context, S.of(context).copied_to_clipboard);
|
await showBar<void>(context, S.of(context).copied_to_clipboard);
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
padding:
|
padding: const EdgeInsets.only(top: 16, bottom: 16, right: 24),
|
||||||
const EdgeInsets.only(top: 16, bottom: 16, right: 24),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
currencyIcon,
|
currencyIcon,
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(left: 12),
|
padding: EdgeInsets.only(left: 12),
|
||||||
child: Text(
|
child: Text(
|
||||||
contact.name,
|
contact.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.normal,
|
fontWeight: FontWeight.normal,
|
||||||
color: Theme.of(context).primaryTextTheme!.titleLarge!.color!),
|
color: Theme.of(context).primaryTextTheme.titleLarge!.color!,
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
)
|
))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -160,60 +164,61 @@ class ContactListPage extends BasePage {
|
||||||
|
|
||||||
Future<bool> showAlertDialog(BuildContext context) async {
|
Future<bool> showAlertDialog(BuildContext context) async {
|
||||||
return await showPopUp<bool>(
|
return await showPopUp<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertWithTwoActions(
|
return AlertWithTwoActions(
|
||||||
alertTitle: S.of(context).address_remove_contact,
|
alertTitle: S.of(context).address_remove_contact,
|
||||||
alertContent: S.of(context).address_remove_content,
|
alertContent: S.of(context).address_remove_content,
|
||||||
rightButtonText: S.of(context).remove,
|
rightButtonText: S.of(context).remove,
|
||||||
leftButtonText: S.of(context).cancel,
|
leftButtonText: S.of(context).cancel,
|
||||||
actionRightButton: () => Navigator.of(context).pop(true),
|
actionRightButton: () => Navigator.of(context).pop(true),
|
||||||
actionLeftButton: () => Navigator.of(context).pop(false));
|
actionLeftButton: () => Navigator.of(context).pop(false));
|
||||||
}) ?? false;
|
}) ??
|
||||||
|
false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> showNameAndAddressDialog(
|
Future<bool> showNameAndAddressDialog(
|
||||||
BuildContext context, String name, String address) async {
|
BuildContext context, String name, String address) async {
|
||||||
return await showPopUp<bool>(
|
return await showPopUp<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertWithTwoActions(
|
return AlertWithTwoActions(
|
||||||
alertTitle: name,
|
alertTitle: name,
|
||||||
alertContent: address,
|
alertContent: address,
|
||||||
rightButtonText: S.of(context).copy,
|
rightButtonText: S.of(context).copy,
|
||||||
leftButtonText: S.of(context).cancel,
|
leftButtonText: S.of(context).cancel,
|
||||||
actionRightButton: () => Navigator.of(context).pop(true),
|
actionRightButton: () => Navigator.of(context).pop(true),
|
||||||
actionLeftButton: () => Navigator.of(context).pop(false));
|
actionLeftButton: () => Navigator.of(context).pop(false));
|
||||||
}) ?? false;
|
}) ??
|
||||||
|
false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionPane _actionPane(BuildContext context, ContactRecord contact) => ActionPane(
|
ActionPane _actionPane(BuildContext context, ContactRecord contact) =>
|
||||||
motion: const ScrollMotion(),
|
ActionPane(
|
||||||
extentRatio: 0.4,
|
motion: const ScrollMotion(),
|
||||||
children: [
|
extentRatio: 0.4,
|
||||||
SlidableAction(
|
children: [
|
||||||
onPressed: (_) async => await Navigator.of(context)
|
SlidableAction(
|
||||||
.pushNamed(Routes.addressBookAddContact,
|
onPressed: (_) async => await Navigator.of(context)
|
||||||
arguments: contact),
|
.pushNamed(Routes.addressBookAddContact, arguments: contact),
|
||||||
backgroundColor: Colors.blue,
|
backgroundColor: Colors.blue,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
icon: Icons.edit,
|
icon: Icons.edit,
|
||||||
label: S.of(context).edit,
|
label: S.of(context).edit,
|
||||||
),
|
),
|
||||||
SlidableAction(
|
SlidableAction(
|
||||||
onPressed: (_) async {
|
onPressed: (_) async {
|
||||||
final isDelete =
|
final isDelete = await showAlertDialog(context);
|
||||||
await showAlertDialog(context);
|
|
||||||
|
|
||||||
if (isDelete) {
|
if (isDelete) {
|
||||||
await contactListViewModel.delete(contact);
|
await contactListViewModel.delete(contact);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: Colors.red,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
icon: CupertinoIcons.delete,
|
icon: CupertinoIcons.delete,
|
||||||
label: S.of(context).delete,
|
label: S.of(context).delete,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||||
final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
|
final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
|
||||||
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
|
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
|
||||||
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
|
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
|
||||||
|
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
|
||||||
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
|
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
|
||||||
|
|
||||||
Image _newWalletImage(BuildContext context) => Image.asset(
|
Image _newWalletImage(BuildContext context) => Image.asset(
|
||||||
|
@ -136,6 +137,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||||
return litecoinIcon;
|
return litecoinIcon;
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return havenIcon;
|
return havenIcon;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return ethereumIcon;
|
||||||
default:
|
default:
|
||||||
return nonWalletTypeIcon;
|
return nonWalletTypeIcon;
|
||||||
}
|
}
|
||||||
|
@ -156,15 +159,29 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
|
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
conditionToDetermineIfToUse2FA:
|
||||||
|
widget.walletListViewModel.shouldRequireTOTP2FAForAccessingWallet,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _navigateToCreateWallet() {
|
void _navigateToCreateWallet() {
|
||||||
if (isSingleCoin) {
|
if (isSingleCoin) {
|
||||||
Navigator.of(context)
|
widget._authService.authenticateAction(
|
||||||
.pushNamed(Routes.newWallet, arguments: widget.walletListViewModel.currentWalletType);
|
context,
|
||||||
|
route: Routes.newWallet,
|
||||||
|
arguments: widget.walletListViewModel.currentWalletType,
|
||||||
|
conditionToDetermineIfToUse2FA: widget
|
||||||
|
.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
Navigator.of(context).pushNamed(Routes.newWalletType);
|
widget._authService.authenticateAction(
|
||||||
|
context,
|
||||||
|
route: Routes.newWalletType,
|
||||||
|
conditionToDetermineIfToUse2FA: widget
|
||||||
|
.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
import 'package:cake_wallet/themes/theme_base.dart';
|
import 'package:cake_wallet/themes/theme_base.dart';
|
||||||
import 'package:cake_wallet/utils/feature_flag.dart';
|
import 'package:cake_wallet/utils/feature_flag.dart';
|
||||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
|
||||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||||
|
@ -68,7 +67,8 @@ class BalancePage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
Observer(
|
Observer(
|
||||||
builder: (_) {
|
builder: (_) {
|
||||||
if (dashboardViewModel.balanceViewModel.isShowCard && FeatureFlag.isCakePayEnabled) {
|
if (dashboardViewModel.balanceViewModel.isShowCard &&
|
||||||
|
FeatureFlag.isCakePayEnabled) {
|
||||||
return IntroducingCard(
|
return IntroducingCard(
|
||||||
title: S.of(context).introducing_cake_pay,
|
title: S.of(context).introducing_cake_pay,
|
||||||
subTitle: S.of(context).cake_pay_learn_more,
|
subTitle: S.of(context).cake_pay_learn_more,
|
||||||
|
@ -90,18 +90,22 @@ class BalancePage extends StatelessWidget {
|
||||||
itemBuilder: (__, index) {
|
itemBuilder: (__, index) {
|
||||||
final balance =
|
final balance =
|
||||||
dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
|
dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
|
||||||
return buildBalanceRow(context,
|
return buildBalanceRow(
|
||||||
availableBalanceLabel:
|
context,
|
||||||
'${dashboardViewModel.balanceViewModel.availableBalanceLabel}',
|
availableBalanceLabel:
|
||||||
availableBalance: balance.availableBalance,
|
'${dashboardViewModel.balanceViewModel.availableBalanceLabel}',
|
||||||
availableFiatBalance: balance.fiatAvailableBalance,
|
availableBalance: balance.availableBalance,
|
||||||
additionalBalanceLabel:
|
availableFiatBalance: balance.fiatAvailableBalance,
|
||||||
'${dashboardViewModel.balanceViewModel.additionalBalanceLabel}',
|
additionalBalanceLabel:
|
||||||
additionalBalance: balance.additionalBalance,
|
'${dashboardViewModel.balanceViewModel.additionalBalanceLabel}',
|
||||||
additionalFiatBalance: balance.fiatAdditionalBalance,
|
additionalBalance: balance.additionalBalance,
|
||||||
frozenBalance: balance.frozenBalance,
|
additionalFiatBalance: balance.fiatAdditionalBalance,
|
||||||
frozenFiatBalance: balance.fiatFrozenBalance,
|
frozenBalance: balance.frozenBalance,
|
||||||
currency: balance.formattedAssetTitle);
|
frozenFiatBalance: balance.fiatFrozenBalance,
|
||||||
|
currency: balance.formattedAssetTitle,
|
||||||
|
hasAdditionalBalance:
|
||||||
|
dashboardViewModel.balanceViewModel.hasAdditionalBalance,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -112,16 +116,19 @@ class BalancePage extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildBalanceRow(BuildContext context,
|
Widget buildBalanceRow(
|
||||||
{required String availableBalanceLabel,
|
BuildContext context, {
|
||||||
required String availableBalance,
|
required String availableBalanceLabel,
|
||||||
required String availableFiatBalance,
|
required String availableBalance,
|
||||||
required String additionalBalanceLabel,
|
required String availableFiatBalance,
|
||||||
required String additionalBalance,
|
required String additionalBalanceLabel,
|
||||||
required String additionalFiatBalance,
|
required String additionalBalance,
|
||||||
required String frozenBalance,
|
required String additionalFiatBalance,
|
||||||
required String frozenFiatBalance,
|
required String frozenBalance,
|
||||||
required String currency}) {
|
required String frozenFiatBalance,
|
||||||
|
required String currency,
|
||||||
|
required bool hasAdditionalBalance,
|
||||||
|
}) {
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(left: 16, right: 16),
|
margin: const EdgeInsets.only(left: 16, right: 16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
@ -135,17 +142,17 @@ class BalancePage extends StatelessWidget {
|
||||||
color: Theme.of(context).textTheme.titleLarge!.backgroundColor!,
|
color: Theme.of(context).textTheme.titleLarge!.backgroundColor!,
|
||||||
),
|
),
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: const EdgeInsets.only(top: 16, left: 24, right: 24, bottom: 24),
|
margin: const EdgeInsets.only(top: 16, left: 24, right: 24, bottom: 24),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: () => _showBalanceDescription(context),
|
onTap: hasAdditionalBalance ? () => _showBalanceDescription(context) : null,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
@ -161,19 +168,19 @@ class BalancePage extends StatelessWidget {
|
||||||
.displaySmall!
|
.displaySmall!
|
||||||
.backgroundColor!,
|
.backgroundColor!,
|
||||||
height: 1)),
|
height: 1)),
|
||||||
Padding(
|
if (hasAdditionalBalance)
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
Padding(
|
||||||
child: Icon(Icons.help_outline,
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
size: 16,
|
child: Icon(Icons.help_outline,
|
||||||
color: Theme.of(context)
|
size: 16,
|
||||||
.accentTextTheme!
|
color: Theme.of(context)
|
||||||
.displaySmall!
|
.accentTextTheme!
|
||||||
.backgroundColor!),
|
.displaySmall!
|
||||||
)
|
.backgroundColor!),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),SizedBox(
|
|
||||||
height: 6,
|
|
||||||
),
|
),
|
||||||
|
SizedBox(height: 6),
|
||||||
AutoSizeText(availableBalance,
|
AutoSizeText(availableBalance,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
|
@ -186,9 +193,7 @@ class BalancePage extends StatelessWidget {
|
||||||
height: 1),
|
height: 1),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
textAlign: TextAlign.start),
|
textAlign: TextAlign.start),
|
||||||
SizedBox(
|
SizedBox(height: 6),
|
||||||
height: 6,
|
|
||||||
),
|
|
||||||
Text('${availableFiatBalance}',
|
Text('${availableFiatBalance}',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
@ -200,7 +205,6 @@ class BalancePage extends StatelessWidget {
|
||||||
.displayMedium!
|
.displayMedium!
|
||||||
.backgroundColor!,
|
.backgroundColor!,
|
||||||
height: 1)),
|
height: 1)),
|
||||||
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -209,90 +213,96 @@ class BalancePage extends StatelessWidget {
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
fontFamily: 'Lato',
|
fontFamily: 'Lato',
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context).accentTextTheme!.displayMedium!.backgroundColor!,
|
||||||
.accentTextTheme!
|
|
||||||
.displayMedium!
|
|
||||||
.backgroundColor!,
|
|
||||||
height: 1)),
|
height: 1)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 26),
|
|
||||||
if (frozenBalance.isNotEmpty)
|
if (frozenBalance.isNotEmpty)
|
||||||
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
Column(
|
||||||
Text(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
S.current.frozen_balance,
|
children: [
|
||||||
textAlign: TextAlign.center,
|
SizedBox(height: 26),
|
||||||
style: TextStyle(
|
Text(
|
||||||
fontSize: 12,
|
S.current.frozen_balance,
|
||||||
fontFamily: 'Lato',
|
textAlign: TextAlign.center,
|
||||||
fontWeight: FontWeight.w400,
|
style: TextStyle(
|
||||||
color: Theme.of(context).accentTextTheme.displaySmall!.backgroundColor!,
|
fontSize: 12,
|
||||||
height: 1,
|
fontFamily: 'Lato',
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Theme.of(context).accentTextTheme.displaySmall!.backgroundColor!,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 8),
|
||||||
SizedBox(height: 8),
|
AutoSizeText(
|
||||||
AutoSizeText(
|
frozenBalance,
|
||||||
frozenBalance,
|
style: TextStyle(
|
||||||
style: TextStyle(
|
fontSize: 20,
|
||||||
fontSize: 20,
|
fontFamily: 'Lato',
|
||||||
fontFamily: 'Lato',
|
fontWeight: FontWeight.w400,
|
||||||
fontWeight: FontWeight.w400,
|
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||||
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
height: 1,
|
||||||
height: 1,
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
SizedBox(height: 4),
|
||||||
textAlign: TextAlign.center,
|
Text(
|
||||||
),
|
frozenFiatBalance,
|
||||||
SizedBox(height: 4),
|
textAlign: TextAlign.center,
|
||||||
Text(
|
style: TextStyle(
|
||||||
frozenFiatBalance,
|
fontSize: 12,
|
||||||
textAlign: TextAlign.center,
|
fontFamily: 'Lato',
|
||||||
style: TextStyle(
|
fontWeight: FontWeight.w400,
|
||||||
fontSize: 12,
|
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||||
fontFamily: 'Lato',
|
height: 1,
|
||||||
fontWeight: FontWeight.w400,
|
),
|
||||||
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
|
||||||
height: 1,
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
SizedBox(height: 24)
|
|
||||||
]),
|
|
||||||
Text(
|
|
||||||
'${additionalBalanceLabel}',
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontFamily: 'Lato',
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
color: Theme.of(context).accentTextTheme.displaySmall!.backgroundColor!,
|
|
||||||
height: 1,
|
|
||||||
),
|
),
|
||||||
),
|
if (hasAdditionalBalance)
|
||||||
SizedBox(height: 8),
|
Column(
|
||||||
AutoSizeText(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
additionalBalance,
|
children: [
|
||||||
style: TextStyle(
|
SizedBox(height: 24),
|
||||||
fontSize: 20,
|
Text(
|
||||||
fontFamily: 'Lato',
|
'${additionalBalanceLabel}',
|
||||||
fontWeight: FontWeight.w400,
|
textAlign: TextAlign.center,
|
||||||
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
style: TextStyle(
|
||||||
height: 1,
|
fontSize: 12,
|
||||||
|
fontFamily: 'Lato',
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Theme.of(context).accentTextTheme.displaySmall!.backgroundColor!,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
AutoSizeText(
|
||||||
|
additionalBalance,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontFamily: 'Lato',
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'${additionalFiatBalance}',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: 'Lato',
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
||||||
|
height: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
'${additionalFiatBalance}',
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontFamily: 'Lato',
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
|
|
||||||
height: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/core/auth_service.dart';
|
||||||
import 'package:cake_wallet/di.dart';
|
import 'package:cake_wallet/di.dart';
|
||||||
import 'package:cake_wallet/src/screens/exchange/widgets/desktop_exchange_cards_section.dart';
|
import 'package:cake_wallet/src/screens/exchange/widgets/desktop_exchange_cards_section.dart';
|
||||||
import 'package:cake_wallet/src/screens/exchange/widgets/mobile_exchange_cards_section.dart';
|
import 'package:cake_wallet/src/screens/exchange/widgets/mobile_exchange_cards_section.dart';
|
||||||
|
@ -37,7 +38,7 @@ import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker
|
||||||
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
|
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
|
||||||
|
|
||||||
class ExchangePage extends BasePage {
|
class ExchangePage extends BasePage {
|
||||||
ExchangePage(this.exchangeViewModel) {
|
ExchangePage(this.exchangeViewModel, this.authService) {
|
||||||
depositWalletName = exchangeViewModel.depositCurrency == CryptoCurrency.xmr
|
depositWalletName = exchangeViewModel.depositCurrency == CryptoCurrency.xmr
|
||||||
? exchangeViewModel.wallet.name
|
? exchangeViewModel.wallet.name
|
||||||
: null;
|
: null;
|
||||||
|
@ -47,6 +48,7 @@ class ExchangePage extends BasePage {
|
||||||
}
|
}
|
||||||
|
|
||||||
final ExchangeViewModel exchangeViewModel;
|
final ExchangeViewModel exchangeViewModel;
|
||||||
|
final AuthService authService;
|
||||||
final depositKey = GlobalKey<ExchangeCardState>();
|
final depositKey = GlobalKey<ExchangeCardState>();
|
||||||
final receiveKey = GlobalKey<ExchangeCardState>();
|
final receiveKey = GlobalKey<ExchangeCardState>();
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
@ -89,16 +91,17 @@ class ExchangePage extends BasePage {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget middle(BuildContext context) => Row(
|
Widget middle(BuildContext context) => Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right:6.0),
|
padding: const EdgeInsets.only(right: 6.0),
|
||||||
child: Observer(builder: (_) => SyncIndicatorIcon(isSynced: exchangeViewModel.status is SyncedSyncStatus),)
|
child: Observer(
|
||||||
),
|
builder: (_) =>
|
||||||
PresentProviderPicker(exchangeViewModel: exchangeViewModel)
|
SyncIndicatorIcon(isSynced: exchangeViewModel.status is SyncedSyncStatus),
|
||||||
],
|
)),
|
||||||
);
|
PresentProviderPicker(exchangeViewModel: exchangeViewModel)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget trailing(BuildContext context) => TrailButton(
|
Widget trailing(BuildContext context) => TrailButton(
|
||||||
|
@ -110,12 +113,13 @@ class ExchangePage extends BasePage {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? leading(BuildContext context) {
|
Widget? leading(BuildContext context) {
|
||||||
final _backButton = Icon(Icons.arrow_back_ios,
|
final _backButton = Icon(
|
||||||
|
Icons.arrow_back_ios,
|
||||||
color: titleColor,
|
color: titleColor,
|
||||||
size: 16,
|
size: 16,
|
||||||
);
|
);
|
||||||
final _closeButton = currentTheme.type == ThemeType.dark
|
final _closeButton =
|
||||||
? closeButtonImageDarkTheme : closeButtonImage;
|
currentTheme.type == ThemeType.dark ? closeButtonImageDarkTheme : closeButtonImage;
|
||||||
|
|
||||||
bool isMobileView = ResponsiveLayoutUtil.instance.isMobile;
|
bool isMobileView = ResponsiveLayoutUtil.instance.isMobile;
|
||||||
|
|
||||||
|
@ -126,13 +130,10 @@ class ExchangePage extends BasePage {
|
||||||
child: ButtonTheme(
|
child: ButtonTheme(
|
||||||
minWidth: double.minPositive,
|
minWidth: double.minPositive,
|
||||||
child: Semantics(
|
child: Semantics(
|
||||||
label: !isMobileView
|
label: !isMobileView ? S.of(context).close : S.of(context).seed_alert_back,
|
||||||
? S.of(context).close
|
|
||||||
: S.of(context).seed_alert_back,
|
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
overlayColor: MaterialStateColor.resolveWith(
|
overlayColor: MaterialStateColor.resolveWith((states) => Colors.transparent),
|
||||||
(states) => Colors.transparent),
|
|
||||||
),
|
),
|
||||||
onPressed: () => onClose(context),
|
onPressed: () => onClose(context),
|
||||||
child: !isMobileView ? _closeButton : _backButton,
|
child: !isMobileView ? _closeButton : _backButton,
|
||||||
|
@ -145,23 +146,19 @@ class ExchangePage extends BasePage {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) {
|
Widget body(BuildContext context) {
|
||||||
WidgetsBinding.instance
|
WidgetsBinding.instance.addPostFrameCallback((_) => _setReactions(context, exchangeViewModel));
|
||||||
.addPostFrameCallback((_) => _setReactions(context, exchangeViewModel));
|
|
||||||
|
|
||||||
return KeyboardActions(
|
return KeyboardActions(
|
||||||
disableScroll: true,
|
disableScroll: true,
|
||||||
config: KeyboardActionsConfig(
|
config: KeyboardActionsConfig(
|
||||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||||
keyboardBarColor:
|
keyboardBarColor: Theme.of(context).accentTextTheme.bodyLarge!.backgroundColor!,
|
||||||
Theme.of(context).accentTextTheme!.bodyLarge!.backgroundColor!,
|
|
||||||
nextFocus: false,
|
nextFocus: false,
|
||||||
actions: [
|
actions: [
|
||||||
KeyboardActionsItem(
|
KeyboardActionsItem(
|
||||||
focusNode: _depositAmountFocus,
|
focusNode: _depositAmountFocus, toolbarButtons: [(_) => KeyboardDoneButton()]),
|
||||||
toolbarButtons: [(_) => KeyboardDoneButton()]),
|
|
||||||
KeyboardActionsItem(
|
KeyboardActionsItem(
|
||||||
focusNode: _receiveAmountFocus,
|
focusNode: _receiveAmountFocus, toolbarButtons: [(_) => KeyboardDoneButton()])
|
||||||
toolbarButtons: [(_) => KeyboardDoneButton()])
|
|
||||||
]),
|
]),
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Theme.of(context).colorScheme.background,
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
@ -169,30 +166,28 @@ class ExchangePage extends BasePage {
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: ScrollableWithBottomSection(
|
child: ScrollableWithBottomSection(
|
||||||
contentPadding: EdgeInsets.only(bottom: 24),
|
contentPadding: EdgeInsets.only(bottom: 24),
|
||||||
content: Observer(builder: (_) => Column(
|
content: Observer(
|
||||||
children: <Widget>[
|
builder: (_) => Column(
|
||||||
_exchangeCardsSection(context),
|
children: <Widget>[
|
||||||
Padding(
|
_exchangeCardsSection(context),
|
||||||
padding: EdgeInsets.only(top: 12, left: 24),
|
Padding(
|
||||||
child: Row(
|
padding: EdgeInsets.only(top: 12, left: 24),
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
child: Row(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
StandardCheckbox(
|
children: [
|
||||||
value: exchangeViewModel.isFixedRateMode,
|
StandardCheckbox(
|
||||||
caption: S.of(context).fixed_rate,
|
value: exchangeViewModel.isFixedRateMode,
|
||||||
onChanged: (value) =>
|
caption: S.of(context).fixed_rate,
|
||||||
exchangeViewModel.isFixedRateMode = value,
|
onChanged: (value) => exchangeViewModel.isFixedRateMode = value,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)),
|
||||||
),
|
SizedBox(height: 30),
|
||||||
SizedBox(height: 30),
|
_buildTemplateSection(context)
|
||||||
_buildTemplateSection(context)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
bottomSectionPadding:
|
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||||
EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
|
||||||
bottomSection: Column(children: <Widget>[
|
bottomSection: Column(children: <Widget>[
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(bottom: 15),
|
padding: EdgeInsets.only(bottom: 15),
|
||||||
|
@ -210,8 +205,7 @@ class ExchangePage extends BasePage {
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.primaryTextTheme!
|
.primaryTextTheme.displayLarge!
|
||||||
.displayLarge!
|
|
||||||
.decorationColor!,
|
.decorationColor!,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 12),
|
fontSize: 12),
|
||||||
|
@ -223,29 +217,34 @@ class ExchangePage extends BasePage {
|
||||||
builder: (_) => LoadingPrimaryButton(
|
builder: (_) => LoadingPrimaryButton(
|
||||||
text: S.of(context).exchange,
|
text: S.of(context).exchange,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_formKey.currentState != null && _formKey.currentState!.validate()) {
|
if (_formKey.currentState != null &&
|
||||||
if ((exchangeViewModel.depositCurrency ==
|
_formKey.currentState!.validate()) {
|
||||||
CryptoCurrency.xmr) &&
|
if ((exchangeViewModel.depositCurrency == CryptoCurrency.xmr) &&
|
||||||
(!(exchangeViewModel.status
|
(!(exchangeViewModel.status is SyncedSyncStatus))) {
|
||||||
is SyncedSyncStatus))) {
|
|
||||||
showPopUp<void>(
|
showPopUp<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertWithOneAction(
|
return AlertWithOneAction(
|
||||||
alertTitle: S.of(context).exchange,
|
alertTitle: S.of(context).exchange,
|
||||||
alertContent: S
|
alertContent: S.of(context).exchange_sync_alert_content,
|
||||||
.of(context)
|
|
||||||
.exchange_sync_alert_content,
|
|
||||||
buttonText: S.of(context).ok,
|
buttonText: S.of(context).ok,
|
||||||
buttonAction: () =>
|
buttonAction: () => Navigator.of(context).pop());
|
||||||
Navigator.of(context).pop());
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
exchangeViewModel.createTrade();
|
final check = exchangeViewModel.shouldDisplayTOTP();
|
||||||
|
authService.authenticateAction(
|
||||||
|
context,
|
||||||
|
conditionToDetermineIfToUse2FA: check,
|
||||||
|
onAuthSuccess: (value) {
|
||||||
|
if (value) {
|
||||||
|
exchangeViewModel.createTrade();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
color: Theme.of(context).accentTextTheme!.bodyLarge!.color!,
|
color: Theme.of(context).accentTextTheme.bodyLarge!.color!,
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
isDisabled: exchangeViewModel.selectedProviders.isEmpty,
|
isDisabled: exchangeViewModel.selectedProviders.isEmpty,
|
||||||
isLoading: exchangeViewModel.tradeState is TradeIsCreating)),
|
isLoading: exchangeViewModel.tradeState is TradeIsCreating)),
|
||||||
|
@ -264,7 +263,7 @@ class ExchangePage extends BasePage {
|
||||||
child: Observer(
|
child: Observer(
|
||||||
builder: (_) {
|
builder: (_) {
|
||||||
final templates = exchangeViewModel.templates;
|
final templates = exchangeViewModel.templates;
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
AddTemplateButton(
|
AddTemplateButton(
|
||||||
|
@ -293,18 +292,15 @@ class ExchangePage extends BasePage {
|
||||||
builder: (dialogContext) {
|
builder: (dialogContext) {
|
||||||
return AlertWithTwoActions(
|
return AlertWithTwoActions(
|
||||||
alertTitle: S.of(context).template,
|
alertTitle: S.of(context).template,
|
||||||
alertContent:
|
alertContent: S.of(context).confirm_delete_template,
|
||||||
S.of(context).confirm_delete_template,
|
|
||||||
rightButtonText: S.of(context).delete,
|
rightButtonText: S.of(context).delete,
|
||||||
leftButtonText: S.of(context).cancel,
|
leftButtonText: S.of(context).cancel,
|
||||||
actionRightButton: () {
|
actionRightButton: () {
|
||||||
Navigator.of(dialogContext).pop();
|
Navigator.of(dialogContext).pop();
|
||||||
exchangeViewModel.removeTemplate(
|
exchangeViewModel.removeTemplate(template: template);
|
||||||
template: template);
|
|
||||||
exchangeViewModel.updateTemplate();
|
exchangeViewModel.updateTemplate();
|
||||||
},
|
},
|
||||||
actionLeftButton: () =>
|
actionLeftButton: () => Navigator.of(dialogContext).pop());
|
||||||
Navigator.of(dialogContext).pop());
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -318,8 +314,8 @@ class ExchangePage extends BasePage {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void applyTemplate(BuildContext context,
|
void applyTemplate(
|
||||||
ExchangeViewModel exchangeViewModel, ExchangeTemplate template) async {
|
BuildContext context, ExchangeViewModel exchangeViewModel, ExchangeTemplate template) async {
|
||||||
exchangeViewModel.changeDepositCurrency(
|
exchangeViewModel.changeDepositCurrency(
|
||||||
currency: CryptoCurrency.fromString(template.depositCurrency));
|
currency: CryptoCurrency.fromString(template.depositCurrency));
|
||||||
exchangeViewModel.changeReceiveCurrency(
|
exchangeViewModel.changeReceiveCurrency(
|
||||||
|
@ -333,22 +329,19 @@ class ExchangePage extends BasePage {
|
||||||
|
|
||||||
var domain = template.depositAddress;
|
var domain = template.depositAddress;
|
||||||
var ticker = template.depositCurrency.toLowerCase();
|
var ticker = template.depositCurrency.toLowerCase();
|
||||||
exchangeViewModel.depositAddress =
|
exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, ticker);
|
||||||
await fetchParsedAddress(context, domain, ticker);
|
|
||||||
|
|
||||||
domain = template.receiveAddress;
|
domain = template.receiveAddress;
|
||||||
ticker = template.receiveCurrency.toLowerCase();
|
ticker = template.receiveCurrency.toLowerCase();
|
||||||
exchangeViewModel.receiveAddress =
|
exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, ticker);
|
||||||
await fetchParsedAddress(context, domain, ticker);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setReactions(
|
void _setReactions(BuildContext context, ExchangeViewModel exchangeViewModel) {
|
||||||
BuildContext context, ExchangeViewModel exchangeViewModel) {
|
|
||||||
if (_isReactionsSet) {
|
if (_isReactionsSet) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exchangeViewModel.isLowFee) {
|
if (exchangeViewModel.isLowFee) {
|
||||||
_showFeeAlert(context);
|
_showFeeAlert(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,42 +352,30 @@ class ExchangePage extends BasePage {
|
||||||
final limitsState = exchangeViewModel.limitsState;
|
final limitsState = exchangeViewModel.limitsState;
|
||||||
|
|
||||||
if (limitsState is LimitsLoadedSuccessfully) {
|
if (limitsState is LimitsLoadedSuccessfully) {
|
||||||
final min = limitsState.limits.min != null
|
final min = limitsState.limits.min != null ? limitsState.limits.min.toString() : null;
|
||||||
? limitsState.limits.min.toString()
|
final max = limitsState.limits.max != null ? limitsState.limits.max.toString() : null;
|
||||||
: null;
|
final key = exchangeViewModel.isFixedRateMode ? receiveKey : depositKey;
|
||||||
final max = limitsState.limits.max != null
|
|
||||||
? limitsState.limits.max.toString()
|
|
||||||
: null;
|
|
||||||
final key = exchangeViewModel.isFixedRateMode
|
|
||||||
? receiveKey
|
|
||||||
: depositKey;
|
|
||||||
key.currentState!.changeLimits(min: min, max: max);
|
key.currentState!.changeLimits(min: min, max: max);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCurrencyChange(
|
_onCurrencyChange(exchangeViewModel.receiveCurrency, exchangeViewModel, receiveKey);
|
||||||
exchangeViewModel.receiveCurrency, exchangeViewModel, receiveKey);
|
_onCurrencyChange(exchangeViewModel.depositCurrency, exchangeViewModel, depositKey);
|
||||||
_onCurrencyChange(
|
|
||||||
exchangeViewModel.depositCurrency, exchangeViewModel, depositKey);
|
|
||||||
|
|
||||||
reaction(
|
reaction(
|
||||||
(_) => exchangeViewModel.wallet.name,
|
(_) => exchangeViewModel.wallet.name,
|
||||||
(String _) => _onWalletNameChange(
|
(String _) =>
|
||||||
exchangeViewModel, exchangeViewModel.receiveCurrency, receiveKey));
|
_onWalletNameChange(exchangeViewModel, exchangeViewModel.receiveCurrency, receiveKey));
|
||||||
|
|
||||||
reaction(
|
reaction(
|
||||||
(_) => exchangeViewModel.wallet.name,
|
(_) => exchangeViewModel.wallet.name,
|
||||||
(String _) => _onWalletNameChange(
|
(String _) =>
|
||||||
exchangeViewModel, exchangeViewModel.depositCurrency, depositKey));
|
_onWalletNameChange(exchangeViewModel, exchangeViewModel.depositCurrency, depositKey));
|
||||||
|
|
||||||
reaction(
|
reaction((_) => exchangeViewModel.receiveCurrency,
|
||||||
(_) => exchangeViewModel.receiveCurrency,
|
(CryptoCurrency currency) => _onCurrencyChange(currency, exchangeViewModel, receiveKey));
|
||||||
(CryptoCurrency currency) =>
|
|
||||||
_onCurrencyChange(currency, exchangeViewModel, receiveKey));
|
|
||||||
|
|
||||||
reaction(
|
reaction((_) => exchangeViewModel.depositCurrency,
|
||||||
(_) => exchangeViewModel.depositCurrency,
|
(CryptoCurrency currency) => _onCurrencyChange(currency, exchangeViewModel, depositKey));
|
||||||
(CryptoCurrency currency) =>
|
|
||||||
_onCurrencyChange(currency, exchangeViewModel, depositKey));
|
|
||||||
|
|
||||||
reaction((_) => exchangeViewModel.depositAmount, (String amount) {
|
reaction((_) => exchangeViewModel.depositAmount, (String amount) {
|
||||||
if (depositKey.currentState!.amountController.text != amount) {
|
if (depositKey.currentState!.amountController.text != amount) {
|
||||||
|
@ -408,8 +389,7 @@ class ExchangePage extends BasePage {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
reaction((_) => exchangeViewModel.isDepositAddressEnabled,
|
reaction((_) => exchangeViewModel.isDepositAddressEnabled, (bool isEnabled) {
|
||||||
(bool isEnabled) {
|
|
||||||
depositKey.currentState!.isAddressEditable(isEditable: isEnabled);
|
depositKey.currentState!.isAddressEditable(isEditable: isEnabled);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -425,13 +405,11 @@ class ExchangePage extends BasePage {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
reaction((_) => exchangeViewModel.isReceiveAddressEnabled,
|
reaction((_) => exchangeViewModel.isReceiveAddressEnabled, (bool isEnabled) {
|
||||||
(bool isEnabled) {
|
|
||||||
receiveKey.currentState!.isAddressEditable(isEditable: isEnabled);
|
receiveKey.currentState!.isAddressEditable(isEditable: isEnabled);
|
||||||
});
|
});
|
||||||
|
|
||||||
reaction((_) => exchangeViewModel.isReceiveAmountEditable,
|
reaction((_) => exchangeViewModel.isReceiveAmountEditable, (bool isReceiveAmountEditable) {
|
||||||
(bool isReceiveAmountEditable) {
|
|
||||||
receiveKey.currentState!.isAmountEditable(isEditable: isReceiveAmountEditable);
|
receiveKey.currentState!.isAmountEditable(isEditable: isReceiveAmountEditable);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -483,20 +461,20 @@ class ExchangePage extends BasePage {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
depositAddressController.addListener(
|
depositAddressController
|
||||||
() => exchangeViewModel.depositAddress = depositAddressController.text);
|
.addListener(() => exchangeViewModel.depositAddress = depositAddressController.text);
|
||||||
|
|
||||||
depositAmountController.addListener(() {
|
depositAmountController.addListener(() {
|
||||||
if (depositAmountController.text != exchangeViewModel.depositAmount) {
|
if (depositAmountController.text != exchangeViewModel.depositAmount) {
|
||||||
_depositAmountDebounce.run(() {
|
_depositAmountDebounce.run(() {
|
||||||
exchangeViewModel.changeDepositAmount(amount: depositAmountController.text);
|
exchangeViewModel.changeDepositAmount(amount: depositAmountController.text);
|
||||||
exchangeViewModel.isReceiveAmountEntered = false;
|
exchangeViewModel.isReceiveAmountEntered = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
receiveAddressController.addListener(
|
receiveAddressController
|
||||||
() => exchangeViewModel.receiveAddress = receiveAddressController.text);
|
.addListener(() => exchangeViewModel.receiveAddress = receiveAddressController.text);
|
||||||
|
|
||||||
receiveAmountController.addListener(() {
|
receiveAmountController.addListener(() {
|
||||||
if (receiveAmountController.text != exchangeViewModel.receiveAmount) {
|
if (receiveAmountController.text != exchangeViewModel.receiveAmount) {
|
||||||
|
@ -507,8 +485,7 @@ class ExchangePage extends BasePage {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
reaction((_) => exchangeViewModel.wallet.walletAddresses.address,
|
reaction((_) => exchangeViewModel.wallet.walletAddresses.address, (String address) {
|
||||||
(String address) {
|
|
||||||
if (exchangeViewModel.depositCurrency == CryptoCurrency.xmr) {
|
if (exchangeViewModel.depositCurrency == CryptoCurrency.xmr) {
|
||||||
depositKey.currentState!.changeAddress(address: address);
|
depositKey.currentState!.changeAddress(address: address);
|
||||||
}
|
}
|
||||||
|
@ -519,22 +496,18 @@ class ExchangePage extends BasePage {
|
||||||
});
|
});
|
||||||
|
|
||||||
_depositAddressFocus.addListener(() async {
|
_depositAddressFocus.addListener(() async {
|
||||||
if (!_depositAddressFocus.hasFocus &&
|
if (!_depositAddressFocus.hasFocus && depositAddressController.text.isNotEmpty) {
|
||||||
depositAddressController.text.isNotEmpty) {
|
|
||||||
final domain = depositAddressController.text;
|
final domain = depositAddressController.text;
|
||||||
final ticker = exchangeViewModel.depositCurrency.title.toLowerCase();
|
final ticker = exchangeViewModel.depositCurrency.title.toLowerCase();
|
||||||
exchangeViewModel.depositAddress =
|
exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, ticker);
|
||||||
await fetchParsedAddress(context, domain, ticker);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_receiveAddressFocus.addListener(() async {
|
_receiveAddressFocus.addListener(() async {
|
||||||
if (!_receiveAddressFocus.hasFocus &&
|
if (!_receiveAddressFocus.hasFocus && receiveAddressController.text.isNotEmpty) {
|
||||||
receiveAddressController.text.isNotEmpty) {
|
|
||||||
final domain = receiveAddressController.text;
|
final domain = receiveAddressController.text;
|
||||||
final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase();
|
final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase();
|
||||||
exchangeViewModel.receiveAddress =
|
exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, ticker);
|
||||||
await fetchParsedAddress(context, domain, ticker);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -554,29 +527,26 @@ class ExchangePage extends BasePage {
|
||||||
_isReactionsSet = true;
|
_isReactionsSet = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onCurrencyChange(CryptoCurrency currency,
|
void _onCurrencyChange(CryptoCurrency currency, ExchangeViewModel exchangeViewModel,
|
||||||
ExchangeViewModel exchangeViewModel, GlobalKey<ExchangeCardState> key) {
|
GlobalKey<ExchangeCardState> key) {
|
||||||
final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency;
|
final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency;
|
||||||
|
|
||||||
key.currentState!.changeSelectedCurrency(currency);
|
key.currentState!.changeSelectedCurrency(currency);
|
||||||
key.currentState!.changeWalletName(
|
key.currentState!.changeWalletName(isCurrentTypeWallet ? exchangeViewModel.wallet.name : '');
|
||||||
isCurrentTypeWallet ? exchangeViewModel.wallet.name : '');
|
|
||||||
|
|
||||||
key.currentState!.changeAddress(
|
key.currentState!.changeAddress(
|
||||||
address: isCurrentTypeWallet
|
address: isCurrentTypeWallet ? exchangeViewModel.wallet.walletAddresses.address : '');
|
||||||
? exchangeViewModel.wallet.walletAddresses.address : '');
|
|
||||||
|
|
||||||
key.currentState!.changeAmount(amount: '');
|
key.currentState!.changeAmount(amount: '');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onWalletNameChange(ExchangeViewModel exchangeViewModel,
|
void _onWalletNameChange(ExchangeViewModel exchangeViewModel, CryptoCurrency currency,
|
||||||
CryptoCurrency currency, GlobalKey<ExchangeCardState> key) {
|
GlobalKey<ExchangeCardState> key) {
|
||||||
final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency;
|
final isCurrentTypeWallet = currency == exchangeViewModel.wallet.currency;
|
||||||
|
|
||||||
if (isCurrentTypeWallet) {
|
if (isCurrentTypeWallet) {
|
||||||
key.currentState!.changeWalletName(exchangeViewModel.wallet.name);
|
key.currentState!.changeWalletName(exchangeViewModel.wallet.name);
|
||||||
key.currentState!.addressController.text =
|
key.currentState!.addressController.text = exchangeViewModel.wallet.walletAddresses.address;
|
||||||
exchangeViewModel.wallet.walletAddresses.address;
|
|
||||||
} else if (key.currentState!.addressController.text ==
|
} else if (key.currentState!.addressController.text ==
|
||||||
exchangeViewModel.wallet.walletAddresses.address) {
|
exchangeViewModel.wallet.walletAddresses.address) {
|
||||||
key.currentState!.changeWalletName('');
|
key.currentState!.changeWalletName('');
|
||||||
|
@ -584,8 +554,7 @@ class ExchangePage extends BasePage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> fetchParsedAddress(
|
Future<String> fetchParsedAddress(BuildContext context, String domain, String ticker) async {
|
||||||
BuildContext context, String domain, String ticker) async {
|
|
||||||
final parsedAddress = await getIt.get<AddressResolver>().resolve(domain, ticker);
|
final parsedAddress = await getIt.get<AddressResolver>().resolve(domain, ticker);
|
||||||
final address = await extractAddressFromParsed(context, parsedAddress);
|
final address = await extractAddressFromParsed(context, parsedAddress);
|
||||||
return address;
|
return address;
|
||||||
|
@ -594,16 +563,17 @@ class ExchangePage extends BasePage {
|
||||||
void _showFeeAlert(BuildContext context) async {
|
void _showFeeAlert(BuildContext context) async {
|
||||||
await Future<void>.delayed(Duration(seconds: 1));
|
await Future<void>.delayed(Duration(seconds: 1));
|
||||||
final confirmed = await showPopUp<bool>(
|
final confirmed = await showPopUp<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (dialogContext) {
|
builder: (dialogContext) {
|
||||||
return AlertWithTwoActions(
|
return AlertWithTwoActions(
|
||||||
alertTitle: S.of(context).low_fee,
|
alertTitle: S.of(context).low_fee,
|
||||||
alertContent: S.of(context).low_fee_alert,
|
alertContent: S.of(context).low_fee_alert,
|
||||||
leftButtonText: S.of(context).ignor,
|
leftButtonText: S.of(context).ignor,
|
||||||
rightButtonText: S.of(context).use_suggested,
|
rightButtonText: S.of(context).use_suggested,
|
||||||
actionLeftButton: () => Navigator.of(dialogContext).pop(false),
|
actionLeftButton: () => Navigator.of(dialogContext).pop(false),
|
||||||
actionRightButton: () => Navigator.of(dialogContext).pop(true));
|
actionRightButton: () => Navigator.of(dialogContext).pop(true));
|
||||||
}) ?? false;
|
}) ??
|
||||||
|
false;
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
exchangeViewModel.setDefaultTransactionPriority();
|
exchangeViewModel.setDefaultTransactionPriority();
|
||||||
}
|
}
|
||||||
|
@ -612,126 +582,122 @@ class ExchangePage extends BasePage {
|
||||||
void disposeBestRateSync() => exchangeViewModel.bestRateSync.cancel();
|
void disposeBestRateSync() => exchangeViewModel.bestRateSync.cancel();
|
||||||
|
|
||||||
Widget _exchangeCardsSection(BuildContext context) {
|
Widget _exchangeCardsSection(BuildContext context) {
|
||||||
final firstExchangeCard = Observer(builder: (_) => ExchangeCard(
|
final firstExchangeCard = Observer(
|
||||||
onDispose: disposeBestRateSync,
|
builder: (_) => ExchangeCard(
|
||||||
hasAllAmount: exchangeViewModel.hasAllAmount,
|
onDispose: disposeBestRateSync,
|
||||||
allAmount: exchangeViewModel.hasAllAmount
|
hasAllAmount: exchangeViewModel.hasAllAmount,
|
||||||
? () => exchangeViewModel.calculateDepositAllAmount()
|
allAmount: exchangeViewModel.hasAllAmount
|
||||||
: null,
|
? () => exchangeViewModel.calculateDepositAllAmount()
|
||||||
amountFocusNode: _depositAmountFocus,
|
: null,
|
||||||
addressFocusNode: _depositAddressFocus,
|
amountFocusNode: _depositAmountFocus,
|
||||||
key: depositKey,
|
addressFocusNode: _depositAddressFocus,
|
||||||
title: S.of(context).you_will_send,
|
key: depositKey,
|
||||||
initialCurrency: exchangeViewModel.depositCurrency,
|
title: S.of(context).you_will_send,
|
||||||
initialWalletName: depositWalletName ?? '',
|
initialCurrency: exchangeViewModel.depositCurrency,
|
||||||
initialAddress:
|
initialWalletName: depositWalletName ?? '',
|
||||||
exchangeViewModel.depositCurrency == exchangeViewModel.wallet.currency
|
initialAddress: exchangeViewModel.depositCurrency == exchangeViewModel.wallet.currency
|
||||||
? exchangeViewModel.wallet.walletAddresses.address
|
? exchangeViewModel.wallet.walletAddresses.address
|
||||||
: exchangeViewModel.depositAddress,
|
: exchangeViewModel.depositAddress,
|
||||||
initialIsAmountEditable: true,
|
initialIsAmountEditable: true,
|
||||||
initialIsAddressEditable: exchangeViewModel.isDepositAddressEnabled,
|
initialIsAddressEditable: exchangeViewModel.isDepositAddressEnabled,
|
||||||
isAmountEstimated: false,
|
isAmountEstimated: false,
|
||||||
hasRefundAddress: true,
|
hasRefundAddress: true,
|
||||||
isMoneroWallet: exchangeViewModel.isMoneroWallet,
|
isMoneroWallet: exchangeViewModel.isMoneroWallet,
|
||||||
currencies: exchangeViewModel.depositCurrencies,
|
currencies: exchangeViewModel.depositCurrencies,
|
||||||
onCurrencySelected: (currency) {
|
onCurrencySelected: (currency) {
|
||||||
// FIXME: need to move it into view model
|
// FIXME: need to move it into view model
|
||||||
if (currency == CryptoCurrency.xmr &&
|
if (currency == CryptoCurrency.xmr &&
|
||||||
exchangeViewModel.wallet.type != WalletType.monero) {
|
exchangeViewModel.wallet.type != WalletType.monero) {
|
||||||
showPopUp<void>(
|
showPopUp<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (dialogContext) {
|
builder: (dialogContext) {
|
||||||
return AlertWithOneAction(
|
return AlertWithOneAction(
|
||||||
alertTitle: S.of(context).error,
|
alertTitle: S.of(context).error,
|
||||||
alertContent:
|
alertContent: S.of(context).exchange_incorrect_current_wallet_for_xmr,
|
||||||
S.of(context).exchange_incorrect_current_wallet_for_xmr,
|
buttonText: S.of(context).ok,
|
||||||
buttonText: S.of(context).ok,
|
buttonAction: () => Navigator.of(dialogContext).pop());
|
||||||
buttonAction: () => Navigator.of(dialogContext).pop());
|
});
|
||||||
});
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
exchangeViewModel.changeDepositCurrency(currency: currency);
|
exchangeViewModel.changeDepositCurrency(currency: currency);
|
||||||
},
|
},
|
||||||
imageArrow: arrowBottomPurple,
|
imageArrow: arrowBottomPurple,
|
||||||
currencyButtonColor: Colors.transparent,
|
currencyButtonColor: Colors.transparent,
|
||||||
addressButtonsColor: Theme.of(context).focusColor!,
|
addressButtonsColor: Theme.of(context).focusColor,
|
||||||
borderColor: Theme.of(context).primaryTextTheme!.bodyLarge!.color!,
|
borderColor: Theme.of(context).primaryTextTheme.bodyLarge!.color!,
|
||||||
currencyValueValidator: (value) {
|
currencyValueValidator: (value) {
|
||||||
return !exchangeViewModel.isFixedRateMode
|
return !exchangeViewModel.isFixedRateMode
|
||||||
? AmountValidator(
|
? AmountValidator(
|
||||||
isAutovalidate: true,
|
isAutovalidate: true,
|
||||||
currency: exchangeViewModel.depositCurrency,
|
currency: exchangeViewModel.depositCurrency,
|
||||||
minValue: exchangeViewModel.limits.min.toString(),
|
minValue: exchangeViewModel.limits.min.toString(),
|
||||||
maxValue: exchangeViewModel.limits.max.toString(),
|
maxValue: exchangeViewModel.limits.max.toString(),
|
||||||
).call(value)
|
).call(value)
|
||||||
: null;
|
: null;
|
||||||
},
|
},
|
||||||
addressTextFieldValidator:
|
addressTextFieldValidator: AddressValidator(type: exchangeViewModel.depositCurrency),
|
||||||
AddressValidator(type: exchangeViewModel.depositCurrency),
|
onPushPasteButton: (context) async {
|
||||||
onPushPasteButton: (context) async {
|
final domain = exchangeViewModel.depositAddress;
|
||||||
final domain = exchangeViewModel.depositAddress;
|
final ticker = exchangeViewModel.depositCurrency.title.toLowerCase();
|
||||||
final ticker = exchangeViewModel.depositCurrency.title.toLowerCase();
|
exchangeViewModel.depositAddress =
|
||||||
exchangeViewModel.depositAddress =
|
await fetchParsedAddress(context, domain, ticker);
|
||||||
await fetchParsedAddress(context, domain, ticker);
|
},
|
||||||
},
|
onPushAddressBookButton: (context) async {
|
||||||
onPushAddressBookButton: (context) async {
|
final domain = exchangeViewModel.depositAddress;
|
||||||
final domain = exchangeViewModel.depositAddress;
|
final ticker = exchangeViewModel.depositCurrency.title.toLowerCase();
|
||||||
final ticker = exchangeViewModel.depositCurrency.title.toLowerCase();
|
exchangeViewModel.depositAddress =
|
||||||
exchangeViewModel.depositAddress =
|
await fetchParsedAddress(context, domain, ticker);
|
||||||
await fetchParsedAddress(context, domain, ticker);
|
},
|
||||||
},
|
));
|
||||||
));
|
|
||||||
|
|
||||||
final secondExchangeCard = Observer(builder: (_) => ExchangeCard(
|
final secondExchangeCard = Observer(
|
||||||
onDispose: disposeBestRateSync,
|
builder: (_) => ExchangeCard(
|
||||||
amountFocusNode: _receiveAmountFocus,
|
onDispose: disposeBestRateSync,
|
||||||
addressFocusNode: _receiveAddressFocus,
|
amountFocusNode: _receiveAmountFocus,
|
||||||
key: receiveKey,
|
addressFocusNode: _receiveAddressFocus,
|
||||||
title: S.of(context).you_will_get,
|
key: receiveKey,
|
||||||
initialCurrency: exchangeViewModel.receiveCurrency,
|
title: S.of(context).you_will_get,
|
||||||
initialWalletName: receiveWalletName ?? '',
|
initialCurrency: exchangeViewModel.receiveCurrency,
|
||||||
initialAddress:
|
initialWalletName: receiveWalletName ?? '',
|
||||||
exchangeViewModel.receiveCurrency == exchangeViewModel.wallet.currency
|
initialAddress: exchangeViewModel.receiveCurrency == exchangeViewModel.wallet.currency
|
||||||
? exchangeViewModel.wallet.walletAddresses.address
|
? exchangeViewModel.wallet.walletAddresses.address
|
||||||
: exchangeViewModel.receiveAddress,
|
: exchangeViewModel.receiveAddress,
|
||||||
initialIsAmountEditable: exchangeViewModel.isReceiveAmountEditable,
|
initialIsAmountEditable: exchangeViewModel.isReceiveAmountEditable,
|
||||||
initialIsAddressEditable: exchangeViewModel.isReceiveAddressEnabled,
|
initialIsAddressEditable: exchangeViewModel.isReceiveAddressEnabled,
|
||||||
isAmountEstimated: true,
|
isAmountEstimated: true,
|
||||||
isMoneroWallet: exchangeViewModel.isMoneroWallet,
|
isMoneroWallet: exchangeViewModel.isMoneroWallet,
|
||||||
currencies: exchangeViewModel.receiveCurrencies,
|
currencies: exchangeViewModel.receiveCurrencies,
|
||||||
onCurrencySelected: (currency) =>
|
onCurrencySelected: (currency) =>
|
||||||
exchangeViewModel.changeReceiveCurrency(currency: currency),
|
exchangeViewModel.changeReceiveCurrency(currency: currency),
|
||||||
imageArrow: arrowBottomCakeGreen,
|
imageArrow: arrowBottomCakeGreen,
|
||||||
currencyButtonColor: Colors.transparent,
|
currencyButtonColor: Colors.transparent,
|
||||||
addressButtonsColor: Theme.of(context).focusColor!,
|
addressButtonsColor: Theme.of(context).focusColor,
|
||||||
borderColor:
|
borderColor: Theme.of(context).primaryTextTheme.bodyLarge!.decorationColor!,
|
||||||
Theme.of(context).primaryTextTheme!.bodyLarge!.decorationColor!,
|
currencyValueValidator: (value) {
|
||||||
currencyValueValidator: (value) {
|
return exchangeViewModel.isFixedRateMode
|
||||||
return exchangeViewModel.isFixedRateMode
|
? AmountValidator(
|
||||||
? AmountValidator(
|
isAutovalidate: true,
|
||||||
isAutovalidate: true,
|
currency: exchangeViewModel.receiveCurrency,
|
||||||
currency: exchangeViewModel.receiveCurrency,
|
minValue: exchangeViewModel.limits.min.toString(),
|
||||||
minValue: exchangeViewModel.limits.min.toString(),
|
maxValue: exchangeViewModel.limits.max.toString(),
|
||||||
maxValue: exchangeViewModel.limits.max.toString(),
|
).call(value)
|
||||||
).call(value)
|
: null;
|
||||||
: null;
|
},
|
||||||
},
|
addressTextFieldValidator: AddressValidator(type: exchangeViewModel.receiveCurrency),
|
||||||
addressTextFieldValidator:
|
onPushPasteButton: (context) async {
|
||||||
AddressValidator(type: exchangeViewModel.receiveCurrency),
|
final domain = exchangeViewModel.receiveAddress;
|
||||||
onPushPasteButton: (context) async {
|
final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase();
|
||||||
final domain = exchangeViewModel.receiveAddress;
|
exchangeViewModel.receiveAddress =
|
||||||
final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase();
|
await fetchParsedAddress(context, domain, ticker);
|
||||||
exchangeViewModel.receiveAddress =
|
},
|
||||||
await fetchParsedAddress(context, domain, ticker);
|
onPushAddressBookButton: (context) async {
|
||||||
},
|
final domain = exchangeViewModel.receiveAddress;
|
||||||
onPushAddressBookButton: (context) async {
|
final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase();
|
||||||
final domain = exchangeViewModel.receiveAddress;
|
exchangeViewModel.receiveAddress =
|
||||||
final ticker = exchangeViewModel.receiveCurrency.title.toLowerCase();
|
await fetchParsedAddress(context, domain, ticker);
|
||||||
exchangeViewModel.receiveAddress =
|
},
|
||||||
await fetchParsedAddress(context, domain, ticker);
|
));
|
||||||
},
|
|
||||||
));
|
|
||||||
|
|
||||||
if (ResponsiveLayoutUtil.instance.isMobile) {
|
if (ResponsiveLayoutUtil.instance.isMobile) {
|
||||||
return MobileExchangeCardsSection(
|
return MobileExchangeCardsSection(
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
|
||||||
class AccountTile extends StatelessWidget {
|
class AccountTile extends StatelessWidget {
|
||||||
AccountTile(
|
AccountTile(
|
||||||
|
@ -19,16 +21,17 @@ class AccountTile extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final color = isCurrent
|
final color = isCurrent
|
||||||
? Theme.of(context).textTheme!.titleSmall!.decorationColor!
|
? Theme.of(context).textTheme.titleSmall!.decorationColor!
|
||||||
: Theme.of(context).textTheme!.displayLarge!.decorationColor!;
|
: Theme.of(context).textTheme.displayLarge!.decorationColor!;
|
||||||
final textColor = isCurrent
|
final textColor = isCurrent
|
||||||
? Theme.of(context).textTheme!.titleSmall!.color!
|
? Theme.of(context).textTheme.titleSmall!.color!
|
||||||
: Theme.of(context).textTheme!.displayLarge!.color!;
|
: Theme.of(context).textTheme.displayLarge!.color!;
|
||||||
|
|
||||||
final Widget cell = GestureDetector(
|
final Widget cell = GestureDetector(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 77,
|
height: 77,
|
||||||
|
width: double.infinity,
|
||||||
padding: EdgeInsets.only(left: 24, right: 24),
|
padding: EdgeInsets.only(left: 24, right: 24),
|
||||||
color: color,
|
color: color,
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
|
@ -58,7 +61,7 @@ class AccountTile extends StatelessWidget {
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
fontFamily: 'Lato',
|
fontFamily: 'Lato',
|
||||||
color: Theme.of(context).textTheme!.headlineMedium!.color!,
|
color: Theme.of(context).textTheme.headlineMedium!.color!,
|
||||||
decoration: TextDecoration.none,
|
decoration: TextDecoration.none,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -67,18 +70,26 @@ class AccountTile extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
// FIX-ME: Splidable
|
|
||||||
return cell;
|
// return cell;
|
||||||
// return Slidable(
|
return Slidable(
|
||||||
// key: Key(accountName),
|
key: Key(accountName),
|
||||||
// child: cell,
|
child: cell,
|
||||||
// actionPane: SlidableDrawerActionPane(),
|
endActionPane: _actionPane(context)
|
||||||
// secondaryActions: <Widget>[
|
);
|
||||||
// IconSlideAction(
|
|
||||||
// caption: S.of(context).edit,
|
|
||||||
// color: Colors.blue,
|
|
||||||
// icon: Icons.edit,
|
|
||||||
// onTap: () => onEdit?.call())
|
|
||||||
// ]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,19 @@ class NodeCreateOrEditPage extends BasePage {
|
||||||
@override
|
@override
|
||||||
String get title => editingNode != null ? S.current.edit_node : S.current.node_new;
|
String get title => editingNode != null ? S.current.edit_node : S.current.node_new;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget trailing(BuildContext context) => IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await nodeCreateOrEditViewModel.scanQRCodeForNewNode();
|
||||||
|
},
|
||||||
|
splashColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.transparent,
|
||||||
|
hoverColor: Colors.transparent,
|
||||||
|
icon: Image.asset(
|
||||||
|
'assets/images/qr_code_icon.png',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final NodeCreateOrEditViewModel nodeCreateOrEditViewModel;
|
final NodeCreateOrEditViewModel nodeCreateOrEditViewModel;
|
||||||
final Node? editingNode;
|
final Node? editingNode;
|
||||||
final bool? isSelected;
|
final bool? isSelected;
|
||||||
|
|
|
@ -44,6 +44,17 @@ class NodeForm extends StatelessWidget {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
reaction((_) => nodeViewModel.address, (String address) {
|
||||||
|
if (address != _addressController.text) {
|
||||||
|
_addressController.text = address;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
reaction((_) => nodeViewModel.port, (String port) {
|
||||||
|
if (port != _portController.text) {
|
||||||
|
_portController.text = port;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
_addressController.addListener(() => nodeViewModel.address = _addressController.text);
|
_addressController.addListener(() => nodeViewModel.address = _addressController.text);
|
||||||
_portController.addListener(() => nodeViewModel.port = _portController.text);
|
_portController.addListener(() => nodeViewModel.port = _portController.text);
|
||||||
|
|
|
@ -97,7 +97,8 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_isInactive && widget.authenticationStore.state == AuthenticationState.allowed) {
|
if (!_isInactive &&
|
||||||
|
widget.authenticationStore.state == AuthenticationState.allowed) {
|
||||||
setState(() => _setInactive(true));
|
setState(() => _setInactive(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,13 +125,16 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
final useTotp = widget.appStore.settingsStore.useTOTP2FA;
|
final useTotp = widget.appStore.settingsStore.useTOTP2FA;
|
||||||
if (useTotp) {
|
final shouldUseTotp2FAToAccessWallets = widget.appStore
|
||||||
|
.settingsStore.shouldRequireTOTP2FAForAccessingWallet;
|
||||||
|
if (useTotp && shouldUseTotp2FAToAccessWallets) {
|
||||||
_reset();
|
_reset();
|
||||||
auth.close(
|
auth.close(
|
||||||
route: Routes.totpAuthCodePage,
|
route: Routes.totpAuthCodePage,
|
||||||
arguments: TotpAuthArgumentsModel(
|
arguments: TotpAuthArgumentsModel(
|
||||||
onTotpAuthenticationFinished:
|
onTotpAuthenticationFinished:
|
||||||
(bool isAuthenticatedSuccessfully, TotpAuthCodePageState totpAuth) {
|
(bool isAuthenticatedSuccessfully,
|
||||||
|
TotpAuthCodePageState totpAuth) {
|
||||||
if (!isAuthenticatedSuccessfully) {
|
if (!isAuthenticatedSuccessfully) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -151,15 +155,11 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
||||||
route: launchUri != null ? Routes.send : null,
|
route: launchUri != null ? Routes.send : null,
|
||||||
arguments: PaymentRequest.fromUri(launchUri),
|
arguments: PaymentRequest.fromUri(launchUri),
|
||||||
);
|
);
|
||||||
launchUri = null;
|
launchUri = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
} else if (launchUri != null) {
|
} else if (launchUri != null) {
|
||||||
widget.navigatorKey.currentState?.pushNamed(
|
widget.navigatorKey.currentState?.pushNamed(
|
||||||
|
|
|
@ -11,9 +11,7 @@ class PreSeedPage extends BasePage {
|
||||||
PreSeedPage(this.type)
|
PreSeedPage(this.type)
|
||||||
: imageLight = Image.asset('assets/images/pre_seed_light.png'),
|
: imageLight = Image.asset('assets/images/pre_seed_light.png'),
|
||||||
imageDark = Image.asset('assets/images/pre_seed_dark.png'),
|
imageDark = Image.asset('assets/images/pre_seed_dark.png'),
|
||||||
wordsCount = type == WalletType.monero
|
wordsCount = _wordsCount(type);
|
||||||
? 25
|
|
||||||
: 24; // FIXME: Stupid fast implementation
|
|
||||||
|
|
||||||
final Image imageDark;
|
final Image imageDark;
|
||||||
final Image imageLight;
|
final Image imageLight;
|
||||||
|
@ -68,4 +66,15 @@ class PreSeedPage extends BasePage {
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int _wordsCount(WalletType type) {
|
||||||
|
switch (type) {
|
||||||
|
case WalletType.monero:
|
||||||
|
return 25;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return 12;
|
||||||
|
default:
|
||||||
|
return 24;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/core/auth_service.dart';
|
||||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||||
import 'package:cake_wallet/entities/template.dart';
|
import 'package:cake_wallet/entities/template.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
|
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
|
||||||
|
@ -32,10 +33,12 @@ import 'package:cw_core/crypto_currency.dart';
|
||||||
class SendPage extends BasePage {
|
class SendPage extends BasePage {
|
||||||
SendPage({
|
SendPage({
|
||||||
required this.sendViewModel,
|
required this.sendViewModel,
|
||||||
|
required this.authService,
|
||||||
this.initialPaymentRequest,
|
this.initialPaymentRequest,
|
||||||
}) : _formKey = GlobalKey<FormState>();
|
}) : _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
final SendViewModel sendViewModel;
|
final SendViewModel sendViewModel;
|
||||||
|
final AuthService authService;
|
||||||
final GlobalKey<FormState> _formKey;
|
final GlobalKey<FormState> _formKey;
|
||||||
final controller = PageController(initialPage: 0);
|
final controller = PageController(initialPage: 0);
|
||||||
final PaymentRequest? initialPaymentRequest;
|
final PaymentRequest? initialPaymentRequest;
|
||||||
|
@ -56,12 +59,14 @@ class SendPage extends BasePage {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? leading(BuildContext context) {
|
Widget? leading(BuildContext context) {
|
||||||
final _backButton = Icon(Icons.arrow_back_ios,
|
final _backButton = Icon(
|
||||||
|
Icons.arrow_back_ios,
|
||||||
color: titleColor,
|
color: titleColor,
|
||||||
size: 16,
|
size: 16,
|
||||||
);
|
);
|
||||||
final _closeButton = currentTheme.type == ThemeType.dark
|
final _closeButton = currentTheme.type == ThemeType.dark
|
||||||
? closeButtonImageDarkTheme : closeButtonImage;
|
? closeButtonImageDarkTheme
|
||||||
|
: closeButtonImage;
|
||||||
|
|
||||||
bool isMobileView = ResponsiveLayoutUtil.instance.isMobile;
|
bool isMobileView = ResponsiveLayoutUtil.instance.isMobile;
|
||||||
|
|
||||||
|
@ -78,7 +83,7 @@ class SendPage extends BasePage {
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
overlayColor: MaterialStateColor.resolveWith(
|
overlayColor: MaterialStateColor.resolveWith(
|
||||||
(states) => Colors.transparent),
|
(states) => Colors.transparent),
|
||||||
),
|
),
|
||||||
onPressed: () => onClose(context),
|
onPressed: () => onClose(context),
|
||||||
child: !isMobileView ? _closeButton : _backButton,
|
child: !isMobileView ? _closeButton : _backButton,
|
||||||
|
@ -114,11 +119,13 @@ class SendPage extends BasePage {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right:8.0),
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
child: Observer(builder: (_) => SyncIndicatorIcon(isSynced: sendViewModel.isReadyForSend),),
|
child: Observer(
|
||||||
|
builder: (_) =>
|
||||||
|
SyncIndicatorIcon(isSynced: sendViewModel.isReadyForSend),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
if (supMiddle != null)
|
if (supMiddle != null) supMiddle
|
||||||
supMiddle
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -200,12 +207,12 @@ class SendPage extends BasePage {
|
||||||
dotWidth: 6.0,
|
dotWidth: 6.0,
|
||||||
dotHeight: 6.0,
|
dotHeight: 6.0,
|
||||||
dotColor: Theme.of(context)
|
dotColor: Theme.of(context)
|
||||||
.primaryTextTheme
|
.primaryTextTheme!
|
||||||
!.displaySmall!
|
.displaySmall!
|
||||||
.backgroundColor!,
|
.backgroundColor!,
|
||||||
activeDotColor: Theme.of(context)
|
activeDotColor: Theme.of(context)
|
||||||
.primaryTextTheme
|
.primaryTextTheme!
|
||||||
!.displayMedium!
|
.displayMedium!
|
||||||
.backgroundColor!),
|
.backgroundColor!),
|
||||||
)
|
)
|
||||||
: Offstage();
|
: Offstage();
|
||||||
|
@ -213,115 +220,108 @@ class SendPage extends BasePage {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (sendViewModel.hasMultiRecipient)
|
Container(
|
||||||
Container(
|
height: 40,
|
||||||
height: 40,
|
width: double.infinity,
|
||||||
width: double.infinity,
|
padding: EdgeInsets.only(left: 24),
|
||||||
padding: EdgeInsets.only(left: 24),
|
child: SingleChildScrollView(
|
||||||
child: SingleChildScrollView(
|
scrollDirection: Axis.horizontal,
|
||||||
scrollDirection: Axis.horizontal,
|
child: Observer(
|
||||||
child: Observer(
|
builder: (_) {
|
||||||
builder: (_) {
|
final templates = sendViewModel.templates;
|
||||||
final templates = sendViewModel.templates;
|
final itemCount = templates.length;
|
||||||
final itemCount = templates.length;
|
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
AddTemplateButton(
|
AddTemplateButton(
|
||||||
onTap: () => Navigator.of(context)
|
onTap: () => Navigator.of(context)
|
||||||
.pushNamed(Routes.sendTemplate),
|
.pushNamed(Routes.sendTemplate),
|
||||||
currentTemplatesLength: templates.length,
|
currentTemplatesLength: templates.length,
|
||||||
),
|
),
|
||||||
ListView.builder(
|
ListView.builder(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: NeverScrollableScrollPhysics(),
|
physics: NeverScrollableScrollPhysics(),
|
||||||
itemCount: itemCount,
|
itemCount: itemCount,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final template = templates[index];
|
final template = templates[index];
|
||||||
return TemplateTile(
|
return TemplateTile(
|
||||||
key: UniqueKey(),
|
key: UniqueKey(),
|
||||||
to: template.name,
|
to: template.name,
|
||||||
hasMultipleRecipients:
|
hasMultipleRecipients:
|
||||||
template.additionalRecipients !=
|
template.additionalRecipients != null &&
|
||||||
null &&
|
template.additionalRecipients!.length > 1,
|
||||||
template.additionalRecipients!
|
amount: template.isCurrencySelected
|
||||||
.length > 1,
|
? template.amount
|
||||||
amount: template.isCurrencySelected
|
: template.amountFiat,
|
||||||
? template.amount
|
from: template.isCurrencySelected
|
||||||
: template.amountFiat,
|
? template.cryptoCurrency
|
||||||
from: template.isCurrencySelected
|
: template.fiatCurrency,
|
||||||
? template.cryptoCurrency
|
onTap: () async {
|
||||||
: template.fiatCurrency,
|
if (template.additionalRecipients?.isNotEmpty ?? false) {
|
||||||
onTap: () async {
|
sendViewModel.clearOutputs();
|
||||||
if (template.additionalRecipients !=
|
|
||||||
null) {
|
|
||||||
sendViewModel.clearOutputs();
|
|
||||||
|
|
||||||
template.additionalRecipients!
|
for (int i = 0;i < template.additionalRecipients!.length;i++) {
|
||||||
.forEach((currentElement) async {
|
Output output;
|
||||||
int i = template
|
try {
|
||||||
.additionalRecipients!
|
output = sendViewModel.outputs[i];
|
||||||
.indexOf(currentElement);
|
} catch (e) {
|
||||||
|
sendViewModel.addOutput();
|
||||||
|
output = sendViewModel.outputs[i];
|
||||||
|
}
|
||||||
|
|
||||||
Output output;
|
await _setInputsFromTemplate(
|
||||||
try {
|
context,
|
||||||
output = sendViewModel.outputs[i];
|
output: output,
|
||||||
} catch (e) {
|
template: template.additionalRecipients![i],
|
||||||
sendViewModel.addOutput();
|
);
|
||||||
output = sendViewModel.outputs[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
await _setInputsFromTemplate(
|
|
||||||
context,
|
|
||||||
output: output,
|
|
||||||
template: currentElement);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
final output = _defineCurrentOutput();
|
|
||||||
await _setInputsFromTemplate(
|
|
||||||
context,
|
|
||||||
output: output,
|
|
||||||
template: template);
|
|
||||||
}
|
}
|
||||||
},
|
} else {
|
||||||
onRemove: () {
|
final output = _defineCurrentOutput();
|
||||||
showPopUp<void>(
|
await _setInputsFromTemplate(
|
||||||
context: context,
|
context,
|
||||||
builder: (dialogContext) {
|
output: output,
|
||||||
return AlertWithTwoActions(
|
template: template,
|
||||||
alertTitle:
|
|
||||||
S.of(context).template,
|
|
||||||
alertContent: S
|
|
||||||
.of(context)
|
|
||||||
.confirm_delete_template,
|
|
||||||
rightButtonText:
|
|
||||||
S.of(context).delete,
|
|
||||||
leftButtonText:
|
|
||||||
S.of(context).cancel,
|
|
||||||
actionRightButton: () {
|
|
||||||
Navigator.of(dialogContext)
|
|
||||||
.pop();
|
|
||||||
sendViewModel
|
|
||||||
.sendTemplateViewModel
|
|
||||||
.removeTemplate(
|
|
||||||
template: template);
|
|
||||||
},
|
|
||||||
actionLeftButton: () =>
|
|
||||||
Navigator.of(dialogContext)
|
|
||||||
.pop());
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
);
|
},
|
||||||
},
|
onRemove: () {
|
||||||
),
|
showPopUp<void>(
|
||||||
],
|
context: context,
|
||||||
);
|
builder: (dialogContext) {
|
||||||
},
|
return AlertWithTwoActions(
|
||||||
),
|
alertTitle:
|
||||||
|
S.of(context).template,
|
||||||
|
alertContent: S
|
||||||
|
.of(context)
|
||||||
|
.confirm_delete_template,
|
||||||
|
rightButtonText:
|
||||||
|
S.of(context).delete,
|
||||||
|
leftButtonText:
|
||||||
|
S.of(context).cancel,
|
||||||
|
actionRightButton: () {
|
||||||
|
Navigator.of(dialogContext)
|
||||||
|
.pop();
|
||||||
|
sendViewModel
|
||||||
|
.sendTemplateViewModel
|
||||||
|
.removeTemplate(
|
||||||
|
template: template);
|
||||||
|
},
|
||||||
|
actionLeftButton: () =>
|
||||||
|
Navigator.of(dialogContext)
|
||||||
|
.pop());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -339,11 +339,11 @@ class SendPage extends BasePage {
|
||||||
'Change your asset (${sendViewModel.selectedCryptoCurrency})',
|
'Change your asset (${sendViewModel.selectedCryptoCurrency})',
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
textColor: Theme.of(context)
|
textColor: Theme.of(context)
|
||||||
.accentTextTheme
|
.accentTextTheme!
|
||||||
!.displaySmall!
|
.displaySmall!
|
||||||
.decorationColor!,
|
.decorationColor!,
|
||||||
))),
|
))),
|
||||||
if (sendViewModel.hasMultiRecipient)
|
if (sendViewModel.sendTemplateViewModel.hasMultiRecipient)
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(bottom: 12),
|
padding: EdgeInsets.only(bottom: 12),
|
||||||
child: PrimaryButton(
|
child: PrimaryButton(
|
||||||
|
@ -357,13 +357,13 @@ class SendPage extends BasePage {
|
||||||
text: S.of(context).add_receiver,
|
text: S.of(context).add_receiver,
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
textColor: Theme.of(context)
|
textColor: Theme.of(context)
|
||||||
.accentTextTheme
|
.accentTextTheme!
|
||||||
!.displaySmall!
|
.displaySmall!
|
||||||
.decorationColor!,
|
.decorationColor!,
|
||||||
isDottedBorder: true,
|
isDottedBorder: true,
|
||||||
borderColor: Theme.of(context)
|
borderColor: Theme.of(context)
|
||||||
.primaryTextTheme
|
.primaryTextTheme!
|
||||||
!.displaySmall!
|
.displaySmall!
|
||||||
.decorationColor!,
|
.decorationColor!,
|
||||||
)),
|
)),
|
||||||
Observer(
|
Observer(
|
||||||
|
@ -390,7 +390,16 @@ class SendPage extends BasePage {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await sendViewModel.createTransaction();
|
final check = sendViewModel.shouldDisplayTotp();
|
||||||
|
authService.authenticateAction(
|
||||||
|
context,
|
||||||
|
conditionToDetermineIfToUse2FA: check,
|
||||||
|
onAuthSuccess: (value) async {
|
||||||
|
if (value) {
|
||||||
|
await sendViewModel.createTransaction();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
text: S.of(context).send,
|
text: S.of(context).send,
|
||||||
color:
|
color:
|
||||||
|
@ -502,6 +511,7 @@ class SendPage extends BasePage {
|
||||||
output.address = template.address;
|
output.address = template.address;
|
||||||
|
|
||||||
if (template.isCurrencySelected) {
|
if (template.isCurrencySelected) {
|
||||||
|
sendViewModel.setSelectedCryptoCurrency(template.cryptoCurrency);
|
||||||
output.setCryptoAmount(template.amount);
|
output.setCryptoAmount(template.amount);
|
||||||
} else {
|
} else {
|
||||||
sendViewModel.setFiatCurrency(fiatFromTemplate);
|
sendViewModel.setFiatCurrency(fiatFromTemplate);
|
||||||
|
|
|
@ -67,8 +67,7 @@ class SendTemplatePage extends BasePage {
|
||||||
controller: controller,
|
controller: controller,
|
||||||
itemCount: sendTemplateViewModel.recipients.length,
|
itemCount: sendTemplateViewModel.recipients.length,
|
||||||
itemBuilder: (_, index) {
|
itemBuilder: (_, index) {
|
||||||
final template =
|
final template = sendTemplateViewModel.recipients[index];
|
||||||
sendTemplateViewModel.recipients[index];
|
|
||||||
return SendTemplateCard(
|
return SendTemplateCard(
|
||||||
template: template,
|
template: template,
|
||||||
index: index,
|
index: index,
|
||||||
|
@ -76,8 +75,7 @@ class SendTemplatePage extends BasePage {
|
||||||
});
|
});
|
||||||
})),
|
})),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(top: 10, left: 24, right: 24, bottom: 10),
|
||||||
top: 10, left: 24, right: 24, bottom: 10),
|
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 10,
|
height: 10,
|
||||||
child: Observer(
|
child: Observer(
|
||||||
|
@ -107,55 +105,42 @@ class SendTemplatePage extends BasePage {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
])),
|
])),
|
||||||
bottomSectionPadding:
|
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||||
EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
|
||||||
bottomSection: Column(children: [
|
bottomSection: Column(children: [
|
||||||
// if (sendViewModel.hasMultiRecipient)
|
if (sendTemplateViewModel.hasMultiRecipient)
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(bottom: 12),
|
padding: EdgeInsets.only(bottom: 12),
|
||||||
child: PrimaryButton(
|
child: PrimaryButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
sendTemplateViewModel.addRecipient();
|
sendTemplateViewModel.addRecipient();
|
||||||
Future.delayed(const Duration(milliseconds: 250), () {
|
Future.delayed(const Duration(milliseconds: 250), () {
|
||||||
controller.jumpToPage(
|
controller.jumpToPage(sendTemplateViewModel.recipients.length - 1);
|
||||||
sendTemplateViewModel.recipients.length - 1);
|
});
|
||||||
});
|
},
|
||||||
},
|
text: S.of(context).add_receiver,
|
||||||
text: S.of(context).add_receiver,
|
color: Colors.transparent,
|
||||||
color: Colors.transparent,
|
textColor: Theme.of(context).accentTextTheme.displaySmall!.decorationColor!,
|
||||||
textColor: Theme.of(context)
|
isDottedBorder: true,
|
||||||
.accentTextTheme
|
borderColor:
|
||||||
.displaySmall!
|
Theme.of(context).primaryTextTheme.displaySmall!.decorationColor!)),
|
||||||
.decorationColor!,
|
|
||||||
isDottedBorder: true,
|
|
||||||
borderColor: Theme.of(context)
|
|
||||||
.primaryTextTheme
|
|
||||||
.displaySmall!
|
|
||||||
.decorationColor!)),
|
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_formKey.currentState != null &&
|
if (_formKey.currentState != null && _formKey.currentState!.validate()) {
|
||||||
_formKey.currentState!.validate()) {
|
|
||||||
final mainTemplate = sendTemplateViewModel.recipients[0];
|
final mainTemplate = sendTemplateViewModel.recipients[0];
|
||||||
print(sendTemplateViewModel.recipients.map((element) =>
|
final additionalRecipients = sendTemplateViewModel.recipients
|
||||||
element.toTemplate(
|
.map((element) => element.toTemplate(
|
||||||
cryptoCurrency:
|
cryptoCurrency: element.selectedCurrency.title,
|
||||||
sendTemplateViewModel.cryptoCurrency.title,
|
fiatCurrency: sendTemplateViewModel.fiatCurrency))
|
||||||
fiatCurrency:
|
.toList();
|
||||||
sendTemplateViewModel.fiatCurrency)));
|
|
||||||
sendTemplateViewModel.addTemplate(
|
sendTemplateViewModel.addTemplate(
|
||||||
isCurrencySelected: mainTemplate.isCurrencySelected,
|
isCurrencySelected: mainTemplate.isCurrencySelected,
|
||||||
name: mainTemplate.name,
|
name: mainTemplate.name,
|
||||||
address: mainTemplate.address,
|
address: mainTemplate.address,
|
||||||
|
cryptoCurrency: mainTemplate.selectedCurrency.title,
|
||||||
amount: mainTemplate.output.cryptoAmount,
|
amount: mainTemplate.output.cryptoAmount,
|
||||||
amountFiat: mainTemplate.output.fiatAmount,
|
amountFiat: mainTemplate.output.fiatAmount,
|
||||||
additionalRecipients: sendTemplateViewModel.recipients
|
additionalRecipients: additionalRecipients);
|
||||||
.map((element) => element.toTemplate(
|
|
||||||
cryptoCurrency: sendTemplateViewModel
|
|
||||||
.cryptoCurrency.title,
|
|
||||||
fiatCurrency:
|
|
||||||
sendTemplateViewModel.fiatCurrency))
|
|
||||||
.toList());
|
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,29 +4,53 @@ class PrefixCurrencyIcon extends StatelessWidget {
|
||||||
PrefixCurrencyIcon({
|
PrefixCurrencyIcon({
|
||||||
required this.isSelected,
|
required this.isSelected,
|
||||||
required this.title,
|
required this.title,
|
||||||
|
this.onTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
final bool isSelected;
|
final bool isSelected;
|
||||||
final String title;
|
final String title;
|
||||||
|
final Function()? onTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Padding(
|
||||||
padding: EdgeInsets.fromLTRB(0, 6.0, 8.0, 0),
|
padding: EdgeInsets.fromLTRB(0, 6.0, 8.0, 0),
|
||||||
child: Column(children: [
|
child: Column(
|
||||||
Container(
|
children: [
|
||||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
Container(
|
||||||
decoration: BoxDecoration(
|
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
||||||
borderRadius: BorderRadius.circular(26),
|
decoration: BoxDecoration(
|
||||||
color: isSelected ? Colors.green : Colors.transparent,
|
borderRadius: BorderRadius.circular(26),
|
||||||
|
color: isSelected ? Colors.green : Colors.transparent,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
if (onTap != null)
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(right: 5),
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/arrow_bottom_purple_icon.png',
|
||||||
|
color: Colors.white,
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
title + ':',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Text(title + ':',
|
],
|
||||||
style: TextStyle(
|
),
|
||||||
fontSize: 16,
|
),
|
||||||
fontWeight: FontWeight.w600,
|
);
|
||||||
color: Colors.white,
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
]));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,10 +172,8 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Theme.of(context)
|
color:
|
||||||
.primaryTextTheme!
|
Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!),
|
||||||
.headlineSmall!
|
|
||||||
.decorationColor!),
|
|
||||||
onPushPasteButton: (context) async {
|
onPushPasteButton: (context) async {
|
||||||
output.resetParsedAddress();
|
output.resetParsedAddress();
|
||||||
await output.fetchParsedAddress(context);
|
await output.fetchParsedAddress(context);
|
||||||
|
@ -469,6 +467,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
|
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
|
||||||
color: Colors.white),
|
color: Colors.white),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
|
||||||
import 'package:cake_wallet/src/screens/send/widgets/prefix_currency_icon_widget.dart';
|
import 'package:cake_wallet/src/screens/send/widgets/prefix_currency_icon_widget.dart';
|
||||||
import 'package:cake_wallet/utils/payment_request.dart';
|
import 'package:cake_wallet/utils/payment_request.dart';
|
||||||
|
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||||
import 'package:cake_wallet/view_model/send/template_view_model.dart';
|
import 'package:cake_wallet/view_model/send/template_view_model.dart';
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/currency.dart';
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
@ -35,161 +39,140 @@ class SendTemplateCard extends StatelessWidget {
|
||||||
_setEffects(context);
|
_setEffects(context);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius:
|
||||||
bottomLeft: Radius.circular(24),
|
BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
|
||||||
bottomRight: Radius.circular(24)),
|
gradient: LinearGradient(colors: [
|
||||||
gradient: LinearGradient(colors: [
|
Theme.of(context).primaryTextTheme.titleMedium!.color!,
|
||||||
Theme.of(context).primaryTextTheme.titleMedium!.color!,
|
Theme.of(context).primaryTextTheme.titleMedium!.decorationColor!
|
||||||
Theme.of(context).primaryTextTheme.titleMedium!.decorationColor!
|
], begin: Alignment.topLeft, end: Alignment.bottomRight)),
|
||||||
], begin: Alignment.topLeft, end: Alignment.bottomRight)),
|
child: Column(
|
||||||
child: Column(children: <Widget>[
|
children: <Widget>[
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.fromLTRB(24, 90, 24, 32),
|
padding: EdgeInsets.fromLTRB(24, 90, 24, 32),
|
||||||
child: Column(children: <Widget>[
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
if (index == 0)
|
if (index == 0)
|
||||||
BaseTextFormField(
|
BaseTextFormField(
|
||||||
controller: _nameController,
|
controller: _nameController,
|
||||||
hintText: sendTemplateViewModel.recipients.length > 1
|
hintText: sendTemplateViewModel.recipients.length > 1
|
||||||
? S.of(context).template_name
|
? S.of(context).template_name
|
||||||
: S.of(context).send_name,
|
: S.of(context).send_name,
|
||||||
borderColor: Theme.of(context)
|
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||||
.primaryTextTheme
|
textStyle:
|
||||||
.headlineSmall!
|
TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||||
.color!,
|
|
||||||
textStyle: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: Colors.white),
|
|
||||||
placeholderTextStyle: TextStyle(
|
placeholderTextStyle: TextStyle(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!,
|
||||||
.primaryTextTheme
|
|
||||||
.headlineSmall!
|
|
||||||
.decorationColor!,
|
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 14),
|
fontSize: 14),
|
||||||
validator: sendTemplateViewModel.templateValidator),
|
validator: sendTemplateViewModel.templateValidator),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 20),
|
padding: EdgeInsets.only(top: 20),
|
||||||
child: AddressTextField(
|
child: AddressTextField(
|
||||||
selectedCurrency: sendTemplateViewModel.cryptoCurrency,
|
selectedCurrency: sendTemplateViewModel.cryptoCurrency,
|
||||||
controller: _addressController,
|
controller: _addressController,
|
||||||
onURIScanned: (uri) {
|
onURIScanned: (uri) {
|
||||||
final paymentRequest = PaymentRequest.fromUri(uri);
|
final paymentRequest = PaymentRequest.fromUri(uri);
|
||||||
_addressController.text = paymentRequest.address;
|
_addressController.text = paymentRequest.address;
|
||||||
_cryptoAmountController.text = paymentRequest.amount;
|
_cryptoAmountController.text = paymentRequest.amount;
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
AddressTextFieldOption.paste,
|
AddressTextFieldOption.paste,
|
||||||
AddressTextFieldOption.qrCode,
|
AddressTextFieldOption.qrCode,
|
||||||
AddressTextFieldOption.addressBook
|
AddressTextFieldOption.addressBook
|
||||||
],
|
],
|
||||||
onPushPasteButton: (context) async {
|
onPushPasteButton: (context) async {
|
||||||
template.output.resetParsedAddress();
|
template.output.resetParsedAddress();
|
||||||
await template.output.fetchParsedAddress(context);
|
await template.output.fetchParsedAddress(context);
|
||||||
},
|
},
|
||||||
onPushAddressBookButton: (context) async {
|
onPushAddressBookButton: (context) async {
|
||||||
template.output.resetParsedAddress();
|
template.output.resetParsedAddress();
|
||||||
await template.output.fetchParsedAddress(context);
|
await template.output.fetchParsedAddress(context);
|
||||||
},
|
},
|
||||||
buttonColor: Theme.of(context)
|
buttonColor: Theme.of(context).primaryTextTheme.headlineMedium!.color!,
|
||||||
.primaryTextTheme
|
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||||
.headlineMedium!
|
textStyle: TextStyle(
|
||||||
.color!,
|
fontSize: 14,
|
||||||
borderColor: Theme.of(context)
|
fontWeight: FontWeight.w500,
|
||||||
.primaryTextTheme
|
color: Colors.white,
|
||||||
.headlineSmall!
|
),
|
||||||
.color!,
|
hintStyle: TextStyle(
|
||||||
textStyle: TextStyle(
|
fontSize: 14,
|
||||||
fontSize: 14,
|
fontWeight: FontWeight.w500,
|
||||||
fontWeight: FontWeight.w500,
|
color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!,
|
||||||
color: Colors.white),
|
),
|
||||||
hintStyle: TextStyle(
|
validator: sendTemplateViewModel.addressValidator,
|
||||||
fontSize: 14,
|
),
|
||||||
fontWeight: FontWeight.w500,
|
),
|
||||||
color: Theme.of(context)
|
|
||||||
.primaryTextTheme
|
|
||||||
.headlineSmall!
|
|
||||||
.decorationColor!),
|
|
||||||
validator: sendTemplateViewModel.addressValidator)),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 20),
|
padding: const EdgeInsets.only(top: 20),
|
||||||
child: Focus(
|
child: Focus(
|
||||||
onFocusChange: (hasFocus) {
|
onFocusChange: (hasFocus) {
|
||||||
if (hasFocus) {
|
if (hasFocus) {
|
||||||
template.selectCurrency();
|
template.selectCurrency();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: BaseTextFormField(
|
child: BaseTextFormField(
|
||||||
focusNode: _cryptoAmountFocus,
|
focusNode: _cryptoAmountFocus,
|
||||||
controller: _cryptoAmountController,
|
controller: _cryptoAmountController,
|
||||||
keyboardType: TextInputType.numberWithOptions(
|
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||||
signed: false, decimal: true),
|
inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))],
|
||||||
inputFormatters: [
|
prefixIcon: Observer(
|
||||||
FilteringTextInputFormatter.deny(
|
builder: (_) => PrefixCurrencyIcon(
|
||||||
RegExp('[\\-|\\ ]'))
|
title: template.selectedCurrency.title,
|
||||||
],
|
isSelected: template.isCurrencySelected,
|
||||||
prefixIcon: Observer(
|
onTap: sendTemplateViewModel.walletCurrencies.length > 1
|
||||||
builder: (_) => PrefixCurrencyIcon(
|
? () => _presentPicker(context)
|
||||||
title: sendTemplateViewModel
|
: null,
|
||||||
.cryptoCurrency.title,
|
),
|
||||||
isSelected: template.isCurrencySelected)),
|
),
|
||||||
hintText: '0.0000',
|
hintText: '0.0000',
|
||||||
borderColor: Theme.of(context)
|
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||||
.primaryTextTheme
|
textStyle:
|
||||||
.headlineSmall!
|
TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||||
.color!,
|
placeholderTextStyle: TextStyle(
|
||||||
textStyle: TextStyle(
|
color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!,
|
||||||
fontSize: 14,
|
fontWeight: FontWeight.w500,
|
||||||
fontWeight: FontWeight.w500,
|
fontSize: 14),
|
||||||
color: Colors.white),
|
validator: sendTemplateViewModel.amountValidator,
|
||||||
placeholderTextStyle: TextStyle(
|
),
|
||||||
color: Theme.of(context)
|
),
|
||||||
.primaryTextTheme
|
),
|
||||||
.headlineSmall!
|
|
||||||
.decorationColor!,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
fontSize: 14),
|
|
||||||
validator: sendTemplateViewModel.amountValidator))),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 20),
|
padding: const EdgeInsets.only(top: 20),
|
||||||
child: Focus(
|
child: Focus(
|
||||||
onFocusChange: (hasFocus) {
|
onFocusChange: (hasFocus) {
|
||||||
if (hasFocus) {
|
if (hasFocus) {
|
||||||
template.selectFiat();
|
template.selectFiat();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: BaseTextFormField(
|
child: BaseTextFormField(
|
||||||
focusNode: _fiatAmountFocus,
|
focusNode: _fiatAmountFocus,
|
||||||
controller: _fiatAmountController,
|
controller: _fiatAmountController,
|
||||||
keyboardType: TextInputType.numberWithOptions(
|
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||||
signed: false, decimal: true),
|
inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))],
|
||||||
inputFormatters: [
|
prefixIcon: Observer(
|
||||||
FilteringTextInputFormatter.deny(
|
builder: (_) => PrefixCurrencyIcon(
|
||||||
RegExp('[\\-|\\ ]'))
|
title: sendTemplateViewModel.fiatCurrency,
|
||||||
],
|
isSelected: template.isFiatSelected)),
|
||||||
prefixIcon: Observer(
|
hintText: '0.00',
|
||||||
builder: (_) => PrefixCurrencyIcon(
|
borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
|
||||||
title: sendTemplateViewModel.fiatCurrency,
|
textStyle:
|
||||||
isSelected: template.isFiatSelected)),
|
TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||||
hintText: '0.00',
|
placeholderTextStyle: TextStyle(
|
||||||
borderColor: Theme.of(context)
|
color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!,
|
||||||
.primaryTextTheme
|
fontWeight: FontWeight.w500,
|
||||||
.headlineSmall!
|
fontSize: 14,
|
||||||
.color!,
|
),
|
||||||
textStyle: TextStyle(
|
),
|
||||||
fontSize: 14,
|
),
|
||||||
fontWeight: FontWeight.w500,
|
),
|
||||||
color: Colors.white),
|
],
|
||||||
placeholderTextStyle: TextStyle(
|
),
|
||||||
color: Theme.of(context)
|
)
|
||||||
.primaryTextTheme
|
],
|
||||||
.headlineSmall!
|
),
|
||||||
.decorationColor!,
|
);
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
fontSize: 14))))
|
|
||||||
]))
|
|
||||||
]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setEffects(BuildContext context) {
|
void _setEffects(BuildContext context) {
|
||||||
|
@ -264,4 +247,16 @@ class SendTemplateCard extends StatelessWidget {
|
||||||
|
|
||||||
_effectsInstalled = true;
|
_effectsInstalled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _presentPicker(BuildContext context) {
|
||||||
|
showPopUp<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => CurrencyPicker(
|
||||||
|
selectedAtIndex: sendTemplateViewModel.walletCurrencies.indexOf(template.selectedCurrency),
|
||||||
|
items: sendTemplateViewModel.walletCurrencies,
|
||||||
|
hintText: S.of(context).search_currency,
|
||||||
|
onItemSelected: (Currency cur) => template.changeSelectedCurrency(cur as CryptoCurrency),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,8 @@ class PrivacyPage extends BasePage {
|
||||||
title: S.current.exchange,
|
title: S.current.exchange,
|
||||||
items: ExchangeApiMode.all,
|
items: ExchangeApiMode.all,
|
||||||
selectedItem: _privacySettingsViewModel.exchangeStatus,
|
selectedItem: _privacySettingsViewModel.exchangeStatus,
|
||||||
onItemSelected: (ExchangeApiMode mode) => _privacySettingsViewModel.setExchangeApiMode(mode),
|
onItemSelected: (ExchangeApiMode mode) =>
|
||||||
|
_privacySettingsViewModel.setExchangeApiMode(mode),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SettingsSwitcherCell(
|
SettingsSwitcherCell(
|
||||||
|
@ -68,6 +69,13 @@ class PrivacyPage extends BasePage {
|
||||||
onValueChange: (BuildContext _, bool value) {
|
onValueChange: (BuildContext _, bool value) {
|
||||||
_privacySettingsViewModel.setDisableSell(value);
|
_privacySettingsViewModel.setDisableSell(value);
|
||||||
}),
|
}),
|
||||||
|
if (_privacySettingsViewModel.canUseEtherscan)
|
||||||
|
SettingsSwitcherCell(
|
||||||
|
title: S.current.etherscan_history,
|
||||||
|
value: _privacySettingsViewModel.useEtherscan,
|
||||||
|
onValueChange: (BuildContext _, bool value) {
|
||||||
|
_privacySettingsViewModel.setUseEtherscan(value);
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -30,12 +30,22 @@ class SecurityBackupPage extends BasePage {
|
||||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
SettingsCellWithArrow(
|
SettingsCellWithArrow(
|
||||||
title: S.current.show_keys,
|
title: S.current.show_keys,
|
||||||
handler: (_) => _authService.authenticateAction(context, route: Routes.showKeys),
|
handler: (_) => _authService.authenticateAction(
|
||||||
|
context,
|
||||||
|
route: Routes.showKeys,
|
||||||
|
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
|
||||||
|
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||||
SettingsCellWithArrow(
|
SettingsCellWithArrow(
|
||||||
title: S.current.create_backup,
|
title: S.current.create_backup,
|
||||||
handler: (_) => _authService.authenticateAction(context, route: Routes.backup),
|
handler: (_) => _authService.authenticateAction(
|
||||||
|
context,
|
||||||
|
route: Routes.backup,
|
||||||
|
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
|
||||||
|
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||||
SettingsCellWithArrow(
|
SettingsCellWithArrow(
|
||||||
|
@ -46,6 +56,8 @@ class SecurityBackupPage extends BasePage {
|
||||||
arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
|
arguments: (PinCodeState<PinCodeWidget> setupPinContext, String _) {
|
||||||
setupPinContext.close();
|
setupPinContext.close();
|
||||||
},
|
},
|
||||||
|
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
|
||||||
|
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||||
|
@ -67,7 +79,10 @@ class SecurityBackupPage extends BasePage {
|
||||||
_securitySettingsViewModel
|
_securitySettingsViewModel
|
||||||
.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
|
.setAllowBiometricalAuthentication(isAuthenticatedSuccessfully);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
|
||||||
|
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
_securitySettingsViewModel.setAllowBiometricalAuthentication(value);
|
_securitySettingsViewModel.setAllowBiometricalAuthentication(value);
|
||||||
}
|
}
|
||||||
|
@ -94,6 +109,8 @@ class SecurityBackupPage extends BasePage {
|
||||||
route: _securitySettingsViewModel.useTotp2FA
|
route: _securitySettingsViewModel.useTotp2FA
|
||||||
? Routes.modify2FAPage
|
? Routes.modify2FAPage
|
||||||
: Routes.setup_2faPage,
|
: Routes.setup_2faPage,
|
||||||
|
conditionToDetermineIfToUse2FA: _securitySettingsViewModel
|
||||||
|
.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -22,7 +22,7 @@ class SettingsChoicesCell extends StatelessWidget {
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.normal,
|
fontWeight: FontWeight.normal,
|
||||||
color: Theme.of(context).primaryTextTheme!.titleLarge!.color!,
|
color: Theme.of(context).primaryTextTheme.titleLarge!.color!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -34,10 +34,7 @@ class SettingsChoicesCell extends StatelessWidget {
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(30),
|
borderRadius: BorderRadius.circular(30),
|
||||||
color: Theme.of(context)
|
color: Theme.of(context).accentTextTheme.displaySmall!.color!,
|
||||||
.accentTextTheme!
|
|
||||||
.displaySmall!
|
|
||||||
.color!,
|
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
@ -52,10 +49,7 @@ class SettingsChoicesCell extends StatelessWidget {
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(30),
|
borderRadius: BorderRadius.circular(30),
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? Theme.of(context)
|
? Theme.of(context).accentTextTheme.bodyLarge!.color!
|
||||||
.accentTextTheme!
|
|
||||||
.bodyLarge!
|
|
||||||
.color!
|
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
@ -63,10 +57,7 @@ class SettingsChoicesCell extends StatelessWidget {
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? Colors.white
|
? Colors.white
|
||||||
: Theme.of(context)
|
: Theme.of(context).primaryTextTheme.bodySmall!.color!,
|
||||||
.primaryTextTheme!
|
|
||||||
.bodySmall!
|
|
||||||
.color!,
|
|
||||||
fontWeight: isSelected ? FontWeight.w700 : FontWeight.normal,
|
fontWeight: isSelected ? FontWeight.w700 : FontWeight.normal,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
|
import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/settings/widgets/settings_choices_cell.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
|
||||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
|
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
|
||||||
import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
|
import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
|
||||||
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||||
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
|
|
||||||
import '../../../routes.dart';
|
import '../../../routes.dart';
|
||||||
|
|
||||||
|
@ -21,35 +25,148 @@ class Modify2FAPage extends BasePage {
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) {
|
Widget body(BuildContext context) {
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Column(
|
child: _2FAControlsWidget(setup2FAViewModel: setup2FAViewModel),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
);
|
||||||
children: [
|
}
|
||||||
SettingsCellWithArrow(
|
}
|
||||||
title: S.current.disable_cake_2fa,
|
|
||||||
handler: (_) async {
|
class _2FAControlsWidget extends StatelessWidget {
|
||||||
await showPopUp<void>(
|
const _2FAControlsWidget({required this.setup2FAViewModel});
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
final Setup2FAViewModel setup2FAViewModel;
|
||||||
return AlertWithTwoActions(
|
|
||||||
alertTitle: S.current.disable_cake_2fa,
|
@override
|
||||||
alertContent: S.current.question_to_disable_2fa,
|
Widget build(BuildContext context) {
|
||||||
leftButtonText: S.current.cancel,
|
return Column(
|
||||||
rightButtonText: S.current.disable,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
actionLeftButton: () {
|
children: [
|
||||||
Navigator.of(context).pop();
|
SettingsCellWithArrow(
|
||||||
},
|
title: S.current.disable_cake_2fa,
|
||||||
actionRightButton: () {
|
handler: (_) async {
|
||||||
setup2FAViewModel.setUseTOTP2FA(false);
|
await showPopUp<void>(
|
||||||
Navigator.pushNamedAndRemoveUntil(
|
context: context,
|
||||||
context, Routes.dashboard, (route) => false);
|
builder: (BuildContext context) {
|
||||||
},
|
return AlertWithTwoActions(
|
||||||
);
|
alertTitle: S.current.disable_cake_2fa,
|
||||||
},
|
alertContent: S.current.question_to_disable_2fa,
|
||||||
);
|
leftButtonText: S.current.cancel,
|
||||||
}),
|
rightButtonText: S.current.disable,
|
||||||
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
actionLeftButton: () => Navigator.of(context).pop(),
|
||||||
],
|
actionRightButton: () {
|
||||||
),
|
setup2FAViewModel.setUseTOTP2FA(false);
|
||||||
|
Navigator.pushNamedAndRemoveUntil(context, Routes.dashboard, (route) => false);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||||
|
Observer(
|
||||||
|
builder: (context) {
|
||||||
|
return SettingsChoicesCell(
|
||||||
|
ChoicesListItem<Cake2FAPresetsOptions>(
|
||||||
|
title: S.current.cake_2fa_preset,
|
||||||
|
onItemSelected: setup2FAViewModel.selectCakePreset,
|
||||||
|
selectedItem: setup2FAViewModel.selectedCake2FAPreset,
|
||||||
|
items: [
|
||||||
|
Cake2FAPresetsOptions.narrow,
|
||||||
|
Cake2FAPresetsOptions.normal,
|
||||||
|
Cake2FAPresetsOptions.aggressive,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Observer(
|
||||||
|
builder: (context) {
|
||||||
|
return SettingsSwitcherCell(
|
||||||
|
title: S.current.require_for_assessing_wallet,
|
||||||
|
value: setup2FAViewModel.shouldRequireTOTP2FAForAccessingWallet,
|
||||||
|
onValueChange: (context, value) async =>
|
||||||
|
setup2FAViewModel.switchShouldRequireTOTP2FAForAccessingWallet(value),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||||
|
Observer(
|
||||||
|
builder: (context) {
|
||||||
|
return SettingsSwitcherCell(
|
||||||
|
title: S.current.require_for_sends_to_non_contacts,
|
||||||
|
value: setup2FAViewModel.shouldRequireTOTP2FAForSendsToNonContact,
|
||||||
|
onValueChange: (context, value) async =>
|
||||||
|
setup2FAViewModel.switchShouldRequireTOTP2FAForSendsToNonContact(value),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||||
|
Observer(
|
||||||
|
builder: (context) {
|
||||||
|
return SettingsSwitcherCell(
|
||||||
|
title: S.current.require_for_sends_to_contacts,
|
||||||
|
value: setup2FAViewModel.shouldRequireTOTP2FAForSendsToContact,
|
||||||
|
onValueChange: (context, value) async =>
|
||||||
|
setup2FAViewModel.switchShouldRequireTOTP2FAForSendsToContact(value),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||||
|
Observer(
|
||||||
|
builder: (context) {
|
||||||
|
return SettingsSwitcherCell(
|
||||||
|
title: S.current.require_for_sends_to_internal_wallets,
|
||||||
|
value: setup2FAViewModel.shouldRequireTOTP2FAForSendsToInternalWallets,
|
||||||
|
onValueChange: (context, value) async =>
|
||||||
|
setup2FAViewModel.switchShouldRequireTOTP2FAForSendsToInternalWallets(value),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||||
|
Observer(
|
||||||
|
builder: (context) {
|
||||||
|
return SettingsSwitcherCell(
|
||||||
|
title: S.current.require_for_exchanges_to_internal_wallets,
|
||||||
|
value: setup2FAViewModel.shouldRequireTOTP2FAForExchangesToInternalWallets,
|
||||||
|
onValueChange: (context, value) async =>
|
||||||
|
setup2FAViewModel.switchShouldRequireTOTP2FAForExchangesToInternalWallets(value),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||||
|
Observer(
|
||||||
|
builder: (context) {
|
||||||
|
return SettingsSwitcherCell(
|
||||||
|
title: S.current.require_for_adding_contacts,
|
||||||
|
value: setup2FAViewModel.shouldRequireTOTP2FAForAddingContacts,
|
||||||
|
onValueChange: (context, value) async =>
|
||||||
|
setup2FAViewModel.switchShouldRequireTOTP2FAForAddingContacts(value),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||||
|
Observer(
|
||||||
|
builder: (context) {
|
||||||
|
return SettingsSwitcherCell(
|
||||||
|
title: S.current.require_for_creating_new_wallets,
|
||||||
|
value: setup2FAViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||||
|
onValueChange: (context, value) async =>
|
||||||
|
setup2FAViewModel.switchShouldRequireTOTP2FAForCreatingNewWallet(value),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||||
|
Observer(
|
||||||
|
builder: (context) {
|
||||||
|
return SettingsSwitcherCell(
|
||||||
|
title: S.current.require_for_all_security_and_backup_settings,
|
||||||
|
value: setup2FAViewModel.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||||
|
onValueChange: (context, value) async => setup2FAViewModel
|
||||||
|
.switchShouldRequireTOTP2FAForAllSecurityAndBackupSettings(value),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,8 @@ class Setup2FAPage extends BasePage {
|
||||||
SizedBox(height: 86),
|
SizedBox(height: 86),
|
||||||
SettingsCellWithArrow(
|
SettingsCellWithArrow(
|
||||||
title: S.current.setup_totp_recommended,
|
title: S.current.setup_totp_recommended,
|
||||||
handler: (_) => Navigator.of(context).pushNamed(Routes.setup_2faQRPage),
|
handler: (_) => Navigator.of(context)
|
||||||
|
.pushReplacementNamed(Routes.setup_2faQRPage),
|
||||||
),
|
),
|
||||||
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
|
||||||
],
|
],
|
||||||
|
|
|
@ -18,7 +18,8 @@ import 'package:mobx/mobx.dart';
|
||||||
import '../../../palette.dart';
|
import '../../../palette.dart';
|
||||||
import '../../../routes.dart';
|
import '../../../routes.dart';
|
||||||
|
|
||||||
typedef OnTotpAuthenticationFinished = void Function(bool, TotpAuthCodePageState);
|
typedef OnTotpAuthenticationFinished = void Function(
|
||||||
|
bool, TotpAuthCodePageState);
|
||||||
|
|
||||||
class TotpAuthCodePage extends StatefulWidget {
|
class TotpAuthCodePage extends StatefulWidget {
|
||||||
TotpAuthCodePage(
|
TotpAuthCodePage(
|
||||||
|
@ -43,8 +44,9 @@ class TotpAuthCodePageState extends State<TotpAuthCodePage> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
if(widget.totpArguments.onTotpAuthenticationFinished != null) {
|
if (widget.totpArguments.onTotpAuthenticationFinished != null) {
|
||||||
_reaction ??= reaction((_) => widget.setup2FAViewModel.state, (ExecutionState state) {
|
_reaction ??= reaction((_) => widget.setup2FAViewModel.state,
|
||||||
|
(ExecutionState state) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (state is ExecutedSuccessfullyState) {
|
if (state is ExecutedSuccessfullyState) {
|
||||||
widget.totpArguments.onTotpAuthenticationFinished!(true, this);
|
widget.totpArguments.onTotpAuthenticationFinished!(true, this);
|
||||||
|
@ -57,9 +59,9 @@ class TotpAuthCodePageState extends State<TotpAuthCodePage> {
|
||||||
|
|
||||||
if (state is AuthenticationBanned) {
|
if (state is AuthenticationBanned) {
|
||||||
widget.totpArguments.onTotpAuthenticationFinished!(false, this);
|
widget.totpArguments.onTotpAuthenticationFinished!(false, this);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -73,7 +75,8 @@ class TotpAuthCodePageState extends State<TotpAuthCodePage> {
|
||||||
|
|
||||||
void changeProcessText(String text) {
|
void changeProcessText(String text) {
|
||||||
dismissFlushBar(_authBar);
|
dismissFlushBar(_authBar);
|
||||||
_progressBar = createBar<void>(text, duration: null)..show(_key.currentContext!);
|
_progressBar = createBar<void>(text, duration: null)
|
||||||
|
..show(_key.currentContext!);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> close({String? route, dynamic arguments}) async {
|
Future<void> close({String? route, dynamic arguments}) async {
|
||||||
|
@ -82,7 +85,8 @@ class TotpAuthCodePageState extends State<TotpAuthCodePage> {
|
||||||
}
|
}
|
||||||
await Future<void>.delayed(Duration(milliseconds: 50));
|
await Future<void>.delayed(Duration(milliseconds: 50));
|
||||||
if (route != null) {
|
if (route != null) {
|
||||||
Navigator.of(_key.currentContext!).pushReplacementNamed(route, arguments: arguments);
|
Navigator.of(_key.currentContext!)
|
||||||
|
.pushReplacementNamed(route, arguments: arguments);
|
||||||
} else {
|
} else {
|
||||||
Navigator.of(_key.currentContext!).pop();
|
Navigator.of(_key.currentContext!).pop();
|
||||||
}
|
}
|
||||||
|
@ -120,7 +124,8 @@ class TOTPEnterCode extends BasePage {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get title => isForSetup ? S.current.setup_2fa : S.current.verify_with_2fa;
|
String get title =>
|
||||||
|
isForSetup ? S.current.setup_2fa : S.current.verify_with_2fa;
|
||||||
|
|
||||||
Widget? leading(BuildContext context) {
|
Widget? leading(BuildContext context) {
|
||||||
return isClosable ? super.leading(context) : null;
|
return isClosable ? super.leading(context) : null;
|
||||||
|
@ -166,21 +171,24 @@ class TOTPEnterCode extends BasePage {
|
||||||
return PrimaryButton(
|
return PrimaryButton(
|
||||||
isDisabled: setup2FAViewModel.enteredOTPCode.length != 8,
|
isDisabled: setup2FAViewModel.enteredOTPCode.length != 8,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final result =
|
final result = await setup2FAViewModel.totp2FAAuth(
|
||||||
await setup2FAViewModel.totp2FAAuth(totpController.text, isForSetup);
|
totpController.text, isForSetup);
|
||||||
final bannedState = setup2FAViewModel.state is AuthenticationBanned;
|
final bannedState =
|
||||||
|
setup2FAViewModel.state is AuthenticationBanned;
|
||||||
|
|
||||||
await showPopUp<void>(
|
await showPopUp<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return PopUpCancellableAlertDialog(
|
return PopUpCancellableAlertDialog(
|
||||||
contentText: _textDisplayedInPopupOnResult(result, bannedState, context),
|
contentText: _textDisplayedInPopupOnResult(
|
||||||
|
result, bannedState, context),
|
||||||
actionButtonText: S.of(context).ok,
|
actionButtonText: S.of(context).ok,
|
||||||
buttonAction: () {
|
buttonAction: () {
|
||||||
result ? setup2FAViewModel.success() : null;
|
result ? setup2FAViewModel.success() : null;
|
||||||
if (isForSetup && result) {
|
if (isForSetup && result) {
|
||||||
Navigator.pushNamedAndRemoveUntil(
|
Navigator.pop(context);
|
||||||
context, Routes.dashboard, (route) => false);
|
// Navigator.of(context)
|
||||||
|
// .popAndPushNamed(Routes.modify2FAPage);
|
||||||
} else {
|
} else {
|
||||||
Navigator.of(context).pop(result);
|
Navigator.of(context).pop(result);
|
||||||
}
|
}
|
||||||
|
@ -188,6 +196,11 @@ class TOTPEnterCode extends BasePage {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
if (isForSetup && result) {
|
||||||
|
Navigator.pushReplacementNamed(
|
||||||
|
context, Routes.modify2FAPage);
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
text: S.of(context).continue_text,
|
text: S.of(context).continue_text,
|
||||||
color: Theme.of(context).accentTextTheme.bodyLarge!.color!,
|
color: Theme.of(context).accentTextTheme.bodyLarge!.color!,
|
||||||
|
@ -201,10 +214,13 @@ class TOTPEnterCode extends BasePage {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _textDisplayedInPopupOnResult(bool result, bool bannedState, BuildContext context) {
|
String _textDisplayedInPopupOnResult(
|
||||||
|
bool result, bool bannedState, BuildContext context) {
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case true:
|
case true:
|
||||||
return isForSetup ? S.current.totp_2fa_success : S.current.totp_verification_success;
|
return isForSetup
|
||||||
|
? S.current.totp_2fa_success
|
||||||
|
: S.current.totp_verification_success;
|
||||||
case false:
|
case false:
|
||||||
if (bannedState) {
|
if (bannedState) {
|
||||||
final state = setup2FAViewModel.state as AuthenticationBanned;
|
final state = setup2FAViewModel.state as AuthenticationBanned;
|
||||||
|
|
|
@ -125,7 +125,9 @@ class WalletEditPage extends BasePage {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSuccessfulAuth(context);
|
_onSuccessfulAuth(context);
|
||||||
});
|
},
|
||||||
|
conditionToDetermineIfToUse2FA: false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSuccessfulAuth(BuildContext context) async {
|
void _onSuccessfulAuth(BuildContext context) async {
|
||||||
|
|
|
@ -57,7 +57,7 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||||
final newWalletImage =
|
final newWalletImage =
|
||||||
Image.asset('assets/images/new_wallet.png', height: 12, width: 12, color: Colors.white);
|
Image.asset('assets/images/new_wallet.png', height: 12, width: 12, color: Colors.white);
|
||||||
final restoreWalletImage = Image.asset('assets/images/restore_wallet.png',
|
final restoreWalletImage = Image.asset('assets/images/restore_wallet.png',
|
||||||
height: 12, width: 12, color: Theme.of(context).primaryTextTheme!.titleLarge!.color!);
|
height: 12, width: 12, color: Theme.of(context).primaryTextTheme.titleLarge!.color!);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.only(top: 16),
|
padding: EdgeInsets.only(top: 16),
|
||||||
|
@ -74,7 +74,7 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||||
itemBuilder: (__, index) {
|
itemBuilder: (__, index) {
|
||||||
final wallet = widget.walletListViewModel.wallets[index];
|
final wallet = widget.walletListViewModel.wallets[index];
|
||||||
final currentColor = wallet.isCurrent
|
final currentColor = wallet.isCurrent
|
||||||
? Theme.of(context).accentTextTheme!.titleSmall!.decorationColor!
|
? Theme.of(context).accentTextTheme.titleSmall!.decorationColor!
|
||||||
: Theme.of(context).colorScheme.background;
|
: Theme.of(context).colorScheme.background;
|
||||||
final row = GestureDetector(
|
final row = GestureDetector(
|
||||||
onTap: () => wallet.isCurrent ? null : _loadWallet(wallet),
|
onTap: () => wallet.isCurrent ? null : _loadWallet(wallet),
|
||||||
|
@ -133,8 +133,7 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||||
: Row(children: [
|
: Row(children: [
|
||||||
Expanded(child: row),
|
Expanded(child: row),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () => Navigator.of(context).pushNamed(
|
onTap: () => Navigator.of(context).pushNamed(Routes.walletEdit,
|
||||||
Routes.walletEdit,
|
|
||||||
arguments: [widget.walletListViewModel, wallet]),
|
arguments: [widget.walletListViewModel, wallet]),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.only(right: 20),
|
padding: EdgeInsets.only(right: 20),
|
||||||
|
@ -152,10 +151,7 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.edit,
|
Icons.edit,
|
||||||
size: 14,
|
size: 14,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context).textTheme.headlineMedium!.color!,
|
||||||
.textTheme
|
|
||||||
.headlineMedium!
|
|
||||||
.color!,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -169,27 +165,59 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||||
bottomSection: Column(children: <Widget>[
|
bottomSection: Column(children: <Widget>[
|
||||||
PrimaryImageButton(
|
PrimaryImageButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
//TODO(David): Find a way to optimize this
|
||||||
if (isSingleCoin) {
|
if (isSingleCoin) {
|
||||||
Navigator.of(context).pushNamed(Routes.newWallet,
|
if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) {
|
||||||
arguments: widget.walletListViewModel.currentWalletType);
|
widget.authService.authenticateAction(
|
||||||
|
context,
|
||||||
|
route: Routes.newWallet,
|
||||||
|
arguments: widget.walletListViewModel.currentWalletType,
|
||||||
|
conditionToDetermineIfToUse2FA:
|
||||||
|
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
Routes.newWallet,
|
||||||
|
arguments: widget.walletListViewModel.currentWalletType,
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Navigator.of(context).pushNamed(Routes.newWalletType);
|
if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) {
|
||||||
|
widget.authService.authenticateAction(
|
||||||
|
context,
|
||||||
|
route: Routes.newWalletType,
|
||||||
|
conditionToDetermineIfToUse2FA:
|
||||||
|
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Navigator.of(context).pushNamed(Routes.newWalletType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
image: newWalletImage,
|
image: newWalletImage,
|
||||||
text: S.of(context).wallet_list_create_new_wallet,
|
text: S.of(context).wallet_list_create_new_wallet,
|
||||||
color: Theme.of(context).accentTextTheme!.bodyLarge!.color!,
|
color: Theme.of(context).accentTextTheme.bodyLarge!.color!,
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
),
|
),
|
||||||
SizedBox(height: 10.0),
|
SizedBox(height: 10.0),
|
||||||
PrimaryImageButton(
|
PrimaryImageButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pushNamed(Routes.restoreOptions, arguments: false);
|
if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) {
|
||||||
|
widget.authService.authenticateAction(
|
||||||
|
context,
|
||||||
|
route: Routes.restoreOptions,
|
||||||
|
arguments: false,
|
||||||
|
conditionToDetermineIfToUse2FA:
|
||||||
|
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Navigator.of(context).pushNamed(Routes.restoreOptions, arguments: false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
image: restoreWalletImage,
|
image: restoreWalletImage,
|
||||||
text: S.of(context).wallet_list_restore_wallet,
|
text: S.of(context).wallet_list_restore_wallet,
|
||||||
color: Theme.of(context).accentTextTheme!.bodySmall!.color!,
|
color: Theme.of(context).accentTextTheme.bodySmall!.color!,
|
||||||
textColor: Theme.of(context).primaryTextTheme!.titleLarge!.color!)
|
textColor: Theme.of(context).primaryTextTheme.titleLarge!.color!)
|
||||||
])),
|
])),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -214,27 +242,31 @@ class WalletListBodyState extends State<WalletListBody> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadWallet(WalletListItem wallet) async {
|
Future<void> _loadWallet(WalletListItem wallet) async {
|
||||||
await widget.authService.authenticateAction(context,
|
await widget.authService.authenticateAction(
|
||||||
onAuthSuccess: (isAuthenticatedSuccessfully) async {
|
context,
|
||||||
if (!isAuthenticatedSuccessfully) {
|
onAuthSuccess: (isAuthenticatedSuccessfully) async {
|
||||||
return;
|
if (!isAuthenticatedSuccessfully) {
|
||||||
}
|
return;
|
||||||
|
|
||||||
try {
|
|
||||||
changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
|
|
||||||
await widget.walletListViewModel.loadWallet(wallet);
|
|
||||||
await hideProgressText();
|
|
||||||
// only pop the wallets route in mobile as it will go back to dashboard page
|
|
||||||
// in desktop platforms the navigation tree is different
|
|
||||||
if (ResponsiveLayoutUtil.instance.shouldRenderMobileUI()) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
|
try {
|
||||||
}
|
changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
|
||||||
});
|
await widget.walletListViewModel.loadWallet(wallet);
|
||||||
|
await hideProgressText();
|
||||||
|
// only pop the wallets route in mobile as it will go back to dashboard page
|
||||||
|
// in desktop platforms the navigation tree is different
|
||||||
|
if (ResponsiveLayoutUtil.instance.shouldRenderMobileUI()) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
conditionToDetermineIfToUse2FA:
|
||||||
|
widget.walletListViewModel.shouldRequireTOTP2FAForAccessingWallet,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void changeProcessText(String text) {
|
void changeProcessText(String text) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
|
import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
|
||||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||||
import 'package:cake_wallet/entities/pin_code_required_duration.dart';
|
import 'package:cake_wallet/entities/pin_code_required_duration.dart';
|
||||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||||
|
@ -58,8 +59,18 @@ abstract class SettingsStoreBase with Store {
|
||||||
required this.isBitcoinBuyEnabled,
|
required this.isBitcoinBuyEnabled,
|
||||||
required this.actionlistDisplayMode,
|
required this.actionlistDisplayMode,
|
||||||
required this.pinTimeOutDuration,
|
required this.pinTimeOutDuration,
|
||||||
|
required Cake2FAPresetsOptions initialCake2FAPresetOptions,
|
||||||
|
required bool initialShouldRequireTOTP2FAForAccessingWallet,
|
||||||
|
required bool initialShouldRequireTOTP2FAForSendsToContact,
|
||||||
|
required bool initialShouldRequireTOTP2FAForSendsToNonContact,
|
||||||
|
required bool initialShouldRequireTOTP2FAForSendsToInternalWallets,
|
||||||
|
required bool initialShouldRequireTOTP2FAForExchangesToInternalWallets,
|
||||||
|
required bool initialShouldRequireTOTP2FAForAddingContacts,
|
||||||
|
required bool initialShouldRequireTOTP2FAForCreatingNewWallets,
|
||||||
|
required bool initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||||
required this.sortBalanceBy,
|
required this.sortBalanceBy,
|
||||||
required this.pinNativeTokenAtTop,
|
required this.pinNativeTokenAtTop,
|
||||||
|
required this.useEtherscan,
|
||||||
TransactionPriority? initialBitcoinTransactionPriority,
|
TransactionPriority? initialBitcoinTransactionPriority,
|
||||||
TransactionPriority? initialMoneroTransactionPriority,
|
TransactionPriority? initialMoneroTransactionPriority,
|
||||||
TransactionPriority? initialHavenTransactionPriority,
|
TransactionPriority? initialHavenTransactionPriority,
|
||||||
|
@ -72,6 +83,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
shouldSaveRecipientAddress = initialSaveRecipientAddress,
|
shouldSaveRecipientAddress = initialSaveRecipientAddress,
|
||||||
fiatApiMode = initialFiatMode,
|
fiatApiMode = initialFiatMode,
|
||||||
allowBiometricalAuthentication = initialAllowBiometricalAuthentication,
|
allowBiometricalAuthentication = initialAllowBiometricalAuthentication,
|
||||||
|
selectedCake2FAPreset = initialCake2FAPresetOptions,
|
||||||
totpSecretKey = initialTotpSecretKey,
|
totpSecretKey = initialTotpSecretKey,
|
||||||
useTOTP2FA = initialUseTOTP2FA,
|
useTOTP2FA = initialUseTOTP2FA,
|
||||||
numberOfFailedTokenTrials = initialFailedTokenTrial,
|
numberOfFailedTokenTrials = initialFailedTokenTrial,
|
||||||
|
@ -83,6 +95,18 @@ abstract class SettingsStoreBase with Store {
|
||||||
currentTheme = initialTheme,
|
currentTheme = initialTheme,
|
||||||
pinCodeLength = initialPinLength,
|
pinCodeLength = initialPinLength,
|
||||||
languageCode = initialLanguageCode,
|
languageCode = initialLanguageCode,
|
||||||
|
shouldRequireTOTP2FAForAccessingWallet = initialShouldRequireTOTP2FAForAccessingWallet,
|
||||||
|
shouldRequireTOTP2FAForSendsToContact = initialShouldRequireTOTP2FAForSendsToContact,
|
||||||
|
shouldRequireTOTP2FAForSendsToNonContact = initialShouldRequireTOTP2FAForSendsToNonContact,
|
||||||
|
shouldRequireTOTP2FAForSendsToInternalWallets =
|
||||||
|
initialShouldRequireTOTP2FAForSendsToInternalWallets,
|
||||||
|
shouldRequireTOTP2FAForExchangesToInternalWallets =
|
||||||
|
initialShouldRequireTOTP2FAForExchangesToInternalWallets,
|
||||||
|
shouldRequireTOTP2FAForAddingContacts = initialShouldRequireTOTP2FAForAddingContacts,
|
||||||
|
shouldRequireTOTP2FAForCreatingNewWallets =
|
||||||
|
initialShouldRequireTOTP2FAForCreatingNewWallets,
|
||||||
|
shouldRequireTOTP2FAForAllSecurityAndBackupSettings =
|
||||||
|
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||||
priority = ObservableMap<WalletType, TransactionPriority>() {
|
priority = ObservableMap<WalletType, TransactionPriority>() {
|
||||||
//this.nodes = ObservableMap<WalletType, Node>.of(nodes);
|
//this.nodes = ObservableMap<WalletType, Node>.of(nodes);
|
||||||
|
|
||||||
|
@ -178,6 +202,57 @@ abstract class SettingsStoreBase with Store {
|
||||||
(bool biometricalAuthentication) => sharedPreferences.setBool(
|
(bool biometricalAuthentication) => sharedPreferences.setBool(
|
||||||
PreferencesKey.allowBiometricalAuthenticationKey, biometricalAuthentication));
|
PreferencesKey.allowBiometricalAuthenticationKey, biometricalAuthentication));
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
(_) => selectedCake2FAPreset,
|
||||||
|
(Cake2FAPresetsOptions selectedCake2FAPreset) => sharedPreferences.setInt(
|
||||||
|
PreferencesKey.selectedCake2FAPreset, selectedCake2FAPreset.serialize()));
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
(_) => shouldRequireTOTP2FAForAccessingWallet,
|
||||||
|
(bool requireTOTP2FAForAccessingWallet) => sharedPreferences.setBool(
|
||||||
|
PreferencesKey.shouldRequireTOTP2FAForAccessingWallet,
|
||||||
|
requireTOTP2FAForAccessingWallet));
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
(_) => shouldRequireTOTP2FAForSendsToContact,
|
||||||
|
(bool requireTOTP2FAForSendsToContact) => sharedPreferences.setBool(
|
||||||
|
PreferencesKey.shouldRequireTOTP2FAForSendsToContact, requireTOTP2FAForSendsToContact));
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
(_) => shouldRequireTOTP2FAForSendsToNonContact,
|
||||||
|
(bool requireTOTP2FAForSendsToNonContact) => sharedPreferences.setBool(
|
||||||
|
PreferencesKey.shouldRequireTOTP2FAForSendsToNonContact,
|
||||||
|
requireTOTP2FAForSendsToNonContact));
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
(_) => shouldRequireTOTP2FAForSendsToInternalWallets,
|
||||||
|
(bool requireTOTP2FAForSendsToInternalWallets) => sharedPreferences.setBool(
|
||||||
|
PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets,
|
||||||
|
requireTOTP2FAForSendsToInternalWallets));
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
(_) => shouldRequireTOTP2FAForExchangesToInternalWallets,
|
||||||
|
(bool requireTOTP2FAForExchangesToInternalWallets) => sharedPreferences.setBool(
|
||||||
|
PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets,
|
||||||
|
requireTOTP2FAForExchangesToInternalWallets));
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
(_) => shouldRequireTOTP2FAForAddingContacts,
|
||||||
|
(bool requireTOTP2FAForAddingContacts) => sharedPreferences.setBool(
|
||||||
|
PreferencesKey.shouldRequireTOTP2FAForAddingContacts, requireTOTP2FAForAddingContacts));
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
(_) => shouldRequireTOTP2FAForCreatingNewWallets,
|
||||||
|
(bool requireTOTP2FAForCreatingNewWallets) => sharedPreferences.setBool(
|
||||||
|
PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||||
|
requireTOTP2FAForCreatingNewWallets));
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
(_) => shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||||
|
(bool requireTOTP2FAForAllSecurityAndBackupSettings) => sharedPreferences.setBool(
|
||||||
|
PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||||
|
requireTOTP2FAForAllSecurityAndBackupSettings));
|
||||||
|
|
||||||
reaction(
|
reaction(
|
||||||
(_) => useTOTP2FA, (bool use) => sharedPreferences.setBool(PreferencesKey.useTOTP2FA, use));
|
(_) => useTOTP2FA, (bool use) => sharedPreferences.setBool(PreferencesKey.useTOTP2FA, use));
|
||||||
|
|
||||||
|
@ -227,6 +302,11 @@ abstract class SettingsStoreBase with Store {
|
||||||
(bool pinNativeTokenAtTop) =>
|
(bool pinNativeTokenAtTop) =>
|
||||||
_sharedPreferences.setBool(PreferencesKey.pinNativeTokenAtTop, pinNativeTokenAtTop));
|
_sharedPreferences.setBool(PreferencesKey.pinNativeTokenAtTop, pinNativeTokenAtTop));
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
(_) => useEtherscan,
|
||||||
|
(bool useEtherscan) =>
|
||||||
|
_sharedPreferences.setBool(PreferencesKey.useEtherscan, useEtherscan));
|
||||||
|
|
||||||
this.nodes.observe((change) {
|
this.nodes.observe((change) {
|
||||||
if (change.newValue != null && change.key != null) {
|
if (change.newValue != null && change.key != null) {
|
||||||
_saveCurrentNode(change.newValue!, change.key!);
|
_saveCurrentNode(change.newValue!, change.key!);
|
||||||
|
@ -271,6 +351,33 @@ abstract class SettingsStoreBase with Store {
|
||||||
@observable
|
@observable
|
||||||
bool allowBiometricalAuthentication;
|
bool allowBiometricalAuthentication;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool shouldRequireTOTP2FAForAccessingWallet;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool shouldRequireTOTP2FAForSendsToContact;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool shouldRequireTOTP2FAForSendsToNonContact;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool shouldRequireTOTP2FAForSendsToInternalWallets;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool shouldRequireTOTP2FAForExchangesToInternalWallets;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
Cake2FAPresetsOptions selectedCake2FAPreset;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool shouldRequireTOTP2FAForAddingContacts;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool shouldRequireTOTP2FAForCreatingNewWallets;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool shouldRequireTOTP2FAForAllSecurityAndBackupSettings;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
String totpSecretKey;
|
String totpSecretKey;
|
||||||
|
|
||||||
|
@ -312,6 +419,9 @@ abstract class SettingsStoreBase with Store {
|
||||||
@observable
|
@observable
|
||||||
bool pinNativeTokenAtTop;
|
bool pinNativeTokenAtTop;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool useEtherscan;
|
||||||
|
|
||||||
String appVersion;
|
String appVersion;
|
||||||
|
|
||||||
String deviceName;
|
String deviceName;
|
||||||
|
@ -390,6 +500,29 @@ abstract class SettingsStoreBase with Store {
|
||||||
FiatApiMode.enabled.raw);
|
FiatApiMode.enabled.raw);
|
||||||
final allowBiometricalAuthentication =
|
final allowBiometricalAuthentication =
|
||||||
sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? false;
|
sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? false;
|
||||||
|
final selectedCake2FAPreset = Cake2FAPresetsOptions.deserialize(
|
||||||
|
raw: sharedPreferences.getInt(PreferencesKey.selectedCake2FAPreset) ??
|
||||||
|
Cake2FAPresetsOptions.normal.raw);
|
||||||
|
final shouldRequireTOTP2FAForAccessingWallet =
|
||||||
|
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAccessingWallet) ?? false;
|
||||||
|
final shouldRequireTOTP2FAForSendsToContact =
|
||||||
|
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToContact) ?? false;
|
||||||
|
final shouldRequireTOTP2FAForSendsToNonContact =
|
||||||
|
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToNonContact) ?? false;
|
||||||
|
final shouldRequireTOTP2FAForSendsToInternalWallets =
|
||||||
|
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets) ??
|
||||||
|
false;
|
||||||
|
final shouldRequireTOTP2FAForExchangesToInternalWallets = sharedPreferences
|
||||||
|
.getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ??
|
||||||
|
false;
|
||||||
|
final shouldRequireTOTP2FAForAddingContacts =
|
||||||
|
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts) ?? false;
|
||||||
|
final shouldRequireTOTP2FAForCreatingNewWallets =
|
||||||
|
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets) ??
|
||||||
|
false;
|
||||||
|
final shouldRequireTOTP2FAForAllSecurityAndBackupSettings = sharedPreferences
|
||||||
|
.getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ??
|
||||||
|
false;
|
||||||
final totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? '';
|
final totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? '';
|
||||||
final useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? false;
|
final useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? false;
|
||||||
final tokenTrialNumber = sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? 0;
|
final tokenTrialNumber = sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? 0;
|
||||||
|
@ -416,6 +549,8 @@ abstract class SettingsStoreBase with Store {
|
||||||
SortBalanceBy.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? 0];
|
SortBalanceBy.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? 0];
|
||||||
final pinNativeTokenAtTop =
|
final pinNativeTokenAtTop =
|
||||||
sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
|
sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
|
||||||
|
final useEtherscan =
|
||||||
|
sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
|
||||||
|
|
||||||
// If no value
|
// If no value
|
||||||
if (pinLength == null || pinLength == 0) {
|
if (pinLength == null || pinLength == 0) {
|
||||||
|
@ -483,6 +618,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
initialDisableSell: disableSell,
|
initialDisableSell: disableSell,
|
||||||
initialFiatMode: currentFiatApiMode,
|
initialFiatMode: currentFiatApiMode,
|
||||||
initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
|
initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
|
||||||
|
initialCake2FAPresetOptions: selectedCake2FAPreset,
|
||||||
initialTotpSecretKey: totpSecretKey,
|
initialTotpSecretKey: totpSecretKey,
|
||||||
initialUseTOTP2FA: useTOTP2FA,
|
initialUseTOTP2FA: useTOTP2FA,
|
||||||
initialFailedTokenTrial: tokenTrialNumber,
|
initialFailedTokenTrial: tokenTrialNumber,
|
||||||
|
@ -494,10 +630,22 @@ abstract class SettingsStoreBase with Store {
|
||||||
initialLanguageCode: savedLanguageCode,
|
initialLanguageCode: savedLanguageCode,
|
||||||
sortBalanceBy: sortBalanceBy,
|
sortBalanceBy: sortBalanceBy,
|
||||||
pinNativeTokenAtTop: pinNativeTokenAtTop,
|
pinNativeTokenAtTop: pinNativeTokenAtTop,
|
||||||
|
useEtherscan: useEtherscan,
|
||||||
initialMoneroTransactionPriority: moneroTransactionPriority,
|
initialMoneroTransactionPriority: moneroTransactionPriority,
|
||||||
initialBitcoinTransactionPriority: bitcoinTransactionPriority,
|
initialBitcoinTransactionPriority: bitcoinTransactionPriority,
|
||||||
initialHavenTransactionPriority: havenTransactionPriority,
|
initialHavenTransactionPriority: havenTransactionPriority,
|
||||||
initialLitecoinTransactionPriority: litecoinTransactionPriority,
|
initialLitecoinTransactionPriority: litecoinTransactionPriority,
|
||||||
|
initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet,
|
||||||
|
initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact,
|
||||||
|
initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact,
|
||||||
|
initialShouldRequireTOTP2FAForSendsToInternalWallets:
|
||||||
|
shouldRequireTOTP2FAForSendsToInternalWallets,
|
||||||
|
initialShouldRequireTOTP2FAForExchangesToInternalWallets:
|
||||||
|
shouldRequireTOTP2FAForExchangesToInternalWallets,
|
||||||
|
initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts,
|
||||||
|
initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets,
|
||||||
|
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
|
||||||
|
shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
|
||||||
initialEthereumTransactionPriority: ethereumTransactionPriority,
|
initialEthereumTransactionPriority: ethereumTransactionPriority,
|
||||||
shouldShowYatPopup: shouldShowYatPopup);
|
shouldShowYatPopup: shouldShowYatPopup);
|
||||||
}
|
}
|
||||||
|
@ -538,6 +686,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
shouldSaveRecipientAddress;
|
shouldSaveRecipientAddress;
|
||||||
totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? totpSecretKey;
|
totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? totpSecretKey;
|
||||||
useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? useTOTP2FA;
|
useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? useTOTP2FA;
|
||||||
|
|
||||||
numberOfFailedTokenTrials =
|
numberOfFailedTokenTrials =
|
||||||
sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? numberOfFailedTokenTrials;
|
sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? numberOfFailedTokenTrials;
|
||||||
sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ??
|
sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ??
|
||||||
|
@ -548,9 +697,35 @@ abstract class SettingsStoreBase with Store {
|
||||||
allowBiometricalAuthentication =
|
allowBiometricalAuthentication =
|
||||||
sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
|
sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
|
||||||
allowBiometricalAuthentication;
|
allowBiometricalAuthentication;
|
||||||
|
selectedCake2FAPreset = Cake2FAPresetsOptions.deserialize(
|
||||||
|
raw: sharedPreferences.getInt(PreferencesKey.selectedCake2FAPreset) ??
|
||||||
|
Cake2FAPresetsOptions.normal.raw);
|
||||||
|
shouldRequireTOTP2FAForAccessingWallet =
|
||||||
|
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAccessingWallet) ?? false;
|
||||||
|
shouldRequireTOTP2FAForSendsToContact =
|
||||||
|
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToContact) ?? false;
|
||||||
|
shouldRequireTOTP2FAForSendsToNonContact =
|
||||||
|
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToNonContact) ?? false;
|
||||||
|
shouldRequireTOTP2FAForSendsToInternalWallets =
|
||||||
|
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForSendsToInternalWallets) ??
|
||||||
|
false;
|
||||||
|
shouldRequireTOTP2FAForExchangesToInternalWallets = sharedPreferences
|
||||||
|
.getBool(PreferencesKey.shouldRequireTOTP2FAForExchangesToInternalWallets) ??
|
||||||
|
false;
|
||||||
|
shouldRequireTOTP2FAForAddingContacts =
|
||||||
|
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForAddingContacts) ?? false;
|
||||||
|
shouldRequireTOTP2FAForCreatingNewWallets =
|
||||||
|
sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets) ??
|
||||||
|
false;
|
||||||
|
shouldRequireTOTP2FAForAllSecurityAndBackupSettings = sharedPreferences
|
||||||
|
.getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings) ??
|
||||||
|
false;
|
||||||
shouldShowMarketPlaceInDashboard =
|
shouldShowMarketPlaceInDashboard =
|
||||||
sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ??
|
sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ??
|
||||||
shouldShowMarketPlaceInDashboard;
|
shouldShowMarketPlaceInDashboard;
|
||||||
|
selectedCake2FAPreset = Cake2FAPresetsOptions.deserialize(
|
||||||
|
raw: sharedPreferences.getInt(PreferencesKey.selectedCake2FAPreset) ??
|
||||||
|
Cake2FAPresetsOptions.narrow.raw);
|
||||||
exchangeStatus = ExchangeApiMode.deserialize(
|
exchangeStatus = ExchangeApiMode.deserialize(
|
||||||
raw: sharedPreferences.getInt(PreferencesKey.exchangeStatusKey) ??
|
raw: sharedPreferences.getInt(PreferencesKey.exchangeStatusKey) ??
|
||||||
ExchangeApiMode.enabled.raw);
|
ExchangeApiMode.enabled.raw);
|
||||||
|
@ -575,6 +750,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
sortBalanceBy = SortBalanceBy
|
sortBalanceBy = SortBalanceBy
|
||||||
.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? sortBalanceBy.index];
|
.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? sortBalanceBy.index];
|
||||||
pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
|
pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
|
||||||
|
useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
|
||||||
|
|
||||||
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
||||||
final bitcoinElectrumServerId =
|
final bitcoinElectrumServerId =
|
||||||
|
|
|
@ -144,6 +144,7 @@ class ExceptionHandler {
|
||||||
"Connection closed before full header was received",
|
"Connection closed before full header was received",
|
||||||
"Connection terminated during handshake",
|
"Connection terminated during handshake",
|
||||||
"PERMISSION_NOT_GRANTED",
|
"PERMISSION_NOT_GRANTED",
|
||||||
|
"Failed host lookup: ",
|
||||||
];
|
];
|
||||||
|
|
||||||
static Future<void> _addDeviceInfo(File file) async {
|
static Future<void> _addDeviceInfo(File file) async {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:cake_wallet/entities/contact_base.dart';
|
import 'package:cake_wallet/entities/contact_base.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_contact.dart';
|
import 'package:cake_wallet/entities/wallet_contact.dart';
|
||||||
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
@ -12,10 +13,12 @@ import 'package:cw_core/crypto_currency.dart';
|
||||||
|
|
||||||
part 'contact_list_view_model.g.dart';
|
part 'contact_list_view_model.g.dart';
|
||||||
|
|
||||||
class ContactListViewModel = ContactListViewModelBase with _$ContactListViewModel;
|
class ContactListViewModel = ContactListViewModelBase
|
||||||
|
with _$ContactListViewModel;
|
||||||
|
|
||||||
abstract class ContactListViewModelBase with Store {
|
abstract class ContactListViewModelBase with Store {
|
||||||
ContactListViewModelBase(this.contactSource, this.walletInfoSource, this._currency)
|
ContactListViewModelBase(this.contactSource, this.walletInfoSource,
|
||||||
|
this._currency, this.settingsStore)
|
||||||
: contacts = ObservableList<ContactRecord>(),
|
: contacts = ObservableList<ContactRecord>(),
|
||||||
walletContacts = [] {
|
walletContacts = [] {
|
||||||
walletInfoSource.values.forEach((info) {
|
walletInfoSource.values.forEach((info) {
|
||||||
|
@ -43,9 +46,14 @@ abstract class ContactListViewModelBase with Store {
|
||||||
final List<WalletContact> walletContacts;
|
final List<WalletContact> walletContacts;
|
||||||
final CryptoCurrency? _currency;
|
final CryptoCurrency? _currency;
|
||||||
StreamSubscription<BoxEvent>? _subscription;
|
StreamSubscription<BoxEvent>? _subscription;
|
||||||
|
final SettingsStore settingsStore;
|
||||||
|
|
||||||
bool get isEditable => _currency == null;
|
bool get isEditable => _currency == null;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get shouldRequireTOTP2FAForAddingContacts =>
|
||||||
|
settingsStore.shouldRequireTOTP2FAForAddingContacts;
|
||||||
|
|
||||||
Future<void> delete(ContactRecord contact) async => contact.original.delete();
|
Future<void> delete(ContactRecord contact) async => contact.original.delete();
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
|
|
|
@ -274,6 +274,9 @@ abstract class BalanceViewModelBase with Store {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get hasAdditionalBalance => wallet.type != WalletType.ethereum;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
List<BalanceRecord> get formattedBalances {
|
List<BalanceRecord> get formattedBalances {
|
||||||
final balance = balances.values.toList();
|
final balance = balances.values.toList();
|
||||||
|
@ -310,15 +313,16 @@ abstract class BalanceViewModelBase with Store {
|
||||||
|
|
||||||
switch (sortBalanceBy) {
|
switch (sortBalanceBy) {
|
||||||
case SortBalanceBy.FiatBalance:
|
case SortBalanceBy.FiatBalance:
|
||||||
return double.parse(_getFiatBalance(
|
final aFiatBalance = _getFiatBalance(
|
||||||
price: fiatConvertationStore.prices[b.asset] ?? 0,
|
price: fiatConvertationStore.prices[a.asset] ?? 0, cryptoAmount: a.availableBalance);
|
||||||
cryptoAmount: b.availableBalance))
|
final bFiatBalance = _getFiatBalance(
|
||||||
.compareTo(double.parse(_getFiatBalance(
|
price: fiatConvertationStore.prices[b.asset] ?? 0, cryptoAmount: b.availableBalance);
|
||||||
price: fiatConvertationStore.prices[a.asset] ?? 0,
|
|
||||||
cryptoAmount: a.availableBalance)));
|
return (double.tryParse(bFiatBalance) ?? 0)
|
||||||
|
.compareTo((double.tryParse(aFiatBalance)) ?? 0);
|
||||||
case SortBalanceBy.GrossBalance:
|
case SortBalanceBy.GrossBalance:
|
||||||
return double.parse(b.availableBalance)
|
return (double.tryParse(b.availableBalance) ?? 0)
|
||||||
.compareTo(double.parse(a.availableBalance));
|
.compareTo(double.tryParse(a.availableBalance) ?? 0);
|
||||||
case SortBalanceBy.Alphabetical:
|
case SortBalanceBy.Alphabetical:
|
||||||
return a.asset.title.compareTo(b.asset.title);
|
return a.asset.title.compareTo(b.asset.title);
|
||||||
}
|
}
|
||||||
|
@ -369,7 +373,7 @@ abstract class BalanceViewModelBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getFiatBalance({required double price, String? cryptoAmount}) {
|
String _getFiatBalance({required double price, String? cryptoAmount}) {
|
||||||
if (cryptoAmount == null || cryptoAmount.isEmpty) {
|
if (cryptoAmount == null || cryptoAmount.isEmpty || double.tryParse(cryptoAmount) == null) {
|
||||||
return '0.00';
|
return '0.00';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -100,6 +100,13 @@ class TransactionListItem extends ActionListItem with Keyable {
|
||||||
.toDouble(),
|
.toDouble(),
|
||||||
price: price);
|
price: price);
|
||||||
break;
|
break;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
final asset = ethereum!.assetOfTransaction(balanceViewModel.wallet, transaction);
|
||||||
|
final price = balanceViewModel.fiatConvertationStore.prices[asset];
|
||||||
|
amount = calculateFiatAmountRaw(
|
||||||
|
cryptoAmount: ethereum!.formatterEthereumAmountToDouble(transaction: transaction),
|
||||||
|
price: price);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,14 @@ import 'dart:convert';
|
||||||
|
|
||||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||||
|
import 'package:cake_wallet/entities/wallet_contact.dart';
|
||||||
import 'package:cake_wallet/exchange/sideshift/sideshift_exchange_provider.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/sideshift/sideshift_request.dart';
|
||||||
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
|
import 'package:cake_wallet/exchange/simpleswap/simpleswap_exchange_provider.dart';
|
||||||
import 'package:cake_wallet/exchange/simpleswap/simpleswap_request.dart';
|
import 'package:cake_wallet/exchange/simpleswap/simpleswap_request.dart';
|
||||||
import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart';
|
import 'package:cake_wallet/exchange/trocador/trocador_exchange_provider.dart';
|
||||||
import 'package:cake_wallet/exchange/trocador/trocador_request.dart';
|
import 'package:cake_wallet/exchange/trocador/trocador_request.dart';
|
||||||
|
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
||||||
import 'package:cw_core/transaction_priority.dart';
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
@ -44,8 +46,14 @@ part 'exchange_view_model.g.dart';
|
||||||
class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel;
|
class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel;
|
||||||
|
|
||||||
abstract class ExchangeViewModelBase with Store {
|
abstract class ExchangeViewModelBase with Store {
|
||||||
ExchangeViewModelBase(this.wallet, this.trades, this._exchangeTemplateStore, this.tradesStore,
|
ExchangeViewModelBase(
|
||||||
this._settingsStore, this.sharedPreferences)
|
this.wallet,
|
||||||
|
this.trades,
|
||||||
|
this._exchangeTemplateStore,
|
||||||
|
this.tradesStore,
|
||||||
|
this._settingsStore,
|
||||||
|
this.sharedPreferences,
|
||||||
|
this.contactListViewModel)
|
||||||
: _cryptoNumberFormat = NumberFormat(),
|
: _cryptoNumberFormat = NumberFormat(),
|
||||||
isFixedRateMode = false,
|
isFixedRateMode = false,
|
||||||
isReceiveAmountEntered = false,
|
isReceiveAmountEntered = false,
|
||||||
|
@ -78,9 +86,10 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
];
|
];
|
||||||
_initialPairBasedOnWallet();
|
_initialPairBasedOnWallet();
|
||||||
|
|
||||||
final Map<String, dynamic> exchangeProvidersSelection =
|
final Map<String, dynamic> exchangeProvidersSelection = json.decode(
|
||||||
json.decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}")
|
sharedPreferences
|
||||||
as Map<String, dynamic>;
|
.getString(PreferencesKey.exchangeProvidersSelection) ??
|
||||||
|
"{}") as Map<String, dynamic>;
|
||||||
|
|
||||||
/// if the provider is not in the user settings (user's first time or newly added provider)
|
/// if the provider is not in the user settings (user's first time or newly added provider)
|
||||||
/// then use its default value decided by us
|
/// then use its default value decided by us
|
||||||
|
@ -93,14 +102,17 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
_setAvailableProviders();
|
_setAvailableProviders();
|
||||||
_calculateBestRate();
|
_calculateBestRate();
|
||||||
|
|
||||||
bestRateSync = Timer.periodic(Duration(seconds: 10), (timer) => _calculateBestRate());
|
bestRateSync =
|
||||||
|
Timer.periodic(Duration(seconds: 10), (timer) => _calculateBestRate());
|
||||||
|
|
||||||
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
|
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
|
||||||
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
|
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
|
||||||
depositAmount = '';
|
depositAmount = '';
|
||||||
receiveAmount = '';
|
receiveAmount = '';
|
||||||
receiveAddress = '';
|
receiveAddress = '';
|
||||||
depositAddress = depositCurrency == wallet.currency ? wallet.walletAddresses.address : '';
|
depositAddress = depositCurrency == wallet.currency
|
||||||
|
? wallet.walletAddresses.address
|
||||||
|
: '';
|
||||||
provider = providersForCurrentPair().first;
|
provider = providersForCurrentPair().first;
|
||||||
final initialProvider = provider;
|
final initialProvider = provider;
|
||||||
provider!.checkIsAvailable().then((bool isAvailable) {
|
provider!.checkIsAvailable().then((bool isAvailable) {
|
||||||
|
@ -111,10 +123,12 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
receiveCurrencies = CryptoCurrency.all
|
receiveCurrencies = CryptoCurrency.all
|
||||||
.where((cryptoCurrency) => !excludeReceiveCurrencies.contains(cryptoCurrency))
|
.where((cryptoCurrency) =>
|
||||||
|
!excludeReceiveCurrencies.contains(cryptoCurrency))
|
||||||
.toList();
|
.toList();
|
||||||
depositCurrencies = CryptoCurrency.all
|
depositCurrencies = CryptoCurrency.all
|
||||||
.where((cryptoCurrency) => !excludeDepositCurrencies.contains(cryptoCurrency))
|
.where((cryptoCurrency) =>
|
||||||
|
!excludeDepositCurrencies.contains(cryptoCurrency))
|
||||||
.toList();
|
.toList();
|
||||||
_defineIsReceiveAmountEditable();
|
_defineIsReceiveAmountEditable();
|
||||||
loadLimits();
|
loadLimits();
|
||||||
|
@ -150,7 +164,8 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
/// initialize with descending comparator
|
/// initialize with descending comparator
|
||||||
/// since we want largest rate first
|
/// since we want largest rate first
|
||||||
final SplayTreeMap<double, ExchangeProvider> _sortedAvailableProviders =
|
final SplayTreeMap<double, ExchangeProvider> _sortedAvailableProviders =
|
||||||
SplayTreeMap<double, ExchangeProvider>((double a, double b) => b.compareTo(a));
|
SplayTreeMap<double, ExchangeProvider>(
|
||||||
|
(double a, double b) => b.compareTo(a));
|
||||||
|
|
||||||
final List<ExchangeProvider> _tradeAvailableProviders = [];
|
final List<ExchangeProvider> _tradeAvailableProviders = [];
|
||||||
|
|
||||||
|
@ -206,7 +221,40 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
SyncStatus get status => wallet.syncStatus;
|
SyncStatus get status => wallet.syncStatus;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
ObservableList<ExchangeTemplate> get templates => _exchangeTemplateStore.templates;
|
ObservableList<ExchangeTemplate> get templates =>
|
||||||
|
_exchangeTemplateStore.templates;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
List<WalletContact> get walletContactsToShow =>
|
||||||
|
contactListViewModel.walletContacts
|
||||||
|
.where((element) =>
|
||||||
|
receiveCurrency == null || element.type == receiveCurrency)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
@action
|
||||||
|
bool checkIfWalletIsAnInternalWallet(String address) {
|
||||||
|
final walletContactList = walletContactsToShow
|
||||||
|
.where((element) => element.address == address)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return walletContactList.isNotEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get shouldDisplayTOTP2FAForExchangesToInternalWallet =>
|
||||||
|
_settingsStore.shouldRequireTOTP2FAForExchangesToInternalWallets;
|
||||||
|
|
||||||
|
//* Still open to further optimize these checks
|
||||||
|
//* It works but can be made better
|
||||||
|
@action
|
||||||
|
bool shouldDisplayTOTP() {
|
||||||
|
final isInternalWallet = checkIfWalletIsAnInternalWallet(receiveAddress);
|
||||||
|
if (isInternalWallet) {
|
||||||
|
return shouldDisplayTOTP2FAForExchangesToInternalWallet;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
TransactionPriority get transactionPriority {
|
TransactionPriority get transactionPriority {
|
||||||
|
@ -229,11 +277,14 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
switch (wallet.type) {
|
switch (wallet.type) {
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return transactionPriority == monero!.getMoneroTransactionPrioritySlow();
|
return transactionPriority ==
|
||||||
|
monero!.getMoneroTransactionPrioritySlow();
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
return transactionPriority == bitcoin!.getBitcoinTransactionPrioritySlow();
|
return transactionPriority ==
|
||||||
|
bitcoin!.getBitcoinTransactionPrioritySlow();
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
return transactionPriority == bitcoin!.getLitecoinTransactionPrioritySlow();
|
return transactionPriority ==
|
||||||
|
bitcoin!.getLitecoinTransactionPrioritySlow();
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -247,6 +298,8 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
|
|
||||||
final SettingsStore _settingsStore;
|
final SettingsStore _settingsStore;
|
||||||
|
|
||||||
|
final ContactListViewModel contactListViewModel;
|
||||||
|
|
||||||
double _bestRate = 0.0;
|
double _bestRate = 0.0;
|
||||||
|
|
||||||
late Timer bestRateSync;
|
late Timer bestRateSync;
|
||||||
|
@ -337,18 +390,20 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _calculateBestRate() async {
|
Future<void> _calculateBestRate() async {
|
||||||
final amount = double.tryParse(isFixedRateMode ? receiveAmount : depositAmount) ?? 1;
|
final amount =
|
||||||
|
double.tryParse(isFixedRateMode ? receiveAmount : depositAmount) ?? 1;
|
||||||
|
|
||||||
final _providers = _tradeAvailableProviders
|
final _providers = _tradeAvailableProviders
|
||||||
.where((element) => !isFixedRateMode || element.supportsFixedRate)
|
.where((element) => !isFixedRateMode || element.supportsFixedRate)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
final result = await Future.wait<double>(_providers.map((element) => element.fetchRate(
|
final result = await Future.wait<double>(_providers.map((element) =>
|
||||||
from: depositCurrency,
|
element.fetchRate(
|
||||||
to: receiveCurrency,
|
from: depositCurrency,
|
||||||
amount: amount,
|
to: receiveCurrency,
|
||||||
isFixedRateMode: isFixedRateMode,
|
amount: amount,
|
||||||
isReceiveAmount: isFixedRateMode)));
|
isFixedRateMode: isFixedRateMode,
|
||||||
|
isReceiveAmount: isFixedRateMode)));
|
||||||
|
|
||||||
_sortedAvailableProviders.clear();
|
_sortedAvailableProviders.clear();
|
||||||
|
|
||||||
|
@ -390,13 +445,14 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final tempLimits =
|
final tempLimits = await provider.fetchLimits(
|
||||||
await provider.fetchLimits(from: from, to: to, isFixedRateMode: isFixedRateMode);
|
from: from, to: to, isFixedRateMode: isFixedRateMode);
|
||||||
|
|
||||||
if (lowestMin != null && (tempLimits.min ?? -1) < lowestMin) {
|
if (lowestMin != null && (tempLimits.min ?? -1) < lowestMin) {
|
||||||
lowestMin = tempLimits.min;
|
lowestMin = tempLimits.min;
|
||||||
}
|
}
|
||||||
if (highestMax != null && (tempLimits.max ?? double.maxFinite) > highestMax) {
|
if (highestMax != null &&
|
||||||
|
(tempLimits.max ?? double.maxFinite) > highestMax) {
|
||||||
highestMax = tempLimits.max;
|
highestMax = tempLimits.max;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -548,8 +604,12 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
isReceiveAmountEntered = false;
|
isReceiveAmountEntered = false;
|
||||||
depositAmount = '';
|
depositAmount = '';
|
||||||
receiveAmount = '';
|
receiveAmount = '';
|
||||||
depositAddress = depositCurrency == wallet.currency ? wallet.walletAddresses.address : '';
|
depositAddress = depositCurrency == wallet.currency
|
||||||
receiveAddress = receiveCurrency == wallet.currency ? wallet.walletAddresses.address : '';
|
? wallet.walletAddresses.address
|
||||||
|
: '';
|
||||||
|
receiveAddress = receiveCurrency == wallet.currency
|
||||||
|
? wallet.walletAddresses.address
|
||||||
|
: '';
|
||||||
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
|
isDepositAddressEnabled = !(depositCurrency == wallet.currency);
|
||||||
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
|
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
|
||||||
isFixedRateMode = false;
|
isFixedRateMode = false;
|
||||||
|
@ -568,7 +628,8 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
final amount = availableBalance - fee;
|
final amount = availableBalance - fee;
|
||||||
changeDepositAmount(amount: bitcoin!.formatterBitcoinAmountToString(amount: amount));
|
changeDepositAmount(
|
||||||
|
amount: bitcoin!.formatterBitcoinAmountToString(amount: amount));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -603,8 +664,9 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
List<ExchangeProvider> _providersForPair(
|
List<ExchangeProvider> _providersForPair(
|
||||||
{required CryptoCurrency from, required CryptoCurrency to}) {
|
{required CryptoCurrency from, required CryptoCurrency to}) {
|
||||||
final providers = providerList
|
final providers = providerList
|
||||||
.where((provider) =>
|
.where((provider) => provider.pairList
|
||||||
provider.pairList.where((pair) => pair.from == from && pair.to == to).isNotEmpty)
|
.where((pair) => pair.from == from && pair.to == to)
|
||||||
|
.isNotEmpty)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
return providers;
|
return providers;
|
||||||
|
@ -688,12 +750,14 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
_bestRate = 0;
|
_bestRate = 0;
|
||||||
_calculateBestRate();
|
_calculateBestRate();
|
||||||
|
|
||||||
final Map<String, dynamic> exchangeProvidersSelection =
|
final Map<String, dynamic> exchangeProvidersSelection = json.decode(
|
||||||
json.decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}")
|
sharedPreferences
|
||||||
as Map<String, dynamic>;
|
.getString(PreferencesKey.exchangeProvidersSelection) ??
|
||||||
|
"{}") as Map<String, dynamic>;
|
||||||
|
|
||||||
for (var provider in providerList) {
|
for (var provider in providerList) {
|
||||||
exchangeProvidersSelection[provider.title] = selectedProviders.contains(provider);
|
exchangeProvidersSelection[provider.title] =
|
||||||
|
selectedProviders.contains(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
sharedPreferences.setString(
|
sharedPreferences.setString(
|
||||||
|
@ -704,15 +768,15 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
|
|
||||||
bool get isAvailableInSelected {
|
bool get isAvailableInSelected {
|
||||||
final providersForPair = providersForCurrentPair();
|
final providersForPair = providersForCurrentPair();
|
||||||
return selectedProviders
|
return selectedProviders.any(
|
||||||
.any((element) => element.isAvailable && providersForPair.contains(element));
|
(element) => element.isAvailable && providersForPair.contains(element));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setAvailableProviders() {
|
void _setAvailableProviders() {
|
||||||
_tradeAvailableProviders.clear();
|
_tradeAvailableProviders.clear();
|
||||||
|
|
||||||
_tradeAvailableProviders.addAll(
|
_tradeAvailableProviders.addAll(selectedProviders
|
||||||
selectedProviders.where((provider) => providersForCurrentPair().contains(provider)));
|
.where((provider) => providersForCurrentPair().contains(provider)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -720,13 +784,16 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
switch (wallet.type) {
|
switch (wallet.type) {
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
_settingsStore.priority[wallet.type] = monero!.getMoneroTransactionPriorityAutomatic();
|
_settingsStore.priority[wallet.type] =
|
||||||
|
monero!.getMoneroTransactionPriorityAutomatic();
|
||||||
break;
|
break;
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
_settingsStore.priority[wallet.type] = bitcoin!.getBitcoinTransactionPriorityMedium();
|
_settingsStore.priority[wallet.type] =
|
||||||
|
bitcoin!.getBitcoinTransactionPriorityMedium();
|
||||||
break;
|
break;
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
_settingsStore.priority[wallet.type] = bitcoin!.getLitecoinTransactionPriorityMedium();
|
_settingsStore.priority[wallet.type] =
|
||||||
|
bitcoin!.getLitecoinTransactionPriorityMedium();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -735,7 +802,9 @@ abstract class ExchangeViewModelBase with Store {
|
||||||
|
|
||||||
void _setProviders() {
|
void _setProviders() {
|
||||||
if (_settingsStore.exchangeStatus == ExchangeApiMode.torOnly) {
|
if (_settingsStore.exchangeStatus == ExchangeApiMode.torOnly) {
|
||||||
providerList = _allProviders.where((provider) => provider.supportsOnionAddress).toList();
|
providerList = _allProviders
|
||||||
|
.where((provider) => provider.supportsOnionAddress)
|
||||||
|
.toList();
|
||||||
} else {
|
} else {
|
||||||
providerList = _allProviders;
|
providerList = _allProviders;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:cake_wallet/core/execution_state.dart';
|
import 'package:cake_wallet/core/execution_state.dart';
|
||||||
|
import 'package:cake_wallet/entities/qr_scanner.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
@ -8,10 +9,12 @@ import 'package:collection/collection.dart';
|
||||||
|
|
||||||
part 'node_create_or_edit_view_model.g.dart';
|
part 'node_create_or_edit_view_model.g.dart';
|
||||||
|
|
||||||
class NodeCreateOrEditViewModel = NodeCreateOrEditViewModelBase with _$NodeCreateOrEditViewModel;
|
class NodeCreateOrEditViewModel = NodeCreateOrEditViewModelBase
|
||||||
|
with _$NodeCreateOrEditViewModel;
|
||||||
|
|
||||||
abstract class NodeCreateOrEditViewModelBase with Store {
|
abstract class NodeCreateOrEditViewModelBase with Store {
|
||||||
NodeCreateOrEditViewModelBase(this._nodeSource, this._walletType, this._settingsStore)
|
NodeCreateOrEditViewModelBase(
|
||||||
|
this._nodeSource, this._walletType, this._settingsStore)
|
||||||
: state = InitialExecutionState(),
|
: state = InitialExecutionState(),
|
||||||
connectionState = InitialExecutionState(),
|
connectionState = InitialExecutionState(),
|
||||||
useSSL = false,
|
useSSL = false,
|
||||||
|
@ -170,4 +173,39 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void setAsCurrent(Node node) => _settingsStore.nodes[_walletType] = node;
|
void setAsCurrent(Node node) => _settingsStore.nodes[_walletType] = node;
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> scanQRCodeForNewNode() async {
|
||||||
|
try {
|
||||||
|
String code = await presentQRScanner();
|
||||||
|
|
||||||
|
if (code.isEmpty) {
|
||||||
|
throw Exception('Unexpected scan QR code value: value is empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
final uri = Uri.tryParse(code);
|
||||||
|
|
||||||
|
if (uri == null) {
|
||||||
|
throw Exception('Unexpected scan QR code value: Value is invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
final userInfo = uri.userInfo.split(':');
|
||||||
|
|
||||||
|
if (userInfo.length < 2) {
|
||||||
|
throw Exception('Unexpected scan QR code value: Value is invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
final rpcUser = userInfo[0];
|
||||||
|
final rpcPassword = userInfo[1];
|
||||||
|
final ipAddress = uri.host;
|
||||||
|
final port = uri.port.toString();
|
||||||
|
|
||||||
|
setAddress(ipAddress);
|
||||||
|
setPassword(rpcPassword);
|
||||||
|
setLogin(rpcUser);
|
||||||
|
setPort(port);
|
||||||
|
} on Exception catch (e) {
|
||||||
|
connectionState = FailureState(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,6 +127,10 @@ abstract class OutputBase with Store {
|
||||||
if (_wallet.type == WalletType.haven) {
|
if (_wallet.type == WalletType.haven) {
|
||||||
return haven!.formatterMoneroAmountToDouble(amount: fee);
|
return haven!.formatterMoneroAmountToDouble(amount: fee);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_wallet.type == WalletType.ethereum) {
|
||||||
|
return ethereum!.formatterEthereumAmountToDouble(amount: BigInt.from(fee));
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e.toString());
|
print(e.toString());
|
||||||
}
|
}
|
||||||
|
@ -137,8 +141,9 @@ abstract class OutputBase with Store {
|
||||||
@computed
|
@computed
|
||||||
String get estimatedFeeFiatAmount {
|
String get estimatedFeeFiatAmount {
|
||||||
try {
|
try {
|
||||||
|
final currency = _wallet.type == WalletType.ethereum ? _wallet.currency : cryptoCurrencyHandler();
|
||||||
final fiat = calculateFiatAmountRaw(
|
final fiat = calculateFiatAmountRaw(
|
||||||
price: _fiatConversationStore.prices[cryptoCurrencyHandler()]!,
|
price: _fiatConversationStore.prices[currency]!,
|
||||||
cryptoAmount: estimatedFee);
|
cryptoAmount: estimatedFee);
|
||||||
return fiat;
|
return fiat;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
|
@ -232,6 +237,9 @@ abstract class OutputBase with Store {
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
maximumFractionDigits = 12;
|
maximumFractionDigits = 12;
|
||||||
break;
|
break;
|
||||||
|
case WalletType.ethereum:
|
||||||
|
maximumFractionDigits = 12;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,7 @@ import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
|
||||||
part 'send_template_view_model.g.dart';
|
part 'send_template_view_model.g.dart';
|
||||||
|
|
||||||
class SendTemplateViewModel = SendTemplateViewModelBase
|
class SendTemplateViewModel = SendTemplateViewModelBase with _$SendTemplateViewModel;
|
||||||
with _$SendTemplateViewModel;
|
|
||||||
|
|
||||||
abstract class SendTemplateViewModelBase with Store {
|
abstract class SendTemplateViewModelBase with Store {
|
||||||
final WalletBase _wallet;
|
final WalletBase _wallet;
|
||||||
|
@ -22,8 +21,8 @@ abstract class SendTemplateViewModelBase with Store {
|
||||||
final SendTemplateStore _sendTemplateStore;
|
final SendTemplateStore _sendTemplateStore;
|
||||||
final FiatConversionStore _fiatConversationStore;
|
final FiatConversionStore _fiatConversationStore;
|
||||||
|
|
||||||
SendTemplateViewModelBase(this._wallet, this._settingsStore,
|
SendTemplateViewModelBase(
|
||||||
this._sendTemplateStore, this._fiatConversationStore)
|
this._wallet, this._settingsStore, this._sendTemplateStore, this._fiatConversationStore)
|
||||||
: recipients = ObservableList<TemplateViewModel>() {
|
: recipients = ObservableList<TemplateViewModel>() {
|
||||||
addRecipient();
|
addRecipient();
|
||||||
}
|
}
|
||||||
|
@ -33,7 +32,6 @@ abstract class SendTemplateViewModelBase with Store {
|
||||||
@action
|
@action
|
||||||
void addRecipient() {
|
void addRecipient() {
|
||||||
recipients.add(TemplateViewModel(
|
recipients.add(TemplateViewModel(
|
||||||
cryptoCurrency: cryptoCurrency,
|
|
||||||
wallet: _wallet,
|
wallet: _wallet,
|
||||||
settingsStore: _settingsStore,
|
settingsStore: _settingsStore,
|
||||||
fiatConversationStore: _fiatConversationStore));
|
fiatConversationStore: _fiatConversationStore));
|
||||||
|
@ -47,11 +45,13 @@ abstract class SendTemplateViewModelBase with Store {
|
||||||
AmountValidator get amountValidator =>
|
AmountValidator get amountValidator =>
|
||||||
AmountValidator(currency: walletTypeToCryptoCurrency(_wallet.type));
|
AmountValidator(currency: walletTypeToCryptoCurrency(_wallet.type));
|
||||||
|
|
||||||
AddressValidator get addressValidator =>
|
AddressValidator get addressValidator => AddressValidator(type: _wallet.currency);
|
||||||
AddressValidator(type: _wallet.currency);
|
|
||||||
|
|
||||||
TemplateValidator get templateValidator => TemplateValidator();
|
TemplateValidator get templateValidator => TemplateValidator();
|
||||||
|
|
||||||
|
bool get hasMultiRecipient =>
|
||||||
|
_wallet.type != WalletType.haven && _wallet.type != WalletType.ethereum;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
CryptoCurrency get cryptoCurrency => _wallet.currency;
|
CryptoCurrency get cryptoCurrency => _wallet.currency;
|
||||||
|
|
||||||
|
@ -68,6 +68,7 @@ abstract class SendTemplateViewModelBase with Store {
|
||||||
void addTemplate(
|
void addTemplate(
|
||||||
{required String name,
|
{required String name,
|
||||||
required bool isCurrencySelected,
|
required bool isCurrencySelected,
|
||||||
|
required String cryptoCurrency,
|
||||||
required String address,
|
required String address,
|
||||||
required String amount,
|
required String amount,
|
||||||
required String amountFiat,
|
required String amountFiat,
|
||||||
|
@ -76,7 +77,7 @@ abstract class SendTemplateViewModelBase with Store {
|
||||||
name: name,
|
name: name,
|
||||||
isCurrencySelected: isCurrencySelected,
|
isCurrencySelected: isCurrencySelected,
|
||||||
address: address,
|
address: address,
|
||||||
cryptoCurrency: cryptoCurrency.title,
|
cryptoCurrency: cryptoCurrency,
|
||||||
fiatCurrency: fiatCurrency,
|
fiatCurrency: fiatCurrency,
|
||||||
amount: amount,
|
amount: amount,
|
||||||
amountFiat: amountFiat,
|
amountFiat: amountFiat,
|
||||||
|
@ -89,4 +90,7 @@ abstract class SendTemplateViewModelBase with Store {
|
||||||
_sendTemplateStore.remove(template: template);
|
_sendTemplateStore.remove(template: template);
|
||||||
updateTemplate();
|
updateTemplate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
|
List<CryptoCurrency> get walletCurrencies => _wallet.balance.keys.toList();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,12 @@ import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/nano/nano.dart';
|
import 'package:cake_wallet/nano/nano.dart';
|
||||||
|
import 'package:cake_wallet/entities/contact_record.dart';
|
||||||
|
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||||
|
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||||
|
import 'package:cake_wallet/entities/wallet_contact.dart';
|
||||||
|
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
||||||
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||||
import 'package:cw_core/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/output.dart';
|
||||||
|
@ -33,8 +39,14 @@ part 'send_view_model.g.dart';
|
||||||
class SendViewModel = SendViewModelBase with _$SendViewModel;
|
class SendViewModel = SendViewModelBase with _$SendViewModel;
|
||||||
|
|
||||||
abstract class SendViewModelBase with Store {
|
abstract class SendViewModelBase with Store {
|
||||||
SendViewModelBase(this._wallet, this._settingsStore, this.sendTemplateViewModel,
|
SendViewModelBase(
|
||||||
this._fiatConversationStore, this.balanceViewModel, this.transactionDescriptionBox)
|
this._wallet,
|
||||||
|
this._settingsStore,
|
||||||
|
this.sendTemplateViewModel,
|
||||||
|
this._fiatConversationStore,
|
||||||
|
this.balanceViewModel,
|
||||||
|
this.contactListViewModel,
|
||||||
|
this.transactionDescriptionBox)
|
||||||
: state = InitialExecutionState(),
|
: state = InitialExecutionState(),
|
||||||
currencies = _wallet.balance.keys.toList(),
|
currencies = _wallet.balance.keys.toList(),
|
||||||
selectedCryptoCurrency = _wallet.currency,
|
selectedCryptoCurrency = _wallet.currency,
|
||||||
|
@ -99,8 +111,11 @@ abstract class SendViewModelBase with Store {
|
||||||
String get pendingTransactionFeeFiatAmount {
|
String get pendingTransactionFeeFiatAmount {
|
||||||
try {
|
try {
|
||||||
if (pendingTransaction != null) {
|
if (pendingTransaction != null) {
|
||||||
|
final currency = walletType == WalletType.ethereum
|
||||||
|
? _wallet.currency
|
||||||
|
: selectedCryptoCurrency;
|
||||||
final fiat = calculateFiatAmount(
|
final fiat = calculateFiatAmount(
|
||||||
price: _fiatConversationStore.prices[selectedCryptoCurrency]!,
|
price: _fiatConversationStore.prices[currency]!,
|
||||||
cryptoAmount: pendingTransaction!.feeFormatted);
|
cryptoAmount: pendingTransaction!.feeFormatted);
|
||||||
return fiat;
|
return fiat;
|
||||||
} else {
|
} else {
|
||||||
|
@ -173,10 +188,9 @@ abstract class SendViewModelBase with Store {
|
||||||
|
|
||||||
List<CryptoCurrency> currencies;
|
List<CryptoCurrency> currencies;
|
||||||
|
|
||||||
bool get hasMultiRecipient => _wallet.type != WalletType.haven;
|
bool get hasYat => outputs.any((out) =>
|
||||||
|
out.isParsedAddress &&
|
||||||
bool get hasYat => outputs
|
out.parsedAddress.parseFrom == ParseFrom.yatRecord);
|
||||||
.any((out) => out.isParsedAddress && out.parsedAddress.parseFrom == ParseFrom.yatRecord);
|
|
||||||
|
|
||||||
WalletType get walletType => _wallet.type;
|
WalletType get walletType => _wallet.type;
|
||||||
|
|
||||||
|
@ -192,10 +206,74 @@ abstract class SendViewModelBase with Store {
|
||||||
final SettingsStore _settingsStore;
|
final SettingsStore _settingsStore;
|
||||||
final SendTemplateViewModel sendTemplateViewModel;
|
final SendTemplateViewModel sendTemplateViewModel;
|
||||||
final BalanceViewModel balanceViewModel;
|
final BalanceViewModel balanceViewModel;
|
||||||
|
final ContactListViewModel contactListViewModel;
|
||||||
final FiatConversionStore _fiatConversationStore;
|
final FiatConversionStore _fiatConversationStore;
|
||||||
final Box<TransactionDescription> transactionDescriptionBox;
|
final Box<TransactionDescription> transactionDescriptionBox;
|
||||||
final bool hasMultipleTokens;
|
final bool hasMultipleTokens;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
List<ContactRecord> get contactsToShow => contactListViewModel.contacts
|
||||||
|
.where((element) => selectedCryptoCurrency == null || element.type == selectedCryptoCurrency)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
@computed
|
||||||
|
List<WalletContact> get walletContactsToShow => contactListViewModel.walletContacts
|
||||||
|
.where((element) => selectedCryptoCurrency == null || element.type == selectedCryptoCurrency)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
@action
|
||||||
|
bool checkIfAddressIsAContact(String address) {
|
||||||
|
final contactList = contactsToShow.where((element) => element.address == address).toList();
|
||||||
|
|
||||||
|
return contactList.isNotEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
bool checkIfWalletIsAnInternalWallet(String address) {
|
||||||
|
final walletContactList =
|
||||||
|
walletContactsToShow.where((element) => element.address == address).toList();
|
||||||
|
|
||||||
|
return walletContactList.isNotEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get shouldDisplayTOTP2FAForContact => _settingsStore.shouldRequireTOTP2FAForSendsToContact;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get shouldDisplayTOTP2FAForNonContact =>
|
||||||
|
_settingsStore.shouldRequireTOTP2FAForSendsToNonContact;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get shouldDisplayTOTP2FAForSendsToInternalWallet =>
|
||||||
|
_settingsStore.shouldRequireTOTP2FAForSendsToInternalWallets;
|
||||||
|
|
||||||
|
//* Still open to further optimize these checks
|
||||||
|
//* It works but can be made better
|
||||||
|
@action
|
||||||
|
bool checkThroughChecksToDisplayTOTP(String address) {
|
||||||
|
final isContact = checkIfAddressIsAContact(address);
|
||||||
|
final isInternalWallet = checkIfWalletIsAnInternalWallet(address);
|
||||||
|
|
||||||
|
if (isContact) {
|
||||||
|
return shouldDisplayTOTP2FAForContact;
|
||||||
|
} else if (isInternalWallet) {
|
||||||
|
return shouldDisplayTOTP2FAForSendsToInternalWallet;
|
||||||
|
} else {
|
||||||
|
return shouldDisplayTOTP2FAForNonContact;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shouldDisplayTotp() {
|
||||||
|
List<bool> conditionsList = [];
|
||||||
|
|
||||||
|
for (var output in outputs) {
|
||||||
|
final show = checkThroughChecksToDisplayTOTP(output.address);
|
||||||
|
conditionsList.add(show);
|
||||||
|
}
|
||||||
|
|
||||||
|
return conditionsList.contains(true);
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> createTransaction() async {
|
Future<void> createTransaction() async {
|
||||||
try {
|
try {
|
||||||
|
@ -316,11 +394,22 @@ abstract class SendViewModelBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isEqualCurrency(String currency) =>
|
bool _isEqualCurrency(String currency) =>
|
||||||
currency.toLowerCase() == _wallet.currency.title.toLowerCase();
|
_wallet.balance.keys.any((e) => currency.toLowerCase() == e.title.toLowerCase());
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void onClose() => _settingsStore.fiatCurrency = fiatFromSettings;
|
void onClose() => _settingsStore.fiatCurrency = fiatFromSettings;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void setFiatCurrency(FiatCurrency fiat) => _settingsStore.fiatCurrency = fiat;
|
void setFiatCurrency(FiatCurrency fiat) =>
|
||||||
|
_settingsStore.fiatCurrency = fiat;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void setSelectedCryptoCurrency(String cryptoCurrency) {
|
||||||
|
try {
|
||||||
|
selectedCryptoCurrency = _wallet.balance.keys
|
||||||
|
.firstWhere((e) => cryptoCurrency.toLowerCase() == e.title.toLowerCase());
|
||||||
|
} catch (e) {
|
||||||
|
selectedCryptoCurrency = _wallet.currency;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,23 +11,20 @@ part 'template_view_model.g.dart';
|
||||||
class TemplateViewModel = TemplateViewModelBase with _$TemplateViewModel;
|
class TemplateViewModel = TemplateViewModelBase with _$TemplateViewModel;
|
||||||
|
|
||||||
abstract class TemplateViewModelBase with Store {
|
abstract class TemplateViewModelBase with Store {
|
||||||
final CryptoCurrency cryptoCurrency;
|
|
||||||
final WalletBase _wallet;
|
final WalletBase _wallet;
|
||||||
final SettingsStore _settingsStore;
|
final SettingsStore _settingsStore;
|
||||||
final FiatConversionStore _fiatConversationStore;
|
final FiatConversionStore _fiatConversationStore;
|
||||||
|
|
||||||
TemplateViewModelBase(
|
TemplateViewModelBase({
|
||||||
{required this.cryptoCurrency,
|
required WalletBase wallet,
|
||||||
required WalletBase wallet,
|
required SettingsStore settingsStore,
|
||||||
required SettingsStore settingsStore,
|
required FiatConversionStore fiatConversationStore,
|
||||||
required FiatConversionStore fiatConversationStore})
|
}) : _wallet = wallet,
|
||||||
: _wallet = wallet,
|
|
||||||
_settingsStore = settingsStore,
|
_settingsStore = settingsStore,
|
||||||
_fiatConversationStore = fiatConversationStore,
|
_fiatConversationStore = fiatConversationStore,
|
||||||
output = Output(wallet, settingsStore, fiatConversationStore,
|
_currency = wallet.currency,
|
||||||
() => wallet.currency) {
|
output = Output(wallet, settingsStore, fiatConversationStore, () => wallet.currency) {
|
||||||
output = Output(
|
output = Output(_wallet, _settingsStore, _fiatConversationStore, () => _currency);
|
||||||
_wallet, _settingsStore, _fiatConversationStore, () => cryptoCurrency);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
|
@ -39,6 +36,9 @@ abstract class TemplateViewModelBase with Store {
|
||||||
@observable
|
@observable
|
||||||
String address = '';
|
String address = '';
|
||||||
|
|
||||||
|
@observable
|
||||||
|
CryptoCurrency _currency;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
bool isCurrencySelected = true;
|
bool isCurrencySelected = true;
|
||||||
|
|
||||||
|
@ -66,8 +66,7 @@ abstract class TemplateViewModelBase with Store {
|
||||||
output.reset();
|
output.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
Template toTemplate(
|
Template toTemplate({required String cryptoCurrency, required String fiatCurrency}) {
|
||||||
{required String cryptoCurrency, required String fiatCurrency}) {
|
|
||||||
return Template(
|
return Template(
|
||||||
isCurrencySelectedRaw: isCurrencySelected,
|
isCurrencySelectedRaw: isCurrencySelected,
|
||||||
nameRaw: name,
|
nameRaw: name,
|
||||||
|
@ -77,4 +76,13 @@ abstract class TemplateViewModelBase with Store {
|
||||||
amountRaw: output.cryptoAmount,
|
amountRaw: output.cryptoAmount,
|
||||||
amountFiatRaw: output.fiatAmount);
|
amountFiatRaw: output.fiatAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void changeSelectedCurrency(CryptoCurrency currency) {
|
||||||
|
isCurrencySelected = true;
|
||||||
|
_currency = currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
|
CryptoCurrency get selectedCurrency => _currency;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// ignore_for_file: prefer_final_fields
|
// ignore_for_file: prefer_final_fields
|
||||||
|
|
||||||
|
import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
import 'package:cake_wallet/utils/totp_utils.dart' as Utils;
|
import 'package:cake_wallet/utils/totp_utils.dart' as Utils;
|
||||||
import 'package:cake_wallet/view_model/auth_state.dart';
|
import 'package:cake_wallet/view_model/auth_state.dart';
|
||||||
|
@ -23,8 +24,11 @@ abstract class Setup2FAViewModelBase with Store {
|
||||||
Setup2FAViewModelBase(this._settingsStore, this._sharedPreferences, this._authService)
|
Setup2FAViewModelBase(this._settingsStore, this._sharedPreferences, this._authService)
|
||||||
: _failureCounter = 0,
|
: _failureCounter = 0,
|
||||||
enteredOTPCode = '',
|
enteredOTPCode = '',
|
||||||
|
unhighlightTabs = false,
|
||||||
|
selected2FASettings = ObservableList<VerboseControlSettings>(),
|
||||||
state = InitialExecutionState() {
|
state = InitialExecutionState() {
|
||||||
_getRandomBase32SecretKey();
|
_getRandomBase32SecretKey();
|
||||||
|
selectCakePreset(selectedCake2FAPreset);
|
||||||
reaction((_) => state, _saveLastAuthTime);
|
reaction((_) => state, _saveLastAuthTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +52,38 @@ abstract class Setup2FAViewModelBase with Store {
|
||||||
@computed
|
@computed
|
||||||
bool get useTOTP2FA => _settingsStore.useTOTP2FA;
|
bool get useTOTP2FA => _settingsStore.useTOTP2FA;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get shouldRequireTOTP2FAForAccessingWallet =>
|
||||||
|
_settingsStore.shouldRequireTOTP2FAForAccessingWallet;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get shouldRequireTOTP2FAForSendsToContact =>
|
||||||
|
_settingsStore.shouldRequireTOTP2FAForSendsToContact;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get shouldRequireTOTP2FAForSendsToNonContact =>
|
||||||
|
_settingsStore.shouldRequireTOTP2FAForSendsToNonContact;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get shouldRequireTOTP2FAForSendsToInternalWallets =>
|
||||||
|
_settingsStore.shouldRequireTOTP2FAForSendsToInternalWallets;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get shouldRequireTOTP2FAForExchangesToInternalWallets =>
|
||||||
|
_settingsStore.shouldRequireTOTP2FAForExchangesToInternalWallets;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get shouldRequireTOTP2FAForAddingContacts =>
|
||||||
|
_settingsStore.shouldRequireTOTP2FAForAddingContacts;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get shouldRequireTOTP2FAForCreatingNewWallets =>
|
||||||
|
_settingsStore.shouldRequireTOTP2FAForCreatingNewWallets;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get shouldRequireTOTP2FAForAllSecurityAndBackupSettings =>
|
||||||
|
_settingsStore.shouldRequireTOTP2FAForAllSecurityAndBackupSettings;
|
||||||
|
|
||||||
void _getRandomBase32SecretKey() {
|
void _getRandomBase32SecretKey() {
|
||||||
final randomBase32Key = Utils.generateRandomBase32SecretKey(16);
|
final randomBase32Key = Utils.generateRandomBase32SecretKey(16);
|
||||||
_setBase32SecretKey(randomBase32Key);
|
_setBase32SecretKey(randomBase32Key);
|
||||||
|
@ -156,4 +192,230 @@ abstract class Setup2FAViewModelBase with Store {
|
||||||
_authService.saveLastAuthTime();
|
_authService.saveLastAuthTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
|
Cake2FAPresetsOptions get selectedCake2FAPreset => _settingsStore.selectedCake2FAPreset;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool unhighlightTabs = false;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
ObservableList<VerboseControlSettings> selected2FASettings;
|
||||||
|
|
||||||
|
//! The code here works, but can be improved
|
||||||
|
//! Still trying out various ways to improve it
|
||||||
|
@action
|
||||||
|
void selectCakePreset(Cake2FAPresetsOptions cake2FAPreset) {
|
||||||
|
// The tabs are ordered in the format [Narrow || Normal || Verbose]
|
||||||
|
// Where Narrow = 0, Normal = 1 and Verbose = 2
|
||||||
|
switch (cake2FAPreset) {
|
||||||
|
case Cake2FAPresetsOptions.narrow:
|
||||||
|
activateCake2FANarrowPreset();
|
||||||
|
break;
|
||||||
|
case Cake2FAPresetsOptions.normal:
|
||||||
|
activateCake2FANormalPreset();
|
||||||
|
break;
|
||||||
|
case Cake2FAPresetsOptions.aggressive:
|
||||||
|
activateCake2FAAggressivePreset();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
activateCake2FANormalPreset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void checkIfTheCurrentSettingMatchesAnyOfThePresets() {
|
||||||
|
final hasNormalPreset = checkIfTheNormalPresetIsPresent();
|
||||||
|
final hasNarrowPreset = checkIfTheNarrowPresetIsPresent();
|
||||||
|
final hasVerbosePreset = checkIfTheVerbosePresetIsPresent();
|
||||||
|
|
||||||
|
if (hasNormalPreset || hasNarrowPreset || hasVerbosePreset) {
|
||||||
|
unhighlightTabs = false;
|
||||||
|
} else {
|
||||||
|
unhighlightTabs = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
bool checkIfTheNormalPresetIsPresent() {
|
||||||
|
final hasContacts = selected2FASettings.contains(VerboseControlSettings.sendsToContacts);
|
||||||
|
final hasNonContacts = selected2FASettings.contains(VerboseControlSettings.sendsToNonContacts);
|
||||||
|
final hasSecurityAndBackup =
|
||||||
|
selected2FASettings.contains(VerboseControlSettings.securityAndBackupSettings);
|
||||||
|
|
||||||
|
final hasSendToInternalWallet =
|
||||||
|
selected2FASettings.contains(VerboseControlSettings.sendsToInternalWallets);
|
||||||
|
|
||||||
|
final hasExchangesToInternalWallet =
|
||||||
|
selected2FASettings.contains(VerboseControlSettings.exchangesToInternalWallets);
|
||||||
|
|
||||||
|
bool isOnlyNormalPresetControlsPresent = selected2FASettings.length == 5;
|
||||||
|
|
||||||
|
return (hasContacts &&
|
||||||
|
hasNonContacts &&
|
||||||
|
hasSecurityAndBackup &&
|
||||||
|
hasSendToInternalWallet &&
|
||||||
|
hasExchangesToInternalWallet &&
|
||||||
|
isOnlyNormalPresetControlsPresent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
bool checkIfTheVerbosePresetIsPresent() {
|
||||||
|
final hasAccessWallets = selected2FASettings.contains(VerboseControlSettings.accessWallet);
|
||||||
|
final hasSecurityAndBackup =
|
||||||
|
selected2FASettings.contains(VerboseControlSettings.securityAndBackupSettings);
|
||||||
|
|
||||||
|
bool isOnlyVerbosePresetControlsPresent = selected2FASettings.length == 2;
|
||||||
|
|
||||||
|
return (hasAccessWallets && hasSecurityAndBackup && isOnlyVerbosePresetControlsPresent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
bool checkIfTheNarrowPresetIsPresent() {
|
||||||
|
final hasNonContacts = selected2FASettings.contains(VerboseControlSettings.sendsToNonContacts);
|
||||||
|
final hasAddContacts = selected2FASettings.contains(VerboseControlSettings.addingContacts);
|
||||||
|
final hasCreateNewWallet =
|
||||||
|
selected2FASettings.contains(VerboseControlSettings.creatingNewWallets);
|
||||||
|
final hasSecurityAndBackup =
|
||||||
|
selected2FASettings.contains(VerboseControlSettings.securityAndBackupSettings);
|
||||||
|
|
||||||
|
bool isOnlyNarrowPresetControlsPresent = selected2FASettings.length == 4;
|
||||||
|
|
||||||
|
return (hasNonContacts &&
|
||||||
|
hasAddContacts &&
|
||||||
|
hasCreateNewWallet &&
|
||||||
|
hasSecurityAndBackup &&
|
||||||
|
isOnlyNarrowPresetControlsPresent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void activateCake2FANormalPreset() {
|
||||||
|
_settingsStore.selectedCake2FAPreset = Cake2FAPresetsOptions.normal;
|
||||||
|
setAllControlsToFalse();
|
||||||
|
switchShouldRequireTOTP2FAForSendsToNonContact(true);
|
||||||
|
switchShouldRequireTOTP2FAForSendsToContact(true);
|
||||||
|
switchShouldRequireTOTP2FAForSendsToInternalWallets(true);
|
||||||
|
switchShouldRequireTOTP2FAForExchangesToInternalWallets(true);
|
||||||
|
switchShouldRequireTOTP2FAForAllSecurityAndBackupSettings(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void activateCake2FANarrowPreset() {
|
||||||
|
_settingsStore.selectedCake2FAPreset = Cake2FAPresetsOptions.narrow;
|
||||||
|
setAllControlsToFalse();
|
||||||
|
switchShouldRequireTOTP2FAForSendsToNonContact(true);
|
||||||
|
switchShouldRequireTOTP2FAForAddingContacts(true);
|
||||||
|
switchShouldRequireTOTP2FAForCreatingNewWallet(true);
|
||||||
|
switchShouldRequireTOTP2FAForAllSecurityAndBackupSettings(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void activateCake2FAAggressivePreset() {
|
||||||
|
_settingsStore.selectedCake2FAPreset = Cake2FAPresetsOptions.aggressive;
|
||||||
|
setAllControlsToFalse();
|
||||||
|
switchShouldRequireTOTP2FAForAccessingWallet(true);
|
||||||
|
switchShouldRequireTOTP2FAForAllSecurityAndBackupSettings(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void setAllControlsToFalse() {
|
||||||
|
switchShouldRequireTOTP2FAForAccessingWallet(false);
|
||||||
|
switchShouldRequireTOTP2FAForSendsToContact(false);
|
||||||
|
switchShouldRequireTOTP2FAForSendsToNonContact(false);
|
||||||
|
switchShouldRequireTOTP2FAForAddingContacts(false);
|
||||||
|
switchShouldRequireTOTP2FAForCreatingNewWallet(false);
|
||||||
|
switchShouldRequireTOTP2FAForExchangesToInternalWallets(false);
|
||||||
|
switchShouldRequireTOTP2FAForSendsToInternalWallets(false);
|
||||||
|
switchShouldRequireTOTP2FAForAllSecurityAndBackupSettings(false);
|
||||||
|
selected2FASettings.clear();
|
||||||
|
unhighlightTabs = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void switchShouldRequireTOTP2FAForAccessingWallet(bool value) {
|
||||||
|
_settingsStore.shouldRequireTOTP2FAForAccessingWallet = value;
|
||||||
|
if (value) {
|
||||||
|
selected2FASettings.add(VerboseControlSettings.accessWallet);
|
||||||
|
} else {
|
||||||
|
selected2FASettings.remove(VerboseControlSettings.accessWallet);
|
||||||
|
}
|
||||||
|
checkIfTheCurrentSettingMatchesAnyOfThePresets();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void switchShouldRequireTOTP2FAForSendsToContact(bool value) {
|
||||||
|
_settingsStore.shouldRequireTOTP2FAForSendsToContact = value;
|
||||||
|
if (value) {
|
||||||
|
selected2FASettings.add(VerboseControlSettings.sendsToContacts);
|
||||||
|
} else {
|
||||||
|
selected2FASettings.remove(VerboseControlSettings.sendsToContacts);
|
||||||
|
}
|
||||||
|
checkIfTheCurrentSettingMatchesAnyOfThePresets();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void switchShouldRequireTOTP2FAForSendsToNonContact(bool value) {
|
||||||
|
_settingsStore.shouldRequireTOTP2FAForSendsToNonContact = value;
|
||||||
|
if (value) {
|
||||||
|
selected2FASettings.add(VerboseControlSettings.sendsToNonContacts);
|
||||||
|
} else {
|
||||||
|
selected2FASettings.remove(VerboseControlSettings.sendsToNonContacts);
|
||||||
|
}
|
||||||
|
checkIfTheCurrentSettingMatchesAnyOfThePresets();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void switchShouldRequireTOTP2FAForSendsToInternalWallets(bool value) {
|
||||||
|
_settingsStore.shouldRequireTOTP2FAForSendsToInternalWallets = value;
|
||||||
|
if (value) {
|
||||||
|
selected2FASettings.add(VerboseControlSettings.sendsToInternalWallets);
|
||||||
|
} else {
|
||||||
|
selected2FASettings.remove(VerboseControlSettings.sendsToInternalWallets);
|
||||||
|
}
|
||||||
|
checkIfTheCurrentSettingMatchesAnyOfThePresets();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void switchShouldRequireTOTP2FAForExchangesToInternalWallets(bool value) {
|
||||||
|
_settingsStore.shouldRequireTOTP2FAForExchangesToInternalWallets = value;
|
||||||
|
if (value) {
|
||||||
|
selected2FASettings.add(VerboseControlSettings.exchangesToInternalWallets);
|
||||||
|
} else {
|
||||||
|
selected2FASettings.remove(VerboseControlSettings.exchangesToInternalWallets);
|
||||||
|
}
|
||||||
|
checkIfTheCurrentSettingMatchesAnyOfThePresets();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void switchShouldRequireTOTP2FAForAddingContacts(bool value) {
|
||||||
|
_settingsStore.shouldRequireTOTP2FAForAddingContacts = value;
|
||||||
|
if (value)
|
||||||
|
selected2FASettings.add(VerboseControlSettings.addingContacts);
|
||||||
|
else {
|
||||||
|
selected2FASettings.remove(VerboseControlSettings.addingContacts);
|
||||||
|
}
|
||||||
|
checkIfTheCurrentSettingMatchesAnyOfThePresets();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void switchShouldRequireTOTP2FAForCreatingNewWallet(bool value) {
|
||||||
|
_settingsStore.shouldRequireTOTP2FAForCreatingNewWallets = value;
|
||||||
|
if (value) {
|
||||||
|
selected2FASettings.add(VerboseControlSettings.creatingNewWallets);
|
||||||
|
} else {
|
||||||
|
selected2FASettings.remove(VerboseControlSettings.creatingNewWallets);
|
||||||
|
}
|
||||||
|
checkIfTheCurrentSettingMatchesAnyOfThePresets();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
void switchShouldRequireTOTP2FAForAllSecurityAndBackupSettings(bool value) {
|
||||||
|
_settingsStore.shouldRequireTOTP2FAForAllSecurityAndBackupSettings = value;
|
||||||
|
if (value)
|
||||||
|
selected2FASettings.add(VerboseControlSettings.securityAndBackupSettings);
|
||||||
|
else {
|
||||||
|
selected2FASettings.remove(VerboseControlSettings.securityAndBackupSettings);
|
||||||
|
}
|
||||||
|
checkIfTheCurrentSettingMatchesAnyOfThePresets();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||||
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
import 'package:cw_core/wallet_base.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||||
|
|
||||||
|
@ -8,9 +11,10 @@ part 'privacy_settings_view_model.g.dart';
|
||||||
class PrivacySettingsViewModel = PrivacySettingsViewModelBase with _$PrivacySettingsViewModel;
|
class PrivacySettingsViewModel = PrivacySettingsViewModelBase with _$PrivacySettingsViewModel;
|
||||||
|
|
||||||
abstract class PrivacySettingsViewModelBase with Store {
|
abstract class PrivacySettingsViewModelBase with Store {
|
||||||
PrivacySettingsViewModelBase(this._settingsStore);
|
PrivacySettingsViewModelBase(this._settingsStore, this._wallet);
|
||||||
|
|
||||||
final SettingsStore _settingsStore;
|
final SettingsStore _settingsStore;
|
||||||
|
final WalletBase _wallet;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
ExchangeApiMode get exchangeStatus => _settingsStore.exchangeStatus;
|
ExchangeApiMode get exchangeStatus => _settingsStore.exchangeStatus;
|
||||||
|
@ -30,8 +34,14 @@ abstract class PrivacySettingsViewModelBase with Store {
|
||||||
@computed
|
@computed
|
||||||
bool get disableSell => _settingsStore.disableSell;
|
bool get disableSell => _settingsStore.disableSell;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get useEtherscan => _settingsStore.useEtherscan;
|
||||||
|
|
||||||
|
bool get canUseEtherscan => _wallet.type == WalletType.ethereum;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void setShouldSaveRecipientAddress(bool value) => _settingsStore.shouldSaveRecipientAddress = value;
|
void setShouldSaveRecipientAddress(bool value) =>
|
||||||
|
_settingsStore.shouldSaveRecipientAddress = value;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void setExchangeApiMode(ExchangeApiMode value) => _settingsStore.exchangeStatus = value;
|
void setExchangeApiMode(ExchangeApiMode value) => _settingsStore.exchangeStatus = value;
|
||||||
|
@ -48,4 +58,9 @@ abstract class PrivacySettingsViewModelBase with Store {
|
||||||
@action
|
@action
|
||||||
void setDisableSell(bool value) => _settingsStore.disableSell = value;
|
void setDisableSell(bool value) => _settingsStore.disableSell = value;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void setUseEtherscan(bool value) {
|
||||||
|
_settingsStore.useEtherscan = value;
|
||||||
|
ethereum!.updateEtherscanUsageState(_wallet, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,10 @@ abstract class SecuritySettingsViewModelBase with Store {
|
||||||
@computed
|
@computed
|
||||||
bool get useTotp2FA => _settingsStore.useTOTP2FA;
|
bool get useTotp2FA => _settingsStore.useTOTP2FA;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get shouldRequireTOTP2FAForAllSecurityAndBackupSettings =>
|
||||||
|
_settingsStore.shouldRequireTOTP2FAForAllSecurityAndBackupSettings;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
PinCodeRequiredDuration get pinCodeRequiredDuration => _settingsStore.pinTimeOutDuration;
|
PinCodeRequiredDuration get pinCodeRequiredDuration => _settingsStore.pinTimeOutDuration;
|
||||||
|
|
||||||
|
|
|
@ -136,6 +136,8 @@ abstract class WalletKeysViewModelBase with Store {
|
||||||
return 'litecoin-wallet';
|
return 'litecoin-wallet';
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return 'haven-wallet';
|
return 'haven-wallet';
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return 'ethereum-wallet';
|
||||||
case WalletType.nano:
|
case WalletType.nano:
|
||||||
return 'nano-wallet';
|
return 'nano-wallet';
|
||||||
case WalletType.banano:
|
case WalletType.banano:
|
||||||
|
|
|
@ -27,6 +27,14 @@ abstract class WalletListViewModelBase with Store {
|
||||||
@observable
|
@observable
|
||||||
ObservableList<WalletListItem> wallets;
|
ObservableList<WalletListItem> wallets;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get shouldRequireTOTP2FAForAccessingWallet =>
|
||||||
|
_appStore.settingsStore.shouldRequireTOTP2FAForAccessingWallet;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get shouldRequireTOTP2FAForCreatingNewWallets =>
|
||||||
|
_appStore.settingsStore.shouldRequireTOTP2FAForCreatingNewWallets;
|
||||||
|
|
||||||
final AppStore _appStore;
|
final AppStore _appStore;
|
||||||
final Box<WalletInfo> _walletInfoSource;
|
final Box<WalletInfo> _walletInfoSource;
|
||||||
final WalletLoadingService _walletLoadingService;
|
final WalletLoadingService _walletLoadingService;
|
||||||
|
@ -38,7 +46,6 @@ abstract class WalletListViewModelBase with Store {
|
||||||
Future<void> loadWallet(WalletListItem walletItem) async {
|
Future<void> loadWallet(WalletListItem walletItem) async {
|
||||||
final wallet =
|
final wallet =
|
||||||
await _walletLoadingService.load(walletItem.type, walletItem.name);
|
await _walletLoadingService.load(walletItem.type, walletItem.name);
|
||||||
|
|
||||||
_appStore.changeCurrentWallet(wallet);
|
_appStore.changeCurrentWallet(wallet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -630,6 +630,18 @@
|
||||||
"setup_totp_recommended": "إعداد TOTP (موصى به)",
|
"setup_totp_recommended": "إعداد TOTP (موصى به)",
|
||||||
"disable_buy": "تعطيل إجراء الشراء",
|
"disable_buy": "تعطيل إجراء الشراء",
|
||||||
"disable_sell": "قم بتعطيل إجراء البيع",
|
"disable_sell": "قم بتعطيل إجراء البيع",
|
||||||
|
"cake_2fa_preset" : " كعكة 2FA مسبقا",
|
||||||
|
"narrow": "ضيق",
|
||||||
|
"normal": "طبيعي",
|
||||||
|
"aggressive": "عنيف",
|
||||||
|
"require_for_assessing_wallet": "تتطلب الوصول إلى المحفظة",
|
||||||
|
"require_for_sends_to_non_contacts" : "تتطلب لارسال لغير جهات الاتصال",
|
||||||
|
"require_for_sends_to_contacts" : "تتطلب لارسال جهات الاتصال",
|
||||||
|
"require_for_sends_to_internal_wallets" : "تتطلب عمليات الإرسال إلى المحافظ الداخلية",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "تتطلب عمليات التبادل إلى المحافظ الداخلية",
|
||||||
|
"require_for_adding_contacts" : "تتطلب إضافة جهات اتصال",
|
||||||
|
"require_for_creating_new_wallets" : "تتطلب إنشاء محافظ جديدة",
|
||||||
|
"require_for_all_security_and_backup_settings" : "مطلوب لجميع إعدادات الأمان والنسخ الاحتياطي",
|
||||||
"available_balance_description": "الرصيد المتاح هو الرصيد الذي يمكنك إنفاقه أو تحويله إلى محفظة أخرى. يتم تجميد الرصيد المتاح للمعاملات الصادرة والمعاملات الواردة غير المؤكدة.",
|
"available_balance_description": "الرصيد المتاح هو الرصيد الذي يمكنك إنفاقه أو تحويله إلى محفظة أخرى. يتم تجميد الرصيد المتاح للمعاملات الصادرة والمعاملات الواردة غير المؤكدة.",
|
||||||
"syncing_wallet_alert_title": "محفظتك تتم مزامنتها",
|
"syncing_wallet_alert_title": "محفظتك تتم مزامنتها",
|
||||||
"syncing_wallet_alert_content": "قد لا يكتمل رصيدك وقائمة المعاملات الخاصة بك حتى تظهر عبارة “SYNCHRONIZED“ في الأعلى. انقر / اضغط لمعرفة المزيد.",
|
"syncing_wallet_alert_content": "قد لا يكتمل رصيدك وقائمة المعاملات الخاصة بك حتى تظهر عبارة “SYNCHRONIZED“ في الأعلى. انقر / اضغط لمعرفة المزيد.",
|
||||||
|
@ -654,7 +666,8 @@
|
||||||
"balance_page": "صفحة التوازن",
|
"balance_page": "صفحة التوازن",
|
||||||
"share": "يشارك",
|
"share": "يشارك",
|
||||||
"slidable": "قابل للانزلاق",
|
"slidable": "قابل للانزلاق",
|
||||||
|
"etherscan_history": "Etherscan تاريخ",
|
||||||
"template_name": "اسم القالب",
|
"template_name": "اسم القالب",
|
||||||
"change_rep": "ﺏﻭﺪﻨﻣ ﺮﻴﻴﻐﺗ",
|
"change_rep": "ﺏﻭﺪﻨﻣ ﺮﻴﻴﻐﺗ",
|
||||||
"change_rep_message": " ؟ﻦﻴﻠﺜﻤﻤﻟﺍ ﺮﻴﻴﻐﺗ ﺪﻳﺮﺗ ﻚﻧﺃ ﺪﻛﺄﺘﻣ ﺖﻧﺃ ﻞﻫ"
|
"change_rep_message": "؟ﻦﻴﻠﺜﻤﻤﻟﺍ ﺮﻴﻴﻐﺗ ﺪﻳﺮﺗ ﻚﻧﺃ ﺪﻛﺄﺘﻣ ﺖﻧﺃ ﻞﻫ"
|
||||||
}
|
}
|
||||||
|
|
|
@ -626,6 +626,18 @@
|
||||||
"setup_totp_recommended": "Настройка на TOTP (препоръчително)",
|
"setup_totp_recommended": "Настройка на TOTP (препоръчително)",
|
||||||
"disable_buy": "Деактивирайте действието за покупка",
|
"disable_buy": "Деактивирайте действието за покупка",
|
||||||
"disable_sell": "Деактивирайте действието за продажба",
|
"disable_sell": "Деактивирайте действието за продажба",
|
||||||
|
"cake_2fa_preset" : "Торта 2FA Preset",
|
||||||
|
"narrow": "Тесен",
|
||||||
|
"normal": "нормално",
|
||||||
|
"aggressive": "Прекалено усърден",
|
||||||
|
"require_for_assessing_wallet": "Изискване за достъп до портфейла",
|
||||||
|
"require_for_sends_to_non_contacts" : "Изискване за изпращане до лица без контакт",
|
||||||
|
"require_for_sends_to_contacts" : "Изискване за изпращане до контакти",
|
||||||
|
"require_for_sends_to_internal_wallets" : "Изискване за изпращане до вътрешни портфейли",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "Изискване за обмен към вътрешни портфейли",
|
||||||
|
"require_for_adding_contacts" : "Изисква се за добавяне на контакти",
|
||||||
|
"require_for_creating_new_wallets" : "Изискване за създаване на нови портфейли",
|
||||||
|
"require_for_all_security_and_backup_settings" : "Изисква се за всички настройки за сигурност и архивиране",
|
||||||
"available_balance_description": "Това е балансът, който можете да използвате за покупка на криптовалути. Това не включва замразените средства.",
|
"available_balance_description": "Това е балансът, който можете да използвате за покупка на криптовалути. Това не включва замразените средства.",
|
||||||
"syncing_wallet_alert_title": "Вашият портфейл се синхронизира",
|
"syncing_wallet_alert_title": "Вашият портфейл се синхронизира",
|
||||||
"syncing_wallet_alert_content": "Списъкът ви с баланс и транзакции може да не е пълен, докато в горната част не пише „СИНХРОНИЗИРАН“. Кликнете/докоснете, за да научите повече.",
|
"syncing_wallet_alert_content": "Списъкът ви с баланс и транзакции може да не е пълен, докато в горната част не пише „СИНХРОНИЗИРАН“. Кликнете/докоснете, за да научите повече.",
|
||||||
|
@ -650,6 +662,7 @@
|
||||||
"balance_page": "Страница за баланс",
|
"balance_page": "Страница за баланс",
|
||||||
"share": "Дял",
|
"share": "Дял",
|
||||||
"slidable": "Плъзгащ се",
|
"slidable": "Плъзгащ се",
|
||||||
|
"etherscan_history": "История на Etherscan",
|
||||||
"template_name": "Име на шаблон",
|
"template_name": "Име на шаблон",
|
||||||
"change_rep": "Смяна на представител",
|
"change_rep": "Смяна на представител",
|
||||||
"change_rep_message": "Сигурни ли сте, че искате да смените представителите?"
|
"change_rep_message": "Сигурни ли сте, че искате да смените представителите?"
|
||||||
|
|
|
@ -626,6 +626,18 @@
|
||||||
"setup_totp_recommended": "Nastavit TOTP (doporučeno)",
|
"setup_totp_recommended": "Nastavit TOTP (doporučeno)",
|
||||||
"disable_buy": "Zakázat akci nákupu",
|
"disable_buy": "Zakázat akci nákupu",
|
||||||
"disable_sell": "Zakázat akci prodeje",
|
"disable_sell": "Zakázat akci prodeje",
|
||||||
|
"cake_2fa_preset" : "Předvolba Cake 2FA",
|
||||||
|
"narrow": "Úzký",
|
||||||
|
"normal": "Normální",
|
||||||
|
"aggressive": "Agresivní",
|
||||||
|
"require_for_assessing_wallet": "Vyžadovat pro přístup k peněžence",
|
||||||
|
"require_for_sends_to_non_contacts" : "Vyžadovat pro odesílání nekontaktním osobám",
|
||||||
|
"require_for_sends_to_contacts" : "Vyžadovat pro odeslání kontaktům",
|
||||||
|
"require_for_sends_to_internal_wallets" : "Vyžadovat pro odesílání do interních peněženek",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "Vyžadovat pro výměny do interních peněženek",
|
||||||
|
"require_for_adding_contacts" : "Vyžadovat pro přidání kontaktů",
|
||||||
|
"require_for_creating_new_wallets" : "Vyžadovat pro vytváření nových peněženek",
|
||||||
|
"require_for_all_security_and_backup_settings" : "Vyžadovat všechna nastavení zabezpečení a zálohování",
|
||||||
"available_balance_description": "Dostupná částka je částka, kterou můžete okamžitě utratit. Zmrazená částka je částka, která ještě není k dispozici, protože ještě nebyla potvrzena síťovým protokolem.",
|
"available_balance_description": "Dostupná částka je částka, kterou můžete okamžitě utratit. Zmrazená částka je částka, která ještě není k dispozici, protože ještě nebyla potvrzena síťovým protokolem.",
|
||||||
"syncing_wallet_alert_title": "Vaše peněženka se synchronizuje",
|
"syncing_wallet_alert_title": "Vaše peněženka se synchronizuje",
|
||||||
"syncing_wallet_alert_content": "Váš seznam zůstatků a transakcí nemusí být úplný, dokud nebude nahoře uvedeno „SYNCHRONIZOVANÉ“. Kliknutím/klepnutím se dozvíte více.",
|
"syncing_wallet_alert_content": "Váš seznam zůstatků a transakcí nemusí být úplný, dokud nebude nahoře uvedeno „SYNCHRONIZOVANÉ“. Kliknutím/klepnutím se dozvíte více.",
|
||||||
|
@ -650,6 +662,7 @@
|
||||||
"balance_page": "Stránka zůstatku",
|
"balance_page": "Stránka zůstatku",
|
||||||
"share": "Podíl",
|
"share": "Podíl",
|
||||||
"slidable": "Posuvné",
|
"slidable": "Posuvné",
|
||||||
|
"etherscan_history": "Historie Etherscanu",
|
||||||
"template_name": "Název šablony",
|
"template_name": "Název šablony",
|
||||||
"change_rep": "Změna zástupce",
|
"change_rep": "Změna zástupce",
|
||||||
"change_rep_message": "Jste si jisti, že chcete změnit zástupce?"
|
"change_rep_message": "Jste si jisti, že chcete změnit zástupce?"
|
||||||
|
|
|
@ -632,6 +632,18 @@
|
||||||
"setup_totp_recommended": "TOTP einrichten (empfohlen)",
|
"setup_totp_recommended": "TOTP einrichten (empfohlen)",
|
||||||
"disable_buy": "Kaufaktion deaktivieren",
|
"disable_buy": "Kaufaktion deaktivieren",
|
||||||
"disable_sell": "Verkaufsaktion deaktivieren",
|
"disable_sell": "Verkaufsaktion deaktivieren",
|
||||||
|
"cake_2fa_preset" : "Kuchen 2FA-Voreinstellung",
|
||||||
|
"narrow": "Eng",
|
||||||
|
"normal": "Normal",
|
||||||
|
"aggressive": "Übereifrig",
|
||||||
|
"require_for_assessing_wallet": "Für den Zugriff auf die Wallet erforderlich",
|
||||||
|
"require_for_sends_to_non_contacts" : "Erforderlich für Versendungen an Nichtkontakte",
|
||||||
|
"require_for_sends_to_contacts" : "Erforderlich für Versendungen an Kontakte",
|
||||||
|
"require_for_sends_to_internal_wallets" : "Erforderlich für Sendungen an interne Wallets",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "Erforderlich für den Umtausch in interne Wallets",
|
||||||
|
"require_for_adding_contacts" : "Erforderlich zum Hinzufügen von Kontakten",
|
||||||
|
"require_for_creating_new_wallets" : "Erforderlich zum Erstellen neuer Wallets",
|
||||||
|
"require_for_all_security_and_backup_settings" : "Für alle Sicherheits- und Sicherungseinstellungen erforderlich",
|
||||||
"available_balance_description": "Verfügbarer Saldo ist der Betrag, den Sie sofort ausgeben können. Dieser Betrag kann sich ändern, wenn Sie eine Transaktion senden oder empfangen.",
|
"available_balance_description": "Verfügbarer Saldo ist der Betrag, den Sie sofort ausgeben können. Dieser Betrag kann sich ändern, wenn Sie eine Transaktion senden oder empfangen.",
|
||||||
"syncing_wallet_alert_title": "Ihr Wallet wird synchronisiert",
|
"syncing_wallet_alert_title": "Ihr Wallet wird synchronisiert",
|
||||||
"syncing_wallet_alert_content": "Ihr Kontostand und Ihre Transaktionsliste sind möglicherweise erst vollständig, wenn oben „SYNCHRONISIERT“ steht. Klicken/tippen Sie, um mehr zu erfahren.",
|
"syncing_wallet_alert_content": "Ihr Kontostand und Ihre Transaktionsliste sind möglicherweise erst vollständig, wenn oben „SYNCHRONISIERT“ steht. Klicken/tippen Sie, um mehr zu erfahren.",
|
||||||
|
@ -656,6 +668,7 @@
|
||||||
"balance_page": "Balance-Seite",
|
"balance_page": "Balance-Seite",
|
||||||
"share": "Aktie",
|
"share": "Aktie",
|
||||||
"slidable": "Verschiebbar",
|
"slidable": "Verschiebbar",
|
||||||
|
"etherscan_history": "Etherscan-Geschichte",
|
||||||
"template_name": "Vorlagenname",
|
"template_name": "Vorlagenname",
|
||||||
"change_rep": "Change-Beauftragter",
|
"change_rep": "Change-Beauftragter",
|
||||||
"change_rep_message": "Sind Sie sicher, dass Sie den Vertreter wechseln möchten?"
|
"change_rep_message": "Sind Sie sicher, dass Sie den Vertreter wechseln möchten?"
|
||||||
|
|
|
@ -632,6 +632,18 @@
|
||||||
"setup_totp_recommended": "Set up TOTP (Recommended)",
|
"setup_totp_recommended": "Set up TOTP (Recommended)",
|
||||||
"disable_buy": "Disable buy action",
|
"disable_buy": "Disable buy action",
|
||||||
"disable_sell": "Disable sell action",
|
"disable_sell": "Disable sell action",
|
||||||
|
"cake_2fa_preset" : "Cake 2FA Preset",
|
||||||
|
"narrow": "Narrow",
|
||||||
|
"normal": "Normal",
|
||||||
|
"aggressive": "Aggressive",
|
||||||
|
"require_for_assessing_wallet": "Require for accessing wallet",
|
||||||
|
"require_for_sends_to_non_contacts" : "Require for sends to non-contacts",
|
||||||
|
"require_for_sends_to_contacts" : "Require for sends to contacts",
|
||||||
|
"require_for_sends_to_internal_wallets" : "Require for sends to internal wallets",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "Require for exchanges to internal wallets",
|
||||||
|
"require_for_adding_contacts" : "Require for adding contacts",
|
||||||
|
"require_for_creating_new_wallets" : "Require for creating new wallets",
|
||||||
|
"require_for_all_security_and_backup_settings" : "Require for all security and backup settings",
|
||||||
"available_balance_description": "The “Available Balance” or “Confirmed Balance” are funds that can be spent immediately. If funds appear in the lower balance but not the top balance, then you must wait a few minutes for the incoming funds to get more network confirmations. After they get more confirmations, they will be spendable.",
|
"available_balance_description": "The “Available Balance” or “Confirmed Balance” are funds that can be spent immediately. If funds appear in the lower balance but not the top balance, then you must wait a few minutes for the incoming funds to get more network confirmations. After they get more confirmations, they will be spendable.",
|
||||||
"syncing_wallet_alert_title": "Your wallet is syncing",
|
"syncing_wallet_alert_title": "Your wallet is syncing",
|
||||||
"syncing_wallet_alert_content": "Your balance and transaction list may not be complete until it says “SYNCHRONIZED” at the top. Click/tap to learn more.",
|
"syncing_wallet_alert_content": "Your balance and transaction list may not be complete until it says “SYNCHRONIZED” at the top. Click/tap to learn more.",
|
||||||
|
@ -656,6 +668,7 @@
|
||||||
"balance_page": "Balance Page",
|
"balance_page": "Balance Page",
|
||||||
"share": "Share",
|
"share": "Share",
|
||||||
"slidable": "Slidable",
|
"slidable": "Slidable",
|
||||||
|
"etherscan_history": "Etherscan history",
|
||||||
"template_name": "Template Name",
|
"template_name": "Template Name",
|
||||||
"change_rep": "Change Representative",
|
"change_rep": "Change Representative",
|
||||||
"change_rep_message": "Are you sure that you want to change representatives?"
|
"change_rep_message": "Are you sure that you want to change representatives?"
|
||||||
|
|
|
@ -632,6 +632,18 @@
|
||||||
"setup_totp_recommended": "Configurar TOTP (Recomendado)",
|
"setup_totp_recommended": "Configurar TOTP (Recomendado)",
|
||||||
"disable_buy": "Desactivar acción de compra",
|
"disable_buy": "Desactivar acción de compra",
|
||||||
"disable_sell": "Desactivar acción de venta",
|
"disable_sell": "Desactivar acción de venta",
|
||||||
|
"cake_2fa_preset" : "Pastel 2FA preestablecido",
|
||||||
|
"narrow": "Angosto",
|
||||||
|
"normal": "Normal",
|
||||||
|
"aggressive": "Demasiado entusiasta",
|
||||||
|
"require_for_assessing_wallet": "Requerido para acceder a la billetera",
|
||||||
|
"require_for_sends_to_non_contacts" : "Requerido para envíos a no contactos",
|
||||||
|
"require_for_sends_to_contacts" : "Requerir para envíos a contactos",
|
||||||
|
"require_for_sends_to_internal_wallets" : "Requerido para envíos a billeteras internas",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "Requerido para intercambios a billeteras internas",
|
||||||
|
"require_for_adding_contacts" : "Requerido para agregar contactos",
|
||||||
|
"require_for_creating_new_wallets" : "Requerido para crear nuevas billeteras",
|
||||||
|
"require_for_all_security_and_backup_settings" : "Requerido para todas las configuraciones de seguridad y copia de seguridad",
|
||||||
"available_balance_description": "Su saldo disponible es la cantidad de fondos que puede gastar. Los fondos que se muestran aquí se pueden gastar inmediatamente.",
|
"available_balance_description": "Su saldo disponible es la cantidad de fondos que puede gastar. Los fondos que se muestran aquí se pueden gastar inmediatamente.",
|
||||||
"syncing_wallet_alert_title": "Tu billetera se está sincronizando",
|
"syncing_wallet_alert_title": "Tu billetera se está sincronizando",
|
||||||
"syncing_wallet_alert_content": "Es posible que su lista de saldo y transacciones no esté completa hasta que diga \"SINCRONIZADO\" en la parte superior. Haga clic/toque para obtener más información.",
|
"syncing_wallet_alert_content": "Es posible que su lista de saldo y transacciones no esté completa hasta que diga \"SINCRONIZADO\" en la parte superior. Haga clic/toque para obtener más información.",
|
||||||
|
@ -656,6 +668,7 @@
|
||||||
"balance_page": "Página de saldo",
|
"balance_page": "Página de saldo",
|
||||||
"share": "Compartir",
|
"share": "Compartir",
|
||||||
"slidable": "deslizable",
|
"slidable": "deslizable",
|
||||||
|
"etherscan_history": "historia de etherscan",
|
||||||
"template_name": "Nombre de la plantilla",
|
"template_name": "Nombre de la plantilla",
|
||||||
"change_rep": "Representante de cambio",
|
"change_rep": "Representante de cambio",
|
||||||
"change_rep_message": "¿Estás seguro de que quieres cambiar de representante?"
|
"change_rep_message": "¿Estás seguro de que quieres cambiar de representante?"
|
||||||
|
|
|
@ -632,6 +632,18 @@
|
||||||
"setup_totp_recommended": "Configurer TOTP (recommandé)",
|
"setup_totp_recommended": "Configurer TOTP (recommandé)",
|
||||||
"disable_buy": "Désactiver l'action d'achat",
|
"disable_buy": "Désactiver l'action d'achat",
|
||||||
"disable_sell": "Désactiver l'action de vente",
|
"disable_sell": "Désactiver l'action de vente",
|
||||||
|
"cake_2fa_preset" : "Gâteau 2FA prédéfini",
|
||||||
|
"narrow": "Étroit",
|
||||||
|
"normal": "Normal",
|
||||||
|
"aggressive": "Trop zélé",
|
||||||
|
"require_for_assessing_wallet": "Nécessaire pour accéder au portefeuille",
|
||||||
|
"require_for_sends_to_non_contacts" : "Exiger pour les envois à des non-contacts",
|
||||||
|
"require_for_sends_to_contacts" : "Exiger pour les envois aux contacts",
|
||||||
|
"require_for_sends_to_internal_wallets" : "Exiger pour les envois vers des portefeuilles internes",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "Exiger pour les échanges vers des portefeuilles internes",
|
||||||
|
"require_for_adding_contacts" : "Requis pour ajouter des contacts",
|
||||||
|
"require_for_creating_new_wallets" : "Nécessaire pour créer de nouveaux portefeuilles",
|
||||||
|
"require_for_all_security_and_backup_settings" : "Exiger pour tous les paramètres de sécurité et de sauvegarde",
|
||||||
"available_balance_description": "Le solde disponible est le montant que vous pouvez dépenser immédiatement. Il est calculé en soustrayant le solde gelé du solde total.",
|
"available_balance_description": "Le solde disponible est le montant que vous pouvez dépenser immédiatement. Il est calculé en soustrayant le solde gelé du solde total.",
|
||||||
"syncing_wallet_alert_title": "Votre portefeuille est en cours de synchronisation",
|
"syncing_wallet_alert_title": "Votre portefeuille est en cours de synchronisation",
|
||||||
"syncing_wallet_alert_content": "Votre solde et votre liste de transactions peuvent ne pas être complets tant qu'il n'y a pas « SYNCHRONISÉ » en haut. Cliquez/appuyez pour en savoir plus.",
|
"syncing_wallet_alert_content": "Votre solde et votre liste de transactions peuvent ne pas être complets tant qu'il n'y a pas « SYNCHRONISÉ » en haut. Cliquez/appuyez pour en savoir plus.",
|
||||||
|
@ -656,6 +668,7 @@
|
||||||
"balance_page": "Page Solde",
|
"balance_page": "Page Solde",
|
||||||
"share": "Partager",
|
"share": "Partager",
|
||||||
"slidable": "Glissable",
|
"slidable": "Glissable",
|
||||||
|
"etherscan_history": "Historique d'Etherscan",
|
||||||
"template_name": "Nom du modèle",
|
"template_name": "Nom du modèle",
|
||||||
"change_rep": "Changer de représentant",
|
"change_rep": "Changer de représentant",
|
||||||
"change_rep_message": "Êtes-vous sûr de vouloir changer de représentant ?"
|
"change_rep_message": "Êtes-vous sûr de vouloir changer de représentant ?"
|
||||||
|
|
|
@ -612,6 +612,18 @@
|
||||||
"prevent_screenshots": "Fada lambobi da jarrabobi na kayan lambobi",
|
"prevent_screenshots": "Fada lambobi da jarrabobi na kayan lambobi",
|
||||||
"disable_buy": "Kashe alama",
|
"disable_buy": "Kashe alama",
|
||||||
"disable_sell": "Kashe karbuwa",
|
"disable_sell": "Kashe karbuwa",
|
||||||
|
"cake_2fa_preset" : "Cake 2FA saiti",
|
||||||
|
"narrow": "kunkuntar",
|
||||||
|
"normal": "Na al'ada",
|
||||||
|
"aggressive": "Mai tsananin kishi",
|
||||||
|
"require_for_assessing_wallet": "Bukatar samun damar walat",
|
||||||
|
"require_for_sends_to_non_contacts" : "Bukatar aika zuwa waɗanda ba lambobin sadarwa ba",
|
||||||
|
"require_for_sends_to_contacts" : "Bukatar aika zuwa lambobin sadarwa",
|
||||||
|
"require_for_sends_to_internal_wallets" : "Bukatar aika zuwa wallet na ciki",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "Bukatar musanya zuwa wallet na ciki",
|
||||||
|
"require_for_adding_contacts" : "Bukatar ƙara lambobin sadarwa",
|
||||||
|
"require_for_creating_new_wallets" : "Bukatar ƙirƙirar sabbin wallet",
|
||||||
|
"require_for_all_security_and_backup_settings" : "Bukatar duk tsaro da saitunan wariyar ajiya",
|
||||||
"available_balance_description": "Ma'auni mai samuwa” ko ”,Tabbataccen Ma'auni”, kudade ne da za a iya kashewa nan da nan. Idan kudade sun bayyana a cikin ƙananan ma'auni amma ba babban ma'auni ba, to dole ne ku jira 'yan mintoci kaɗan don kudaden shiga don samun ƙarin tabbaci na hanyar sadarwa. Bayan sun sami ƙarin tabbaci, za a kashe su.",
|
"available_balance_description": "Ma'auni mai samuwa” ko ”,Tabbataccen Ma'auni”, kudade ne da za a iya kashewa nan da nan. Idan kudade sun bayyana a cikin ƙananan ma'auni amma ba babban ma'auni ba, to dole ne ku jira 'yan mintoci kaɗan don kudaden shiga don samun ƙarin tabbaci na hanyar sadarwa. Bayan sun sami ƙarin tabbaci, za a kashe su.",
|
||||||
"syncing_wallet_alert_title": "Walat ɗin ku yana aiki tare",
|
"syncing_wallet_alert_title": "Walat ɗin ku yana aiki tare",
|
||||||
"syncing_wallet_alert_content": "Ma'aunin ku da lissafin ma'amala bazai cika ba har sai an ce \"SYNCHRONIZED\" a saman. Danna/matsa don ƙarin koyo.",
|
"syncing_wallet_alert_content": "Ma'aunin ku da lissafin ma'amala bazai cika ba har sai an ce \"SYNCHRONIZED\" a saman. Danna/matsa don ƙarin koyo.",
|
||||||
|
@ -636,6 +648,7 @@
|
||||||
"balance_page": "Ma'auni Page",
|
"balance_page": "Ma'auni Page",
|
||||||
"share": "Raba",
|
"share": "Raba",
|
||||||
"slidable": "Mai iya zamewa",
|
"slidable": "Mai iya zamewa",
|
||||||
|
"etherscan_history": "Etherscan tarihin kowane zamani",
|
||||||
"template_name": "Sunan Samfura",
|
"template_name": "Sunan Samfura",
|
||||||
"change_rep": "Canza Wakili",
|
"change_rep": "Canza Wakili",
|
||||||
"change_rep_message": "Shin kun tabbata kuna son canza wakilai?"
|
"change_rep_message": "Shin kun tabbata kuna son canza wakilai?"
|
||||||
|
|
|
@ -632,6 +632,18 @@
|
||||||
"setup_totp_recommended": "टीओटीपी सेट अप करें (अनुशंसित)",
|
"setup_totp_recommended": "टीओटीपी सेट अप करें (अनुशंसित)",
|
||||||
"disable_buy": "खरीद कार्रवाई अक्षम करें",
|
"disable_buy": "खरीद कार्रवाई अक्षम करें",
|
||||||
"disable_sell": "बेचने की कार्रवाई अक्षम करें",
|
"disable_sell": "बेचने की कार्रवाई अक्षम करें",
|
||||||
|
"cake_2fa_preset" : "केक 2एफए प्रीसेट",
|
||||||
|
"narrow": "सँकरा",
|
||||||
|
"normal": "सामान्य",
|
||||||
|
"aggressive": "ज्यादा",
|
||||||
|
"require_for_assessing_wallet": "वॉलेट तक पहुँचने के लिए आवश्यकता है",
|
||||||
|
"require_for_sends_to_non_contacts" : "गैर-संपर्कों को भेजने की आवश्यकता",
|
||||||
|
"require_for_sends_to_contacts" : "संपर्कों को भेजने के लिए आवश्यक है",
|
||||||
|
"require_for_sends_to_internal_wallets" : "आंतरिक वॉलेट में भेजने की आवश्यकता है",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "आंतरिक वॉलेट में आदान-प्रदान की आवश्यकता है",
|
||||||
|
"require_for_adding_contacts" : "संपर्क जोड़ने के लिए आवश्यकता है",
|
||||||
|
"require_for_creating_new_wallets" : "नए वॉलेट बनाने की आवश्यकता है",
|
||||||
|
"require_for_all_security_and_backup_settings" : "सभी सुरक्षा और बैकअप सेटिंग्स की आवश्यकता है",
|
||||||
"available_balance_description": "उपलब्ध शेष या ”पुष्टिकृत शेष”, वे धनराशि हैं जिन्हें तुरंत खर्च किया जा सकता है। यदि फंड निचले बैलेंस में दिखाई देते हैं, लेकिन शीर्ष बैलेंस में नहीं, तो आपको आने वाले फंड के लिए अधिक नेटवर्क पुष्टिकरण प्राप्त करने के लिए कुछ मिनट इंतजार करना होगा। अधिक पुष्टि मिलने के बाद, वे खर्च करने योग्य हो जाएंगे।",
|
"available_balance_description": "उपलब्ध शेष या ”पुष्टिकृत शेष”, वे धनराशि हैं जिन्हें तुरंत खर्च किया जा सकता है। यदि फंड निचले बैलेंस में दिखाई देते हैं, लेकिन शीर्ष बैलेंस में नहीं, तो आपको आने वाले फंड के लिए अधिक नेटवर्क पुष्टिकरण प्राप्त करने के लिए कुछ मिनट इंतजार करना होगा। अधिक पुष्टि मिलने के बाद, वे खर्च करने योग्य हो जाएंगे।",
|
||||||
"syncing_wallet_alert_title": "आपका वॉलेट सिंक हो रहा है",
|
"syncing_wallet_alert_title": "आपका वॉलेट सिंक हो रहा है",
|
||||||
"syncing_wallet_alert_content": "आपकी शेष राशि और लेनदेन सूची तब तक पूरी नहीं हो सकती जब तक कि शीर्ष पर \"सिंक्रनाइज़्ड\" न लिखा हो। अधिक जानने के लिए क्लिक/टैप करें।",
|
"syncing_wallet_alert_content": "आपकी शेष राशि और लेनदेन सूची तब तक पूरी नहीं हो सकती जब तक कि शीर्ष पर \"सिंक्रनाइज़्ड\" न लिखा हो। अधिक जानने के लिए क्लिक/टैप करें।",
|
||||||
|
@ -656,6 +668,7 @@
|
||||||
"balance_page": "बैलेंस पेज",
|
"balance_page": "बैलेंस पेज",
|
||||||
"share": "शेयर करना",
|
"share": "शेयर करना",
|
||||||
"slidable": "फिसलने लायक",
|
"slidable": "फिसलने लायक",
|
||||||
|
"etherscan_history": "इथरस्कैन इतिहास",
|
||||||
"template_name": "टेम्पलेट नाम",
|
"template_name": "टेम्पलेट नाम",
|
||||||
"change_rep": "प्रतिनिधि बदलें",
|
"change_rep": "प्रतिनिधि बदलें",
|
||||||
"change_rep_message": "क्या आप वाकई प्रतिनिधियों को बदलना चाहते हैं?"
|
"change_rep_message": "क्या आप वाकई प्रतिनिधियों को बदलना चाहते हैं?"
|
||||||
|
|
|
@ -632,6 +632,18 @@
|
||||||
"setup_totp_recommended": "Postavite TOTP (preporučeno)",
|
"setup_totp_recommended": "Postavite TOTP (preporučeno)",
|
||||||
"disable_buy": "Onemogući kupnju",
|
"disable_buy": "Onemogući kupnju",
|
||||||
"disable_sell": "Onemogući akciju prodaje",
|
"disable_sell": "Onemogući akciju prodaje",
|
||||||
|
"cake_2fa_preset" : "Cake 2FA Preset",
|
||||||
|
"narrow": "Usko",
|
||||||
|
"normal": "Normalno",
|
||||||
|
"aggressive": "Preterano",
|
||||||
|
"require_for_assessing_wallet": "Potreban za pristup novčaniku",
|
||||||
|
"require_for_sends_to_non_contacts" : "Zahtijeva za slanje nekontaktima",
|
||||||
|
"require_for_sends_to_contacts" : "Zahtijeva za slanje kontaktima",
|
||||||
|
"require_for_sends_to_internal_wallets" : "Zahtijeva za slanje u interne novčanike",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "Potreban za razmjenu na interne novčanike",
|
||||||
|
"require_for_adding_contacts" : "Zahtijeva za dodavanje kontakata",
|
||||||
|
"require_for_creating_new_wallets" : "Potreban za kreiranje novih novčanika",
|
||||||
|
"require_for_all_security_and_backup_settings" : "Zahtijeva za sve postavke sigurnosti i sigurnosne kopije",
|
||||||
"available_balance_description": "Dostupno stanje je iznos koji možete potrošiti. To je vaš saldo minus bilo kakve transakcije koje su još uvijek u tijeku.",
|
"available_balance_description": "Dostupno stanje je iznos koji možete potrošiti. To je vaš saldo minus bilo kakve transakcije koje su još uvijek u tijeku.",
|
||||||
"syncing_wallet_alert_title": "Vaš novčanik se sinkronizira",
|
"syncing_wallet_alert_title": "Vaš novčanik se sinkronizira",
|
||||||
"syncing_wallet_alert_content": "Vaš saldo i popis transakcija možda neće biti potpuni sve dok na vrhu ne piše \"SINKRONIZIRANO\". Kliknite/dodirnite da biste saznali više.",
|
"syncing_wallet_alert_content": "Vaš saldo i popis transakcija možda neće biti potpuni sve dok na vrhu ne piše \"SINKRONIZIRANO\". Kliknite/dodirnite da biste saznali više.",
|
||||||
|
@ -656,6 +668,7 @@
|
||||||
"balance_page": "Stranica sa stanjem",
|
"balance_page": "Stranica sa stanjem",
|
||||||
"share": "Udio",
|
"share": "Udio",
|
||||||
"slidable": "Klizna",
|
"slidable": "Klizna",
|
||||||
|
"etherscan_history": "Etherscan povijest",
|
||||||
"template_name": "Naziv predloška",
|
"template_name": "Naziv predloška",
|
||||||
"change_rep": "Promjena predstavnika",
|
"change_rep": "Promjena predstavnika",
|
||||||
"change_rep_message": "Jeste li sigurni da želite promijeniti predstavnika?"
|
"change_rep_message": "Jeste li sigurni da želite promijeniti predstavnika?"
|
||||||
|
|
|
@ -622,6 +622,18 @@
|
||||||
"setup_totp_recommended": "Siapkan TOTP (Disarankan)",
|
"setup_totp_recommended": "Siapkan TOTP (Disarankan)",
|
||||||
"disable_buy": "Nonaktifkan tindakan beli",
|
"disable_buy": "Nonaktifkan tindakan beli",
|
||||||
"disable_sell": "Nonaktifkan aksi jual",
|
"disable_sell": "Nonaktifkan aksi jual",
|
||||||
|
"cake_2fa_preset" : "Preset Kue 2FA",
|
||||||
|
"narrow": "Sempit",
|
||||||
|
"normal": "Normal",
|
||||||
|
"aggressive": "Terlalu bersemangat",
|
||||||
|
"require_for_assessing_wallet": "Diperlukan untuk mengakses dompet",
|
||||||
|
"require_for_sends_to_non_contacts" : "Wajibkan untuk mengirim ke non-kontak",
|
||||||
|
"require_for_sends_to_contacts" : "Membutuhkan untuk mengirim ke kontak",
|
||||||
|
"require_for_sends_to_internal_wallets" : "Diperlukan untuk mengirim ke dompet internal",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "Diperlukan untuk pertukaran ke dompet internal",
|
||||||
|
"require_for_adding_contacts" : "Membutuhkan untuk menambahkan kontak",
|
||||||
|
"require_for_creating_new_wallets" : "Diperlukan untuk membuat dompet baru",
|
||||||
|
"require_for_all_security_and_backup_settings" : "Memerlukan untuk semua pengaturan keamanan dan pencadangan",
|
||||||
"available_balance_description": "“Saldo yang Tersedia” atau “Saldo yang Dikonfirmasi” adalah dana yang dapat langsung dibelanjakan. Jika dana muncul di saldo bawah tetapi tidak di saldo atas, maka Anda harus menunggu beberapa menit agar dana masuk mendapatkan konfirmasi jaringan lainnya. Setelah mereka mendapatkan lebih banyak konfirmasi, mereka akan dapat dibelanjakan.",
|
"available_balance_description": "“Saldo yang Tersedia” atau “Saldo yang Dikonfirmasi” adalah dana yang dapat langsung dibelanjakan. Jika dana muncul di saldo bawah tetapi tidak di saldo atas, maka Anda harus menunggu beberapa menit agar dana masuk mendapatkan konfirmasi jaringan lainnya. Setelah mereka mendapatkan lebih banyak konfirmasi, mereka akan dapat dibelanjakan.",
|
||||||
"syncing_wallet_alert_title": "Dompet Anda sedang disinkronkan",
|
"syncing_wallet_alert_title": "Dompet Anda sedang disinkronkan",
|
||||||
"syncing_wallet_alert_content": "Saldo dan daftar transaksi Anda mungkin belum lengkap sampai tertulis “SYNCHRONIZED” di bagian atas. Klik/ketuk untuk mempelajari lebih lanjut.",
|
"syncing_wallet_alert_content": "Saldo dan daftar transaksi Anda mungkin belum lengkap sampai tertulis “SYNCHRONIZED” di bagian atas. Klik/ketuk untuk mempelajari lebih lanjut.",
|
||||||
|
@ -646,6 +658,7 @@
|
||||||
"balance_page": "Halaman Saldo",
|
"balance_page": "Halaman Saldo",
|
||||||
"share": "Membagikan",
|
"share": "Membagikan",
|
||||||
"slidable": "Dapat digeser",
|
"slidable": "Dapat digeser",
|
||||||
|
"etherscan_history": "Sejarah Etherscan",
|
||||||
"template_name": "Nama Templat",
|
"template_name": "Nama Templat",
|
||||||
"change_rep": "Ubah Perwakilan",
|
"change_rep": "Ubah Perwakilan",
|
||||||
"change_rep_message": "Apakah Anda yakin ingin mengubah perwakilan?"
|
"change_rep_message": "Apakah Anda yakin ingin mengubah perwakilan?"
|
||||||
|
|
|
@ -632,6 +632,18 @@
|
||||||
"setup_totp_recommended": "Imposta TOTP (consigliato)",
|
"setup_totp_recommended": "Imposta TOTP (consigliato)",
|
||||||
"disable_buy": "Disabilita l'azione di acquisto",
|
"disable_buy": "Disabilita l'azione di acquisto",
|
||||||
"disable_sell": "Disabilita l'azione di vendita",
|
"disable_sell": "Disabilita l'azione di vendita",
|
||||||
|
"cake_2fa_preset" : "Torta 2FA Preset",
|
||||||
|
"narrow": "Stretto",
|
||||||
|
"normal": "Normale",
|
||||||
|
"aggressive": "Fervente",
|
||||||
|
"require_for_assessing_wallet": "Richiesto per l'accesso al portafoglio",
|
||||||
|
"require_for_sends_to_non_contacts" : "Richiesto per invii a non contatti",
|
||||||
|
"require_for_sends_to_contacts" : "Richiedi per gli invii ai contatti",
|
||||||
|
"require_for_sends_to_internal_wallets" : "Richiesto per invii a portafogli interni",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "Richiedi per gli scambi ai portafogli interni",
|
||||||
|
"require_for_adding_contacts" : "Richiesto per l'aggiunta di contatti",
|
||||||
|
"require_for_creating_new_wallets" : "Richiesto per la creazione di nuovi portafogli",
|
||||||
|
"require_for_all_security_and_backup_settings" : "Richiedi per tutte le impostazioni di sicurezza e backup",
|
||||||
"available_balance_description": "Il saldo disponibile è il saldo totale meno i fondi congelati. I fondi congelati sono fondi che sono stati inviati ma non sono ancora stati confermati.",
|
"available_balance_description": "Il saldo disponibile è il saldo totale meno i fondi congelati. I fondi congelati sono fondi che sono stati inviati ma non sono ancora stati confermati.",
|
||||||
"syncing_wallet_alert_title": "Il tuo portafoglio si sta sincronizzando",
|
"syncing_wallet_alert_title": "Il tuo portafoglio si sta sincronizzando",
|
||||||
"syncing_wallet_alert_content": "Il saldo e l'elenco delle transazioni potrebbero non essere completi fino a quando non viene visualizzato \"SYNCHRONIZED\" in alto. Clicca/tocca per saperne di più.",
|
"syncing_wallet_alert_content": "Il saldo e l'elenco delle transazioni potrebbero non essere completi fino a quando non viene visualizzato \"SYNCHRONIZED\" in alto. Clicca/tocca per saperne di più.",
|
||||||
|
@ -656,6 +668,7 @@
|
||||||
"balance_page": "Pagina di equilibrio",
|
"balance_page": "Pagina di equilibrio",
|
||||||
"share": "Condividere",
|
"share": "Condividere",
|
||||||
"slidable": "Scorrevole",
|
"slidable": "Scorrevole",
|
||||||
|
"etherscan_history": "Storia Etherscan",
|
||||||
"template_name": "Nome modello",
|
"template_name": "Nome modello",
|
||||||
"change_rep": "Cambia rappresentante",
|
"change_rep": "Cambia rappresentante",
|
||||||
"change_rep_message": "Sei sicuro di voler cambiare rappresentante?"
|
"change_rep_message": "Sei sicuro di voler cambiare rappresentante?"
|
||||||
|
|
|
@ -632,6 +632,18 @@
|
||||||
"setup_totp_recommended": "TOTP を設定する (推奨)",
|
"setup_totp_recommended": "TOTP を設定する (推奨)",
|
||||||
"disable_buy": "購入アクションを無効にする",
|
"disable_buy": "購入アクションを無効にする",
|
||||||
"disable_sell": "販売アクションを無効にする",
|
"disable_sell": "販売アクションを無効にする",
|
||||||
|
"cake_2fa_preset" : "ケーキ 2FA プリセット",
|
||||||
|
"narrow": "狭い",
|
||||||
|
"normal": "普通",
|
||||||
|
"aggressive": "熱心すぎる",
|
||||||
|
"require_for_assessing_wallet": "ウォレットにアクセスするために必要です",
|
||||||
|
"require_for_sends_to_non_contacts" : "非連絡先への送信に必須",
|
||||||
|
"require_for_sends_to_contacts" : "連絡先に送信する場合に必須",
|
||||||
|
"require_for_sends_to_internal_wallets" : "内部ウォレットへの送信に必須",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "内部ウォレットへの交換に必要",
|
||||||
|
"require_for_adding_contacts" : "連絡先の追加に必要",
|
||||||
|
"require_for_creating_new_wallets" : "新しいウォレットを作成するために必要です",
|
||||||
|
"require_for_all_security_and_backup_settings" : "すべてのセキュリティおよびバックアップ設定に必須",
|
||||||
"available_balance_description": "利用可能な残高は、ウォレットの残高から冷凍残高を差し引いたものです。",
|
"available_balance_description": "利用可能な残高は、ウォレットの残高から冷凍残高を差し引いたものです。",
|
||||||
"syncing_wallet_alert_title": "ウォレットは同期中です",
|
"syncing_wallet_alert_title": "ウォレットは同期中です",
|
||||||
"syncing_wallet_alert_content": "上部に「同期済み」と表示されるまで、残高と取引リストが完了していない可能性があります。詳細については、クリック/タップしてください。",
|
"syncing_wallet_alert_content": "上部に「同期済み」と表示されるまで、残高と取引リストが完了していない可能性があります。詳細については、クリック/タップしてください。",
|
||||||
|
@ -656,6 +668,7 @@
|
||||||
"balance_page": "残高ページ",
|
"balance_page": "残高ページ",
|
||||||
"share": "共有",
|
"share": "共有",
|
||||||
"slidable": "スライド可能",
|
"slidable": "スライド可能",
|
||||||
|
"etherscan_history": "イーサスキャンの歴史",
|
||||||
"template_name": "テンプレート名",
|
"template_name": "テンプレート名",
|
||||||
"change_rep": "代表者の変更",
|
"change_rep": "代表者の変更",
|
||||||
"change_rep_message": "代表者を変更してもよろしいですか?"
|
"change_rep_message": "代表者を変更してもよろしいですか?"
|
||||||
|
|
|
@ -633,6 +633,18 @@
|
||||||
"setup_totp_recommended": "TOTP 설정(권장)",
|
"setup_totp_recommended": "TOTP 설정(권장)",
|
||||||
"disable_buy": "구매 행동 비활성화",
|
"disable_buy": "구매 행동 비활성화",
|
||||||
"disable_sell": "판매 조치 비활성화",
|
"disable_sell": "판매 조치 비활성화",
|
||||||
|
"cake_2fa_preset" : "케이크 2FA 프리셋",
|
||||||
|
"narrow": "좁은",
|
||||||
|
"normal": "정상",
|
||||||
|
"aggressive": "지나치게 열심인",
|
||||||
|
"require_for_assessing_wallet": "지갑 접근을 위해 필요",
|
||||||
|
"require_for_sends_to_non_contacts" : "비접촉자에게 보내는 데 필요",
|
||||||
|
"require_for_sends_to_contacts" : "연락처로 보내기에 필요",
|
||||||
|
"require_for_sends_to_internal_wallets" : "내부 지갑으로 보내는 데 필요",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "내부 지갑으로의 교환에 필요",
|
||||||
|
"require_for_adding_contacts" : "연락처 추가에 필요",
|
||||||
|
"require_for_creating_new_wallets" : "새 지갑 생성에 필요",
|
||||||
|
"require_for_all_security_and_backup_settings" : "모든 보안 및 백업 설정에 필요",
|
||||||
"available_balance_description": "이 지갑에서 사용할 수 있는 잔액입니다. 이 잔액은 블록체인에서 가져온 것이며, Cake Wallet이 사용할 수 없습니다.",
|
"available_balance_description": "이 지갑에서 사용할 수 있는 잔액입니다. 이 잔액은 블록체인에서 가져온 것이며, Cake Wallet이 사용할 수 없습니다.",
|
||||||
"syncing_wallet_alert_title": "지갑 동기화 중",
|
"syncing_wallet_alert_title": "지갑 동기화 중",
|
||||||
"syncing_wallet_alert_content": "상단에 \"동기화됨\"이라고 표시될 때까지 잔액 및 거래 목록이 완전하지 않을 수 있습니다. 자세히 알아보려면 클릭/탭하세요.",
|
"syncing_wallet_alert_content": "상단에 \"동기화됨\"이라고 표시될 때까지 잔액 및 거래 목록이 완전하지 않을 수 있습니다. 자세히 알아보려면 클릭/탭하세요.",
|
||||||
|
@ -657,7 +669,8 @@
|
||||||
"balance_page": "잔액 페이지",
|
"balance_page": "잔액 페이지",
|
||||||
"share": "공유하다",
|
"share": "공유하다",
|
||||||
"slidable": "슬라이딩 가능",
|
"slidable": "슬라이딩 가능",
|
||||||
|
"etherscan_history": "이더스캔 역사",
|
||||||
"template_name": "템플릿 이름",
|
"template_name": "템플릿 이름",
|
||||||
"change_rep": "대표자 변경",
|
"change_rep": "대표자 변경",
|
||||||
"change_rep_message": "담당자를 변경하시겠습니까?"
|
"change_rep_message": "담당자를 변경하시겠습니까?"
|
||||||
}
|
}
|
||||||
|
|
|
@ -632,6 +632,18 @@
|
||||||
"setup_totp_recommended": "TOTP ကို စနစ်ထည့်သွင်းပါ (အကြံပြုထားသည်)",
|
"setup_totp_recommended": "TOTP ကို စနစ်ထည့်သွင်းပါ (အကြံပြုထားသည်)",
|
||||||
"disable_buy": "ဝယ်ယူမှု လုပ်ဆောင်ချက်ကို ပိတ်ပါ။",
|
"disable_buy": "ဝယ်ယူမှု လုပ်ဆောင်ချက်ကို ပိတ်ပါ။",
|
||||||
"disable_sell": "ရောင်းချခြင်းလုပ်ဆောင်ချက်ကို ပိတ်ပါ။",
|
"disable_sell": "ရောင်းချခြင်းလုပ်ဆောင်ချက်ကို ပိတ်ပါ။",
|
||||||
|
"cake_2fa_preset" : "ကိတ်မုန့် 2FA ကြိုတင်သတ်မှတ်",
|
||||||
|
"narrow": "ကျဉ်းသော",
|
||||||
|
"normal": "ပုံမှန်",
|
||||||
|
"aggressive": "စိတ်အားထက်သန်ခြင်း။",
|
||||||
|
"require_for_assessing_wallet": "ပိုက်ဆံအိတ်ကို ဝင်သုံးရန် လိုအပ်သည်။",
|
||||||
|
"require_for_sends_to_non_contacts" : "အဆက်အသွယ်မရှိသူများထံ ပေးပို့ရန် လိုအပ်သည်။",
|
||||||
|
"require_for_sends_to_contacts" : "အဆက်အသွယ်များထံ ပေးပို့ရန် လိုအပ်သည်။",
|
||||||
|
"require_for_sends_to_internal_wallets" : "အတွင်းပိုင်း ပိုက်ဆံအိတ်များသို့ ပေးပို့ရန် လိုအပ်သည်။",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "အတွင်းပိုင်းပိုက်ဆံအိတ်များသို့ လဲလှယ်ရန် လိုအပ်သည်။",
|
||||||
|
"require_for_adding_contacts" : "အဆက်အသွယ်များထည့်ရန် လိုအပ်သည်။",
|
||||||
|
"require_for_creating_new_wallets" : "ပိုက်ဆံအိတ်အသစ်များ ဖန်တီးရန် လိုအပ်သည်။",
|
||||||
|
"require_for_all_security_and_backup_settings" : "လုံခြုံရေးနှင့် အရန်ဆက်တင်များအားလုံးအတွက် လိုအပ်ပါသည်။",
|
||||||
"available_balance_description": "သင့်ရဲ့ အကောင့်တွင် ရရှိနိုင်သော ငွေကျန်ငွေကို ပြန်လည်ပေးသွင်းပါ။",
|
"available_balance_description": "သင့်ရဲ့ အကောင့်တွင် ရရှိနိုင်သော ငွေကျန်ငွေကို ပြန်လည်ပေးသွင်းပါ။",
|
||||||
"syncing_wallet_alert_title": "သင့်ပိုက်ဆံအိတ်ကို စင့်ခ်လုပ်နေပါသည်။",
|
"syncing_wallet_alert_title": "သင့်ပိုက်ဆံအိတ်ကို စင့်ခ်လုပ်နေပါသည်။",
|
||||||
"syncing_wallet_alert_content": "သင်၏လက်ကျန်နှင့် ငွေပေးငွေယူစာရင်းသည် ထိပ်တွင် \"Synchronizeed\" ဟုပြောသည်အထိ မပြီးမြောက်နိုင်ပါ။ ပိုမိုလေ့လာရန် နှိပ်/နှိပ်ပါ။",
|
"syncing_wallet_alert_content": "သင်၏လက်ကျန်နှင့် ငွေပေးငွေယူစာရင်းသည် ထိပ်တွင် \"Synchronizeed\" ဟုပြောသည်အထိ မပြီးမြောက်နိုင်ပါ။ ပိုမိုလေ့လာရန် နှိပ်/နှိပ်ပါ။",
|
||||||
|
@ -656,6 +668,7 @@
|
||||||
"balance_page": "လက်ကျန်စာမျက်နှာ",
|
"balance_page": "လက်ကျန်စာမျက်နှာ",
|
||||||
"share": "မျှဝေပါ။",
|
"share": "မျှဝေပါ။",
|
||||||
"slidable": "လျှောချနိုင်သည်။",
|
"slidable": "လျှောချနိုင်သည်။",
|
||||||
|
"etherscan_history": "Etherscan သမိုင်း",
|
||||||
"template_name": "နမူနာပုံစံ",
|
"template_name": "နမူနာပုံစံ",
|
||||||
"change_rep": "ကိုယ်စားလှယ်ပြောင်းပါ။",
|
"change_rep": "ကိုယ်စားလှယ်ပြောင်းပါ။",
|
||||||
"change_rep_message": "ကိုယ်စားလှယ်ပြောင်းလိုသည်မှာ သေချာပါသလား။"
|
"change_rep_message": "ကိုယ်စားလှယ်ပြောင်းလိုသည်မှာ သေချာပါသလား။"
|
||||||
|
|
|
@ -632,6 +632,18 @@
|
||||||
"setup_totp_recommended": "TOTP instellen (aanbevolen)",
|
"setup_totp_recommended": "TOTP instellen (aanbevolen)",
|
||||||
"disable_buy": "Koopactie uitschakelen",
|
"disable_buy": "Koopactie uitschakelen",
|
||||||
"disable_sell": "Verkoopactie uitschakelen",
|
"disable_sell": "Verkoopactie uitschakelen",
|
||||||
|
"cake_2fa_preset" : "Taart 2FA Voorinstelling",
|
||||||
|
"narrow": "Smal",
|
||||||
|
"normal": "Normaal",
|
||||||
|
"aggressive": "Overijverig",
|
||||||
|
"require_for_assessing_wallet": "Vereist voor toegang tot portemonnee",
|
||||||
|
"require_for_sends_to_non_contacts" : "Vereist voor verzendingen naar niet-contacten",
|
||||||
|
"require_for_sends_to_contacts" : "Vereist voor verzending naar contacten",
|
||||||
|
"require_for_sends_to_internal_wallets" : "Vereist voor verzendingen naar interne portefeuilles",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "Vereist voor uitwisselingen naar interne portefeuilles",
|
||||||
|
"require_for_adding_contacts" : "Vereist voor het toevoegen van contacten",
|
||||||
|
"require_for_creating_new_wallets" : "Vereist voor het maken van nieuwe portefeuilles",
|
||||||
|
"require_for_all_security_and_backup_settings" : "Vereist voor alle beveiligings- en back-upinstellingen",
|
||||||
"available_balance_description": "Beschikbaar saldo is het saldo dat u kunt uitgeven. Het kan lager zijn dan uw totale saldo als u onlangs geld hebt verzonden.",
|
"available_balance_description": "Beschikbaar saldo is het saldo dat u kunt uitgeven. Het kan lager zijn dan uw totale saldo als u onlangs geld hebt verzonden.",
|
||||||
"syncing_wallet_alert_title": "Uw portemonnee wordt gesynchroniseerd",
|
"syncing_wallet_alert_title": "Uw portemonnee wordt gesynchroniseerd",
|
||||||
"syncing_wallet_alert_content": "Uw saldo- en transactielijst is mogelijk pas compleet als er bovenaan 'GESYNCHRONISEERD' staat. Klik/tik voor meer informatie.",
|
"syncing_wallet_alert_content": "Uw saldo- en transactielijst is mogelijk pas compleet als er bovenaan 'GESYNCHRONISEERD' staat. Klik/tik voor meer informatie.",
|
||||||
|
@ -656,6 +668,7 @@
|
||||||
"balance_page": "Saldo pagina",
|
"balance_page": "Saldo pagina",
|
||||||
"share": "Deel",
|
"share": "Deel",
|
||||||
"slidable": "Verschuifbaar",
|
"slidable": "Verschuifbaar",
|
||||||
|
"etherscan_history": "Etherscan-geschiedenis",
|
||||||
"template_name": "Sjabloonnaam",
|
"template_name": "Sjabloonnaam",
|
||||||
"change_rep": "Vertegenwoordiger wijzigen",
|
"change_rep": "Vertegenwoordiger wijzigen",
|
||||||
"change_rep_message": "Weet u zeker dat u van vertegenwoordiger wilt veranderen?"
|
"change_rep_message": "Weet u zeker dat u van vertegenwoordiger wilt veranderen?"
|
||||||
|
|
|
@ -632,6 +632,18 @@
|
||||||
"setup_totp_recommended": "Skonfiguruj TOTP (zalecane)",
|
"setup_totp_recommended": "Skonfiguruj TOTP (zalecane)",
|
||||||
"disable_buy": "Wyłącz akcję kupna",
|
"disable_buy": "Wyłącz akcję kupna",
|
||||||
"disable_sell": "Wyłącz akcję sprzedaży",
|
"disable_sell": "Wyłącz akcję sprzedaży",
|
||||||
|
"cake_2fa_preset" : "Ciasto 2FA Preset",
|
||||||
|
"narrow": "Wąski",
|
||||||
|
"normal": "Normalna",
|
||||||
|
"aggressive": "Nadgorliwy",
|
||||||
|
"require_for_assessing_wallet": "Wymagaj dostępu do portfela",
|
||||||
|
"require_for_sends_to_non_contacts" : "Wymagaj wysyłania do osób niekontaktowych",
|
||||||
|
"require_for_sends_to_contacts" : "Wymagaj wysyłania do kontaktów",
|
||||||
|
"require_for_sends_to_internal_wallets" : "Wymagaj wysyłania do portfeli wewnętrznych",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "Wymagaj wymiany do portfeli wewnętrznych",
|
||||||
|
"require_for_adding_contacts" : "Wymagane do dodania kontaktów",
|
||||||
|
"require_for_creating_new_wallets" : "Wymagane do tworzenia nowych portfeli",
|
||||||
|
"require_for_all_security_and_backup_settings" : "Wymagaj dla wszystkich ustawień zabezpieczeń i kopii zapasowych",
|
||||||
"available_balance_description": "Dostępne saldo jest równoważne z saldem portfela minus zamrożone saldo.",
|
"available_balance_description": "Dostępne saldo jest równoważne z saldem portfela minus zamrożone saldo.",
|
||||||
"syncing_wallet_alert_title": "Twój portfel się synchronizuje",
|
"syncing_wallet_alert_title": "Twój portfel się synchronizuje",
|
||||||
"syncing_wallet_alert_content": "Twoje saldo i lista transakcji mogą nie być kompletne, dopóki u góry nie pojawi się napis „SYNCHRONIZOWANY”. Kliknij/stuknij, aby dowiedzieć się więcej.",
|
"syncing_wallet_alert_content": "Twoje saldo i lista transakcji mogą nie być kompletne, dopóki u góry nie pojawi się napis „SYNCHRONIZOWANY”. Kliknij/stuknij, aby dowiedzieć się więcej.",
|
||||||
|
@ -656,6 +668,7 @@
|
||||||
"balance_page": "Strona salda",
|
"balance_page": "Strona salda",
|
||||||
"share": "Udział",
|
"share": "Udział",
|
||||||
"slidable": "Przesuwne",
|
"slidable": "Przesuwne",
|
||||||
|
"etherscan_history": "Historia Etherscanu",
|
||||||
"template_name": "Nazwa szablonu",
|
"template_name": "Nazwa szablonu",
|
||||||
"change_rep": "Zmień przedstawiciela",
|
"change_rep": "Zmień przedstawiciela",
|
||||||
"change_rep_message": "Czy na pewno chcesz zmienić przedstawiciela?"
|
"change_rep_message": "Czy na pewno chcesz zmienić przedstawiciela?"
|
||||||
|
|
|
@ -631,6 +631,18 @@
|
||||||
"setup_totp_recommended": "Configurar TOTP (recomendado)",
|
"setup_totp_recommended": "Configurar TOTP (recomendado)",
|
||||||
"disable_buy": "Desativar ação de compra",
|
"disable_buy": "Desativar ação de compra",
|
||||||
"disable_sell": "Desativar ação de venda",
|
"disable_sell": "Desativar ação de venda",
|
||||||
|
"cake_2fa_preset" : "Predefinição de bolo 2FA",
|
||||||
|
"narrow": "Estreito",
|
||||||
|
"normal": "Normal",
|
||||||
|
"aggressive": "excessivamente zeloso",
|
||||||
|
"require_for_assessing_wallet": "Requer para acessar a carteira",
|
||||||
|
"require_for_sends_to_non_contacts" : "Exigir para envios para não-contatos",
|
||||||
|
"require_for_sends_to_contacts" : "Exigir para envios para contatos",
|
||||||
|
"require_for_sends_to_internal_wallets" : "Exigir envios para carteiras internas",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "Requer trocas para carteiras internas",
|
||||||
|
"require_for_adding_contacts" : "Requer para adicionar contatos",
|
||||||
|
"require_for_creating_new_wallets" : "Requer para criar novas carteiras",
|
||||||
|
"require_for_all_security_and_backup_settings" : "Exigir todas as configurações de segurança e backup",
|
||||||
"available_balance_description": "Seu saldo disponível é o saldo total menos o saldo congelado. O saldo congelado é o saldo que você não pode gastar, mas que ainda não foi confirmado na blockchain. O saldo congelado é geralmente o resultado de transações recentes.",
|
"available_balance_description": "Seu saldo disponível é o saldo total menos o saldo congelado. O saldo congelado é o saldo que você não pode gastar, mas que ainda não foi confirmado na blockchain. O saldo congelado é geralmente o resultado de transações recentes.",
|
||||||
"syncing_wallet_alert_title": "Sua carteira está sincronizando",
|
"syncing_wallet_alert_title": "Sua carteira está sincronizando",
|
||||||
"syncing_wallet_alert_content": "Seu saldo e lista de transações podem não estar completos até que diga “SYNCHRONIZED” no topo. Clique/toque para saber mais.",
|
"syncing_wallet_alert_content": "Seu saldo e lista de transações podem não estar completos até que diga “SYNCHRONIZED” no topo. Clique/toque para saber mais.",
|
||||||
|
@ -655,6 +667,7 @@
|
||||||
"balance_page": "Página de saldo",
|
"balance_page": "Página de saldo",
|
||||||
"share": "Compartilhar",
|
"share": "Compartilhar",
|
||||||
"slidable": "Deslizável",
|
"slidable": "Deslizável",
|
||||||
|
"etherscan_history": "história Etherscan",
|
||||||
"template_name": "Nome do modelo",
|
"template_name": "Nome do modelo",
|
||||||
"change_rep": "Alterar representante",
|
"change_rep": "Alterar representante",
|
||||||
"change_rep_message": "Tem certeza de que deseja alterar os representantes?"
|
"change_rep_message": "Tem certeza de que deseja alterar os representantes?"
|
||||||
|
|
|
@ -632,6 +632,18 @@
|
||||||
"setup_totp_recommended": "Настроить TOTP (рекомендуется)",
|
"setup_totp_recommended": "Настроить TOTP (рекомендуется)",
|
||||||
"disable_buy": "Отключить действие покупки",
|
"disable_buy": "Отключить действие покупки",
|
||||||
"disable_sell": "Отключить действие продажи",
|
"disable_sell": "Отключить действие продажи",
|
||||||
|
"cake_2fa_preset" : "Торт 2FA Preset",
|
||||||
|
"narrow": "Узкий",
|
||||||
|
"normal": "Нормальный",
|
||||||
|
"aggressive": "чрезмерно усердный",
|
||||||
|
"require_for_assessing_wallet": "Требовать для доступа к кошельку",
|
||||||
|
"require_for_sends_to_non_contacts" : "Требовать для отправки не контактам",
|
||||||
|
"require_for_sends_to_contacts" : "Требовать для отправки контактам",
|
||||||
|
"require_for_sends_to_internal_wallets" : "Требовать отправки на внутренние кошельки",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "Требовать для обмена на внутренние кошельки",
|
||||||
|
"require_for_adding_contacts" : "Требовать добавления контактов",
|
||||||
|
"require_for_creating_new_wallets" : "Требовать для создания новых кошельков",
|
||||||
|
"require_for_all_security_and_backup_settings" : "Требовать все настройки безопасности и резервного копирования",
|
||||||
"available_balance_description": "Доступный баланс - это средства, которые вы можете использовать для покупки или продажи криптовалюты.",
|
"available_balance_description": "Доступный баланс - это средства, которые вы можете использовать для покупки или продажи криптовалюты.",
|
||||||
"syncing_wallet_alert_title": "Ваш кошелек синхронизируется",
|
"syncing_wallet_alert_title": "Ваш кошелек синхронизируется",
|
||||||
"syncing_wallet_alert_content": "Ваш баланс и список транзакций могут быть неполными, пока вверху не будет написано «СИНХРОНИЗИРОВАНО». Щелкните/коснитесь, чтобы узнать больше.",
|
"syncing_wallet_alert_content": "Ваш баланс и список транзакций могут быть неполными, пока вверху не будет написано «СИНХРОНИЗИРОВАНО». Щелкните/коснитесь, чтобы узнать больше.",
|
||||||
|
@ -656,6 +668,7 @@
|
||||||
"balance_page": "Страница баланса",
|
"balance_page": "Страница баланса",
|
||||||
"share": "Делиться",
|
"share": "Делиться",
|
||||||
"slidable": "Скользящий",
|
"slidable": "Скользящий",
|
||||||
|
"etherscan_history": "История Эфириума",
|
||||||
"template_name": "Имя Шаблона",
|
"template_name": "Имя Шаблона",
|
||||||
"change_rep": "Изменить представителя",
|
"change_rep": "Изменить представителя",
|
||||||
"change_rep_message": "Вы уверены, что хотите сменить представителя?"
|
"change_rep_message": "Вы уверены, что хотите сменить представителя?"
|
||||||
|
|
|
@ -632,6 +632,18 @@
|
||||||
"setup_totp_recommended": "ตั้งค่า TOTP (แนะนำ)",
|
"setup_totp_recommended": "ตั้งค่า TOTP (แนะนำ)",
|
||||||
"disable_buy": "ปิดการใช้งานการซื้อ",
|
"disable_buy": "ปิดการใช้งานการซื้อ",
|
||||||
"disable_sell": "ปิดการใช้งานการขาย",
|
"disable_sell": "ปิดการใช้งานการขาย",
|
||||||
|
"cake_2fa_preset" : "เค้ก 2FA ที่ตั้งไว้ล่วงหน้า",
|
||||||
|
"narrow": "แคบ",
|
||||||
|
"normal": "ปกติ",
|
||||||
|
"aggressive": "กระตือรือร้นมากเกินไป",
|
||||||
|
"require_for_assessing_wallet": "จำเป็นสำหรับการเข้าถึงกระเป๋าเงิน",
|
||||||
|
"require_for_sends_to_non_contacts" : "จำเป็นต้องส่งไปยังผู้ที่ไม่ได้ติดต่อ",
|
||||||
|
"require_for_sends_to_contacts" : "จำเป็นต้องส่งไปยังผู้ติดต่อ",
|
||||||
|
"require_for_sends_to_internal_wallets" : "จำเป็นต้องส่งไปยังกระเป๋าเงินภายใน",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "ต้องการการแลกเปลี่ยนไปยังกระเป๋าเงินภายใน",
|
||||||
|
"require_for_adding_contacts" : "ต้องการสำหรับการเพิ่มผู้ติดต่อ",
|
||||||
|
"require_for_creating_new_wallets" : "จำเป็นสำหรับการสร้างกระเป๋าเงินใหม่",
|
||||||
|
"require_for_all_security_and_backup_settings" : "จำเป็นสำหรับการตั้งค่าความปลอดภัยและการสำรองข้อมูลทั้งหมด",
|
||||||
"available_balance_description": "จำนวนเงินที่คุณสามารถใช้ได้ในการซื้อหรือขาย",
|
"available_balance_description": "จำนวนเงินที่คุณสามารถใช้ได้ในการซื้อหรือขาย",
|
||||||
"syncing_wallet_alert_title": "กระเป๋าสตางค์ของคุณกำลังซิงค์",
|
"syncing_wallet_alert_title": "กระเป๋าสตางค์ของคุณกำลังซิงค์",
|
||||||
"syncing_wallet_alert_content": "รายการยอดเงินและธุรกรรมของคุณอาจไม่สมบูรณ์จนกว่าจะมีข้อความว่า “ซิงโครไนซ์” ที่ด้านบน คลิก/แตะเพื่อเรียนรู้เพิ่มเติม่",
|
"syncing_wallet_alert_content": "รายการยอดเงินและธุรกรรมของคุณอาจไม่สมบูรณ์จนกว่าจะมีข้อความว่า “ซิงโครไนซ์” ที่ด้านบน คลิก/แตะเพื่อเรียนรู้เพิ่มเติม่",
|
||||||
|
@ -656,6 +668,7 @@
|
||||||
"balance_page": "หน้ายอดคงเหลือ",
|
"balance_page": "หน้ายอดคงเหลือ",
|
||||||
"share": "แบ่งปัน",
|
"share": "แบ่งปัน",
|
||||||
"slidable": "เลื่อนได้",
|
"slidable": "เลื่อนได้",
|
||||||
|
"etherscan_history": "ประวัติอีเธอร์สแกน",
|
||||||
"template_name": "ชื่อแม่แบบ",
|
"template_name": "ชื่อแม่แบบ",
|
||||||
"change_rep": "เปลี่ยนผู้แทน",
|
"change_rep": "เปลี่ยนผู้แทน",
|
||||||
"change_rep_message": "คุณแน่ใจหรือไม่ว่าต้องการเปลี่ยนตัวแทน"
|
"change_rep_message": "คุณแน่ใจหรือไม่ว่าต้องการเปลี่ยนตัวแทน"
|
||||||
|
|
|
@ -631,6 +631,19 @@
|
||||||
"setup_2fa_text": "Cake 2FA, soğuk hava deposu kadar güvenli DEĞİLDİR. 2FA, siz uyurken arkadaşınızın parmak izinizi sağlaması gibi temel saldırı türlerine karşı koruma sağlar.\n\n Cake 2FA, gelişmiş bir saldırgan tarafından güvenliği ihlal edilmiş bir cihaza karşı koruma SAĞLAMAZ.\n\n 2FA kodlarınıza erişimi kaybederseniz , BU CÜZDANA ERİŞİMİNİZİ KAYBEDECEKSİNİZ. Mnemonic seed'den cüzdanınızı geri yüklemeniz gerekecek. BU NEDENLE HATIRLAYICI TOHUMLARINIZI YEDEKLEMELİSİNİZ! Ayrıca anımsatıcı tohumlarınıza erişimi olan biri, Cake 2FA'yı atlayarak paranızı çalabilir.\n\n Cake, anımsatıcı tohumlarınıza erişimi kaybederseniz size yardımcı olamaz, çünkü Cake bir saklama dışı cüzdan.",
|
"setup_2fa_text": "Cake 2FA, soğuk hava deposu kadar güvenli DEĞİLDİR. 2FA, siz uyurken arkadaşınızın parmak izinizi sağlaması gibi temel saldırı türlerine karşı koruma sağlar.\n\n Cake 2FA, gelişmiş bir saldırgan tarafından güvenliği ihlal edilmiş bir cihaza karşı koruma SAĞLAMAZ.\n\n 2FA kodlarınıza erişimi kaybederseniz , BU CÜZDANA ERİŞİMİNİZİ KAYBEDECEKSİNİZ. Mnemonic seed'den cüzdanınızı geri yüklemeniz gerekecek. BU NEDENLE HATIRLAYICI TOHUMLARINIZI YEDEKLEMELİSİNİZ! Ayrıca anımsatıcı tohumlarınıza erişimi olan biri, Cake 2FA'yı atlayarak paranızı çalabilir.\n\n Cake, anımsatıcı tohumlarınıza erişimi kaybederseniz size yardımcı olamaz, çünkü Cake bir saklama dışı cüzdan.",
|
||||||
"setup_totp_recommended": "TOTP'yi kurun (Önerilir)",
|
"setup_totp_recommended": "TOTP'yi kurun (Önerilir)",
|
||||||
"disable_buy": "Satın alma işlemini devre dışı bırak",
|
"disable_buy": "Satın alma işlemini devre dışı bırak",
|
||||||
|
"disable_sell": "Satış işlemini devre dışı bırak",
|
||||||
|
"cake_2fa_preset" : "Kek 2FA Ön Ayarı",
|
||||||
|
"narrow": "Dar",
|
||||||
|
"normal": "Normal",
|
||||||
|
"aggressive": "Aşırı duyarlı",
|
||||||
|
"require_for_assessing_wallet": "Cüzdana erişmek için gerekli",
|
||||||
|
"require_for_sends_to_non_contacts" : "Kişi olmayan kişilere göndermeler için gerekli kıl",
|
||||||
|
"require_for_sends_to_contacts" : "Kişilere göndermeler için gerekli kıl",
|
||||||
|
"require_for_sends_to_internal_wallets" : "Dahili cüzdanlara yapılan gönderimler için gereklilik",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "Dahili cüzdanlara değişim gerektir",
|
||||||
|
"require_for_adding_contacts" : "Kişi eklemek için gerekli",
|
||||||
|
"require_for_creating_new_wallets" : "Yeni cüzdan oluşturmak için gerekli",
|
||||||
|
"require_for_all_security_and_backup_settings" : "Tüm güvenlik ve yedekleme ayarları için iste",
|
||||||
"disable_sell": "Satış işlemini devre dışı bırak",
|
"disable_sell": "Satış işlemini devre dışı bırak",
|
||||||
"available_balance_description": "Bu, cüzdanınızda harcayabileceğiniz miktar. Bu miktar, cüzdanınızdan çekilebilecek toplam bakiyeden daha düşük olabilir, çünkü bazı fonlar henüz kullanılamaz durumda olabilir.",
|
"available_balance_description": "Bu, cüzdanınızda harcayabileceğiniz miktar. Bu miktar, cüzdanınızdan çekilebilecek toplam bakiyeden daha düşük olabilir, çünkü bazı fonlar henüz kullanılamaz durumda olabilir.",
|
||||||
"syncing_wallet_alert_title": "Cüzdanınız senkronize ediliyor",
|
"syncing_wallet_alert_title": "Cüzdanınız senkronize ediliyor",
|
||||||
|
@ -656,6 +669,7 @@
|
||||||
"balance_page": "Bakiye Sayfası",
|
"balance_page": "Bakiye Sayfası",
|
||||||
"share": "Paylaşmak",
|
"share": "Paylaşmak",
|
||||||
"slidable": "kaydırılabilir",
|
"slidable": "kaydırılabilir",
|
||||||
|
"etherscan_history": "Etherscan geçmişi",
|
||||||
"template_name": "şablon adı",
|
"template_name": "şablon adı",
|
||||||
"change_rep": "Temsilciyi Değiştir",
|
"change_rep": "Temsilciyi Değiştir",
|
||||||
"change_rep_message": "Temsilcileri değiştirmek istediğinizden emin misiniz?"
|
"change_rep_message": "Temsilcileri değiştirmek istediğinizden emin misiniz?"
|
||||||
|
|
|
@ -632,6 +632,18 @@
|
||||||
"setup_totp_recommended": "Налаштувати TOTP (рекомендовано)",
|
"setup_totp_recommended": "Налаштувати TOTP (рекомендовано)",
|
||||||
"disable_buy": "Вимкнути дію покупки",
|
"disable_buy": "Вимкнути дію покупки",
|
||||||
"disable_sell": "Вимкнути дію продажу",
|
"disable_sell": "Вимкнути дію продажу",
|
||||||
|
"cake_2fa_preset" : "Торт 2FA Preset",
|
||||||
|
"narrow": "вузькі",
|
||||||
|
"normal": "нормальний",
|
||||||
|
"aggressive": "Надто старанний",
|
||||||
|
"require_for_assessing_wallet": "Потрібен доступ до гаманця",
|
||||||
|
"require_for_sends_to_non_contacts" : "Вимагати для надсилання неконтактним особам",
|
||||||
|
"require_for_sends_to_contacts" : "Вимагати для надсилання контактам",
|
||||||
|
"require_for_sends_to_internal_wallets" : "Вимагати надсилання на внутрішні гаманці",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "Вимагати обміну на внутрішні гаманці",
|
||||||
|
"require_for_adding_contacts" : "Потрібен для додавання контактів",
|
||||||
|
"require_for_creating_new_wallets" : "Потрібно для створення нових гаманців",
|
||||||
|
"require_for_all_security_and_backup_settings" : "Вимагати всіх налаштувань безпеки та резервного копіювання",
|
||||||
"available_balance_description": "Це сума, яку ви можете витратити, не включаючи невизначені кошти. Це може бути менше, ніж загальний баланс, якщо ви витратили кошти, які ще не підтверджені.",
|
"available_balance_description": "Це сума, яку ви можете витратити, не включаючи невизначені кошти. Це може бути менше, ніж загальний баланс, якщо ви витратили кошти, які ще не підтверджені.",
|
||||||
"syncing_wallet_alert_title": "Ваш гаманець синхронізується",
|
"syncing_wallet_alert_title": "Ваш гаманець синхронізується",
|
||||||
"syncing_wallet_alert_content": "Ваш баланс та список транзакцій може бути неповним, доки вгорі не буде написано «СИНХРОНІЗОВАНО». Натисніть/торкніться, щоб дізнатися більше.",
|
"syncing_wallet_alert_content": "Ваш баланс та список транзакцій може бути неповним, доки вгорі не буде написано «СИНХРОНІЗОВАНО». Натисніть/торкніться, щоб дізнатися більше.",
|
||||||
|
@ -656,6 +668,7 @@
|
||||||
"balance_page": "Сторінка балансу",
|
"balance_page": "Сторінка балансу",
|
||||||
"share": "Поділіться",
|
"share": "Поділіться",
|
||||||
"slidable": "Розсувний",
|
"slidable": "Розсувний",
|
||||||
|
"etherscan_history": "Історія Etherscan",
|
||||||
"template_name": "Назва шаблону",
|
"template_name": "Назва шаблону",
|
||||||
"change_rep": "Зміна представника",
|
"change_rep": "Зміна представника",
|
||||||
"change_rep_message": "Ви впевнені, що хочете змінити представника?"
|
"change_rep_message": "Ви впевнені, що хочете змінити представника?"
|
||||||
|
|
|
@ -626,6 +626,18 @@
|
||||||
"setup_totp_recommended": "TOTP ترتیب دیں (تجویز کردہ)",
|
"setup_totp_recommended": "TOTP ترتیب دیں (تجویز کردہ)",
|
||||||
"disable_buy": "خرید ایکشن کو غیر فعال کریں۔",
|
"disable_buy": "خرید ایکشن کو غیر فعال کریں۔",
|
||||||
"disable_sell": "فروخت کی کارروائی کو غیر فعال کریں۔",
|
"disable_sell": "فروخت کی کارروائی کو غیر فعال کریں۔",
|
||||||
|
"cake_2fa_preset" : "کیک 2FA پیش سیٹ",
|
||||||
|
"narrow": "تنگ",
|
||||||
|
"normal": "نارمل",
|
||||||
|
"aggressive": "حد سے زیادہ پرجوش",
|
||||||
|
"require_for_assessing_wallet": "بٹوے تک رسائی کے لیے درکار ہے۔",
|
||||||
|
"require_for_sends_to_non_contacts" : "غیر رابطوں کو بھیجنے کی ضرورت ہے۔",
|
||||||
|
"require_for_sends_to_contacts" : "رابطوں کو بھیجنے کی ضرورت ہے۔",
|
||||||
|
"require_for_sends_to_internal_wallets" : "اندرونی بٹوے پر بھیجنے کے لیے درکار ہے۔",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "اندرونی بٹوے میں تبادلے کی ضرورت ہے۔",
|
||||||
|
"require_for_adding_contacts" : "رابطوں کو شامل کرنے کی ضرورت ہے۔",
|
||||||
|
"require_for_creating_new_wallets" : "نئے بٹوے بنانے کی ضرورت ہے۔",
|
||||||
|
"require_for_all_security_and_backup_settings" : "تمام سیکورٹی اور بیک اپ کی ترتیبات کے لیے درکار ہے۔",
|
||||||
"available_balance_description": "”دستیاب بیلنس” یا ”تصدیق شدہ بیلنس” وہ فنڈز ہیں جو فوری طور پر خرچ کیے جا سکتے ہیں۔ اگر فنڈز کم بیلنس میں ظاہر ہوتے ہیں لیکن اوپر کے بیلنس میں نہیں، تو آپ کو مزید نیٹ ورک کی تصدیقات حاصل کرنے کے لیے آنے والے فنڈز کے لیے چند منٹ انتظار کرنا چاہیے۔ مزید تصدیق حاصل کرنے کے بعد، وہ قابل خرچ ہوں گے۔",
|
"available_balance_description": "”دستیاب بیلنس” یا ”تصدیق شدہ بیلنس” وہ فنڈز ہیں جو فوری طور پر خرچ کیے جا سکتے ہیں۔ اگر فنڈز کم بیلنس میں ظاہر ہوتے ہیں لیکن اوپر کے بیلنس میں نہیں، تو آپ کو مزید نیٹ ورک کی تصدیقات حاصل کرنے کے لیے آنے والے فنڈز کے لیے چند منٹ انتظار کرنا چاہیے۔ مزید تصدیق حاصل کرنے کے بعد، وہ قابل خرچ ہوں گے۔",
|
||||||
"syncing_wallet_alert_title": "آپ کا بٹوہ مطابقت پذیر ہو رہا ہے۔",
|
"syncing_wallet_alert_title": "آپ کا بٹوہ مطابقت پذیر ہو رہا ہے۔",
|
||||||
"syncing_wallet_alert_content": "آپ کے بیلنس اور لین دین کی فہرست اس وقت تک مکمل نہیں ہو سکتی جب تک کہ یہ سب سے اوپر \"SYNCRONIZED\" نہ کہے۔ مزید جاننے کے لیے کلک/تھپتھپائیں۔",
|
"syncing_wallet_alert_content": "آپ کے بیلنس اور لین دین کی فہرست اس وقت تک مکمل نہیں ہو سکتی جب تک کہ یہ سب سے اوپر \"SYNCRONIZED\" نہ کہے۔ مزید جاننے کے لیے کلک/تھپتھپائیں۔",
|
||||||
|
@ -650,7 +662,8 @@
|
||||||
"balance_page": "بیلنس صفحہ",
|
"balance_page": "بیلنس صفحہ",
|
||||||
"share": "بانٹیں",
|
"share": "بانٹیں",
|
||||||
"slidable": "سلائیڈ ایبل",
|
"slidable": "سلائیڈ ایبل",
|
||||||
|
"etherscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﺮﮭﺘﯾﺍ",
|
||||||
"template_name": "ٹیمپلیٹ کا نام",
|
"template_name": "ٹیمپلیٹ کا نام",
|
||||||
"change_rep": " ۔ﮟﯾﺮﮐ ﻞﯾﺪﺒﺗ ﮦﺪﻨﺋﺎﻤﻧ",
|
"change_rep": "۔ﮟﯾﺮﮐ ﻞﯾﺪﺒﺗ ﮦﺪﻨﺋﺎﻤﻧ",
|
||||||
"change_rep_message": " ؟ﮟﯿﮨ ﮯﺘﮨﺎﭼ ﺎﻧﺮﮐ ﻞﯾﺪﺒﺗ ﻮﮐ ﮞﻭﺪﻨﺋﺎﻤﻧ ﯽﻌﻗﺍﻭ ﭖﺁ ﺎﯿﮐ"
|
"change_rep_message": "؟ﮟﯿﮨ ﮯﺘﮨﺎﭼ ﺎﻧﺮﮐ ﻞﯾﺪﺒﺗ ﻮﮐ ﮞﻭﺪﻨﺋﺎﻤﻧ ﯽﻌﻗﺍﻭ ﭖﺁ ﺎﯿﮐ"
|
||||||
}
|
}
|
||||||
|
|
|
@ -628,6 +628,18 @@
|
||||||
"setup_totp_recommended": "Sọ TOTP (Kẹṣọdọ)",
|
"setup_totp_recommended": "Sọ TOTP (Kẹṣọdọ)",
|
||||||
"disable_buy": "Ko iṣọrọ ọja",
|
"disable_buy": "Ko iṣọrọ ọja",
|
||||||
"disable_sell": "Ko iṣọrọ iṣọrọ",
|
"disable_sell": "Ko iṣọrọ iṣọrọ",
|
||||||
|
"cake_2fa_preset" : "Cake 2FA Tito",
|
||||||
|
"narrow": "Taara",
|
||||||
|
"normal": "Deede",
|
||||||
|
"aggressive": "Onítara",
|
||||||
|
"require_for_assessing_wallet": "Beere fun wiwọle si apamọwọ",
|
||||||
|
"require_for_sends_to_non_contacts" : "Beere fun fifiranṣẹ si awọn ti kii ṣe awọn olubasọrọ",
|
||||||
|
"require_for_sends_to_contacts" : "Beere fun fifiranṣẹ si awọn olubasọrọ",
|
||||||
|
"require_for_sends_to_internal_wallets" : "Beere fun fifiranṣẹ si awọn apamọwọ inu",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "Beere fun awọn paṣipaarọ si awọn apamọwọ inu",
|
||||||
|
"require_for_adding_contacts" : "Beere fun fifi awọn olubasọrọ kun",
|
||||||
|
"require_for_creating_new_wallets" : "Beere fun ṣiṣẹda titun Woleti",
|
||||||
|
"require_for_all_security_and_backup_settings" : "Beere fun gbogbo aabo ati awọn eto afẹyinti",
|
||||||
"available_balance_description": "“Iwọntunwọnsi Wa” tabi “Iwọntunwọnsi Ijẹrisi” jẹ awọn owo ti o le ṣee lo lẹsẹkẹsẹ. Ti awọn owo ba han ni iwọntunwọnsi kekere ṣugbọn kii ṣe iwọntunwọnsi oke, lẹhinna o gbọdọ duro iṣẹju diẹ fun awọn owo ti nwọle lati gba awọn ijẹrisi nẹtiwọọki diẹ sii. Lẹhin ti wọn gba awọn ijẹrisi diẹ sii, wọn yoo jẹ inawo.",
|
"available_balance_description": "“Iwọntunwọnsi Wa” tabi “Iwọntunwọnsi Ijẹrisi” jẹ awọn owo ti o le ṣee lo lẹsẹkẹsẹ. Ti awọn owo ba han ni iwọntunwọnsi kekere ṣugbọn kii ṣe iwọntunwọnsi oke, lẹhinna o gbọdọ duro iṣẹju diẹ fun awọn owo ti nwọle lati gba awọn ijẹrisi nẹtiwọọki diẹ sii. Lẹhin ti wọn gba awọn ijẹrisi diẹ sii, wọn yoo jẹ inawo.",
|
||||||
"syncing_wallet_alert_title": "Apamọwọ rẹ n muṣiṣẹpọ",
|
"syncing_wallet_alert_title": "Apamọwọ rẹ n muṣiṣẹpọ",
|
||||||
"syncing_wallet_alert_content": "Iwontunws.funfun rẹ ati atokọ idunadura le ma pari titi ti yoo fi sọ “SYNCHRONIZED” ni oke. Tẹ/tẹ ni kia kia lati ni imọ siwaju sii.",
|
"syncing_wallet_alert_content": "Iwontunws.funfun rẹ ati atokọ idunadura le ma pari titi ti yoo fi sọ “SYNCHRONIZED” ni oke. Tẹ/tẹ ni kia kia lati ni imọ siwaju sii.",
|
||||||
|
@ -652,6 +664,7 @@
|
||||||
"balance_page": "Oju-iwe iwọntunwọnsi",
|
"balance_page": "Oju-iwe iwọntunwọnsi",
|
||||||
"share": "Pinpin",
|
"share": "Pinpin",
|
||||||
"slidable": "Slidable",
|
"slidable": "Slidable",
|
||||||
|
"etherscan_history": "Etherscan itan",
|
||||||
"template_name": "Orukọ Awoṣe",
|
"template_name": "Orukọ Awoṣe",
|
||||||
"change_rep": "Yi Aṣoju",
|
"change_rep": "Yi Aṣoju",
|
||||||
"change_rep_message": "Ṣe o da ọ loju pe o fẹ yi awọn aṣoju pada?"
|
"change_rep_message": "Ṣe o da ọ loju pe o fẹ yi awọn aṣoju pada?"
|
||||||
|
|
|
@ -631,6 +631,18 @@
|
||||||
"setup_totp_recommended": "设置 TOTP(推荐)",
|
"setup_totp_recommended": "设置 TOTP(推荐)",
|
||||||
"disable_buy": "禁用购买操作",
|
"disable_buy": "禁用购买操作",
|
||||||
"disable_sell": "禁用卖出操作",
|
"disable_sell": "禁用卖出操作",
|
||||||
|
"cake_2fa_preset" : "蛋糕 2FA 预设",
|
||||||
|
"narrow": "狭窄的",
|
||||||
|
"normal": "普通的",
|
||||||
|
"aggressive": "过分热心",
|
||||||
|
"require_for_assessing_wallet": "需要访问钱包",
|
||||||
|
"require_for_sends_to_non_contacts" : "需要发送给非联系人",
|
||||||
|
"require_for_sends_to_contacts" : "需要发送给联系人",
|
||||||
|
"require_for_sends_to_internal_wallets" : "需要发送到内部钱包",
|
||||||
|
"require_for_exchanges_to_internal_wallets" : "需要兑换到内部钱包",
|
||||||
|
"require_for_adding_contacts" : "需要添加联系人",
|
||||||
|
"require_for_creating_new_wallets" : "创建新钱包的要求",
|
||||||
|
"require_for_all_security_and_backup_settings" : "需要所有安全和备份设置",
|
||||||
"available_balance_description": "可用余额是您可以使用的金额。冻结余额是您当前正在等待确认的金额。",
|
"available_balance_description": "可用余额是您可以使用的金额。冻结余额是您当前正在等待确认的金额。",
|
||||||
"syncing_wallet_alert_title": "您的钱包正在同步",
|
"syncing_wallet_alert_title": "您的钱包正在同步",
|
||||||
"syncing_wallet_alert_content": "您的余额和交易列表可能不完整,直到顶部显示“已同步”。单击/点击以了解更多信息。",
|
"syncing_wallet_alert_content": "您的余额和交易列表可能不完整,直到顶部显示“已同步”。单击/点击以了解更多信息。",
|
||||||
|
@ -655,6 +667,7 @@
|
||||||
"balance_page": "余额页",
|
"balance_page": "余额页",
|
||||||
"share": "分享",
|
"share": "分享",
|
||||||
"slidable": "可滑动",
|
"slidable": "可滑动",
|
||||||
|
"etherscan_history": "以太扫描历史",
|
||||||
"template_name": "模板名称",
|
"template_name": "模板名称",
|
||||||
"change_rep": "变革代表",
|
"change_rep": "变革代表",
|
||||||
"change_rep_message": "您确定要更换代表吗?"
|
"change_rep_message": "您确定要更换代表吗?"
|
||||||
|
|
|
@ -8,4 +8,6 @@ fi
|
||||||
cd ../..
|
cd ../..
|
||||||
sed -i "0,/version:/{s/version:.*/version: ${APP_ANDROID_VERSION}+${APP_ANDROID_BUILD_NUMBER}/}" ./pubspec.yaml
|
sed -i "0,/version:/{s/version:.*/version: ${APP_ANDROID_VERSION}+${APP_ANDROID_BUILD_NUMBER}/}" ./pubspec.yaml
|
||||||
sed -i "0,/version:/{s/__APP_PACKAGE__/${APP_ANDROID_PACKAGE}/}" ./android/app/src/main/AndroidManifest.xml
|
sed -i "0,/version:/{s/__APP_PACKAGE__/${APP_ANDROID_PACKAGE}/}" ./android/app/src/main/AndroidManifest.xml
|
||||||
cd scripts/android
|
sed -i "0,/version:/{s/__versionCode__/${APP_ANDROID_BUILD_NUMBER}/}" ./android/app/src/main/AndroidManifest.xml
|
||||||
|
sed -i "0,/version:/{s/__versionName__/${APP_ANDROID_VERSION}/}" ./android/app/src/main/AndroidManifest.xml
|
||||||
|
cd scripts/android
|
||||||
|
|
|
@ -494,6 +494,7 @@ abstract class HavenAccountList {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> generateEthereum(bool hasImplementation) async {
|
Future<void> generateEthereum(bool hasImplementation) async {
|
||||||
|
|
||||||
final outputFile = File(ethereumOutputPath);
|
final outputFile = File(ethereumOutputPath);
|
||||||
const ethereumCommonHeaders = """
|
const ethereumCommonHeaders = """
|
||||||
""";
|
""";
|
||||||
|
@ -530,7 +531,6 @@ abstract class Ethereum {
|
||||||
TransactionPriority getDefaultTransactionPriority();
|
TransactionPriority getDefaultTransactionPriority();
|
||||||
List<TransactionPriority> getTransactionPriorities();
|
List<TransactionPriority> getTransactionPriorities();
|
||||||
TransactionPriority deserializeEthereumTransactionPriority(int raw);
|
TransactionPriority deserializeEthereumTransactionPriority(int raw);
|
||||||
int getEstimatedFee(Object wallet, TransactionPriority priority);
|
|
||||||
|
|
||||||
Object createEthereumTransactionCredentials(
|
Object createEthereumTransactionCredentials(
|
||||||
List<Output> outputs, {
|
List<Output> outputs, {
|
||||||
|
@ -547,25 +547,26 @@ abstract class Ethereum {
|
||||||
});
|
});
|
||||||
|
|
||||||
int formatterEthereumParseAmount(String amount);
|
int formatterEthereumParseAmount(String amount);
|
||||||
double formatterEthereumAmountToDouble({required TransactionInfo transaction});
|
double formatterEthereumAmountToDouble({TransactionInfo? transaction, BigInt? amount, int exponent = 18});
|
||||||
List<Erc20Token> getERC20Currencies(WalletBase wallet);
|
List<Erc20Token> getERC20Currencies(WalletBase wallet);
|
||||||
Future<void> addErc20Token(WalletBase wallet, Erc20Token token);
|
Future<void> addErc20Token(WalletBase wallet, Erc20Token token);
|
||||||
Future<void> deleteErc20Token(WalletBase wallet, Erc20Token token);
|
Future<void> deleteErc20Token(WalletBase wallet, Erc20Token token);
|
||||||
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress);
|
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress);
|
||||||
|
|
||||||
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction);
|
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction);
|
||||||
|
void updateEtherscanUsageState(WalletBase wallet, bool isEnabled);
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
|
|
||||||
const ethereumEmptyDefinition = 'Ethereum? ethereum;\n';
|
const ethereumEmptyDefinition = 'Ethereum? ethereum;\n';
|
||||||
const ethereumCWDefinition = 'Ethereum? ethereum = CWEthereum();\n';
|
const ethereumCWDefinition = 'Ethereum? ethereum = CWEthereum();\n';
|
||||||
|
|
||||||
final output = '$ethereumCommonHeaders\n' +
|
final output = '$ethereumCommonHeaders\n'
|
||||||
(hasImplementation ? '$ethereumCWHeaders\n' : '\n') +
|
+ (hasImplementation ? '$ethereumCWHeaders\n' : '\n')
|
||||||
(hasImplementation ? '$ethereumCwPart\n\n' : '\n') +
|
+ (hasImplementation ? '$ethereumCwPart\n\n' : '\n')
|
||||||
(hasImplementation ? ethereumCWDefinition : ethereumEmptyDefinition) +
|
+ (hasImplementation ? ethereumCWDefinition : ethereumEmptyDefinition)
|
||||||
'\n' +
|
+ '\n'
|
||||||
ethereumContent;
|
+ ethereumContent;
|
||||||
|
|
||||||
if (outputFile.existsSync()) {
|
if (outputFile.existsSync()) {
|
||||||
await outputFile.delete();
|
await outputFile.delete();
|
||||||
|
|
Loading…
Reference in a new issue