mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-11-16 17:27:37 +00:00
Backup.
This commit is contained in:
parent
c20d57e9a9
commit
c09a41b090
18 changed files with 826 additions and 24 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
|
||||
|
|
244
lib/core/backup.dart
Normal file
244
lib/core/backup.dart
Normal file
|
@ -0,0 +1,244 @@
|
|||
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';
|
||||
|
||||
class BackupService {
|
||||
BackupService(this._flutterSecureStorage, this._authService,
|
||||
this._walletInfoSource, this._keyService, this._sharedPreferences)
|
||||
: _cipher = chacha20Poly1305Aead;
|
||||
|
||||
final Cipher _cipher;
|
||||
final FlutterSecureStorage _flutterSecureStorage;
|
||||
final SharedPreferences _sharedPreferences;
|
||||
final AuthService _authService;
|
||||
final Box<WalletInfo> _walletInfoSource;
|
||||
final KeyService _keyService;
|
||||
|
||||
Future<void> importBackup(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 data = file.content as List<int>;
|
||||
File('${appDir.path}/' + filename)
|
||||
..createSync(recursive: true)
|
||||
..writeAsBytesSync(data);
|
||||
} else {
|
||||
Directory('${appDir.path}/' + filename)..create(recursive: true);
|
||||
}
|
||||
|
||||
print(filename);
|
||||
});
|
||||
|
||||
await importKeychainDump(password, nonce: nonce);
|
||||
await importPreferencesDump();
|
||||
}
|
||||
|
||||
Future<void> importPreferencesDump() async {
|
||||
final appDir = await getApplicationDocumentsDirectory();
|
||||
final preferencesFile = File('${appDir.path}/~_preferences_dump');
|
||||
|
||||
if (!preferencesFile.existsSync()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final data =
|
||||
json.decode(preferencesFile.readAsStringSync()) as Map<String, Object>;
|
||||
print('data $data');
|
||||
|
||||
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 preferencesFile.delete();
|
||||
}
|
||||
|
||||
Future<void> importKeychainDump(String password,
|
||||
{@required String nonce}) async {
|
||||
final appDir = await getApplicationDocumentsDirectory();
|
||||
final keychainDumpFile = File('${appDir.path}/~_keychain_dump');
|
||||
final decryptedKeychainDumpFileData =
|
||||
await _decrypt(keychainDumpFile.readAsBytesSync(), 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);
|
||||
|
||||
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> exportBackup(String password,
|
||||
{@required String nonce}) 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);
|
||||
|
||||
return await _encrypt(content, password, nonce);
|
||||
}
|
||||
|
||||
Future<Uint8List> exportKeychainDump(String password,
|
||||
{@required String nonce}) 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 data =
|
||||
utf8.encode(json.encode({'pin': decodedPin, 'wallets': wallets}));
|
||||
final encrypted = await _encrypt(Uint8List.fromList(data), password, nonce);
|
||||
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
Future<String> exportPreferencesJSON() async {
|
||||
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.currentDarkTheme:
|
||||
_sharedPreferences.getBool(PreferencesKey.currentDarkTheme),
|
||||
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),
|
||||
'currentTheme': _sharedPreferences.getInt('current_theme')
|
||||
// FIX-ME: Unnamed constant.
|
||||
};
|
||||
|
||||
return json.encode(preferences);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
59
lib/di.dart
59
lib/di.dart
|
@ -1,4 +1,5 @@
|
|||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
|
||||
import 'package:cake_wallet/core/backup.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';
|
||||
|
@ -20,6 +23,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';
|
||||
|
@ -32,6 +37,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';
|
||||
|
@ -46,13 +52,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/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/auth_view_model.dart';
|
||||
|
@ -127,6 +136,11 @@ Future setup(
|
|||
getIt.registerSingleton<ExchangeTemplateStore>(
|
||||
ExchangeTemplateStore(templateSource: exchangeTemplates));
|
||||
|
||||
final secretStore =
|
||||
await SecretStoreBase.load(getIt.get<FlutterSecureStorage>());
|
||||
|
||||
getIt.registerSingleton<SecretStore>(secretStore);
|
||||
|
||||
getIt.registerFactory<KeyService>(
|
||||
() => KeyService(getIt.get<FlutterSecureStorage>()));
|
||||
|
||||
|
@ -291,17 +305,16 @@ Future setup(
|
|||
getIt.get<MoneroAccountEditOrCreateViewModel>()));*/
|
||||
|
||||
getIt.registerFactoryParam<MoneroAccountEditOrCreateViewModel,
|
||||
AccountListItem, void>(
|
||||
(AccountListItem account, _) =>
|
||||
MoneroAccountEditOrCreateViewModel((
|
||||
getIt.get<AppStore>().wallet as MoneroWallet).accountList,
|
||||
accountListItem: account));
|
||||
AccountListItem, void>(
|
||||
(AccountListItem account, _) => MoneroAccountEditOrCreateViewModel(
|
||||
(getIt.get<AppStore>().wallet as MoneroWallet).accountList,
|
||||
accountListItem: account));
|
||||
|
||||
getIt.registerFactoryParam<MoneroAccountEditOrCreatePage,
|
||||
AccountListItem, void>((AccountListItem account, _) =>
|
||||
MoneroAccountEditOrCreatePage(
|
||||
getIt.registerFactoryParam<MoneroAccountEditOrCreatePage, AccountListItem,
|
||||
void>(
|
||||
(AccountListItem account, _) => MoneroAccountEditOrCreatePage(
|
||||
moneroAccountCreationViewModel:
|
||||
getIt.get<MoneroAccountEditOrCreateViewModel>(param1: account)));
|
||||
getIt.get<MoneroAccountEditOrCreateViewModel>(param1: account)));
|
||||
|
||||
getIt.registerFactory(() {
|
||||
final appStore = getIt.get<AppStore>();
|
||||
|
@ -422,4 +435,32 @@ Future setup(
|
|||
transactionDescriptionBox));
|
||||
|
||||
getIt.registerFactory(() => PreSeedPage());
|
||||
|
||||
getIt.registerFactory(() => BackupService(
|
||||
getIt.get<FlutterSecureStorage>(),
|
||||
getIt.get<AuthService>(),
|
||||
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.registerFactoryParam<RestoreOptionsPage, WalletType, void>(
|
||||
(WalletType type, _) => RestoreOptionsPage(type: type));
|
||||
|
||||
getIt.registerFactory(
|
||||
() => RestoreFromBackupViewModel(getIt.get<BackupService>()));
|
||||
|
||||
getIt.registerFactory(
|
||||
() => RestoreFromBackupPage(getIt.get<RestoreFromBackupViewModel>()));
|
||||
}
|
||||
|
|
|
@ -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,9 +23,15 @@ String generateStoreKeyFor({SecretStoreKey key, String walletName = "",}) {
|
|||
}
|
||||
break;
|
||||
|
||||
case SecretStoreKey.backupPassword:
|
||||
{
|
||||
_key = backupPassword;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{}
|
||||
}
|
||||
|
||||
return _key;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'package:cake_wallet/core/backup.dart';
|
||||
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
|
|
@ -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';
|
||||
|
@ -65,15 +68,13 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
builder: (_) => getIt.get<SetupPinCodePage>(param1:
|
||||
(PinCodeState<PinCodeWidget> context, dynamic _) async {
|
||||
try {
|
||||
context.changeProcessText(
|
||||
S.current.creating_new_wallet);
|
||||
context.changeProcessText(S.current.creating_new_wallet);
|
||||
final newWalletVM =
|
||||
getIt.get<WalletNewVM>(param1: WalletType.monero);
|
||||
await newWalletVM.create(
|
||||
options: 'English'); // FIXME: Unnamed constant
|
||||
context.hideProgressText();
|
||||
await Navigator.of(context.context)
|
||||
.pushNamed(Routes.preSeed);
|
||||
await Navigator.of(context.context).pushNamed(Routes.preSeed);
|
||||
} catch (e) {
|
||||
context.changeProcessText('Error: ${e.toString()}');
|
||||
}
|
||||
|
@ -115,7 +116,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
case Routes.restoreOptions:
|
||||
final type = settings.arguments as WalletType;
|
||||
return CupertinoPageRoute<void>(
|
||||
builder: (_) => RestoreOptionsPage(type: type));
|
||||
builder: (_) => getIt.get<RestoreOptionsPage>(param1: type));
|
||||
|
||||
case Routes.restoreWalletOptions:
|
||||
final type = WalletType.monero; //settings.arguments as WalletType;
|
||||
|
@ -264,7 +265,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
case Routes.accountCreation:
|
||||
return CupertinoPageRoute<String>(
|
||||
builder: (_) => getIt.get<MoneroAccountEditOrCreatePage>(
|
||||
param1: settings.arguments as AccountListItem));
|
||||
param1: settings.arguments as AccountListItem));
|
||||
|
||||
case Routes.addressBook:
|
||||
return MaterialPageRoute<void>(
|
||||
|
@ -326,8 +327,19 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
builder: (_) => getIt.get<LanguageListPage>());
|
||||
|
||||
case Routes.preSeed:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => getIt.get<PreSeedPage>());
|
||||
return MaterialPageRoute<void>(builder: (_) => getIt.get<PreSeedPage>());
|
||||
|
||||
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>(
|
||||
|
|
|
@ -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';
|
||||
}
|
93
lib/src/screens/backup/backup_page.dart
Normal file
93
lib/src/screens/backup/backup_page.dart
Normal file
|
@ -0,0 +1,93 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:esys_flutter_share/esys_flutter_share.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: 225,
|
||||
child: Column(children: [
|
||||
Text(
|
||||
'Backup password:',
|
||||
style: TextStyle(fontSize: 30),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20, bottom: 10),
|
||||
child: Observer(
|
||||
builder: (_) => Text(
|
||||
backupViewModelBase.backupPassword,
|
||||
style: TextStyle(fontSize: 26),
|
||||
))),
|
||||
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());
|
||||
});
|
||||
}
|
||||
}
|
88
lib/src/screens/restore/restore_from_backup_page.dart
Normal file
88
lib/src/screens/restore/restore_from_backup_page.dart
Normal file
|
@ -0,0 +1,88 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
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';
|
||||
|
@ -39,7 +38,8 @@ class RestoreOptionsPage extends BasePage {
|
|||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: RestoreButton(
|
||||
onPressed: () {},
|
||||
onPressed: () => Navigator.pushNamed(
|
||||
context, Routes.restoreFromBackup),
|
||||
image: imageBackup,
|
||||
title: S.of(context).restore_title_from_backup,
|
||||
description: S.of(context).restore_description_from_backup
|
||||
|
|
|
@ -21,7 +21,7 @@ class TrailButton extends StatelessWidget {
|
|||
child: Text(
|
||||
caption,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.subhead.decorationColor,
|
||||
color: Theme.of(context).textTheme.display1.color,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14),
|
||||
),
|
||||
|
|
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;
|
||||
}
|
68
lib/view_model/backup_view_model.dart
Normal file
68
lib/view_model/backup_view_model.dart
Normal file
|
@ -0,0 +1,68 @@
|
|||
import 'package:cake_wallet/core/backup.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('', nonce: '');
|
||||
state = ExecutedSuccessfullyState();
|
||||
|
||||
return BackupExportFile(backupContent.toList(),
|
||||
name: 'backup_${DateTime.now().toString()}.zip');
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
state = FailureState(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
29
lib/view_model/restore_from_backup_view_model.dart
Normal file
29
lib/view_model/restore_from_backup_view_model.dart
Normal file
|
@ -0,0 +1,29 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/core/backup.dart';
|
||||
import 'package:mobx/mobx.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;
|
||||
|
||||
Future<void> import(String password) async {
|
||||
if (filePath?.isEmpty ?? true) {
|
||||
// FIXME: throw exception;
|
||||
return;
|
||||
}
|
||||
|
||||
final file = File(filePath);
|
||||
final data = await file.readAsBytes();
|
||||
|
||||
await backupService.importBackup(data, password, nonce: null);
|
||||
}
|
||||
}
|
16
pubspec.lock
16
pubspec.lock
|
@ -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"
|
||||
|
@ -253,6 +253,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
cryptography:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cryptography
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -344,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:
|
||||
|
|
|
@ -67,6 +67,9 @@ dependencies:
|
|||
connectivity: ^0.4.9+2
|
||||
keyboard_actions: ^3.3.0
|
||||
flushbar: ^1.10.4
|
||||
archive: ^2.0.13
|
||||
cryptography: ^1.4.0
|
||||
file_picker: ^2.1.4
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Reference in a new issue