diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 895c1024..06f47107 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -76,8 +76,11 @@ jobs: - name: Create install folder to upload run: | mkdir ./FileTreeHasher-linux-x64 - mv flutter_app/build/linux/x64/release/bundle ./FileTreeHasher-linux-x64/ - cp flutter_app/install/linux/install.sh ./FileTreeHasher-linux-x64/ + mv flutter_app/build/linux/x64/release/bundle ./FileTreeHasher-linux-x64/bin + cp flutter_app/assets/img/logo.png ./FileTreeHasher-linux-x64/icon.png + cp flutter_app/install/linux/install.sh ./FileTreeHasher-linux-x64/install.sh + sed -i "s/^p_bundlefiles=.*$/p_bundlefiles=\$currentDir\/bin\/*/" ./FileTreeHasher-linux-x64/install.sh + sed -i "s/^p_bundleIcon=.*$/p_bundleIcon=\$currentDir\/icon.png/" ./FileTreeHasher-linux-x64/install.sh - name: Pack bundle to artifact uses: actions/upload-artifact@v3 @@ -110,11 +113,16 @@ jobs: flutter config --enable-macos-desktop flutter build macos --release + - name: Move application into parent folder + run: | + mkdir file_tree_hasher + mv flutter_app/build/macos/Build/Products/Release/File\ Tree\ Hasher.app file_tree_hasher/ + - name: Pack bundle to artifact uses: actions/upload-artifact@v3 with: name: FileTreeHasher-macos-x64 - path: flutter_app/build/macos/Build/Products/Release/File\ Tree\ Hasher.app/ + path: file_tree_hasher/ if-no-files-found: error # Update page and deploy builds @@ -171,23 +179,22 @@ jobs: run: | mv assets/downloads/* ../website/assets/downloads/ - - name: Checkout repository on develop + - name: Checkout repository on tag uses: actions/checkout@v3 - with: - ref: develop - name: Restore all buffered website (including downloads) run: | rm -r ./website/ mv ../website/ . - - name: Push new website to develop (excluding downloads) + - name: Push new website to new branch (excluding downloads) run: | git config --global user.email "nilshenrich@web.de" git config --global user.name "Nils Henrich - deploy workflow" + git checkout -b actions/${{ steps.get-tag-name.outputs.out }} git add -- . ':!website/assets/downloads/' git commit -m " Deploy version ${{ steps.get-tag-name.outputs.out }}" - git push origin + git push --set-upstream origin actions/${{ steps.get-tag-name.outputs.out }} - name: Clean repository (only website to be left) run: | diff --git a/.vscode/settings.json b/.vscode/settings.json index 6f29079e..54aa4125 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,19 @@ { - "cmake.sourceDirectory": "/home/nils/Dokumente/GitRepos/FileTreeHasher/flutter_app/linux", "cSpell.words": [ "Hasher", "nilshenrich" - ] + ], + "dart.lineLength": 150, + "[dart]": { + "editor.tabSize": 2, + "editor.insertSpaces": true, + "editor.detectIndentation": false, + "editor.suggest.insertMode": "replace", + "editor.defaultFormatter": "Dart-Code.dart-code", + "editor.inlayHints.enabled": "offUnlessPressed", + "editor.rulers": [ + 150 + ] + }, + "cmake.configureOnOpen": false } diff --git a/flutter_app/install/linux/install.sh b/flutter_app/install/linux/install.sh index da27e419..26458976 100755 --- a/flutter_app/install/linux/install.sh +++ b/flutter_app/install/linux/install.sh @@ -6,6 +6,7 @@ if [ "$EUID" -ne 0 ]; then exit -1 fi + # Get directory of this file currentDir=$(dirname $(readlink -f $0)) @@ -14,11 +15,15 @@ binname="file_tree_hasher" appname="filetreehasher" installpath="/opt/$appname" +# Bundle path +p_bundlefiles=$currentDir/../../build/linux/x64/release/bundle/* +p_bundleIcon=$currentDir/../../assets/img/logo.png + # Create installation directory mkdir -p $installpath # Copy bundle folder -cp -r $currentDir/../../build/linux/x64/release/bundle/* $installpath/ +cp -r $p_bundlefiles $installpath/ # Rename binary file mv $installpath/$binname $installpath/$appname @@ -37,7 +42,7 @@ chmod 755 $installpath/$appname ln -s $installpath/$appname /usr/local/bin/$appname # Create desktop entry by copying the desktop file -convert $currentDir/../../assets/img/logo.png -resize 256x256 $installpath/favicon.png +convert $p_bundleIcon -resize 256x256 $installpath/favicon.png echo -e """[Desktop Entry] Type=Application Name=File Tree Hasher diff --git a/flutter_app/install/windows/setupscript.iss b/flutter_app/install/windows/setupscript.iss index e8b487c1..506fbdfd 100644 --- a/flutter_app/install/windows/setupscript.iss +++ b/flutter_app/install/windows/setupscript.iss @@ -1,5 +1,5 @@ #define MyAppName "File Tree Hasher" -#define MyAppVersion "1.0.1+4" +#define MyAppVersion "2.0.0" #define MyAppPublisher "Nils Henrich" #define MyAppURL "https://nilshenrich.github.io/FileTreeHasher/" #define MyAppExeName "file_tree_hasher.exe" diff --git a/flutter_app/ios/Flutter/Debug.xcconfig b/flutter_app/ios/Flutter/Debug.xcconfig index 592ceee8..ec97fc6f 100644 --- a/flutter_app/ios/Flutter/Debug.xcconfig +++ b/flutter_app/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/flutter_app/ios/Flutter/Release.xcconfig b/flutter_app/ios/Flutter/Release.xcconfig index 592ceee8..c4855bfe 100644 --- a/flutter_app/ios/Flutter/Release.xcconfig +++ b/flutter_app/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/flutter_app/ios/Podfile b/flutter_app/ios/Podfile new file mode 100644 index 00000000..d97f17e2 --- /dev/null +++ b/flutter_app/ios/Podfile @@ -0,0 +1,44 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/flutter_app/ios/Runner.xcodeproj/project.pbxproj b/flutter_app/ios/Runner.xcodeproj/project.pbxproj index c99b5014..89f60283 100644 --- a/flutter_app/ios/Runner.xcodeproj/project.pbxproj +++ b/flutter_app/ios/Runner.xcodeproj/project.pbxproj @@ -13,8 +13,19 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -42,6 +53,8 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -66,12 +79,21 @@ name = Flutter; sourceTree = ""; }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, ); sourceTree = ""; }; @@ -79,6 +101,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -101,6 +124,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807E294A63A400263BE5 /* Frameworks */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; @@ -127,9 +168,14 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -150,11 +196,19 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -176,6 +230,7 @@ files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -202,6 +257,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -213,6 +276,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -274,7 +345,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -296,7 +367,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterApp; + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterApplicationMac; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -304,6 +375,56 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterApplicationMac.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterApplicationMac.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterApplicationMac.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -351,7 +472,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -400,7 +521,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -424,7 +545,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterApp; + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterApplicationMac; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -446,7 +567,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterApp; + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterApplicationMac; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -457,6 +578,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/flutter_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/flutter_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a3..87131a09 100644 --- a/flutter_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/flutter_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + + + + UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents diff --git a/flutter_app/ios/RunnerTests/RunnerTests.swift b/flutter_app/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..86a7c3b1 --- /dev/null +++ b/flutter_app/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/flutter_app/lib/definies/datatypes.dart b/flutter_app/lib/defines/datatypes.dart similarity index 92% rename from flutter_app/lib/definies/datatypes.dart rename to flutter_app/lib/defines/datatypes.dart index 9e21c4f5..98e7e2d3 100644 --- a/flutter_app/lib/definies/datatypes.dart +++ b/flutter_app/lib/defines/datatypes.dart @@ -2,7 +2,7 @@ // # @file datatypes.dart // # @author Nils Henrich // # @brief Definition of useful datatypes usable in entire project -// # @version 1.0.1+4 +// # @version 2.0.0 // # @date 2023-05-15 // # // # @copyright Copyright (c) 2023 @@ -14,11 +14,12 @@ // ################################################## // # Hash comparison result // ################################################## - enum E_HashComparisonResult { - none, // No comparison - equal, // Generated hash and comparison hash match - notEqual, // Generated hash and comparison hash differ + none, // Hash generation completed: No comparison + queued, // Hash to be generated but not started + busy, // Hash being generated + equal, // Hash generation completed: hash match + notEqual, // Hash generation completed: hash differ } // ################################################## diff --git a/flutter_app/lib/definies/defaults.dart b/flutter_app/lib/defines/defaults.dart similarity index 94% rename from flutter_app/lib/definies/defaults.dart rename to flutter_app/lib/defines/defaults.dart index 20911730..68d76e75 100644 --- a/flutter_app/lib/definies/defaults.dart +++ b/flutter_app/lib/defines/defaults.dart @@ -2,7 +2,7 @@ // # @file defaults // # @author Nils Henrich // # @brief All globally defined default values are set here -// # @version 1.0.1+4 +// # @version 2.0.0 // # @date 2023-03-29 // # // # @copyright Copyright (c) 2023 @@ -12,7 +12,7 @@ // ignore_for_file: constant_identifier_names, non_constant_identifier_names import 'dart:io'; -import 'package:file_tree_hasher/definies/hashalgorithms.dart'; +import 'package:file_tree_hasher/defines/hashalgorithms.dart'; // ---------- Default hash algorithm ---------- // ---------- This algorithm is selected on startup ---------- diff --git a/flutter_app/lib/definies/hashalgorithms.dart b/flutter_app/lib/defines/hashalgorithms.dart similarity index 86% rename from flutter_app/lib/definies/hashalgorithms.dart rename to flutter_app/lib/defines/hashalgorithms.dart index b20d0a43..08e68b3d 100644 --- a/flutter_app/lib/definies/hashalgorithms.dart +++ b/flutter_app/lib/defines/hashalgorithms.dart @@ -2,7 +2,7 @@ // # @file hashalgorithms.dart // # @author Nils Henrich // # @brief Global definitions about hash algorithms that are used -// # @version 1.0.1+4 +// # @version 2.0.0 // # @date 2023-03-29 // # // # @copyright Copyright (c) 2023 @@ -16,11 +16,11 @@ // # All hash algorithms the tool can handle // ################################################## enum E_HashAlgorithms { - MD5("MD5"), - SHA1("SHA1"), - SHA256("SHA256"), - SHA384("SHA384"), - SHA512("SHA512"), + MD5("MD5"), // Length: 32 chars + SHA1("SHA1"), // Length: 40 chars + SHA256("SHA256"), // Length: 64 chars + SHA384("SHA384"), // Length: 96 chars + SHA512("SHA512"), // Length: 128 chars NONE("NONE"); // Constructor and value to hold diff --git a/flutter_app/lib/definies/info.dart b/flutter_app/lib/defines/info.dart similarity index 85% rename from flutter_app/lib/definies/info.dart rename to flutter_app/lib/defines/info.dart index 1e6b09d3..c6312591 100644 --- a/flutter_app/lib/definies/info.dart +++ b/flutter_app/lib/defines/info.dart @@ -1,15 +1,15 @@ // #################################################################################################### // # @file info.dart // # @author Nils Henrich -// # @brief General information about theapp -// # @version 1.0.1+4 +// # @brief General information about the app +// # @version 2.0.0 // # @date 2023-06-18 // # // # @copyright Copyright (c) 2023 // # // #################################################################################################### -// ignore_for_file: non_constant_identifier_names +// ignore_for_file: constant_identifier_names // ################################################## // # Hash file header block containing general info diff --git a/flutter_app/lib/definies/styles.dart b/flutter_app/lib/defines/styles.dart similarity index 70% rename from flutter_app/lib/definies/styles.dart rename to flutter_app/lib/defines/styles.dart index a88932c1..f27ae2fb 100644 --- a/flutter_app/lib/definies/styles.dart +++ b/flutter_app/lib/defines/styles.dart @@ -2,7 +2,7 @@ // # @file styles.dart // # @author Nils Henrich // # @brief General widget styles -// # @version 1.0.1+4 +// # @version 2.0.0 // # @date 2023-04-03 // # // # @copyright Copyright (c) 2023 @@ -15,15 +15,26 @@ // ################################################## // Icons -import 'package:file_tree_hasher/definies/datatypes.dart'; +import 'package:file_tree_hasher/defines/datatypes.dart'; import 'package:flutter/material.dart'; // File tree icon const double Style_FileTree_Icon_Width_px = 24; const double Style_FileTree_Icon_Height_px = 24; +// File tree theme +const Color Style_FileTree_Header_Color = Colors.blue; +const Color Style_FileTree_Item_Color = Colors.transparent; + +// File tree sizing +const EdgeInsets Style_FileTree_Header_Padding = EdgeInsets.all(8); +const EdgeInsets Style_FileTree_Item_Padding = EdgeInsets.all(0); + // File tree text -const TextStyle Style_FileTree_Text_ParentPath = TextStyle(color: Colors.grey); +const TextStyle Style_FileTree_Header_Text_Name = TextStyle(color: Colors.black, fontSize: 18); +const TextStyle Style_FileTree_Header_Text_Parent = TextStyle(color: Colors.black, fontSize: 18); +const TextStyle Style_FileTree_Item_Text_Name = TextStyle(color: Colors.black); +const TextStyle Style_FileTree_Item_Text_Parent = TextStyle(color: Colors.grey); // Hash selector const double Style_FileTree_HashSelector_Height_px = 24; @@ -42,10 +53,11 @@ const InputDecoration Style_FileTree_ComparisonInput_Decoration = InputDecoration(border: OutlineInputBorder(), labelText: "Comparison hash", contentPadding: EdgeInsets.symmetric(horizontal: 8)); // Sub-item -const double Style_FileTree_SubItem_ShiftRight_px = 20; +const double Style_FileTree_SubItem_ShiftRight_px = 25; // Item spacing const double Style_FileTree_Item_ElementSpaces_px = 10; +const double Style_FileTree_Item_Expander_Width_px = 25; // Text style for generated hash const TextStyle Style_FileTree_HashGen_Text = TextStyle(color: Colors.grey, overflow: TextOverflow.ellipsis); diff --git a/flutter_app/lib/functions/general.dart b/flutter_app/lib/functions/general.dart index 81ddf7f0..febab1bf 100644 --- a/flutter_app/lib/functions/general.dart +++ b/flutter_app/lib/functions/general.dart @@ -2,7 +2,7 @@ // # @file general.dart // # @author Nils Henrich // # @brief Collection of general functions and algorithms -// # @version 1.0.1+4 +// # @version 2.0.0 // # @date 2023-04-22 // # // # @copyright Copyright (c) 2023 @@ -50,7 +50,7 @@ Directory GetHomeDir() { // @param: path // @return: String // ################################################## -String GetParentPath(String path) { +String GetParentPath(String path, {bool trailingSlash = false}) { String parent = libpath.dirname(path); // Raltive path without directory @@ -61,7 +61,9 @@ String GetParentPath(String path) { if (RegExp(r"^[A-Za-z]:\\$").hasMatch(parent)) return ""; // Add trailing slash - return "$parent/"; + if (trailingSlash) parent = "$parent/"; + + return parent; } // ################################################## diff --git a/flutter_app/lib/functions/hashfile.dart b/flutter_app/lib/functions/hashfile.dart deleted file mode 100644 index 550cb773..00000000 --- a/flutter_app/lib/functions/hashfile.dart +++ /dev/null @@ -1,151 +0,0 @@ -// #################################################################################################### -// # @file hashfile.dart -// # @author Nils Henrich -// # @brief Algorithms for hash file: creating, loading -// Hash file format: -// -// -// -// abcde,md5,"top-file" -// abcde,sha1,"top-folder/sub-file" -// # @version 1.0.1+4 -// # @date 2023-06-16 -// # -// # @copyright Copyright (c) 2023 -// # -// #################################################################################################### - -// ignore_for_file: non_constant_identifier_names - -import 'dart:io'; -import 'package:csv/csv.dart'; -import 'package:file_tree_hasher/definies/datatypes.dart'; -import 'package:file_tree_hasher/definies/defaults.dart'; -import 'package:file_tree_hasher/definies/hashalgorithms.dart'; -import 'package:file_tree_hasher/definies/info.dart'; -import 'package:file_tree_hasher/functions/general.dart'; -import 'package:file_tree_hasher/templates/filetree.dart'; -import 'package:path/path.dart' as libpath; - -// ################################################## -// @brief: Generate hash file from given file paths and hashes -// @param: fileviewhashes -// @param: storagepath -// @param: [override] -// @param: [level] -// ################################################## -void GenerateHashfile(C_FileViewHashes fileviewhashes, String storagepath, {bool override = true, int level = 0}) { - // Get file socket - File filesocket = File(storagepath); - - // If file shall be overridded, just recreate it with information header - if (override) { - filesocket.writeAsStringSync("$HashFileHeader\n\n${fileviewhashes.name}\n", mode: FileMode.writeOnly); - } - - // Loop over all view elements - // - For a file, add entry to hash file - // - For a folder, recurse on folder - for (C_FileViewHashes folder in fileviewhashes.folders) { - GenerateHashfile(folder, storagepath, override: false, level: level + 1); - } - for (C_FileHashPair file in fileviewhashes.files) { - // If no hash algorithm is selected, skip this file - if (file.algorithm == E_HashAlgorithms.NONE.value) continue; - - String newLine = ""; - newLine += file.hash ?? ""; - newLine += ","; - newLine += file.algorithm; - newLine += ",\""; - newLine += GetRawString(file.file); - newLine += "\"$LineendingChar"; - filesocket.writeAsStringSync(newLine, mode: FileMode.writeOnlyAppend); - } -} - -// ################################################## -// @brief: Load hash file from system and return info in usable form (List of hashed files with absolute path) -// @param: storagepath -// @return C_FileViewHashes? (No containing folders, just having full path files) -// ################################################## -C_FileViewHashes? LoadHashfile(String storagepath) { - // Lists to return - List filesToReturn = []; - - // Read hash file - File filesocket = File(storagepath); - if (!filesocket.existsSync()) return null; - List lines = filesocket.readAsLinesSync(); // TODO: Better read file dynamically (https://github.com/nilshenrich/FileTreeHasher/issues/14) - - // Iterate over all lines - // Ignore all lines before the empty line, they are part of the file header - bool isRealData = false; - String? rootpath; - for (String line in lines) { - // Search for empty line to identify usable data - if (line.isEmpty) { - isRealData = true; - continue; - } - if (!isRealData) continue; - - // First line of usable data is the tree views root path or the marker "Single files" - if (rootpath == null) { - rootpath = line; - continue; - } - - // Get 3 CSV colums from line - List> csvrow_list = const CsvToListConverter() - .convert(line, fieldDelimiter: ",", textDelimiter: '"', textEndDelimiter: '"', eol: LineendingChar, shouldParseNumbers: false); - if (csvrow_list.isEmpty) continue; - List csvrow = csvrow_list[0]; - if (csvrow.length != 3) continue; - String hashstring = csvrow[0]; - String hashalg = csvrow[1]; - String filepath = csvrow[2]; - - // Get the current line (file) into return object - filesToReturn.add(C_FileHashPair(filepath, hashstring, hashalg)); - } - - if (rootpath == null) { - return null; - } - return C_FileViewHashes(rootpath, filesToReturn, []); -} - -// ################################################## -// @brief: Transform a given list of file tree items into a C_FileViewHashes -// @param: items -// @param: name -// @return: C_FileViewHashes -// ################################################## -C_FileViewHashes FileTreeItems_to_FileViewHashes(List items, String name, String rootpath) { - List folders = []; - List files = []; - for (T_FileTreeItem item in items) { - String relpath = libpath.relative(item.path, from: rootpath); - if (item is T_FolderView) { - folders.add(FileTreeItems_to_FileViewHashes(item.subitems, item.path, rootpath)); - } else if (item is T_FileView) { - files.add(C_FileHashPair(relpath, item.globKey_HashGenerationView.currentState!.HashGen, item.globKey_HashAlgorithm.currentState!.get()!)); - } - } - return C_FileViewHashes(name, files, folders); -} - -// ################################################## -// @brief: Transform a given list of single files into a C_FileViewHashes -// @param: files -// @param: name -// @return: C_FileViewHashes -// ################################################## -C_FileViewHashes SingleFiles_to_FileViewHashes(List fileViews, String name) { - List files = []; - for (T_FileView file in fileViews) { - files.add(C_FileHashPair(file.path, file.globKey_HashGenerationView.currentState!.HashGen, file.globKey_HashAlgorithm.currentState!.get()!)); - } - return C_FileViewHashes(name, files, []); -} diff --git a/flutter_app/lib/main.dart b/flutter_app/lib/main.dart index 16b166d7..e4ab0377 100644 --- a/flutter_app/lib/main.dart +++ b/flutter_app/lib/main.dart @@ -2,7 +2,7 @@ // # @file main.dart // # @author Nils Henrich // # @brief App entry point -// # @version 1.0.1+4 +// # @version 2.0.0 // # @date 2023-03-19 // # // # @copyright Copyright (c) 2023 diff --git a/flutter_app/lib/templates/contentarea.dart b/flutter_app/lib/templates/contentarea.dart index 896d9d6e..f7d9d3e8 100644 --- a/flutter_app/lib/templates/contentarea.dart +++ b/flutter_app/lib/templates/contentarea.dart @@ -2,7 +2,7 @@ // # @file contentarea.dart // # @author Nils Henrich // # @brief Big content clusters like header bar or body -// # @version 1.0.1+4 +// # @version 2.0.0 // # @date 2023-03-30 // # // # @copyright Copyright (c) 2023 @@ -11,35 +11,58 @@ // ignore_for_file: camel_case_types, non_constant_identifier_names, library_private_types_in_public_api +import 'dart:async'; +import 'dart:convert'; import 'dart:io'; +import 'package:csv/csv.dart'; import 'package:file_picker/file_picker.dart'; -import 'package:file_tree_hasher/definies/datatypes.dart'; -import 'package:file_tree_hasher/definies/defaults.dart'; -import 'package:file_tree_hasher/definies/styles.dart'; -import 'package:file_tree_hasher/functions/hashfile.dart'; +import 'package:file_tree_hasher/defines/defaults.dart'; +import 'package:file_tree_hasher/defines/info.dart'; +import 'package:file_tree_hasher/defines/styles.dart'; import 'package:file_tree_hasher/templates/contentdivider.dart'; import 'package:file_tree_hasher/functions/general.dart'; import 'package:flutter/material.dart'; import 'package:file_tree_hasher/templates/hashselector.dart'; import 'package:file_tree_hasher/templates/headercontroller.dart'; import 'package:file_tree_hasher/templates/filetree.dart'; -import 'package:path/path.dart' as libpath; // ################################################## // # Body content // ################################################## GlobalKey BodyContent = GlobalKey(); +// ################################################## +// # Hash input update trigger object +// # Items are identified by path. Null means all items are included +// # Can update comparison input and selected hash algorithm. Null means not to be updated +// ################################################## +class HashInputUpdater { + String? itempath; // Itentifies item by path. Null means all items + String? compInput; // Hash comparison input to be set. Null means don't update + String? hashAlg; // Hash algorithm to be selected. Null means don't update + HashInputUpdater({required this.itempath, this.compInput, this.hashAlg}); +} + +// ################################################## +// # Global stream controllers every widget can listen to +// ################################################## +StreamController Controller_SelectedGlobalHashAlg = StreamController.broadcast(); // Globally selected hash algorithm +StreamController Controller_ComparisonInput = StreamController.broadcast(); // Comparison input to be updated + // ################################################## // # CONTENT // # Header bar containing general control elements // ################################################## class T_HeaderBar extends StatelessWidget implements PreferredSizeWidget { + // Constructor const T_HeaderBar({super.key}); @override Widget build(BuildContext context) { + Controller_SelectedGlobalHashAlg.stream.listen((selected) { + SelectedGlobalHashAlg = selected.value; + }); return AppBar( flexibleSpace: Column(children: [ const SizedBox(height: 10), @@ -59,8 +82,7 @@ class T_HeaderBar extends StatelessWidget implements PreferredSizeWidget { // -------------------- Section: Hash algorithm -------------------- T_HeaderControlSection(headingText: "Algorithm selection", items: [ T_GlobalHashSelector(onChanged: (selected) { - SelectedGlobalHashAlg = selected; - BodyContent.currentState!.updateHashAlg(selected); + Controller_SelectedGlobalHashAlg.add(C_HashAlg(selected)); }) ]), // -------------------- Section: Comparison -------------------- @@ -68,7 +90,7 @@ class T_HeaderBar extends StatelessWidget implements PreferredSizeWidget { IconButton(onPressed: BodyContent.currentState!.loadHashfile, icon: const Icon(Icons.upload_outlined), tooltip: "Load checksum file(s)"), IconButton(onPressed: BodyContent.currentState!.safeHashFile, icon: const Icon(Icons.download_outlined), tooltip: "Safe checksum file(s)"), IconButton( - onPressed: BodyContent.currentState!.clearComparisonInputs, + onPressed: () => Controller_ComparisonInput.add(HashInputUpdater(itempath: null, compInput: "")), icon: const Icon(Icons.delete_forever_outlined), tooltip: "Clear comparison strings") ]) @@ -97,16 +119,19 @@ class T_BodyContent extends StatefulWidget { // ################################################## class T_BodyContent_state extends State { // Currently loaded file trees - final List _loadedTrees = []; - final List _loadedFiles = []; + List loadedTrees = List.empty(growable: true); + List loadedFiles = List.empty(growable: true); @override Widget build(BuildContext context) { return Column(children: [ - const ContentDivider_folders(), - Column(children: _loadedTrees), - const ContentDivider_files(), - Row(children: [Flexible(child: Column(children: _loadedFiles)), const SizedBox(width: Style_FileTree_Item_ElementSpaces_px)]) + ContentDivider_folders(visible: loadedTrees.isNotEmpty), + Column(children: loadedTrees.map((c) => c.item).toList()), + ContentDivider_files(visible: loadedFiles.isNotEmpty), + Row(children: [ + Flexible(child: Column(children: loadedFiles.map((c) => c.item).toList())), + const SizedBox(width: Style_FileTree_Item_ElementSpaces_px) + ]) ]); } @@ -118,12 +143,18 @@ class T_BodyContent_state extends State { // -------------------- Select folder from system -------------------- // TODO: Multiple folders could be selected (Button description to be adapted). Think that is not possible for folders String? filetreePath = await FilePicker.platform.getDirectoryPath(initialDirectory: GetHomeDir().path); - if (filetreePath == null) { - return; - } + if (filetreePath == null) return; // -------------------- Show selected folder as tree view -------------------- - _showNewFolder(filetreePath); + StreamController controller_HashFile_SavePath = StreamController(); + setState(() { + loadedTrees.add(S_FileTree_StreamControlled_Item( + item: I_FileTree_Head( + path: filetreePath, + stream_hashAlg: Controller_SelectedGlobalHashAlg.stream, + stream_hashFile_savePath: controller_HashFile_SavePath.stream), + controllers: [Controller_SelectedGlobalHashAlg, controller_HashFile_SavePath])); + }); } // ################################################## @@ -132,46 +163,40 @@ class T_BodyContent_state extends State { // ################################################## void selectNewFiles() async { // -------------------- Select files from system -------------------- - FilePickerResult? filePaths = await FilePicker.platform.pickFiles(initialDirectory: GetHomeDir().path, allowMultiple: true); - if (filePaths == null) { + FilePickerResult? picked = await FilePicker.platform.pickFiles(initialDirectory: GetHomeDir().path, allowMultiple: true); + if (picked == null) { return; } // -------------------- Show selected files in body -------------------- - List paths = filePaths.paths; - for (String? path in paths) { - setState(() { - _loadedFiles.add(T_FileView(path: path!, name: path)); - }); + List l_loadedFiles = loadedFiles; + for (PlatformFile sysFile in picked.files) { + String? path = sysFile.path; + if (path == null) continue; + StreamController controller_HashFile_SavePath = StreamController(); + I_FileTree_File file = I_FileTree_File( + path: path, + stream_hashAlg: Controller_SelectedGlobalHashAlg.stream, + stream_hashFile_savePath: controller_HashFile_SavePath.stream, + showFullPath: true); + l_loadedFiles.add(S_FileTree_StreamControlled_Item(item: file, controllers: [Controller_SelectedGlobalHashAlg, controller_HashFile_SavePath])); } + setState(() { + loadedFiles = l_loadedFiles; + }); } // ################################################## // @brief: Remove all loaded file trees and files // ################################################## void clearContent() { + // Remove all loaded trees and files setState(() { - // Remove all loaded trees and files - _loadedTrees.clear(); - _loadedFiles.clear(); + loadedTrees.clear(); + loadedFiles.clear(); }); } - // ################################################## - // @brief: Update all hash algorithms recursively - // @param: selected - // ################################################## - void updateHashAlg(String? selected) { - for (T_FileTreeView view in _loadedTrees) { - for (T_FileTreeItem item in view.items) { - item.globKey_HashAlgorithm.currentState!.set(selected); - } - } - for (T_FileView item in _loadedFiles) { - item.globKey_HashAlgorithm.currentState!.set(selected); - } - } - // ################################################## // @brief: Create hash files from generated hashes // Hash file clusters: @@ -185,13 +210,31 @@ class T_BodyContent_state extends State { // - For single file section the hash files default location is the users home directory // ################################################## // TODO: What if hash generation is ongoing? + // TODO: Add creation date as well void safeHashFile() { // Get all file trees and single files into widgets List dialogRows = []; - for (T_FileTreeView view in _loadedTrees) { - dialogRows.add(T_StorageChooserRow(title: view.title, fileTreeView: view)); + for (S_FileTree_StreamControlled_Item view in loadedTrees) { + dialogRows.add(T_StorageChooserRow(title: view.item.path, fileTreeView: view)); + } + if (loadedFiles.isNotEmpty) { + dialogRows.add(T_StorageChooserRow(title: HashfileSingletext)); + } + if (dialogRows.isEmpty) { + showDialog( + context: context, + barrierDismissible: true, + builder: (BuildContext context) { + return AlertDialog(title: const Text("Nothing to be saved"), actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text("OK")) + ]); + }); + return; } - dialogRows.add(T_StorageChooserRow(title: HashfileSingletext)); // Add exit buttons at the end dialogRows.add(Row(children: [ @@ -200,18 +243,24 @@ class T_BodyContent_state extends State { onPressed: () { for (Widget row in dialogRows) { if (row is! T_StorageChooserRow) continue; - String storagepath = row.getStoragePath(); + String hashFilePath = row.getStoragePath(); + File hashFileSocket = File(hashFilePath); + hashFileSocket.writeAsStringSync("$HashFileHeader\n\n"); if (row.fileTreeView == null) { - GenerateHashfile(SingleFiles_to_FileViewHashes(_loadedFiles, HashfileSingletext), storagepath); + hashFileSocket.writeAsStringSync("$HashfileSingletext\n", mode: FileMode.append); + for (S_FileTree_StreamControlled_Item file in loadedFiles) { + file.send(C_HashFile_SavePath(hashFileSocket)); + } } else { - T_FileTreeView view = row.fileTreeView!; - GenerateHashfile(FileTreeItems_to_FileViewHashes(view.items, view.title, view.title), storagepath); + hashFileSocket.writeAsStringSync("${GetParentPath(hashFilePath)}\n", mode: FileMode.append); + S_FileTree_StreamControlled_Item view = row.fileTreeView!; + view.send(C_HashFile_SavePath(hashFileSocket, row.fileTreeView!.item.path)); } } Navigator.pop(context); }, - icon: const Icon(Icons.check)), - IconButton(onPressed: () => Navigator.pop(context), icon: const Icon(Icons.close)) + icon: const Icon(Icons.check)), // Exit with saving + IconButton(onPressed: () => Navigator.pop(context), icon: const Icon(Icons.close)) // Exit without saving ])); // Show dialog @@ -242,159 +291,41 @@ class T_BodyContent_state extends State { List paths = filePaths.paths; for (String? path in paths) { // Load and parse hash file - if (path == null) { - continue; - } - C_FileViewHashes? parsedHashfile = LoadHashfile(path); - if (parsedHashfile == null) { - continue; - } - String viewpath = parsedHashfile.name; - List hashlist = parsedHashfile.files; - - // ---------- Update single files ---------- - if (viewpath == HashfileSingletext) { - // For all hash string pairs: - // Just find if a single file exists with matching path - for (C_FileHashPair hashPair in hashlist) { - for (T_FileView singlefile in _loadedFiles) { - if (singlefile.path == hashPair.file) { - singlefile.globKey_HashAlgorithm.currentState!.set(hashPair.algorithm); - singlefile.globKey_HashComparisonView.currentState!.set(hashPair.hash ?? ""); - } - } - } - } - - // ---------- Update tree views ---------- - else { - // Find matching file tree view - T_FileTreeView? matchingview; - for (T_FileTreeView view in _loadedTrees) { - if (view.title == viewpath) matchingview = view; - } - if (matchingview == null) { - continue; - } - - // For all hash string pairs: - // Go along file path and update file view if existing - for (C_FileHashPair hashpair in hashlist) { - List pathparts = libpath.split(hashpair.file); - List folders = pathparts.sublist(0, pathparts.length - 1); - String file = pathparts.last; - T_FileView? matchingFileview = _getMatchingFileview(matchingview.items, folders, file); - if (matchingFileview == null) { - continue; - } - matchingFileview.globKey_HashAlgorithm.currentState!.set(hashpair.algorithm); - matchingFileview.globKey_HashComparisonView.currentState!.set(hashpair.hash ?? ""); + if (path == null) continue; + + // Read file line by line + // Ignore all lines before the mpty line, they are part of the file header + bool isRealData = false; + String? rootPath; + File(path).openRead().transform(utf8.decoder).transform(const LineSplitter()).forEach((line) { + // Search for empty line to identify usable data + if (line.isEmpty) { + isRealData = true; + return; } - } - } - } - - // ################################################## - // @brief: Clear all inputs for comparison hash - // ################################################## - void clearComparisonInputs() { - for (T_FileTreeView view in _loadedTrees) { - for (T_FileTreeItem item in view.items) { - if (item is T_FileView) { - item.globKey_HashComparisonView.currentState!.set(""); - } else if (item is T_FolderView) { - _clearCompInp(item); - } - } - } - for (T_FileView item in _loadedFiles) { - item.globKey_HashComparisonView.currentState!.set(""); - } - } - - // ################################################## - // @brief: Show file tree from a given path - // @param: path - // ################################################## - void _showNewFolder(String path) { - setState(() { - _loadedTrees.add(T_FileTreeView(items: _loadFolder(Directory(path)), title: path)); - }); - } - - // ################################################## - // @brief: Load folders and files and add to given list - // @param: rootFolder - // @return list of items - // ################################################## - List _loadFolder(Directory rootFolder) { - List itemsList = []; - List items = rootFolder.listSync(); - - // Loop over all files and subdirectories - for (FileSystemEntity item in items) { - // For subfolders - if (item is Directory) { - // Load all sub items of this subfolder and add to list - T_FolderView subfolder = T_FolderView(path: item.path, name: GetFileName(item.path), subitems: _loadFolder(item)); - itemsList.add(subfolder); - } - - // For files - else if (item is File) { - // Add file element to list - T_FileView file = T_FileView(path: item.path, name: GetFileName(item.path)); - itemsList.add(file); - } - } - return itemsList; - } - - // ################################################## - // @brief: Clear all inputs for comparison hash for folder sub-items - // @param: folder - // ################################################## - void _clearCompInp(T_FolderView folder) { - for (T_FileTreeItem item in folder.subitems) { - if (item is T_FileView) { - item.globKey_HashComparisonView.currentState!.set(""); - } else if (item is T_FolderView) { - _clearCompInp(item); - } - } - } + if (!isRealData) return; - // ################################################## - // @brief: Get file view element (if existing) that matches a folder path - // @param: viewitems - // @param: folders - // @param: file - // @return: T_FileView? - // ################################################## - T_FileView? _getMatchingFileview(List viewitems, List folders, String file) { - for (T_FileTreeItem item in viewitems) { - // Work on folder - if (item is T_FolderView) { - if (folders.isEmpty) { - continue; // File searched, folder found + // First line of usable data is the tree view root path or the marker for single files + if (rootPath == null) { + rootPath = line; + return; } - T_FileView? found = _getMatchingFileview(item.subitems, folders.sublist(1, folders.length), file); - if (found != null) { - return found; - } - } - // Work on file - if (item is T_FileView) { - if (folders.isNotEmpty) { - continue; // Folder searched, file found - } - if (item.name == file) { - return item; - } - } + // Get 3 CSV columns from line + List> csvrow_list = const CsvToListConverter() + .convert(line, fieldDelimiter: ",", textDelimiter: '"', eol: LineendingChar, shouldParseNumbers: false, convertEmptyTo: ""); + if (csvrow_list.isEmpty) return; + List csvrow = csvrow_list[0]; + if (csvrow.length != 3) return; + String hashstring = csvrow[0]; + String hashalg = csvrow[1]; + String filepath = csvrow[2]; + + // Trigger input update + Controller_ComparisonInput.add( + HashInputUpdater(itempath: rootPath == HashfileSingletext ? filepath : "$rootPath/$filepath", compInput: hashstring, hashAlg: hashalg)); + }); } - return null; } } @@ -405,7 +336,7 @@ class T_BodyContent_state extends State { class T_StorageChooserRow extends StatelessWidget { // Attributes final String title; - final T_FileTreeView? fileTreeView; // null means single files + final S_FileTree_StreamControlled_Item? fileTreeView; // null means single files final TextEditingController _textEditingController = TextEditingController(); // Constructor diff --git a/flutter_app/lib/templates/contentdivider.dart b/flutter_app/lib/templates/contentdivider.dart index 69bf168a..9f7a6bee 100644 --- a/flutter_app/lib/templates/contentdivider.dart +++ b/flutter_app/lib/templates/contentdivider.dart @@ -26,34 +26,37 @@ class _TextPart extends StatelessWidget { abstract class _ContentDivider extends StatelessWidget { final String text; + final bool visible; // Constructor - const _ContentDivider({super.key, required this.text}); + const _ContentDivider({super.key, required this.text, this.visible = true}); @override Widget build(BuildContext context) { - return Column(children: [ - const SizedBox(height: 10), - Row(children: [ - const SizedBox(width: 10), // Space to the left - const _DividerPart(), // Left divider line part - const SizedBox(width: 10), // Text space to the left - _TextPart(text: text), // Divider text - const SizedBox(width: 10), // Text space to the right - const _DividerPart(), // Right divider line part - const SizedBox(width: 10) // Space to the right - ]), - const SizedBox(height: 5) - ]); + return visible + ? Column(children: [ + const SizedBox(height: 10), + Row(children: [ + const SizedBox(width: 10), // Space to the left + const _DividerPart(), // Left divider line part + const SizedBox(width: 10), // Text space to the left + _TextPart(text: text), // Divider text + const SizedBox(width: 10), // Text space to the right + const _DividerPart(), // Right divider line part + const SizedBox(width: 10) // Space to the right + ]), + const SizedBox(height: 5) + ]) + : const SizedBox.shrink(); } } class ContentDivider_folders extends _ContentDivider { // Constructor - const ContentDivider_folders({super.key}) : super(text: "Loaded folders"); + const ContentDivider_folders({super.key, super.visible}) : super(text: "Loaded folders"); } class ContentDivider_files extends _ContentDivider { // Constructor - const ContentDivider_files({super.key}) : super(text: "Loaded single files"); + const ContentDivider_files({super.key, super.visible}) : super(text: "Loaded single files"); } diff --git a/flutter_app/lib/templates/filetree.dart b/flutter_app/lib/templates/filetree.dart index e4e69765..d5d147d2 100644 --- a/flutter_app/lib/templates/filetree.dart +++ b/flutter_app/lib/templates/filetree.dart @@ -1,9 +1,9 @@ // #################################################################################################### // # @file filetree.dart // # @author Nils Henrich -// # @brief Templates for loaded file, file tree and corresponding hashing and comparison funcionality -// # @version 1.0.1+4 -// # @date 2023-03-30 +// # @brief Build file tree from system path and provide hash generating and checking +// # @version 2.0.0 +// # @date 2023-12-07 // # // # @copyright Copyright (c) 2023 // # @@ -11,16 +11,19 @@ // ignore_for_file: camel_case_types, non_constant_identifier_names +import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:file_tree_hasher/defines/datatypes.dart'; +import 'package:path/path.dart' as libpath; import 'package:convert/convert.dart'; import 'package:crypto/crypto.dart'; -import 'package:file_tree_hasher/definies/datatypes.dart'; -import 'package:file_tree_hasher/definies/defaults.dart'; -import 'package:file_tree_hasher/definies/hashalgorithms.dart'; -import 'package:file_tree_hasher/definies/styles.dart'; +import 'package:file_tree_hasher/defines/defaults.dart'; +import 'package:file_tree_hasher/defines/hashalgorithms.dart'; +import 'package:file_tree_hasher/defines/styles.dart'; import 'package:file_tree_hasher/functions/general.dart'; +import 'package:file_tree_hasher/templates/contentarea.dart'; import 'package:file_tree_hasher/templates/hashselector.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -28,296 +31,415 @@ import 'package:percent_indicator/percent_indicator.dart'; // ################################################## // # TEMPLATE -// # Single file tree element (folder or file) +// # File tree item to be shown in file tree // ################################################## -abstract class T_FileTreeItem extends StatefulWidget { +abstract class T_FileTree_Item extends StatefulWidget { // Parameter - final String name; // File name used for view - final String path; // Absolute file path used for loading file - final String namePathPart; // Path to parent folder if name is given as a path + final String name; // Elements name (to be shown in GUI) + final String path; // Elements absolute system path (used for hash generation and shown in tree header) + final String parent; // Elements parents absolute system path (With trailing slash) - // Hash algorithm selector key - final globKey_HashAlgorithm = GlobalKey(); + // Status change: Parent stream + final Stream s_hashAlg_stream; // Selected hash algorithm + final Stream s_hashFile_savePath_stream; // File path to save hash file to // Constructor - T_FileTreeItem({super.key, required name, required this.path}) - : name = GetFileName(name), - namePathPart = GetParentPath(name); + T_FileTree_Item( + {super.key, + required this.path, + required Stream stream_hashAlg, + required Stream stream_hashFile_savePath, + required showFullPath}) + : name = GetFileName(path), + s_hashAlg_stream = stream_hashAlg, + s_hashFile_savePath_stream = stream_hashFile_savePath, + parent = showFullPath ? GetParentPath(path, trailingSlash: true) : ""; } // ################################################## -// # TEMPLATE -// # Single folder view +// # ITEM +// # Folder item // ################################################## -class T_FolderView extends T_FileTreeItem { - // Parameter - final List subitems; - +class I_FileTree_Folder extends T_FileTree_Item { // Constructor - T_FolderView({super.key, required super.path, required super.name, this.subitems = const []}); + I_FileTree_Folder( + {super.key, required super.path, required super.stream_hashAlg, required super.stream_hashFile_savePath, super.showFullPath = false}); + + // Style parameter + final bool _param_showIcon = true; + final TextStyle _param_textStyle_parent = Style_FileTree_Item_Text_Parent; + final TextStyle _param_textStyle_name = Style_FileTree_Item_Text_Name; + final Color _param_color_header = Style_FileTree_Item_Color; + final EdgeInsets _param_padding = Style_FileTree_Item_Padding; @override - State createState() => _T_FolderView_state(); + State createState() => I_FileTree_Folder_state(); } // ################################################## // # STATE -// # Single folder view state +// # Folder item // ################################################## -class _T_FolderView_state extends State { - // States - bool expanded = true; // Is folder extended? +class I_FileTree_Folder_state extends State with SingleTickerProviderStateMixin { + // State parameter + bool expanded = true; // Is folder expanded + List children = []; // Direct child items to be shown in tree + StreamController s_children = StreamController(); // Stream to add a child item with live update + + // Hash algorithm selector key + GlobalKey globalkey_hashAlgSel = GlobalKey(); + + // Toggle animation + final Duration _duration = const Duration(milliseconds: 250); + final Icon _iconToggle = const Icon(Icons.expand_more); + late AnimationController _animationcontroller; + late Animation _animation_expand; + late Animation _animation_iconturn; @override Widget build(BuildContext context) { - return Column(children: [ - Row(children: [ - SizedBox( - width: Style_FileTree_Icon_Width_px, - height: Style_FileTree_Icon_Height_px, - child: IconButton( - icon: Icon(expanded ? Icons.chevron_right : Icons.expand_more), - hoverColor: Colors.transparent, - highlightColor: Colors.transparent, - splashColor: Colors.transparent, - padding: EdgeInsets.zero, - onPressed: click_expander, - )), - const Icon(Icons.folder), - Text(widget.namePathPart, style: Style_FileTree_Text_ParentPath), - Expanded(child: Text(widget.name)), - const SizedBox(width: Style_FileTree_Item_ElementSpaces_px), - T_FileHashSelector(key: widget.globKey_HashAlgorithm, onChanged: change_hashAlgorithm), + // ---------- Header row ---------- + Widget areaHeader_clickable = MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + expanded ? _animationcontroller.reverse() : _animationcontroller.forward(); + setState(() { + expanded = !expanded; + }); + }, + child: Row( + children: [ + SizedBox( + width: Style_FileTree_Item_Expander_Width_px, + child: RotationTransition( + turns: _animation_iconturn, + child: _iconToggle, + ), + ), + widget._param_showIcon ? const Icon(Icons.folder) : const SizedBox.shrink(), + Text(widget.parent, style: widget._param_textStyle_parent), + Text(widget.name, style: widget._param_textStyle_name), + ], + ), + ), + ); + Widget areaHeader_unclickable = Row( + children: [ + T_FileHashSelector( + key: globalkey_hashAlgSel, + onChanged: (selected) { + for (var item in children) { + item.send(C_HashAlg(selected)); + } + }, + ), const SizedBox(width: Style_FileTree_Item_ElementSpaces_px), - const SizedBox(width: Style_FileTree_ComparisonInput_Width_px), - ]), - buildSubitems() - ]); + SizedBox(width: Style_FileTree_ComparisonInput_Width_px - widget._param_padding.right), + ], + ); + Widget areaHeader = Container( + padding: widget._param_padding, + color: widget._param_color_header, + child: Row( + children: [Expanded(child: areaHeader_clickable), areaHeader_unclickable], + ), + ); + + // ---------- Content column ---------- + Padding areaContent = Padding( + padding: const EdgeInsets.fromLTRB(Style_FileTree_SubItem_ShiftRight_px, 0, 0, 0), + child: Column(children: children.map((c) => c.item).toList()), + ); + + // ---------- Expandable ---------- + return Column( + children: [ + areaHeader, + SizeTransition( + sizeFactor: _animation_expand, + axisAlignment: -1, + child: areaContent, + ) + ], + ); } - // Sub-items - Offstage buildSubitems() { - return Offstage( - offstage: !expanded, - child: Row(children: [const SizedBox(width: Style_FileTree_SubItem_ShiftRight_px), Expanded(child: Column(children: widget.subitems))])); - } + @override + void initState() { + // ---------- Add event listener to be triggered when adding a new child item ---------- + s_children.stream.listen((sysItem) { + T_FileTree_Item item; + StreamController controller_hashAlg = StreamController(); + StreamController controller_hashFile_savePath = StreamController(); + + // ---------- Item is a file ---------- + if (sysItem is File) { + item = I_FileTree_File( + path: sysItem.path, + stream_hashAlg: controller_hashAlg.stream, + stream_hashFile_savePath: controller_hashFile_savePath.stream, + showFullPath: false); + } - // ################################################## - // # Event handlers - // ################################################## + // ---------- Item is a folder ---------- + else if (sysItem is Directory) { + item = I_FileTree_Folder( + path: sysItem.path, stream_hashAlg: controller_hashAlg.stream, stream_hashFile_savePath: controller_hashFile_savePath.stream); + } - // ################################################## - // @brief: Click handler: Expander button - // Expand or collapse this folder and rotate expasion icon respectively - // ################################################## - void click_expander() { - setState(() { - expanded = !expanded; + // ---------- Item is none of these ---------- + else { + return; + } + + setState(() { + children.add(S_FileTree_StreamControlled_Item(item: item, controllers: [controller_hashAlg, controller_hashFile_savePath])); + }); }); + widget.s_hashAlg_stream.listen((hash) { + globalkey_hashAlgSel.currentState!.set(hash.value); + }); + widget.s_hashFile_savePath_stream.listen((path) { + for (S_FileTree_StreamControlled_Item child in children) { + child.send(path); + } + }); + + // ---------- Call base method as usual ---------- + super.initState(); + + // ---------- Initialize toggle animation ---------- + _animationcontroller = AnimationController(vsync: this, duration: _duration); + _animation_expand = _animationcontroller.drive(CurveTween(curve: Curves.easeIn)); + _animation_iconturn = _animationcontroller.drive(Tween(begin: 0, end: -0.25).chain(CurveTween(curve: Curves.easeIn))); + if (expanded) _animationcontroller.value = 1; + + // ---------- Load all direct children items from system ---------- + loadChildren(); } // ################################################## - // @brief: Change handler: Selected hash algorithm has changed - // Also change selected hash algorithm for all sub-items - // @param: selected + // @brief: Load child items from system path // ################################################## - void change_hashAlgorithm(String? selected) { - // For all sub-elements change hash algorithm to same vale (Sub-folders will automatically do for their sub-elements) - for (T_FileTreeItem subitem in widget.subitems) { - subitem.globKey_HashAlgorithm.currentState!.set(selected); - } + void loadChildren() async { + Directory systemDir = Directory(widget.path); + Stream systemItems = systemDir.list(); + // await for (FileSystemEntity sysItem in systemItems) { + systemItems.forEach((sysItem) { + s_children.add(sysItem); + }); } } // ################################################## -// # TEMPLATE -// # Single file view +// # ITEM +// # File tree head (differently designed folder under the hood) // ################################################## -class T_FileView extends T_FileTreeItem { - final globKey_HashGenerationView = GlobalKey(); - final globKey_HashComparisonView = GlobalKey(); - +class I_FileTree_Head extends I_FileTree_Folder { // Constructor - T_FileView({super.key, required super.path, required super.name}); + I_FileTree_Head( + {super.key, required super.path, required super.stream_hashAlg, required super.stream_hashFile_savePath, super.showFullPath = true}); + // Style parameter @override - State createState() => _T_FileView_state(); -} - -// ################################################## -// # STATE -// # Single file view state -// ################################################## -class _T_FileView_state extends State { - // No state attributes - + bool get _param_showIcon => false; @override - Widget build(BuildContext context) { - return Row(children: [ - const SizedBox(width: Style_FileTree_Icon_Width_px), - const Icon(Icons.description), - Text(widget.namePathPart, style: Style_FileTree_Text_ParentPath), - Text(widget.name), - const SizedBox(width: Style_FileTree_Item_ElementSpaces_px), - Expanded( - child: T_HashGenerationView( - key: widget.globKey_HashGenerationView, filepath: widget.path, globKey_HashComparisonView: widget.globKey_HashComparisonView)), - const SizedBox(width: Style_FileTree_Item_ElementSpaces_px), - T_FileHashSelector( - key: widget.globKey_HashAlgorithm, - onChanged: (selected) { - T_HashGenerationView_state hashGen = widget.globKey_HashGenerationView.currentState!; - hashGen.abortHashGeneration(); - hashGen.generateHash(selected); - }), - const SizedBox(width: Style_FileTree_Item_ElementSpaces_px), - T_HashComparisonView( - key: widget.globKey_HashComparisonView, - onChanged: (value) { - widget.globKey_HashGenerationView.currentState!.compareHashes(value); - }), - ]); - } -} - -// ################################################## -// # TEMPLATE -// # File tree view area -// ################################################## -class T_FileTreeView extends StatelessWidget { - final String title; - final List items; - - // Constructor - const T_FileTreeView({super.key, required this.items, required this.title}); - + TextStyle get _param_textStyle_parent => Style_FileTree_Header_Text_Parent; @override - Widget build(BuildContext context) { - return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox(height: 10), - ExpansionTile( - maintainState: true, - initiallyExpanded: true, - leading: const Icon(Icons.folder), - title: Text(title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), - childrenPadding: const EdgeInsets.symmetric(horizontal: Style_FileTree_Item_ElementSpaces_px), - children: [const SizedBox(height: 10), Column(children: items), const SizedBox(height: 10)]) - ]); - } + TextStyle get _param_textStyle_name => Style_FileTree_Header_Text_Name; + @override + Color get _param_color_header => Style_FileTree_Header_Color; + @override + EdgeInsets get _param_padding => Style_FileTree_Header_Padding; } // ################################################## -// # TEMPLATE -// # Hash generation view -// # This widget can be inserted into file view to show hash calculation progress or generated hash comparison +// # ITEM +// # File item // ################################################## -class T_HashGenerationView extends StatefulWidget { - // Attributes - final String filepath; - final GlobalKey globKey_HashComparisonView; - +class I_FileTree_File extends T_FileTree_Item { // Constructor - const T_HashGenerationView({super.key, required this.filepath, required this.globKey_HashComparisonView}); + I_FileTree_File( + {super.key, required super.path, required super.stream_hashAlg, required super.stream_hashFile_savePath, required super.showFullPath}); @override - State createState() => T_HashGenerationView_state(); + State createState() => I_FileTree_File_state(); } // ################################################## // # STATE -// # Hash generation view state +// # File item // ################################################## -class T_HashGenerationView_state extends State { - // State attributes - String _hashGen = ""; - double _genProgress = 0; - bool _ongoing = false; - E_HashComparisonResult _comparisonResult = E_HashComparisonResult.none; +class I_FileTree_File_state extends State { + // State parameter + String _hashComp = ""; + int _hashComp_cursorPos = 0; + E_HashComparisonResult _hashComparisonResult = E_HashComparisonResult.none; + String? _hashGen; // Generated hash + double _hashGenProgress = 0; // Hash generation progress (0-1) + final StreamController _s_hashGenProgress = StreamController(); // Stream to update live progress + bool _hashOngoing = false; // Hash generation ongoing? (Used for abortion) + + // Hash algorithm selector key + GlobalKey globalkey_hashAlgSel = GlobalKey(); @override Widget build(BuildContext context) { - return _hashGen.isEmpty - ? LinearPercentIndicator( - percent: _genProgress, - lineHeight: Style_FileTree_HashGen_Prg_Height_px, - center: Text("${(_genProgress * 100).toStringAsFixed(1)}%", style: Style_FileTree_HashGen_Prg_Text), - progressColor: Style_FileTree_HashGen_Prg_Color) - : Row(children: [ - Flexible( - child: - Container(color: Style_FileTree_HashComp_Colors[_comparisonResult], child: Text(_hashGen, style: Style_FileTree_HashGen_Text))), - SizedBox( - height: Style_FileTree_HashSelector_FontSize_px, - child: IconButton( - onPressed: () { - Clipboard.setData(ClipboardData(text: _hashGen)); - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Copied to clipboard"))); - }, - iconSize: Style_FileTree_HashSelector_FontSize_px, - padding: EdgeInsets.zero, - color: Style_FileTree_HashGen_Text.color, - hoverColor: Colors.transparent, - highlightColor: Colors.transparent, - splashColor: Colors.transparent, - icon: const Icon(Icons.copy))) - ]); + return Row( + children: [ + const SizedBox(width: Style_FileTree_Item_Expander_Width_px), + const Icon(Icons.description), + Text(widget.parent, style: Style_FileTree_Item_Text_Parent), + Text(widget.name, style: Style_FileTree_Item_Text_Name), + const SizedBox(width: Style_FileTree_Item_ElementSpaces_px), + Expanded(child: _buildHashGenerationView(context)), + const SizedBox(width: Style_FileTree_Item_ElementSpaces_px), + T_FileHashSelector( + key: globalkey_hashAlgSel, + onChanged: (selected) { + generateHash(selected); + }), + const SizedBox(width: Style_FileTree_Item_ElementSpaces_px), + _buildHashComparisonView() + ], + ); } - @override - void initState() { - super.initState(); - generateHash(SelectedGlobalHashAlg); + // ################################################## + // @brief: Build hash generation view + // @param: context + // @return: Widget + // ################################################## + Widget _buildHashGenerationView(BuildContext context) { + if (_hashGen == null) { + return LinearPercentIndicator( + percent: _hashGenProgress, + lineHeight: Style_FileTree_HashGen_Prg_Height_px, + center: Text("${(_hashGenProgress * 100).toStringAsFixed(1)}%", style: Style_FileTree_HashGen_Prg_Text), + progressColor: Style_FileTree_HashGen_Prg_Color, + ); + } + return Row( + children: [ + Flexible( + child: Container( + color: Style_FileTree_HashComp_Colors[_hashComparisonResult], + child: Text(_hashGen!, style: Style_FileTree_HashGen_Text), + )), + SizedBox( + height: Style_FileTree_HashSelector_FontSize_px, + child: IconButton( + onPressed: () { + Clipboard.setData(ClipboardData(text: _hashGen!)); + ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Copied to clipboard"))); + }, + iconSize: Style_FileTree_HashSelector_FontSize_px, + padding: EdgeInsets.zero, + color: Style_FileTree_HashGen_Text.color, + hoverColor: Colors.transparent, + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + icon: const Icon(Icons.copy))), + ], + ); } // ################################################## - // @brief: Compare generated hash with text input - // @param: hashComp + // @brief: Build hash comparison view + // @return: Widget // ################################################## - void compareHashes(String hashComp) { - setState(() { - // If any of both hashes is empty, no comparison is done - if (_hashGen.isEmpty || hashComp.isEmpty) { - _comparisonResult = E_HashComparisonResult.none; - return; + Widget _buildHashComparisonView() { + TextEditingController hashComp_controller = TextEditingController(text: _hashComp); + hashComp_controller.selection = TextSelection.collapsed(offset: _hashComp_cursorPos); + return SizedBox( + width: Style_FileTree_ComparisonInput_Width_px, + height: Style_FileTree_ComparisonInput_Height_px, + child: TextField( + style: Style_FileTree_ComparisonInput_Text, + decoration: Style_FileTree_ComparisonInput_Decoration, + controller: hashComp_controller, + onChanged: (String hashComp) { + // Update buffer + _hashComp = hashComp; + _hashComp_cursorPos = hashComp_controller.selection.baseOffset; + + // Compare + _compareHash(); + }, + ), + ); + } + + @override + void initState() { + // ---------- Add event listener to be triggered when updating progress ---------- + _s_hashGenProgress.stream.listen((prog) { + setState(() { + _hashGenProgress = prog; + }); + }); + widget.s_hashAlg_stream.listen((hash) { + globalkey_hashAlgSel.currentState!.set(hash.value); + }); + widget.s_hashFile_savePath_stream.listen((file) { + file.value.writeAsStringSync( + // rootDir null means single file -> Use absolute path + "$_hashGen,${globalkey_hashAlgSel.currentState!.get()},\"${file.rootDir == null ? widget.path : libpath.relative(widget.path, from: file.rootDir)}\"\n", + mode: FileMode.append); + }); + Controller_ComparisonInput.stream.listen((input) { + // TODO: Can be done more efficient? + // TODO: Filter by tree and single files as well + if (input.itempath == null || input.itempath == widget.path) { + // Update comparison input and selected hash algorithm + // Trigger comparison explicitly if hash algorithm didn't change + bool triggerComparison = input.hashAlg == null || input.hashAlg == globalkey_hashAlgSel.currentState!.get(); + if (input.compInput != null) { + _hashComp = input.compInput!; + _hashComp_cursorPos = _hashComp.length; + } + if (triggerComparison) + _compareHash(); + else + globalkey_hashAlgSel.currentState!.set(input.hashAlg); } + }); - // For 2 valid inputs, the result is equal or not equal - _comparisonResult = _hashGen.toLowerCase() == hashComp.toLowerCase() ? E_HashComparisonResult.equal : E_HashComparisonResult.notEqual; + // ---------- Call base method as usual ---------- + super.initState(); - return; - }); + // ---------- Start generating hash ---------- + generateHash(SelectedGlobalHashAlg); } // ################################################## - // @brief: Calculate hash and update GUI - // @param alg + // @brief: Generate hash and update progress bar + // @param: alg // ################################################## void generateHash(String? alg) async { - // -------------------- Read file -------------------- - File file = File(widget.filepath); - if (!await file.exists()) { - // throw FileSystemException("File ${widget.filepath} does not exist"); - setState(() { - _hashGen = ""; - _genProgress = 0; - _comparisonResult = E_HashComparisonResult.none; - }); + // -------------------- Open file read stream -------------------- + + // Check if file exists + File file = File(widget.path); + if (!file.existsSync()) { + _hashGen = ""; + _hashGenProgress = 0; + _compareHash(); return; } - // -------------------- Generate hash -------------------- - - // Reset old hash and comparison - setState(() { - _genProgress = 0; - _hashGen = ""; - _comparisonResult = E_HashComparisonResult.none; - }); + // Reset any old status + // TODO: Reset _hashGen as well + _s_hashGenProgress.add(0); // File size and processed size for progress calculation - int totalBytes = await file.length(); + int totalBytes = file.lengthSync(); int bytesRead = 0; + // -------------------- Choose hash generator -------------------- + // Select hash algorithm var hashOut = AccumulatorSink(); ByteConversionSink hasher; @@ -332,24 +454,22 @@ class T_HashGenerationView_state extends State { } else if (alg == E_HashAlgorithms.SHA512.value) { hasher = sha512.startChunkedConversion(hashOut); } else if (alg == E_HashAlgorithms.NONE.value) { - setState(() { - _hashGen = ""; - }); + _hashGen = ""; + _compareHash(); return; } else { - setState(() { - _hashGen = ""; - }); + _hashGen = ""; + _compareHash(); return; } - // -------------------- Start -------------------- - _ongoing = true; + // -------------------- Generate hash block wise -------------------- + _hashOngoing = true; // Read file step by step and generate hash await for (var chunk in file.openRead()) { // Abort process here if flag is unset - if (!_ongoing) { + if (!_hashOngoing) { return; } @@ -358,23 +478,19 @@ class T_HashGenerationView_state extends State { hasher.add(chunk); // Update progress bar - if (!mounted) { - return; - } - setState(() { - _genProgress = bytesRead / totalBytes; - }); + _s_hashGenProgress.add(bytesRead / totalBytes); } - _ongoing = false; + _hashOngoing = false; + // -------------------- Done -------------------- // Extract hash string hasher.close(); - String hashString = hashOut.events.single.toString(); + _hashGen = hashOut.events.single.toString(); - _hashGen = hashString; - compareHashes(widget.globKey_HashComparisonView.currentState!.get()); + // Compare hash (Widget is rebuilt from here) + _compareHash(); } // ################################################## @@ -382,7 +498,7 @@ class T_HashGenerationView_state extends State { // ################################################## void abortHashGeneration() { // Unset flag to mark abortion - _ongoing = false; + _hashOngoing = false; // Reset hash generation view setState(() { @@ -391,77 +507,72 @@ class T_HashGenerationView_state extends State { } // ################################################## - // @brief: Getter: generated hash - // @return: String + // @brief: Compare generated hash with text input + // hashComp Set comparison result accordingly + // @param: hashComp Text input // ################################################## - String get HashGen { - return _hashGen; + void _compareHash() { + // If generated hash does not match expected format or comparison is empty, set comparison result None + String r_allowedChars = "a-fA-F0-9"; + List allowedLengths = [32, 40, 64, 96, 128]; + if (_hashComp.isEmpty || !RegExp('^(${allowedLengths.map((i) => '[$r_allowedChars]{$i}').join('|')})\$').hasMatch(_hashGen!)) { + setState(() { + _hashComparisonResult = E_HashComparisonResult.none; + }); + return; + } + + // For 2 valid inputs, the result is equal or not equal + setState(() { + _hashComparisonResult = _hashGen!.toLowerCase() == _hashComp.toLowerCase() ? E_HashComparisonResult.equal : E_HashComparisonResult.notEqual; + }); } } // ################################################## -// # TEMPLATE -// # Hash comparison view -// # This widget can be inserted into file view to provide user input for hash comparison +// # STRUCT +// # Stream controlled item // ################################################## -class T_HashComparisonView extends StatefulWidget { - // Function call on changed - final Function(String)? onChanged; +class S_FileTree_StreamControlled_Item { + // Private attributes + final T_FileTree_Item _item; + final List _controllers; // Constructor - T_HashComparisonView({super.key, this.onChanged}); - - // Hash comparison view key - final globKey_HashCompView = GlobalKey(); - - @override - State createState() => T_HashComparisonView_state(); + S_FileTree_StreamControlled_Item({required T_FileTree_Item item, required List controllers}) + : _item = item, + _controllers = controllers; + + // Getter + T_FileTree_Item get item => _item; + // StreamController get controller => _controller; + // Stream get stream => _controller.stream; + + // Stream setter + void send(T e) { + for (StreamController controller in _controllers) { + if (controller is StreamController) { + controller.add(e); + } + } + } } // ################################################## -// # STATE -// # Hash comparison view state +// # TYPE +// # Explicit types to be used in stream controllers to identify // ################################################## -class T_HashComparisonView_state extends State { - // state attributes - String _hashComp = ""; - - @override - Widget build(BuildContext context) { - return SizedBox( - width: Style_FileTree_ComparisonInput_Width_px, - height: Style_FileTree_ComparisonInput_Height_px, - child: TextField( - style: Style_FileTree_ComparisonInput_Text, - decoration: Style_FileTree_ComparisonInput_Decoration, - controller: TextEditingController(text: _hashComp), - onChanged: _onChange)); - } - - // ################################################## - // @brief: Work on onChange - // @param: value - // ################################################## - void _onChange(String value) { - _hashComp = value; - if (widget.onChanged != null) { - widget.onChanged!(value); - } - } +abstract class TC_Explicit { + final T _value; + TC_Explicit(T value) : _value = value; + T get value => _value; +} - // ################################################## - // @brief: Getter/Setter - // @param: val - // @return: String - // ################################################## - String get() { - return _hashComp; - } +class C_HashAlg extends TC_Explicit { + C_HashAlg(super.value); +} - void set(String val) { - setState(() { - _hashComp = val; - }); - _onChange(val); - } +class C_HashFile_SavePath extends TC_Explicit { + final String? rootDir; // Directory of header if file saves a tree view + C_HashFile_SavePath(super.value, [this.rootDir]); } diff --git a/flutter_app/lib/templates/hashselector.dart b/flutter_app/lib/templates/hashselector.dart index 521d43cd..a40ccfd8 100644 --- a/flutter_app/lib/templates/hashselector.dart +++ b/flutter_app/lib/templates/hashselector.dart @@ -3,7 +3,7 @@ // # @author Nils Henrich // # @brief Template for hash selection drop-down menu. // # Globally in header bar or special in file tree -// # @version 1.0.1+4 +// # @version 2.0.0 // # @date 2023-04-03 // # // # @copyright Copyright (c) 2023 @@ -16,9 +16,9 @@ // # TEMPLATE // # Overall hash selector // ################################################## -import 'package:file_tree_hasher/definies/defaults.dart'; -import 'package:file_tree_hasher/definies/hashalgorithms.dart'; -import 'package:file_tree_hasher/definies/styles.dart'; +import 'package:file_tree_hasher/defines/defaults.dart'; +import 'package:file_tree_hasher/defines/hashalgorithms.dart'; +import 'package:file_tree_hasher/defines/styles.dart'; import 'package:flutter/material.dart'; abstract class T_HashSelector extends StatefulWidget { diff --git a/flutter_app/lib/templates/headercontroller.dart b/flutter_app/lib/templates/headercontroller.dart index e194b2a5..f0576b41 100644 --- a/flutter_app/lib/templates/headercontroller.dart +++ b/flutter_app/lib/templates/headercontroller.dart @@ -2,7 +2,7 @@ // # @file headercontroller.dart // # @author Nils Henrich // # @brief Template for general control section sitting in the header bar -// # @version 1.0.1+4 +// # @version 2.0.0 // # @date 2023-03-29 // # // # @copyright Copyright (c) 2023 @@ -16,7 +16,7 @@ import 'package:flutter/material.dart'; // ################################################## // # TEMPLATE // # Control section in header bar -// # This section is made of an overall heading and some items aranged horizontally +// # This section is made of an overall heading and some items arranged horizontally // ################################################## class T_HeaderControlSection extends StatelessWidget { final String headingText; diff --git a/flutter_app/macos/Flutter/Flutter-Debug.xcconfig b/flutter_app/macos/Flutter/Flutter-Debug.xcconfig index c2efd0b6..4b81f9b2 100644 --- a/flutter_app/macos/Flutter/Flutter-Debug.xcconfig +++ b/flutter_app/macos/Flutter/Flutter-Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/flutter_app/macos/Flutter/Flutter-Release.xcconfig b/flutter_app/macos/Flutter/Flutter-Release.xcconfig index c2efd0b6..5caa9d15 100644 --- a/flutter_app/macos/Flutter/Flutter-Release.xcconfig +++ b/flutter_app/macos/Flutter/Flutter-Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/flutter_app/macos/Podfile b/flutter_app/macos/Podfile new file mode 100644 index 00000000..c795730d --- /dev/null +++ b/flutter_app/macos/Podfile @@ -0,0 +1,43 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/flutter_app/macos/Podfile.lock b/flutter_app/macos/Podfile.lock new file mode 100644 index 00000000..1f6779f9 --- /dev/null +++ b/flutter_app/macos/Podfile.lock @@ -0,0 +1,16 @@ +PODS: + - FlutterMacOS (1.0.0) + +DEPENDENCIES: + - FlutterMacOS (from `Flutter/ephemeral`) + +EXTERNAL SOURCES: + FlutterMacOS: + :path: Flutter/ephemeral + +SPEC CHECKSUMS: + FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + +PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 + +COCOAPODS: 1.14.3 diff --git a/flutter_app/macos/Runner.xcodeproj/project.pbxproj b/flutter_app/macos/Runner.xcodeproj/project.pbxproj index dbedc2d6..4a72c6bf 100644 --- a/flutter_app/macos/Runner.xcodeproj/project.pbxproj +++ b/flutter_app/macos/Runner.xcodeproj/project.pbxproj @@ -21,14 +21,24 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 758AD0DE81ADA2AD92F0FA4C /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08C3A7900CA004E30EF1078C /* Pods_RunnerTests.framework */; }; + B7CCB9917D4FDEF40C0EC4ED /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 897EA275EA80DA17A87FDED2 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 33CC10E52044A3C60003C045 /* Project object */; @@ -52,9 +62,13 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 08C3A7900CA004E30EF1078C /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* file_tree_hasher.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "file_tree_hasher.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33ADA257ED196CD422E5B89E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* flutter_appflutter_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = flutter_appflutter_app.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -66,21 +80,44 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 3D8F235F476E2C00A08F2AE1 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 6D8C4F9AA3128B06E291B7E4 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 7D33D34A05C2C7D2ABC0AC0D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 897EA275EA80DA17A87FDED2 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + B649916C47977D5DE0DD3492 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + D22324A341159E63C694457A /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 758AD0DE81ADA2AD92F0FA4C /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EA2044A3C60003C045 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B7CCB9917D4FDEF40C0EC4ED /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 33BA886A226E78AF003329D5 /* Configs */ = { isa = PBXGroup; children = ( @@ -97,15 +134,18 @@ children = ( 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + 3A539D77F9F601F2C9330503 /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* file_tree_hasher.app */, + 33CC10ED2044A3C60003C045 /* flutter_appflutter_app.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -145,9 +185,25 @@ path = Runner; sourceTree = ""; }; + 3A539D77F9F601F2C9330503 /* Pods */ = { + isa = PBXGroup; + children = ( + 33ADA257ED196CD422E5B89E /* Pods-Runner.debug.xcconfig */, + 7D33D34A05C2C7D2ABC0AC0D /* Pods-Runner.release.xcconfig */, + 3D8F235F476E2C00A08F2AE1 /* Pods-Runner.profile.xcconfig */, + D22324A341159E63C694457A /* Pods-RunnerTests.debug.xcconfig */, + B649916C47977D5DE0DD3492 /* Pods-RunnerTests.release.xcconfig */, + 6D8C4F9AA3128B06E291B7E4 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + 897EA275EA80DA17A87FDED2 /* Pods_Runner.framework */, + 08C3A7900CA004E30EF1078C /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -155,10 +211,30 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + FAE56A7D472133822CB7764C /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 33CC10EC2044A3C60003C045 /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 018B0A686AA9D7DC96F4BA15 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, @@ -172,7 +248,7 @@ ); name = Runner; productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* file_tree_hasher.app */; + productReference = 33CC10ED2044A3C60003C045 /* flutter_appflutter_app.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -182,9 +258,13 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; @@ -215,12 +295,20 @@ projectRoot = ""; targets = ( 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, 33CC111A2044C6BA0003C045 /* Flutter Assemble */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10EB2044A3C60003C045 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -233,6 +321,28 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 018B0A686AA9D7DC96F4BA15 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -271,9 +381,39 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + FAE56A7D472133822CB7764C /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 33CC10E92044A3C60003C045 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -287,6 +427,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; @@ -307,6 +452,51 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D22324A341159E63C694457A /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterApplicationMac.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/flutter_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/flutter_appflutter_app"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B649916C47977D5DE0DD3492 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterApplicationMac.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/flutter_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/flutter_appflutter_app"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6D8C4F9AA3128B06E291B7E4 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterApplicationMac.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/flutter_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/flutter_appflutter_app"; + }; + name = Profile; + }; 338D0CE9231458BD00FA5F75 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; @@ -537,6 +727,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 57d9cc91..743915ff 100644 --- a/flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ + + + + + + diff --git a/flutter_app/macos/Runner/Configs/AppInfo.xcconfig b/flutter_app/macos/Runner/Configs/AppInfo.xcconfig index bacf26cf..020cdd84 100644 --- a/flutter_app/macos/Runner/Configs/AppInfo.xcconfig +++ b/flutter_app/macos/Runner/Configs/AppInfo.xcconfig @@ -11,4 +11,4 @@ PRODUCT_NAME = File Tree Hasher PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterApp // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2023 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2024 com.example. All rights reserved. diff --git a/flutter_app/macos/RunnerTests/RunnerTests.swift b/flutter_app/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..5418c9f5 --- /dev/null +++ b/flutter_app/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import FlutterMacOS +import Cocoa +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/flutter_app/pubspec.lock b/flutter_app/pubspec.lock index 511af83c..4b4354cb 100644 --- a/flutter_app/pubspec.lock +++ b/flutter_app/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: archive - sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a" + sha256: "7e0d52067d05f2e0324268097ba723b71cb41ac8a6a2b24d1edf9c536b987b03" url: "https://pub.dev" source: hosted - version: "3.3.7" + version: "3.4.6" args: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" boolean_selector: dependency: transitive description: @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" checked_yaml: dependency: transitive description: @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.17.2" convert: dependency: "direct main" description: @@ -93,10 +93,10 @@ packages: dependency: "direct main" description: name: csv - sha256: "016b31a51a913744a0a1655c74ff13c9379e1200e246a03d96c81c5d9ed297b5" + sha256: "142bdf2b24f4a49e35a0fc4398f21d861c4c0f9015e8054dcacd0bb8e23ee27d" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.1.0" fake_async: dependency: transitive description: @@ -109,18 +109,18 @@ packages: dependency: transitive description: name: ffi - sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.1.0" file_picker: dependency: "direct main" description: name: file_picker - sha256: c7a8e25ca60e7f331b153b0cb3d405828f18d3e72a6fa1d9440c86556fffc877 + sha256: "903dd4ba13eae7cef64acc480e91bf54c3ddd23b5b90b639c170f3911e489620" url: "https://pub.dev" source: hosted - version: "5.3.0" + version: "6.0.0" flutter: dependency: "direct main" description: flutter @@ -138,18 +138,18 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.3" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360" + sha256: f185ac890306b5779ecbd611f52502d8d4d63d27703ef73161ca0407e815f02c url: "https://pub.dev" source: hosted - version: "2.0.15" + version: "2.0.16" flutter_test: dependency: "direct dev" description: flutter @@ -164,18 +164,18 @@ packages: dependency: transitive description: name: image - sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf + sha256: "028f61960d56f26414eb616b48b04eb37d700cbe477b7fb09bf1d7ce57fd9271" url: "https://pub.dev" source: hosted - version: "4.0.17" + version: "4.1.3" js: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.7" json_annotation: dependency: transitive description: @@ -188,42 +188,50 @@ packages: dependency: transitive description: name: lints - sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.1" matcher: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.0.0" path: dependency: "direct main" description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" percent_indicator: dependency: "direct main" description: @@ -236,18 +244,18 @@ packages: dependency: transitive description: name: petitparser - sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "5.4.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.6" pointycastle: dependency: transitive description: @@ -256,6 +264,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.7.3" + provider: + dependency: "direct main" + description: + name: provider + sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + url: "https://pub.dev" + source: hosted + version: "6.0.5" sky_engine: dependency: transitive description: flutter @@ -265,10 +281,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: @@ -305,10 +321,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.6.0" typed_data: dependency: transitive description: @@ -325,22 +341,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" win32: dependency: transitive description: name: win32 - sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" + sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" url: "https://pub.dev" source: hosted - version: "4.1.4" + version: "5.0.9" xml: dependency: transitive description: name: xml - sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" + sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.3.0" yaml: dependency: transitive description: @@ -350,5 +374,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=2.19.4 <3.0.0" - flutter: ">=3.3.0" + dart: ">=3.1.0 <4.0.0" + flutter: ">=3.7.0" diff --git a/flutter_app/pubspec.yaml b/flutter_app/pubspec.yaml index 5b502a37..5c7c8a5e 100644 --- a/flutter_app/pubspec.yaml +++ b/flutter_app/pubspec.yaml @@ -1,10 +1,10 @@ name: file_tree_hasher -description: A new Flutter project. +description: Hash generation and comparison tool publish_to: "none" -version: 1.0.1+4 +version: 2.0.0 environment: - sdk: ">=2.19.4 <3.0.0" + sdk: ">=3.1.0 <4.0.0" dependencies: flutter: @@ -13,8 +13,9 @@ dependencies: percent_indicator: ^4.0.1 crypto: ^3.0.3 convert: ^3.1.1 - file_picker: ^5.3.0 + file_picker: ^6.0.0 csv: ^5.0.2 + provider: ^6.0.5 dev_dependencies: flutter_test: diff --git a/website/_posts/documentation/2023-07-02-user-manual.md b/website/_posts/documentation/2023-07-02-user-manual.md index a950ca45..91a4f8d2 100644 --- a/website/_posts/documentation/2023-07-02-user-manual.md +++ b/website/_posts/documentation/2023-07-02-user-manual.md @@ -16,7 +16,7 @@ Using **File Tree Hasher** you are able to generate hash for files of any type a - SHA256 - SHA384 - SHA512 -- no hash +- NONE The hash algorithm to use can be selected for each file individually.\ If a file is added or the hash algorithm is changed, the hash generation starts automatically. @@ -49,10 +49,10 @@ Source code: https://github.com/nilshenrich/FileTreeHasher Version: /path/to/my/folder -da39a3ee5e6b4b0d3255bfef95601890afd80709,sha1,"top-file" -d41d8cd98f00b204e9800998ecf8427e,md5,"top-folder/sub-file" -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,sha256,"top-folder/sub-file-2" -b2f5ff47436671b6e533d8dc3614845d,md5,"top-folder-2/sub-folder/sub-sub-file" +da39a3ee5e6b4b0d3255bfef95601890afd80709,SHA1,"top-file" +d41d8cd98f00b204e9800998ecf8427e,MD5,"top-folder/sub-file" +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,SHA256,"top-folder/sub-file-2" +b2f5ff47436671b6e533d8dc3614845d,MD5,"top-folder-2/sub-folder/sub-sub-file" ``` ``` @@ -63,10 +63,10 @@ Source code: https://github.com/nilshenrich/FileTreeHasher Version: Single files -da39a3ee5e6b4b0d3255bfef95601890afd80709,sha1,"/path/to/file/one" -d41d8cd98f00b204e9800998ecf8427e,md5,"/file/paths/are/absolute" -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,sha256,"/no/need/to/be/in/same/directory" -b2f5ff47436671b6e533d8dc3614845d,md5,"/another/file" +da39a3ee5e6b4b0d3255bfef95601890afd80709,SHA1,"/path/to/file/one" +d41d8cd98f00b204e9800998ecf8427e,MD5,"/file/paths/are/absolute" +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,SHA256,"/no/need/to/be/in/same/directory" +b2f5ff47436671b6e533d8dc3614845d,MD5,"/another/file" ``` A hash list file has the extension ".hash" and can be opened using a regular text editor or a CSV editor. Using a CSV editor, the following settings should be used for optimal readability: diff --git a/website/_posts/downloads/2023-10-06-v1.0.1+4.md b/website/_posts/downloads/2023-10-06-v1.0.1+4.md new file mode 100644 index 00000000..6c99d525 --- /dev/null +++ b/website/_posts/downloads/2023-10-06-v1.0.1+4.md @@ -0,0 +1,12 @@ +--- +layout: bundle +title: 'v1.0.1+4' +subtitle: 'Hotfix: Fix MacOS build action' +author: nilshenrich +date: 2023-10-06 14:31:55 +0000 +permalink: 'downloads/v1.0.1+4/' +pin: false +--- + +Hotfix: Fix MacOS build action + diff --git a/website/_posts/known-issues/2024-04-03-47.md b/website/_posts/known-issues/2024-04-03-47.md new file mode 100644 index 00000000..0bf7a164 --- /dev/null +++ b/website/_posts/known-issues/2024-04-03-47.md @@ -0,0 +1,12 @@ +--- +layout: post +title: "Hash file can update out of scope" +author: nilshenrich +date: 2024-04-03 12:36:00 +0000 +order: 47 +permalink: 'known-issues/47/' +pin: false +--- + +If a hash file is loaded, some loaded single files can be updated with algorithm and comparison input even if they don't belong to the loaded hash file but have same absolute path. +But it might be wanted sometimes to load the same file in several scopes, so the root path must be considered as well when loading a hash file. diff --git a/website/_posts/release-notes/2023-08-05-v1.0.0.md b/website/_posts/release-notes/2023-09-08-v1.0.0.md similarity index 85% rename from website/_posts/release-notes/2023-08-05-v1.0.0.md rename to website/_posts/release-notes/2023-09-08-v1.0.0.md index 7e939c01..d3da2b8c 100644 --- a/website/_posts/release-notes/2023-08-05-v1.0.0.md +++ b/website/_posts/release-notes/2023-09-08-v1.0.0.md @@ -2,7 +2,7 @@ layout: post title: 'v1.0.0' author: nilshenrich -date: 2023-08-05 14:31:00 +0000 +date: 2023-09-08 14:31:00 +0000 order: 1 permalink: 'release-notes/v1.0.0/' pin: false diff --git a/website/_posts/release-notes/2024-04-03-v2.0.0.md b/website/_posts/release-notes/2024-04-03-v2.0.0.md new file mode 100644 index 00000000..8188ab25 --- /dev/null +++ b/website/_posts/release-notes/2024-04-03-v2.0.0.md @@ -0,0 +1,19 @@ +--- +layout: post +title: 'v2.0.0' +author: nilshenrich +date: 2024-04-03 12:50:00 +0000 +order: 1 +permalink: 'release-notes/v2.0.0/' +pin: false +--- + +This version comes with a fundamental structural update. The user experience stays the same, but under the hood the whole loading and displaying structure has changed.\ +This will be noticeable in better performance, especially when dealing with many files. + +Improvements: +- Better performance when loading big number of files. The load process doesn't freeze the GUI anymore but updates it while the process is ongoing. +- Saving and loading hash files is not deserialized to the file notes themselves to be more memory efficient. + +Bug fixes +- Fix installer for linux and macOS.