mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-11-17 09:47:35 +00:00
commit
ab5077e5f5
32 changed files with 1139 additions and 141 deletions
|
@ -26,8 +26,42 @@ PODS:
|
|||
- Flutter
|
||||
- devicelocale (0.0.1):
|
||||
- Flutter
|
||||
- DKImagePickerController/Core (4.3.2):
|
||||
- DKImagePickerController/ImageDataManager
|
||||
- DKImagePickerController/Resource
|
||||
- DKImagePickerController/ImageDataManager (4.3.2)
|
||||
- DKImagePickerController/PhotoGallery (4.3.2):
|
||||
- DKImagePickerController/Core
|
||||
- DKPhotoGallery
|
||||
- DKImagePickerController/Resource (4.3.2)
|
||||
- DKPhotoGallery (0.0.17):
|
||||
- DKPhotoGallery/Core (= 0.0.17)
|
||||
- DKPhotoGallery/Model (= 0.0.17)
|
||||
- DKPhotoGallery/Preview (= 0.0.17)
|
||||
- DKPhotoGallery/Resource (= 0.0.17)
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Core (0.0.17):
|
||||
- DKPhotoGallery/Model
|
||||
- DKPhotoGallery/Preview
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Model (0.0.17):
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Preview (0.0.17):
|
||||
- DKPhotoGallery/Model
|
||||
- DKPhotoGallery/Resource
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Resource (0.0.17):
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- esys_flutter_share (0.0.1):
|
||||
- Flutter
|
||||
- file_picker (0.0.1):
|
||||
- DKImagePickerController/PhotoGallery
|
||||
- Flutter
|
||||
- Flutter (1.0.0)
|
||||
- flutter_secure_storage (3.3.1):
|
||||
- Flutter
|
||||
|
@ -39,11 +73,15 @@ PODS:
|
|||
- path_provider (0.0.1):
|
||||
- Flutter
|
||||
- Reachability (3.2)
|
||||
- SDWebImage (5.9.1):
|
||||
- SDWebImage/Core (= 5.9.1)
|
||||
- SDWebImage/Core (5.9.1)
|
||||
- share (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences (0.0.1):
|
||||
- Flutter
|
||||
- SwiftProtobuf (1.12.0)
|
||||
- SwiftyGif (5.3.0)
|
||||
- url_launcher (0.0.1):
|
||||
- Flutter
|
||||
|
||||
|
@ -54,6 +92,7 @@ DEPENDENCIES:
|
|||
- cw_monero (from `.symlinks/plugins/cw_monero/ios`)
|
||||
- devicelocale (from `.symlinks/plugins/devicelocale/ios`)
|
||||
- esys_flutter_share (from `.symlinks/plugins/esys_flutter_share/ios`)
|
||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||
- local_auth (from `.symlinks/plugins/local_auth/ios`)
|
||||
|
@ -66,9 +105,13 @@ DEPENDENCIES:
|
|||
SPEC REPOS:
|
||||
https://github.com/CocoaPods/Specs.git:
|
||||
- CryptoSwift
|
||||
- DKImagePickerController
|
||||
- DKPhotoGallery
|
||||
- MTBBarcodeScanner
|
||||
- Reachability
|
||||
- SDWebImage
|
||||
- SwiftProtobuf
|
||||
- SwiftyGif
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
barcode_scan:
|
||||
|
@ -81,6 +124,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/devicelocale/ios"
|
||||
esys_flutter_share:
|
||||
:path: ".symlinks/plugins/esys_flutter_share/ios"
|
||||
file_picker:
|
||||
:path: ".symlinks/plugins/file_picker/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_secure_storage:
|
||||
|
@ -104,7 +149,10 @@ SPEC CHECKSUMS:
|
|||
CryptoSwift: 093499be1a94b0cae36e6c26b70870668cb56060
|
||||
cw_monero: 2e1f79929880cc2293b5bc1b25e28152e4d84649
|
||||
devicelocale: feebbe5e7a30adb8c4f83185de1b50ff19b44f00
|
||||
DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d
|
||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||
esys_flutter_share: 403498dab005b36ce1f8d7aff377e81f0621b0b4
|
||||
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
|
||||
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
|
||||
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
|
||||
local_auth: 25938960984c3a7f6e3253e3f8d962fdd16852bd
|
||||
|
@ -112,9 +160,11 @@ SPEC CHECKSUMS:
|
|||
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
|
||||
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
|
||||
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
|
||||
SDWebImage: a990c053fff71e388a10f3357edb0be17929c9c5
|
||||
share: 0b2c3e82132f5888bccca3351c504d0003b3b410
|
||||
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
|
||||
SwiftProtobuf: 4ef85479c18ca85b5482b343df9c319c62bda699
|
||||
SwiftyGif: e466e86c660d343357ab944a819a101c4127cb40
|
||||
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
|
||||
|
||||
PODFILE CHECKSUM: ba3d2157523e2f4dc333b987efdac6635da8125d
|
||||
|
|
301
lib/core/backup_service.dart
Normal file
301
lib/core/backup_service.dart
Normal file
|
@ -0,0 +1,301 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:cryptography/cryptography.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/core/key_service.dart';
|
||||
import 'package:cake_wallet/entities/encrypt.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/entities/secret_store_key.dart';
|
||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||
|
||||
class BackupService {
|
||||
BackupService(this._flutterSecureStorage, this._walletInfoSource,
|
||||
this._keyService, this._sharedPreferences)
|
||||
: _cipher = chacha20Poly1305Aead;
|
||||
|
||||
static const currentVersion = _v1;
|
||||
|
||||
static const _v1 = 1;
|
||||
|
||||
final Cipher _cipher;
|
||||
final FlutterSecureStorage _flutterSecureStorage;
|
||||
final SharedPreferences _sharedPreferences;
|
||||
final Box<WalletInfo> _walletInfoSource;
|
||||
final KeyService _keyService;
|
||||
|
||||
Future<void> importBackup(Uint8List data, String password,
|
||||
{String nonce = secrets.backupSalt}) async {
|
||||
final version = getVersion(data);
|
||||
final backupBytes = data.toList()..removeAt(0);
|
||||
final backupData = Uint8List.fromList(backupBytes);
|
||||
|
||||
switch (version) {
|
||||
case _v1:
|
||||
await _importBackupV1(backupData, password, nonce: nonce);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Uint8List> exportBackup(String password,
|
||||
{String nonce = secrets.backupSalt, int version = currentVersion}) async {
|
||||
switch (version) {
|
||||
case _v1:
|
||||
return await _exportBackupV1(password, nonce: nonce);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Uint8List> _exportBackupV1(String password,
|
||||
{String nonce = secrets.backupSalt}) async {
|
||||
final zipEncoder = ZipFileEncoder();
|
||||
final appDir = await getApplicationDocumentsDirectory();
|
||||
final now = DateTime.now();
|
||||
final tmpDir = Directory('${appDir.path}/~_BACKUP_TMP');
|
||||
final archivePath = '${tmpDir.path}/backup_${now.toString()}.zip';
|
||||
final fileEntities = appDir.listSync(recursive: false);
|
||||
final keychainDump = await _exportKeychainDump(password, nonce: nonce);
|
||||
final preferencesDump = await _exportPreferencesJSON();
|
||||
final preferencesDumpFile = File('${tmpDir.path}/~_preferences_dump_TMP');
|
||||
final keychainDumpFile = File('${tmpDir.path}/~_keychain_dump_TMP');
|
||||
|
||||
if (tmpDir.existsSync()) {
|
||||
tmpDir.deleteSync(recursive: true);
|
||||
}
|
||||
|
||||
tmpDir.createSync();
|
||||
zipEncoder.create(archivePath);
|
||||
|
||||
fileEntities.forEach((entity) {
|
||||
if (entity.path == archivePath || entity.path == tmpDir.path) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity.statSync().type == FileSystemEntityType.directory) {
|
||||
zipEncoder.addDirectory(Directory(entity.path));
|
||||
} else {
|
||||
zipEncoder.addFile(File(entity.path));
|
||||
}
|
||||
});
|
||||
await keychainDumpFile.writeAsBytes(keychainDump.toList());
|
||||
await preferencesDumpFile.writeAsString(preferencesDump);
|
||||
zipEncoder.addFile(preferencesDumpFile, '~_preferences_dump');
|
||||
zipEncoder.addFile(keychainDumpFile, '~_keychain_dump');
|
||||
zipEncoder.close();
|
||||
|
||||
final content = File(archivePath).readAsBytesSync();
|
||||
tmpDir.deleteSync(recursive: true);
|
||||
final encryptedData = await _encrypt(content, password, nonce);
|
||||
|
||||
return setVersion(encryptedData, currentVersion);
|
||||
}
|
||||
|
||||
Future<void> _importBackupV1(Uint8List data, String password,
|
||||
{@required String nonce}) async {
|
||||
final appDir = await getApplicationDocumentsDirectory();
|
||||
final decryptedData = await _decrypt(data, password, nonce);
|
||||
final zip = ZipDecoder().decodeBytes(decryptedData);
|
||||
|
||||
zip.files.forEach((file) {
|
||||
final filename = file.name;
|
||||
|
||||
if (file.isFile) {
|
||||
final content = file.content as List<int>;
|
||||
File('${appDir.path}/' + filename)
|
||||
..createSync(recursive: true)
|
||||
..writeAsBytesSync(content);
|
||||
} else {
|
||||
Directory('${appDir.path}/' + filename)..create(recursive: true);
|
||||
}
|
||||
});
|
||||
|
||||
await _importKeychainDump(password, nonce: nonce);
|
||||
await _importPreferencesDump();
|
||||
}
|
||||
|
||||
Future<void> _importPreferencesDump() async {
|
||||
final appDir = await getApplicationDocumentsDirectory();
|
||||
final preferencesFile = File('${appDir.path}/~_preferences_dump');
|
||||
const defaultSettingsMigrationVersionKey = PreferencesKey.currentDefaultSettingsMigrationVersion;
|
||||
|
||||
if (!preferencesFile.existsSync()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final data =
|
||||
json.decode(preferencesFile.readAsStringSync()) as Map<String, Object>;
|
||||
|
||||
await _sharedPreferences.setString(PreferencesKey.currentWalletName,
|
||||
data[PreferencesKey.currentWalletName] as String);
|
||||
await _sharedPreferences.setInt(PreferencesKey.currentNodeIdKey,
|
||||
data[PreferencesKey.currentNodeIdKey] as int);
|
||||
await _sharedPreferences.setInt(PreferencesKey.currentBalanceDisplayModeKey,
|
||||
data[PreferencesKey.currentBalanceDisplayModeKey] as int);
|
||||
await _sharedPreferences.setInt(PreferencesKey.currentWalletType,
|
||||
data[PreferencesKey.currentWalletType] as int);
|
||||
await _sharedPreferences.setString(PreferencesKey.currentFiatCurrencyKey,
|
||||
data[PreferencesKey.currentFiatCurrencyKey] as String);
|
||||
await _sharedPreferences.setBool(
|
||||
PreferencesKey.shouldSaveRecipientAddressKey,
|
||||
data[PreferencesKey.shouldSaveRecipientAddressKey] as bool);
|
||||
await _sharedPreferences.setInt(
|
||||
PreferencesKey.currentTransactionPriorityKey,
|
||||
data[PreferencesKey.currentTransactionPriorityKey] as int);
|
||||
await _sharedPreferences.setBool(
|
||||
PreferencesKey.allowBiometricalAuthenticationKey,
|
||||
data[PreferencesKey.allowBiometricalAuthenticationKey] as bool);
|
||||
await _sharedPreferences.setInt(
|
||||
PreferencesKey.currentBitcoinElectrumSererIdKey,
|
||||
data[PreferencesKey.currentBitcoinElectrumSererIdKey] as int);
|
||||
await _sharedPreferences.setInt(PreferencesKey.currentLanguageCode,
|
||||
data[PreferencesKey.currentLanguageCode] as int);
|
||||
await _sharedPreferences.setInt(PreferencesKey.displayActionListModeKey,
|
||||
data[PreferencesKey.displayActionListModeKey] as int);
|
||||
await _sharedPreferences.setInt(
|
||||
'current_theme', data['current_theme'] as int);
|
||||
await _sharedPreferences.setInt(defaultSettingsMigrationVersionKey,
|
||||
data[defaultSettingsMigrationVersionKey] as int);
|
||||
|
||||
await preferencesFile.delete();
|
||||
}
|
||||
|
||||
Future<void> _importKeychainDump(String password,
|
||||
{@required String nonce,
|
||||
String keychainSalt = secrets.backupKeychainSalt}) async {
|
||||
final appDir = await getApplicationDocumentsDirectory();
|
||||
final keychainDumpFile = File('${appDir.path}/~_keychain_dump');
|
||||
final decryptedKeychainDumpFileData = await _decrypt(
|
||||
keychainDumpFile.readAsBytesSync(), '$keychainSalt$password', nonce);
|
||||
final keychainJSON = json.decode(utf8.decode(decryptedKeychainDumpFileData))
|
||||
as Map<String, dynamic>;
|
||||
final keychainWalletsInfo = keychainJSON['wallets'] as List;
|
||||
final decodedPin = keychainJSON['pin'] as String;
|
||||
final pinCodeKey = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
|
||||
final backupPasswordKey =
|
||||
generateStoreKeyFor(key: SecretStoreKey.backupPassword);
|
||||
final backupPassword = keychainJSON[backupPasswordKey] as String;
|
||||
|
||||
await _flutterSecureStorage.write(
|
||||
key: backupPasswordKey, value: backupPassword);
|
||||
|
||||
keychainWalletsInfo.forEach((dynamic rawInfo) async {
|
||||
final info = rawInfo as Map<String, dynamic>;
|
||||
await importWalletKeychainInfo(info);
|
||||
});
|
||||
|
||||
await _flutterSecureStorage.write(
|
||||
key: pinCodeKey, value: encodedPinCode(pin: decodedPin));
|
||||
|
||||
keychainDumpFile.deleteSync();
|
||||
}
|
||||
|
||||
Future<void> importWalletKeychainInfo(Map<String, dynamic> info) async {
|
||||
final name = info['name'] as String;
|
||||
final password = info['password'] as String;
|
||||
|
||||
await _keyService.saveWalletPassword(walletName: name, password: password);
|
||||
}
|
||||
|
||||
Future<Uint8List> _exportKeychainDump(String password,
|
||||
{@required String nonce,
|
||||
String keychainSalt = secrets.backupKeychainSalt}) async {
|
||||
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
|
||||
final encodedPin = await _flutterSecureStorage.read(key: key);
|
||||
final decodedPin = decodedPinCode(pin: encodedPin);
|
||||
final wallets =
|
||||
await Future.wait(_walletInfoSource.values.map((walletInfo) async {
|
||||
return {
|
||||
'name': walletInfo.name,
|
||||
'type': walletInfo.type.toString(),
|
||||
'password':
|
||||
await _keyService.getWalletPassword(walletName: walletInfo.name)
|
||||
};
|
||||
}));
|
||||
final backupPasswordKey =
|
||||
generateStoreKeyFor(key: SecretStoreKey.backupPassword);
|
||||
final backupPassword =
|
||||
await _flutterSecureStorage.read(key: backupPasswordKey);
|
||||
final data = utf8.encode(json.encode({
|
||||
'pin': decodedPin,
|
||||
'wallets': wallets,
|
||||
backupPasswordKey: backupPassword
|
||||
}));
|
||||
final encrypted = await _encrypt(
|
||||
Uint8List.fromList(data), '$keychainSalt$password', nonce);
|
||||
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
Future<String> _exportPreferencesJSON() async {
|
||||
const defaultSettingsMigrationVersionKey =
|
||||
'current_default_settings_migration_version';
|
||||
|
||||
final preferences = <String, Object>{
|
||||
PreferencesKey.currentWalletName:
|
||||
_sharedPreferences.getString(PreferencesKey.currentWalletName),
|
||||
PreferencesKey.currentNodeIdKey:
|
||||
_sharedPreferences.getInt(PreferencesKey.currentNodeIdKey),
|
||||
PreferencesKey.currentBalanceDisplayModeKey: _sharedPreferences
|
||||
.getInt(PreferencesKey.currentBalanceDisplayModeKey),
|
||||
PreferencesKey.currentWalletType:
|
||||
_sharedPreferences.getInt(PreferencesKey.currentWalletType),
|
||||
PreferencesKey.currentFiatCurrencyKey:
|
||||
_sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey),
|
||||
PreferencesKey.shouldSaveRecipientAddressKey: _sharedPreferences
|
||||
.getBool(PreferencesKey.shouldSaveRecipientAddressKey),
|
||||
PreferencesKey.isDarkThemeLegacy:
|
||||
_sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy),
|
||||
PreferencesKey.currentPinLength:
|
||||
_sharedPreferences.getInt(PreferencesKey.currentPinLength),
|
||||
PreferencesKey.currentTransactionPriorityKey: _sharedPreferences
|
||||
.getInt(PreferencesKey.currentTransactionPriorityKey),
|
||||
PreferencesKey.allowBiometricalAuthenticationKey: _sharedPreferences
|
||||
.getBool(PreferencesKey.allowBiometricalAuthenticationKey),
|
||||
PreferencesKey.currentBitcoinElectrumSererIdKey: _sharedPreferences
|
||||
.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey),
|
||||
PreferencesKey.currentLanguageCode:
|
||||
_sharedPreferences.getString(PreferencesKey.currentLanguageCode),
|
||||
PreferencesKey.displayActionListModeKey:
|
||||
_sharedPreferences.getInt(PreferencesKey.displayActionListModeKey),
|
||||
PreferencesKey.currentTheme:
|
||||
_sharedPreferences.getInt(PreferencesKey.currentTheme),
|
||||
defaultSettingsMigrationVersionKey:
|
||||
_sharedPreferences.getInt(defaultSettingsMigrationVersionKey)
|
||||
};
|
||||
|
||||
return json.encode(preferences);
|
||||
}
|
||||
|
||||
int getVersion(Uint8List data) => data.toList().first;
|
||||
|
||||
Uint8List setVersion(Uint8List data, int version) {
|
||||
final bytes = data.toList()..insert(0, version);
|
||||
return Uint8List.fromList(bytes);
|
||||
}
|
||||
|
||||
Future<Uint8List> _encrypt(
|
||||
Uint8List data, String secretKeySource, String nonceBase64) async {
|
||||
final secretKeyHash = await sha256.hash(utf8.encode(secretKeySource));
|
||||
final secretKey = SecretKey(secretKeyHash.bytes);
|
||||
final nonce = Nonce(base64.decode(nonceBase64));
|
||||
return await _cipher.encrypt(data, secretKey: secretKey, nonce: nonce);
|
||||
}
|
||||
|
||||
Future<Uint8List> _decrypt(
|
||||
Uint8List data, String secretKeySource, String nonceBase64) async {
|
||||
final secretKeyHash = await sha256.hash(utf8.encode(secretKeySource));
|
||||
final secretKey = SecretKey(secretKeyHash.bytes);
|
||||
final nonce = Nonce(base64.decode(nonceBase64));
|
||||
return await _cipher.decrypt(data, secretKey: secretKey, nonce: nonce);
|
||||
}
|
||||
}
|
111
lib/di.dart
111
lib/di.dart
|
@ -1,4 +1,5 @@
|
|||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
|
||||
import 'package:cake_wallet/core/backup_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_service.dart';
|
||||
import 'package:cake_wallet/entities/biometric_auth.dart';
|
||||
import 'package:cake_wallet/entities/contact_record.dart';
|
||||
|
@ -10,6 +11,8 @@ import 'package:cake_wallet/entities/contact.dart';
|
|||
import 'package:cake_wallet/entities/node.dart';
|
||||
import 'package:cake_wallet/exchange/trade.dart';
|
||||
import 'package:cake_wallet/reactions/on_authentication_state_change.dart';
|
||||
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
|
||||
import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
|
||||
|
||||
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/contact/contact_page.dart';
|
||||
|
@ -21,6 +24,8 @@ import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart';
|
|||
import 'package:cake_wallet/src/screens/nodes/nodes_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/rescan/rescan_page.dart';
|
||||
import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
|
||||
import 'package:cake_wallet/src/screens/restore/restore_options_page.dart';
|
||||
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
|
||||
import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
|
||||
import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
|
||||
|
@ -34,6 +39,7 @@ import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
|
|||
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
|
||||
import 'package:cake_wallet/store/node_list_store.dart';
|
||||
import 'package:cake_wallet/store/secret_store.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/core/key_service.dart';
|
||||
|
@ -48,13 +54,16 @@ import 'package:cake_wallet/src/screens/send/send_page.dart';
|
|||
import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
|
||||
import 'package:cake_wallet/store/wallet_list_store.dart';
|
||||
import 'package:cake_wallet/view_model/backup_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/rescan_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/restore_from_backup_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/trade_details_view_model.dart';
|
||||
|
@ -96,6 +105,15 @@ import 'package:cake_wallet/exchange/exchange_template.dart';
|
|||
|
||||
final getIt = GetIt.instance;
|
||||
|
||||
var _isSetupFinished = false;
|
||||
Box<WalletInfo> _walletInfoSource;
|
||||
Box<Node> _nodeSource;
|
||||
Box<Contact> _contactSource;
|
||||
Box<Trade> _tradesSource;
|
||||
Box<Template> _templates;
|
||||
Box<ExchangeTemplate> _exchangeTemplates;
|
||||
Box<TransactionDescription> _transactionDescriptionBox;
|
||||
|
||||
Future setup(
|
||||
{Box<WalletInfo> walletInfoSource,
|
||||
Box<Node> nodeSource,
|
||||
|
@ -104,12 +122,26 @@ Future setup(
|
|||
Box<Template> templates,
|
||||
Box<ExchangeTemplate> exchangeTemplates,
|
||||
Box<TransactionDescription> transactionDescriptionBox}) async {
|
||||
getIt.registerSingletonAsync<SharedPreferences>(
|
||||
() => SharedPreferences.getInstance());
|
||||
_walletInfoSource = walletInfoSource;
|
||||
_nodeSource = nodeSource;
|
||||
_contactSource = contactSource;
|
||||
_tradesSource = tradesSource;
|
||||
_templates = templates;
|
||||
_exchangeTemplates = exchangeTemplates;
|
||||
_transactionDescriptionBox = transactionDescriptionBox;
|
||||
|
||||
final settingsStore = await SettingsStoreBase.load(nodeSource: nodeSource);
|
||||
if (!_isSetupFinished) {
|
||||
getIt.registerSingletonAsync<SharedPreferences>(
|
||||
() => SharedPreferences.getInstance());
|
||||
}
|
||||
|
||||
getIt.registerSingleton<Box<Node>>(nodeSource);
|
||||
final settingsStore = await SettingsStoreBase.load(nodeSource: _nodeSource);
|
||||
|
||||
if (_isSetupFinished) {
|
||||
return;
|
||||
}
|
||||
|
||||
getIt.registerFactory<Box<Node>>(() => _nodeSource);
|
||||
|
||||
getIt.registerSingleton<FlutterSecureStorage>(FlutterSecureStorage());
|
||||
getIt.registerSingleton(AuthenticationStore());
|
||||
|
@ -122,14 +154,19 @@ Future setup(
|
|||
settingsStore: getIt.get<SettingsStore>(),
|
||||
nodeListStore: getIt.get<NodeListStore>()));
|
||||
getIt.registerSingleton<TradesStore>(TradesStore(
|
||||
tradesSource: tradesSource, settingsStore: getIt.get<SettingsStore>()));
|
||||
tradesSource: _tradesSource, settingsStore: getIt.get<SettingsStore>()));
|
||||
getIt.registerSingleton<TradeFilterStore>(TradeFilterStore());
|
||||
getIt.registerSingleton<TransactionFilterStore>(TransactionFilterStore());
|
||||
getIt.registerSingleton<FiatConversionStore>(FiatConversionStore());
|
||||
getIt.registerSingleton<SendTemplateStore>(
|
||||
SendTemplateStore(templateSource: templates));
|
||||
SendTemplateStore(templateSource: _templates));
|
||||
getIt.registerSingleton<ExchangeTemplateStore>(
|
||||
ExchangeTemplateStore(templateSource: exchangeTemplates));
|
||||
ExchangeTemplateStore(templateSource: _exchangeTemplates));
|
||||
|
||||
final secretStore =
|
||||
await SecretStoreBase.load(getIt.get<FlutterSecureStorage>());
|
||||
|
||||
getIt.registerSingleton<SecretStore>(secretStore);
|
||||
|
||||
getIt.registerFactory<KeyService>(
|
||||
() => KeyService(getIt.get<FlutterSecureStorage>()));
|
||||
|
@ -143,7 +180,7 @@ Future setup(
|
|||
|
||||
getIt.registerFactoryParam<WalletNewVM, WalletType, void>((type, _) =>
|
||||
WalletNewVM(getIt.get<AppStore>(),
|
||||
getIt.get<WalletCreationService>(param1: type), walletInfoSource,
|
||||
getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
|
||||
type: type));
|
||||
|
||||
getIt
|
||||
|
@ -153,7 +190,7 @@ Future setup(
|
|||
final mnemonic = args[2] as String;
|
||||
|
||||
return WalletRestorationFromSeedVM(getIt.get<AppStore>(),
|
||||
getIt.get<WalletCreationService>(param1: type), walletInfoSource,
|
||||
getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
|
||||
type: type, language: language, seed: mnemonic);
|
||||
});
|
||||
|
||||
|
@ -163,7 +200,7 @@ Future setup(
|
|||
final language = args[1] as String;
|
||||
|
||||
return WalletRestorationFromKeysVM(getIt.get<AppStore>(),
|
||||
getIt.get<WalletCreationService>(param1: type), walletInfoSource,
|
||||
getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
|
||||
type: type, language: language);
|
||||
});
|
||||
|
||||
|
@ -248,7 +285,7 @@ Future setup(
|
|||
getIt.get<AppStore>().settingsStore,
|
||||
getIt.get<SendTemplateStore>(),
|
||||
getIt.get<FiatConversionStore>(),
|
||||
transactionDescriptionBox));
|
||||
_transactionDescriptionBox));
|
||||
|
||||
getIt.registerFactory(
|
||||
() => SendPage(sendViewModel: getIt.get<SendViewModel>()));
|
||||
|
@ -257,7 +294,7 @@ Future setup(
|
|||
() => SendTemplatePage(sendViewModel: getIt.get<SendViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => WalletListViewModel(
|
||||
walletInfoSource,
|
||||
_walletInfoSource,
|
||||
getIt.get<AppStore>(),
|
||||
getIt.get<KeyService>(),
|
||||
getIt.get<WalletNewVM>(param1: WalletType.monero)));
|
||||
|
@ -328,10 +365,10 @@ Future setup(
|
|||
|
||||
getIt.registerFactoryParam<ContactViewModel, ContactRecord, void>(
|
||||
(ContactRecord contact, _) =>
|
||||
ContactViewModel(contactSource, contact: contact));
|
||||
ContactViewModel(_contactSource, contact: contact));
|
||||
|
||||
getIt.registerFactory(
|
||||
() => ContactListViewModel(contactSource, walletInfoSource));
|
||||
() => ContactListViewModel(_contactSource, _walletInfoSource));
|
||||
|
||||
getIt.registerFactoryParam<ContactListPage, bool, void>(
|
||||
(bool isEditable, _) => ContactListPage(getIt.get<ContactListViewModel>(),
|
||||
|
@ -344,27 +381,27 @@ Future setup(
|
|||
getIt.registerFactory(() {
|
||||
final appStore = getIt.get<AppStore>();
|
||||
return NodeListViewModel(
|
||||
nodeSource, appStore.wallet, appStore.settingsStore);
|
||||
_nodeSource, appStore.wallet, appStore.settingsStore);
|
||||
});
|
||||
|
||||
getIt.registerFactory(() => NodeListPage(getIt.get<NodeListViewModel>()));
|
||||
|
||||
getIt.registerFactory(() =>
|
||||
NodeCreateOrEditViewModel(nodeSource, getIt.get<AppStore>().wallet));
|
||||
NodeCreateOrEditViewModel(_nodeSource, getIt.get<AppStore>().wallet));
|
||||
|
||||
getIt.registerFactory(
|
||||
() => NodeCreateOrEditPage(getIt.get<NodeCreateOrEditViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => ExchangeViewModel(
|
||||
getIt.get<AppStore>().wallet,
|
||||
tradesSource,
|
||||
_tradesSource,
|
||||
getIt.get<ExchangeTemplateStore>(),
|
||||
getIt.get<TradesStore>(),
|
||||
getIt.get<AppStore>().settingsStore));
|
||||
|
||||
getIt.registerFactory(() => ExchangeTradeViewModel(
|
||||
wallet: getIt.get<AppStore>().wallet,
|
||||
trades: tradesSource,
|
||||
trades: _tradesSource,
|
||||
tradesStore: getIt.get<TradesStore>(),
|
||||
sendViewModel: getIt.get<SendViewModel>()));
|
||||
|
||||
|
@ -379,9 +416,9 @@ Future setup(
|
|||
getIt.registerFactory(
|
||||
() => ExchangeTemplatePage(getIt.get<ExchangeViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => MoneroWalletService(walletInfoSource));
|
||||
getIt.registerFactory(() => MoneroWalletService(_walletInfoSource));
|
||||
|
||||
getIt.registerFactory(() => BitcoinWalletService(walletInfoSource));
|
||||
getIt.registerFactory(() => BitcoinWalletService(_walletInfoSource));
|
||||
|
||||
getIt.registerFactoryParam<WalletService, WalletType, void>(
|
||||
(WalletType param1, __) {
|
||||
|
@ -414,7 +451,7 @@ Future setup(
|
|||
|
||||
getIt.registerFactoryParam<WalletRestoreViewModel, WalletType, void>(
|
||||
(type, _) => WalletRestoreViewModel(getIt.get<AppStore>(),
|
||||
getIt.get<WalletCreationService>(param1: type), walletInfoSource,
|
||||
getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
|
||||
type: type));
|
||||
|
||||
getIt.registerFactoryParam<WalletRestorePage, WalletType, void>((type, _) =>
|
||||
|
@ -424,7 +461,7 @@ Future setup(
|
|||
.registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>(
|
||||
(TransactionInfo transactionInfo, _) => TransactionDetailsViewModel(
|
||||
transactionInfo: transactionInfo,
|
||||
transactionDescriptionBox: transactionDescriptionBox,
|
||||
transactionDescriptionBox: _transactionDescriptionBox,
|
||||
settingsStore: getIt.get<SettingsStore>()));
|
||||
|
||||
getIt.registerFactoryParam<TransactionDetailsPage, TransactionInfo, void>(
|
||||
|
@ -441,8 +478,36 @@ Future setup(
|
|||
(WalletType type, _) => PreSeedPage(type));
|
||||
|
||||
getIt.registerFactoryParam<TradeDetailsViewModel, Trade, void>((trade, _) =>
|
||||
TradeDetailsViewModel(tradeForDetails: trade, trades: tradesSource));
|
||||
TradeDetailsViewModel(tradeForDetails: trade, trades: _tradesSource));
|
||||
|
||||
getIt.registerFactory(() => BackupService(
|
||||
getIt.get<FlutterSecureStorage>(),
|
||||
_walletInfoSource,
|
||||
getIt.get<KeyService>(),
|
||||
getIt.get<SharedPreferences>()));
|
||||
|
||||
getIt.registerFactory(() => BackupViewModel(getIt.get<FlutterSecureStorage>(),
|
||||
getIt.get<SecretStore>(), getIt.get<BackupService>()));
|
||||
|
||||
getIt.registerFactory(() => BackupPage(getIt.get<BackupViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => EditBackupPasswordViewModel(
|
||||
getIt.get<FlutterSecureStorage>(), getIt.get<SecretStore>())
|
||||
..init());
|
||||
|
||||
getIt.registerFactory(
|
||||
() => EditBackupPasswordPage(getIt.get<EditBackupPasswordViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => RestoreOptionsPage());
|
||||
|
||||
getIt.registerFactory(
|
||||
() => RestoreFromBackupViewModel(getIt.get<BackupService>()));
|
||||
|
||||
getIt.registerFactory(
|
||||
() => RestoreFromBackupPage(getIt.get<RestoreFromBackupViewModel>()));
|
||||
|
||||
getIt.registerFactoryParam<TradeDetailsPage, Trade, void>((Trade trade, _) =>
|
||||
TradeDetailsPage(getIt.get<TradeDetailsViewModel>(param1: trade)));
|
||||
|
||||
_isSetupFinished = true;
|
||||
}
|
||||
|
|
|
@ -5,11 +5,12 @@ import 'package:cake_wallet/utils/mobx.dart';
|
|||
|
||||
part 'contact.g.dart';
|
||||
|
||||
@HiveType(typeId: 0)
|
||||
@HiveType(typeId: Contact.typeId)
|
||||
class Contact extends HiveObject with Keyable {
|
||||
Contact({@required this.name, @required this.address, CryptoCurrency type})
|
||||
: raw = type?.raw;
|
||||
|
||||
static const typeId = 0;
|
||||
static const boxName = 'Contacts';
|
||||
|
||||
@HiveField(0)
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import 'dart:io' show File, Platform;
|
||||
import 'package:cake_wallet/core/generate_wallet_password.dart';
|
||||
import 'package:cake_wallet/core/key_service.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/pathForWallet.dart';
|
||||
import 'package:cake_wallet/entities/secret_store_key.dart';
|
||||
import 'package:cake_wallet/monero/monero_wallet_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
|
@ -17,10 +20,12 @@ import 'package:cake_wallet/entities/contact.dart';
|
|||
import 'package:cake_wallet/entities/fs_migration.dart';
|
||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
||||
import 'package:cake_wallet/exchange/trade.dart';
|
||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
||||
|
||||
Future defaultSettingsMigration(
|
||||
{@required int version,
|
||||
@required SharedPreferences sharedPreferences,
|
||||
@required FlutterSecureStorage secureStorage,
|
||||
@required Box<Node> nodes,
|
||||
@required Box<WalletInfo> walletInfoSource,
|
||||
@required Box<Trade> tradeSource,
|
||||
|
@ -29,9 +34,9 @@ Future defaultSettingsMigration(
|
|||
await ios_migrate_v1(walletInfoSource, tradeSource, contactSource);
|
||||
}
|
||||
|
||||
final currentVersion =
|
||||
sharedPreferences.getInt('current_default_settings_migration_version') ??
|
||||
0;
|
||||
final currentVersion = sharedPreferences
|
||||
.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) ??
|
||||
0;
|
||||
if (currentVersion >= version) {
|
||||
return;
|
||||
}
|
||||
|
@ -85,6 +90,10 @@ Future defaultSettingsMigration(
|
|||
await updateDisplayModes(sharedPreferences);
|
||||
break;
|
||||
|
||||
case 9:
|
||||
await generateBackupPassword(secureStorage);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -230,5 +239,17 @@ Future<void> updateDisplayModes(SharedPreferences sharedPreferences) async {
|
|||
final currentBalanceDisplayMode =
|
||||
sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey);
|
||||
final balanceDisplayMode = currentBalanceDisplayMode < 2 ? 3 : 2;
|
||||
await sharedPreferences.setInt(PreferencesKey.currentBalanceDisplayModeKey, balanceDisplayMode);
|
||||
await sharedPreferences.setInt(
|
||||
PreferencesKey.currentBalanceDisplayModeKey, balanceDisplayMode);
|
||||
}
|
||||
|
||||
Future<void> generateBackupPassword(FlutterSecureStorage secureStorage) async {
|
||||
final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
|
||||
|
||||
if ((await secureStorage.read(key: key))?.isNotEmpty ?? false) {
|
||||
return;
|
||||
}
|
||||
|
||||
final password = encrypt.Key.fromSecureRandom(32).base16;
|
||||
await secureStorage.write(key: key, value: password);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import 'package:cake_wallet/entities/digest_request.dart';
|
|||
|
||||
part 'node.g.dart';
|
||||
|
||||
@HiveType(typeId: 1)
|
||||
@HiveType(typeId: Node.typeId)
|
||||
class Node extends HiveObject with Keyable {
|
||||
Node(
|
||||
{@required this.uri,
|
||||
|
@ -26,6 +26,7 @@ class Node extends HiveObject with Keyable {
|
|||
typeRaw = map['typeRaw'] as int,
|
||||
useSSL = map['useSSL'] as bool;
|
||||
|
||||
static const typeId = 1;
|
||||
static const boxName = 'Nodes';
|
||||
|
||||
@HiveField(0)
|
||||
|
|
|
@ -14,4 +14,5 @@ class PreferencesKey {
|
|||
static const displayActionListModeKey = 'display_list_mode';
|
||||
static const currentPinLength = 'current_pin_length';
|
||||
static const currentLanguageCode = 'language_code';
|
||||
static const currentDefaultSettingsMigrationVersion = 'current_default_settings_migration_version';
|
||||
}
|
|
@ -1,9 +1,13 @@
|
|||
enum SecretStoreKey { moneroWalletPassword, pinCodePassword }
|
||||
enum SecretStoreKey { moneroWalletPassword, pinCodePassword, backupPassword }
|
||||
|
||||
const moneroWalletPassword = "MONERO_WALLET_PASSWORD";
|
||||
const pinCodePassword = "PIN_CODE_PASSWORD";
|
||||
const backupPassword = "BACKUP_CODE_PASSWORD";
|
||||
|
||||
String generateStoreKeyFor({SecretStoreKey key, String walletName = "",}) {
|
||||
String generateStoreKeyFor({
|
||||
SecretStoreKey key,
|
||||
String walletName = "",
|
||||
}) {
|
||||
var _key = "";
|
||||
|
||||
switch (key) {
|
||||
|
@ -19,6 +23,12 @@ String generateStoreKeyFor({SecretStoreKey key, String walletName = "",}) {
|
|||
}
|
||||
break;
|
||||
|
||||
case SecretStoreKey.backupPassword:
|
||||
{
|
||||
_key = backupPassword;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,11 @@ import 'package:hive/hive.dart';
|
|||
|
||||
part 'template.g.dart';
|
||||
|
||||
@HiveType(typeId: 6)
|
||||
@HiveType(typeId: Template.typeId)
|
||||
class Template extends HiveObject {
|
||||
Template({this.name, this.address, this.cryptoCurrency, this.amount});
|
||||
|
||||
static const typeId = 6;
|
||||
static const boxName = 'Template';
|
||||
|
||||
@HiveField(0)
|
||||
|
|
|
@ -2,10 +2,11 @@ import 'package:hive/hive.dart';
|
|||
|
||||
part 'transaction_description.g.dart';
|
||||
|
||||
@HiveType(typeId: 2)
|
||||
@HiveType(typeId: TransactionDescription.typeId)
|
||||
class TransactionDescription extends HiveObject {
|
||||
TransactionDescription({this.id, this.recipientAddress, this.transactionNote});
|
||||
|
||||
static const typeId = 2;
|
||||
static const boxName = 'TransactionDescriptions';
|
||||
static const boxKey = 'transactionDescriptionsBoxKey';
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'package:cake_wallet/entities/wallet_type.dart';
|
|||
|
||||
part 'wallet_info.g.dart';
|
||||
|
||||
@HiveType(typeId: 4)
|
||||
@HiveType(typeId: WalletInfo.typeId)
|
||||
class WalletInfo extends HiveObject {
|
||||
WalletInfo(this.id, this.name, this.type, this.isRecovery, this.restoreHeight,
|
||||
this.timestamp, this.dirPath, this.path, this.address);
|
||||
|
@ -23,6 +23,7 @@ class WalletInfo extends HiveObject {
|
|||
date.millisecondsSinceEpoch ?? 0, dirPath, path, address);
|
||||
}
|
||||
|
||||
static const typeId = 4;
|
||||
static const boxName = 'WalletInfo';
|
||||
|
||||
@HiveField(0)
|
||||
|
|
|
@ -4,8 +4,9 @@ import 'package:hive/hive.dart';
|
|||
part 'wallet_type.g.dart';
|
||||
|
||||
const walletTypes = [WalletType.monero, WalletType.bitcoin];
|
||||
const walletTypeTypeId = 5;
|
||||
|
||||
@HiveType(typeId: 5)
|
||||
@HiveType(typeId: walletTypeTypeId)
|
||||
enum WalletType {
|
||||
@HiveField(0)
|
||||
monero,
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'package:hive/hive.dart';
|
|||
|
||||
part 'exchange_template.g.dart';
|
||||
|
||||
@HiveType(typeId: 7)
|
||||
@HiveType(typeId: ExchangeTemplate.typeId)
|
||||
class ExchangeTemplate extends HiveObject {
|
||||
ExchangeTemplate({
|
||||
this.amount,
|
||||
|
@ -13,6 +13,7 @@ class ExchangeTemplate extends HiveObject {
|
|||
this.receiveAddress
|
||||
});
|
||||
|
||||
static const typeId = 7;
|
||||
static const boxName = 'ExchangeTemplate';
|
||||
|
||||
@HiveField(0)
|
||||
|
|
|
@ -6,7 +6,7 @@ import 'package:cake_wallet/entities/format_amount.dart';
|
|||
|
||||
part 'trade.g.dart';
|
||||
|
||||
@HiveType(typeId: 3)
|
||||
@HiveType(typeId: Trade.typeId)
|
||||
class Trade extends HiveObject {
|
||||
Trade(
|
||||
{this.id,
|
||||
|
@ -27,6 +27,7 @@ class Trade extends HiveObject {
|
|||
toRaw = to?.raw,
|
||||
stateRaw = state?.raw;
|
||||
|
||||
static const typeId = 3;
|
||||
static const boxName = 'Trades';
|
||||
static const boxKey = 'tradesBoxKey';
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
@ -10,6 +8,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
|||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cw_monero/wallet.dart' as monero_wallet;
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/router.dart' as Router;
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
@ -30,20 +29,46 @@ import 'package:cake_wallet/src/screens/root/root.dart';
|
|||
|
||||
final navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
void main() async {
|
||||
Future<void> main() async {
|
||||
try {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
final appDir = await getApplicationDocumentsDirectory();
|
||||
await Hive.close();
|
||||
Hive.init(appDir.path);
|
||||
Hive.registerAdapter(ContactAdapter());
|
||||
Hive.registerAdapter(NodeAdapter());
|
||||
Hive.registerAdapter(TransactionDescriptionAdapter());
|
||||
Hive.registerAdapter(TradeAdapter());
|
||||
Hive.registerAdapter(WalletInfoAdapter());
|
||||
Hive.registerAdapter(WalletTypeAdapter());
|
||||
Hive.registerAdapter(TemplateAdapter());
|
||||
Hive.registerAdapter(ExchangeTemplateAdapter());
|
||||
|
||||
if (!Hive.isAdapterRegistered(Contact.typeId)) {
|
||||
Hive.registerAdapter(ContactAdapter());
|
||||
}
|
||||
|
||||
if (!Hive.isAdapterRegistered(Node.typeId)) {
|
||||
Hive.registerAdapter(NodeAdapter());
|
||||
}
|
||||
|
||||
if (!Hive.isAdapterRegistered(TransactionDescription.typeId)) {
|
||||
Hive.registerAdapter(TransactionDescriptionAdapter());
|
||||
}
|
||||
|
||||
if (!Hive.isAdapterRegistered(Trade.typeId)) {
|
||||
Hive.registerAdapter(TradeAdapter());
|
||||
}
|
||||
|
||||
if (!Hive.isAdapterRegistered(WalletInfo.typeId)) {
|
||||
Hive.registerAdapter(WalletInfoAdapter());
|
||||
}
|
||||
|
||||
if (!Hive.isAdapterRegistered(walletTypeTypeId)) {
|
||||
Hive.registerAdapter(WalletTypeAdapter());
|
||||
}
|
||||
|
||||
if (!Hive.isAdapterRegistered(Template.typeId)) {
|
||||
Hive.registerAdapter(TemplateAdapter());
|
||||
}
|
||||
|
||||
if (!Hive.isAdapterRegistered(ExchangeTemplate.typeId)) {
|
||||
Hive.registerAdapter(ExchangeTemplateAdapter());
|
||||
}
|
||||
|
||||
final secureStorage = FlutterSecureStorage();
|
||||
final transactionDescriptionsBoxKey = await getEncryptionKey(
|
||||
secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
|
||||
|
@ -55,11 +80,11 @@ void main() async {
|
|||
TransactionDescription.boxName,
|
||||
encryptionKey: transactionDescriptionsBoxKey);
|
||||
final trades =
|
||||
await Hive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey);
|
||||
await Hive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey);
|
||||
final walletInfoSource = await Hive.openBox<WalletInfo>(WalletInfo.boxName);
|
||||
final templates = await Hive.openBox<Template>(Template.boxName);
|
||||
final exchangeTemplates =
|
||||
await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
|
||||
await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
|
||||
await initialSetup(
|
||||
sharedPreferences: await SharedPreferences.getInstance(),
|
||||
nodes: nodes,
|
||||
|
@ -70,7 +95,8 @@ void main() async {
|
|||
templates: templates,
|
||||
exchangeTemplates: exchangeTemplates,
|
||||
transactionDescriptions: transactionDescriptions,
|
||||
initialMigrationVersion: 5);
|
||||
secureStorage: secureStorage,
|
||||
initialMigrationVersion: 9);
|
||||
runApp(App());
|
||||
} catch (e) {
|
||||
runApp(MaterialApp(
|
||||
|
@ -78,7 +104,7 @@ void main() async {
|
|||
home: Scaffold(
|
||||
body: Container(
|
||||
margin:
|
||||
EdgeInsets.only(top: 50, left: 20, right: 20, bottom: 20),
|
||||
EdgeInsets.only(top: 50, left: 20, right: 20, bottom: 20),
|
||||
child: Text(
|
||||
'Error:\n${e.toString()}',
|
||||
style: TextStyle(fontSize: 22),
|
||||
|
@ -86,17 +112,20 @@ void main() async {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> initialSetup({@required SharedPreferences sharedPreferences,
|
||||
@required Box<Node> nodes,
|
||||
@required Box<WalletInfo> walletInfoSource,
|
||||
@required Box<Contact> contactSource,
|
||||
@required Box<Trade> tradesSource,
|
||||
// @required FiatConvertationService fiatConvertationService,
|
||||
@required Box<Template> templates,
|
||||
@required Box<ExchangeTemplate> exchangeTemplates,
|
||||
@required Box<TransactionDescription> transactionDescriptions,
|
||||
int initialMigrationVersion = 6}) async {
|
||||
Future<void> initialSetup(
|
||||
{@required SharedPreferences sharedPreferences,
|
||||
@required Box<Node> nodes,
|
||||
@required Box<WalletInfo> walletInfoSource,
|
||||
@required Box<Contact> contactSource,
|
||||
@required Box<Trade> tradesSource,
|
||||
// @required FiatConvertationService fiatConvertationService,
|
||||
@required Box<Template> templates,
|
||||
@required Box<ExchangeTemplate> exchangeTemplates,
|
||||
@required Box<TransactionDescription> transactionDescriptions,
|
||||
FlutterSecureStorage secureStorage,
|
||||
int initialMigrationVersion = 9}) async {
|
||||
await defaultSettingsMigration(
|
||||
secureStorage: secureStorage,
|
||||
version: initialMigrationVersion,
|
||||
sharedPreferences: sharedPreferences,
|
||||
walletInfoSource: walletInfoSource,
|
||||
|
@ -111,7 +140,7 @@ Future<void> initialSetup({@required SharedPreferences sharedPreferences,
|
|||
templates: templates,
|
||||
exchangeTemplates: exchangeTemplates,
|
||||
transactionDescriptionBox: transactionDescriptions);
|
||||
bootstrap(navigatorKey);
|
||||
await bootstrap(navigatorKey);
|
||||
monero_wallet.onStartup();
|
||||
}
|
||||
|
||||
|
@ -123,16 +152,14 @@ class App extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final settingsStore = getIt
|
||||
.get<AppStore>()
|
||||
.settingsStore;
|
||||
final statusBarColor = Colors.transparent;
|
||||
final authenticationStore = getIt.get<AuthenticationStore>();
|
||||
final initialRoute = authenticationStore.state == AuthenticationState.denied
|
||||
? Routes.disclaimer
|
||||
: Routes.login;
|
||||
|
||||
return Observer(builder: (BuildContext context) {
|
||||
final settingsStore = getIt.get<AppStore>().settingsStore;
|
||||
final statusBarColor = Colors.transparent;
|
||||
final authenticationStore = getIt.get<AuthenticationStore>();
|
||||
final initialRoute =
|
||||
authenticationStore.state == AuthenticationState.denied
|
||||
? Routes.disclaimer
|
||||
: Routes.login;
|
||||
final currentTheme = settingsStore.currentTheme;
|
||||
final statusBarBrightness = currentTheme.type == ThemeType.dark
|
||||
? Brightness.light
|
||||
|
|
|
@ -19,14 +19,12 @@ Future<void> bootstrap(GlobalKey<NavigatorState> navigatorKey) async {
|
|||
final settingsStore = getIt.get<SettingsStore>();
|
||||
final fiatConversionStore = getIt.get<FiatConversionStore>();
|
||||
|
||||
if (authenticationStore.state == AuthenticationState.uninitialized) {
|
||||
final currentWalletName = getIt
|
||||
.get<SharedPreferences>()
|
||||
.getString(PreferencesKey.currentWalletName);
|
||||
authenticationStore.state = currentWalletName == null
|
||||
? AuthenticationState.denied
|
||||
: AuthenticationState.installed;
|
||||
}
|
||||
final currentWalletName = getIt
|
||||
.get<SharedPreferences>()
|
||||
.getString(PreferencesKey.currentWalletName);
|
||||
authenticationStore.state = currentWalletName == null
|
||||
? AuthenticationState.denied
|
||||
: AuthenticationState.installed;
|
||||
|
||||
startAuthenticationStateChange(authenticationStore, navigatorKey);
|
||||
startCurrentWalletChangeReaction(
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import 'package:cake_wallet/entities/contact_record.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
|
||||
import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
|
||||
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
|
||||
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
|
||||
import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
|
@ -103,9 +106,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
param2: false));
|
||||
|
||||
case Routes.restoreOptions:
|
||||
final type = settings.arguments as WalletType;
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => RestoreOptionsPage(type: type));
|
||||
builder: (_) => getIt.get<RestoreOptionsPage>());
|
||||
|
||||
case Routes.restoreWalletOptions:
|
||||
final type = WalletType.monero; //settings.arguments as WalletType;
|
||||
|
@ -317,6 +319,18 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
builder: (_) =>
|
||||
getIt.get<PreSeedPage>(param1: settings.arguments as WalletType));
|
||||
|
||||
case Routes.backup:
|
||||
return CupertinoPageRoute<void>(
|
||||
fullscreenDialog: true, builder: (_) => getIt.get<BackupPage>());
|
||||
|
||||
case Routes.editBackupPassword:
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => getIt.get<EditBackupPasswordPage>());
|
||||
|
||||
case Routes.restoreFromBackup:
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => getIt.get<RestoreFromBackupPage>());
|
||||
|
||||
default:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => Scaffold(
|
||||
|
|
|
@ -48,4 +48,7 @@ class Routes {
|
|||
static const restoreWalletType = '/restore_wallet_type';
|
||||
static const restoreWallet = '/restore_wallet';
|
||||
static const preSeed = '/pre_seed';
|
||||
static const backup = '/backup';
|
||||
static const editBackupPassword = '/edit_backup_passowrd';
|
||||
static const restoreFromBackup = '/restore_from_backup';
|
||||
}
|
107
lib/src/screens/backup/backup_page.dart
Normal file
107
lib/src/screens/backup/backup_page.dart
Normal file
|
@ -0,0 +1,107 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:esys_flutter_share/esys_flutter_share.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/trail_button.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/backup_view_model.dart';
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
|
||||
class BackupPage extends BasePage {
|
||||
BackupPage(this.backupViewModelBase);
|
||||
|
||||
final BackupViewModelBase backupViewModelBase;
|
||||
|
||||
@override
|
||||
String get title => 'Backup';
|
||||
|
||||
@override
|
||||
Widget trailing(BuildContext context) => TrailButton(
|
||||
caption: S.of(context).edit,
|
||||
onPressed: () =>
|
||||
Navigator.of(context).pushNamed(Routes.editBackupPassword));
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Center(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 20, right: 20),
|
||||
height: 300,
|
||||
child: Column(children: [
|
||||
Text(
|
||||
'Backup password:',
|
||||
style: TextStyle(fontSize: 30),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20, bottom: 10),
|
||||
child: Observer(
|
||||
builder: (_) => GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(
|
||||
text:
|
||||
backupViewModelBase.backupPassword));
|
||||
showBar<void>(
|
||||
context,
|
||||
S.of(context).transaction_details_copied(
|
||||
'Backup password'));
|
||||
},
|
||||
child: Text(
|
||||
backupViewModelBase.backupPassword,
|
||||
style: TextStyle(fontSize: 26),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
))),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Text(
|
||||
'Please write down your Backup Password. Backup Password uses for import of backup files.',
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey),
|
||||
textAlign: TextAlign.center,
|
||||
))
|
||||
]))),
|
||||
Positioned(
|
||||
child: Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
isLoading: backupViewModelBase.state is IsExecutingState,
|
||||
onPressed: () => onExportBackup(context),
|
||||
text: 'Export backup',
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white)),
|
||||
bottom: 30,
|
||||
left: 20,
|
||||
right: 20,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void onExportBackup(BuildContext context) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: 'Export backup',
|
||||
alertContent:
|
||||
'Please be sure that you have saved your Backup Password.You will be no available to import backup files without Backup Password.\n\nHave you written it down?',
|
||||
rightButtonText: S.of(context).seed_alert_yes,
|
||||
leftButtonText: S.of(context).seed_alert_back,
|
||||
actionRightButton: () async {
|
||||
Navigator.of(dialogContext).pop();
|
||||
final backup = await backupViewModelBase.exportBackup();
|
||||
await Share.file(
|
||||
'Backup file', backup.name, backup.content, 'text');
|
||||
},
|
||||
actionLeftButton: () => Navigator.of(dialogContext).pop());
|
||||
});
|
||||
}
|
||||
}
|
73
lib/src/screens/backup/edit_backup_password_page.dart
Normal file
73
lib/src/screens/backup/edit_backup_password_page.dart
Normal file
|
@ -0,0 +1,73 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
|
||||
|
||||
class EditBackupPasswordPage extends BasePage {
|
||||
EditBackupPasswordPage(this.editBackupPasswordViewModel)
|
||||
: textEditingController = TextEditingController() {
|
||||
textEditingController.text = editBackupPasswordViewModel.backupPassword;
|
||||
textEditingController.addListener(() => editBackupPasswordViewModel
|
||||
.backupPassword = textEditingController.text);
|
||||
}
|
||||
|
||||
final EditBackupPasswordViewModel editBackupPasswordViewModel;
|
||||
final TextEditingController textEditingController;
|
||||
|
||||
@override
|
||||
String get title => 'Edit Backup Password';
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(left: 20, right: 20),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Center(
|
||||
child: Observer(
|
||||
builder: (_) => TextFormField(
|
||||
autofocus: true,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller: textEditingController,
|
||||
style: TextStyle(fontSize: 26, color: Colors.black)))),
|
||||
Positioned(
|
||||
child: Observer(
|
||||
builder: (_) => PrimaryButton(
|
||||
onPressed: () => onSave(context),
|
||||
text: 'Save',
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white,
|
||||
isDisabled: !editBackupPasswordViewModel.canSave)),
|
||||
bottom: 30,
|
||||
left: 0,
|
||||
right: 0)
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
void onSave(BuildContext context) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: 'Save backup password',
|
||||
alertContent:
|
||||
'Your previous backup files will be not available to import with new backup password. New backup password will be used only for new backup files. Are you sure that you want to change backup password ?',
|
||||
rightButtonText: S.of(context).ok,
|
||||
leftButtonText: S.of(context).cancel,
|
||||
actionRightButton: () async {
|
||||
await editBackupPasswordViewModel.save();
|
||||
Navigator.of(dialogContext)..pop()..pop();
|
||||
},
|
||||
actionLeftButton: () => Navigator.of(dialogContext).pop());
|
||||
});
|
||||
}
|
||||
}
|
89
lib/src/screens/restore/restore_from_backup_page.dart
Normal file
89
lib/src/screens/restore/restore_from_backup_page.dart
Normal file
|
@ -0,0 +1,89 @@
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/view_model/restore_from_backup_view_model.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
|
||||
class RestoreFromBackupPage extends BasePage {
|
||||
RestoreFromBackupPage(this.restoreFromBackupViewModel)
|
||||
: textEditingController = TextEditingController();
|
||||
|
||||
final RestoreFromBackupViewModel restoreFromBackupViewModel;
|
||||
final TextEditingController textEditingController;
|
||||
|
||||
@override
|
||||
String get title => 'Restore from backup';
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(bottom: 30, left: 25, right: 25),
|
||||
child: Column(children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
child: Center(
|
||||
child: TextFormField(
|
||||
obscureText: true,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Enter backup password here'),
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller: textEditingController,
|
||||
style: TextStyle(fontSize: 26, color: Colors.black))),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
onPressed: () => presentFilePicker(),
|
||||
text: 'Select backup file',
|
||||
color: Colors.grey,
|
||||
textColor: Colors.white)),
|
||||
SizedBox(width: 20),
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
onPressed: () => onImportHandler(context),
|
||||
text: 'Import',
|
||||
color: Theme.of(context).accentTextTheme.body2.color,
|
||||
textColor: Colors.white))
|
||||
])),
|
||||
]));
|
||||
}
|
||||
|
||||
Future<void> presentFilePicker() async {
|
||||
final result = await FilePicker.platform.pickFiles();
|
||||
|
||||
if (result?.files?.isEmpty ?? true) {
|
||||
return;
|
||||
}
|
||||
|
||||
restoreFromBackupViewModel.filePath = result.files.first.path;
|
||||
}
|
||||
|
||||
Future<void> onImportHandler(BuildContext context) async {
|
||||
if (textEditingController.text.isEmpty ||
|
||||
(restoreFromBackupViewModel.filePath.isEmpty ?? true)) {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.current.error,
|
||||
alertContent:
|
||||
'Please select backup file and enter backup password.',
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await restoreFromBackupViewModel.import(textEditingController.text);
|
||||
textEditingController.text = '';
|
||||
}
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
import 'package:cake_wallet/entities/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:cake_wallet/src/screens/restore/widgets/restore_button.dart';
|
||||
|
@ -8,10 +6,9 @@ import 'package:cake_wallet/src/screens/base_page.dart';
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class RestoreOptionsPage extends BasePage {
|
||||
RestoreOptionsPage({@required this.type});
|
||||
RestoreOptionsPage();
|
||||
|
||||
static const _aspectRatioImage = 2.086;
|
||||
final WalletType type;
|
||||
|
||||
@override
|
||||
String get title => S.current.restore_restore_wallet;
|
||||
|
@ -22,32 +19,30 @@ class RestoreOptionsPage extends BasePage {
|
|||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
padding: EdgeInsets.all(24),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
RestoreButton(
|
||||
onPressed: () =>
|
||||
Navigator.pushNamed(
|
||||
context, Routes.restoreWalletOptionsFromWelcome),
|
||||
image: imageSeedKeys,
|
||||
title: S.of(context).restore_title_from_seed_keys,
|
||||
description: S.of(context).restore_description_from_seed_keys
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: RestoreButton(
|
||||
onPressed: () {},
|
||||
image: imageBackup,
|
||||
title: S.of(context).restore_title_from_backup,
|
||||
description: S.of(context).restore_description_from_backup
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
);
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
padding: EdgeInsets.all(24),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
RestoreButton(
|
||||
onPressed: () =>
|
||||
Navigator.pushNamed(context, Routes.restoreWalletOptionsFromWelcome),
|
||||
image: imageSeedKeys,
|
||||
title: S.of(context).restore_title_from_seed_keys,
|
||||
description:
|
||||
S.of(context).restore_description_from_seed_keys),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: RestoreButton(
|
||||
onPressed: () =>
|
||||
Navigator.pushNamed(context, Routes.restoreFromBackup),
|
||||
image: imageBackup,
|
||||
title: S.of(context).restore_title_from_backup,
|
||||
description: S.of(context).restore_description_from_backup),
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -160,7 +160,7 @@ class WelcomePage extends BasePage {
|
|||
child: PrimaryImageButton(
|
||||
onPressed: () =>
|
||||
Navigator.pushNamed(context,
|
||||
Routes.restoreWalletOptionsFromWelcome),
|
||||
Routes.restoreOptions),
|
||||
image: restoreWalletImage,
|
||||
text: S
|
||||
.of(context)
|
||||
|
|
|
@ -1,34 +1,28 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class TrailButton extends StatelessWidget {
|
||||
TrailButton({
|
||||
@required this.caption,
|
||||
@required this.onPressed
|
||||
});
|
||||
TrailButton({@required this.caption, @required this.onPressed});
|
||||
|
||||
final String caption;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return ButtonTheme(
|
||||
minWidth: double.minPositive,
|
||||
highlightColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
child: FlatButton(
|
||||
padding: EdgeInsets.all(0),
|
||||
child: Text(
|
||||
caption,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme
|
||||
.display4
|
||||
.decorationColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14),
|
||||
),
|
||||
onPressed: onPressed),
|
||||
padding: EdgeInsets.all(0),
|
||||
child: Text(
|
||||
caption,
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).accentTextTheme.bodyText2.color,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14),
|
||||
),
|
||||
onPressed: onPressed),
|
||||
);
|
||||
}
|
||||
}
|
28
lib/store/secret_store.dart
Normal file
28
lib/store/secret_store.dart
Normal file
|
@ -0,0 +1,28 @@
|
|||
import 'package:cake_wallet/entities/secret_store_key.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'secret_store.g.dart';
|
||||
|
||||
class SecretStore = SecretStoreBase with _$SecretStore;
|
||||
|
||||
abstract class SecretStoreBase with Store {
|
||||
static Future<SecretStore> load(FlutterSecureStorage storage) async {
|
||||
final secretStore = SecretStore();
|
||||
final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
|
||||
final backupPassword = await storage.read(key: backupPasswordKey);
|
||||
secretStore.write(key: backupPasswordKey, value: backupPassword);
|
||||
|
||||
return secretStore;
|
||||
}
|
||||
|
||||
SecretStoreBase() : values = ObservableMap<String, String>();
|
||||
|
||||
ObservableMap values;
|
||||
|
||||
String read(String key) => values[key] as String;
|
||||
|
||||
String write({@required String key, @required String value}) =>
|
||||
values[key] = value;
|
||||
}
|
|
@ -85,10 +85,10 @@ abstract class SettingsStoreBase with Store {
|
|||
(String languageCode) => sharedPreferences.setString(
|
||||
PreferencesKey.currentLanguageCode, languageCode));
|
||||
|
||||
reaction((_) => balanceDisplayMode,
|
||||
(BalanceDisplayMode mode) => sharedPreferences.setInt(
|
||||
PreferencesKey.currentBalanceDisplayModeKey,
|
||||
mode.serialize()));
|
||||
reaction(
|
||||
(_) => balanceDisplayMode,
|
||||
(BalanceDisplayMode mode) => sharedPreferences.setInt(
|
||||
PreferencesKey.currentBalanceDisplayModeKey, mode.serialize()));
|
||||
|
||||
this
|
||||
.nodes
|
||||
|
@ -158,7 +158,7 @@ abstract class SettingsStoreBase with Store {
|
|||
.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
|
||||
false;
|
||||
final legacyTheme =
|
||||
(sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false)
|
||||
(sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false)
|
||||
? ThemeType.dark.index
|
||||
: ThemeType.bright.index;
|
||||
final savedTheme = ThemeList.deserialize(
|
||||
|
@ -203,6 +203,29 @@ abstract class SettingsStoreBase with Store {
|
|||
initialLanguageCode: savedLanguageCode);
|
||||
}
|
||||
|
||||
Future<void> reload(
|
||||
{@required Box<Node> nodeSource,
|
||||
FiatCurrency initialFiatCurrency = FiatCurrency.usd,
|
||||
TransactionPriority initialTransactionPriority = TransactionPriority.slow,
|
||||
BalanceDisplayMode initialBalanceDisplayMode =
|
||||
BalanceDisplayMode.availableBalance}) async {
|
||||
final settings = await SettingsStoreBase.load(
|
||||
nodeSource: nodeSource,
|
||||
initialBalanceDisplayMode: initialBalanceDisplayMode,
|
||||
initialFiatCurrency: initialFiatCurrency,
|
||||
initialTransactionPriority: initialTransactionPriority);
|
||||
fiatCurrency = settings.fiatCurrency;
|
||||
actionlistDisplayMode = settings.actionlistDisplayMode;
|
||||
transactionPriority = settings.transactionPriority;
|
||||
balanceDisplayMode = settings.balanceDisplayMode;
|
||||
shouldSaveRecipientAddress = settings.shouldSaveRecipientAddress;
|
||||
allowBiometricalAuthentication = settings.allowBiometricalAuthentication;
|
||||
currentTheme = settings.currentTheme;
|
||||
pinCodeLength = settings.pinCodeLength;
|
||||
languageCode = settings.languageCode;
|
||||
appVersion = settings.appVersion;
|
||||
}
|
||||
|
||||
Future<void> _saveCurrentNode(Node node, WalletType walletType) async {
|
||||
switch (walletType) {
|
||||
case WalletType.bitcoin:
|
||||
|
|
69
lib/view_model/backup_view_model.dart
Normal file
69
lib/view_model/backup_view_model.dart
Normal file
|
@ -0,0 +1,69 @@
|
|||
import 'package:cake_wallet/core/backup_service.dart';
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/entities/secret_store_key.dart';
|
||||
import 'package:cake_wallet/store/secret_store.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'backup_view_model.g.dart';
|
||||
|
||||
class BackupExportFile {
|
||||
BackupExportFile(this.content, {@required this.name});
|
||||
|
||||
final String name;
|
||||
final List<int> content;
|
||||
}
|
||||
|
||||
class BackupViewModel = BackupViewModelBase with _$BackupViewModel;
|
||||
|
||||
abstract class BackupViewModelBase with Store {
|
||||
BackupViewModelBase(this.secureStorage, this.secretStore, this.backupService)
|
||||
: isBackupPasswordVisible = false {
|
||||
state = InitialExecutionState();
|
||||
final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
|
||||
secretStore.values.observe((change) {
|
||||
if (change.key == key) {
|
||||
backupPassword = secretStore.read(key);
|
||||
}
|
||||
}, fireImmediately: true);
|
||||
}
|
||||
|
||||
final FlutterSecureStorage secureStorage;
|
||||
final SecretStore secretStore;
|
||||
final BackupService backupService;
|
||||
|
||||
@observable
|
||||
ExecutionState state;
|
||||
|
||||
@observable
|
||||
bool isBackupPasswordVisible;
|
||||
|
||||
@observable
|
||||
String backupPassword;
|
||||
|
||||
@action
|
||||
Future<void> init() async {
|
||||
final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
|
||||
backupPassword = await secureStorage.read(key: key);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<BackupExportFile> exportBackup() async {
|
||||
try {
|
||||
state = IsExecutingState();
|
||||
final backupContent = await backupService.exportBackup(backupPassword);
|
||||
state = ExecutedSuccessfullyState();
|
||||
|
||||
return BackupExportFile(backupContent.toList(),
|
||||
name: 'backup_${DateTime.now().toString()}.zip');
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
state = FailureState(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
void showMasterPassword() => isBackupPasswordVisible = true;
|
||||
}
|
44
lib/view_model/edit_backup_password_view_model.dart
Normal file
44
lib/view_model/edit_backup_password_view_model.dart
Normal file
|
@ -0,0 +1,44 @@
|
|||
import 'package:mobx/mobx.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:cake_wallet/entities/secret_store_key.dart';
|
||||
import 'package:cake_wallet/store/secret_store.dart';
|
||||
|
||||
part 'edit_backup_password_view_model.g.dart';
|
||||
|
||||
class EditBackupPasswordViewModel = EditBackupPasswordViewModelBase
|
||||
with _$EditBackupPasswordViewModel;
|
||||
|
||||
abstract class EditBackupPasswordViewModelBase with Store {
|
||||
EditBackupPasswordViewModelBase(this.secureStorage, this.secretStore) {
|
||||
final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
|
||||
backupPassword = secretStore.read(key);
|
||||
}
|
||||
|
||||
final FlutterSecureStorage secureStorage;
|
||||
final SecretStore secretStore;
|
||||
|
||||
@observable
|
||||
String backupPassword;
|
||||
|
||||
@computed
|
||||
bool get canSave {
|
||||
return !(_originalPassword == backupPassword);
|
||||
}
|
||||
|
||||
String _originalPassword;
|
||||
|
||||
@action
|
||||
Future<void> init() async {
|
||||
final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
|
||||
final password = await secureStorage.read(key: key);
|
||||
_originalPassword = password;
|
||||
backupPassword = password;
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> save() async {
|
||||
final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
|
||||
await secureStorage.write(key: key, value: backupPassword);
|
||||
secretStore.write(key: key, value: backupPassword);
|
||||
}
|
||||
}
|
56
lib/view_model/restore_from_backup_view_model.dart
Normal file
56
lib/view_model/restore_from_backup_view_model.dart
Normal file
|
@ -0,0 +1,56 @@
|
|||
import 'dart:io';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/main.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/core/backup_service.dart';
|
||||
import 'package:cake_wallet/entities/node.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/store/authentication_store.dart';
|
||||
|
||||
part 'restore_from_backup_view_model.g.dart';
|
||||
|
||||
class RestoreFromBackupViewModel = RestoreFromBackupViewModelBase
|
||||
with _$RestoreFromBackupViewModel;
|
||||
|
||||
abstract class RestoreFromBackupViewModelBase with Store {
|
||||
RestoreFromBackupViewModelBase(this.backupService);
|
||||
|
||||
@observable
|
||||
String filePath;
|
||||
|
||||
final BackupService backupService;
|
||||
|
||||
@action
|
||||
void reset() => filePath = '';
|
||||
|
||||
Future<void> import(String password) async {
|
||||
try {
|
||||
if (filePath?.isEmpty ?? true) {
|
||||
// FIXME: throw exception;
|
||||
return;
|
||||
}
|
||||
|
||||
final file = File(filePath);
|
||||
final data = await file.readAsBytes();
|
||||
|
||||
await backupService.importBackup(data, password);
|
||||
await main();
|
||||
|
||||
final store = getIt.get<AppStore>();
|
||||
ReactionDisposer reaction;
|
||||
await store.settingsStore.reload(nodeSource: getIt.get<Box<Node>>());
|
||||
|
||||
reaction = autorun((_) {
|
||||
final wallet = store.wallet;
|
||||
|
||||
if (wallet != null) {
|
||||
store.authenticationStore.state = AuthenticationState.allowed;
|
||||
reaction?.reaction?.dispose();
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -122,6 +122,19 @@ abstract class SettingsViewModelBase with Store {
|
|||
onItemSelected: (ThemeBase theme) =>
|
||||
_settingsStore.currentTheme = theme)
|
||||
],
|
||||
[
|
||||
RegularListItem(
|
||||
title: 'Backup',
|
||||
handler: (BuildContext context) {
|
||||
Navigator.of(context).pushNamed(Routes.auth, arguments:
|
||||
(bool isAuthenticatedSuccessfully, AuthPageState auth) {
|
||||
auth.close();
|
||||
if (isAuthenticatedSuccessfully) {
|
||||
Navigator.of(context).pushNamed(Routes.backup);
|
||||
}
|
||||
});
|
||||
}),
|
||||
],
|
||||
[
|
||||
LinkListItem(
|
||||
title: 'Email',
|
||||
|
|
|
@ -16,7 +16,7 @@ packages:
|
|||
source: hosted
|
||||
version: "0.40.6"
|
||||
archive:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: archive
|
||||
url: "https://pub.dartlang.org"
|
||||
|
@ -351,6 +351,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.2.1"
|
||||
file_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -68,8 +68,10 @@ dependencies:
|
|||
connectivity: ^0.4.9+2
|
||||
keyboard_actions: ^3.3.0
|
||||
flushbar: ^1.10.4
|
||||
unorm_dart: ^0.1.2
|
||||
archive: ^2.0.13
|
||||
cryptography: ^1.4.0
|
||||
file_picker: ^2.1.4
|
||||
unorm_dart: ^0.1.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Reference in a new issue