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 UNSPENT_COINS_INFO_TYPE_ID = 9;
|
||||
const ANONPAY_INVOICE_INFO_TYPE_ID = 10;
|
||||
|
||||
const ADDRESS_INFO_TYPE_ID = 11;
|
||||
const ERC20_TOKEN_TYPE_ID = 12;
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import 'package:cw_core/address_info.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
|
||||
abstract class WalletAddresses {
|
||||
WalletAddresses(this.walletInfo)
|
||||
: addressesMap = {};
|
||||
: addressesMap = {},
|
||||
addressInfos = {};
|
||||
|
||||
final WalletInfo walletInfo;
|
||||
|
||||
|
@ -12,6 +14,10 @@ abstract class WalletAddresses {
|
|||
|
||||
Map<String, String> addressesMap;
|
||||
|
||||
Map<int, List<AddressInfo>> addressInfos;
|
||||
|
||||
Set<String> usedAddresses = {};
|
||||
|
||||
Future<void> init();
|
||||
|
||||
Future<void> updateAddressesInBox();
|
||||
|
@ -20,6 +26,8 @@ abstract class WalletAddresses {
|
|||
try {
|
||||
walletInfo.address = address;
|
||||
walletInfo.addresses = addressesMap;
|
||||
walletInfo.addressInfos = addressInfos;
|
||||
walletInfo.usedAddresses = usedAddresses.toList();
|
||||
|
||||
if (walletInfo.isInBox) {
|
||||
await walletInfo.save();
|
||||
|
|
|
@ -52,6 +52,10 @@ abstract class WalletBase<
|
|||
|
||||
late HistoryType transactionHistory;
|
||||
|
||||
set isEnabledAutoGenerateSubaddress(bool value) {}
|
||||
|
||||
bool get isEnabledAutoGenerateSubaddress => false;
|
||||
|
||||
Future<void> connectToNode({required Node node});
|
||||
|
||||
Future<void> startSync();
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'package:cw_core/address_info.dart';
|
||||
import 'package:cw_core/hive_type_ids.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
@ -72,6 +73,12 @@ class WalletInfo extends HiveObject {
|
|||
@HiveField(13)
|
||||
bool? showIntroCakePayCard;
|
||||
|
||||
@HiveField(14)
|
||||
Map<int, List<AddressInfo>>? addressInfos;
|
||||
|
||||
@HiveField(15)
|
||||
List<String>? usedAddresses;
|
||||
|
||||
String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
|
||||
|
||||
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:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_haven/api/transaction_history.dart'
|
||||
as haven_transaction_history;
|
||||
import 'package:cw_haven/api/transaction_history.dart' as haven_transaction_history;
|
||||
//import 'package:cw_haven/wallet.dart';
|
||||
import 'package:cw_haven/api/wallet.dart' as haven_wallet;
|
||||
import 'package:cw_haven/api/transaction_history.dart' as transaction_history;
|
||||
|
@ -37,8 +36,8 @@ const moneroBlockSize = 1000;
|
|||
|
||||
class HavenWallet = HavenWalletBase with _$HavenWallet;
|
||||
|
||||
abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
||||
HavenTransactionHistory, HavenTransactionInfo> with Store {
|
||||
abstract class HavenWalletBase
|
||||
extends WalletBase<MoneroBalance, HavenTransactionHistory, HavenTransactionInfo> with Store {
|
||||
HavenWalletBase({required WalletInfo walletInfo})
|
||||
: balance = ObservableMap.of(getHavenBalance(accountIndex: 0)),
|
||||
_isTransactionUpdating = false,
|
||||
|
@ -47,8 +46,7 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
|||
syncStatus = NotConnectedSyncStatus(),
|
||||
super(walletInfo) {
|
||||
transactionHistory = HavenTransactionHistory();
|
||||
_onAccountChangeReaction = reaction((_) => walletAddresses.account,
|
||||
(Account? account) {
|
||||
_onAccountChangeReaction = reaction((_) => walletAddresses.account, (Account? account) {
|
||||
if (account == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -96,14 +94,12 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
|||
haven_wallet.setRecoveringFromSeed(isRecovery: walletInfo.isRecovery);
|
||||
|
||||
if (haven_wallet.getCurrentHeight() <= 1) {
|
||||
haven_wallet.setRefreshFromBlockHeight(
|
||||
height: walletInfo.restoreHeight);
|
||||
haven_wallet.setRefreshFromBlockHeight(height: walletInfo.restoreHeight);
|
||||
}
|
||||
}
|
||||
|
||||
_autoSaveTimer = Timer.periodic(
|
||||
Duration(seconds: _autoSaveInterval),
|
||||
(_) async => await save());
|
||||
_autoSaveTimer =
|
||||
Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save());
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -170,26 +166,25 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
|||
}
|
||||
|
||||
if (hasMultiDestination) {
|
||||
if (outputs.any((item) => item.sendAll
|
||||
|| (item.formattedCryptoAmount ?? 0) <= 0)) {
|
||||
throw HavenTransactionCreationException('You do not have enough coins to send this amount.');
|
||||
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
|
||||
throw HavenTransactionCreationException(
|
||||
'You do not have enough coins to send this amount.');
|
||||
}
|
||||
|
||||
final int totalAmount = outputs.fold(0, (acc, value) =>
|
||||
acc + (value.formattedCryptoAmount ?? 0));
|
||||
final int totalAmount =
|
||||
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0));
|
||||
|
||||
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) =>
|
||||
MoneroOutput(
|
||||
address: output.address,
|
||||
amount: output.cryptoAmount!.replaceAll(',', '.')))
|
||||
final moneroOutputs = outputs
|
||||
.map((output) => MoneroOutput(
|
||||
address: output.address, amount: output.cryptoAmount!.replaceAll(',', '.')))
|
||||
.toList();
|
||||
|
||||
pendingTransactionDescription =
|
||||
await transaction_history.createTransactionMultDest(
|
||||
pendingTransactionDescription = await transaction_history.createTransactionMultDest(
|
||||
outputs: moneroOutputs,
|
||||
priorityRaw: _credentials.priority.serialize(),
|
||||
accountIndex: walletAddresses.account!.id);
|
||||
|
@ -198,12 +193,8 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
|||
final address = output.isParsedAddress && (output.extractedAddress?.isNotEmpty ?? false)
|
||||
? output.extractedAddress!
|
||||
: output.address;
|
||||
final amount = output.sendAll
|
||||
? null
|
||||
: output.cryptoAmount!.replaceAll(',', '.');
|
||||
final int? formattedAmount = output.sendAll
|
||||
? null
|
||||
: output.formattedCryptoAmount;
|
||||
final amount = output.sendAll ? null : output.cryptoAmount!.replaceAll(',', '.');
|
||||
final int? formattedAmount = output.sendAll ? null : output.formattedCryptoAmount;
|
||||
|
||||
if ((formattedAmount != null && unlockedBalance < formattedAmount) ||
|
||||
(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}.');
|
||||
}
|
||||
|
||||
pendingTransactionDescription =
|
||||
await transaction_history.createTransaction(
|
||||
pendingTransactionDescription = await transaction_history.createTransaction(
|
||||
address: address,
|
||||
assetType: _credentials.assetType,
|
||||
amount: amount,
|
||||
|
@ -307,15 +297,13 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
|||
}
|
||||
|
||||
String getTransactionAddress(int accountIndex, int addressIndex) =>
|
||||
haven_wallet.getAddress(
|
||||
accountIndex: accountIndex,
|
||||
addressIndex: addressIndex);
|
||||
haven_wallet.getAddress(accountIndex: accountIndex, addressIndex: addressIndex);
|
||||
|
||||
@override
|
||||
Future<Map<String, HavenTransactionInfo>> fetchTransactions() async {
|
||||
haven_transaction_history.refreshTransactions();
|
||||
return _getAllTransactions(null).fold<Map<String, HavenTransactionInfo>>(
|
||||
<String, HavenTransactionInfo>{},
|
||||
return _getAllTransactions(null)
|
||||
.fold<Map<String, HavenTransactionInfo>>(<String, HavenTransactionInfo>{},
|
||||
(Map<String, HavenTransactionInfo> acc, HavenTransactionInfo tx) {
|
||||
acc[tx.id] = tx;
|
||||
return acc;
|
||||
|
@ -364,8 +352,7 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
|||
}
|
||||
|
||||
int _getHeightDistance(DateTime date) {
|
||||
final distance =
|
||||
DateTime.now().millisecondsSinceEpoch - date.millisecondsSinceEpoch;
|
||||
final distance = DateTime.now().millisecondsSinceEpoch - date.millisecondsSinceEpoch;
|
||||
final daysTmp = (distance / 86400).round();
|
||||
final days = daysTmp < 1 ? 1 : daysTmp;
|
||||
|
||||
|
@ -386,8 +373,7 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
|||
void _askForUpdateBalance() =>
|
||||
balance.addAll(getHavenBalance(accountIndex: walletAddresses.account!.id));
|
||||
|
||||
Future<void> _askForUpdateTransactionHistory() async =>
|
||||
await updateTransactions();
|
||||
Future<void> _askForUpdateTransactionHistory() async => await updateTransactions();
|
||||
|
||||
void _onNewBlock(int height, int blocksLeft, double ptc) async {
|
||||
try {
|
||||
|
|
|
@ -6,8 +6,7 @@ import 'package:cw_core/subaddress.dart';
|
|||
|
||||
part 'monero_subaddress_list.g.dart';
|
||||
|
||||
class MoneroSubaddressList = MoneroSubaddressListBase
|
||||
with _$MoneroSubaddressList;
|
||||
class MoneroSubaddressList = MoneroSubaddressListBase with _$MoneroSubaddressList;
|
||||
|
||||
abstract class MoneroSubaddressListBase with Store {
|
||||
MoneroSubaddressListBase()
|
||||
|
@ -15,6 +14,8 @@ abstract class MoneroSubaddressListBase with Store {
|
|||
_isUpdating = false,
|
||||
subaddresses = ObservableList<Subaddress>();
|
||||
|
||||
final List<String> _usedAddresses = [];
|
||||
|
||||
@observable
|
||||
ObservableList<Subaddress> subaddresses;
|
||||
|
||||
|
@ -49,20 +50,24 @@ abstract class MoneroSubaddressListBase with Store {
|
|||
subaddresses = [primary] + rest.toList();
|
||||
}
|
||||
|
||||
return subaddresses
|
||||
.map((subaddressRow) => Subaddress(
|
||||
return subaddresses.map((subaddressRow) {
|
||||
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(),
|
||||
address: subaddressRow.getAddress(),
|
||||
label: subaddressRow.getId() == 0 &&
|
||||
subaddressRow.getLabel().toLowerCase() == 'Primary account'.toLowerCase()
|
||||
label: isPrimaryAddress
|
||||
? 'Primary address'
|
||||
: subaddressRow.getLabel()))
|
||||
.toList();
|
||||
: hasDefaultAddressName
|
||||
? ''
|
||||
: subaddressRow.getLabel());
|
||||
}).toList();
|
||||
}
|
||||
|
||||
Future<void> addSubaddress({required int accountIndex, required String label}) async {
|
||||
await subaddress_list.addSubaddress(
|
||||
accountIndex: accountIndex, label: label);
|
||||
await subaddress_list.addSubaddress(accountIndex: accountIndex, label: label);
|
||||
update(accountIndex: accountIndex);
|
||||
}
|
||||
|
||||
|
@ -88,4 +93,59 @@ abstract class MoneroSubaddressListBase with Store {
|
|||
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,
|
||||
_hasSyncAfterStartup = false,
|
||||
walletAddresses = MoneroWalletAddresses(walletInfo),
|
||||
isEnabledAutoGenerateSubaddress = false,
|
||||
syncStatus = NotConnectedSyncStatus(),
|
||||
unspentCoins = [],
|
||||
this.unspentCoinsInfo = unspentCoinsInfo,
|
||||
super(walletInfo) {
|
||||
transactionHistory = MoneroTransactionHistory();
|
||||
walletAddresses = MoneroWalletAddresses(walletInfo, transactionHistory);
|
||||
|
||||
_onAccountChangeReaction = reaction((_) => walletAddresses.account, (Account? account) {
|
||||
if (account == null) {
|
||||
return;
|
||||
|
@ -64,7 +66,11 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
fullBalance: monero_wallet.getFullBalance(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;
|
||||
|
||||
@override
|
||||
MoneroWalletAddresses walletAddresses;
|
||||
late MoneroWalletAddresses walletAddresses;
|
||||
|
||||
@override
|
||||
@observable
|
||||
bool isEnabledAutoGenerateSubaddress;
|
||||
|
||||
@override
|
||||
@observable
|
||||
|
@ -287,6 +297,14 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
await walletAddresses.updateUsedSubaddress();
|
||||
|
||||
if (isEnabledAutoGenerateSubaddress) {
|
||||
walletAddresses.updateUnusedSubaddress(
|
||||
accountIndex: walletAddresses.account?.id ?? 0,
|
||||
defaultLabel: walletAddresses.account?.label ?? '');
|
||||
}
|
||||
|
||||
await walletAddresses.updateAddressesInBox();
|
||||
await backupWalletFiles(name);
|
||||
await monero_wallet.store();
|
||||
|
@ -610,4 +628,15 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
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,23 +1,28 @@
|
|||
import 'package:cw_core/address_info.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_info.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_subaddress_list.dart';
|
||||
import 'package:cw_core/subaddress.dart';
|
||||
import 'package:cw_monero/monero_transaction_history.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'monero_wallet_addresses.g.dart';
|
||||
|
||||
class MoneroWalletAddresses = MoneroWalletAddressesBase
|
||||
with _$MoneroWalletAddresses;
|
||||
class MoneroWalletAddresses = MoneroWalletAddressesBase with _$MoneroWalletAddresses;
|
||||
|
||||
abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
|
||||
MoneroWalletAddressesBase(WalletInfo walletInfo)
|
||||
MoneroWalletAddressesBase(
|
||||
WalletInfo walletInfo, MoneroTransactionHistory moneroTransactionHistory)
|
||||
: accountList = MoneroAccountList(),
|
||||
_moneroTransactionHistory = moneroTransactionHistory,
|
||||
subaddressList = MoneroSubaddressList(),
|
||||
address = '',
|
||||
super(walletInfo);
|
||||
|
||||
final MoneroTransactionHistory _moneroTransactionHistory;
|
||||
@override
|
||||
@observable
|
||||
String address;
|
||||
|
@ -36,7 +41,6 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
|
|||
Future<void> init() async {
|
||||
accountList.update();
|
||||
account = accountList.accounts.first;
|
||||
updateSubaddressList(accountIndex: account?.id ?? 0);
|
||||
await updateAddressesInBox();
|
||||
}
|
||||
|
||||
|
@ -46,11 +50,15 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
|
|||
final _subaddressList = MoneroSubaddressList();
|
||||
|
||||
addressesMap.clear();
|
||||
addressInfos.clear();
|
||||
|
||||
accountList.accounts.forEach((account) {
|
||||
_subaddressList.update(accountIndex: account.id);
|
||||
_subaddressList.subaddresses.forEach((subaddress) {
|
||||
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() {
|
||||
accountList.update();
|
||||
final accountListLength = accountList.accounts.length ?? 0;
|
||||
final accountListLength = accountList.accounts.length;
|
||||
|
||||
if (accountListLength <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
subaddressList.update(accountIndex: accountList.accounts.first.id);
|
||||
final subaddressListLength = subaddressList.subaddresses.length ?? 0;
|
||||
final subaddressListLength = subaddressList.subaddresses.length;
|
||||
|
||||
if (subaddressListLength <= 0) {
|
||||
return false;
|
||||
|
@ -83,4 +91,24 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
|
|||
subaddress = subaddressList.subaddresses.first;
|
||||
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 syncAll = data[PreferencesKey.syncAllKey] as bool?;
|
||||
final syncMode = data[PreferencesKey.syncModeKey] as int?;
|
||||
final autoGenerateSubaddressStatus = data[PreferencesKey.autoGenerateSubaddressStatusKey] as int?;
|
||||
|
||||
await _sharedPreferences.setString(PreferencesKey.currentWalletName, currentWalletName);
|
||||
|
||||
|
@ -296,6 +297,9 @@ class BackupService {
|
|||
|
||||
if (fiatApiMode != null)
|
||||
await _sharedPreferences.setInt(PreferencesKey.currentFiatApiModeKey, fiatApiMode);
|
||||
if (autoGenerateSubaddressStatus != null)
|
||||
await _sharedPreferences.setInt(PreferencesKey.autoGenerateSubaddressStatusKey,
|
||||
autoGenerateSubaddressStatus);
|
||||
|
||||
if (currentPinLength != null)
|
||||
await _sharedPreferences.setInt(PreferencesKey.currentPinLength, currentPinLength);
|
||||
|
@ -523,6 +527,8 @@ class BackupService {
|
|||
_sharedPreferences.getInt(PreferencesKey.syncModeKey),
|
||||
PreferencesKey.syncAllKey:
|
||||
_sharedPreferences.getBool(PreferencesKey.syncAllKey),
|
||||
PreferencesKey.autoGenerateSubaddressStatusKey:
|
||||
_sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey),
|
||||
};
|
||||
|
||||
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/core/yat_service.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/parse_address_from_domain.dart';
|
||||
import 'package:cake_wallet/entities/receive_page_option.dart';
|
||||
|
@ -245,7 +246,6 @@ Future<void> setup({
|
|||
if (!_isSetupFinished) {
|
||||
getIt.registerSingletonAsync<SharedPreferences>(() => SharedPreferences.getInstance());
|
||||
}
|
||||
|
||||
if (!_isSetupFinished) {
|
||||
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}';
|
||||
|
||||
static const exchangeProvidersSelection = 'exchange-providers-selection';
|
||||
static const autoGenerateSubaddressStatusKey = 'auto_generate_subaddress_status';
|
||||
static const clearnetDonationLink = 'clearnet_donation_link';
|
||||
static const onionDonationLink = 'onion_donation_link';
|
||||
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/store/yat/yat_store.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:cw_core/hive_type_ids.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
@ -89,6 +90,10 @@ Future<void> initializeAppConfigs() async {
|
|||
CakeHive.registerAdapter(TradeAdapter());
|
||||
}
|
||||
|
||||
if (!CakeHive.isAdapterRegistered(AddressInfo.typeId)) {
|
||||
CakeHive.registerAdapter(AddressInfoAdapter());
|
||||
}
|
||||
|
||||
if (!CakeHive.isAdapterRegistered(WalletInfo.typeId)) {
|
||||
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/update_haven_rate.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
|
@ -21,8 +22,8 @@ ReactionDisposer? _onCurrentWalletChangeReaction;
|
|||
ReactionDisposer? _onCurrentWalletChangeFiatRateUpdateReaction;
|
||||
//ReactionDisposer _onCurrentWalletAddressChangeReaction;
|
||||
|
||||
void startCurrentWalletChangeReaction(AppStore appStore,
|
||||
SettingsStore settingsStore, FiatConversionStore fiatConversionStore) {
|
||||
void startCurrentWalletChangeReaction(
|
||||
AppStore appStore, SettingsStore settingsStore, FiatConversionStore fiatConversionStore) {
|
||||
_onCurrentWalletChangeReaction?.reaction.dispose();
|
||||
_onCurrentWalletChangeFiatRateUpdateReaction?.reaction.dispose();
|
||||
//_onCurrentWalletAddressChangeReaction?.reaction?dispose();
|
||||
|
@ -48,8 +49,8 @@ void startCurrentWalletChangeReaction(AppStore appStore,
|
|||
//}
|
||||
//});
|
||||
|
||||
_onCurrentWalletChangeReaction = reaction((_) => appStore.wallet, (WalletBase<
|
||||
Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>?
|
||||
_onCurrentWalletChangeReaction = reaction((_) => appStore.wallet,
|
||||
(WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>?
|
||||
wallet) async {
|
||||
try {
|
||||
if (wallet == null) {
|
||||
|
@ -59,11 +60,13 @@ void startCurrentWalletChangeReaction(AppStore appStore,
|
|||
final node = settingsStore.getCurrentNode(wallet.type);
|
||||
startWalletSyncStatusChangeReaction(wallet, fiatConversionStore);
|
||||
startCheckConnectionReaction(wallet, settingsStore);
|
||||
await getIt.get<SharedPreferences>().setString(PreferencesKey.currentWalletName, wallet.name);
|
||||
await getIt
|
||||
.get<SharedPreferences>()
|
||||
.setString(PreferencesKey.currentWalletName, wallet.name);
|
||||
await getIt.get<SharedPreferences>().setInt(
|
||||
PreferencesKey.currentWalletType, serializeToInt(wallet.type));
|
||||
.setInt(PreferencesKey.currentWalletType, serializeToInt(wallet.type));
|
||||
if (wallet.type == WalletType.monero) {
|
||||
_setAutoGenerateSubaddressStatus(wallet, settingsStore);
|
||||
}
|
||||
await wallet.connectToNode(node: node);
|
||||
|
||||
if (wallet.type == WalletType.haven) {
|
||||
|
@ -82,9 +85,8 @@ void startCurrentWalletChangeReaction(AppStore appStore,
|
|||
}
|
||||
});
|
||||
|
||||
_onCurrentWalletChangeFiatRateUpdateReaction =
|
||||
reaction((_) => appStore.wallet, (WalletBase<Balance,
|
||||
TransactionHistoryBase<TransactionInfo>, TransactionInfo>?
|
||||
_onCurrentWalletChangeFiatRateUpdateReaction = reaction((_) => appStore.wallet,
|
||||
(WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>?
|
||||
wallet) async {
|
||||
try {
|
||||
if (wallet == null || settingsStore.fiatApiMode == FiatApiMode.disabled) {
|
||||
|
@ -92,8 +94,7 @@ void startCurrentWalletChangeReaction(AppStore appStore,
|
|||
}
|
||||
|
||||
fiatConversionStore.prices[wallet.currency] = 0;
|
||||
fiatConversionStore.prices[wallet.currency] =
|
||||
await FiatConversionService.fetchPrice(
|
||||
fiatConversionStore.prices[wallet.currency] = await FiatConversionService.fetchPrice(
|
||||
crypto: wallet.currency,
|
||||
fiat: settingsStore.fiatCurrency,
|
||||
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
|
||||
|
@ -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/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/entities/preferences_key.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/widgets/alert_with_two_actions.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:mobx/mobx.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/balance_page_theme.dart';
|
||||
|
||||
|
@ -174,7 +175,11 @@ class AddressPage extends BasePage {
|
|||
Observer(builder: (_) {
|
||||
if (addressListViewModel.hasAddressList) {
|
||||
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(
|
||||
height: 50,
|
||||
padding: EdgeInsets.only(left: 24, right: 12),
|
||||
|
@ -193,17 +198,26 @@ class AddressPage extends BasePage {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Observer(
|
||||
builder: (_) => Text(
|
||||
addressListViewModel.hasAccounts
|
||||
builder: (_) {
|
||||
String label = addressListViewModel.hasAccounts
|
||||
? S.of(context).accounts_subaddresses
|
||||
: S.of(context).addresses,
|
||||
: S.of(context).addresses;
|
||||
|
||||
if (dashboardViewModel.isAutoGenerateSubaddressesEnabled) {
|
||||
label = addressListViewModel.hasAccounts
|
||||
? S.of(context).accounts
|
||||
: S.of(context).account;
|
||||
}
|
||||
return Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.extension<SyncIndicatorTheme>()!
|
||||
.textColor),
|
||||
)),
|
||||
);
|
||||
},),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
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,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
|
|
|
@ -5,13 +5,14 @@ import 'package:flutter_slidable/flutter_slidable.dart';
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class AccountTile extends StatelessWidget {
|
||||
AccountTile(
|
||||
{required this.isCurrent,
|
||||
AccountTile({
|
||||
required this.isCurrent,
|
||||
required this.accountName,
|
||||
this.accountBalance,
|
||||
required this.currency,
|
||||
required this.onTap,
|
||||
required this.onEdit});
|
||||
required this.onEdit,
|
||||
});
|
||||
|
||||
final bool isCurrent;
|
||||
final String accountName;
|
||||
|
|
|
@ -73,7 +73,7 @@ class RestoreOptionsPage extends BasePage {
|
|||
await restoreFromQRViewModel.create(restoreWallet: restoreWallet);
|
||||
if (restoreFromQRViewModel.state is FailureState) {
|
||||
_onWalletCreateFailure(context,
|
||||
'Create wallet state: ${restoreFromQRViewModel.state.runtimeType.toString()}');
|
||||
'Create wallet state: ${(restoreFromQRViewModel.state as FailureState).error}');
|
||||
}
|
||||
} catch (e) {
|
||||
_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) {
|
||||
_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)
|
||||
SettingsSwitcherCell(
|
||||
title: S.current.prevent_screenshots,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:io';
|
||||
|
||||
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/background_tasks.dart';
|
||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||
|
@ -42,6 +43,7 @@ abstract class SettingsStoreBase with Store {
|
|||
required FiatCurrency initialFiatCurrency,
|
||||
required BalanceDisplayMode initialBalanceDisplayMode,
|
||||
required bool initialSaveRecipientAddress,
|
||||
required AutoGenerateSubaddressStatus initialAutoGenerateSubaddressStatus,
|
||||
required bool initialAppSecure,
|
||||
required bool initialDisableBuy,
|
||||
required bool initialDisableSell,
|
||||
|
@ -87,6 +89,7 @@ abstract class SettingsStoreBase with Store {
|
|||
fiatCurrency = initialFiatCurrency,
|
||||
balanceDisplayMode = initialBalanceDisplayMode,
|
||||
shouldSaveRecipientAddress = initialSaveRecipientAddress,
|
||||
autoGenerateSubaddressStatus = initialAutoGenerateSubaddressStatus,
|
||||
fiatApiMode = initialFiatMode,
|
||||
allowBiometricalAuthentication = initialAllowBiometricalAuthentication,
|
||||
selectedCake2FAPreset = initialCake2FAPresetOptions,
|
||||
|
@ -197,6 +200,11 @@ abstract class SettingsStoreBase with Store {
|
|||
(bool disableSell) =>
|
||||
sharedPreferences.setBool(PreferencesKey.disableSellKey, disableSell));
|
||||
|
||||
reaction(
|
||||
(_) => autoGenerateSubaddressStatus,
|
||||
(AutoGenerateSubaddressStatus autoGenerateSubaddressStatus) => sharedPreferences.setInt(
|
||||
PreferencesKey.autoGenerateSubaddressStatusKey, autoGenerateSubaddressStatus.value));
|
||||
|
||||
reaction(
|
||||
(_) => fiatApiMode,
|
||||
(FiatApiMode mode) =>
|
||||
|
@ -337,6 +345,7 @@ abstract class SettingsStoreBase with Store {
|
|||
static const defaultPinLength = 4;
|
||||
static const defaultActionsMode = 11;
|
||||
static const defaultPinCodeTimeOutDuration = PinCodeRequiredDuration.tenminutes;
|
||||
static const defaultAutoGenerateSubaddressStatus = AutoGenerateSubaddressStatus.initialized;
|
||||
|
||||
@observable
|
||||
FiatCurrency fiatCurrency;
|
||||
|
@ -359,6 +368,9 @@ abstract class SettingsStoreBase with Store {
|
|||
@observable
|
||||
bool shouldSaveRecipientAddress;
|
||||
|
||||
@observable
|
||||
AutoGenerateSubaddressStatus autoGenerateSubaddressStatus;
|
||||
|
||||
@observable
|
||||
bool isAppSecure;
|
||||
|
||||
|
@ -602,7 +614,12 @@ abstract class SettingsStoreBase with Store {
|
|||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
final deviceName = await _getDeviceName() ?? '';
|
||||
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>{};
|
||||
|
||||
if (moneroNode != null) {
|
||||
|
@ -640,6 +657,7 @@ abstract class SettingsStoreBase with Store {
|
|||
initialFiatCurrency: currentFiatCurrency,
|
||||
initialBalanceDisplayMode: currentBalanceDisplayMode,
|
||||
initialSaveRecipientAddress: shouldSaveRecipientAddress,
|
||||
initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus,
|
||||
initialAppSecure: isAppSecure,
|
||||
initialDisableBuy: disableBuy,
|
||||
initialDisableSell: disableSell,
|
||||
|
@ -709,6 +727,13 @@ abstract class SettingsStoreBase with Store {
|
|||
priority[WalletType.ethereum]!;
|
||||
}
|
||||
|
||||
final generateSubaddresses =
|
||||
sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
|
||||
|
||||
autoGenerateSubaddressStatus = generateSubaddresses != null
|
||||
? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses)
|
||||
: defaultAutoGenerateSubaddressStatus;
|
||||
|
||||
balanceDisplayMode = BalanceDisplayMode.deserialize(
|
||||
raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
|
||||
shouldSaveRecipientAddress =
|
||||
|
@ -719,8 +744,6 @@ abstract class SettingsStoreBase with Store {
|
|||
|
||||
numberOfFailedTokenTrials =
|
||||
sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? numberOfFailedTokenTrials;
|
||||
sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ??
|
||||
shouldSaveRecipientAddress;
|
||||
isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure;
|
||||
disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? disableBuy;
|
||||
disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? disableSell;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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/wallet_contact.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/utils/mobx.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
part 'contact_list_view_model.g.dart';
|
||||
|
||||
|
@ -20,12 +22,26 @@ abstract class ContactListViewModelBase with Store {
|
|||
ContactListViewModelBase(this.contactSource, this.walletInfoSource,
|
||||
this._currency, this.settingsStore)
|
||||
: contacts = ObservableList<ContactRecord>(),
|
||||
walletContacts = [] {
|
||||
walletContacts = [],
|
||||
isAutoGenerateEnabled =
|
||||
settingsStore.autoGenerateSubaddressStatus == AutoGenerateSubaddressStatus.enabled {
|
||||
walletInfoSource.values.forEach((info) {
|
||||
if (info.addresses?.isNotEmpty ?? false) {
|
||||
info.addresses?.forEach((address, label) {
|
||||
final name = label.isNotEmpty ? info.name + ' ($label)' : info.name;
|
||||
|
||||
if (isAutoGenerateEnabled && info.type == WalletType.monero && info.addressInfos != null) {
|
||||
info.addressInfos!.forEach((key, value) {
|
||||
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(
|
||||
address,
|
||||
name,
|
||||
|
@ -40,6 +56,11 @@ abstract class ContactListViewModelBase with Store {
|
|||
initialFire: true);
|
||||
}
|
||||
|
||||
String _createName(String walletName, String label) {
|
||||
return label.isNotEmpty ? '$walletName ($label)' : walletName;
|
||||
}
|
||||
|
||||
final bool isAutoGenerateEnabled;
|
||||
final Box<Contact> contactSource;
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
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/store/anonpay/anonpay_transactions_store.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
|
||||
|
@ -235,6 +236,10 @@ abstract class DashboardViewModelBase with Store {
|
|||
@computed
|
||||
double get price => balanceViewModel.price;
|
||||
|
||||
@computed
|
||||
bool get isAutoGenerateSubaddressesEnabled =>
|
||||
settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled;
|
||||
|
||||
@computed
|
||||
List<ActionListItem> get items {
|
||||
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/ethereum/ethereum.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_type.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
@ -14,11 +18,27 @@ abstract class PrivacySettingsViewModelBase with Store {
|
|||
PrivacySettingsViewModelBase(this._settingsStore, this._wallet);
|
||||
|
||||
final SettingsStore _settingsStore;
|
||||
final WalletBase _wallet;
|
||||
final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> _wallet;
|
||||
|
||||
@computed
|
||||
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
|
||||
bool get shouldSaveRecipientAddress => _settingsStore.shouldSaveRecipientAddress;
|
||||
|
||||
|
|
|
@ -682,5 +682,7 @@
|
|||
"support_title_other_links": "روابط دعم أخرى",
|
||||
"support_description_other_links": "انضم إلى مجتمعاتنا أو تصل إلينا شركائنا من خلال أساليب أخرى",
|
||||
"select_destination": ".ﻲﻃﺎﻴﺘﺣﻻﺍ ﺦﺴﻨﻟﺍ ﻒﻠﻣ ﺔﻬﺟﻭ ﺪﻳﺪﺤﺗ ءﺎﺟﺮﻟﺍ",
|
||||
"save_to_downloads": "ﺕﻼﻳﺰﻨﺘﻟﺍ ﻲﻓ ﻆﻔﺣ"
|
||||
"save_to_downloads": "ﺕﻼﻳﺰﻨﺘﻟﺍ ﻲﻓ ﻆﻔﺣ",
|
||||
"support_description_other_links": "انضم إلى مجتمعاتنا أو تصل إلينا شركائنا من خلال أساليب أخرى",
|
||||
"auto_generate_subaddresses": "تلقائي توليد subddresses"
|
||||
}
|
||||
|
|
|
@ -626,6 +626,7 @@
|
|||
"setup_totp_recommended": "Настройка на TOTP (препоръчително)",
|
||||
"disable_buy": "Деактивирайте действието за покупка",
|
||||
"disable_sell": "Деактивирайте действието за продажба",
|
||||
"auto_generate_subaddresses": "Автоматично генериране на подадреси",
|
||||
"cake_2fa_preset": "Торта 2FA Preset",
|
||||
"narrow": "Тесен",
|
||||
"normal": "нормално",
|
||||
|
|
|
@ -626,6 +626,7 @@
|
|||
"setup_totp_recommended": "Nastavit TOTP (doporučeno)",
|
||||
"disable_buy": "Zakázat akci nákupu",
|
||||
"disable_sell": "Zakázat akci prodeje",
|
||||
"auto_generate_subaddresses": "Automaticky generovat podadresy",
|
||||
"cake_2fa_preset": "Předvolba Cake 2FA",
|
||||
"narrow": "Úzký",
|
||||
"normal": "Normální",
|
||||
|
|
|
@ -640,6 +640,7 @@
|
|||
"high_contrast_theme": "Kontrastreiches Thema",
|
||||
"matrix_green_dark_theme": "Matrix Green Dark Theme",
|
||||
"monero_light_theme": "Monero Light-Thema",
|
||||
"auto_generate_subaddresses": "Unteradressen automatisch generieren",
|
||||
"cake_2fa_preset" : "Cake 2FA-Voreinstellung",
|
||||
"narrow": "Eng",
|
||||
"normal": "Normal",
|
||||
|
|
|
@ -568,6 +568,7 @@
|
|||
"privacy": "Privacy",
|
||||
"display_settings": "Display settings",
|
||||
"other_settings": "Other settings",
|
||||
"auto_generate_subaddresses": "Auto generate subaddresses",
|
||||
"require_pin_after": "Require PIN after",
|
||||
"always": "Always",
|
||||
"minutes_to_pin_code": "${minute} minutes",
|
||||
|
|
|
@ -640,6 +640,7 @@
|
|||
"high_contrast_theme": "Tema de alto contraste",
|
||||
"matrix_green_dark_theme": "Matrix verde oscuro tema",
|
||||
"monero_light_theme": "Tema ligero de Monero",
|
||||
"auto_generate_subaddresses": "Generar subdirecciones automáticamente",
|
||||
"cake_2fa_preset": "Pastel 2FA preestablecido",
|
||||
"narrow": "Angosto",
|
||||
"normal": "Normal",
|
||||
|
|
|
@ -640,6 +640,7 @@
|
|||
"high_contrast_theme": "Thème à contraste élevé",
|
||||
"matrix_green_dark_theme": "Thème Matrix Green Dark",
|
||||
"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",
|
||||
"narrow": "Étroit",
|
||||
"normal": "Normal",
|
||||
|
|
|
@ -618,6 +618,7 @@
|
|||
"high_contrast_theme": "Babban Jigon Kwatance",
|
||||
"matrix_green_dark_theme": "Matrix Green Dark Jigo",
|
||||
"monero_light_theme": "Jigon Hasken Monero",
|
||||
"auto_generate_subaddresses": "Saɓaƙa subaddresses ta kai tsaye",
|
||||
"cake_2fa_preset": "Cake 2FA saiti",
|
||||
"narrow": "kunkuntar",
|
||||
"normal": "Na al'ada",
|
||||
|
|
|
@ -640,6 +640,7 @@
|
|||
"high_contrast_theme": "उच्च कंट्रास्ट थीम",
|
||||
"matrix_green_dark_theme": "मैट्रिक्स ग्रीन डार्क थीम",
|
||||
"monero_light_theme": "मोनेरो लाइट थीम",
|
||||
"auto_generate_subaddresses": "स्वचालित रूप से उप-पते उत्पन्न करें",
|
||||
"cake_2fa_preset": "केक 2एफए प्रीसेट",
|
||||
"narrow": "सँकरा",
|
||||
"normal": "सामान्य",
|
||||
|
|
|
@ -640,6 +640,7 @@
|
|||
"high_contrast_theme": "Tema visokog kontrasta",
|
||||
"matrix_green_dark_theme": "Matrix Green Dark Theme",
|
||||
"monero_light_theme": "Monero lagana tema",
|
||||
"auto_generate_subaddresses": "Automatski generirajte podadrese",
|
||||
"cake_2fa_preset": "Cake 2FA Preset",
|
||||
"narrow": "Usko",
|
||||
"normal": "Normalno",
|
||||
|
|
|
@ -622,6 +622,7 @@
|
|||
"setup_totp_recommended": "Siapkan TOTP (Disarankan)",
|
||||
"disable_buy": "Nonaktifkan tindakan beli",
|
||||
"disable_sell": "Nonaktifkan aksi jual",
|
||||
"auto_generate_subaddresses": "Menghasilkan subalamat secara otomatis",
|
||||
"cake_2fa_preset": "Preset Kue 2FA",
|
||||
"narrow": "Sempit",
|
||||
"normal": "Normal",
|
||||
|
|
|
@ -634,6 +634,7 @@
|
|||
"setup_totp_recommended": "Imposta TOTP (consigliato)",
|
||||
"disable_buy": "Disabilita l'azione di acquisto",
|
||||
"disable_sell": "Disabilita l'azione di vendita",
|
||||
"auto_generate_subaddresses": "Genera automaticamente sottindirizzi",
|
||||
"cake_2fa_preset": "Torta 2FA Preset",
|
||||
"narrow": "Stretto",
|
||||
"normal": "Normale",
|
||||
|
|
|
@ -686,5 +686,6 @@
|
|||
"support_title_other_links": "その他のサポートリンク",
|
||||
"support_description_other_links": "私たちのコミュニティに参加するか、他の方法を通して私たちのパートナーに連絡してください",
|
||||
"select_destination": "バックアップファイルの保存先を選択してください。",
|
||||
"save_to_downloads": "ダウンロードに保存"
|
||||
"save_to_downloads": "ダウンロードに保存",
|
||||
"auto_generate_subaddresses": "Autoはサブアドレスを生成します"
|
||||
}
|
||||
|
|
|
@ -686,5 +686,6 @@
|
|||
"support_title_other_links": "다른 지원 링크",
|
||||
"support_description_other_links": "다른 방법을 통해 커뮤니티에 가입하거나 파트너에게 연락하십시오.",
|
||||
"select_destination": "백업 파일의 대상을 선택하십시오.",
|
||||
"save_to_downloads": "다운로드에 저장"
|
||||
"save_to_downloads": "다운로드에 저장",
|
||||
"auto_generate_subaddresses": "자동 생성 서브 아드 드레스"
|
||||
}
|
||||
|
|
|
@ -684,5 +684,6 @@
|
|||
"support_title_other_links": "အခြားအထောက်အပံ့လင့်များ",
|
||||
"support_description_other_links": "ကျွန်ုပ်တို့၏လူမှုအသိုင်းအဝိုင်းများသို့ 0 င်ရောက်ပါ",
|
||||
"select_destination": "အရန်ဖိုင်အတွက် ဦးတည်ရာကို ရွေးပါ။",
|
||||
"save_to_downloads": "ဒေါင်းလုဒ်များထံ သိမ်းဆည်းပါ။"
|
||||
"save_to_downloads": "ဒေါင်းလုဒ်များထံ သိမ်းဆည်းပါ။",
|
||||
"auto_generate_subaddresses": "အော်တို Generate Subaddresses"
|
||||
}
|
||||
|
|
|
@ -634,6 +634,7 @@
|
|||
"setup_totp_recommended": "TOTP instellen (aanbevolen)",
|
||||
"disable_buy": "Koopactie uitschakelen",
|
||||
"disable_sell": "Verkoopactie uitschakelen",
|
||||
"auto_generate_subaddresses": "Automatisch subadressen genereren",
|
||||
"cake_2fa_preset": "Taart 2FA Voorinstelling",
|
||||
"narrow": "Smal",
|
||||
"normal": "Normaal",
|
||||
|
|
|
@ -634,6 +634,7 @@
|
|||
"setup_totp_recommended": "Skonfiguruj TOTP (zalecane)",
|
||||
"disable_buy": "Wyłącz akcję kupna",
|
||||
"disable_sell": "Wyłącz akcję sprzedaży",
|
||||
"auto_generate_subaddresses": "Automatycznie generuj podadresy",
|
||||
"cake_2fa_preset": "Ciasto 2FA Preset",
|
||||
"narrow": "Wąski",
|
||||
"normal": "Normalna",
|
||||
|
|
|
@ -633,6 +633,7 @@
|
|||
"setup_totp_recommended": "Configurar TOTP (recomendado)",
|
||||
"disable_buy": "Desativar ação de compra",
|
||||
"disable_sell": "Desativar ação de venda",
|
||||
"auto_generate_subaddresses": "Gerar subendereços automaticamente",
|
||||
"cake_2fa_preset": "Predefinição de bolo 2FA",
|
||||
"narrow": "Estreito",
|
||||
"normal": "Normal",
|
||||
|
|
|
@ -686,5 +686,6 @@
|
|||
"support_title_other_links": "Другие ссылки на поддержку",
|
||||
"support_description_other_links": "Присоединяйтесь к нашим сообществам или охватите нас наших партнеров с помощью других методов",
|
||||
"select_destination": "Пожалуйста, выберите место для файла резервной копии.",
|
||||
"save_to_downloads": "Сохранить в загрузках"
|
||||
"save_to_downloads": "Сохранить в загрузках",
|
||||
"auto_generate_subaddresses": "Авто генерируйте Subaddresses"
|
||||
}
|
||||
|
|
|
@ -684,5 +684,6 @@
|
|||
"support_title_other_links": "ลิงค์สนับสนุนอื่น ๆ",
|
||||
"support_description_other_links": "เข้าร่วมชุมชนของเราหรือเข้าถึงเราพันธมิตรของเราผ่านวิธีการอื่น ๆ",
|
||||
"select_destination": "โปรดเลือกปลายทางสำหรับไฟล์สำรอง",
|
||||
"save_to_downloads": "บันทึกลงดาวน์โหลด"
|
||||
"save_to_downloads": "บันทึกลงดาวน์โหลด",
|
||||
"auto_generate_subaddresses": "Auto สร้าง subaddresses"
|
||||
}
|
||||
|
|
|
@ -632,6 +632,7 @@
|
|||
"setup_totp_recommended": "TOTP'yi kurun (Önerilir)",
|
||||
"disable_buy": "Satın alma 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ı",
|
||||
"narrow": "Dar",
|
||||
"normal": "Normal",
|
||||
|
|
|
@ -634,6 +634,7 @@
|
|||
"setup_totp_recommended": "Налаштувати TOTP (рекомендовано)",
|
||||
"disable_buy": "Вимкнути дію покупки",
|
||||
"disable_sell": "Вимкнути дію продажу",
|
||||
"auto_generate_subaddresses": "Автоматично генерувати підадреси",
|
||||
"cake_2fa_preset": "Торт 2FA Preset",
|
||||
"narrow": "вузькі",
|
||||
"normal": "нормальний",
|
||||
|
|
|
@ -678,5 +678,6 @@
|
|||
"support_title_other_links": "دوسرے سپورٹ لنکس",
|
||||
"support_description_other_links": "ہماری برادریوں میں شامل ہوں یا دوسرے طریقوں سے ہمارے شراکت داروں تک پہنچیں",
|
||||
"select_destination": "۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﻝﺰﻨﻣ ﮯﯿﻟ ﮯﮐ ﻞﺋﺎﻓ ﭖﺍ ﮏﯿﺑ ﻡﺮﮐ ﮦﺍﺮﺑ",
|
||||
"save_to_downloads": "۔ﮟﯾﺮﮐ ﻅﻮﻔﺤﻣ ﮟﯿﻣ ﺯﮈﻮﻟ ﻥﺅﺍﮈ"
|
||||
"save_to_downloads": "۔ﮟﯾﺮﮐ ﻅﻮﻔﺤﻣ ﮟﯿﻣ ﺯﮈﻮﻟ ﻥﺅﺍﮈ",
|
||||
"auto_generate_subaddresses": "آٹو سب ایڈریس تیار کرتا ہے"
|
||||
}
|
||||
|
|
|
@ -680,5 +680,6 @@
|
|||
"matrix_green_dark_theme": "Matrix Green Dark Akori",
|
||||
"monero_light_theme": "Monero Light Akori",
|
||||
"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": "矩阵绿暗主题",
|
||||
"monero_light_theme": "门罗币浅色主题",
|
||||
"select_destination": "请选择备份文件的目的地。",
|
||||
"save_to_downloads": "保存到下载"
|
||||
"save_to_downloads": "保存到下载",
|
||||
"auto_generate_subaddresses": "自动生成子辅助"
|
||||
}
|
||||
|
|
|
@ -1,15 +1,6 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
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();
|
||||
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 {
|
||||
if (args.length != 2) {
|
||||
|
@ -23,44 +14,9 @@ void main(List<String> args) async {
|
|||
print('Appending "$name": "$text"');
|
||||
|
||||
for (var lang in langs) {
|
||||
final fileName = getFileName(lang);
|
||||
final fileName = getArbFileName(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