Backup.
M committed
Jan 13, 2021 at 18:43 UTC
c09a41b090aa19202321f9d3f269e63301c404ec
18 files changed
+827
-25
ios/Podfile.lock
+50
@@ -26,8 +26,42 @@ PODS:
26
- Flutter
27
- devicelocale (0.0.1):
28
- Flutter
29
+ - DKImagePickerController/Core (4.3.2):
30
+ - DKImagePickerController/ImageDataManager
31
+ - DKImagePickerController/Resource
32
+ - DKImagePickerController/ImageDataManager (4.3.2)
33
+ - DKImagePickerController/PhotoGallery (4.3.2):
34
+ - DKImagePickerController/Core
35
+ - DKPhotoGallery
36
+ - DKImagePickerController/Resource (4.3.2)
37
+ - DKPhotoGallery (0.0.17):
38
+ - DKPhotoGallery/Core (= 0.0.17)
39
+ - DKPhotoGallery/Model (= 0.0.17)
40
+ - DKPhotoGallery/Preview (= 0.0.17)
41
+ - DKPhotoGallery/Resource (= 0.0.17)
42
+ - SDWebImage
43
+ - SwiftyGif
44
+ - DKPhotoGallery/Core (0.0.17):
45
+ - DKPhotoGallery/Model
46
+ - DKPhotoGallery/Preview
47
+ - SDWebImage
48
+ - SwiftyGif
49
+ - DKPhotoGallery/Model (0.0.17):
50
+ - SDWebImage
51
+ - SwiftyGif
52
+ - DKPhotoGallery/Preview (0.0.17):
53
+ - DKPhotoGallery/Model
54
+ - DKPhotoGallery/Resource
55
+ - SDWebImage
56
+ - SwiftyGif
57
+ - DKPhotoGallery/Resource (0.0.17):
58
+ - SDWebImage
59
+ - SwiftyGif
60
- esys_flutter_share (0.0.1):
61
- Flutter
62
+ - file_picker (0.0.1):
63
+ - DKImagePickerController/PhotoGallery
64
+ - Flutter
65
- Flutter (1.0.0)
66
- flutter_secure_storage (3.3.1):
67
- Flutter
@@ -39,11 +73,15 @@ PODS:
73
- path_provider (0.0.1):
74
- Flutter
75
- Reachability (3.2)
76
+ - SDWebImage (5.9.1):
77
+ - SDWebImage/Core (= 5.9.1)
78
+ - SDWebImage/Core (5.9.1)
79
- share (0.0.1):
80
- Flutter
81
- shared_preferences (0.0.1):
82
- Flutter
83
- SwiftProtobuf (1.12.0)
84
+ - SwiftyGif (5.3.0)
85
- url_launcher (0.0.1):
86
- Flutter
87
@@ -54,6 +92,7 @@ DEPENDENCIES:
92
- cw_monero (from `.symlinks/plugins/cw_monero/ios`)
93
- devicelocale (from `.symlinks/plugins/devicelocale/ios`)
94
- esys_flutter_share (from `.symlinks/plugins/esys_flutter_share/ios`)
95
+ - file_picker (from `.symlinks/plugins/file_picker/ios`)
96
- Flutter (from `Flutter`)
97
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
98
- local_auth (from `.symlinks/plugins/local_auth/ios`)
@@ -66,9 +105,13 @@ DEPENDENCIES:
105
SPEC REPOS:
106
https://github.com/CocoaPods/Specs.git:
107
- CryptoSwift
108
+ - DKImagePickerController
109
+ - DKPhotoGallery
110
- MTBBarcodeScanner
111
- Reachability
112
+ - SDWebImage
113
- SwiftProtobuf
114
+ - SwiftyGif
115
116
EXTERNAL SOURCES:
117
barcode_scan:
@@ -81,6 +124,8 @@ EXTERNAL SOURCES:
124
:path: ".symlinks/plugins/devicelocale/ios"
125
esys_flutter_share:
126
:path: ".symlinks/plugins/esys_flutter_share/ios"
127
+ file_picker:
128
+ :path: ".symlinks/plugins/file_picker/ios"
129
Flutter:
130
:path: Flutter
131
flutter_secure_storage:
@@ -104,7 +149,10 @@ SPEC CHECKSUMS:
149
CryptoSwift: 093499be1a94b0cae36e6c26b70870668cb56060
150
cw_monero: 2e1f79929880cc2293b5bc1b25e28152e4d84649
151
devicelocale: feebbe5e7a30adb8c4f83185de1b50ff19b44f00
152
+ DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d
153
+ DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
154
esys_flutter_share: 403498dab005b36ce1f8d7aff377e81f0621b0b4
155
+ file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
156
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
157
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
158
local_auth: 25938960984c3a7f6e3253e3f8d962fdd16852bd
@@ -112,9 +160,11 @@ SPEC CHECKSUMS:
160
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
161
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
162
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
163
+ SDWebImage: a990c053fff71e388a10f3357edb0be17929c9c5
164
share: 0b2c3e82132f5888bccca3351c504d0003b3b410
165
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
166
SwiftProtobuf: 4ef85479c18ca85b5482b343df9c319c62bda699
167
+ SwiftyGif: e466e86c660d343357ab944a819a101c4127cb40
168
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
169
170
PODFILE CHECKSUM: ba3d2157523e2f4dc333b987efdac6635da8125d
lib/core/backup.dart
new
+244
@@ -0,0 +1,244 @@
1
+import 'dart:convert';
2
+import 'dart:io';
3
+import 'dart:typed_data';
4
+import 'package:flutter/foundation.dart';
5
+import 'package:hive/hive.dart';
6
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
7
+import 'package:path_provider/path_provider.dart';
8
+import 'package:cryptography/cryptography.dart';
9
+import 'package:shared_preferences/shared_preferences.dart';
10
+import 'package:archive/archive_io.dart';
11
+import 'package:cake_wallet/core/auth_service.dart';
12
+import 'package:cake_wallet/core/key_service.dart';
13
+import 'package:cake_wallet/entities/encrypt.dart';
14
+import 'package:cake_wallet/entities/preferences_key.dart';
15
+import 'package:cake_wallet/entities/secret_store_key.dart';
16
+import 'package:cake_wallet/entities/wallet_info.dart';
17
+
18
+class BackupService {
19
+ BackupService(this._flutterSecureStorage, this._authService,
20
+ this._walletInfoSource, this._keyService, this._sharedPreferences)
21
+ : _cipher = chacha20Poly1305Aead;
22
+
23
+ final Cipher _cipher;
24
+ final FlutterSecureStorage _flutterSecureStorage;
25
+ final SharedPreferences _sharedPreferences;
26
+ final AuthService _authService;
27
+ final Box<WalletInfo> _walletInfoSource;
28
+ final KeyService _keyService;
29
+
30
+ Future<void> importBackup(Uint8List data, String password,
31
+ {@required String nonce}) async {
32
+ final appDir = await getApplicationDocumentsDirectory();
33
+ final decryptedData = await _decrypt(data, password, nonce);
34
+ final zip = ZipDecoder().decodeBytes(decryptedData);
35
+
36
+ zip.files.forEach((file) {
37
+ final filename = file.name;
38
+
39
+ if (file.isFile) {
40
+ final data = file.content as List<int>;
41
+ File('${appDir.path}/' + filename)
42
+ ..createSync(recursive: true)
43
+ ..writeAsBytesSync(data);
44
+ } else {
45
+ Directory('${appDir.path}/' + filename)..create(recursive: true);
46
+ }
47
+
48
+ print(filename);
49
+ });
50
+
51
+ await importKeychainDump(password, nonce: nonce);
52
+ await importPreferencesDump();
53
+ }
54
+
55
+ Future<void> importPreferencesDump() async {
56
+ final appDir = await getApplicationDocumentsDirectory();
57
+ final preferencesFile = File('${appDir.path}/~_preferences_dump');
58
+
59
+ if (!preferencesFile.existsSync()) {
60
+ return;
61
+ }
62
+
63
+ final data =
64
+ json.decode(preferencesFile.readAsStringSync()) as Map<String, Object>;
65
+ print('data $data');
66
+
67
+ await _sharedPreferences.setString(PreferencesKey.currentWalletName,
68
+ data[PreferencesKey.currentWalletName] as String);
69
+ await _sharedPreferences.setInt(PreferencesKey.currentNodeIdKey,
70
+ data[PreferencesKey.currentNodeIdKey] as int);
71
+ await _sharedPreferences.setInt(PreferencesKey.currentBalanceDisplayModeKey,
72
+ data[PreferencesKey.currentBalanceDisplayModeKey] as int);
73
+ await _sharedPreferences.setInt(PreferencesKey.currentWalletType,
74
+ data[PreferencesKey.currentWalletType] as int);
75
+ await _sharedPreferences.setString(PreferencesKey.currentFiatCurrencyKey,
76
+ data[PreferencesKey.currentFiatCurrencyKey] as String);
77
+ await _sharedPreferences.setBool(
78
+ PreferencesKey.shouldSaveRecipientAddressKey,
79
+ data[PreferencesKey.shouldSaveRecipientAddressKey] as bool);
80
+ await _sharedPreferences.setInt(
81
+ PreferencesKey.currentTransactionPriorityKey,
82
+ data[PreferencesKey.currentTransactionPriorityKey] as int);
83
+ await _sharedPreferences.setBool(
84
+ PreferencesKey.allowBiometricalAuthenticationKey,
85
+ data[PreferencesKey.allowBiometricalAuthenticationKey] as bool);
86
+ await _sharedPreferences.setInt(
87
+ PreferencesKey.currentBitcoinElectrumSererIdKey,
88
+ data[PreferencesKey.currentBitcoinElectrumSererIdKey] as int);
89
+ await _sharedPreferences.setInt(PreferencesKey.currentLanguageCode,
90
+ data[PreferencesKey.currentLanguageCode] as int);
91
+ await _sharedPreferences.setInt(PreferencesKey.displayActionListModeKey,
92
+ data[PreferencesKey.displayActionListModeKey] as int);
93
+ await _sharedPreferences.setInt(
94
+ 'current_theme', data['current_theme'] as int);
95
+
96
+ await preferencesFile.delete();
97
+ }
98
+
99
+ Future<void> importKeychainDump(String password,
100
+ {@required String nonce}) async {
101
+ final appDir = await getApplicationDocumentsDirectory();
102
+ final keychainDumpFile = File('${appDir.path}/~_keychain_dump');
103
+ final decryptedKeychainDumpFileData =
104
+ await _decrypt(keychainDumpFile.readAsBytesSync(), password, nonce);
105
+ final keychainJSON = json.decode(utf8.decode(decryptedKeychainDumpFileData))
106
+ as Map<String, dynamic>;
107
+ final keychainWalletsInfo = keychainJSON['wallets'] as List;
108
+ final decodedPin = keychainJSON['pin'] as String;
109
+ final pinCodeKey = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
110
+
111
+ keychainWalletsInfo.forEach((dynamic rawInfo) async {
112
+ final info = rawInfo as Map<String, dynamic>;
113
+ await importWalletKeychainInfo(info);
114
+ });
115
+
116
+ await _flutterSecureStorage.write(
117
+ key: pinCodeKey, value: encodedPinCode(pin: decodedPin));
118
+
119
+ keychainDumpFile.deleteSync();
120
+ }
121
+
122
+ Future<void> importWalletKeychainInfo(Map<String, dynamic> info) async {
123
+ final name = info['name'] as String;
124
+ final password = info['password'] as String;
125
+
126
+ await _keyService.saveWalletPassword(walletName: name, password: password);
127
+ }
128
+
129
+ Future<Uint8List> exportBackup(String password,
130
+ {@required String nonce}) async {
131
+ final zipEncoder = ZipFileEncoder();
132
+ final appDir = await getApplicationDocumentsDirectory();
133
+ final now = DateTime.now();
134
+ final tmpDir = Directory('${appDir.path}/~_BACKUP_TMP');
135
+ final archivePath = '${tmpDir.path}/backup_${now.toString()}.zip';
136
+ final fileEntities = appDir.listSync(recursive: false);
137
+ final keychainDump = await exportKeychainDump(password, nonce: nonce);
138
+ final preferencesDump = await exportPreferencesJSON();
139
+ final preferencesDumpFile = File('${tmpDir.path}/~_preferences_dump_TMP');
140
+ final keychainDumpFile = File('${tmpDir.path}/~_keychain_dump_TMP');
141
+
142
+ if (tmpDir.existsSync()) {
143
+ tmpDir.deleteSync(recursive: true);
144
+ }
145
+
146
+ tmpDir.createSync();
147
+ zipEncoder.create(archivePath);
148
+
149
+ fileEntities.forEach((entity) {
150
+ if (entity.path == archivePath || entity.path == tmpDir.path) {
151
+ return;
152
+ }
153
+
154
+ if (entity.statSync().type == FileSystemEntityType.directory) {
155
+ zipEncoder.addDirectory(Directory(entity.path));
156
+ } else {
157
+ zipEncoder.addFile(File(entity.path));
158
+ }
159
+ });
160
+ await keychainDumpFile.writeAsBytes(keychainDump.toList());
161
+ await preferencesDumpFile.writeAsString(preferencesDump);
162
+ zipEncoder.addFile(preferencesDumpFile, '~_preferences_dump');
163
+ zipEncoder.addFile(keychainDumpFile, '~_keychain_dump');
164
+ zipEncoder.close();
165
+
166
+ final content = File(archivePath).readAsBytesSync();
167
+ tmpDir.deleteSync(recursive: true);
168
+
169
+ return await _encrypt(content, password, nonce);
170
+ }
171
+
172
+ Future<Uint8List> exportKeychainDump(String password,
173
+ {@required String nonce}) async {
174
+ final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
175
+ final encodedPin = await _flutterSecureStorage.read(key: key);
176
+ final decodedPin = decodedPinCode(pin: encodedPin);
177
+ final wallets =
178
+ await Future.wait(_walletInfoSource.values.map((walletInfo) async {
179
+ return {
180
+ 'name': walletInfo.name,
181
+ 'type': walletInfo.type.toString(),
182
+ 'password':
183
+ await _keyService.getWalletPassword(walletName: walletInfo.name)
184
+ };
185
+ }));
186
+
187
+ final data =
188
+ utf8.encode(json.encode({'pin': decodedPin, 'wallets': wallets}));
189
+ final encrypted = await _encrypt(Uint8List.fromList(data), password, nonce);
190
+
191
+ return encrypted;
192
+ }
193
+
194
+ Future<String> exportPreferencesJSON() async {
195
+ final preferences = <String, Object>{
196
+ PreferencesKey.currentWalletName:
197
+ _sharedPreferences.getString(PreferencesKey.currentWalletName),
198
+ PreferencesKey.currentNodeIdKey:
199
+ _sharedPreferences.getInt(PreferencesKey.currentNodeIdKey),
200
+ PreferencesKey.currentBalanceDisplayModeKey: _sharedPreferences
201
+ .getInt(PreferencesKey.currentBalanceDisplayModeKey),
202
+ PreferencesKey.currentWalletType:
203
+ _sharedPreferences.getInt(PreferencesKey.currentWalletType),
204
+ PreferencesKey.currentFiatCurrencyKey:
205
+ _sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey),
206
+ PreferencesKey.shouldSaveRecipientAddressKey: _sharedPreferences
207
+ .getBool(PreferencesKey.shouldSaveRecipientAddressKey),
208
+ PreferencesKey.currentDarkTheme:
209
+ _sharedPreferences.getBool(PreferencesKey.currentDarkTheme),
210
+ PreferencesKey.currentPinLength:
211
+ _sharedPreferences.getInt(PreferencesKey.currentPinLength),
212
+ PreferencesKey.currentTransactionPriorityKey: _sharedPreferences
213
+ .getInt(PreferencesKey.currentTransactionPriorityKey),
214
+ PreferencesKey.allowBiometricalAuthenticationKey: _sharedPreferences
215
+ .getBool(PreferencesKey.allowBiometricalAuthenticationKey),
216
+ PreferencesKey.currentBitcoinElectrumSererIdKey: _sharedPreferences
217
+ .getInt(PreferencesKey.currentBitcoinElectrumSererIdKey),
218
+ PreferencesKey.currentLanguageCode:
219
+ _sharedPreferences.getString(PreferencesKey.currentLanguageCode),
220
+ PreferencesKey.displayActionListModeKey:
221
+ _sharedPreferences.getInt(PreferencesKey.displayActionListModeKey),
222
+ 'currentTheme': _sharedPreferences.getInt('current_theme')
223
+ // FIX-ME: Unnamed constant.
224
+ };
225
+
226
+ return json.encode(preferences);
227
+ }
228
+
229
+ Future<Uint8List> _encrypt(
230
+ Uint8List data, String secretKeySource, String nonceBase64) async {
231
+ final secretKeyHash = await sha256.hash(utf8.encode(secretKeySource));
232
+ final secretKey = SecretKey(secretKeyHash.bytes);
233
+ final nonce = Nonce(base64.decode(nonceBase64));
234
+ return await _cipher.encrypt(data, secretKey: secretKey, nonce: nonce);
235
+ }
236
+
237
+ Future<Uint8List> _decrypt(
238
+ Uint8List data, String secretKeySource, String nonceBase64) async {
239
+ final secretKeyHash = await sha256.hash(utf8.encode(secretKeySource));
240
+ final secretKey = SecretKey(secretKeyHash.bytes);
241
+ final nonce = Nonce(base64.decode(nonceBase64));
242
+ return await _cipher.decrypt(data, secretKey: secretKey, nonce: nonce);
243
+ }
244
+}
lib/di.dart
+51
-10
@@ -1,4 +1,5 @@
1
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
2
+import 'package:cake_wallet/core/backup.dart';
3
import 'package:cake_wallet/core/wallet_service.dart';
4
import 'package:cake_wallet/entities/biometric_auth.dart';
5
import 'package:cake_wallet/entities/contact_record.dart';
@@ -10,6 +11,8 @@ import 'package:cake_wallet/entities/contact.dart';
11
import 'package:cake_wallet/entities/node.dart';
12
import 'package:cake_wallet/exchange/trade.dart';
13
import 'package:cake_wallet/reactions/on_authentication_state_change.dart';
14
+import 'package:cake_wallet/src/screens/backup/backup_page.dart';
15
+import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
16
17
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
18
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';
23
import 'package:cake_wallet/src/screens/nodes/nodes_list_page.dart';
24
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
25
import 'package:cake_wallet/src/screens/rescan/rescan_page.dart';
26
+import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
27
+import 'package:cake_wallet/src/screens/restore/restore_options_page.dart';
28
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
29
import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
30
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';
37
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
38
import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
39
import 'package:cake_wallet/store/node_list_store.dart';
40
+import 'package:cake_wallet/store/secret_store.dart';
41
import 'package:cake_wallet/store/settings_store.dart';
42
import 'package:cake_wallet/core/auth_service.dart';
43
import 'package:cake_wallet/core/key_service.dart';
@@ -46,13 +52,16 @@ import 'package:cake_wallet/src/screens/send/send_page.dart';
52
import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart';
53
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
54
import 'package:cake_wallet/store/wallet_list_store.dart';
55
+import 'package:cake_wallet/view_model/backup_view_model.dart';
56
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
57
import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart';
58
+import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
59
import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
60
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
61
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
62
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
63
import 'package:cake_wallet/view_model/rescan_view_model.dart';
64
+import 'package:cake_wallet/view_model/restore_from_backup_view_model.dart';
65
import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
66
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
67
import 'package:cake_wallet/view_model/auth_view_model.dart';
@@ -127,6 +136,11 @@ Future setup(
136
getIt.registerSingleton<ExchangeTemplateStore>(
137
ExchangeTemplateStore(templateSource: exchangeTemplates));
138
139
+ final secretStore =
140
+ await SecretStoreBase.load(getIt.get<FlutterSecureStorage>());
141
+
142
+ getIt.registerSingleton<SecretStore>(secretStore);
143
+
144
getIt.registerFactory<KeyService>(
145
() => KeyService(getIt.get<FlutterSecureStorage>()));
146
@@ -291,17 +305,16 @@ Future setup(
305
getIt.get<MoneroAccountEditOrCreateViewModel>()));*/
306
307
getIt.registerFactoryParam<MoneroAccountEditOrCreateViewModel,
294
- AccountListItem, void>(
295
- (AccountListItem account, _) =>
296
- MoneroAccountEditOrCreateViewModel((
297
- getIt.get<AppStore>().wallet as MoneroWallet).accountList,
298
- accountListItem: account));
299
-
300
- getIt.registerFactoryParam<MoneroAccountEditOrCreatePage,
301
- AccountListItem, void>((AccountListItem account, _) =>
302
- MoneroAccountEditOrCreatePage(
308
+ AccountListItem, void>(
309
+ (AccountListItem account, _) => MoneroAccountEditOrCreateViewModel(
310
+ (getIt.get<AppStore>().wallet as MoneroWallet).accountList,
311
+ accountListItem: account));
312
+
313
+ getIt.registerFactoryParam<MoneroAccountEditOrCreatePage, AccountListItem,
314
+ void>(
315
+ (AccountListItem account, _) => MoneroAccountEditOrCreatePage(
316
moneroAccountCreationViewModel:
304
- getIt.get<MoneroAccountEditOrCreateViewModel>(param1: account)));
317
+ getIt.get<MoneroAccountEditOrCreateViewModel>(param1: account)));
318
319
getIt.registerFactory(() {
320
final appStore = getIt.get<AppStore>();
@@ -422,4 +435,32 @@ Future setup(
435
transactionDescriptionBox));
436
437
getIt.registerFactory(() => PreSeedPage());
438
+
439
+ getIt.registerFactory(() => BackupService(
440
+ getIt.get<FlutterSecureStorage>(),
441
+ getIt.get<AuthService>(),
442
+ walletInfoSource,
443
+ getIt.get<KeyService>(),
444
+ getIt.get<SharedPreferences>()));
445
+
446
+ getIt.registerFactory(() => BackupViewModel(getIt.get<FlutterSecureStorage>(),
447
+ getIt.get<SecretStore>(), getIt.get<BackupService>()));
448
+
449
+ getIt.registerFactory(() => BackupPage(getIt.get<BackupViewModel>()));
450
+
451
+ getIt.registerFactory(() => EditBackupPasswordViewModel(
452
+ getIt.get<FlutterSecureStorage>(), getIt.get<SecretStore>())
453
+ ..init());
454
+
455
+ getIt.registerFactory(
456
+ () => EditBackupPasswordPage(getIt.get<EditBackupPasswordViewModel>()));
457
+
458
+ getIt.registerFactoryParam<RestoreOptionsPage, WalletType, void>(
459
+ (WalletType type, _) => RestoreOptionsPage(type: type));
460
+
461
+ getIt.registerFactory(
462
+ () => RestoreFromBackupViewModel(getIt.get<BackupService>()));
463
+
464
+ getIt.registerFactory(
465
+ () => RestoreFromBackupPage(getIt.get<RestoreFromBackupViewModel>()));
466
}
lib/entities/secret_store_key.dart
+13
-3
@@ -1,9 +1,13 @@
1
-enum SecretStoreKey { moneroWalletPassword, pinCodePassword }
1
+enum SecretStoreKey { moneroWalletPassword, pinCodePassword, backupPassword }
2
3
const moneroWalletPassword = "MONERO_WALLET_PASSWORD";
4
const pinCodePassword = "PIN_CODE_PASSWORD";
5
+const backupPassword = "BACKUP_CODE_PASSWORD";
6
6
-String generateStoreKeyFor({SecretStoreKey key, String walletName = "",}) {
7
+String generateStoreKeyFor({
8
+ SecretStoreKey key,
9
+ String walletName = "",
10
+}) {
11
var _key = "";
12
13
switch (key) {
@@ -19,9 +23,15 @@ String generateStoreKeyFor({SecretStoreKey key, String walletName = "",}) {
23
}
24
break;
25
26
+ case SecretStoreKey.backupPassword:
27
+ {
28
+ _key = backupPassword;
29
+ }
30
+ break;
31
+
32
default:
33
{}
34
}
35
36
return _key;
27
-}
\ No newline at end of file
37
+}
lib/main.dart
+2
@@ -1,3 +1,5 @@
1
+import 'package:cake_wallet/core/backup.dart';
2
+import 'package:cake_wallet/src/screens/backup/backup_page.dart';
3
import 'package:flutter/material.dart';
4
import 'package:flutter/services.dart';
5
import 'package:hive/hive.dart';
lib/router.dart
+20
-8
@@ -1,6 +1,9 @@
1
import 'package:cake_wallet/entities/contact_record.dart';
2
import 'package:cake_wallet/entities/transaction_description.dart';
3
+import 'package:cake_wallet/src/screens/backup/backup_page.dart';
4
+import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
5
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
6
+import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart';
7
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
8
import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
9
import 'package:cake_wallet/store/settings_store.dart';
@@ -65,15 +68,13 @@ Route<dynamic> createRoute(RouteSettings settings) {
68
builder: (_) => getIt.get<SetupPinCodePage>(param1:
69
(PinCodeState<PinCodeWidget> context, dynamic _) async {
70
try {
68
- context.changeProcessText(
69
- S.current.creating_new_wallet);
71
+ context.changeProcessText(S.current.creating_new_wallet);
72
final newWalletVM =
73
getIt.get<WalletNewVM>(param1: WalletType.monero);
74
await newWalletVM.create(
75
options: 'English'); // FIXME: Unnamed constant
76
context.hideProgressText();
75
- await Navigator.of(context.context)
76
- .pushNamed(Routes.preSeed);
77
+ await Navigator.of(context.context).pushNamed(Routes.preSeed);
78
} catch (e) {
79
context.changeProcessText('Error: ${e.toString()}');
80
}
@@ -115,7 +116,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
116
case Routes.restoreOptions:
117
final type = settings.arguments as WalletType;
118
return CupertinoPageRoute<void>(
118
- builder: (_) => RestoreOptionsPage(type: type));
119
+ builder: (_) => getIt.get<RestoreOptionsPage>(param1: type));
120
121
case Routes.restoreWalletOptions:
122
final type = WalletType.monero; //settings.arguments as WalletType;
@@ -264,7 +265,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
265
case Routes.accountCreation:
266
return CupertinoPageRoute<String>(
267
builder: (_) => getIt.get<MoneroAccountEditOrCreatePage>(
267
- param1: settings.arguments as AccountListItem));
268
+ param1: settings.arguments as AccountListItem));
269
270
case Routes.addressBook:
271
return MaterialPageRoute<void>(
@@ -326,8 +327,19 @@ Route<dynamic> createRoute(RouteSettings settings) {
327
builder: (_) => getIt.get<LanguageListPage>());
328
329
case Routes.preSeed:
329
- return MaterialPageRoute<void>(
330
- builder: (_) => getIt.get<PreSeedPage>());
330
+ return MaterialPageRoute<void>(builder: (_) => getIt.get<PreSeedPage>());
331
+
332
+ case Routes.backup:
333
+ return CupertinoPageRoute<void>(
334
+ fullscreenDialog: true, builder: (_) => getIt.get<BackupPage>());
335
+
336
+ case Routes.editBackupPassword:
337
+ return CupertinoPageRoute<void>(
338
+ builder: (_) => getIt.get<EditBackupPasswordPage>());
339
+
340
+ case Routes.restoreFromBackup:
341
+ return CupertinoPageRoute<void>(
342
+ builder: (_) => getIt.get<RestoreFromBackupPage>());
343
344
default:
345
return MaterialPageRoute<void>(
lib/routes.dart
+3
@@ -48,4 +48,7 @@ class Routes {
48
static const restoreWalletType = '/restore_wallet_type';
49
static const restoreWallet = '/restore_wallet';
50
static const preSeed = '/pre_seed';
51
+ static const backup = '/backup';
52
+ static const editBackupPassword = '/edit_backup_passowrd';
53
+ static const restoreFromBackup = '/restore_from_backup';
54
}
\ No newline at end of file
lib/src/screens/backup/backup_page.dart
new
+93
@@ -0,0 +1,93 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:flutter/cupertino.dart';
3
+import 'package:flutter_mobx/flutter_mobx.dart';
4
+import 'package:esys_flutter_share/esys_flutter_share.dart';
5
+import 'package:cake_wallet/routes.dart';
6
+import 'package:cake_wallet/generated/i18n.dart';
7
+import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
8
+import 'package:cake_wallet/src/widgets/primary_button.dart';
9
+import 'package:cake_wallet/src/widgets/trail_button.dart';
10
+import 'package:cake_wallet/utils/show_pop_up.dart';
11
+import 'package:cake_wallet/view_model/backup_view_model.dart';
12
+import 'package:cake_wallet/core/execution_state.dart';
13
+import 'package:cake_wallet/src/screens/base_page.dart';
14
+
15
+class BackupPage extends BasePage {
16
+ BackupPage(this.backupViewModelBase);
17
+
18
+ final BackupViewModelBase backupViewModelBase;
19
+
20
+ @override
21
+ String get title => 'Backup';
22
+
23
+ @override
24
+ Widget trailing(BuildContext context) => TrailButton(
25
+ caption: S.of(context).edit,
26
+ onPressed: () =>
27
+ Navigator.of(context).pushNamed(Routes.editBackupPassword));
28
+
29
+ @override
30
+ Widget body(BuildContext context) {
31
+ return Stack(
32
+ fit: StackFit.expand,
33
+ children: [
34
+ Center(
35
+ child: Container(
36
+ padding: EdgeInsets.only(left: 20, right: 20),
37
+ height: 225,
38
+ child: Column(children: [
39
+ Text(
40
+ 'Backup password:',
41
+ style: TextStyle(fontSize: 30),
42
+ ),
43
+ Padding(
44
+ padding: EdgeInsets.only(top: 20, bottom: 10),
45
+ child: Observer(
46
+ builder: (_) => Text(
47
+ backupViewModelBase.backupPassword,
48
+ style: TextStyle(fontSize: 26),
49
+ ))),
50
+ Padding(
51
+ padding: EdgeInsets.all(20),
52
+ child: Text(
53
+ 'Please write down your Backup Password. Backup Password uses for import of backup files.',
54
+ style: TextStyle(fontSize: 14, color: Colors.grey),
55
+ textAlign: TextAlign.center,
56
+ ))
57
+ ]))),
58
+ Positioned(
59
+ child: Observer(
60
+ builder: (_) => LoadingPrimaryButton(
61
+ isLoading: backupViewModelBase.state is IsExecutingState,
62
+ onPressed: () => onExportBackup(context),
63
+ text: 'Export backup',
64
+ color: Theme.of(context).accentTextTheme.body2.color,
65
+ textColor: Colors.white)),
66
+ bottom: 30,
67
+ left: 20,
68
+ right: 20,
69
+ )
70
+ ],
71
+ );
72
+ }
73
+
74
+ void onExportBackup(BuildContext context) {
75
+ showPopUp<void>(
76
+ context: context,
77
+ builder: (dialogContext) {
78
+ return AlertWithTwoActions(
79
+ alertTitle: 'Export backup',
80
+ alertContent:
81
+ '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?',
82
+ rightButtonText: S.of(context).seed_alert_yes,
83
+ leftButtonText: S.of(context).seed_alert_back,
84
+ actionRightButton: () async {
85
+ Navigator.of(dialogContext).pop();
86
+ final backup = await backupViewModelBase.exportBackup();
87
+ await Share.file(
88
+ 'Backup file', backup.name, backup.content, 'text');
89
+ },
90
+ actionLeftButton: () => Navigator.of(dialogContext).pop());
91
+ });
92
+ }
93
+}
lib/src/screens/backup/edit_backup_password_page.dart
new
+73
@@ -0,0 +1,73 @@
1
+import 'package:flutter/material.dart';
2
+import 'package:flutter/cupertino.dart';
3
+import 'package:flutter_mobx/flutter_mobx.dart';
4
+import 'package:cake_wallet/generated/i18n.dart';
5
+import 'package:cake_wallet/utils/show_pop_up.dart';
6
+import 'package:cake_wallet/src/screens/base_page.dart';
7
+import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
8
+import 'package:cake_wallet/src/widgets/primary_button.dart';
9
+import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
10
+
11
+class EditBackupPasswordPage extends BasePage {
12
+ EditBackupPasswordPage(this.editBackupPasswordViewModel)
13
+ : textEditingController = TextEditingController() {
14
+ textEditingController.text = editBackupPasswordViewModel.backupPassword;
15
+ textEditingController.addListener(() => editBackupPasswordViewModel
16
+ .backupPassword = textEditingController.text);
17
+ }
18
+
19
+ final EditBackupPasswordViewModel editBackupPasswordViewModel;
20
+ final TextEditingController textEditingController;
21
+
22
+ @override
23
+ String get title => 'Edit Backup Password';
24
+
25
+ @override
26
+ Widget body(BuildContext context) {
27
+ return Padding(
28
+ padding: EdgeInsets.only(left: 20, right: 20),
29
+ child: Stack(
30
+ fit: StackFit.expand,
31
+ children: [
32
+ Center(
33
+ child: Observer(
34
+ builder: (_) => TextFormField(
35
+ autofocus: true,
36
+ enableSuggestions: false,
37
+ autocorrect: false,
38
+ keyboardType: TextInputType.visiblePassword,
39
+ controller: textEditingController,
40
+ style: TextStyle(fontSize: 26, color: Colors.black)))),
41
+ Positioned(
42
+ child: Observer(
43
+ builder: (_) => PrimaryButton(
44
+ onPressed: () => onSave(context),
45
+ text: 'Save',
46
+ color: Theme.of(context).accentTextTheme.body2.color,
47
+ textColor: Colors.white,
48
+ isDisabled: !editBackupPasswordViewModel.canSave)),
49
+ bottom: 30,
50
+ left: 0,
51
+ right: 0)
52
+ ],
53
+ ));
54
+ }
55
+
56
+ void onSave(BuildContext context) {
57
+ showPopUp<void>(
58
+ context: context,
59
+ builder: (dialogContext) {
60
+ return AlertWithTwoActions(
61
+ alertTitle: 'Save backup password',
62
+ alertContent:
63
+ '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 ?',
64
+ rightButtonText: S.of(context).ok,
65
+ leftButtonText: S.of(context).cancel,
66
+ actionRightButton: () async {
67
+ await editBackupPasswordViewModel.save();
68
+ Navigator.of(dialogContext)..pop()..pop();
69
+ },
70
+ actionLeftButton: () => Navigator.of(dialogContext).pop());
71
+ });
72
+ }
73
+}
lib/src/screens/restore/restore_from_backup_page.dart
new
+88
@@ -0,0 +1,88 @@
1
+import 'package:cake_wallet/generated/i18n.dart';
2
+import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
3
+import 'package:cake_wallet/utils/show_pop_up.dart';
4
+import 'package:flutter/material.dart';
5
+import 'package:flutter/src/widgets/framework.dart';
6
+import 'package:cake_wallet/src/widgets/primary_button.dart';
7
+import 'package:cake_wallet/view_model/restore_from_backup_view_model.dart';
8
+import 'package:cake_wallet/src/screens/base_page.dart';
9
+import 'package:file_picker/file_picker.dart';
10
+
11
+class RestoreFromBackupPage extends BasePage {
12
+ RestoreFromBackupPage(this.restoreFromBackupViewModel)
13
+ : textEditingController = TextEditingController();
14
+
15
+ final RestoreFromBackupViewModel restoreFromBackupViewModel;
16
+ final TextEditingController textEditingController;
17
+
18
+ @override
19
+ String get title => 'Restore from backup';
20
+
21
+ @override
22
+ Widget body(BuildContext context) {
23
+ return Container(
24
+ padding: EdgeInsets.only(bottom: 30, left: 25, right: 25),
25
+ child: Column(children: [
26
+ Expanded(
27
+ child: Container(
28
+ child: Center(
29
+ child: TextFormField(
30
+ obscureText: true,
31
+ enableSuggestions: false,
32
+ autocorrect: false,
33
+ decoration: InputDecoration(
34
+ hintText: 'Enter backup password here'),
35
+ keyboardType: TextInputType.visiblePassword,
36
+ controller: textEditingController,
37
+ style: TextStyle(fontSize: 26, color: Colors.black))),
38
+ ),
39
+ ),
40
+ Container(
41
+ child: Row(children: [
42
+ Expanded(
43
+ child: PrimaryButton(
44
+ onPressed: () => presentFilePicker(),
45
+ text: 'Select backup file',
46
+ color: Colors.grey,
47
+ textColor: Colors.white)),
48
+ SizedBox(width: 20),
49
+ Expanded(
50
+ child: PrimaryButton(
51
+ onPressed: () => onImportHandler(context),
52
+ text: 'Import',
53
+ color: Theme.of(context).accentTextTheme.body2.color,
54
+ textColor: Colors.white))
55
+ ])),
56
+ ]));
57
+ }
58
+
59
+ Future<void> presentFilePicker() async {
60
+ final result = await FilePicker.platform.pickFiles();
61
+
62
+ if (result?.files?.isEmpty ?? true) {
63
+ return;
64
+ }
65
+
66
+ restoreFromBackupViewModel.filePath = result.files.first.path;
67
+ }
68
+
69
+ Future<void> onImportHandler(BuildContext context) async {
70
+ if (textEditingController.text.isEmpty ||
71
+ (restoreFromBackupViewModel.filePath.isEmpty ?? true)) {
72
+ await showPopUp<void>(
73
+ context: context,
74
+ builder: (_) {
75
+ return AlertWithOneAction(
76
+ alertTitle: S.current.error,
77
+ alertContent:
78
+ 'Please select backup file and enter backup password.',
79
+ buttonText: S.of(context).ok,
80
+ buttonAction: () => Navigator.of(context).pop());
81
+ });
82
+
83
+ return;
84
+ }
85
+
86
+ await restoreFromBackupViewModel.import(textEditingController.text);
87
+ }
88
+}
lib/src/screens/restore/restore_options_page.dart
+2
-2
@@ -1,6 +1,5 @@
1
import 'package:cake_wallet/entities/wallet_type.dart';
2
import 'package:flutter/material.dart';
3
-import 'package:cake_wallet/palette.dart';
3
import 'package:cake_wallet/routes.dart';
4
import 'package:flutter/cupertino.dart';
5
import 'package:cake_wallet/src/screens/restore/widgets/restore_button.dart';
@@ -39,7 +38,8 @@ class RestoreOptionsPage extends BasePage {
38
Padding(
39
padding: EdgeInsets.only(top: 24),
40
child: RestoreButton(
42
- onPressed: () {},
41
+ onPressed: () => Navigator.pushNamed(
42
+ context, Routes.restoreFromBackup),
43
image: imageBackup,
44
title: S.of(context).restore_title_from_backup,
45
description: S.of(context).restore_description_from_backup
lib/src/widgets/trail_button.dart
+1
-1
@@ -21,7 +21,7 @@ class TrailButton extends StatelessWidget {
21
child: Text(
22
caption,
23
style: TextStyle(
24
- color: Theme.of(context).textTheme.subhead.decorationColor,
24
+ color: Theme.of(context).textTheme.display1.color,
25
fontWeight: FontWeight.w500,
26
fontSize: 14),
27
),
lib/store/secret_store.dart
new
+28
@@ -0,0 +1,28 @@
1
+import 'package:cake_wallet/entities/secret_store_key.dart';
2
+import 'package:flutter/foundation.dart';
3
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
4
+import 'package:mobx/mobx.dart';
5
+
6
+part 'secret_store.g.dart';
7
+
8
+class SecretStore = SecretStoreBase with _$SecretStore;
9
+
10
+abstract class SecretStoreBase with Store {
11
+ static Future<SecretStore> load(FlutterSecureStorage storage) async {
12
+ final secretStore = SecretStore();
13
+ final backupPasswordKey = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
14
+ final backupPassword = await storage.read(key: backupPasswordKey);
15
+ secretStore.write(key: backupPasswordKey, value: backupPassword);
16
+
17
+ return secretStore;
18
+ }
19
+
20
+ SecretStoreBase() : values = ObservableMap<String, String>();
21
+
22
+ ObservableMap values;
23
+
24
+ String read(String key) => values[key] as String;
25
+
26
+ String write({@required String key, @required String value}) =>
27
+ values[key] = value;
28
+}
lib/view_model/backup_view_model.dart
new
+68
@@ -0,0 +1,68 @@
1
+import 'package:cake_wallet/core/backup.dart';
2
+import 'package:cake_wallet/core/execution_state.dart';
3
+import 'package:cake_wallet/entities/secret_store_key.dart';
4
+import 'package:cake_wallet/store/secret_store.dart';
5
+import 'package:flutter/foundation.dart';
6
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
7
+import 'package:mobx/mobx.dart';
8
+
9
+part 'backup_view_model.g.dart';
10
+
11
+class BackupExportFile {
12
+ BackupExportFile(this.content, {@required this.name});
13
+
14
+ final String name;
15
+ final List<int> content;
16
+}
17
+
18
+class BackupViewModel = BackupViewModelBase with _$BackupViewModel;
19
+
20
+abstract class BackupViewModelBase with Store {
21
+ BackupViewModelBase(this.secureStorage, this.secretStore, this.backupService)
22
+ : isBackupPasswordVisible = false {
23
+ state = InitialExecutionState();
24
+ final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
25
+ secretStore.values.observe((change) {
26
+ if (change.key == key) {
27
+ backupPassword = secretStore.read(key);
28
+ }
29
+ }, fireImmediately: true);
30
+ }
31
+
32
+ final FlutterSecureStorage secureStorage;
33
+ final SecretStore secretStore;
34
+ final BackupService backupService;
35
+
36
+ @observable
37
+ ExecutionState state;
38
+
39
+ @observable
40
+ bool isBackupPasswordVisible;
41
+
42
+ @observable
43
+ String backupPassword;
44
+
45
+ @action
46
+ Future<void> init() async {
47
+ final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
48
+ backupPassword = await secureStorage.read(key: key);
49
+ }
50
+
51
+ @action
52
+ Future<BackupExportFile> exportBackup() async {
53
+ try {
54
+ state = IsExecutingState();
55
+ final backupContent = await backupService.exportBackup('', nonce: '');
56
+ state = ExecutedSuccessfullyState();
57
+
58
+ return BackupExportFile(backupContent.toList(),
59
+ name: 'backup_${DateTime.now().toString()}.zip');
60
+ } catch (e) {
61
+ print(e.toString());
62
+ state = FailureState(e.toString());
63
+ }
64
+ }
65
+
66
+ @action
67
+ void showMasterPassword() => isBackupPasswordVisible = true;
68
+}
lib/view_model/edit_backup_password_view_model.dart
new
+44
@@ -0,0 +1,44 @@
1
+import 'package:mobx/mobx.dart';
2
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
3
+import 'package:cake_wallet/entities/secret_store_key.dart';
4
+import 'package:cake_wallet/store/secret_store.dart';
5
+
6
+part 'edit_backup_password_view_model.g.dart';
7
+
8
+class EditBackupPasswordViewModel = EditBackupPasswordViewModelBase
9
+ with _$EditBackupPasswordViewModel;
10
+
11
+abstract class EditBackupPasswordViewModelBase with Store {
12
+ EditBackupPasswordViewModelBase(this.secureStorage, this.secretStore) {
13
+ final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
14
+ backupPassword = secretStore.read(key);
15
+ }
16
+
17
+ final FlutterSecureStorage secureStorage;
18
+ final SecretStore secretStore;
19
+
20
+ @observable
21
+ String backupPassword;
22
+
23
+ @computed
24
+ bool get canSave {
25
+ return !(_originalPassword == backupPassword);
26
+ }
27
+
28
+ String _originalPassword;
29
+
30
+ @action
31
+ Future<void> init() async {
32
+ final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
33
+ final password = await secureStorage.read(key: key);
34
+ _originalPassword = password;
35
+ backupPassword = password;
36
+ }
37
+
38
+ @action
39
+ Future<void> save() async {
40
+ final key = generateStoreKeyFor(key: SecretStoreKey.backupPassword);
41
+ await secureStorage.write(key: key, value: backupPassword);
42
+ secretStore.write(key: key, value: backupPassword);
43
+ }
44
+}
lib/view_model/restore_from_backup_view_model.dart
new
+29
@@ -0,0 +1,29 @@
1
+import 'dart:io';
2
+
3
+import 'package:cake_wallet/core/backup.dart';
4
+import 'package:mobx/mobx.dart';
5
+
6
+part 'restore_from_backup_view_model.g.dart';
7
+
8
+class RestoreFromBackupViewModel = RestoreFromBackupViewModelBase with _$RestoreFromBackupViewModel;
9
+
10
+abstract class RestoreFromBackupViewModelBase with Store {
11
+ RestoreFromBackupViewModelBase(this.backupService);
12
+
13
+ @observable
14
+ String filePath;
15
+
16
+ final BackupService backupService;
17
+
18
+ Future<void> import(String password) async {
19
+ if (filePath?.isEmpty ?? true) {
20
+ // FIXME: throw exception;
21
+ return;
22
+ }
23
+
24
+ final file = File(filePath);
25
+ final data = await file.readAsBytes();
26
+
27
+ await backupService.importBackup(data, password, nonce: null);
28
+ }
29
+}
\ No newline at end of file
pubspec.lock
+15
-1
@@ -16,7 +16,7 @@ packages:
16
source: hosted
17
version: "0.40.6"
18
archive:
19
- dependency: transitive
19
+ dependency: "direct main"
20
description:
21
name: archive
22
url: "https://pub.dartlang.org"
@@ -253,6 +253,13 @@ packages:
253
url: "https://pub.dartlang.org"
254
source: hosted
255
version: "2.1.5"
256
+ cryptography:
257
+ dependency: "direct main"
258
+ description:
259
+ name: cryptography
260
+ url: "https://pub.dartlang.org"
261
+ source: hosted
262
+ version: "1.4.1"
263
cupertino_icons:
264
dependency: "direct main"
265
description:
@@ -344,6 +351,13 @@ packages:
351
url: "https://pub.dartlang.org"
352
source: hosted
353
version: "5.2.1"
354
+ file_picker:
355
+ dependency: "direct main"
356
+ description:
357
+ name: file_picker
358
+ url: "https://pub.dartlang.org"
359
+ source: hosted
360
+ version: "2.1.4"
361
fixnum:
362
dependency: transitive
363
description:
pubspec.yaml
+3
@@ -67,6 +67,9 @@ dependencies:
67
connectivity: ^0.4.9+2
68
keyboard_actions: ^3.3.0
69
flushbar: ^1.10.4
70
+ archive: ^2.0.13
71
+ cryptography: ^1.4.0
72
+ file_picker: ^2.1.4
73
74
dev_dependencies:
75
flutter_test: