diff --git a/android/app/build.gradle b/android/app/build.gradle index 3449e22..3b85608 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -61,6 +61,11 @@ android { targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName + + ndk { + //noinspection ChromeOsAbiSupport + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86_64' + } } signingConfigs { diff --git a/android/gradle.properties b/android/gradle.properties index b9a9a24..57cba12 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -4,3 +4,4 @@ android.enableJetifier=true android.defaults.buildfeatures.buildconfig=true android.nonTransitiveRClass=false android.nonFinalResIds=false +android.bundle.enableUncompressedNativeLibs=false \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/10201.txt b/fastlane/metadata/android/en-US/changelogs/10201.txt new file mode 100644 index 0000000..f7ca328 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/10201.txt @@ -0,0 +1 @@ +- Fixed bugs and improved stability. \ No newline at end of file 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/en-US/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png index e8208d2..afaa11b 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png index 0e5c610..a13fff9 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png index 152a986..abb3cc1 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png index 3233b4c..f64d10e 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png index 54204c3..939473a 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png index e149d25..51cfe42 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png index 026c99d..a372397 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/8.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/8.png index ebeca97..0e866db 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/8.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/8.png differ diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/1.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/1.png new file mode 100644 index 0000000..b863cf6 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/1.png differ diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/2.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/2.png new file mode 100644 index 0000000..35dd998 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/2.png differ diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/3.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/3.png new file mode 100644 index 0000000..212fe86 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/3.png differ diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/4.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/4.png new file mode 100644 index 0000000..97379e6 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/4.png differ diff --git a/fastlane/metadata/android/en-US/images/tenInchScreenshots/1.png b/fastlane/metadata/android/en-US/images/tenInchScreenshots/1.png new file mode 100644 index 0000000..148ebbc Binary files /dev/null and b/fastlane/metadata/android/en-US/images/tenInchScreenshots/1.png differ diff --git a/fastlane/metadata/android/en-US/images/tenInchScreenshots/2.png b/fastlane/metadata/android/en-US/images/tenInchScreenshots/2.png new file mode 100644 index 0000000..bc5fb67 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/tenInchScreenshots/2.png differ diff --git a/fastlane/metadata/android/en-US/images/tenInchScreenshots/3.png b/fastlane/metadata/android/en-US/images/tenInchScreenshots/3.png new file mode 100644 index 0000000..c0f89fc Binary files /dev/null and b/fastlane/metadata/android/en-US/images/tenInchScreenshots/3.png differ diff --git a/fastlane/metadata/android/en-US/images/tenInchScreenshots/4.png b/fastlane/metadata/android/en-US/images/tenInchScreenshots/4.png new file mode 100644 index 0000000..f0af7f9 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/tenInchScreenshots/4.png differ diff --git a/fastlane/metadata/android/fr-FR/changelogs/10201.txt b/fastlane/metadata/android/fr-FR/changelogs/10201.txt new file mode 100644 index 0000000..5dc3b43 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/10201.txt @@ -0,0 +1 @@ +- Bugs reglés et amelioration générale de la stabilité \ 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/fastlane/metadata/android/fr-FR/images/phoneScreenshots/1.png b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/1.png index 9429600..30f7fcc 100644 Binary files a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/1.png and b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/1.png differ diff --git a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/2.png b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/2.png index 80c47b9..cb0cc13 100644 Binary files a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/2.png and b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/2.png differ diff --git a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/3.png b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/3.png index 964a270..298988d 100644 Binary files a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/3.png and b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/3.png differ diff --git a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/4.png b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/4.png index 69fdb3e..2c6f3d8 100644 Binary files a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/4.png and b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/4.png differ diff --git a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/5.png b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/5.png index 918be22..fa48f0a 100644 Binary files a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/5.png and b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/5.png differ diff --git a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/6.png b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/6.png index b699b77..c4f25db 100644 Binary files a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/6.png and b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/6.png differ diff --git a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/7.png b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/7.png index 1f5eaa6..7fcd61c 100644 Binary files a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/7.png and b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/7.png differ diff --git a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/8.png b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/8.png index 8154fbc..0be02f5 100644 Binary files a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/8.png and b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/8.png differ diff --git a/lib/file_explorer/file_explorer.dart b/lib/file_explorer/file_explorer.dart index 5196ab4..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)), @@ -970,7 +927,6 @@ class FileExplorerState extends State { loaderCurrentLoad = loaderCurrentLoad! + 1; }); } - setState(() { loaderTarget = null; loaderCurrentLoad = null; @@ -987,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..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'; @@ -33,16 +35,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 @@ -99,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); @@ -113,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/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/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/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/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/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/life-chest-banner-fr.png b/life-chest-banner-fr.png new file mode 100644 index 0000000..d1ba196 Binary files /dev/null and b/life-chest-banner-fr.png differ diff --git a/life-chest-banner.png b/life-chest-banner.png new file mode 100644 index 0000000..ab4f4bc Binary files /dev/null and b/life-chest-banner.png differ diff --git a/life-chest-banner.svg b/life-chest-banner.svg new file mode 100644 index 0000000..25c3907 --- /dev/null +++ b/life-chest-banner.svg @@ -0,0 +1,101 @@ + + + + + Life ChestVotre securité, +simplifié diff --git a/pubspec.lock b/pubspec.lock index 2c2a194..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: @@ -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: @@ -242,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: @@ -267,10 +291,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: @@ -330,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: @@ -514,10 +538,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 +687,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: @@ -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 7a94d4f..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.0+1 +version: 1.2.2+10202 environment: sdk: '>=3.0.0 <4.0.0' @@ -34,9 +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: 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