Merge branch 'main' into CW-522-Enable-On-Off-ramp-activities-to-show-on-transaction-history-screen

This commit is contained in:
Serhii 2024-02-15 16:31:25 +02:00
commit 786baab173
62 changed files with 20057 additions and 19530 deletions

View file

@ -8,6 +8,7 @@
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<application <application
android:name=".Application" android:name=".Application"

View file

@ -15,6 +15,10 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.view.WindowManager; import android.view.WindowManager;
import android.content.Intent;
import android.net.Uri;
import android.os.PowerManager;
import android.provider.Settings;
import com.unstoppabledomains.resolution.DomainResolution; import com.unstoppabledomains.resolution.DomainResolution;
import com.unstoppabledomains.resolution.Resolution; import com.unstoppabledomains.resolution.Resolution;
@ -65,6 +69,14 @@ public class MainActivity extends FlutterFragmentActivity {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
} }
break; break;
case "disableBatteryOptimization":
disableBatteryOptimization();
handler.post(() -> result.success(null));
break;
case "isBatteryOptimizationDisabled":
boolean isDisabled = isBatteryOptimizationDisabled();
handler.post(() -> result.success(isDisabled));
break;
default: default:
handler.post(() -> result.notImplemented()); handler.post(() -> result.notImplemented());
} }
@ -89,4 +101,22 @@ public class MainActivity extends FlutterFragmentActivity {
} }
}); });
} }
private void disableBatteryOptimization() {
String packageName = getPackageName();
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
if (!pm.isIgnoringBatteryOptimizations(packageName)) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + packageName));
startActivity(intent);
}
}
private boolean isBatteryOptimizationDisabled() {
String packageName = getPackageName();
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
return pm.isIgnoringBatteryOptimizations(packageName);
}
} }

View file

@ -14,6 +14,10 @@ import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.view.WindowManager; import android.view.WindowManager;
import android.content.Intent;
import android.net.Uri;
import android.os.PowerManager;
import android.provider.Settings;
import com.unstoppabledomains.resolution.DomainResolution; import com.unstoppabledomains.resolution.DomainResolution;
import com.unstoppabledomains.resolution.Resolution; import com.unstoppabledomains.resolution.Resolution;
@ -55,6 +59,14 @@ public class MainActivity extends FlutterFragmentActivity {
handler.post(() -> result.success("")); handler.post(() -> result.success(""));
} }
break; break;
case "disableBatteryOptimization":
disableBatteryOptimization();
handler.post(() -> result.success(null));
break;
case "isBatteryOptimizationDisabled":
boolean isDisabled = isBatteryOptimizationDisabled();
handler.post(() -> result.success(isDisabled));
break;
default: default:
handler.post(() -> result.notImplemented()); handler.post(() -> result.notImplemented());
} }
@ -79,4 +91,22 @@ public class MainActivity extends FlutterFragmentActivity {
} }
}); });
} }
private void disableBatteryOptimization() {
String packageName = getPackageName();
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
if (!pm.isIgnoringBatteryOptimizations(packageName)) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + packageName));
startActivity(intent);
}
}
private boolean isBatteryOptimizationDisabled() {
String packageName = getPackageName();
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
return pm.isIgnoringBatteryOptimizations(packageName);
}
} }

View file

@ -14,6 +14,10 @@ import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.view.WindowManager; import android.view.WindowManager;
import android.content.Intent;
import android.net.Uri;
import android.os.PowerManager;
import android.provider.Settings;
import com.unstoppabledomains.resolution.DomainResolution; import com.unstoppabledomains.resolution.DomainResolution;
import com.unstoppabledomains.resolution.Resolution; import com.unstoppabledomains.resolution.Resolution;
@ -64,6 +68,14 @@ public class MainActivity extends FlutterFragmentActivity {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
} }
break; break;
case "disableBatteryOptimization":
disableBatteryOptimization();
handler.post(() -> result.success(null));
break;
case "isBatteryOptimizationDisabled":
boolean isDisabled = isBatteryOptimizationDisabled();
handler.post(() -> result.success(isDisabled));
break;
default: default:
handler.post(() -> result.notImplemented()); handler.post(() -> result.notImplemented());
} }
@ -88,4 +100,22 @@ public class MainActivity extends FlutterFragmentActivity {
} }
}); });
} }
private void disableBatteryOptimization() {
String packageName = getPackageName();
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
if (!pm.isIgnoringBatteryOptimizations(packageName)) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse("package:" + packageName));
startActivity(intent);
}
}
private boolean isBatteryOptimizationDisabled() {
String packageName = getPackageName();
PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
return pm.isIgnoringBatteryOptimizations(packageName);
}
} }

View file

@ -1,3 +1,3 @@
Security and Privacy enhancements Improve wallet recovery and error tolerance
Usability enhancements Enhance Background sync for Monero wallets
Bug fixes Bug fixes

View file

@ -1,4 +1,5 @@
List previously used Bitcoin addresses Bitcoin transactions fixes and enhancements
Security and Privacy enhancements EVM wallets enhancements (Ethereum and Polygon)
Usability enhancements Improve wallet recovery and error tolerance
Enhance Background sync for Monero wallets
Bug fixes Bug fixes

View file

@ -28,7 +28,7 @@ class BitcoinAddressRecord {
} }
final String address; final String address;
final bool isHidden; bool isHidden;
final int index; final int index;
int _txCount; int _txCount;
int _balance; int _balance;

View file

@ -12,10 +12,8 @@ import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
class BitcoinWalletService extends WalletService< class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
BitcoinNewWalletCredentials, BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromWIFCredentials> {
BitcoinRestoreWalletFromSeedCredentials,
BitcoinRestoreWalletFromWIFCredentials> {
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
final Box<WalletInfo> walletInfoSource; final Box<WalletInfo> walletInfoSource;
@ -42,28 +40,41 @@ class BitcoinWalletService extends WalletService<
@override @override
Future<BitcoinWallet> openWallet(String name, String password) async { Future<BitcoinWallet> openWallet(String name, String password) async {
final walletInfo = walletInfoSource.values.firstWhereOrNull( final walletInfo = walletInfoSource.values
(info) => info.id == WalletBase.idFor(name, getType()))!; .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
final wallet = await BitcoinWalletBase.open( try {
password: password, name: name, walletInfo: walletInfo, final wallet = await BitcoinWalletBase.open(
unspentCoinsInfo: unspentCoinsInfoSource); password: password,
await wallet.init(); name: name,
return wallet; 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 @override
Future<void> remove(String wallet) async { Future<void> remove(String wallet) async {
File(await pathForWalletDir(name: wallet, type: getType())) File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
.delete(recursive: true); final walletInfo = walletInfoSource.values
final walletInfo = walletInfoSource.values.firstWhereOrNull( .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
(info) => info.id == WalletBase.idFor(wallet, getType()))!;
await walletInfoSource.delete(walletInfo.key); await walletInfoSource.delete(walletInfo.key);
} }
@override @override
Future<void> rename(String currentName, String password, String newName) async { Future<void> rename(String currentName, String password, String newName) async {
final currentWalletInfo = walletInfoSource.values.firstWhereOrNull( final currentWalletInfo = walletInfoSource.values
(info) => info.id == WalletBase.idFor(currentName, getType()))!; .firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
final currentWallet = await BitcoinWalletBase.open( final currentWallet = await BitcoinWalletBase.open(
password: password, password: password,
name: currentName, name: currentName,
@ -71,6 +82,7 @@ class BitcoinWalletService extends WalletService<
unspentCoinsInfo: unspentCoinsInfoSource); unspentCoinsInfo: unspentCoinsInfoSource);
await currentWallet.renameWalletFiles(newName); await currentWallet.renameWalletFiles(newName);
await saveBackup(newName);
final newWalletInfo = currentWalletInfo; final newWalletInfo = currentWalletInfo;
newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.id = WalletBase.idFor(newName, getType());
@ -80,13 +92,11 @@ class BitcoinWalletService extends WalletService<
} }
@override @override
Future<BitcoinWallet> restoreFromKeys( Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials) async =>
BitcoinRestoreWalletFromWIFCredentials credentials) async =>
throw UnimplementedError(); throw UnimplementedError();
@override @override
Future<BitcoinWallet> restoreFromSeed( Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials) async {
BitcoinRestoreWalletFromSeedCredentials credentials) async {
if (!validateMnemonic(credentials.mnemonic)) { if (!validateMnemonic(credentials.mnemonic)) {
throw BitcoinMnemonicIsIncorrectException(); throw BitcoinMnemonicIsIncorrectException();
} }
@ -100,4 +110,4 @@ class BitcoinWalletService extends WalletService<
await wallet.init(); await wallet.init();
return wallet; return wallet;
} }
} }

View file

@ -601,8 +601,6 @@ abstract class ElectrumWalletBase
electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history})); electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history}));
final historyResults = await Future.wait(histories); final historyResults = await Future.wait(histories);
historyResults.forEach((history) { historyResults.forEach((history) {
history.entries.forEach((historyItem) { history.entries.forEach((historyItem) {
if (historyItem.value.isNotEmpty) { if (historyItem.value.isNotEmpty) {
@ -622,7 +620,6 @@ abstract class ElectrumWalletBase
} }
} }
addressHashes.forEach((sh, addressRecord) { addressHashes.forEach((sh, addressRecord) {
addressRecord.txCount = newTxCounts[sh] ?? 0; addressRecord.txCount = newTxCounts[sh] ?? 0;
}); });

View file

@ -53,7 +53,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
String get address { String get address {
if (isEnabledAutoGenerateSubaddress) { if (isEnabledAutoGenerateSubaddress) {
if (receiveAddresses.isEmpty) { if (receiveAddresses.isEmpty) {
final newAddress = generateNewAddress().address; final newAddress = generateNewAddress(hd: mainHd).address;
return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(newAddress) : newAddress; return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(newAddress) : newAddress;
} }
final receiveAddress = receiveAddresses.first.address; final receiveAddress = receiveAddresses.first.address;
@ -215,6 +215,13 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
List<BitcoinAddressRecord> addrs; List<BitcoinAddressRecord> addrs;
if (addresses.isNotEmpty) { if (addresses.isNotEmpty) {
if(!isHidden) {
final receiveAddressesList = addresses.where((addr) => !addr.isHidden).toList();
validateSideHdAddresses(receiveAddressesList);
}
addrs = addresses.where((addr) => addr.isHidden == isHidden).toList(); addrs = addresses.where((addr) => addr.isHidden == isHidden).toList();
} else { } else {
addrs = await _createNewAddresses( addrs = await _createNewAddresses(
@ -296,4 +303,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
final transactionHistory = await electrumClient.getHistory(sh); final transactionHistory = await electrumClient.getHistory(sh);
return transactionHistory.isNotEmpty; return transactionHistory.isNotEmpty;
} }
void validateSideHdAddresses(List<BitcoinAddressRecord> addrWithTransactions) {
addrWithTransactions.forEach((element) {
if (element.address != getAddress(index: element.index, hd: mainHd)) element.isHidden = true;
});
}
} }

View file

@ -45,11 +45,22 @@ class LitecoinWalletService extends WalletService<
Future<LitecoinWallet> openWallet(String name, String password) async { Future<LitecoinWallet> openWallet(String name, String password) async {
final walletInfo = walletInfoSource.values.firstWhereOrNull( final walletInfo = walletInfoSource.values.firstWhereOrNull(
(info) => info.id == WalletBase.idFor(name, getType()))!; (info) => info.id == WalletBase.idFor(name, getType()))!;
final wallet = await LitecoinWalletBase.open(
password: password, name: name, walletInfo: walletInfo, try {
unspentCoinsInfo: unspentCoinsInfoSource); final wallet = await LitecoinWalletBase.open(
await wallet.init(); password: password, name: name, walletInfo: walletInfo,
return wallet; 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 @override
@ -72,6 +83,7 @@ class LitecoinWalletService extends WalletService<
unspentCoinsInfo: unspentCoinsInfoSource); unspentCoinsInfo: unspentCoinsInfoSource);
await currentWallet.renameWalletFiles(newName); await currentWallet.renameWalletFiles(newName);
await saveBackup(newName);
final newWalletInfo = currentWalletInfo; final newWalletInfo = currentWalletInfo;
newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.id = WalletBase.idFor(newName, getType());

View file

@ -51,11 +51,22 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
Future<BitcoinCashWallet> openWallet(String name, String password) async { Future<BitcoinCashWallet> openWallet(String name, String password) async {
final walletInfo = walletInfoSource.values.firstWhereOrNull( final walletInfo = walletInfoSource.values.firstWhereOrNull(
(info) => info.id == WalletBase.idFor(name, getType()))!; (info) => info.id == WalletBase.idFor(name, getType()))!;
final wallet = await BitcoinCashWalletBase.open(
password: password, name: name, walletInfo: walletInfo, try {
unspentCoinsInfo: unspentCoinsInfoSource); final wallet = await BitcoinCashWalletBase.open(
await wallet.init(); password: password, name: name, walletInfo: walletInfo,
return wallet; 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 @override
@ -78,6 +89,7 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
unspentCoinsInfo: unspentCoinsInfoSource); unspentCoinsInfo: unspentCoinsInfoSource);
await currentWallet.renameWalletFiles(newName); await currentWallet.renameWalletFiles(newName);
await saveBackup(newName);
final newWalletInfo = currentWalletInfo; final newWalletInfo = currentWalletInfo;
newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.id = WalletBase.idFor(newName, getType());

View file

@ -0,0 +1,22 @@
import 'package:flutter/services.dart';
const MethodChannel _channel = MethodChannel('com.cake_wallet/native_utils');
Future<void> requestDisableBatteryOptimization() async {
try {
await _channel.invokeMethod('disableBatteryOptimization');
} on PlatformException catch (e) {
print("Failed to disable battery optimization: '${e.message}'.");
}
}
Future<bool> isBatteryOptimizationDisabled() async {
try {
final bool isDisabled = await _channel.invokeMethod('isBatteryOptimizationDisabled') as bool;
print('It\'s actually disabled? $isDisabled');
return isDisabled;
} on PlatformException catch (e) {
print("Failed to check battery optimization status: '${e.message}'.");
return false;
}
}

View file

@ -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 { Future<bool> backupWalletFilesExists(String name) async {
final walletDirPath = await pathForWalletDir(name: name, type: WalletType.monero); final walletDirPath = await pathForWalletDir(name: name, type: WalletType.monero);
final cacheFilePath = '$walletDirPath/$name'; final cacheFilePath = '$walletDirPath/$name';
@ -63,9 +74,9 @@ Future<bool> backupWalletFilesExists(String name) async {
final backupKeysFile = File(backupFileName(keysFilePath)); final backupKeysFile = File(backupFileName(keysFilePath));
final backupAddressListFile = File(backupFileName(addressListFilePath)); final backupAddressListFile = File(backupFileName(addressListFilePath));
return backupCacheFile.existsSync() return backupCacheFile.existsSync() &&
&& backupKeysFile.existsSync() backupKeysFile.existsSync() &&
&& backupAddressListFile.existsSync(); backupAddressListFile.existsSync();
} }
Future<void> removeCache(String name) async { Future<void> removeCache(String name) async {
@ -85,4 +96,4 @@ Future<void> restoreOrResetWalletFiles(String name) async {
} }
removeCache(name); removeCache(name);
} }

View file

@ -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_base.dart';
import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
abstract class WalletService<N extends WalletCredentials, RFS extends WalletCredentials, 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> remove(String wallet);
Future<void> rename(String currentName, String password, String newName); 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);
}
}
} }

View file

@ -39,16 +39,31 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
Future<EthereumWallet> openWallet(String name, String password) async { Future<EthereumWallet> openWallet(String name, String password) async {
final walletInfo = final walletInfo =
walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType())); walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
final wallet = await EthereumWallet.open(
name: name,
password: password,
walletInfo: walletInfo,
);
await wallet.init(); try {
await wallet.save(); 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 @override
@ -59,6 +74,7 @@ class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
password: password, name: currentName, walletInfo: currentWalletInfo); password: password, name: currentName, walletInfo: currentWalletInfo);
await currentWallet.renameWalletFiles(newName); await currentWallet.renameWalletFiles(newName);
await saveBackup(newName);
final newWalletInfo = currentWalletInfo; final newWalletInfo = currentWalletInfo;
newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.id = WalletBase.idFor(newName, getType());

View file

@ -163,6 +163,7 @@ class HavenWalletService extends WalletService<
final currentWallet = HavenWallet(walletInfo: currentWalletInfo); final currentWallet = HavenWallet(walletInfo: currentWalletInfo);
await currentWallet.renameWalletFiles(newName); await currentWallet.renameWalletFiles(newName);
await saveBackup(newName);
final newWalletInfo = currentWalletInfo; final newWalletInfo = currentWalletInfo;
newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.id = WalletBase.idFor(newName, getType());

View file

@ -36,6 +36,8 @@ import 'package:mobx/mobx.dart';
part 'monero_wallet.g.dart'; part 'monero_wallet.g.dart';
const moneroBlockSize = 1000; 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; class MoneroWallet = MoneroWalletBase with _$MoneroWallet;
@ -79,7 +81,7 @@ abstract class MoneroWalletBase
Box<UnspentCoinsInfo> unspentCoinsInfo; Box<UnspentCoinsInfo> unspentCoinsInfo;
void Function(FlutterErrorDetails)? _onError; void Function(FlutterErrorDetails)? onError;
@override @override
late MoneroWalletAddresses walletAddresses; late MoneroWalletAddresses walletAddresses;
@ -171,7 +173,26 @@ abstract class MoneroWalletBase
Future<void> startSync() async { Future<void> startSync() async {
try { try {
_setInitialHeight(); _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 { try {
syncStatus = AttemptingSyncStatus(); syncStatus = AttemptingSyncStatus();
@ -339,6 +360,8 @@ abstract class MoneroWalletBase
if (currentAddressListFile.existsSync()) { if (currentAddressListFile.existsSync()) {
await currentAddressListFile.rename('$newWalletPath.address.txt'); await currentAddressListFile.rename('$newWalletPath.address.txt');
} }
await backupWalletFiles(newWalletName);
} catch (e) { } catch (e) {
final currentWalletPath = await pathForWallet(name: name, type: type); final currentWalletPath = await pathForWallet(name: name, type: type);
@ -402,9 +425,7 @@ abstract class MoneroWalletBase
if (coin.spent == 0) { if (coin.spent == 0) {
final unspent = MoneroUnspent.fromCoinsInfoRow(coin); final unspent = MoneroUnspent.fromCoinsInfoRow(coin);
if (unspent.hash.isNotEmpty) { if (unspent.hash.isNotEmpty) {
unspent.isChange = transaction_history unspent.isChange = transaction_history.getTransaction(unspent.hash).direction == 1;
.getTransaction(unspent.hash)
.direction == 1;
} }
unspentCoins.add(unspent); unspentCoins.add(unspent);
} }
@ -418,7 +439,7 @@ abstract class MoneroWalletBase
if (unspentCoins.isNotEmpty) { if (unspentCoins.isNotEmpty) {
unspentCoins.forEach((coin) { unspentCoins.forEach((coin) {
final coinInfoList = unspentCoinsInfo.values.where((element) => final coinInfoList = unspentCoinsInfo.values.where((element) =>
element.walletId.contains(id) && element.walletId.contains(id) &&
element.accountIndex == walletAddresses.account!.id && element.accountIndex == walletAddresses.account!.id &&
element.keyImage!.contains(coin.keyImage!)); element.keyImage!.contains(coin.keyImage!));
@ -438,7 +459,7 @@ abstract class MoneroWalletBase
_askForUpdateBalance(); _askForUpdateBalance();
} catch (e, s) { } catch (e, s) {
print(e.toString()); print(e.toString());
_onError?.call(FlutterErrorDetails( onError?.call(FlutterErrorDetails(
exception: e, exception: e,
stack: s, stack: s,
library: this.runtimeType.toString(), library: this.runtimeType.toString(),
@ -534,18 +555,36 @@ abstract class MoneroWalletBase
_listener = monero_wallet.setListeners(_onNewBlock, _onNewTransaction); _listener = monero_wallet.setListeners(_onNewBlock, _onNewTransaction);
} }
// check if the height is correct:
void _setInitialHeight() { void _setInitialHeight() {
if (walletInfo.isRecovery) { if (walletInfo.isRecovery) {
return; return;
} }
final currentHeight = monero_wallet.getCurrentHeight(); final height = monero_wallet.getCurrentHeight();
if (currentHeight <= 1) { if (height > MIN_RESTORE_HEIGHT) {
final height = _getHeightByDate(walletInfo.date); // 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.setRecoveringFromSeed(isRecovery: true);
monero_wallet.setRefreshFromBlockHeight(height: height); monero_wallet.setRefreshFromBlockHeight(height: height);
return;
} }
throw Exception("height isn't > $MIN_RESTORE_HEIGHT!");
} }
int _getHeightDistance(DateTime date) { int _getHeightDistance(DateTime date) {
@ -561,7 +600,8 @@ abstract class MoneroWalletBase
final heightDistance = _getHeightDistance(date); final heightDistance = _getHeightDistance(date);
if (nodeHeight <= 0) { 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; return nodeHeight - heightDistance;
@ -650,7 +690,7 @@ abstract class MoneroWalletBase
} }
@override @override
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError; void setExceptionHandler(void Function(FlutterErrorDetails) e) => onError = e;
@override @override
String signMessage(String message, {String? address}) { String signMessage(String message, {String? address}) {

View file

@ -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/exceptions/wallet_opening_exception.dart';
import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager; import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager;
import 'package:cw_monero/monero_wallet.dart'; import 'package:cw_monero/monero_wallet.dart';
import 'package:flutter/widgets.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:polyseed/polyseed.dart'; import 'package:polyseed/polyseed.dart';
class MoneroNewWalletCredentials extends WalletCredentials { 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); : super(name: name, password: password);
final String language; final String language;
@ -52,10 +54,8 @@ class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
final String spendKey; final String spendKey;
} }
class MoneroWalletService extends WalletService< class MoneroWalletService extends WalletService<MoneroNewWalletCredentials,
MoneroNewWalletCredentials, MoneroRestoreWalletFromSeedCredentials, MoneroRestoreWalletFromKeysCredentials> {
MoneroRestoreWalletFromSeedCredentials,
MoneroRestoreWalletFromKeysCredentials> {
MoneroWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); MoneroWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
final Box<WalletInfo> walletInfoSource; final Box<WalletInfo> walletInfoSource;
@ -112,6 +112,7 @@ class MoneroWalletService extends WalletService<
@override @override
Future<MoneroWallet> openWallet(String name, String password) async { Future<MoneroWallet> openWallet(String name, String password) async {
MoneroWallet? wallet;
try { try {
final path = await pathForWallet(name: name, type: getType()); final path = await pathForWallet(name: name, type: getType());
@ -119,11 +120,10 @@ class MoneroWalletService extends WalletService<
await repairOldAndroidWallet(name); await repairOldAndroidWallet(name);
} }
await monero_wallet_manager await monero_wallet_manager.openWalletAsync({'path': path, 'password': password});
.openWalletAsync({'path': path, 'password': password}); final walletInfo = walletInfoSource.values
final walletInfo = walletInfoSource.values.firstWhere( .firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
(info) => info.id == WalletBase.idFor(name, getType())); wallet = MoneroWallet(walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
final wallet = MoneroWallet(walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
final isValid = wallet.walletAddresses.validate(); final isValid = wallet.walletAddresses.validate();
if (!isValid) { if (!isValid) {
@ -135,7 +135,7 @@ class MoneroWalletService extends WalletService<
await wallet.init(); await wallet.init();
return wallet; return wallet;
} catch (e) { } catch (e, s) {
// TODO: Implement Exception for wallet list service. // TODO: Implement Exception for wallet list service.
final bool isBadAlloc = e.toString().contains('bad_alloc') || final bool isBadAlloc = e.toString().contains('bad_alloc') ||
@ -156,16 +156,18 @@ class MoneroWalletService extends WalletService<
final bool invalidSignature = e.toString().contains('invalid signature') || final bool invalidSignature = e.toString().contains('invalid signature') ||
(e is WalletOpeningException && e.message.contains('invalid signature')); (e is WalletOpeningException && e.message.contains('invalid signature'));
if (isBadAlloc || if (!isBadAlloc &&
doesNotCorrespond || !doesNotCorrespond &&
isMissingCacheFilesIOS || !isMissingCacheFilesIOS &&
isMissingCacheFilesAndroid || !isMissingCacheFilesAndroid &&
invalidSignature) { !invalidSignature &&
await restoreOrResetWalletFiles(name); wallet != null &&
return openWallet(name, password); 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 @override
Future<void> rename( Future<void> rename(String currentName, String password, String newName) async {
String currentName, String password, String newName) async { final currentWalletInfo = walletInfoSource.values
final currentWalletInfo = walletInfoSource.values.firstWhere( .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
(info) => info.id == WalletBase.idFor(currentName, getType()));
final currentWallet = final currentWallet =
MoneroWallet(walletInfo: currentWalletInfo, unspentCoinsInfo: unspentCoinsInfoSource); MoneroWallet(walletInfo: currentWalletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
@ -202,8 +203,7 @@ class MoneroWalletService extends WalletService<
} }
@override @override
Future<MoneroWallet> restoreFromKeys( Future<MoneroWallet> restoreFromKeys(MoneroRestoreWalletFromKeysCredentials credentials) async {
MoneroRestoreWalletFromKeysCredentials credentials) async {
try { try {
final path = await pathForWallet(name: credentials.name, type: getType()); final path = await pathForWallet(name: credentials.name, type: getType());
await monero_wallet_manager.restoreFromKeys( await monero_wallet_manager.restoreFromKeys(
@ -227,9 +227,7 @@ class MoneroWalletService extends WalletService<
} }
@override @override
Future<MoneroWallet> restoreFromSeed( Future<MoneroWallet> restoreFromSeed(MoneroRestoreWalletFromSeedCredentials credentials) async {
MoneroRestoreWalletFromSeedCredentials credentials) async {
// Restore from Polyseed // Restore from Polyseed
if (Polyseed.isValidSeed(credentials.mnemonic)) { if (Polyseed.isValidSeed(credentials.mnemonic)) {
return restoreFromPolyseed(credentials); return restoreFromPolyseed(credentials);
@ -254,14 +252,16 @@ class MoneroWalletService extends WalletService<
} }
} }
Future<MoneroWallet> restoreFromPolyseed(MoneroRestoreWalletFromSeedCredentials credentials) async { Future<MoneroWallet> restoreFromPolyseed(
MoneroRestoreWalletFromSeedCredentials credentials) async {
try { try {
final path = await pathForWallet(name: credentials.name, type: getType()); final path = await pathForWallet(name: credentials.name, type: getType());
final polyseedCoin = PolyseedCoin.POLYSEED_MONERO; final polyseedCoin = PolyseedCoin.POLYSEED_MONERO;
final lang = PolyseedLang.getByPhrase(credentials.mnemonic); final lang = PolyseedLang.getByPhrase(credentials.mnemonic);
final polyseed = Polyseed.decode(credentials.mnemonic, lang, polyseedCoin); 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) { } catch (e) {
// TODO: Implement Exception for wallet list service. // TODO: Implement Exception for wallet list service.
print('MoneroWalletsManager Error: $e'); print('MoneroWalletsManager Error: $e');
@ -269,11 +269,11 @@ class MoneroWalletService extends WalletService<
} }
} }
Future<MoneroWallet> _restoreFromPolyseed(String path, String password, Polyseed polyseed, Future<MoneroWallet> _restoreFromPolyseed(
WalletInfo walletInfo, PolyseedLang lang, String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang,
{PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO, int? overrideHeight}) async { {PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO, int? overrideHeight}) async {
final height = overrideHeight ?? getMoneroHeigthByDate( final height = overrideHeight ??
date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000)); getMoneroHeigthByDate(date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000));
final spendKey = polyseed.generateKey(coin, 32).toHexString(); final spendKey = polyseed.generateKey(coin, 32).toHexString();
final seed = polyseed.encode(lang, coin); final seed = polyseed.encode(lang, coin);
@ -288,8 +288,7 @@ class MoneroWalletService extends WalletService<
restoreHeight: height, restoreHeight: height,
spendKey: spendKey); spendKey: spendKey);
final wallet = MoneroWallet( final wallet = MoneroWallet(walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
await wallet.init(); await wallet.init();
return wallet; return wallet;
@ -301,16 +300,14 @@ class MoneroWalletService extends WalletService<
return; return;
} }
final oldAndroidWalletDirPath = final oldAndroidWalletDirPath = await outdatedAndroidPathForWalletDir(name: name);
await outdatedAndroidPathForWalletDir(name: name);
final dir = Directory(oldAndroidWalletDirPath); final dir = Directory(oldAndroidWalletDirPath);
if (!dir.existsSync()) { if (!dir.existsSync()) {
return; return;
} }
final newWalletDirPath = final newWalletDirPath = await pathForWalletDir(name: name, type: getType());
await pathForWalletDir(name: name, type: getType());
dir.listSync().forEach((f) { dir.listSync().forEach((f) {
final file = File(f.path); final file = File(f.path);

View file

@ -69,6 +69,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
NanoWallet(walletInfo: currentWalletInfo, password: password, mnemonic: randomWords); NanoWallet(walletInfo: currentWalletInfo, password: password, mnemonic: randomWords);
await currentWallet.renameWalletFiles(newName); await currentWallet.renameWalletFiles(newName);
await saveBackup(newName);
final newWalletInfo = currentWalletInfo; final newWalletInfo = currentWalletInfo;
newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.id = WalletBase.idFor(newName, getType());
@ -150,14 +151,29 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
Future<NanoWallet> openWallet(String name, String password) async { Future<NanoWallet> openWallet(String name, String password) async {
final walletInfo = final walletInfo =
walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType())); walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
final wallet = await NanoWalletBase.open(
name: name,
password: password,
walletInfo: walletInfo,
);
await wallet.init(); try {
await wallet.save(); final wallet = await NanoWalletBase.open(
return wallet; 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;
}
} }
} }

View file

@ -42,16 +42,31 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
Future<PolygonWallet> openWallet(String name, String password) async { Future<PolygonWallet> openWallet(String name, String password) async {
final walletInfo = final walletInfo =
walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType())); walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
final wallet = await PolygonWallet.open(
name: name,
password: password,
walletInfo: walletInfo,
);
await wallet.init(); try {
await wallet.save(); 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 @override
@ -100,6 +115,7 @@ class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
password: password, name: currentName, walletInfo: currentWalletInfo); password: password, name: currentName, walletInfo: currentWalletInfo);
await currentWallet.renameWalletFiles(newName); await currentWallet.renameWalletFiles(newName);
await saveBackup(newName);
final newWalletInfo = currentWalletInfo; final newWalletInfo = currentWalletInfo;
newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.id = WalletBase.idFor(newName, getType());

View file

@ -191,7 +191,7 @@
<true/> <true/>
</dict> </dict>
<key>NSCameraUsageDescription</key> <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> <key>NSDocumentsFolderUsageDescription</key>
<string>We need access to documents folder for getting access to open/save backup file</string> <string>We need access to documents folder for getting access to open/save backup file</string>
<key>NSFaceIDUsageDescription</key> <key>NSFaceIDUsageDescription</key>

View file

@ -65,7 +65,7 @@ class CWBitcoin extends Bitcoin {
@override @override
Future<void> generateNewAddress(Object wallet, String label) async { Future<void> generateNewAddress(Object wallet, String label) async {
final bitcoinWallet = wallet as ElectrumWallet; final bitcoinWallet = wallet as ElectrumWallet;
await bitcoinWallet.walletAddresses.generateNewAddress(label: label); await bitcoinWallet.walletAddresses.generateNewAddress(label: label, hd: bitcoinWallet.hd);
await wallet.save(); await wallet.save();
} }

View file

@ -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/entities/qr_view_data.dart';
import 'package:cake_wallet/themes/extensions/qr_code_theme.dart'; import 'package:cake_wallet/themes/extensions/qr_code_theme.dart';
import 'package:cake_wallet/routes.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/brightness_util.dart';
import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.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/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
@ -146,10 +144,9 @@ class QRWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: AutoSizeText( child: Text(
addressListViewModel.address.address, addressListViewModel.address.address,
textAlign: TextAlign.center, textAlign: TextAlign.center,
maxLines: addressListViewModel.wallet.type == WalletType.monero ? 2 : 1,
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,

View file

@ -189,7 +189,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
output.loadContact(contact); output.loadContact(contact);
}, },
validator: validator, validator: validator,
selectedCurrency: sendViewModel.currency, selectedCurrency: sendViewModel.selectedCryptoCurrency,
); );
}), }),
if (output.isParsedAddress) if (output.isParsedAddress)

View file

@ -69,40 +69,44 @@ class SendTemplateCard extends StatelessWidget {
validator: sendTemplateViewModel.templateValidator), validator: sendTemplateViewModel.templateValidator),
Padding( Padding(
padding: EdgeInsets.only(top: 20), padding: EdgeInsets.only(top: 20),
child: AddressTextField( child: Observer(
selectedCurrency: sendTemplateViewModel.cryptoCurrency, builder: (context) {
controller: _addressController, return AddressTextField(
onURIScanned: (uri) { selectedCurrency: template.selectedCurrency,
final paymentRequest = PaymentRequest.fromUri(uri); controller: _addressController,
_addressController.text = paymentRequest.address; onURIScanned: (uri) {
_cryptoAmountController.text = paymentRequest.amount; final paymentRequest = PaymentRequest.fromUri(uri);
}, _addressController.text = paymentRequest.address;
options: [ _cryptoAmountController.text = paymentRequest.amount;
AddressTextFieldOption.paste, },
AddressTextFieldOption.qrCode, options: [
AddressTextFieldOption.addressBook AddressTextFieldOption.paste,
], AddressTextFieldOption.qrCode,
onPushPasteButton: (context) async { AddressTextFieldOption.addressBook
template.output.resetParsedAddress(); ],
await template.output.fetchParsedAddress(context); onPushPasteButton: (context) async {
}, template.output.resetParsedAddress();
onPushAddressBookButton: (context) async { await template.output.fetchParsedAddress(context);
template.output.resetParsedAddress(); },
await template.output.fetchParsedAddress(context); onPushAddressBookButton: (context) async {
}, template.output.resetParsedAddress();
buttonColor: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor, await template.output.fetchParsedAddress(context);
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor, },
textStyle: TextStyle( buttonColor: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
fontSize: 14, borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
fontWeight: FontWeight.w500, textStyle: TextStyle(
color: Colors.white, fontSize: 14,
), fontWeight: FontWeight.w500,
hintStyle: TextStyle( color: Colors.white,
fontSize: 14, ),
fontWeight: FontWeight.w500, hintStyle: TextStyle(
color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor, fontSize: 14,
), fontWeight: FontWeight.w500,
validator: sendTemplateViewModel.addressValidator, color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
),
validator: sendTemplateViewModel.addressValidator,
);
}
), ),
), ),
Padding( Padding(

View file

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
@ -8,6 +10,7 @@ import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/settings/sync_mode.dart'; import 'package:cake_wallet/view_model/settings/sync_mode.dart';
import 'package:cw_core/battery_optimization_native.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
@ -45,12 +48,39 @@ class ConnectionSyncPage extends BasePage {
if (DeviceInfo.instance.isMobile) ...[ if (DeviceInfo.instance.isMobile) ...[
Observer(builder: (context) { Observer(builder: (context) {
return SettingsPickerCell<SyncMode>( return SettingsPickerCell<SyncMode>(
title: S.current.background_sync_mode, title: S.current.background_sync_mode,
items: SyncMode.all, items: SyncMode.all,
displayItem: (SyncMode syncMode) => syncMode.name, displayItem: (SyncMode syncMode) => syncMode.name,
selectedItem: dashboardViewModel.syncMode, selectedItem: dashboardViewModel.syncMode,
onItemSelected: dashboardViewModel.setSyncMode, onItemSelected: (syncMode) async {
); dashboardViewModel.setSyncMode(syncMode);
if (Platform.isIOS) return;
if (syncMode.type != SyncType.disabled) {
final isDisabled = await isBatteryOptimizationDisabled();
if (isDisabled) return;
await showPopUp<void>(
context: context,
builder: (BuildContext dialogContext) {
return AlertWithTwoActions(
alertTitle: S.current.disableBatteryOptimization,
alertContent: S.current.disableBatteryOptimizationDescription,
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).ok,
actionLeftButton: () => Navigator.of(dialogContext).pop(),
actionRightButton: () async {
await requestDisableBatteryOptimization();
Navigator.of(dialogContext).pop();
},
);
},
);
}
});
}), }),
const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
Observer(builder: (context) { Observer(builder: (context) {
@ -99,7 +129,6 @@ class ConnectionSyncPage extends BasePage {
); );
} }
Future<void> _presentReconnectAlert(BuildContext context) async { Future<void> _presentReconnectAlert(BuildContext context) async {
await showPopUp<void>( await showPopUp<void>(
context: context, context: context,

View file

@ -16,11 +16,15 @@ import 'package:shared_preferences/shared_preferences.dart';
class ExceptionHandler { class ExceptionHandler {
static bool _hasError = false; static bool _hasError = false;
static const _coolDownDurationInDays = 7; static const _coolDownDurationInDays = 7;
static File? _file;
static void _saveException(String? error, StackTrace? stackTrace, {String? library}) async { 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 = { final exception = {
"${DateTime.now()}": { "${DateTime.now()}": {
"Error": "$error\n\n", "Error": "$error\n\n",
@ -33,14 +37,14 @@ class ExceptionHandler {
==========================================================\n\n'''; ==========================================================\n\n''';
/// don't save existing errors /// don't save existing errors
if (file.existsSync()) { if (_file!.existsSync()) {
final String fileContent = await file.readAsString(); final String fileContent = await _file!.readAsString();
if (fileContent.contains("${exception.values.first}")) { if (fileContent.contains("${exception.values.first}")) {
return; return;
} }
} }
file.writeAsStringSync( _file!.writeAsStringSync(
"$exception $separator", "$exception $separator",
mode: FileMode.append, mode: FileMode.append,
); );
@ -48,16 +52,18 @@ class ExceptionHandler {
static void _sendExceptionFile() async { static void _sendExceptionFile() async {
try { 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( final MailOptions mailOptions = MailOptions(
subject: 'Mobile App Issue', subject: 'Mobile App Issue',
recipients: ['support@cakewallet.com'], recipients: ['support@cakewallet.com'],
attachments: [file.path], attachments: [_file!.path],
); );
final result = await FlutterMailer.send(mailOptions); final result = await FlutterMailer.send(mailOptions);
@ -67,7 +73,7 @@ class ExceptionHandler {
if (result.name == MailerResponse.sent.name || if (result.name == MailerResponse.sent.name ||
result.name == MailerResponse.saved.name || result.name == MailerResponse.saved.name ||
result.name == MailerResponse.android.name) { result.name == MailerResponse.android.name) {
file.writeAsString("", mode: FileMode.write); _file!.writeAsString("", mode: FileMode.write);
} }
} catch (e, s) { } catch (e, s) {
_saveException(e.toString(), s); _saveException(e.toString(), s);

View file

@ -9,7 +9,7 @@ class SyncMode {
static final all = [ static final all = [
SyncMode("Disabled", SyncType.disabled, Duration.zero), SyncMode("Disabled", SyncType.disabled, Duration.zero),
SyncMode("Unobtrusive", SyncType.unobtrusive, Duration(days: 1)), SyncMode("Unobtrusive", SyncType.unobtrusive, Duration(hours: 12)),
SyncMode("Aggressive", SyncType.aggressive, Duration(hours: 6)), SyncMode("Aggressive", SyncType.aggressive, Duration(hours: 3)),
]; ];
} }

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

View file

@ -15,15 +15,15 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
APP_ANDROID_TYPE=$1 APP_ANDROID_TYPE=$1
MONERO_COM_NAME="Monero.com" MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="1.10.2" MONERO_COM_VERSION="1.10.3"
MONERO_COM_BUILD_NUMBER=74 MONERO_COM_BUILD_NUMBER=75
MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_BUNDLE_ID="com.monero.app"
MONERO_COM_PACKAGE="com.monero.app" MONERO_COM_PACKAGE="com.monero.app"
MONERO_COM_SCHEME="monero.com" MONERO_COM_SCHEME="monero.com"
CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.13.2" CAKEWALLET_VERSION="4.13.3"
CAKEWALLET_BUILD_NUMBER=191 CAKEWALLET_BUILD_NUMBER=192
CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
CAKEWALLET_SCHEME="cakewallet" CAKEWALLET_SCHEME="cakewallet"

View file

@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
APP_IOS_TYPE=$1 APP_IOS_TYPE=$1
MONERO_COM_NAME="Monero.com" MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="1.10.2" MONERO_COM_VERSION="1.10.3"
MONERO_COM_BUILD_NUMBER=72 MONERO_COM_BUILD_NUMBER=73
MONERO_COM_BUNDLE_ID="com.cakewallet.monero" MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.13.2" CAKEWALLET_VERSION="4.13.3"
CAKEWALLET_BUILD_NUMBER=210 CAKEWALLET_BUILD_NUMBER=212
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
HAVEN_NAME="Haven" HAVEN_NAME="Haven"

View file

@ -16,13 +16,13 @@ if [ -n "$1" ]; then
fi fi
MONERO_COM_NAME="Monero.com" MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="1.0.2" MONERO_COM_VERSION="1.0.3"
MONERO_COM_BUILD_NUMBER=4 MONERO_COM_BUILD_NUMBER=5
MONERO_COM_BUNDLE_ID="com.cakewallet.monero" MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="1.6.2" CAKEWALLET_VERSION="1.6.3"
CAKEWALLET_BUILD_NUMBER=52 CAKEWALLET_BUILD_NUMBER=53
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then

View file

@ -13,10 +13,20 @@ void main(List<String> args) async {
print('Appending "$name": "$text"'); print('Appending "$name": "$text"');
// add translation to all languages:
for (var lang in langs) { for (var lang in langs) {
final fileName = getArbFileName(lang); final fileName = getArbFileName(lang);
final translation = await getTranslation(text, lang); final translation = await getTranslation(text, lang);
appendStringToArbFile(fileName, name, translation); appendStringToArbFile(fileName, name, translation);
} }
print('Alphabetizing all files...');
for (var lang in langs) {
final fileName = getArbFileName(lang);
alphabetizeArbFile(fileName);
}
print('Done!');
} }

View file

@ -525,6 +525,8 @@ import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_service.dart';
import 'package:hive/hive.dart';
import 'package:web3dart/web3dart.dart';
"""; """;
const ethereumCWHeaders = """ const ethereumCWHeaders = """
@ -540,8 +542,6 @@ import 'package:cw_ethereum/ethereum_wallet.dart';
import 'package:cw_ethereum/ethereum_wallet_service.dart'; import 'package:cw_ethereum/ethereum_wallet_service.dart';
import 'package:eth_sig_util/util/utils.dart'; import 'package:eth_sig_util/util/utils.dart';
import 'package:hive/hive.dart';
import 'package:web3dart/web3dart.dart';
"""; """;
const ethereumCwPart = "part 'cw_ethereum.dart';"; const ethereumCwPart = "part 'cw_ethereum.dart';";
@ -617,6 +617,8 @@ import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_service.dart';
import 'package:hive/hive.dart';
import 'package:web3dart/web3dart.dart';
"""; """;
const polygonCWHeaders = """ const polygonCWHeaders = """
@ -631,8 +633,6 @@ import 'package:cw_polygon/polygon_client.dart';
import 'package:cw_polygon/polygon_wallet.dart'; import 'package:cw_polygon/polygon_wallet.dart';
import 'package:cw_polygon/polygon_wallet_service.dart'; import 'package:cw_polygon/polygon_wallet_service.dart';
import 'package:hive/hive.dart';
import 'package:web3dart/web3dart.dart';
import 'package:eth_sig_util/util/utils.dart'; import 'package:eth_sig_util/util/utils.dart';
"""; """;

View file

@ -30,8 +30,10 @@ void main(List<String> args) async {
missingDefaults[key] = arbObj[key] as String; missingDefaults[key] = arbObj[key] as String;
}); });
if (missingDefaults.isNotEmpty) if (missingDefaults.isNotEmpty) {
await appendTranslations(lang, missingDefaults); await appendTranslations(lang, missingDefaults);
alphabetizeArbFile(fileName);
}
} }
} }
} }

View file

@ -47,9 +47,7 @@ Map<String, dynamic> readArbFile(File file) {
} }
String getArbFileName(String lang) { String getArbFileName(String lang) {
final shortLang = lang final shortLang = lang.split("-").first;
.split("-")
.first;
return "./res/values/strings_$shortLang.arb"; return "./res/values/strings_$shortLang.arb";
} }
@ -66,3 +64,25 @@ List<String> getMissingKeysInArbFile(String fileName, Iterable<String> langKeys)
return results; 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);
}