From 2aeed7906823898cd58763a69decf1cc998fc364 Mon Sep 17 00:00:00 2001 From: theskyblockman Date: Tue, 11 Jul 2023 16:03:46 +0200 Subject: [PATCH 1/4] Create PRIVACY.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 📝 Added privacy notice --- PRIVACY.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 PRIVACY.md diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 0000000..8854270 --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1 @@ +The application, Life Chest does not collect, use or receive any user data. The user's privacy is our priority. All possible data we harvest is how many people have downloaded our application. From 2b117da9d2b940f0b6653b25f71cfb38acbe1a65 Mon Sep 17 00:00:00 2001 From: theskyblockman Date: Thu, 13 Jul 2023 23:44:31 +0200 Subject: [PATCH 2/4] :white_check_mark: Fixed the seeking issues related to reading portions of encrypted bytes. --- lib/file_explorer/file_explorer.dart | 126 ++++++------------ .../single_threaded_recovery.dart | 15 ++- lib/file_viewers/audio.dart | 4 +- lib/generated/intl/messages_en.dart | 13 +- lib/generated/intl/messages_fr.dart | 14 +- lib/generated/l10n.dart | 8 +- lib/l10n/intl_en.arb | 9 +- lib/l10n/intl_fr.arb | 2 +- lib/main.dart | 1 + lib/vault.dart | 17 ++- pubspec.lock | 24 ++-- pubspec.yaml | 3 +- test/assets/file_to_clone | 7 + test/file_encrypt_and_decrypt_test.dart | 62 +++++++++ test/test_file_to_encrypt_backup.txt | 7 + 15 files changed, 175 insertions(+), 137 deletions(-) create mode 100644 test/assets/file_to_clone create mode 100644 test/file_encrypt_and_decrypt_test.dart create mode 100644 test/test_file_to_encrypt_backup.txt diff --git a/lib/file_explorer/file_explorer.dart b/lib/file_explorer/file_explorer.dart index 4d19c1e..e1d6458 100644 --- a/lib/file_explorer/file_explorer.dart +++ b/lib/file_explorer/file_explorer.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'dart:isolate'; import 'package:cryptography/cryptography.dart'; +import 'package:document_file_save_plus/document_file_save_plus.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -18,6 +19,8 @@ import 'package:life_chest/vault.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; +typedef FileExportArgs = (String thumbnailFilePath, List encryptionKey, Map data, List fileContent, String unlockMechanismType, Map additionalUnlockData); + /// The file reader, this enable the user to see file and to browse between them while keeping a cache of them class FileReader extends StatefulWidget { final List Function() thumbnails; @@ -257,32 +260,11 @@ class FileExplorerState extends State { bool isGridView = true; static Future exportEncryptedThumbnails( - List< - ( - String thumbnailFilePath, - List encryptionKey, - Map data, - List fileContent, - String unlockMechanismType, - Map additionalUnlockData, - String saveLocationPath - )> - message) async { - for (( - String thumbnailFilePath, - List encryptionKey, - Map data, - List fileContent, - String unlockMechanismType, - Map additionalUnlockData, - String saveLocationPath - ) data in message) { + (SendPort, List) message) async { + for (FileExportArgs data in message.$2) { List exportedFile = await FileExporter.exportFile(basename(data.$1), SecretKey(data.$2), data.$3, data.$4, data.$5, data.$6); - File fileToSaveTo = - File(join(data.$7, 'Life_Chest_${md5RandomFileName()}.lcef')); - fileToSaveTo.createSync(); - fileToSaveTo.writeAsBytesSync(exportedFile); + message.$1.send(exportedFile); } } @@ -446,83 +428,59 @@ class FileExplorerState extends State { filesToExport.add(thumbnail); } } - String validDirectoryName = - S.of(context).lifeChestBulkSave; - Directory? downloadDirectory; - if (Platform.isIOS) { - downloadDirectory = - await getDownloadsDirectory(); - - if (downloadDirectory == null) return; - } else { - downloadDirectory = Directory( - '/storage/emulated/0/Download'); - // Put file in global download folder, if for an unknown reason it didn't exist, we fallback - if (!await downloadDirectory.exists()) { - downloadDirectory = - await getExternalStorageDirectory(); - } - } - - Directory saveLocation = downloadDirectory!; - - if (filesToExport.length > 1) { - String currentSuffix = ''; - int currentDirID = 2; - List dirFiles = - downloadDirectory.listSync(); - while (dirFiles.any((element) => - basename(element.path) == - validDirectoryName + currentSuffix)) { - currentSuffix = ' ($currentDirID)'; - currentDirID++; - } - validDirectoryName = - validDirectoryName + currentSuffix; - saveLocation = Directory(join( - saveLocation.path, validDirectoryName)); - saveLocation.createSync(); - } + setState(() { + loaderTarget = filesToExport.length; + loaderCurrentLoad = 0; + }); List encryptionKey = await widget .vault.encryptionKey! .extractBytes(); - Isolate.spawn( - exportEncryptedThumbnails, - List< - ( - String thumbnailFilePath, - List encryptionKey, - Map data, - List fileContent, - String unlockMechanismType, - Map additionalUnlockData, - String saveLocationPath - )>.generate(filesToExport.length, + final receivePort = ReceivePort(); + + Isolate.spawn(exportEncryptedThumbnails, + (receivePort.sendPort, List.generate(filesToExport.length, (index) { FileThumbnail fileToExport = filesToExport[index]; - return ( fileToExport.localPath, encryptionKey, fileToExport.data, fileToExport.file.readAsBytesSync(), widget.vault.unlockMechanismType, - widget.vault.additionalUnlockData, - saveLocation.path + widget.vault.additionalUnlockData ); - })).then((value) { + }))); + + List decryptedFiles = []; + + await for(List decryptedFile in receivePort) { + decryptedFiles.add(Uint8List.fromList(decryptedFile)); + setState(() { + loaderCurrentLoad = decryptedFiles.length; + }); + if(loaderTarget == loaderCurrentLoad) break; + } + + setState(() { + loaderTarget = null; + loaderCurrentLoad = null; + }); + + DocumentFileSavePlus().saveMultipleFiles( + dataList: decryptedFiles, + fileNameList: [for (FileThumbnail thumbnail in filesToExport) setExtension(basenameWithoutExtension(thumbnail.data['name']), '.lcef')], + mimeTypeList: List.filled(filesToExport.length, 'plain') + ).then((value) { if (context.mounted) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar( - content: Text(S - .of(context) - .savedToFolder(basename( - saveLocation.path))))); + content: Text(S + .of(context) + .savedToFolder))); } }); } @@ -591,8 +549,7 @@ class FileExplorerState extends State { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text(S.of(context).savedToFolder( - basename(saveLocation.path))))); + content: Text(S.of(context).savedToFolder))); } }, child: Text(S.of(context).exportAsCleartext)), @@ -986,7 +943,6 @@ class FileExplorerState extends State { return; } } - /// A helper method to know if an entity is 1 level deeper in the tree than a local path so that whe can know if the UI should draw it static bool shouldThumbnailBeShown( String fileLocalPath, String currentLocalPath) { diff --git a/lib/file_recovery/single_threaded_recovery.dart b/lib/file_recovery/single_threaded_recovery.dart index 5175024..0015748 100644 --- a/lib/file_recovery/single_threaded_recovery.dart +++ b/lib/file_recovery/single_threaded_recovery.dart @@ -33,16 +33,17 @@ class SingleThreadedRecovery { } /// Selects the portion to decrypt and decrypts it (maybe the file reading part could be worked on) - static Future>> loadAndDecryptPartialFile( + static Stream> loadAndDecryptPartialFile( SecretKey encryptionKey, File fileToRead, int startByte, - int endByte) async { - return VaultsManager.cipher.decryptStream( - fileToRead.openRead(startByte, endByte), - mac: Mac.empty, - secretKey: encryptionKey, - nonce: Uint8List(VaultsManager.cipher.nonceLength)); + int endByte) { + int currentLength = startByte; + return fileToRead.openRead(startByte, endByte).asyncMap((event) async { + currentLength += event.length; + return await VaultsManager.cipher + .decrypt(SecretBox(event, nonce: Uint8List(VaultsManager.cipher.nonceLength), mac: Mac.empty), secretKey: encryptionKey, keyStreamIndex: currentLength - event.length); + }); } /// Encrypts the file in a specific location with the [encryptionKey] in the ChaCha20 algorithm diff --git a/lib/file_viewers/audio.dart b/lib/file_viewers/audio.dart index a0c37b9..18b76e7 100644 --- a/lib/file_viewers/audio.dart +++ b/lib/file_viewers/audio.dart @@ -348,10 +348,10 @@ class EncryptedAudioSource extends StreamAudioSource { return StreamAudioResponse( sourceLength: fileByteLength, contentLength: (end ?? fileByteLength) - (start ?? 0), - offset: start, + offset: start ?? 0, stream: start == null && end == null ? SingleThreadedRecovery.loadAndDecryptFile(encryptionKey, fileToRead) : - await SingleThreadedRecovery.loadAndDecryptPartialFile(encryptionKey, fileToRead, start ?? 0, end ?? fileByteLength), + SingleThreadedRecovery.loadAndDecryptPartialFile(encryptionKey, fileToRead, start ?? 0, end ?? fileByteLength), contentType: mimeType, rangeRequestsSupported: true); } diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index bed49e5..ca55d8b 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -25,11 +25,9 @@ class MessageLookup extends MessageLookupByLibrary { static String m1(groupID) => "Group n.${groupID}"; - static String m2(path) => "Saved the file(s) to ${path}"; + static String m2(count) => "${count} selected"; - static String m3(count) => "${count} selected"; - - static String m4(unlockName) => + static String m3(unlockName) => "This group can be unlocked with: ${unlockName}."; final messages = _notInlinedMessages(_notInlinedMessages); @@ -141,12 +139,13 @@ class MessageLookup extends MessageLookupByLibrary { "pleaseUseBiometrics": MessageLookupByLibrary.simpleMessage( "Please use your biometrics to unlock the chest"), "rename": MessageLookupByLibrary.simpleMessage("Rename"), - "savedToFolder": m2, + "savedToFolder": MessageLookupByLibrary.simpleMessage( + "We successfully saved the file(s)"), "scheme": MessageLookupByLibrary.simpleMessage("Pattern"), "selectAll": MessageLookupByLibrary.simpleMessage("Select all"), - "selected": m3, + "selected": m2, "sortBy": MessageLookupByLibrary.simpleMessage("Sort by..."), - "unlockAbleBy": m4, + "unlockAbleBy": m3, "unlockChest": MessageLookupByLibrary.simpleMessage("Unlock the chest"), "unlockFile": MessageLookupByLibrary.simpleMessage("Unlock the file"), "unlockWizard": MessageLookupByLibrary.simpleMessage("Unlock wizard"), diff --git a/lib/generated/intl/messages_fr.dart b/lib/generated/intl/messages_fr.dart index 48afdd3..8d485e3 100644 --- a/lib/generated/intl/messages_fr.dart +++ b/lib/generated/intl/messages_fr.dart @@ -25,13 +25,10 @@ class MessageLookup extends MessageLookupByLibrary { static String m1(groupID) => "Groupe n°${groupID}"; - static String m2(path) => - "Le(s) fichier(s) a/ont Ă©tĂ© sauvegardĂ©(s) dans ${path}"; - - static String m3(count) => + static String m2(count) => "${count} ${Intl.plural(count, one: 'fichier sĂ©lectionnĂ©', other: 'fichiers sĂ©lectionnĂ©s')}"; - static String m4(unlockName) => + static String m3(unlockName) => "Ce groupe peut ĂȘtre dĂ©bloquer grĂące Ă  : ${unlockName}."; final messages = _notInlinedMessages(_notInlinedMessages); @@ -149,12 +146,13 @@ class MessageLookup extends MessageLookupByLibrary { "pleaseUseBiometrics": MessageLookupByLibrary.simpleMessage( "Veuillez utiliser votre empreinte digitale pour dĂ©verrouiller le coffre"), "rename": MessageLookupByLibrary.simpleMessage("Renommer"), - "savedToFolder": m2, + "savedToFolder": MessageLookupByLibrary.simpleMessage( + "Le(s) fichier(s) a/ont Ă©tĂ© sauvegardĂ©(s)."), "scheme": MessageLookupByLibrary.simpleMessage("ModĂšle"), "selectAll": MessageLookupByLibrary.simpleMessage("Tout sĂ©lectionner"), - "selected": m3, + "selected": m2, "sortBy": MessageLookupByLibrary.simpleMessage("Trier par..."), - "unlockAbleBy": m4, + "unlockAbleBy": m3, "unlockChest": MessageLookupByLibrary.simpleMessage( "Veuillez dĂ©verrouiller le coffre"), "unlockFile": diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index abbe8d6..08aecaa 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -830,13 +830,13 @@ class S { ); } - /// `Saved the file(s) to {path}` - String savedToFolder(String path) { + /// `We successfully saved the file(s)` + String get savedToFolder { return Intl.message( - 'Saved the file(s) to $path', + 'We successfully saved the file(s)', name: 'savedToFolder', desc: '', - args: [path], + args: [], ); } diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 8b47caf..2e0d69f 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -92,14 +92,7 @@ "unlockWizard": "Unlock wizard", "ignore": "Ignore", "lifeChestBulkSave": "Life Chest bulk file export", - "@savedToFolder": { - "placeholders": { - "path": { - "type": "String" - } - } - }, - "savedToFolder": "Saved the file(s) to {path}", + "savedToFolder": "We successfully saved the file(s)", "import": "Import", "@group": { "placeholders": { diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index f9a4eb5..72cae49 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -78,7 +78,7 @@ "exportAsCleartext": "Exporter en tant que fichier lisible (fichier non chiffrĂ©)", "exportAsEncrypted": "Exporter en tant que fichier chiffrĂ©", "lifeChestBulkSave": "Sauvegarde de fichiers en masse", - "savedToFolder": "Le(s) fichier(s) a/ont Ă©tĂ© sauvegardĂ©(s) dans {path}", + "savedToFolder": "Le(s) fichier(s) a/ont Ă©tĂ© sauvegardĂ©(s).", "import": "Importer", "group": "Groupe n°{groupID}", "unlockAbleBy": "Ce groupe peut ĂȘtre dĂ©bloquer grĂące Ă  : {unlockName}.", diff --git a/lib/main.dart b/lib/main.dart index 084dfc4..da998d9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -26,6 +26,7 @@ void main() async { Directory appDocuments = await getApplicationDocumentsDirectory(); VaultsManager.appFolder = appDocuments.path; VaultsManager.mainConfigFile = File('${VaultsManager.appFolder}/.config'); + VaultsManager.packageInfo = await PackageInfo.fromPlatform(); bool firstLaunch = !VaultsManager.mainConfigFile.existsSync() || kDebugMode; if (firstLaunch) { VaultsManager.mainConfigFile.createSync(); diff --git a/lib/vault.dart b/lib/vault.dart index b4fef14..bd68988 100644 --- a/lib/vault.dart +++ b/lib/vault.dart @@ -7,6 +7,7 @@ import 'package:cryptography/cryptography.dart'; import 'package:flutter/services.dart'; import 'package:life_chest/file_explorer/file_explorer.dart'; import 'package:life_chest/file_explorer/file_sort_methods.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'package:path/path.dart' as p; class PermissionError extends Error { @@ -23,6 +24,7 @@ class VaultsManager { static List storedVaults = []; static late final String appFolder; static late final File mainConfigFile; + static late final PackageInfo packageInfo; static final cipher = Chacha20(macAlgorithm: MacAlgorithm.empty); static bool shouldUpdateVaultList = false; static Map globalAdditionalUnlockData = {}; @@ -207,7 +209,7 @@ class Vault { required this.name, required this.securityLevel, required this.unlockMechanismType, - this.encryptionKey}); + this.encryptionKey}) : lastVersionCode = int.parse(VaultsManager.packageInfo.buildNumber); Vault.fromJson(Map storedData) { locked = storedData['locked']; @@ -219,6 +221,13 @@ class Vault { securityLevel = storedData['security_level']; unlockMechanismType = storedData['unlock_mechanism_type'] ?? 'password'; additionalUnlockData = storedData['additional_unlock_data'] ?? {}; + if(storedData.containsKey('last_version_code')) { + lastVersionCode = storedData['last_version_code']; + } else { + PackageInfo.fromPlatform().then((value) { + lastVersionCode = int.parse(value.buildNumber); + }); + } } Map toJson() { @@ -230,7 +239,8 @@ class Vault { 'name': name, 'security_level': securityLevel, 'unlock_mechanism_type': unlockMechanismType, - 'additional_unlock_data': additionalUnlockData + 'additional_unlock_data': additionalUnlockData, + 'last_version_code': lastVersionCode, }; } @@ -264,4 +274,7 @@ class Vault { /// Any data needed to generate a key to unlock the vault. late Map additionalUnlockData; + + /// The version code of the app that the chest is currently used, this could bw used to upgrade it to a new security standard. + late int lastVersionCode; } diff --git a/pubspec.lock b/pubspec.lock index 2c2a194..ce5ac5e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -202,6 +202,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" + document_file_save_plus: + dependency: "direct main" + description: + name: document_file_save_plus + sha256: ff05c6a3b072377566e8e92666db38eb277786f90c0ac267ea47dc22725c1df3 + url: "https://pub.dev" + source: hosted + version: "2.0.0" dynamic_color: dependency: "direct main" description: @@ -267,10 +275,10 @@ packages: dependency: transitive description: name: flutter_cache_manager - sha256: "32cd900555219333326a2d0653aaaf8671264c29befa65bbd9856d204a4c9fb3" + sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "3.3.1" flutter_launcher_icons: dependency: "direct dev" description: @@ -514,10 +522,10 @@ packages: dependency: transitive description: name: local_auth_windows - sha256: "19323b75ab781d5362dbb15dcb7e0916d2431c7a6dbdda016ec9708689877f73" + sha256: "5af808e108c445d0cf702a8c5f8242f1363b7970320334f82e6e1e8ad0b0d7d4" url: "https://pub.dev" source: hosted - version: "1.0.8" + version: "1.0.9" logging: dependency: transitive description: @@ -663,14 +671,6 @@ packages: url: "https://github.com/theskyblockman/pattern_lock.git" source: git version: "3.0.1" - pedantic: - dependency: transitive - description: - name: pedantic - sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602" - url: "https://pub.dev" - source: hosted - version: "1.11.1" petitparser: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 39b656a..2f3f396 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: A safe container for your data publish_to: 'none' homepage: https://github.com/theskyblockman/life-chest -version: 1.2.1+1 +version: 1.2.1+10201 environment: sdk: '>=3.0.0 <4.0.0' @@ -37,6 +37,7 @@ dependencies: git: https://github.com/tintran-dev/betterplayer.git flutter_pdfview: ^1.3.1 package_info_plus: ^3.1.2 + document_file_save_plus: ^2.0.0 dev_dependencies: flutter_test: diff --git a/test/assets/file_to_clone b/test/assets/file_to_clone new file mode 100644 index 0000000..40ce9b2 --- /dev/null +++ b/test/assets/file_to_clone @@ -0,0 +1,7 @@ +Copyright 2023 Haroun El Omri + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/test/file_encrypt_and_decrypt_test.dart b/test/file_encrypt_and_decrypt_test.dart new file mode 100644 index 0000000..dfbb7d0 --- /dev/null +++ b/test/file_encrypt_and_decrypt_test.dart @@ -0,0 +1,62 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:math'; + +import 'package:cryptography/cryptography.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:life_chest/file_recovery/single_threaded_recovery.dart'; +import 'package:path/path.dart'; + +void main() async { + (String, Map)? savedData; + SecretKey secretKey = SecretKey('This is a secret key to test fil' + .codeUnits); // This is a secret key to test file encryption/decryption + test('Encrypt a file', () async { + expect(File('./test/assets/file_to_clone').existsSync(), isTrue, reason: 'file_to_clone does not exists in the assets folder in the tests'); + + File fileToClone = File('./test/assets/file_to_clone'); + File clonedFile = File('./test/assets/cloned_file'); + + if(clonedFile.existsSync()) clonedFile.deleteSync(); + clonedFile.createSync(); + + clonedFile.writeAsBytesSync(fileToClone.readAsBytesSync()); + + savedData = await SingleThreadedRecovery.saveFile(secretKey, './test/assets/', '', '/', createdFile: clonedFile); + + + }); + + test('Decrypts partially a file', () async { + if(savedData == null) { + Directory assetsDirectory = Directory('./test/assets/'); + expect(assetsDirectory.existsSync() && assetsDirectory.listSync().isNotEmpty, isTrue, reason: 'The first test hasn\'t been ran once'); + + savedData = (basename(assetsDirectory.listSync().first.path), {}); + } + + + File savedFile = File(join('test', 'assets', savedData!.$1)); + List fullFileDecryption = await SingleThreadedRecovery.loadAndDecryptFullFile(secretKey, savedFile); + List partialFileDecryption = List.empty(growable: true); + + Completer completer = Completer(); + + int fileLength = savedFile.lengthSync(); + int fileStart = Random().nextInt(fileLength); + fileStart = fileStart == 0 ? 1 : fileStart; + SingleThreadedRecovery.loadAndDecryptPartialFile(secretKey, savedFile, fileStart, savedFile.lengthSync()).listen((event) { + partialFileDecryption.addAll(event); + }, onDone: () => completer.complete()); + + await completer.future; + + for(int i = 0; i < fileLength; i++) { + print('$i : ${fullFileDecryption.elementAtOrNull(i) ?? 'NOT FOUND'} : ${i - fileStart < 0 ? 'NOT FOUND' : partialFileDecryption.elementAtOrNull(i - fileStart) ?? 'NOT FOUND'}'); + } + print('Full length: ${fullFileDecryption.length} Partial length: ${partialFileDecryption.length} File Start: $fileStart}'); + + expect(listEquals(fullFileDecryption.sublist(fileStart), partialFileDecryption), isTrue); + }); +} \ No newline at end of file diff --git a/test/test_file_to_encrypt_backup.txt b/test/test_file_to_encrypt_backup.txt new file mode 100644 index 0000000..40ce9b2 --- /dev/null +++ b/test/test_file_to_encrypt_backup.txt @@ -0,0 +1,7 @@ +Copyright 2023 Haroun El Omri + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file From 35c21da5e0433e838f6642f5492b03c74a7cc4ee Mon Sep 17 00:00:00 2001 From: theskyblockman Date: Fri, 14 Jul 2023 12:45:12 +0200 Subject: [PATCH 3/4] :bug: Fixed some crashes with biometrics on unsupported systems. Improved the Aspect Ratio in the single_threaded_recovery.dart --- .../single_threaded_recovery.dart | 17 +++++++++-- lib/file_viewers/video.dart | 11 ++++--- .../biometrics_unlock_mechanism.dart | 4 +-- pubspec.lock | 30 ++++++++++++++----- pubspec.yaml | 3 +- 5 files changed, 47 insertions(+), 18 deletions(-) diff --git a/lib/file_recovery/single_threaded_recovery.dart b/lib/file_recovery/single_threaded_recovery.dart index 0015748..e6d734c 100644 --- a/lib/file_recovery/single_threaded_recovery.dart +++ b/lib/file_recovery/single_threaded_recovery.dart @@ -4,6 +4,8 @@ import 'dart:io'; import 'dart:typed_data'; import 'package:cryptography/cryptography.dart'; +import 'package:ffmpeg_kit_flutter/ffprobe_kit.dart'; +import 'package:ffmpeg_kit_flutter/media_information_session.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/services.dart'; import 'package:flutter_media_metadata/flutter_media_metadata.dart'; @@ -100,8 +102,10 @@ class SingleThreadedRecovery { ? relative(createdFile.path, from: rootFolderPath) : relative(importedFile!.$1['name'], from: rootFolderPath)); Map finalData = {'name': finalName, 'type': type}; - if (FileThumbnailsPlaceholder.getPlaceholderFromFileName( - [finalData])[finalName] == + + FileThumbnailsPlaceholder? placeholder = FileThumbnailsPlaceholder.getPlaceholderFromFileName( + [finalData])[finalName]; + if (placeholder == FileThumbnailsPlaceholder.audio) { if (createdFile != null) { Metadata foundData = await MetadataRetriever.fromFile(createdFile); @@ -114,6 +118,15 @@ class SingleThreadedRecovery { } else { finalData['audioData'] = importedFile!.$1['audioData']; } + } else if (placeholder == FileThumbnailsPlaceholder.videos) { + if (createdFile != null) { + MediaInformationSession result = await FFprobeKit.getMediaInformation(createdFile.absolute.path); + + finalData['videoData'] = result.getMediaInformation()!.getAllProperties(); + + } else { + finalData['videoData'] = importedFile!.$1.containsKey('videoData') ? importedFile.$1['videoData'] : {}; + } } try { diff --git a/lib/file_viewers/video.dart b/lib/file_viewers/video.dart index 8a87492..56643e8 100644 --- a/lib/file_viewers/video.dart +++ b/lib/file_viewers/video.dart @@ -43,7 +43,9 @@ class VideoViewer extends FileViewer { autoPlay: false, looping: true, autoDetectFullscreenDeviceOrientation: true, - controlsConfiguration: BetterPlayerControlsConfiguration.white(), + controlsConfiguration: const BetterPlayerControlsConfiguration(), + aspectRatio: fileData['videoData']['streams'][0]['width'] / fileData['videoData']['streams'][0]['height'], + fit: BoxFit.contain, translations: [ BetterPlayerTranslations(), BetterPlayerTranslations( @@ -54,10 +56,10 @@ class VideoViewer extends FileViewer { generalRetry: 'RĂ©essayer', playlistLoadingNextVideo: "Chargement de la prochaine vidĂ©o", controlsLive: "DIRECT", - controlsNextVideoIn: "Prochaine vidĂ©o dans ", + controlsNextVideoIn: "Prochaine vidĂ©o dans", overflowMenuPlaybackSpeed: "Vitesse de lecture", overflowMenuSubtitles: "Sous-titres", - overflowMenuQuality: "QualitĂ©e", + overflowMenuQuality: "QualitĂ©", overflowMenuAudioTracks: "Audio", qualityAuto: "Auto" ) @@ -68,9 +70,6 @@ class VideoViewer extends FileViewer { const BetterPlayerPlaylistConfiguration( loopVideos: true, initialStartIndex: 0)); - controller!.setOverriddenAspectRatio( - controller!.videoPlayerController!.value.aspectRatio); - return true; } diff --git a/lib/unlock_mechanism/biometrics_unlock_mechanism.dart b/lib/unlock_mechanism/biometrics_unlock_mechanism.dart index 1bbf4c5..4ac68da 100644 --- a/lib/unlock_mechanism/biometrics_unlock_mechanism.dart +++ b/lib/unlock_mechanism/biometrics_unlock_mechanism.dart @@ -89,14 +89,14 @@ class BiometricsUnlockMechanism extends UnlockMechanism { @override Widget keyCreationBuild(BuildContext context, VaultPolicy policy) { - return ListTile(leading: const Icon(Icons.warning_amber, color: Colors.orange), title: Text(S.of(context).biometricsAreLocal, style: const TextStyle(color: Colors.orange),)); + return ListTile(leading: const Icon(Icons.warning_amber, color: Colors.orange), title: Text(S.of(context).biometricsAreLocal, style: const TextStyle(color: Colors.orange))); } @override Future isAvailable() async { return await LocalAuthentication().canCheckBiometrics && (await LocalAuthentication().getAvailableBiometrics()) - .contains(BiometricType.strong); + .contains(BiometricType.strong) && await LocalAuthentication().isDeviceSupported(); } @override diff --git a/pubspec.lock b/pubspec.lock index ce5ac5e..7d1fb56 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -86,10 +86,10 @@ packages: description: path: "." ref: HEAD - resolved-ref: "91a9625524cdff448e0a9deb5047bcf20cc8dcf1" - url: "https://github.com/tintran-dev/betterplayer.git" + resolved-ref: d59cf112b8e3487a6f417371a083c8cebf9bebf6 + url: "https://github.com/AnonymHK/betterplayer.git" source: git - version: "0.0.83" + version: "0.1.0" boolean_selector: dependency: transitive description: @@ -250,6 +250,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + ffmpeg_kit_flutter: + dependency: "direct main" + description: + name: ffmpeg_kit_flutter + sha256: "2b13913a5850d5c0149fe5a322770e72b107e0941d112a5334bce71d91051134" + url: "https://pub.dev" + source: hosted + version: "5.1.0-LTS" + ffmpeg_kit_flutter_platform_interface: + dependency: transitive + description: + name: ffmpeg_kit_flutter_platform_interface + sha256: addf046ae44e190ad0101b2fde2ad909a3cd08a2a109f6106d2f7048b7abedee + url: "https://pub.dev" + source: hosted + version: "0.2.1" file: dependency: transitive description: @@ -338,10 +354,10 @@ packages: dependency: transitive description: name: flutter_widget_from_html_core - sha256: e8f4f8b461a140ffb7c71f938bc76efc758893e7468843d9dbf70cb0b9e900cb + sha256: b733a240388736fcf40692920033dc3a749155dc49c840f6c0e4a732f553908b url: "https://pub.dev" source: hosted - version: "0.8.5+3" + version: "0.10.3" frontend_server_client: dependency: transitive description: @@ -904,10 +920,10 @@ packages: dependency: transitive description: name: visibility_detector - sha256: "15c54a459ec2c17b4705450483f3d5a2858e733aee893dcee9d75fd04814940d" + sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420 url: "https://pub.dev" source: hosted - version: "0.3.3" + version: "0.4.0+2" vm_service: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 2f3f396..7d9c93e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,10 +34,11 @@ dependencies: dynamic_color: ^1.6.6 marquee: ^2.2.3 better_player: - git: https://github.com/tintran-dev/betterplayer.git + git: https://github.com/AnonymHK/betterplayer.git flutter_pdfview: ^1.3.1 package_info_plus: ^3.1.2 document_file_save_plus: ^2.0.0 + ffmpeg_kit_flutter: 5.1.0-LTS dev_dependencies: flutter_test: From 70a018bd70593ce584cc7af5e813b7f4b41cf894 Mon Sep 17 00:00:00 2001 From: theskyblockman Date: Fri, 14 Jul 2023 12:49:20 +0200 Subject: [PATCH 4/4] :memo: Updated version and added changelog --- fastlane/metadata/android/en-US/changelogs/10202.txt | 1 + fastlane/metadata/android/fr-FR/changelogs/10202.txt | 1 + pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/10202.txt create mode 100644 fastlane/metadata/android/fr-FR/changelogs/10202.txt diff --git a/fastlane/metadata/android/en-US/changelogs/10202.txt b/fastlane/metadata/android/en-US/changelogs/10202.txt new file mode 100644 index 0000000..f7ca328 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/10202.txt @@ -0,0 +1 @@ +- Fixed bugs and improved stability. \ No newline at end of file diff --git a/fastlane/metadata/android/fr-FR/changelogs/10202.txt b/fastlane/metadata/android/fr-FR/changelogs/10202.txt new file mode 100644 index 0000000..5dc3b43 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/10202.txt @@ -0,0 +1 @@ +- Bugs reglĂ©s et amelioration gĂ©nĂ©rale de la stabilitĂ© \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 7d9c93e..b7ac905 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: A safe container for your data publish_to: 'none' homepage: https://github.com/theskyblockman/life-chest -version: 1.2.1+10201 +version: 1.2.2+10202 environment: sdk: '>=3.0.0 <4.0.0'