mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 11:39:22 +00:00
Merge branch 'main' into CW-522-Enable-On-Off-ramp-activities-to-show-on-transaction-history-screen
This commit is contained in:
commit
786baab173
62 changed files with 20057 additions and 19530 deletions
|
@ -8,6 +8,7 @@
|
|||
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||
|
||||
<application
|
||||
android:name=".Application"
|
||||
|
|
|
@ -15,6 +15,10 @@ import android.os.Bundle;
|
|||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
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.Resolution;
|
||||
|
@ -65,6 +69,14 @@ public class MainActivity extends FlutterFragmentActivity {
|
|||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
break;
|
||||
case "disableBatteryOptimization":
|
||||
disableBatteryOptimization();
|
||||
handler.post(() -> result.success(null));
|
||||
break;
|
||||
case "isBatteryOptimizationDisabled":
|
||||
boolean isDisabled = isBatteryOptimizationDisabled();
|
||||
handler.post(() -> result.success(isDisabled));
|
||||
break;
|
||||
default:
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -14,6 +14,10 @@ import android.os.Build;
|
|||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
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.Resolution;
|
||||
|
@ -55,6 +59,14 @@ public class MainActivity extends FlutterFragmentActivity {
|
|||
handler.post(() -> result.success(""));
|
||||
}
|
||||
break;
|
||||
case "disableBatteryOptimization":
|
||||
disableBatteryOptimization();
|
||||
handler.post(() -> result.success(null));
|
||||
break;
|
||||
case "isBatteryOptimizationDisabled":
|
||||
boolean isDisabled = isBatteryOptimizationDisabled();
|
||||
handler.post(() -> result.success(isDisabled));
|
||||
break;
|
||||
default:
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -14,6 +14,10 @@ import android.os.Build;
|
|||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
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.Resolution;
|
||||
|
@ -64,6 +68,14 @@ public class MainActivity extends FlutterFragmentActivity {
|
|||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
break;
|
||||
case "disableBatteryOptimization":
|
||||
disableBatteryOptimization();
|
||||
handler.post(() -> result.success(null));
|
||||
break;
|
||||
case "isBatteryOptimizationDisabled":
|
||||
boolean isDisabled = isBatteryOptimizationDisabled();
|
||||
handler.post(() -> result.success(isDisabled));
|
||||
break;
|
||||
default:
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
Security and Privacy enhancements
|
||||
Usability enhancements
|
||||
Improve wallet recovery and error tolerance
|
||||
Enhance Background sync for Monero wallets
|
||||
Bug fixes
|
|
@ -1,4 +1,5 @@
|
|||
List previously used Bitcoin addresses
|
||||
Security and Privacy enhancements
|
||||
Usability enhancements
|
||||
Bitcoin transactions fixes and enhancements
|
||||
EVM wallets enhancements (Ethereum and Polygon)
|
||||
Improve wallet recovery and error tolerance
|
||||
Enhance Background sync for Monero wallets
|
||||
Bug fixes
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -601,8 +601,6 @@ abstract class ElectrumWalletBase
|
|||
electrumClient.getHistory(scriptHash).then((history) => {scriptHash: history}));
|
||||
final historyResults = await Future.wait(histories);
|
||||
|
||||
|
||||
|
||||
historyResults.forEach((history) {
|
||||
history.entries.forEach((historyItem) {
|
||||
if (historyItem.value.isNotEmpty) {
|
||||
|
@ -622,7 +620,6 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
addressHashes.forEach((sh, addressRecord) {
|
||||
addressRecord.txCount = newTxCounts[sh] ?? 0;
|
||||
});
|
||||
|
|
|
@ -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());
|
||||
|
|
22
cw_core/lib/battery_optimization_native.dart
Normal file
22
cw_core/lib/battery_optimization_native.dart
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:io';
|
||||
|
||||
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_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/view_model/dashboard/dashboard_view_model.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:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
@ -45,12 +48,39 @@ class ConnectionSyncPage extends BasePage {
|
|||
if (DeviceInfo.instance.isMobile) ...[
|
||||
Observer(builder: (context) {
|
||||
return SettingsPickerCell<SyncMode>(
|
||||
title: S.current.background_sync_mode,
|
||||
items: SyncMode.all,
|
||||
displayItem: (SyncMode syncMode) => syncMode.name,
|
||||
selectedItem: dashboardViewModel.syncMode,
|
||||
onItemSelected: dashboardViewModel.setSyncMode,
|
||||
);
|
||||
title: S.current.background_sync_mode,
|
||||
items: SyncMode.all,
|
||||
displayItem: (SyncMode syncMode) => syncMode.name,
|
||||
selectedItem: dashboardViewModel.syncMode,
|
||||
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)),
|
||||
Observer(builder: (context) {
|
||||
|
@ -99,7 +129,6 @@ class ConnectionSyncPage extends BasePage {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
Future<void> _presentReconnectAlert(BuildContext context) async {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -9,7 +9,7 @@ class SyncMode {
|
|||
|
||||
static final all = [
|
||||
SyncMode("Disabled", SyncType.disabled, Duration.zero),
|
||||
SyncMode("Unobtrusive", SyncType.unobtrusive, Duration(days: 1)),
|
||||
SyncMode("Aggressive", SyncType.aggressive, Duration(hours: 6)),
|
||||
SyncMode("Unobtrusive", SyncType.unobtrusive, Duration(hours: 12)),
|
||||
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
|
@ -15,15 +15,15 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
|
|||
APP_ANDROID_TYPE=$1
|
||||
|
||||
MONERO_COM_NAME="Monero.com"
|
||||
MONERO_COM_VERSION="1.10.2"
|
||||
MONERO_COM_BUILD_NUMBER=74
|
||||
MONERO_COM_VERSION="1.10.3"
|
||||
MONERO_COM_BUILD_NUMBER=75
|
||||
MONERO_COM_BUNDLE_ID="com.monero.app"
|
||||
MONERO_COM_PACKAGE="com.monero.app"
|
||||
MONERO_COM_SCHEME="monero.com"
|
||||
|
||||
CAKEWALLET_NAME="Cake Wallet"
|
||||
CAKEWALLET_VERSION="4.13.2"
|
||||
CAKEWALLET_BUILD_NUMBER=191
|
||||
CAKEWALLET_VERSION="4.13.3"
|
||||
CAKEWALLET_BUILD_NUMBER=192
|
||||
CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
|
||||
CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
|
||||
CAKEWALLET_SCHEME="cakewallet"
|
||||
|
|
|
@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
|
|||
APP_IOS_TYPE=$1
|
||||
|
||||
MONERO_COM_NAME="Monero.com"
|
||||
MONERO_COM_VERSION="1.10.2"
|
||||
MONERO_COM_BUILD_NUMBER=72
|
||||
MONERO_COM_VERSION="1.10.3"
|
||||
MONERO_COM_BUILD_NUMBER=73
|
||||
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
|
||||
|
||||
CAKEWALLET_NAME="Cake Wallet"
|
||||
CAKEWALLET_VERSION="4.13.2"
|
||||
CAKEWALLET_BUILD_NUMBER=210
|
||||
CAKEWALLET_VERSION="4.13.3"
|
||||
CAKEWALLET_BUILD_NUMBER=212
|
||||
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
|
||||
|
||||
HAVEN_NAME="Haven"
|
||||
|
|
|
@ -16,13 +16,13 @@ if [ -n "$1" ]; then
|
|||
fi
|
||||
|
||||
MONERO_COM_NAME="Monero.com"
|
||||
MONERO_COM_VERSION="1.0.2"
|
||||
MONERO_COM_BUILD_NUMBER=4
|
||||
MONERO_COM_VERSION="1.0.3"
|
||||
MONERO_COM_BUILD_NUMBER=5
|
||||
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
|
||||
|
||||
CAKEWALLET_NAME="Cake Wallet"
|
||||
CAKEWALLET_VERSION="1.6.2"
|
||||
CAKEWALLET_BUILD_NUMBER=52
|
||||
CAKEWALLET_VERSION="1.6.3"
|
||||
CAKEWALLET_BUILD_NUMBER=53
|
||||
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
|
||||
|
||||
if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then
|
||||
|
|
|
@ -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!');
|
||||
}
|
|
@ -525,6 +525,8 @@ 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_service.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:web3dart/web3dart.dart';
|
||||
|
||||
""";
|
||||
const ethereumCWHeaders = """
|
||||
|
@ -540,8 +542,6 @@ import 'package:cw_ethereum/ethereum_wallet.dart';
|
|||
import 'package:cw_ethereum/ethereum_wallet_service.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';";
|
||||
|
@ -617,6 +617,8 @@ 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_service.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:web3dart/web3dart.dart';
|
||||
|
||||
""";
|
||||
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_service.dart';
|
||||
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:web3dart/web3dart.dart';
|
||||
import 'package:eth_sig_util/util/utils.dart';
|
||||
|
||||
""";
|
||||
|
|
|
@ -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