mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 19:49:22 +00:00
Merge branches 'CW-519-tor' and 'main' of https://github.com/cake-tech/cake_wallet into CW-519-tor
This commit is contained in:
commit
c1c5e4b1cf
48 changed files with 19443 additions and 19597 deletions
|
@ -28,7 +28,7 @@ class BitcoinAddressRecord {
|
|||
}
|
||||
|
||||
final String address;
|
||||
final bool isHidden;
|
||||
bool isHidden;
|
||||
final int index;
|
||||
int _txCount;
|
||||
int _balance;
|
||||
|
|
|
@ -12,10 +12,8 @@ import 'package:cw_core/wallet_type.dart';
|
|||
import 'package:hive/hive.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
class BitcoinWalletService extends WalletService<
|
||||
BitcoinNewWalletCredentials,
|
||||
BitcoinRestoreWalletFromSeedCredentials,
|
||||
BitcoinRestoreWalletFromWIFCredentials> {
|
||||
class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
|
||||
BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromWIFCredentials> {
|
||||
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
|
@ -42,28 +40,41 @@ class BitcoinWalletService extends WalletService<
|
|||
|
||||
@override
|
||||
Future<BitcoinWallet> openWallet(String name, String password) async {
|
||||
final walletInfo = walletInfoSource.values.firstWhereOrNull(
|
||||
(info) => info.id == WalletBase.idFor(name, getType()))!;
|
||||
final wallet = await BitcoinWalletBase.open(
|
||||
password: password, name: name, walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
final walletInfo = walletInfoSource.values
|
||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
|
||||
try {
|
||||
final wallet = await BitcoinWalletBase.open(
|
||||
password: password,
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.init();
|
||||
saveBackup(name);
|
||||
return wallet;
|
||||
} catch (_) {
|
||||
await restoreWalletFilesFromBackup(name);
|
||||
final wallet = await BitcoinWalletBase.open(
|
||||
password: password,
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> remove(String wallet) async {
|
||||
File(await pathForWalletDir(name: wallet, type: getType()))
|
||||
.delete(recursive: true);
|
||||
final walletInfo = walletInfoSource.values.firstWhereOrNull(
|
||||
(info) => info.id == WalletBase.idFor(wallet, getType()))!;
|
||||
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
|
||||
final walletInfo = walletInfoSource.values
|
||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
|
||||
await walletInfoSource.delete(walletInfo.key);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> rename(String currentName, String password, String newName) async {
|
||||
final currentWalletInfo = walletInfoSource.values.firstWhereOrNull(
|
||||
(info) => info.id == WalletBase.idFor(currentName, getType()))!;
|
||||
final currentWalletInfo = walletInfoSource.values
|
||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
|
||||
final currentWallet = await BitcoinWalletBase.open(
|
||||
password: password,
|
||||
name: currentName,
|
||||
|
@ -71,6 +82,7 @@ class BitcoinWalletService extends WalletService<
|
|||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
await saveBackup(newName);
|
||||
|
||||
final newWalletInfo = currentWalletInfo;
|
||||
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||
|
@ -80,13 +92,11 @@ class BitcoinWalletService extends WalletService<
|
|||
}
|
||||
|
||||
@override
|
||||
Future<BitcoinWallet> restoreFromKeys(
|
||||
BitcoinRestoreWalletFromWIFCredentials credentials) async =>
|
||||
Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials) async =>
|
||||
throw UnimplementedError();
|
||||
|
||||
@override
|
||||
Future<BitcoinWallet> restoreFromSeed(
|
||||
BitcoinRestoreWalletFromSeedCredentials credentials) async {
|
||||
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials) async {
|
||||
if (!validateMnemonic(credentials.mnemonic)) {
|
||||
throw BitcoinMnemonicIsIncorrectException();
|
||||
}
|
||||
|
@ -100,4 +110,4 @@ class BitcoinWalletService extends WalletService<
|
|||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
String get address {
|
||||
if (isEnabledAutoGenerateSubaddress) {
|
||||
if (receiveAddresses.isEmpty) {
|
||||
final newAddress = generateNewAddress().address;
|
||||
final newAddress = generateNewAddress(hd: mainHd).address;
|
||||
return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(newAddress) : newAddress;
|
||||
}
|
||||
final receiveAddress = receiveAddresses.first.address;
|
||||
|
@ -215,6 +215,13 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
List<BitcoinAddressRecord> addrs;
|
||||
|
||||
if (addresses.isNotEmpty) {
|
||||
|
||||
|
||||
if(!isHidden) {
|
||||
final receiveAddressesList = addresses.where((addr) => !addr.isHidden).toList();
|
||||
validateSideHdAddresses(receiveAddressesList);
|
||||
}
|
||||
|
||||
addrs = addresses.where((addr) => addr.isHidden == isHidden).toList();
|
||||
} else {
|
||||
addrs = await _createNewAddresses(
|
||||
|
@ -296,4 +303,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
final transactionHistory = await electrumClient.getHistory(sh);
|
||||
return transactionHistory.isNotEmpty;
|
||||
}
|
||||
|
||||
void validateSideHdAddresses(List<BitcoinAddressRecord> addrWithTransactions) {
|
||||
addrWithTransactions.forEach((element) {
|
||||
if (element.address != getAddress(index: element.index, hd: mainHd)) element.isHidden = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,11 +45,22 @@ class LitecoinWalletService extends WalletService<
|
|||
Future<LitecoinWallet> openWallet(String name, String password) async {
|
||||
final walletInfo = walletInfoSource.values.firstWhereOrNull(
|
||||
(info) => info.id == WalletBase.idFor(name, getType()))!;
|
||||
final wallet = await LitecoinWalletBase.open(
|
||||
password: password, name: name, walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
|
||||
try {
|
||||
final wallet = await LitecoinWalletBase.open(
|
||||
password: password, name: name, walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.init();
|
||||
saveBackup(name);
|
||||
return wallet;
|
||||
} catch (_) {
|
||||
await restoreWalletFilesFromBackup(name);
|
||||
final wallet = await LitecoinWalletBase.open(
|
||||
password: password, name: name, walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -72,6 +83,7 @@ class LitecoinWalletService extends WalletService<
|
|||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
await saveBackup(newName);
|
||||
|
||||
final newWalletInfo = currentWalletInfo;
|
||||
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||
|
|
|
@ -51,11 +51,22 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
|
|||
Future<BitcoinCashWallet> openWallet(String name, String password) async {
|
||||
final walletInfo = walletInfoSource.values.firstWhereOrNull(
|
||||
(info) => info.id == WalletBase.idFor(name, getType()))!;
|
||||
final wallet = await BitcoinCashWalletBase.open(
|
||||
password: password, name: name, walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
|
||||
try {
|
||||
final wallet = await BitcoinCashWalletBase.open(
|
||||
password: password, name: name, walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.init();
|
||||
saveBackup(name);
|
||||
return wallet;
|
||||
} catch(_) {
|
||||
await restoreWalletFilesFromBackup(name);
|
||||
final wallet = await BitcoinCashWalletBase.open(
|
||||
password: password, name: name, walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -78,6 +89,7 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
|
|||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
await saveBackup(newName);
|
||||
|
||||
final newWalletInfo = currentWalletInfo;
|
||||
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||
|
|
|
@ -54,6 +54,17 @@ Future<void> restoreWalletFiles(String name) async {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> resetCache(String name) async {
|
||||
await removeCache(name);
|
||||
|
||||
final walletDirPath = await pathForWalletDir(name: name, type: WalletType.monero);
|
||||
final cacheFilePath = '$walletDirPath/$name';
|
||||
final backupCacheFile = File(backupFileName(cacheFilePath));
|
||||
if (backupCacheFile.existsSync()) {
|
||||
await backupCacheFile.copy(cacheFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> backupWalletFilesExists(String name) async {
|
||||
final walletDirPath = await pathForWalletDir(name: name, type: WalletType.monero);
|
||||
final cacheFilePath = '$walletDirPath/$name';
|
||||
|
@ -63,9 +74,9 @@ Future<bool> backupWalletFilesExists(String name) async {
|
|||
final backupKeysFile = File(backupFileName(keysFilePath));
|
||||
final backupAddressListFile = File(backupFileName(addressListFilePath));
|
||||
|
||||
return backupCacheFile.existsSync()
|
||||
&& backupKeysFile.existsSync()
|
||||
&& backupAddressListFile.existsSync();
|
||||
return backupCacheFile.existsSync() &&
|
||||
backupKeysFile.existsSync() &&
|
||||
backupAddressListFile.existsSync();
|
||||
}
|
||||
|
||||
Future<void> removeCache(String name) async {
|
||||
|
@ -85,4 +96,4 @@ Future<void> restoreOrResetWalletFiles(String name) async {
|
|||
}
|
||||
|
||||
removeCache(name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import 'package:cw_core/node.dart';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_credentials.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
abstract class WalletService<N extends WalletCredentials, RFS extends WalletCredentials,
|
||||
|
@ -21,4 +22,22 @@ abstract class WalletService<N extends WalletCredentials, RFS extends WalletCred
|
|||
Future<void> remove(String wallet);
|
||||
|
||||
Future<void> rename(String currentName, String password, String newName);
|
||||
|
||||
Future<void> restoreWalletFilesFromBackup(String name) async {
|
||||
final backupWalletDirPath = await pathForWalletDir(name: "$name.backup", type: getType());
|
||||
final walletDirPath = await pathForWalletDir(name: name, type: getType());
|
||||
|
||||
if (File(backupWalletDirPath).existsSync()) {
|
||||
await File(backupWalletDirPath).copy(walletDirPath);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveBackup(String name) async {
|
||||
final backupWalletDirPath = await pathForWalletDir(name: "$name.backup", type: getType());
|
||||
final walletDirPath = await pathForWalletDir(name: name, type: getType());
|
||||
|
||||
if (File(walletDirPath).existsSync()) {
|
||||
await File(walletDirPath).copy(backupWalletDirPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,16 +39,31 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
|
|||
Future<EthereumWallet> openWallet(String name, String password) async {
|
||||
final walletInfo =
|
||||
walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
|
||||
final wallet = await EthereumWallet.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
await wallet.save();
|
||||
try {
|
||||
final wallet = await EthereumWallet.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
);
|
||||
|
||||
return wallet;
|
||||
await wallet.init();
|
||||
await wallet.save();
|
||||
saveBackup(name);
|
||||
return wallet;
|
||||
} catch (_) {
|
||||
|
||||
await restoreWalletFilesFromBackup(name);
|
||||
|
||||
final wallet = await EthereumWallet.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
);
|
||||
await wallet.init();
|
||||
await wallet.save();
|
||||
return wallet;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -59,6 +74,7 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
|
|||
password: password, name: currentName, walletInfo: currentWalletInfo);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
await saveBackup(newName);
|
||||
|
||||
final newWalletInfo = currentWalletInfo;
|
||||
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||
|
|
|
@ -163,6 +163,7 @@ class HavenWalletService extends WalletService<
|
|||
final currentWallet = HavenWallet(walletInfo: currentWalletInfo);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
await saveBackup(newName);
|
||||
|
||||
final newWalletInfo = currentWalletInfo;
|
||||
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||
|
|
|
@ -36,6 +36,8 @@ import 'package:mobx/mobx.dart';
|
|||
part 'monero_wallet.g.dart';
|
||||
|
||||
const moneroBlockSize = 1000;
|
||||
// not sure if this should just be 0 but setting it higher feels safer / should catch more cases:
|
||||
const MIN_RESTORE_HEIGHT = 1000;
|
||||
|
||||
class MoneroWallet = MoneroWalletBase with _$MoneroWallet;
|
||||
|
||||
|
@ -79,7 +81,7 @@ abstract class MoneroWalletBase
|
|||
|
||||
Box<UnspentCoinsInfo> unspentCoinsInfo;
|
||||
|
||||
void Function(FlutterErrorDetails)? _onError;
|
||||
void Function(FlutterErrorDetails)? onError;
|
||||
|
||||
@override
|
||||
late MoneroWalletAddresses walletAddresses;
|
||||
|
@ -171,7 +173,26 @@ abstract class MoneroWalletBase
|
|||
Future<void> startSync() async {
|
||||
try {
|
||||
_setInitialHeight();
|
||||
} catch (_) {}
|
||||
} catch (_) {
|
||||
// our restore height wasn't correct, so lets see if using the backup works:
|
||||
try {
|
||||
await resetCache(name);
|
||||
_setInitialHeight();
|
||||
} catch (e) {
|
||||
// we still couldn't get a valid height from the backup?!:
|
||||
// try to use the date instead:
|
||||
try {
|
||||
_setHeightFromDate();
|
||||
} catch (e, s) {
|
||||
// we still couldn't get a valid sync height :/
|
||||
onError?.call(FlutterErrorDetails(
|
||||
exception: e,
|
||||
stack: s,
|
||||
library: this.runtimeType.toString(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
syncStatus = AttemptingSyncStatus();
|
||||
|
@ -339,6 +360,8 @@ abstract class MoneroWalletBase
|
|||
if (currentAddressListFile.existsSync()) {
|
||||
await currentAddressListFile.rename('$newWalletPath.address.txt');
|
||||
}
|
||||
|
||||
await backupWalletFiles(newWalletName);
|
||||
} catch (e) {
|
||||
final currentWalletPath = await pathForWallet(name: name, type: type);
|
||||
|
||||
|
@ -402,9 +425,7 @@ abstract class MoneroWalletBase
|
|||
if (coin.spent == 0) {
|
||||
final unspent = MoneroUnspent.fromCoinsInfoRow(coin);
|
||||
if (unspent.hash.isNotEmpty) {
|
||||
unspent.isChange = transaction_history
|
||||
.getTransaction(unspent.hash)
|
||||
.direction == 1;
|
||||
unspent.isChange = transaction_history.getTransaction(unspent.hash).direction == 1;
|
||||
}
|
||||
unspentCoins.add(unspent);
|
||||
}
|
||||
|
@ -418,7 +439,7 @@ abstract class MoneroWalletBase
|
|||
if (unspentCoins.isNotEmpty) {
|
||||
unspentCoins.forEach((coin) {
|
||||
final coinInfoList = unspentCoinsInfo.values.where((element) =>
|
||||
element.walletId.contains(id) &&
|
||||
element.walletId.contains(id) &&
|
||||
element.accountIndex == walletAddresses.account!.id &&
|
||||
element.keyImage!.contains(coin.keyImage!));
|
||||
|
||||
|
@ -438,7 +459,7 @@ abstract class MoneroWalletBase
|
|||
_askForUpdateBalance();
|
||||
} catch (e, s) {
|
||||
print(e.toString());
|
||||
_onError?.call(FlutterErrorDetails(
|
||||
onError?.call(FlutterErrorDetails(
|
||||
exception: e,
|
||||
stack: s,
|
||||
library: this.runtimeType.toString(),
|
||||
|
@ -534,18 +555,36 @@ abstract class MoneroWalletBase
|
|||
_listener = monero_wallet.setListeners(_onNewBlock, _onNewTransaction);
|
||||
}
|
||||
|
||||
// check if the height is correct:
|
||||
void _setInitialHeight() {
|
||||
if (walletInfo.isRecovery) {
|
||||
return;
|
||||
}
|
||||
|
||||
final currentHeight = monero_wallet.getCurrentHeight();
|
||||
final height = monero_wallet.getCurrentHeight();
|
||||
|
||||
if (currentHeight <= 1) {
|
||||
final height = _getHeightByDate(walletInfo.date);
|
||||
if (height > MIN_RESTORE_HEIGHT) {
|
||||
// the restore height is probably correct, so we do nothing:
|
||||
return;
|
||||
}
|
||||
|
||||
throw Exception("height isn't > $MIN_RESTORE_HEIGHT!");
|
||||
}
|
||||
|
||||
void _setHeightFromDate() {
|
||||
if (walletInfo.isRecovery) {
|
||||
return;
|
||||
}
|
||||
|
||||
final height = _getHeightByDate(walletInfo.date);
|
||||
|
||||
if (height > MIN_RESTORE_HEIGHT) {
|
||||
monero_wallet.setRecoveringFromSeed(isRecovery: true);
|
||||
monero_wallet.setRefreshFromBlockHeight(height: height);
|
||||
return;
|
||||
}
|
||||
|
||||
throw Exception("height isn't > $MIN_RESTORE_HEIGHT!");
|
||||
}
|
||||
|
||||
int _getHeightDistance(DateTime date) {
|
||||
|
@ -561,7 +600,8 @@ abstract class MoneroWalletBase
|
|||
final heightDistance = _getHeightDistance(date);
|
||||
|
||||
if (nodeHeight <= 0) {
|
||||
return 0;
|
||||
// the node returned 0 (an error state), so lets just restore from cache:
|
||||
throw Exception("nodeHeight is <= 0!");
|
||||
}
|
||||
|
||||
return nodeHeight - heightDistance;
|
||||
|
@ -650,7 +690,7 @@ abstract class MoneroWalletBase
|
|||
}
|
||||
|
||||
@override
|
||||
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError;
|
||||
void setExceptionHandler(void Function(FlutterErrorDetails) e) => onError = e;
|
||||
|
||||
@override
|
||||
String signMessage(String message, {String? address}) {
|
||||
|
|
|
@ -11,11 +11,13 @@ import 'package:cw_core/get_height_by_date.dart';
|
|||
import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart';
|
||||
import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager;
|
||||
import 'package:cw_monero/monero_wallet.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:polyseed/polyseed.dart';
|
||||
|
||||
class MoneroNewWalletCredentials extends WalletCredentials {
|
||||
MoneroNewWalletCredentials({required String name, required this.language, required this.isPolyseed, String? password})
|
||||
MoneroNewWalletCredentials(
|
||||
{required String name, required this.language, required this.isPolyseed, String? password})
|
||||
: super(name: name, password: password);
|
||||
|
||||
final String language;
|
||||
|
@ -52,10 +54,8 @@ class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
|
|||
final String spendKey;
|
||||
}
|
||||
|
||||
class MoneroWalletService extends WalletService<
|
||||
MoneroNewWalletCredentials,
|
||||
MoneroRestoreWalletFromSeedCredentials,
|
||||
MoneroRestoreWalletFromKeysCredentials> {
|
||||
class MoneroWalletService extends WalletService<MoneroNewWalletCredentials,
|
||||
MoneroRestoreWalletFromSeedCredentials, MoneroRestoreWalletFromKeysCredentials> {
|
||||
MoneroWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
|
@ -112,6 +112,7 @@ class MoneroWalletService extends WalletService<
|
|||
|
||||
@override
|
||||
Future<MoneroWallet> openWallet(String name, String password) async {
|
||||
MoneroWallet? wallet;
|
||||
try {
|
||||
final path = await pathForWallet(name: name, type: getType());
|
||||
|
||||
|
@ -119,11 +120,10 @@ class MoneroWalletService extends WalletService<
|
|||
await repairOldAndroidWallet(name);
|
||||
}
|
||||
|
||||
await monero_wallet_manager
|
||||
.openWalletAsync({'path': path, 'password': password});
|
||||
final walletInfo = walletInfoSource.values.firstWhere(
|
||||
(info) => info.id == WalletBase.idFor(name, getType()));
|
||||
final wallet = MoneroWallet(walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await monero_wallet_manager.openWalletAsync({'path': path, 'password': password});
|
||||
final walletInfo = walletInfoSource.values
|
||||
.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
|
||||
wallet = MoneroWallet(walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
final isValid = wallet.walletAddresses.validate();
|
||||
|
||||
if (!isValid) {
|
||||
|
@ -135,7 +135,7 @@ class MoneroWalletService extends WalletService<
|
|||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
} catch (e) {
|
||||
} catch (e, s) {
|
||||
// TODO: Implement Exception for wallet list service.
|
||||
|
||||
final bool isBadAlloc = e.toString().contains('bad_alloc') ||
|
||||
|
@ -156,16 +156,18 @@ class MoneroWalletService extends WalletService<
|
|||
final bool invalidSignature = e.toString().contains('invalid signature') ||
|
||||
(e is WalletOpeningException && e.message.contains('invalid signature'));
|
||||
|
||||
if (isBadAlloc ||
|
||||
doesNotCorrespond ||
|
||||
isMissingCacheFilesIOS ||
|
||||
isMissingCacheFilesAndroid ||
|
||||
invalidSignature) {
|
||||
await restoreOrResetWalletFiles(name);
|
||||
return openWallet(name, password);
|
||||
if (!isBadAlloc &&
|
||||
!doesNotCorrespond &&
|
||||
!isMissingCacheFilesIOS &&
|
||||
!isMissingCacheFilesAndroid &&
|
||||
!invalidSignature &&
|
||||
wallet != null &&
|
||||
wallet.onError != null) {
|
||||
wallet.onError!(FlutterErrorDetails(exception: e, stack: s));
|
||||
}
|
||||
|
||||
rethrow;
|
||||
await restoreOrResetWalletFiles(name);
|
||||
return openWallet(name, password);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,10 +187,9 @@ class MoneroWalletService extends WalletService<
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> rename(
|
||||
String currentName, String password, String newName) async {
|
||||
final currentWalletInfo = walletInfoSource.values.firstWhere(
|
||||
(info) => info.id == WalletBase.idFor(currentName, getType()));
|
||||
Future<void> rename(String currentName, String password, String newName) async {
|
||||
final currentWalletInfo = walletInfoSource.values
|
||||
.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
|
||||
final currentWallet =
|
||||
MoneroWallet(walletInfo: currentWalletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
|
||||
|
@ -202,8 +203,7 @@ class MoneroWalletService extends WalletService<
|
|||
}
|
||||
|
||||
@override
|
||||
Future<MoneroWallet> restoreFromKeys(
|
||||
MoneroRestoreWalletFromKeysCredentials credentials) async {
|
||||
Future<MoneroWallet> restoreFromKeys(MoneroRestoreWalletFromKeysCredentials credentials) async {
|
||||
try {
|
||||
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||
await monero_wallet_manager.restoreFromKeys(
|
||||
|
@ -227,9 +227,7 @@ class MoneroWalletService extends WalletService<
|
|||
}
|
||||
|
||||
@override
|
||||
Future<MoneroWallet> restoreFromSeed(
|
||||
MoneroRestoreWalletFromSeedCredentials credentials) async {
|
||||
|
||||
Future<MoneroWallet> restoreFromSeed(MoneroRestoreWalletFromSeedCredentials credentials) async {
|
||||
// Restore from Polyseed
|
||||
if (Polyseed.isValidSeed(credentials.mnemonic)) {
|
||||
return restoreFromPolyseed(credentials);
|
||||
|
@ -254,14 +252,16 @@ class MoneroWalletService extends WalletService<
|
|||
}
|
||||
}
|
||||
|
||||
Future<MoneroWallet> restoreFromPolyseed(MoneroRestoreWalletFromSeedCredentials credentials) async {
|
||||
Future<MoneroWallet> restoreFromPolyseed(
|
||||
MoneroRestoreWalletFromSeedCredentials credentials) async {
|
||||
try {
|
||||
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||
final polyseedCoin = PolyseedCoin.POLYSEED_MONERO;
|
||||
final lang = PolyseedLang.getByPhrase(credentials.mnemonic);
|
||||
final polyseed = Polyseed.decode(credentials.mnemonic, lang, polyseedCoin);
|
||||
|
||||
return _restoreFromPolyseed(path, credentials.password!, polyseed, credentials.walletInfo!, lang);
|
||||
return _restoreFromPolyseed(
|
||||
path, credentials.password!, polyseed, credentials.walletInfo!, lang);
|
||||
} catch (e) {
|
||||
// TODO: Implement Exception for wallet list service.
|
||||
print('MoneroWalletsManager Error: $e');
|
||||
|
@ -269,11 +269,11 @@ class MoneroWalletService extends WalletService<
|
|||
}
|
||||
}
|
||||
|
||||
Future<MoneroWallet> _restoreFromPolyseed(String path, String password, Polyseed polyseed,
|
||||
WalletInfo walletInfo, PolyseedLang lang,
|
||||
Future<MoneroWallet> _restoreFromPolyseed(
|
||||
String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang,
|
||||
{PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO, int? overrideHeight}) async {
|
||||
final height = overrideHeight ?? getMoneroHeigthByDate(
|
||||
date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000));
|
||||
final height = overrideHeight ??
|
||||
getMoneroHeigthByDate(date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000));
|
||||
final spendKey = polyseed.generateKey(coin, 32).toHexString();
|
||||
final seed = polyseed.encode(lang, coin);
|
||||
|
||||
|
@ -288,8 +288,7 @@ class MoneroWalletService extends WalletService<
|
|||
restoreHeight: height,
|
||||
spendKey: spendKey);
|
||||
|
||||
final wallet = MoneroWallet(
|
||||
walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
final wallet = MoneroWallet(walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.init();
|
||||
|
||||
return wallet;
|
||||
|
@ -301,16 +300,14 @@ class MoneroWalletService extends WalletService<
|
|||
return;
|
||||
}
|
||||
|
||||
final oldAndroidWalletDirPath =
|
||||
await outdatedAndroidPathForWalletDir(name: name);
|
||||
final oldAndroidWalletDirPath = await outdatedAndroidPathForWalletDir(name: name);
|
||||
final dir = Directory(oldAndroidWalletDirPath);
|
||||
|
||||
if (!dir.existsSync()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final newWalletDirPath =
|
||||
await pathForWalletDir(name: name, type: getType());
|
||||
final newWalletDirPath = await pathForWalletDir(name: name, type: getType());
|
||||
|
||||
dir.listSync().forEach((f) {
|
||||
final file = File(f.path);
|
||||
|
|
|
@ -69,6 +69,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
|
|||
NanoWallet(walletInfo: currentWalletInfo, password: password, mnemonic: randomWords);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
await saveBackup(newName);
|
||||
|
||||
final newWalletInfo = currentWalletInfo;
|
||||
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||
|
@ -150,14 +151,29 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
|
|||
Future<NanoWallet> openWallet(String name, String password) async {
|
||||
final walletInfo =
|
||||
walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
|
||||
final wallet = await NanoWalletBase.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
await wallet.save();
|
||||
return wallet;
|
||||
try {
|
||||
final wallet = await NanoWalletBase.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
await wallet.save();
|
||||
saveBackup(name);
|
||||
return wallet;
|
||||
} catch (_) {
|
||||
await restoreWalletFilesFromBackup(name);
|
||||
final wallet = await NanoWalletBase.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
await wallet.save();
|
||||
return wallet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,16 +42,31 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
|
|||
Future<PolygonWallet> openWallet(String name, String password) async {
|
||||
final walletInfo =
|
||||
walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
|
||||
final wallet = await PolygonWallet.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
await wallet.save();
|
||||
try {
|
||||
final wallet = await PolygonWallet.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
);
|
||||
|
||||
return wallet;
|
||||
await wallet.init();
|
||||
await wallet.save();
|
||||
saveBackup(name);
|
||||
return wallet;
|
||||
} catch (_) {
|
||||
await restoreWalletFilesFromBackup(name);
|
||||
|
||||
final wallet = await PolygonWallet.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
await wallet.save();
|
||||
return wallet;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -100,6 +115,7 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
|
|||
password: password, name: currentName, walletInfo: currentWalletInfo);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
await saveBackup(newName);
|
||||
|
||||
final newWalletInfo = currentWalletInfo;
|
||||
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||
|
|
|
@ -191,7 +191,7 @@
|
|||
<true/>
|
||||
</dict>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Used for scan QR code</string>
|
||||
<string>Used for scanning QR code and can be used to capture images for identification purposes by third-party providers.</string>
|
||||
<key>NSDocumentsFolderUsageDescription</key>
|
||||
<string>We need access to documents folder for getting access to open/save backup file</string>
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
|
|
|
@ -65,7 +65,7 @@ class CWBitcoin extends Bitcoin {
|
|||
@override
|
||||
Future<void> generateNewAddress(Object wallet, String label) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
await bitcoinWallet.walletAddresses.generateNewAddress(label: label);
|
||||
await bitcoinWallet.walletAddresses.generateNewAddress(label: label, hd: bitcoinWallet.hd);
|
||||
await wallet.save();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:cake_wallet/entities/qr_view_data.dart';
|
||||
import 'package:cake_wallet/themes/extensions/qr_code_theme.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
|
@ -7,7 +6,6 @@ import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dar
|
|||
import 'package:cake_wallet/utils/brightness_util.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
@ -146,10 +144,9 @@ class QRWidget extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: AutoSizeText(
|
||||
child: Text(
|
||||
addressListViewModel.address.address,
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: addressListViewModel.wallet.type == WalletType.monero ? 2 : 1,
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w500,
|
||||
|
|
|
@ -189,7 +189,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
|||
output.loadContact(contact);
|
||||
},
|
||||
validator: validator,
|
||||
selectedCurrency: sendViewModel.currency,
|
||||
selectedCurrency: sendViewModel.selectedCryptoCurrency,
|
||||
);
|
||||
}),
|
||||
if (output.isParsedAddress)
|
||||
|
|
|
@ -69,40 +69,44 @@ class SendTemplateCard extends StatelessWidget {
|
|||
validator: sendTemplateViewModel.templateValidator),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: AddressTextField(
|
||||
selectedCurrency: sendTemplateViewModel.cryptoCurrency,
|
||||
controller: _addressController,
|
||||
onURIScanned: (uri) {
|
||||
final paymentRequest = PaymentRequest.fromUri(uri);
|
||||
_addressController.text = paymentRequest.address;
|
||||
_cryptoAmountController.text = paymentRequest.amount;
|
||||
},
|
||||
options: [
|
||||
AddressTextFieldOption.paste,
|
||||
AddressTextFieldOption.qrCode,
|
||||
AddressTextFieldOption.addressBook
|
||||
],
|
||||
onPushPasteButton: (context) async {
|
||||
template.output.resetParsedAddress();
|
||||
await template.output.fetchParsedAddress(context);
|
||||
},
|
||||
onPushAddressBookButton: (context) async {
|
||||
template.output.resetParsedAddress();
|
||||
await template.output.fetchParsedAddress(context);
|
||||
},
|
||||
buttonColor: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
|
||||
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
|
||||
),
|
||||
validator: sendTemplateViewModel.addressValidator,
|
||||
child: Observer(
|
||||
builder: (context) {
|
||||
return AddressTextField(
|
||||
selectedCurrency: template.selectedCurrency,
|
||||
controller: _addressController,
|
||||
onURIScanned: (uri) {
|
||||
final paymentRequest = PaymentRequest.fromUri(uri);
|
||||
_addressController.text = paymentRequest.address;
|
||||
_cryptoAmountController.text = paymentRequest.amount;
|
||||
},
|
||||
options: [
|
||||
AddressTextFieldOption.paste,
|
||||
AddressTextFieldOption.qrCode,
|
||||
AddressTextFieldOption.addressBook
|
||||
],
|
||||
onPushPasteButton: (context) async {
|
||||
template.output.resetParsedAddress();
|
||||
await template.output.fetchParsedAddress(context);
|
||||
},
|
||||
onPushAddressBookButton: (context) async {
|
||||
template.output.resetParsedAddress();
|
||||
await template.output.fetchParsedAddress(context);
|
||||
},
|
||||
buttonColor: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
|
||||
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||
textStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
|
||||
),
|
||||
validator: sendTemplateViewModel.addressValidator,
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
|
|
|
@ -16,11 +16,15 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||
class ExceptionHandler {
|
||||
static bool _hasError = false;
|
||||
static const _coolDownDurationInDays = 7;
|
||||
static File? _file;
|
||||
|
||||
static void _saveException(String? error, StackTrace? stackTrace, {String? library}) async {
|
||||
final appDocDir = await getApplicationDocumentsDirectory();
|
||||
if (_file == null) {
|
||||
final appDocDir = await getApplicationDocumentsDirectory();
|
||||
|
||||
_file = File('${appDocDir.path}/error.txt');
|
||||
}
|
||||
|
||||
final file = File('${appDocDir.path}/error.txt');
|
||||
final exception = {
|
||||
"${DateTime.now()}": {
|
||||
"Error": "$error\n\n",
|
||||
|
@ -33,14 +37,14 @@ class ExceptionHandler {
|
|||
==========================================================\n\n''';
|
||||
|
||||
/// don't save existing errors
|
||||
if (file.existsSync()) {
|
||||
final String fileContent = await file.readAsString();
|
||||
if (_file!.existsSync()) {
|
||||
final String fileContent = await _file!.readAsString();
|
||||
if (fileContent.contains("${exception.values.first}")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
file.writeAsStringSync(
|
||||
_file!.writeAsStringSync(
|
||||
"$exception $separator",
|
||||
mode: FileMode.append,
|
||||
);
|
||||
|
@ -48,16 +52,18 @@ class ExceptionHandler {
|
|||
|
||||
static void _sendExceptionFile() async {
|
||||
try {
|
||||
final appDocDir = await getApplicationDocumentsDirectory();
|
||||
if (_file == null) {
|
||||
final appDocDir = await getApplicationDocumentsDirectory();
|
||||
|
||||
final file = File('${appDocDir.path}/error.txt');
|
||||
_file = File('${appDocDir.path}/error.txt');
|
||||
}
|
||||
|
||||
await _addDeviceInfo(file);
|
||||
await _addDeviceInfo(_file!);
|
||||
|
||||
final MailOptions mailOptions = MailOptions(
|
||||
subject: 'Mobile App Issue',
|
||||
recipients: ['support@cakewallet.com'],
|
||||
attachments: [file.path],
|
||||
attachments: [_file!.path],
|
||||
);
|
||||
|
||||
final result = await FlutterMailer.send(mailOptions);
|
||||
|
@ -67,7 +73,7 @@ class ExceptionHandler {
|
|||
if (result.name == MailerResponse.sent.name ||
|
||||
result.name == MailerResponse.saved.name ||
|
||||
result.name == MailerResponse.android.name) {
|
||||
file.writeAsString("", mode: FileMode.write);
|
||||
_file!.writeAsString("", mode: FileMode.write);
|
||||
}
|
||||
} catch (e, s) {
|
||||
_saveException(e.toString(), s);
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -13,10 +13,20 @@ void main(List<String> args) async {
|
|||
|
||||
print('Appending "$name": "$text"');
|
||||
|
||||
// add translation to all languages:
|
||||
for (var lang in langs) {
|
||||
final fileName = getArbFileName(lang);
|
||||
final translation = await getTranslation(text, lang);
|
||||
|
||||
appendStringToArbFile(fileName, name, translation);
|
||||
}
|
||||
|
||||
print('Alphabetizing all files...');
|
||||
|
||||
for (var lang in langs) {
|
||||
final fileName = getArbFileName(lang);
|
||||
alphabetizeArbFile(fileName);
|
||||
}
|
||||
|
||||
print('Done!');
|
||||
}
|
|
@ -30,8 +30,10 @@ void main(List<String> args) async {
|
|||
missingDefaults[key] = arbObj[key] as String;
|
||||
});
|
||||
|
||||
if (missingDefaults.isNotEmpty)
|
||||
if (missingDefaults.isNotEmpty) {
|
||||
await appendTranslations(lang, missingDefaults);
|
||||
alphabetizeArbFile(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,9 +47,7 @@ Map<String, dynamic> readArbFile(File file) {
|
|||
}
|
||||
|
||||
String getArbFileName(String lang) {
|
||||
final shortLang = lang
|
||||
.split("-")
|
||||
.first;
|
||||
final shortLang = lang.split("-").first;
|
||||
return "./res/values/strings_$shortLang.arb";
|
||||
}
|
||||
|
||||
|
@ -66,3 +64,25 @@ List<String> getMissingKeysInArbFile(String fileName, Iterable<String> langKeys)
|
|||
|
||||
return results;
|
||||
}
|
||||
|
||||
void alphabetizeArbFile(String fileName) {
|
||||
final file = File(fileName);
|
||||
final arbObj = readArbFile(file);
|
||||
|
||||
final sortedKeys = arbObj.keys.toList()
|
||||
..sort((a, b) => a.toLowerCase().compareTo(b.toLowerCase()));
|
||||
final Map<String, dynamic> sortedArbObj = {};
|
||||
for (var key in sortedKeys) {
|
||||
sortedArbObj[key] = arbObj[key];
|
||||
}
|
||||
|
||||
final outputContent = json
|
||||
.encode(sortedArbObj)
|
||||
.replaceAll('","', '",\n "')
|
||||
.replaceAll('{"', '{\n "')
|
||||
.replaceAll('"}', '"\n}')
|
||||
.replaceAll('":"', '": "')
|
||||
.replaceAll('\$ {', '\${');
|
||||
|
||||
file.writeAsStringSync(outputContent);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue