mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 11:39:22 +00:00
CW-228 Auto generate monero subaddress (#902)
* Add UI and setting logic for subaddresses * Enable auto generate subaddresses * Rename variable * Add comment to unused code * Fix issue with initial state change * Fix observable for isAppSecure * Filter sub account contacts * Fix select account use unused address * Use add address if last address is unused * Fix auto generate wallet issues * Fix button color * Add translation and refactored naming * Fix PR review * Remove unused code * Remove unused overrides in electrum * Fix address info null check * CW-228 Fix ContactListViewModel condition * CW-228 Fix Account Tile; Rework updateAddressesInBox; Fix _getAllUnusedAddresses * CW-228 Fix unintentional address_page.dart regression * CW-228 Fix Merge Conflicts * CW-228 Add more translation Tools * CW-228 More merge conflict fixes * CW-228 Fix Merge Conflicts * CW-228 Auto Translation improvements * CW-228 Resolve requested Changes --------- Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com>
This commit is contained in:
parent
9999816850
commit
fff5a1c419
54 changed files with 603 additions and 200 deletions
21
cw_core/lib/address_info.dart
Normal file
21
cw_core/lib/address_info.dart
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import 'package:cw_core/hive_type_ids.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
|
||||||
|
part 'address_info.g.dart';
|
||||||
|
|
||||||
|
@HiveType(typeId: ADDRESS_INFO_TYPE_ID)
|
||||||
|
class AddressInfo extends HiveObject {
|
||||||
|
AddressInfo({required this.address, this.accountIndex, required this.label});
|
||||||
|
|
||||||
|
static const typeId = ADDRESS_INFO_TYPE_ID;
|
||||||
|
static const boxName = 'AddressInfo';
|
||||||
|
|
||||||
|
@HiveField(0)
|
||||||
|
int? accountIndex;
|
||||||
|
|
||||||
|
@HiveField(1, defaultValue: '')
|
||||||
|
String address;
|
||||||
|
|
||||||
|
@HiveField(2, defaultValue: '')
|
||||||
|
String label;
|
||||||
|
}
|
|
@ -9,5 +9,5 @@ const EXCHANGE_TEMPLATE_TYPE_ID = 7;
|
||||||
const ORDER_TYPE_ID = 8;
|
const ORDER_TYPE_ID = 8;
|
||||||
const UNSPENT_COINS_INFO_TYPE_ID = 9;
|
const UNSPENT_COINS_INFO_TYPE_ID = 9;
|
||||||
const ANONPAY_INVOICE_INFO_TYPE_ID = 10;
|
const ANONPAY_INVOICE_INFO_TYPE_ID = 10;
|
||||||
|
const ADDRESS_INFO_TYPE_ID = 11;
|
||||||
const ERC20_TOKEN_TYPE_ID = 12;
|
const ERC20_TOKEN_TYPE_ID = 12;
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
import 'package:cw_core/address_info.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
|
||||||
abstract class WalletAddresses {
|
abstract class WalletAddresses {
|
||||||
WalletAddresses(this.walletInfo)
|
WalletAddresses(this.walletInfo)
|
||||||
: addressesMap = {};
|
: addressesMap = {},
|
||||||
|
addressInfos = {};
|
||||||
|
|
||||||
final WalletInfo walletInfo;
|
final WalletInfo walletInfo;
|
||||||
|
|
||||||
|
@ -12,6 +14,10 @@ abstract class WalletAddresses {
|
||||||
|
|
||||||
Map<String, String> addressesMap;
|
Map<String, String> addressesMap;
|
||||||
|
|
||||||
|
Map<int, List<AddressInfo>> addressInfos;
|
||||||
|
|
||||||
|
Set<String> usedAddresses = {};
|
||||||
|
|
||||||
Future<void> init();
|
Future<void> init();
|
||||||
|
|
||||||
Future<void> updateAddressesInBox();
|
Future<void> updateAddressesInBox();
|
||||||
|
@ -20,6 +26,8 @@ abstract class WalletAddresses {
|
||||||
try {
|
try {
|
||||||
walletInfo.address = address;
|
walletInfo.address = address;
|
||||||
walletInfo.addresses = addressesMap;
|
walletInfo.addresses = addressesMap;
|
||||||
|
walletInfo.addressInfos = addressInfos;
|
||||||
|
walletInfo.usedAddresses = usedAddresses.toList();
|
||||||
|
|
||||||
if (walletInfo.isInBox) {
|
if (walletInfo.isInBox) {
|
||||||
await walletInfo.save();
|
await walletInfo.save();
|
||||||
|
|
|
@ -52,6 +52,10 @@ abstract class WalletBase<
|
||||||
|
|
||||||
late HistoryType transactionHistory;
|
late HistoryType transactionHistory;
|
||||||
|
|
||||||
|
set isEnabledAutoGenerateSubaddress(bool value) {}
|
||||||
|
|
||||||
|
bool get isEnabledAutoGenerateSubaddress => false;
|
||||||
|
|
||||||
Future<void> connectToNode({required Node node});
|
Future<void> connectToNode({required Node node});
|
||||||
|
|
||||||
Future<void> startSync();
|
Future<void> startSync();
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:cw_core/address_info.dart';
|
||||||
import 'package:cw_core/hive_type_ids.dart';
|
import 'package:cw_core/hive_type_ids.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';
|
||||||
|
@ -72,6 +73,12 @@ class WalletInfo extends HiveObject {
|
||||||
@HiveField(13)
|
@HiveField(13)
|
||||||
bool? showIntroCakePayCard;
|
bool? showIntroCakePayCard;
|
||||||
|
|
||||||
|
@HiveField(14)
|
||||||
|
Map<int, List<AddressInfo>>? addressInfos;
|
||||||
|
|
||||||
|
@HiveField(15)
|
||||||
|
List<String>? usedAddresses;
|
||||||
|
|
||||||
String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
|
String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
|
||||||
|
|
||||||
set yatLastUsedAddress(String address) {
|
set yatLastUsedAddress(String address) {
|
||||||
|
|
|
@ -12,8 +12,7 @@ import 'package:cw_core/monero_wallet_utils.dart';
|
||||||
import 'package:cw_haven/api/structs/pending_transaction.dart';
|
import 'package:cw_haven/api/structs/pending_transaction.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cw_haven/api/transaction_history.dart'
|
import 'package:cw_haven/api/transaction_history.dart' as haven_transaction_history;
|
||||||
as haven_transaction_history;
|
|
||||||
//import 'package:cw_haven/wallet.dart';
|
//import 'package:cw_haven/wallet.dart';
|
||||||
import 'package:cw_haven/api/wallet.dart' as haven_wallet;
|
import 'package:cw_haven/api/wallet.dart' as haven_wallet;
|
||||||
import 'package:cw_haven/api/transaction_history.dart' as transaction_history;
|
import 'package:cw_haven/api/transaction_history.dart' as transaction_history;
|
||||||
|
@ -37,8 +36,8 @@ const moneroBlockSize = 1000;
|
||||||
|
|
||||||
class HavenWallet = HavenWalletBase with _$HavenWallet;
|
class HavenWallet = HavenWalletBase with _$HavenWallet;
|
||||||
|
|
||||||
abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
abstract class HavenWalletBase
|
||||||
HavenTransactionHistory, HavenTransactionInfo> with Store {
|
extends WalletBase<MoneroBalance, HavenTransactionHistory, HavenTransactionInfo> with Store {
|
||||||
HavenWalletBase({required WalletInfo walletInfo})
|
HavenWalletBase({required WalletInfo walletInfo})
|
||||||
: balance = ObservableMap.of(getHavenBalance(accountIndex: 0)),
|
: balance = ObservableMap.of(getHavenBalance(accountIndex: 0)),
|
||||||
_isTransactionUpdating = false,
|
_isTransactionUpdating = false,
|
||||||
|
@ -47,8 +46,7 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
||||||
syncStatus = NotConnectedSyncStatus(),
|
syncStatus = NotConnectedSyncStatus(),
|
||||||
super(walletInfo) {
|
super(walletInfo) {
|
||||||
transactionHistory = HavenTransactionHistory();
|
transactionHistory = HavenTransactionHistory();
|
||||||
_onAccountChangeReaction = reaction((_) => walletAddresses.account,
|
_onAccountChangeReaction = reaction((_) => walletAddresses.account, (Account? account) {
|
||||||
(Account? account) {
|
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -96,14 +94,12 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
||||||
haven_wallet.setRecoveringFromSeed(isRecovery: walletInfo.isRecovery);
|
haven_wallet.setRecoveringFromSeed(isRecovery: walletInfo.isRecovery);
|
||||||
|
|
||||||
if (haven_wallet.getCurrentHeight() <= 1) {
|
if (haven_wallet.getCurrentHeight() <= 1) {
|
||||||
haven_wallet.setRefreshFromBlockHeight(
|
haven_wallet.setRefreshFromBlockHeight(height: walletInfo.restoreHeight);
|
||||||
height: walletInfo.restoreHeight);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_autoSaveTimer = Timer.periodic(
|
_autoSaveTimer =
|
||||||
Duration(seconds: _autoSaveInterval),
|
Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save());
|
||||||
(_) async => await save());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -115,7 +111,7 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
||||||
_onAccountChangeReaction?.reaction.dispose();
|
_onAccountChangeReaction?.reaction.dispose();
|
||||||
_autoSaveTimer?.cancel();
|
_autoSaveTimer?.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> connectToNode({required Node node}) async {
|
Future<void> connectToNode({required Node node}) async {
|
||||||
try {
|
try {
|
||||||
|
@ -170,26 +166,25 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasMultiDestination) {
|
if (hasMultiDestination) {
|
||||||
if (outputs.any((item) => item.sendAll
|
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
|
||||||
|| (item.formattedCryptoAmount ?? 0) <= 0)) {
|
throw HavenTransactionCreationException(
|
||||||
throw HavenTransactionCreationException('You do not have enough coins to send this amount.');
|
'You do not have enough coins to send this amount.');
|
||||||
}
|
}
|
||||||
|
|
||||||
final int totalAmount = outputs.fold(0, (acc, value) =>
|
final int totalAmount =
|
||||||
acc + (value.formattedCryptoAmount ?? 0));
|
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0));
|
||||||
|
|
||||||
if (unlockedBalance < totalAmount) {
|
if (unlockedBalance < totalAmount) {
|
||||||
throw HavenTransactionCreationException('You do not have enough coins to send this amount.');
|
throw HavenTransactionCreationException(
|
||||||
|
'You do not have enough coins to send this amount.');
|
||||||
}
|
}
|
||||||
|
|
||||||
final moneroOutputs = outputs.map((output) =>
|
final moneroOutputs = outputs
|
||||||
MoneroOutput(
|
.map((output) => MoneroOutput(
|
||||||
address: output.address,
|
address: output.address, amount: output.cryptoAmount!.replaceAll(',', '.')))
|
||||||
amount: output.cryptoAmount!.replaceAll(',', '.')))
|
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
pendingTransactionDescription =
|
pendingTransactionDescription = await transaction_history.createTransactionMultDest(
|
||||||
await transaction_history.createTransactionMultDest(
|
|
||||||
outputs: moneroOutputs,
|
outputs: moneroOutputs,
|
||||||
priorityRaw: _credentials.priority.serialize(),
|
priorityRaw: _credentials.priority.serialize(),
|
||||||
accountIndex: walletAddresses.account!.id);
|
accountIndex: walletAddresses.account!.id);
|
||||||
|
@ -198,12 +193,8 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
||||||
final address = output.isParsedAddress && (output.extractedAddress?.isNotEmpty ?? false)
|
final address = output.isParsedAddress && (output.extractedAddress?.isNotEmpty ?? false)
|
||||||
? output.extractedAddress!
|
? output.extractedAddress!
|
||||||
: output.address;
|
: output.address;
|
||||||
final amount = output.sendAll
|
final amount = output.sendAll ? null : output.cryptoAmount!.replaceAll(',', '.');
|
||||||
? null
|
final int? formattedAmount = output.sendAll ? null : output.formattedCryptoAmount;
|
||||||
: output.cryptoAmount!.replaceAll(',', '.');
|
|
||||||
final int? formattedAmount = output.sendAll
|
|
||||||
? null
|
|
||||||
: output.formattedCryptoAmount;
|
|
||||||
|
|
||||||
if ((formattedAmount != null && unlockedBalance < formattedAmount) ||
|
if ((formattedAmount != null && unlockedBalance < formattedAmount) ||
|
||||||
(formattedAmount == null && unlockedBalance <= 0)) {
|
(formattedAmount == null && unlockedBalance <= 0)) {
|
||||||
|
@ -213,8 +204,7 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
||||||
'You do not have enough unlocked balance. Unlocked: $formattedBalance. Transaction amount: ${output.cryptoAmount}.');
|
'You do not have enough unlocked balance. Unlocked: $formattedBalance. Transaction amount: ${output.cryptoAmount}.');
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingTransactionDescription =
|
pendingTransactionDescription = await transaction_history.createTransaction(
|
||||||
await transaction_history.createTransaction(
|
|
||||||
address: address,
|
address: address,
|
||||||
assetType: _credentials.assetType,
|
assetType: _credentials.assetType,
|
||||||
amount: amount,
|
amount: amount,
|
||||||
|
@ -307,16 +297,14 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
||||||
}
|
}
|
||||||
|
|
||||||
String getTransactionAddress(int accountIndex, int addressIndex) =>
|
String getTransactionAddress(int accountIndex, int addressIndex) =>
|
||||||
haven_wallet.getAddress(
|
haven_wallet.getAddress(accountIndex: accountIndex, addressIndex: addressIndex);
|
||||||
accountIndex: accountIndex,
|
|
||||||
addressIndex: addressIndex);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, HavenTransactionInfo>> fetchTransactions() async {
|
Future<Map<String, HavenTransactionInfo>> fetchTransactions() async {
|
||||||
haven_transaction_history.refreshTransactions();
|
haven_transaction_history.refreshTransactions();
|
||||||
return _getAllTransactions(null).fold<Map<String, HavenTransactionInfo>>(
|
return _getAllTransactions(null)
|
||||||
<String, HavenTransactionInfo>{},
|
.fold<Map<String, HavenTransactionInfo>>(<String, HavenTransactionInfo>{},
|
||||||
(Map<String, HavenTransactionInfo> acc, HavenTransactionInfo tx) {
|
(Map<String, HavenTransactionInfo> acc, HavenTransactionInfo tx) {
|
||||||
acc[tx.id] = tx;
|
acc[tx.id] = tx;
|
||||||
return acc;
|
return acc;
|
||||||
});
|
});
|
||||||
|
@ -340,9 +328,9 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
||||||
}
|
}
|
||||||
|
|
||||||
List<HavenTransactionInfo> _getAllTransactions(dynamic _) => haven_transaction_history
|
List<HavenTransactionInfo> _getAllTransactions(dynamic _) => haven_transaction_history
|
||||||
.getAllTransations()
|
.getAllTransations()
|
||||||
.map((row) => HavenTransactionInfo.fromRow(row))
|
.map((row) => HavenTransactionInfo.fromRow(row))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
void _setListeners() {
|
void _setListeners() {
|
||||||
_listener?.stop();
|
_listener?.stop();
|
||||||
|
@ -364,8 +352,7 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
||||||
}
|
}
|
||||||
|
|
||||||
int _getHeightDistance(DateTime date) {
|
int _getHeightDistance(DateTime date) {
|
||||||
final distance =
|
final distance = DateTime.now().millisecondsSinceEpoch - date.millisecondsSinceEpoch;
|
||||||
DateTime.now().millisecondsSinceEpoch - date.millisecondsSinceEpoch;
|
|
||||||
final daysTmp = (distance / 86400).round();
|
final daysTmp = (distance / 86400).round();
|
||||||
final days = daysTmp < 1 ? 1 : daysTmp;
|
final days = daysTmp < 1 ? 1 : daysTmp;
|
||||||
|
|
||||||
|
@ -386,8 +373,7 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
||||||
void _askForUpdateBalance() =>
|
void _askForUpdateBalance() =>
|
||||||
balance.addAll(getHavenBalance(accountIndex: walletAddresses.account!.id));
|
balance.addAll(getHavenBalance(accountIndex: walletAddresses.account!.id));
|
||||||
|
|
||||||
Future<void> _askForUpdateTransactionHistory() async =>
|
Future<void> _askForUpdateTransactionHistory() async => await updateTransactions();
|
||||||
await updateTransactions();
|
|
||||||
|
|
||||||
void _onNewBlock(int height, int blocksLeft, double ptc) async {
|
void _onNewBlock(int height, int blocksLeft, double ptc) async {
|
||||||
try {
|
try {
|
||||||
|
@ -404,9 +390,9 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
||||||
syncStatus = SyncedSyncStatus();
|
syncStatus = SyncedSyncStatus();
|
||||||
|
|
||||||
if (!_hasSyncAfterStartup) {
|
if (!_hasSyncAfterStartup) {
|
||||||
_hasSyncAfterStartup = true;
|
_hasSyncAfterStartup = true;
|
||||||
await save();
|
await save();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (walletInfo.isRecovery) {
|
if (walletInfo.isRecovery) {
|
||||||
await setAsRecovered();
|
await setAsRecovered();
|
||||||
|
|
|
@ -6,14 +6,15 @@ import 'package:cw_core/subaddress.dart';
|
||||||
|
|
||||||
part 'monero_subaddress_list.g.dart';
|
part 'monero_subaddress_list.g.dart';
|
||||||
|
|
||||||
class MoneroSubaddressList = MoneroSubaddressListBase
|
class MoneroSubaddressList = MoneroSubaddressListBase with _$MoneroSubaddressList;
|
||||||
with _$MoneroSubaddressList;
|
|
||||||
|
|
||||||
abstract class MoneroSubaddressListBase with Store {
|
abstract class MoneroSubaddressListBase with Store {
|
||||||
MoneroSubaddressListBase()
|
MoneroSubaddressListBase()
|
||||||
: _isRefreshing = false,
|
: _isRefreshing = false,
|
||||||
_isUpdating = false,
|
_isUpdating = false,
|
||||||
subaddresses = ObservableList<Subaddress>();
|
subaddresses = ObservableList<Subaddress>();
|
||||||
|
|
||||||
|
final List<String> _usedAddresses = [];
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
ObservableList<Subaddress> subaddresses;
|
ObservableList<Subaddress> subaddresses;
|
||||||
|
@ -49,20 +50,24 @@ abstract class MoneroSubaddressListBase with Store {
|
||||||
subaddresses = [primary] + rest.toList();
|
subaddresses = [primary] + rest.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return subaddresses
|
return subaddresses.map((subaddressRow) {
|
||||||
.map((subaddressRow) => Subaddress(
|
final hasDefaultAddressName =
|
||||||
|
subaddressRow.getLabel().toLowerCase() == 'Primary account'.toLowerCase() ||
|
||||||
|
subaddressRow.getLabel().toLowerCase() == 'Untitled account'.toLowerCase();
|
||||||
|
final isPrimaryAddress = subaddressRow.getId() == 0 && hasDefaultAddressName;
|
||||||
|
return Subaddress(
|
||||||
id: subaddressRow.getId(),
|
id: subaddressRow.getId(),
|
||||||
address: subaddressRow.getAddress(),
|
address: subaddressRow.getAddress(),
|
||||||
label: subaddressRow.getId() == 0 &&
|
label: isPrimaryAddress
|
||||||
subaddressRow.getLabel().toLowerCase() == 'Primary account'.toLowerCase()
|
? 'Primary address'
|
||||||
? 'Primary address'
|
: hasDefaultAddressName
|
||||||
: subaddressRow.getLabel()))
|
? ''
|
||||||
.toList();
|
: subaddressRow.getLabel());
|
||||||
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addSubaddress({required int accountIndex, required String label}) async {
|
Future<void> addSubaddress({required int accountIndex, required String label}) async {
|
||||||
await subaddress_list.addSubaddress(
|
await subaddress_list.addSubaddress(accountIndex: accountIndex, label: label);
|
||||||
accountIndex: accountIndex, label: label);
|
|
||||||
update(accountIndex: accountIndex);
|
update(accountIndex: accountIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,4 +93,59 @@ abstract class MoneroSubaddressListBase with Store {
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> updateWithAutoGenerate({
|
||||||
|
required int accountIndex,
|
||||||
|
required String defaultLabel,
|
||||||
|
required List<String> usedAddresses,
|
||||||
|
}) async {
|
||||||
|
_usedAddresses.addAll(usedAddresses);
|
||||||
|
if (_isUpdating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_isUpdating = true;
|
||||||
|
refresh(accountIndex: accountIndex);
|
||||||
|
subaddresses.clear();
|
||||||
|
final newSubAddresses =
|
||||||
|
await _getAllUnusedAddresses(accountIndex: accountIndex, label: defaultLabel);
|
||||||
|
subaddresses.addAll(newSubAddresses);
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
} finally {
|
||||||
|
_isUpdating = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Subaddress>> _getAllUnusedAddresses(
|
||||||
|
{required int accountIndex, required String label}) async {
|
||||||
|
final allAddresses = subaddress_list.getAllSubaddresses();
|
||||||
|
|
||||||
|
if (allAddresses.isEmpty || _usedAddresses.contains(allAddresses.last.getAddress())) {
|
||||||
|
final isAddressUnused = await _newSubaddress(accountIndex: accountIndex, label: label);
|
||||||
|
if (!isAddressUnused) {
|
||||||
|
return await _getAllUnusedAddresses(accountIndex: accountIndex, label: label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allAddresses
|
||||||
|
.map((subaddressRow) => Subaddress(
|
||||||
|
id: subaddressRow.getId(),
|
||||||
|
address: subaddressRow.getAddress(),
|
||||||
|
label: subaddressRow.getId() == 0 &&
|
||||||
|
subaddressRow.getLabel().toLowerCase() == 'Primary account'.toLowerCase()
|
||||||
|
? 'Primary address'
|
||||||
|
: subaddressRow.getLabel()))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _newSubaddress({required int accountIndex, required String label}) async {
|
||||||
|
await subaddress_list.addSubaddress(accountIndex: accountIndex, label: label);
|
||||||
|
|
||||||
|
return subaddress_list
|
||||||
|
.getAllSubaddresses()
|
||||||
|
.where((subaddressRow) => !_usedAddresses.contains(subaddressRow.getAddress()))
|
||||||
|
.isNotEmpty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,12 +48,14 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
}),
|
}),
|
||||||
_isTransactionUpdating = false,
|
_isTransactionUpdating = false,
|
||||||
_hasSyncAfterStartup = false,
|
_hasSyncAfterStartup = false,
|
||||||
walletAddresses = MoneroWalletAddresses(walletInfo),
|
isEnabledAutoGenerateSubaddress = false,
|
||||||
syncStatus = NotConnectedSyncStatus(),
|
syncStatus = NotConnectedSyncStatus(),
|
||||||
unspentCoins = [],
|
unspentCoins = [],
|
||||||
this.unspentCoinsInfo = unspentCoinsInfo,
|
this.unspentCoinsInfo = unspentCoinsInfo,
|
||||||
super(walletInfo) {
|
super(walletInfo) {
|
||||||
transactionHistory = MoneroTransactionHistory();
|
transactionHistory = MoneroTransactionHistory();
|
||||||
|
walletAddresses = MoneroWalletAddresses(walletInfo, transactionHistory);
|
||||||
|
|
||||||
_onAccountChangeReaction = reaction((_) => walletAddresses.account, (Account? account) {
|
_onAccountChangeReaction = reaction((_) => walletAddresses.account, (Account? account) {
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -64,7 +66,11 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
fullBalance: monero_wallet.getFullBalance(accountIndex: account.id),
|
fullBalance: monero_wallet.getFullBalance(accountIndex: account.id),
|
||||||
unlockedBalance: monero_wallet.getUnlockedBalance(accountIndex: account.id))
|
unlockedBalance: monero_wallet.getUnlockedBalance(accountIndex: account.id))
|
||||||
});
|
});
|
||||||
walletAddresses.updateSubaddressList(accountIndex: account.id);
|
_updateSubAddress(isEnabledAutoGenerateSubaddress, account: account);
|
||||||
|
});
|
||||||
|
|
||||||
|
reaction((_) => isEnabledAutoGenerateSubaddress, (bool enabled) {
|
||||||
|
_updateSubAddress(enabled, account: walletAddresses.account);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +79,11 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
Box<UnspentCoinsInfo> unspentCoinsInfo;
|
Box<UnspentCoinsInfo> unspentCoinsInfo;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MoneroWalletAddresses walletAddresses;
|
late MoneroWalletAddresses walletAddresses;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@observable
|
||||||
|
bool isEnabledAutoGenerateSubaddress;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@observable
|
@observable
|
||||||
|
@ -287,6 +297,14 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> save() async {
|
Future<void> save() async {
|
||||||
|
await walletAddresses.updateUsedSubaddress();
|
||||||
|
|
||||||
|
if (isEnabledAutoGenerateSubaddress) {
|
||||||
|
walletAddresses.updateUnusedSubaddress(
|
||||||
|
accountIndex: walletAddresses.account?.id ?? 0,
|
||||||
|
defaultLabel: walletAddresses.account?.label ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
await walletAddresses.updateAddressesInBox();
|
await walletAddresses.updateAddressesInBox();
|
||||||
await backupWalletFiles(name);
|
await backupWalletFiles(name);
|
||||||
await monero_wallet.store();
|
await monero_wallet.store();
|
||||||
|
@ -610,4 +628,15 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
||||||
print(e.toString());
|
print(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _updateSubAddress(bool enableAutoGenerate, {Account? account}) {
|
||||||
|
if (enableAutoGenerate) {
|
||||||
|
walletAddresses.updateUnusedSubaddress(
|
||||||
|
accountIndex: account?.id ?? 0,
|
||||||
|
defaultLabel: account?.label ?? '',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
walletAddresses.updateSubaddressList(accountIndex: account?.id ?? 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,32 @@
|
||||||
|
import 'package:cw_core/address_info.dart';
|
||||||
import 'package:cw_core/wallet_addresses.dart';
|
import 'package:cw_core/wallet_addresses.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_core/account.dart';
|
import 'package:cw_core/account.dart';
|
||||||
|
import 'package:cw_monero/api/wallet.dart';
|
||||||
import 'package:cw_monero/monero_account_list.dart';
|
import 'package:cw_monero/monero_account_list.dart';
|
||||||
import 'package:cw_monero/monero_subaddress_list.dart';
|
import 'package:cw_monero/monero_subaddress_list.dart';
|
||||||
import 'package:cw_core/subaddress.dart';
|
import 'package:cw_core/subaddress.dart';
|
||||||
|
import 'package:cw_monero/monero_transaction_history.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
part 'monero_wallet_addresses.g.dart';
|
part 'monero_wallet_addresses.g.dart';
|
||||||
|
|
||||||
class MoneroWalletAddresses = MoneroWalletAddressesBase
|
class MoneroWalletAddresses = MoneroWalletAddressesBase with _$MoneroWalletAddresses;
|
||||||
with _$MoneroWalletAddresses;
|
|
||||||
|
|
||||||
abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
|
abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
|
||||||
MoneroWalletAddressesBase(WalletInfo walletInfo)
|
MoneroWalletAddressesBase(
|
||||||
: accountList = MoneroAccountList(),
|
WalletInfo walletInfo, MoneroTransactionHistory moneroTransactionHistory)
|
||||||
subaddressList = MoneroSubaddressList(),
|
: accountList = MoneroAccountList(),
|
||||||
address = '',
|
_moneroTransactionHistory = moneroTransactionHistory,
|
||||||
super(walletInfo);
|
subaddressList = MoneroSubaddressList(),
|
||||||
|
address = '',
|
||||||
|
super(walletInfo);
|
||||||
|
|
||||||
|
final MoneroTransactionHistory _moneroTransactionHistory;
|
||||||
@override
|
@override
|
||||||
@observable
|
@observable
|
||||||
String address;
|
String address;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
Account? account;
|
Account? account;
|
||||||
|
|
||||||
|
@ -36,7 +41,6 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
accountList.update();
|
accountList.update();
|
||||||
account = accountList.accounts.first;
|
account = accountList.accounts.first;
|
||||||
updateSubaddressList(accountIndex: account?.id ?? 0);
|
|
||||||
await updateAddressesInBox();
|
await updateAddressesInBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,11 +50,15 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
|
||||||
final _subaddressList = MoneroSubaddressList();
|
final _subaddressList = MoneroSubaddressList();
|
||||||
|
|
||||||
addressesMap.clear();
|
addressesMap.clear();
|
||||||
|
addressInfos.clear();
|
||||||
|
|
||||||
accountList.accounts.forEach((account) {
|
accountList.accounts.forEach((account) {
|
||||||
_subaddressList.update(accountIndex: account.id);
|
_subaddressList.update(accountIndex: account.id);
|
||||||
_subaddressList.subaddresses.forEach((subaddress) {
|
_subaddressList.subaddresses.forEach((subaddress) {
|
||||||
addressesMap[subaddress.address] = subaddress.label;
|
addressesMap[subaddress.address] = subaddress.label;
|
||||||
|
addressInfos[account.id] ??= [];
|
||||||
|
addressInfos[account.id]?.add(AddressInfo(
|
||||||
|
address: subaddress.address, label: subaddress.label, accountIndex: account.id));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -62,14 +70,14 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
|
||||||
bool validate() {
|
bool validate() {
|
||||||
accountList.update();
|
accountList.update();
|
||||||
final accountListLength = accountList.accounts.length ?? 0;
|
final accountListLength = accountList.accounts.length;
|
||||||
|
|
||||||
if (accountListLength <= 0) {
|
if (accountListLength <= 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
subaddressList.update(accountIndex: accountList.accounts.first.id);
|
subaddressList.update(accountIndex: accountList.accounts.first.id);
|
||||||
final subaddressListLength = subaddressList.subaddresses.length ?? 0;
|
final subaddressListLength = subaddressList.subaddresses.length;
|
||||||
|
|
||||||
if (subaddressListLength <= 0) {
|
if (subaddressListLength <= 0) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -83,4 +91,24 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
|
||||||
subaddress = subaddressList.subaddresses.first;
|
subaddress = subaddressList.subaddresses.first;
|
||||||
address = subaddress!.address;
|
address = subaddress!.address;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
Future<void> updateUsedSubaddress() async {
|
||||||
|
final transactions = _moneroTransactionHistory.transactions.values.toList();
|
||||||
|
|
||||||
|
transactions.forEach((element) {
|
||||||
|
final accountIndex = element.accountIndex;
|
||||||
|
final addressIndex = element.addressIndex;
|
||||||
|
usedAddresses.add(getAddress(accountIndex: accountIndex, addressIndex: addressIndex));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateUnusedSubaddress(
|
||||||
|
{required int accountIndex, required String defaultLabel}) async {
|
||||||
|
await subaddressList.updateWithAutoGenerate(
|
||||||
|
accountIndex: accountIndex,
|
||||||
|
defaultLabel: defaultLabel,
|
||||||
|
usedAddresses: usedAddresses.toList());
|
||||||
|
subaddress = subaddressList.subaddresses.last;
|
||||||
|
address = subaddress!.address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -246,6 +246,7 @@ class BackupService {
|
||||||
final useEtherscan = data[PreferencesKey.useEtherscan] as bool?;
|
final useEtherscan = data[PreferencesKey.useEtherscan] as bool?;
|
||||||
final syncAll = data[PreferencesKey.syncAllKey] as bool?;
|
final syncAll = data[PreferencesKey.syncAllKey] as bool?;
|
||||||
final syncMode = data[PreferencesKey.syncModeKey] as int?;
|
final syncMode = data[PreferencesKey.syncModeKey] as int?;
|
||||||
|
final autoGenerateSubaddressStatus = data[PreferencesKey.autoGenerateSubaddressStatusKey] as int?;
|
||||||
|
|
||||||
await _sharedPreferences.setString(PreferencesKey.currentWalletName, currentWalletName);
|
await _sharedPreferences.setString(PreferencesKey.currentWalletName, currentWalletName);
|
||||||
|
|
||||||
|
@ -296,6 +297,9 @@ class BackupService {
|
||||||
|
|
||||||
if (fiatApiMode != null)
|
if (fiatApiMode != null)
|
||||||
await _sharedPreferences.setInt(PreferencesKey.currentFiatApiModeKey, fiatApiMode);
|
await _sharedPreferences.setInt(PreferencesKey.currentFiatApiModeKey, fiatApiMode);
|
||||||
|
if (autoGenerateSubaddressStatus != null)
|
||||||
|
await _sharedPreferences.setInt(PreferencesKey.autoGenerateSubaddressStatusKey,
|
||||||
|
autoGenerateSubaddressStatus);
|
||||||
|
|
||||||
if (currentPinLength != null)
|
if (currentPinLength != null)
|
||||||
await _sharedPreferences.setInt(PreferencesKey.currentPinLength, currentPinLength);
|
await _sharedPreferences.setInt(PreferencesKey.currentPinLength, currentPinLength);
|
||||||
|
@ -523,6 +527,8 @@ class BackupService {
|
||||||
_sharedPreferences.getInt(PreferencesKey.syncModeKey),
|
_sharedPreferences.getInt(PreferencesKey.syncModeKey),
|
||||||
PreferencesKey.syncAllKey:
|
PreferencesKey.syncAllKey:
|
||||||
_sharedPreferences.getBool(PreferencesKey.syncAllKey),
|
_sharedPreferences.getBool(PreferencesKey.syncAllKey),
|
||||||
|
PreferencesKey.autoGenerateSubaddressStatusKey:
|
||||||
|
_sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey),
|
||||||
};
|
};
|
||||||
|
|
||||||
return json.encode(preferences);
|
return json.encode(preferences);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
|
||||||
import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart';
|
import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart';
|
||||||
import 'package:cake_wallet/core/yat_service.dart';
|
import 'package:cake_wallet/core/yat_service.dart';
|
||||||
import 'package:cake_wallet/entities/background_tasks.dart';
|
import 'package:cake_wallet/entities/background_tasks.dart';
|
||||||
|
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
||||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||||
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
|
||||||
import 'package:cake_wallet/entities/receive_page_option.dart';
|
import 'package:cake_wallet/entities/receive_page_option.dart';
|
||||||
|
@ -245,7 +246,6 @@ Future<void> setup({
|
||||||
if (!_isSetupFinished) {
|
if (!_isSetupFinished) {
|
||||||
getIt.registerSingletonAsync<SharedPreferences>(() => SharedPreferences.getInstance());
|
getIt.registerSingletonAsync<SharedPreferences>(() => SharedPreferences.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_isSetupFinished) {
|
if (!_isSetupFinished) {
|
||||||
getIt.registerFactory(() => BackgroundTasks());
|
getIt.registerFactory(() => BackgroundTasks());
|
||||||
}
|
}
|
||||||
|
|
13
lib/entities/auto_generate_subaddress_status.dart
Normal file
13
lib/entities/auto_generate_subaddress_status.dart
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
enum AutoGenerateSubaddressStatus {
|
||||||
|
initialized(1),
|
||||||
|
enabled(2),
|
||||||
|
disabled(3);
|
||||||
|
|
||||||
|
const AutoGenerateSubaddressStatus(this.value);
|
||||||
|
final int value;
|
||||||
|
|
||||||
|
static AutoGenerateSubaddressStatus deserialize({required int raw}) =>
|
||||||
|
AutoGenerateSubaddressStatus.values.firstWhere((e) => e.value == raw);
|
||||||
|
|
||||||
|
}
|
|
@ -50,6 +50,7 @@ class PreferencesKey {
|
||||||
'${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';
|
'${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';
|
||||||
|
|
||||||
static const exchangeProvidersSelection = 'exchange-providers-selection';
|
static const exchangeProvidersSelection = 'exchange-providers-selection';
|
||||||
|
static const autoGenerateSubaddressStatusKey = 'auto_generate_subaddress_status';
|
||||||
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';
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:cake_wallet/buy/order.dart';
|
||||||
import 'package:cake_wallet/locales/locale.dart';
|
import 'package:cake_wallet/locales/locale.dart';
|
||||||
import 'package:cake_wallet/store/yat/yat_store.dart';
|
import 'package:cake_wallet/store/yat/yat_store.dart';
|
||||||
import 'package:cake_wallet/utils/exception_handler.dart';
|
import 'package:cake_wallet/utils/exception_handler.dart';
|
||||||
|
import 'package:cw_core/address_info.dart';
|
||||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||||
import 'package:cw_core/hive_type_ids.dart';
|
import 'package:cw_core/hive_type_ids.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
@ -89,6 +90,10 @@ Future<void> initializeAppConfigs() async {
|
||||||
CakeHive.registerAdapter(TradeAdapter());
|
CakeHive.registerAdapter(TradeAdapter());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!CakeHive.isAdapterRegistered(AddressInfo.typeId)) {
|
||||||
|
CakeHive.registerAdapter(AddressInfoAdapter());
|
||||||
|
}
|
||||||
|
|
||||||
if (!CakeHive.isAdapterRegistered(WalletInfo.typeId)) {
|
if (!CakeHive.isAdapterRegistered(WalletInfo.typeId)) {
|
||||||
CakeHive.registerAdapter(WalletInfoAdapter());
|
CakeHive.registerAdapter(WalletInfoAdapter());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
||||||
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||||
import 'package:cake_wallet/entities/update_haven_rate.dart';
|
import 'package:cake_wallet/entities/update_haven_rate.dart';
|
||||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
|
@ -21,36 +22,36 @@ ReactionDisposer? _onCurrentWalletChangeReaction;
|
||||||
ReactionDisposer? _onCurrentWalletChangeFiatRateUpdateReaction;
|
ReactionDisposer? _onCurrentWalletChangeFiatRateUpdateReaction;
|
||||||
//ReactionDisposer _onCurrentWalletAddressChangeReaction;
|
//ReactionDisposer _onCurrentWalletAddressChangeReaction;
|
||||||
|
|
||||||
void startCurrentWalletChangeReaction(AppStore appStore,
|
void startCurrentWalletChangeReaction(
|
||||||
SettingsStore settingsStore, FiatConversionStore fiatConversionStore) {
|
AppStore appStore, SettingsStore settingsStore, FiatConversionStore fiatConversionStore) {
|
||||||
_onCurrentWalletChangeReaction?.reaction.dispose();
|
_onCurrentWalletChangeReaction?.reaction.dispose();
|
||||||
_onCurrentWalletChangeFiatRateUpdateReaction?.reaction.dispose();
|
_onCurrentWalletChangeFiatRateUpdateReaction?.reaction.dispose();
|
||||||
//_onCurrentWalletAddressChangeReaction?.reaction?dispose();
|
//_onCurrentWalletAddressChangeReaction?.reaction?dispose();
|
||||||
|
|
||||||
//_onCurrentWalletAddressChangeReaction = reaction((_) => appStore.wallet.walletAddresses.address,
|
//_onCurrentWalletAddressChangeReaction = reaction((_) => appStore.wallet.walletAddresses.address,
|
||||||
//(String address) async {
|
//(String address) async {
|
||||||
//if (address == appStore.wallet.walletInfo.yatLastUsedAddress) {
|
//if (address == appStore.wallet.walletInfo.yatLastUsedAddress) {
|
||||||
// return;
|
// return;
|
||||||
//}
|
//}
|
||||||
|
|
||||||
//try {
|
//try {
|
||||||
// final yatStore = getIt.get<YatStore>();
|
// final yatStore = getIt.get<YatStore>();
|
||||||
// await updateEmojiIdAddress(
|
// await updateEmojiIdAddress(
|
||||||
// appStore.wallet.walletInfo.yatEmojiId,
|
// appStore.wallet.walletInfo.yatEmojiId,
|
||||||
// appStore.wallet.walletAddresses.address,
|
// appStore.wallet.walletAddresses.address,
|
||||||
// yatStore.apiKey,
|
// yatStore.apiKey,
|
||||||
// appStore.wallet.type
|
// appStore.wallet.type
|
||||||
// );
|
// );
|
||||||
// appStore.wallet.walletInfo.yatLastUsedAddress = address;
|
// appStore.wallet.walletInfo.yatLastUsedAddress = address;
|
||||||
// await appStore.wallet.walletInfo.save();
|
// await appStore.wallet.walletInfo.save();
|
||||||
//} catch (e) {
|
//} catch (e) {
|
||||||
// print(e.toString());
|
// print(e.toString());
|
||||||
//}
|
//}
|
||||||
//});
|
//});
|
||||||
|
|
||||||
_onCurrentWalletChangeReaction = reaction((_) => appStore.wallet, (WalletBase<
|
_onCurrentWalletChangeReaction = reaction((_) => appStore.wallet,
|
||||||
Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>?
|
(WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>?
|
||||||
wallet) async {
|
wallet) async {
|
||||||
try {
|
try {
|
||||||
if (wallet == null) {
|
if (wallet == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -59,11 +60,13 @@ void startCurrentWalletChangeReaction(AppStore appStore,
|
||||||
final node = settingsStore.getCurrentNode(wallet.type);
|
final node = settingsStore.getCurrentNode(wallet.type);
|
||||||
startWalletSyncStatusChangeReaction(wallet, fiatConversionStore);
|
startWalletSyncStatusChangeReaction(wallet, fiatConversionStore);
|
||||||
startCheckConnectionReaction(wallet, settingsStore);
|
startCheckConnectionReaction(wallet, settingsStore);
|
||||||
|
await getIt.get<SharedPreferences>().setString(PreferencesKey.currentWalletName, wallet.name);
|
||||||
await getIt
|
await getIt
|
||||||
.get<SharedPreferences>()
|
.get<SharedPreferences>()
|
||||||
.setString(PreferencesKey.currentWalletName, wallet.name);
|
.setInt(PreferencesKey.currentWalletType, serializeToInt(wallet.type));
|
||||||
await getIt.get<SharedPreferences>().setInt(
|
if (wallet.type == WalletType.monero) {
|
||||||
PreferencesKey.currentWalletType, serializeToInt(wallet.type));
|
_setAutoGenerateSubaddressStatus(wallet, settingsStore);
|
||||||
|
}
|
||||||
await wallet.connectToNode(node: node);
|
await wallet.connectToNode(node: node);
|
||||||
|
|
||||||
if (wallet.type == WalletType.haven) {
|
if (wallet.type == WalletType.haven) {
|
||||||
|
@ -82,9 +85,8 @@ void startCurrentWalletChangeReaction(AppStore appStore,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_onCurrentWalletChangeFiatRateUpdateReaction =
|
_onCurrentWalletChangeFiatRateUpdateReaction = reaction((_) => appStore.wallet,
|
||||||
reaction((_) => appStore.wallet, (WalletBase<Balance,
|
(WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>?
|
||||||
TransactionHistoryBase<TransactionInfo>, TransactionInfo>?
|
|
||||||
wallet) async {
|
wallet) async {
|
||||||
try {
|
try {
|
||||||
if (wallet == null || settingsStore.fiatApiMode == FiatApiMode.disabled) {
|
if (wallet == null || settingsStore.fiatApiMode == FiatApiMode.disabled) {
|
||||||
|
@ -92,11 +94,10 @@ void startCurrentWalletChangeReaction(AppStore appStore,
|
||||||
}
|
}
|
||||||
|
|
||||||
fiatConversionStore.prices[wallet.currency] = 0;
|
fiatConversionStore.prices[wallet.currency] = 0;
|
||||||
fiatConversionStore.prices[wallet.currency] =
|
fiatConversionStore.prices[wallet.currency] = await FiatConversionService.fetchPrice(
|
||||||
await FiatConversionService.fetchPrice(
|
crypto: wallet.currency,
|
||||||
crypto: wallet.currency,
|
fiat: settingsStore.fiatCurrency,
|
||||||
fiat: settingsStore.fiatCurrency,
|
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
||||||
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
|
||||||
|
|
||||||
if (wallet.type == WalletType.ethereum) {
|
if (wallet.type == WalletType.ethereum) {
|
||||||
final currencies =
|
final currencies =
|
||||||
|
@ -116,3 +117,17 @@ void startCurrentWalletChangeReaction(AppStore appStore,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _setAutoGenerateSubaddressStatus(
|
||||||
|
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet,
|
||||||
|
SettingsStore settingsStore,
|
||||||
|
) async {
|
||||||
|
final walletHasAddresses = await wallet.walletAddresses.addressesMap.length > 1;
|
||||||
|
if (settingsStore.autoGenerateSubaddressStatus == AutoGenerateSubaddressStatus.initialized &&
|
||||||
|
walletHasAddresses) {
|
||||||
|
settingsStore.autoGenerateSubaddressStatus = AutoGenerateSubaddressStatus.disabled;
|
||||||
|
}
|
||||||
|
wallet.isEnabledAutoGenerateSubaddress =
|
||||||
|
settingsStore.autoGenerateSubaddressStatus == AutoGenerateSubaddressStatus.enabled ||
|
||||||
|
settingsStore.autoGenerateSubaddressStatus == AutoGenerateSubaddressStatus.initialized;
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||||
|
import 'package:cake_wallet/di.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
|
||||||
import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
|
import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
|
||||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||||
import 'package:cake_wallet/entities/receive_page_option.dart';
|
import 'package:cake_wallet/entities/receive_page_option.dart';
|
||||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.dart';
|
import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.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/src/widgets/gradient_background.dart';
|
import 'package:cake_wallet/src/widgets/gradient_background.dart';
|
||||||
|
@ -24,7 +26,6 @@ import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
import 'package:keyboard_actions/keyboard_actions.dart';
|
import 'package:keyboard_actions/keyboard_actions.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:cake_wallet/di.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
|
||||||
|
|
||||||
|
@ -174,7 +175,11 @@ class AddressPage extends BasePage {
|
||||||
Observer(builder: (_) {
|
Observer(builder: (_) {
|
||||||
if (addressListViewModel.hasAddressList) {
|
if (addressListViewModel.hasAddressList) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => Navigator.of(context).pushNamed(Routes.receive),
|
onTap: () async => dashboardViewModel.isAutoGenerateSubaddressesEnabled
|
||||||
|
? await showPopUp<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => getIt.get<MoneroAccountListPage>())
|
||||||
|
: Navigator.of(context).pushNamed(Routes.receive),
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 50,
|
height: 50,
|
||||||
padding: EdgeInsets.only(left: 24, right: 12),
|
padding: EdgeInsets.only(left: 24, right: 12),
|
||||||
|
@ -193,17 +198,26 @@ class AddressPage extends BasePage {
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Observer(
|
Observer(
|
||||||
builder: (_) => Text(
|
builder: (_) {
|
||||||
addressListViewModel.hasAccounts
|
String label = addressListViewModel.hasAccounts
|
||||||
? S.of(context).accounts_subaddresses
|
? S.of(context).accounts_subaddresses
|
||||||
: S.of(context).addresses,
|
: S.of(context).addresses;
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
if (dashboardViewModel.isAutoGenerateSubaddressesEnabled) {
|
||||||
fontWeight: FontWeight.w500,
|
label = addressListViewModel.hasAccounts
|
||||||
color: Theme.of(context)
|
? S.of(context).accounts
|
||||||
|
: S.of(context).account;
|
||||||
|
}
|
||||||
|
return Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context)
|
||||||
.extension<SyncIndicatorTheme>()!
|
.extension<SyncIndicatorTheme>()!
|
||||||
.textColor),
|
.textColor),
|
||||||
)),
|
);
|
||||||
|
},),
|
||||||
Icon(
|
Icon(
|
||||||
Icons.arrow_forward_ios,
|
Icons.arrow_forward_ios,
|
||||||
size: 14,
|
size: 14,
|
||||||
|
@ -213,7 +227,7 @@ class AddressPage extends BasePage {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if (addressListViewModel.showElectrumAddressDisclaimer) {
|
} else if (dashboardViewModel.isAutoGenerateSubaddressesEnabled || addressListViewModel.showElectrumAddressDisclaimer) {
|
||||||
return Text(S.of(context).electrum_address_disclaimer,
|
return Text(S.of(context).electrum_address_disclaimer,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|
|
@ -5,13 +5,14 @@ import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
|
||||||
class AccountTile extends StatelessWidget {
|
class AccountTile extends StatelessWidget {
|
||||||
AccountTile(
|
AccountTile({
|
||||||
{required this.isCurrent,
|
required this.isCurrent,
|
||||||
required this.accountName,
|
required this.accountName,
|
||||||
this.accountBalance,
|
this.accountBalance,
|
||||||
required this.currency,
|
required this.currency,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
required this.onEdit});
|
required this.onEdit,
|
||||||
|
});
|
||||||
|
|
||||||
final bool isCurrent;
|
final bool isCurrent;
|
||||||
final String accountName;
|
final String accountName;
|
||||||
|
|
|
@ -73,7 +73,7 @@ class RestoreOptionsPage extends BasePage {
|
||||||
await restoreFromQRViewModel.create(restoreWallet: restoreWallet);
|
await restoreFromQRViewModel.create(restoreWallet: restoreWallet);
|
||||||
if (restoreFromQRViewModel.state is FailureState) {
|
if (restoreFromQRViewModel.state is FailureState) {
|
||||||
_onWalletCreateFailure(context,
|
_onWalletCreateFailure(context,
|
||||||
'Create wallet state: ${restoreFromQRViewModel.state.runtimeType.toString()}');
|
'Create wallet state: ${(restoreFromQRViewModel.state as FailureState).error}');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_onWalletCreateFailure(context, e.toString());
|
_onWalletCreateFailure(context, e.toString());
|
||||||
|
|
0
lib/src/screens/restore/widgets/backup_file_button.dart
Normal file
0
lib/src/screens/restore/widgets/backup_file_button.dart
Normal file
|
@ -50,6 +50,14 @@ class PrivacyPage extends BasePage {
|
||||||
onValueChange: (BuildContext _, bool value) {
|
onValueChange: (BuildContext _, bool value) {
|
||||||
_privacySettingsViewModel.setShouldSaveRecipientAddress(value);
|
_privacySettingsViewModel.setShouldSaveRecipientAddress(value);
|
||||||
}),
|
}),
|
||||||
|
if (_privacySettingsViewModel.isAutoGenerateSubaddressesVisible)
|
||||||
|
SettingsSwitcherCell(
|
||||||
|
title: S.current.auto_generate_subaddresses,
|
||||||
|
value: _privacySettingsViewModel.isAutoGenerateSubaddressesEnabled,
|
||||||
|
onValueChange: (BuildContext _, bool value) {
|
||||||
|
_privacySettingsViewModel.setAutoGenerateSubaddresses(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
if (DeviceInfo.instance.isMobile)
|
if (DeviceInfo.instance.isMobile)
|
||||||
SettingsSwitcherCell(
|
SettingsSwitcherCell(
|
||||||
title: S.current.prevent_screenshots,
|
title: S.current.prevent_screenshots,
|
||||||
|
|
|
@ -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/auto_generate_subaddress_status.dart';
|
||||||
import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
|
import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
|
||||||
import 'package:cake_wallet/entities/background_tasks.dart';
|
import 'package:cake_wallet/entities/background_tasks.dart';
|
||||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||||
|
@ -42,6 +43,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
required FiatCurrency initialFiatCurrency,
|
required FiatCurrency initialFiatCurrency,
|
||||||
required BalanceDisplayMode initialBalanceDisplayMode,
|
required BalanceDisplayMode initialBalanceDisplayMode,
|
||||||
required bool initialSaveRecipientAddress,
|
required bool initialSaveRecipientAddress,
|
||||||
|
required AutoGenerateSubaddressStatus initialAutoGenerateSubaddressStatus,
|
||||||
required bool initialAppSecure,
|
required bool initialAppSecure,
|
||||||
required bool initialDisableBuy,
|
required bool initialDisableBuy,
|
||||||
required bool initialDisableSell,
|
required bool initialDisableSell,
|
||||||
|
@ -87,6 +89,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
fiatCurrency = initialFiatCurrency,
|
fiatCurrency = initialFiatCurrency,
|
||||||
balanceDisplayMode = initialBalanceDisplayMode,
|
balanceDisplayMode = initialBalanceDisplayMode,
|
||||||
shouldSaveRecipientAddress = initialSaveRecipientAddress,
|
shouldSaveRecipientAddress = initialSaveRecipientAddress,
|
||||||
|
autoGenerateSubaddressStatus = initialAutoGenerateSubaddressStatus,
|
||||||
fiatApiMode = initialFiatMode,
|
fiatApiMode = initialFiatMode,
|
||||||
allowBiometricalAuthentication = initialAllowBiometricalAuthentication,
|
allowBiometricalAuthentication = initialAllowBiometricalAuthentication,
|
||||||
selectedCake2FAPreset = initialCake2FAPresetOptions,
|
selectedCake2FAPreset = initialCake2FAPresetOptions,
|
||||||
|
@ -197,6 +200,11 @@ abstract class SettingsStoreBase with Store {
|
||||||
(bool disableSell) =>
|
(bool disableSell) =>
|
||||||
sharedPreferences.setBool(PreferencesKey.disableSellKey, disableSell));
|
sharedPreferences.setBool(PreferencesKey.disableSellKey, disableSell));
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
(_) => autoGenerateSubaddressStatus,
|
||||||
|
(AutoGenerateSubaddressStatus autoGenerateSubaddressStatus) => sharedPreferences.setInt(
|
||||||
|
PreferencesKey.autoGenerateSubaddressStatusKey, autoGenerateSubaddressStatus.value));
|
||||||
|
|
||||||
reaction(
|
reaction(
|
||||||
(_) => fiatApiMode,
|
(_) => fiatApiMode,
|
||||||
(FiatApiMode mode) =>
|
(FiatApiMode mode) =>
|
||||||
|
@ -337,6 +345,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
static const defaultPinLength = 4;
|
static const defaultPinLength = 4;
|
||||||
static const defaultActionsMode = 11;
|
static const defaultActionsMode = 11;
|
||||||
static const defaultPinCodeTimeOutDuration = PinCodeRequiredDuration.tenminutes;
|
static const defaultPinCodeTimeOutDuration = PinCodeRequiredDuration.tenminutes;
|
||||||
|
static const defaultAutoGenerateSubaddressStatus = AutoGenerateSubaddressStatus.initialized;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
FiatCurrency fiatCurrency;
|
FiatCurrency fiatCurrency;
|
||||||
|
@ -359,6 +368,9 @@ abstract class SettingsStoreBase with Store {
|
||||||
@observable
|
@observable
|
||||||
bool shouldSaveRecipientAddress;
|
bool shouldSaveRecipientAddress;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
AutoGenerateSubaddressStatus autoGenerateSubaddressStatus;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
bool isAppSecure;
|
bool isAppSecure;
|
||||||
|
|
||||||
|
@ -602,7 +614,12 @@ abstract class SettingsStoreBase with Store {
|
||||||
final packageInfo = await PackageInfo.fromPlatform();
|
final packageInfo = await PackageInfo.fromPlatform();
|
||||||
final deviceName = await _getDeviceName() ?? '';
|
final deviceName = await _getDeviceName() ?? '';
|
||||||
final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
|
final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
|
||||||
|
final generateSubaddresses =
|
||||||
|
sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
|
||||||
|
|
||||||
|
final autoGenerateSubaddressStatus = generateSubaddresses != null
|
||||||
|
? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses)
|
||||||
|
: defaultAutoGenerateSubaddressStatus;
|
||||||
final nodes = <WalletType, Node>{};
|
final nodes = <WalletType, Node>{};
|
||||||
|
|
||||||
if (moneroNode != null) {
|
if (moneroNode != null) {
|
||||||
|
@ -640,6 +657,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
initialFiatCurrency: currentFiatCurrency,
|
initialFiatCurrency: currentFiatCurrency,
|
||||||
initialBalanceDisplayMode: currentBalanceDisplayMode,
|
initialBalanceDisplayMode: currentBalanceDisplayMode,
|
||||||
initialSaveRecipientAddress: shouldSaveRecipientAddress,
|
initialSaveRecipientAddress: shouldSaveRecipientAddress,
|
||||||
|
initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus,
|
||||||
initialAppSecure: isAppSecure,
|
initialAppSecure: isAppSecure,
|
||||||
initialDisableBuy: disableBuy,
|
initialDisableBuy: disableBuy,
|
||||||
initialDisableSell: disableSell,
|
initialDisableSell: disableSell,
|
||||||
|
@ -709,6 +727,13 @@ abstract class SettingsStoreBase with Store {
|
||||||
priority[WalletType.ethereum]!;
|
priority[WalletType.ethereum]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final generateSubaddresses =
|
||||||
|
sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
|
||||||
|
|
||||||
|
autoGenerateSubaddressStatus = generateSubaddresses != null
|
||||||
|
? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses)
|
||||||
|
: defaultAutoGenerateSubaddressStatus;
|
||||||
|
|
||||||
balanceDisplayMode = BalanceDisplayMode.deserialize(
|
balanceDisplayMode = BalanceDisplayMode.deserialize(
|
||||||
raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
|
raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
|
||||||
shouldSaveRecipientAddress =
|
shouldSaveRecipientAddress =
|
||||||
|
@ -719,8 +744,6 @@ abstract class SettingsStoreBase with Store {
|
||||||
|
|
||||||
numberOfFailedTokenTrials =
|
numberOfFailedTokenTrials =
|
||||||
sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? numberOfFailedTokenTrials;
|
sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? numberOfFailedTokenTrials;
|
||||||
sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ??
|
|
||||||
shouldSaveRecipientAddress;
|
|
||||||
isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure;
|
isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure;
|
||||||
disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? disableBuy;
|
disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? disableBuy;
|
||||||
disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? disableSell;
|
disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? disableSell;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
||||||
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:cake_wallet/store/settings_store.dart';
|
||||||
|
@ -10,6 +11,7 @@ import 'package:cake_wallet/entities/contact_record.dart';
|
||||||
import 'package:cake_wallet/entities/contact.dart';
|
import 'package:cake_wallet/entities/contact.dart';
|
||||||
import 'package:cake_wallet/utils/mobx.dart';
|
import 'package:cake_wallet/utils/mobx.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
part 'contact_list_view_model.g.dart';
|
part 'contact_list_view_model.g.dart';
|
||||||
|
|
||||||
|
@ -20,12 +22,26 @@ abstract class ContactListViewModelBase with Store {
|
||||||
ContactListViewModelBase(this.contactSource, this.walletInfoSource,
|
ContactListViewModelBase(this.contactSource, this.walletInfoSource,
|
||||||
this._currency, this.settingsStore)
|
this._currency, this.settingsStore)
|
||||||
: contacts = ObservableList<ContactRecord>(),
|
: contacts = ObservableList<ContactRecord>(),
|
||||||
walletContacts = [] {
|
walletContacts = [],
|
||||||
|
isAutoGenerateEnabled =
|
||||||
|
settingsStore.autoGenerateSubaddressStatus == AutoGenerateSubaddressStatus.enabled {
|
||||||
walletInfoSource.values.forEach((info) {
|
walletInfoSource.values.forEach((info) {
|
||||||
if (info.addresses?.isNotEmpty ?? false) {
|
if (isAutoGenerateEnabled && info.type == WalletType.monero && info.addressInfos != null) {
|
||||||
info.addresses?.forEach((address, label) {
|
info.addressInfos!.forEach((key, value) {
|
||||||
final name = label.isNotEmpty ? info.name + ' ($label)' : info.name;
|
final nextUnusedAddress = value.firstWhereOrNull(
|
||||||
|
(addressInfo) => !(info.usedAddresses?.contains(addressInfo.address) ?? false));
|
||||||
|
if (nextUnusedAddress != null) {
|
||||||
|
final name = _createName(info.name, nextUnusedAddress.label);
|
||||||
|
walletContacts.add(WalletContact(
|
||||||
|
nextUnusedAddress.address,
|
||||||
|
name,
|
||||||
|
walletTypeToCryptoCurrency(info.type),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (info.addresses?.isNotEmpty == true) {
|
||||||
|
info.addresses!.forEach((address, label) {
|
||||||
|
final name = _createName(info.name, label);
|
||||||
walletContacts.add(WalletContact(
|
walletContacts.add(WalletContact(
|
||||||
address,
|
address,
|
||||||
name,
|
name,
|
||||||
|
@ -40,6 +56,11 @@ abstract class ContactListViewModelBase with Store {
|
||||||
initialFire: true);
|
initialFire: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _createName(String walletName, String label) {
|
||||||
|
return label.isNotEmpty ? '$walletName ($label)' : walletName;
|
||||||
|
}
|
||||||
|
|
||||||
|
final bool isAutoGenerateEnabled;
|
||||||
final Box<Contact> contactSource;
|
final Box<Contact> contactSource;
|
||||||
final Box<WalletInfo> walletInfoSource;
|
final Box<WalletInfo> walletInfoSource;
|
||||||
final ObservableList<ContactRecord> contacts;
|
final ObservableList<ContactRecord> contacts;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
||||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||||
import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
|
import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
|
import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
|
||||||
|
@ -235,6 +236,10 @@ abstract class DashboardViewModelBase with Store {
|
||||||
@computed
|
@computed
|
||||||
double get price => balanceViewModel.price;
|
double get price => balanceViewModel.price;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get isAutoGenerateSubaddressesEnabled =>
|
||||||
|
settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
List<ActionListItem> get items {
|
List<ActionListItem> get items {
|
||||||
final _items = <ActionListItem>[];
|
final _items = <ActionListItem>[];
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
||||||
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/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
import 'package:cw_core/balance.dart';
|
||||||
|
import 'package:cw_core/transaction_history.dart';
|
||||||
|
import 'package:cw_core/transaction_info.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
@ -14,11 +18,27 @@ abstract class PrivacySettingsViewModelBase with Store {
|
||||||
PrivacySettingsViewModelBase(this._settingsStore, this._wallet);
|
PrivacySettingsViewModelBase(this._settingsStore, this._wallet);
|
||||||
|
|
||||||
final SettingsStore _settingsStore;
|
final SettingsStore _settingsStore;
|
||||||
final WalletBase _wallet;
|
final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> _wallet;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
ExchangeApiMode get exchangeStatus => _settingsStore.exchangeStatus;
|
ExchangeApiMode get exchangeStatus => _settingsStore.exchangeStatus;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get isAutoGenerateSubaddressesEnabled =>
|
||||||
|
_settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void setAutoGenerateSubaddresses(bool value) {
|
||||||
|
_wallet.isEnabledAutoGenerateSubaddress = value;
|
||||||
|
if (value) {
|
||||||
|
_settingsStore.autoGenerateSubaddressStatus = AutoGenerateSubaddressStatus.enabled;
|
||||||
|
} else {
|
||||||
|
_settingsStore.autoGenerateSubaddressStatus = AutoGenerateSubaddressStatus.disabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isAutoGenerateSubaddressesVisible => _wallet.type == WalletType.monero;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
bool get shouldSaveRecipientAddress => _settingsStore.shouldSaveRecipientAddress;
|
bool get shouldSaveRecipientAddress => _settingsStore.shouldSaveRecipientAddress;
|
||||||
|
|
||||||
|
|
|
@ -682,5 +682,7 @@
|
||||||
"support_title_other_links": "روابط دعم أخرى",
|
"support_title_other_links": "روابط دعم أخرى",
|
||||||
"support_description_other_links": "انضم إلى مجتمعاتنا أو تصل إلينا شركائنا من خلال أساليب أخرى",
|
"support_description_other_links": "انضم إلى مجتمعاتنا أو تصل إلينا شركائنا من خلال أساليب أخرى",
|
||||||
"select_destination": ".ﻲﻃﺎﻴﺘﺣﻻﺍ ﺦﺴﻨﻟﺍ ﻒﻠﻣ ﺔﻬﺟﻭ ﺪﻳﺪﺤﺗ ءﺎﺟﺮﻟﺍ",
|
"select_destination": ".ﻲﻃﺎﻴﺘﺣﻻﺍ ﺦﺴﻨﻟﺍ ﻒﻠﻣ ﺔﻬﺟﻭ ﺪﻳﺪﺤﺗ ءﺎﺟﺮﻟﺍ",
|
||||||
"save_to_downloads": "ﺕﻼﻳﺰﻨﺘﻟﺍ ﻲﻓ ﻆﻔﺣ"
|
"save_to_downloads": "ﺕﻼﻳﺰﻨﺘﻟﺍ ﻲﻓ ﻆﻔﺣ",
|
||||||
|
"support_description_other_links": "انضم إلى مجتمعاتنا أو تصل إلينا شركائنا من خلال أساليب أخرى",
|
||||||
|
"auto_generate_subaddresses": "تلقائي توليد subddresses"
|
||||||
}
|
}
|
||||||
|
|
|
@ -626,6 +626,7 @@
|
||||||
"setup_totp_recommended": "Настройка на TOTP (препоръчително)",
|
"setup_totp_recommended": "Настройка на TOTP (препоръчително)",
|
||||||
"disable_buy": "Деактивирайте действието за покупка",
|
"disable_buy": "Деактивирайте действието за покупка",
|
||||||
"disable_sell": "Деактивирайте действието за продажба",
|
"disable_sell": "Деактивирайте действието за продажба",
|
||||||
|
"auto_generate_subaddresses": "Автоматично генериране на подадреси",
|
||||||
"cake_2fa_preset": "Торта 2FA Preset",
|
"cake_2fa_preset": "Торта 2FA Preset",
|
||||||
"narrow": "Тесен",
|
"narrow": "Тесен",
|
||||||
"normal": "нормално",
|
"normal": "нормално",
|
||||||
|
|
|
@ -626,6 +626,7 @@
|
||||||
"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",
|
||||||
|
"auto_generate_subaddresses": "Automaticky generovat podadresy",
|
||||||
"cake_2fa_preset": "Předvolba Cake 2FA",
|
"cake_2fa_preset": "Předvolba Cake 2FA",
|
||||||
"narrow": "Úzký",
|
"narrow": "Úzký",
|
||||||
"normal": "Normální",
|
"normal": "Normální",
|
||||||
|
|
|
@ -640,6 +640,7 @@
|
||||||
"high_contrast_theme": "Kontrastreiches Thema",
|
"high_contrast_theme": "Kontrastreiches Thema",
|
||||||
"matrix_green_dark_theme": "Matrix Green Dark Theme",
|
"matrix_green_dark_theme": "Matrix Green Dark Theme",
|
||||||
"monero_light_theme": "Monero Light-Thema",
|
"monero_light_theme": "Monero Light-Thema",
|
||||||
|
"auto_generate_subaddresses": "Unteradressen automatisch generieren",
|
||||||
"cake_2fa_preset" : "Cake 2FA-Voreinstellung",
|
"cake_2fa_preset" : "Cake 2FA-Voreinstellung",
|
||||||
"narrow": "Eng",
|
"narrow": "Eng",
|
||||||
"normal": "Normal",
|
"normal": "Normal",
|
||||||
|
|
|
@ -568,6 +568,7 @@
|
||||||
"privacy": "Privacy",
|
"privacy": "Privacy",
|
||||||
"display_settings": "Display settings",
|
"display_settings": "Display settings",
|
||||||
"other_settings": "Other settings",
|
"other_settings": "Other settings",
|
||||||
|
"auto_generate_subaddresses": "Auto generate subaddresses",
|
||||||
"require_pin_after": "Require PIN after",
|
"require_pin_after": "Require PIN after",
|
||||||
"always": "Always",
|
"always": "Always",
|
||||||
"minutes_to_pin_code": "${minute} minutes",
|
"minutes_to_pin_code": "${minute} minutes",
|
||||||
|
|
|
@ -640,6 +640,7 @@
|
||||||
"high_contrast_theme": "Tema de alto contraste",
|
"high_contrast_theme": "Tema de alto contraste",
|
||||||
"matrix_green_dark_theme": "Matrix verde oscuro tema",
|
"matrix_green_dark_theme": "Matrix verde oscuro tema",
|
||||||
"monero_light_theme": "Tema ligero de Monero",
|
"monero_light_theme": "Tema ligero de Monero",
|
||||||
|
"auto_generate_subaddresses": "Generar subdirecciones automáticamente",
|
||||||
"cake_2fa_preset": "Pastel 2FA preestablecido",
|
"cake_2fa_preset": "Pastel 2FA preestablecido",
|
||||||
"narrow": "Angosto",
|
"narrow": "Angosto",
|
||||||
"normal": "Normal",
|
"normal": "Normal",
|
||||||
|
|
|
@ -640,6 +640,7 @@
|
||||||
"high_contrast_theme": "Thème à contraste élevé",
|
"high_contrast_theme": "Thème à contraste élevé",
|
||||||
"matrix_green_dark_theme": "Thème Matrix Green Dark",
|
"matrix_green_dark_theme": "Thème Matrix Green Dark",
|
||||||
"monero_light_theme": "Thème de lumière Monero",
|
"monero_light_theme": "Thème de lumière Monero",
|
||||||
|
"auto_generate_subaddresses": "Générer automatiquement des sous-adresses",
|
||||||
"cake_2fa_preset": "Gâteau 2FA prédéfini",
|
"cake_2fa_preset": "Gâteau 2FA prédéfini",
|
||||||
"narrow": "Étroit",
|
"narrow": "Étroit",
|
||||||
"normal": "Normal",
|
"normal": "Normal",
|
||||||
|
|
|
@ -618,6 +618,7 @@
|
||||||
"high_contrast_theme": "Babban Jigon Kwatance",
|
"high_contrast_theme": "Babban Jigon Kwatance",
|
||||||
"matrix_green_dark_theme": "Matrix Green Dark Jigo",
|
"matrix_green_dark_theme": "Matrix Green Dark Jigo",
|
||||||
"monero_light_theme": "Jigon Hasken Monero",
|
"monero_light_theme": "Jigon Hasken Monero",
|
||||||
|
"auto_generate_subaddresses": "Saɓaƙa subaddresses ta kai tsaye",
|
||||||
"cake_2fa_preset": "Cake 2FA saiti",
|
"cake_2fa_preset": "Cake 2FA saiti",
|
||||||
"narrow": "kunkuntar",
|
"narrow": "kunkuntar",
|
||||||
"normal": "Na al'ada",
|
"normal": "Na al'ada",
|
||||||
|
|
|
@ -640,6 +640,7 @@
|
||||||
"high_contrast_theme": "उच्च कंट्रास्ट थीम",
|
"high_contrast_theme": "उच्च कंट्रास्ट थीम",
|
||||||
"matrix_green_dark_theme": "मैट्रिक्स ग्रीन डार्क थीम",
|
"matrix_green_dark_theme": "मैट्रिक्स ग्रीन डार्क थीम",
|
||||||
"monero_light_theme": "मोनेरो लाइट थीम",
|
"monero_light_theme": "मोनेरो लाइट थीम",
|
||||||
|
"auto_generate_subaddresses": "स्वचालित रूप से उप-पते उत्पन्न करें",
|
||||||
"cake_2fa_preset": "केक 2एफए प्रीसेट",
|
"cake_2fa_preset": "केक 2एफए प्रीसेट",
|
||||||
"narrow": "सँकरा",
|
"narrow": "सँकरा",
|
||||||
"normal": "सामान्य",
|
"normal": "सामान्य",
|
||||||
|
|
|
@ -640,6 +640,7 @@
|
||||||
"high_contrast_theme": "Tema visokog kontrasta",
|
"high_contrast_theme": "Tema visokog kontrasta",
|
||||||
"matrix_green_dark_theme": "Matrix Green Dark Theme",
|
"matrix_green_dark_theme": "Matrix Green Dark Theme",
|
||||||
"monero_light_theme": "Monero lagana tema",
|
"monero_light_theme": "Monero lagana tema",
|
||||||
|
"auto_generate_subaddresses": "Automatski generirajte podadrese",
|
||||||
"cake_2fa_preset": "Cake 2FA Preset",
|
"cake_2fa_preset": "Cake 2FA Preset",
|
||||||
"narrow": "Usko",
|
"narrow": "Usko",
|
||||||
"normal": "Normalno",
|
"normal": "Normalno",
|
||||||
|
|
|
@ -622,6 +622,7 @@
|
||||||
"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",
|
||||||
|
"auto_generate_subaddresses": "Menghasilkan subalamat secara otomatis",
|
||||||
"cake_2fa_preset": "Preset Kue 2FA",
|
"cake_2fa_preset": "Preset Kue 2FA",
|
||||||
"narrow": "Sempit",
|
"narrow": "Sempit",
|
||||||
"normal": "Normal",
|
"normal": "Normal",
|
||||||
|
|
|
@ -634,6 +634,7 @@
|
||||||
"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",
|
||||||
|
"auto_generate_subaddresses": "Genera automaticamente sottindirizzi",
|
||||||
"cake_2fa_preset": "Torta 2FA Preset",
|
"cake_2fa_preset": "Torta 2FA Preset",
|
||||||
"narrow": "Stretto",
|
"narrow": "Stretto",
|
||||||
"normal": "Normale",
|
"normal": "Normale",
|
||||||
|
|
|
@ -686,5 +686,6 @@
|
||||||
"support_title_other_links": "その他のサポートリンク",
|
"support_title_other_links": "その他のサポートリンク",
|
||||||
"support_description_other_links": "私たちのコミュニティに参加するか、他の方法を通して私たちのパートナーに連絡してください",
|
"support_description_other_links": "私たちのコミュニティに参加するか、他の方法を通して私たちのパートナーに連絡してください",
|
||||||
"select_destination": "バックアップファイルの保存先を選択してください。",
|
"select_destination": "バックアップファイルの保存先を選択してください。",
|
||||||
"save_to_downloads": "ダウンロードに保存"
|
"save_to_downloads": "ダウンロードに保存",
|
||||||
|
"auto_generate_subaddresses": "Autoはサブアドレスを生成します"
|
||||||
}
|
}
|
||||||
|
|
|
@ -686,5 +686,6 @@
|
||||||
"support_title_other_links": "다른 지원 링크",
|
"support_title_other_links": "다른 지원 링크",
|
||||||
"support_description_other_links": "다른 방법을 통해 커뮤니티에 가입하거나 파트너에게 연락하십시오.",
|
"support_description_other_links": "다른 방법을 통해 커뮤니티에 가입하거나 파트너에게 연락하십시오.",
|
||||||
"select_destination": "백업 파일의 대상을 선택하십시오.",
|
"select_destination": "백업 파일의 대상을 선택하십시오.",
|
||||||
"save_to_downloads": "다운로드에 저장"
|
"save_to_downloads": "다운로드에 저장",
|
||||||
|
"auto_generate_subaddresses": "자동 생성 서브 아드 드레스"
|
||||||
}
|
}
|
||||||
|
|
|
@ -684,5 +684,6 @@
|
||||||
"support_title_other_links": "အခြားအထောက်အပံ့လင့်များ",
|
"support_title_other_links": "အခြားအထောက်အပံ့လင့်များ",
|
||||||
"support_description_other_links": "ကျွန်ုပ်တို့၏လူမှုအသိုင်းအဝိုင်းများသို့ 0 င်ရောက်ပါ",
|
"support_description_other_links": "ကျွန်ုပ်တို့၏လူမှုအသိုင်းအဝိုင်းများသို့ 0 င်ရောက်ပါ",
|
||||||
"select_destination": "အရန်ဖိုင်အတွက် ဦးတည်ရာကို ရွေးပါ။",
|
"select_destination": "အရန်ဖိုင်အတွက် ဦးတည်ရာကို ရွေးပါ။",
|
||||||
"save_to_downloads": "ဒေါင်းလုဒ်များထံ သိမ်းဆည်းပါ။"
|
"save_to_downloads": "ဒေါင်းလုဒ်များထံ သိမ်းဆည်းပါ။",
|
||||||
|
"auto_generate_subaddresses": "အော်တို Generate Subaddresses"
|
||||||
}
|
}
|
||||||
|
|
|
@ -634,6 +634,7 @@
|
||||||
"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",
|
||||||
|
"auto_generate_subaddresses": "Automatisch subadressen genereren",
|
||||||
"cake_2fa_preset": "Taart 2FA Voorinstelling",
|
"cake_2fa_preset": "Taart 2FA Voorinstelling",
|
||||||
"narrow": "Smal",
|
"narrow": "Smal",
|
||||||
"normal": "Normaal",
|
"normal": "Normaal",
|
||||||
|
|
|
@ -634,6 +634,7 @@
|
||||||
"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",
|
||||||
|
"auto_generate_subaddresses": "Automatycznie generuj podadresy",
|
||||||
"cake_2fa_preset": "Ciasto 2FA Preset",
|
"cake_2fa_preset": "Ciasto 2FA Preset",
|
||||||
"narrow": "Wąski",
|
"narrow": "Wąski",
|
||||||
"normal": "Normalna",
|
"normal": "Normalna",
|
||||||
|
|
|
@ -633,6 +633,7 @@
|
||||||
"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",
|
||||||
|
"auto_generate_subaddresses": "Gerar subendereços automaticamente",
|
||||||
"cake_2fa_preset": "Predefinição de bolo 2FA",
|
"cake_2fa_preset": "Predefinição de bolo 2FA",
|
||||||
"narrow": "Estreito",
|
"narrow": "Estreito",
|
||||||
"normal": "Normal",
|
"normal": "Normal",
|
||||||
|
|
|
@ -686,5 +686,6 @@
|
||||||
"support_title_other_links": "Другие ссылки на поддержку",
|
"support_title_other_links": "Другие ссылки на поддержку",
|
||||||
"support_description_other_links": "Присоединяйтесь к нашим сообществам или охватите нас наших партнеров с помощью других методов",
|
"support_description_other_links": "Присоединяйтесь к нашим сообществам или охватите нас наших партнеров с помощью других методов",
|
||||||
"select_destination": "Пожалуйста, выберите место для файла резервной копии.",
|
"select_destination": "Пожалуйста, выберите место для файла резервной копии.",
|
||||||
"save_to_downloads": "Сохранить в загрузках"
|
"save_to_downloads": "Сохранить в загрузках",
|
||||||
|
"auto_generate_subaddresses": "Авто генерируйте Subaddresses"
|
||||||
}
|
}
|
||||||
|
|
|
@ -684,5 +684,6 @@
|
||||||
"support_title_other_links": "ลิงค์สนับสนุนอื่น ๆ",
|
"support_title_other_links": "ลิงค์สนับสนุนอื่น ๆ",
|
||||||
"support_description_other_links": "เข้าร่วมชุมชนของเราหรือเข้าถึงเราพันธมิตรของเราผ่านวิธีการอื่น ๆ",
|
"support_description_other_links": "เข้าร่วมชุมชนของเราหรือเข้าถึงเราพันธมิตรของเราผ่านวิธีการอื่น ๆ",
|
||||||
"select_destination": "โปรดเลือกปลายทางสำหรับไฟล์สำรอง",
|
"select_destination": "โปรดเลือกปลายทางสำหรับไฟล์สำรอง",
|
||||||
"save_to_downloads": "บันทึกลงดาวน์โหลด"
|
"save_to_downloads": "บันทึกลงดาวน์โหลด",
|
||||||
|
"auto_generate_subaddresses": "Auto สร้าง subaddresses"
|
||||||
}
|
}
|
||||||
|
|
|
@ -632,6 +632,7 @@
|
||||||
"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",
|
"disable_sell": "Satış işlemini devre dışı bırak",
|
||||||
|
"auto_generate_subaddresses": "Alt adresleri otomatik olarak oluştur",
|
||||||
"cake_2fa_preset": "Kek 2FA Ön Ayarı",
|
"cake_2fa_preset": "Kek 2FA Ön Ayarı",
|
||||||
"narrow": "Dar",
|
"narrow": "Dar",
|
||||||
"normal": "Normal",
|
"normal": "Normal",
|
||||||
|
|
|
@ -634,6 +634,7 @@
|
||||||
"setup_totp_recommended": "Налаштувати TOTP (рекомендовано)",
|
"setup_totp_recommended": "Налаштувати TOTP (рекомендовано)",
|
||||||
"disable_buy": "Вимкнути дію покупки",
|
"disable_buy": "Вимкнути дію покупки",
|
||||||
"disable_sell": "Вимкнути дію продажу",
|
"disable_sell": "Вимкнути дію продажу",
|
||||||
|
"auto_generate_subaddresses": "Автоматично генерувати підадреси",
|
||||||
"cake_2fa_preset": "Торт 2FA Preset",
|
"cake_2fa_preset": "Торт 2FA Preset",
|
||||||
"narrow": "вузькі",
|
"narrow": "вузькі",
|
||||||
"normal": "нормальний",
|
"normal": "нормальний",
|
||||||
|
|
|
@ -678,5 +678,6 @@
|
||||||
"support_title_other_links": "دوسرے سپورٹ لنکس",
|
"support_title_other_links": "دوسرے سپورٹ لنکس",
|
||||||
"support_description_other_links": "ہماری برادریوں میں شامل ہوں یا دوسرے طریقوں سے ہمارے شراکت داروں تک پہنچیں",
|
"support_description_other_links": "ہماری برادریوں میں شامل ہوں یا دوسرے طریقوں سے ہمارے شراکت داروں تک پہنچیں",
|
||||||
"select_destination": "۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﻝﺰﻨﻣ ﮯﯿﻟ ﮯﮐ ﻞﺋﺎﻓ ﭖﺍ ﮏﯿﺑ ﻡﺮﮐ ﮦﺍﺮﺑ",
|
"select_destination": "۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﻝﺰﻨﻣ ﮯﯿﻟ ﮯﮐ ﻞﺋﺎﻓ ﭖﺍ ﮏﯿﺑ ﻡﺮﮐ ﮦﺍﺮﺑ",
|
||||||
"save_to_downloads": "۔ﮟﯾﺮﮐ ﻅﻮﻔﺤﻣ ﮟﯿﻣ ﺯﮈﻮﻟ ﻥﺅﺍﮈ"
|
"save_to_downloads": "۔ﮟﯾﺮﮐ ﻅﻮﻔﺤﻣ ﮟﯿﻣ ﺯﮈﻮﻟ ﻥﺅﺍﮈ",
|
||||||
|
"auto_generate_subaddresses": "آٹو سب ایڈریس تیار کرتا ہے"
|
||||||
}
|
}
|
||||||
|
|
|
@ -680,5 +680,6 @@
|
||||||
"matrix_green_dark_theme": "Matrix Green Dark Akori",
|
"matrix_green_dark_theme": "Matrix Green Dark Akori",
|
||||||
"monero_light_theme": "Monero Light Akori",
|
"monero_light_theme": "Monero Light Akori",
|
||||||
"select_destination": "Jọwọ yan ibi ti o nlo fun faili afẹyinti.",
|
"select_destination": "Jọwọ yan ibi ti o nlo fun faili afẹyinti.",
|
||||||
"save_to_downloads": "Fipamọ si Awọn igbasilẹ"
|
"save_to_downloads": "Fipamọ si Awọn igbasilẹ",
|
||||||
|
"auto_generate_subaddresses": "Aṣiṣe Ibi-Afọwọkọ"
|
||||||
}
|
}
|
||||||
|
|
|
@ -685,5 +685,6 @@
|
||||||
"matrix_green_dark_theme": "矩阵绿暗主题",
|
"matrix_green_dark_theme": "矩阵绿暗主题",
|
||||||
"monero_light_theme": "门罗币浅色主题",
|
"monero_light_theme": "门罗币浅色主题",
|
||||||
"select_destination": "请选择备份文件的目的地。",
|
"select_destination": "请选择备份文件的目的地。",
|
||||||
"save_to_downloads": "保存到下载"
|
"save_to_downloads": "保存到下载",
|
||||||
|
"auto_generate_subaddresses": "自动生成子辅助"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'utils/translation/arb_file_utils.dart';
|
||||||
import 'dart:io';
|
import 'utils/translation/translation_constants.dart';
|
||||||
|
import 'utils/translation/translation_utils.dart';
|
||||||
import 'package:translator/translator.dart';
|
|
||||||
|
|
||||||
const defaultLang = "en";
|
|
||||||
const langs = [
|
|
||||||
"ar", "bg", "cs", "de", "en", "es", "fr", "ha", "hi", "hr", "id", "it",
|
|
||||||
"ja", "ko", "my", "nl", "pl", "pt", "ru", "th", "tr", "uk", "ur", "yo",
|
|
||||||
"zh-cn" // zh, but Google Translate uses zh-cn for Chinese (Simplified)
|
|
||||||
];
|
|
||||||
final translator = GoogleTranslator();
|
|
||||||
|
|
||||||
void main(List<String> args) async {
|
void main(List<String> args) async {
|
||||||
if (args.length != 2) {
|
if (args.length != 2) {
|
||||||
|
@ -23,44 +14,9 @@ void main(List<String> args) async {
|
||||||
print('Appending "$name": "$text"');
|
print('Appending "$name": "$text"');
|
||||||
|
|
||||||
for (var lang in langs) {
|
for (var lang in langs) {
|
||||||
final fileName = getFileName(lang);
|
final fileName = getArbFileName(lang);
|
||||||
final translation = await getTranslation(text, lang);
|
final translation = await getTranslation(text, lang);
|
||||||
|
|
||||||
appendArbFile(fileName, name, translation);
|
appendStringToArbFile(fileName, name, translation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void appendArbFile(String fileName, String name, String text) {
|
|
||||||
final file = File(fileName);
|
|
||||||
final inputContent = file.readAsStringSync();
|
|
||||||
final arbObj = json.decode(inputContent) as Map<String, dynamic>;
|
|
||||||
|
|
||||||
if (arbObj.containsKey(name)) {
|
|
||||||
print("String $name already exists in $fileName!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
arbObj.addAll({name: text});
|
|
||||||
|
|
||||||
final outputContent = json
|
|
||||||
.encode(arbObj)
|
|
||||||
.replaceAll('","', '",\n "')
|
|
||||||
.replaceAll('{"', '{\n "')
|
|
||||||
.replaceAll('"}', '"\n}')
|
|
||||||
.replaceAll('":"', '": "');
|
|
||||||
|
|
||||||
file.writeAsStringSync(outputContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Future<String> getTranslation(String text, String lang) async {
|
|
||||||
if (lang == defaultLang) return text;
|
|
||||||
return (await translator.translate(text, from: defaultLang, to: lang)).text;
|
|
||||||
}
|
|
||||||
|
|
||||||
String getFileName(String lang) {
|
|
||||||
final shortLang = lang
|
|
||||||
.split("-")
|
|
||||||
.first;
|
|
||||||
return "./res/values/strings_$shortLang.arb";
|
|
||||||
}
|
|
||||||
|
|
37
tool/translation_consistence.dart
Normal file
37
tool/translation_consistence.dart
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'utils/translation/arb_file_utils.dart';
|
||||||
|
import 'utils/translation/translation_constants.dart';
|
||||||
|
import 'utils/translation/translation_utils.dart';
|
||||||
|
|
||||||
|
void main(List<String> args) async {
|
||||||
|
print('Checking Consistency of all arb-files. Default: $defaultLang');
|
||||||
|
|
||||||
|
final doFix = args.contains("--fix");
|
||||||
|
|
||||||
|
if (doFix)
|
||||||
|
print('Auto fixing enabled!\n');
|
||||||
|
else
|
||||||
|
print('Auto fixing disabled!\nRun with arg "--fix" to enable autofix\n');
|
||||||
|
|
||||||
|
final fileName = getArbFileName(defaultLang);
|
||||||
|
final file = File(fileName);
|
||||||
|
final arbObj = readArbFile(file);
|
||||||
|
|
||||||
|
for (var lang in langs) {
|
||||||
|
final fileName = getArbFileName(lang);
|
||||||
|
final missingKeys = getMissingKeysInArbFile(fileName, arbObj.keys);
|
||||||
|
if (missingKeys.isNotEmpty) {
|
||||||
|
final missingDefaults = <String, String>{};
|
||||||
|
|
||||||
|
missingKeys.forEach((key) {
|
||||||
|
print('Missing in "$lang": "$key"');
|
||||||
|
if (doFix)
|
||||||
|
missingDefaults[key] = arbObj[key] as String;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (missingDefaults.isNotEmpty)
|
||||||
|
await appendTranslations(lang, missingDefaults);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
66
tool/utils/translation/arb_file_utils.dart
Normal file
66
tool/utils/translation/arb_file_utils.dart
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
void appendStringToArbFile(String fileName, String name, String text) {
|
||||||
|
final file = File(fileName);
|
||||||
|
final arbObj = readArbFile(file);
|
||||||
|
|
||||||
|
if (arbObj.containsKey(name)) {
|
||||||
|
print("String $name already exists in $fileName!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
arbObj.addAll({name: text});
|
||||||
|
|
||||||
|
final outputContent = json
|
||||||
|
.encode(arbObj)
|
||||||
|
.replaceAll('","', '",\n "')
|
||||||
|
.replaceAll('{"', '{\n "')
|
||||||
|
.replaceAll('"}', '"\n}')
|
||||||
|
.replaceAll('":"', '": "');
|
||||||
|
|
||||||
|
file.writeAsStringSync(outputContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void appendStringsToArbFile(String fileName, Map<String, String> strings) {
|
||||||
|
final file = File(fileName);
|
||||||
|
final arbObj = readArbFile(file);
|
||||||
|
|
||||||
|
arbObj.addAll(strings);
|
||||||
|
|
||||||
|
final outputContent = json
|
||||||
|
.encode(arbObj)
|
||||||
|
.replaceAll('","', '",\n "')
|
||||||
|
.replaceAll('{"', '{\n "')
|
||||||
|
.replaceAll('"}', '"\n}')
|
||||||
|
.replaceAll('":"', '": "');
|
||||||
|
|
||||||
|
file.writeAsStringSync(outputContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> readArbFile(File file) {
|
||||||
|
final inputContent = file.readAsStringSync();
|
||||||
|
|
||||||
|
return json.decode(inputContent) as Map<String, dynamic>;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getArbFileName(String lang) {
|
||||||
|
final shortLang = lang
|
||||||
|
.split("-")
|
||||||
|
.first;
|
||||||
|
return "./res/values/strings_$shortLang.arb";
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> getMissingKeysInArbFile(String fileName, Iterable<String> langKeys) {
|
||||||
|
final file = File(fileName);
|
||||||
|
final arbObj = readArbFile(file);
|
||||||
|
final results = <String>[];
|
||||||
|
|
||||||
|
for (var langKey in langKeys) {
|
||||||
|
if (!arbObj.containsKey(langKey)) {
|
||||||
|
results.add(langKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
6
tool/utils/translation/translation_constants.dart
Normal file
6
tool/utils/translation/translation_constants.dart
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
const defaultLang = "en";
|
||||||
|
const langs = [
|
||||||
|
"ar", "bg", "cs", "de", "en", "es", "fr", "ha", "hi", "hr", "id", "it",
|
||||||
|
"ja", "ko", "my", "nl", "pl", "pt", "ru", "th", "tr", "uk", "ur", "yo",
|
||||||
|
"zh-cn" // zh, but Google Translate uses zh-cn for Chinese (Simplified)
|
||||||
|
];
|
37
tool/utils/translation/translation_utils.dart
Normal file
37
tool/utils/translation/translation_utils.dart
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import 'package:translator/translator.dart';
|
||||||
|
|
||||||
|
import 'arb_file_utils.dart';
|
||||||
|
import 'translation_constants.dart';
|
||||||
|
|
||||||
|
final translator = GoogleTranslator();
|
||||||
|
|
||||||
|
Future<void> appendTranslation(String lang, String key, String text) async {
|
||||||
|
final fileName = getArbFileName(lang);
|
||||||
|
final translation = await getTranslation(text, lang);
|
||||||
|
|
||||||
|
appendStringToArbFile(fileName, key, translation);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> appendTranslations(String lang, Map<String, String> defaults) async {
|
||||||
|
final fileName = getArbFileName(lang);
|
||||||
|
final translations = <String, String>{};
|
||||||
|
|
||||||
|
for (var key in defaults.keys) {
|
||||||
|
final value = defaults[key]!;
|
||||||
|
|
||||||
|
if (value.contains("{")) continue;
|
||||||
|
final translation = await getTranslation(value, lang);
|
||||||
|
|
||||||
|
translations[key] = translation;
|
||||||
|
}
|
||||||
|
|
||||||
|
print(translations);
|
||||||
|
|
||||||
|
appendStringsToArbFile(fileName, translations);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> getTranslation(String text, String lang) async {
|
||||||
|
if (lang == defaultLang) return text;
|
||||||
|
return (await translator.translate(text, from: defaultLang, to: lang)).text;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue