diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3a7ca0d08..da534e1d5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,15 +21,16 @@ jobs: runs-on: windows-latest strategy: matrix: - configuration: [Release] # No need to distribute Debug builds + configuration: [Debug] # No need to distribute Debug builds platform: [x64] - framework: [net8.0-windows10.0.22621.0] + framework: [net9.0-windows10.0.22621.0] env: Configuration: ${{ matrix.configuration }} Platform: ${{ matrix.platform }} DOTNET_INSTALL_DIR: '.\.dotnet' - DOTNET_VERSION: '8.x' + DOTNET_VERSION: '9.x' + DOTNET_QUALITY: 'preview' NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages steps: @@ -42,6 +43,48 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} + dotnet-quality: ${{ env.DOTNET_QUALITY }} + cache: true + cache-dependency-path: CollapseLauncher/packages.lock.json + + - name: Build + run: | + dotnet publish CollapseLauncher -p:PublishProfile=Publish-DebugCIRelease -p:PublishDir=".\debug-build\" + + - name: Upload Artifact + uses: actions/upload-artifact@v4.3.1 + with: + name: collapse_${{ matrix.platform }}-${{ matrix.configuration }}_${{ matrix.framework }}_${{ github.sha }} + path: ./CollapseLauncher/debug-build/ + compression-level: 9 + + build-nativeaot: + runs-on: windows-latest + strategy: + matrix: + configuration: [Debug] + platform: [x64] + framework: [net9.0-windows10.0.22621.0] + + env: + Configuration: ${{ matrix.configuration }} + Platform: ${{ matrix.platform }} + DOTNET_INSTALL_DIR: '.\.dotnet' + DOTNET_VERSION: '9.x' + DOTNET_QUALITY: 'preview' + NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages + + steps: + - name: Checkout + uses: actions/checkout@v4.1.5 + with: + submodules: recursive + + - name: Install .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + dotnet-quality: ${{ env.DOTNET_QUALITY }} cache: true cache-dependency-path: CollapseLauncher/packages.lock.json @@ -51,16 +94,20 @@ jobs: - name: Build run: | - dotnet publish CollapseLauncher -p:PublishProfile=Publish-PreviewRelease -p:PublishDir=".\preview-build\" + dotnet publish CollapseLauncher -p:PublishProfile=Publish-DebugCIReleaseAOT -p:PublishDir=".\debug-aot-build\" - - name: Upload Artifact (Release) + - name: Upload Artifact uses: actions/upload-artifact@v4.3.1 - if: ${{ matrix.configuration == 'Release' }} with: - name: collapse_${{ matrix.platform }}-${{ matrix.configuration }}_${{ matrix.framework }}_${{ github.sha }} - path: ./CollapseLauncher/preview-build/ + name: aot-experimental_collapse_${{ matrix.platform }}-${{ matrix.configuration }}_${{ matrix.framework }}_${{ github.sha }} + path: ./CollapseLauncher/debug-aot-build/ compression-level: 9 + notify-discord: + runs-on: ubuntu-latest + if: always() + needs: [build, build-nativeaot] + steps: - name: Notify Discord uses: sarisia/actions-status-discord@v1.13.0 if: always() diff --git a/.gitignore b/.gitignore index 7fe060537..b2fcd1f69 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -*/bin/* -*/obj/* +**/bin/* +**/obj/* *build/* .vs/* *.user @@ -7,7 +7,7 @@ packages/* CollapseLauncher/Deps/* CollapseLauncher/Invoker/* -CollapseLauncher/Generated Files/** +**/Generated Files/** *.psd InstallerProp/Output/* InstallerProp/temp/** diff --git a/.gitmodules b/.gitmodules index 8c68a51a8..bd0331340 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "SevenZipExtractor"] path = SevenZipExtractor url = https://github.com/CollapseLauncher/SevenZipExtractor +[submodule "H.NotifyIcon"] + path = H.NotifyIcon + url = https://github.com/CollapseLauncher/H.NotifyIcon diff --git a/Backup/CommunityToolkit.WinUI.Controls.ImageCropper.csproj b/Backup/CommunityToolkit.WinUI.Controls.ImageCropper.csproj new file mode 100644 index 000000000..0e97ae7dd --- /dev/null +++ b/Backup/CommunityToolkit.WinUI.Controls.ImageCropper.csproj @@ -0,0 +1,30 @@ + + + + + ImageCropper + The ImageCropper control allows the user to freely crop an image. + + + CommunityToolkit.WinUI.Controls.ImageCropperRns + ReadMe.md + + + + + + + + + + + + True + \ + + + + + $(PackageIdPrefix).$(PackageIdVariant).Controls.$(ToolkitComponentName) + + diff --git a/Backup/ReadMe.md b/Backup/ReadMe.md new file mode 100644 index 000000000..07942198d --- /dev/null +++ b/Backup/ReadMe.md @@ -0,0 +1,38 @@ + +# Windows Community Toolkit - ImageCropper + +This package is part of the [Windows Community Toolkit](https://aka.ms/toolkit/windows) from the [.NET Foundation](https://dotnetfoundation.org). + +## Package Contents + +This package contains the following controls in the `CommunityToolkit.WinUI.Controls` namespace: + +- ImageCropper + +## Which Package is for me? + +If you're developing with _UWP/WinUI 2 or Uno.UI_ you should be using the `CommunityToolkit.Uwp.Controls.ImageCropper` package. + +If you're developing with _WindowsAppSDK/WinUI 3 or Uno.WinUI_ you should be using the `CommunityToolkit.WinUI.Controls.ImageCropper` package. + +## WinUI Resources (UWP) + +For UWP projects, the WinUI 2 reference requires you include the WinUI XAML Resources in your App.xaml file: + +```xml + + + +``` + +See [Getting Started in WinUI 2](https://learn.microsoft.com/windows/apps/winui/winui2/getting-started) for more information. + +## Documentation + +Further documentation about these components can be found at: https://aka.ms/windowstoolkitdocs + +## License + +MIT + +See License.md in package for more details. diff --git a/ClearCache.bat b/ClearCache.bat index 50a691b8b..c42722760 100644 --- a/ClearCache.bat +++ b/ClearCache.bat @@ -3,6 +3,10 @@ echo Clearing Collapse cache rmdir /S /Q CollapseLauncher\bin && rmdir /S /Q CollapseLauncher\obj echo Clearing ColorThief cache rmdir /S /Q ColorThief\ColorThief\bin && rmdir /S /Q ColorThief\ColorThief\obj +echo Clearing CommunityToolkit.ImageCropper cache +rmdir /S /Q Hi3Helper.CommunityToolkit\ImageCropper\bin && rmdir /S /Q Hi3Helper.CommunityToolkit\ImageCropper\obj +echo Clearing CommunityToolkit.SettingsControls cache +rmdir /S /Q Hi3Helper.CommunityToolkit\SettingsControls\bin && rmdir /S /Q Hi3Helper.CommunityToolkit\SettingsControls\obj echo Clearing Core cache rmdir /S /Q Hi3Helper.Core\bin && rmdir /S /Q Hi3Helper.Core\obj echo Clearing EncTool cache @@ -13,12 +17,16 @@ echo Clearing Http cache rmdir /S /Q Hi3Helper.Http\bin && rmdir /S /Q Hi3Helper.Http\obj echo Clearing Http tester cache rmdir /S /Q Hi3Helper.Http\Test\bin && rmdir /S /Q Hi3Helper.Http\Test\obj +echo Clearing TaskScheduler cache +rmdir /S /Q Hi3Helper.TaskScheduler\bin && rmdir /S /Q Hi3Helper.TaskScheduler\obj echo Clearing HDiff cache rmdir /S /Q Hi3Helper.SharpHDiffPatch\Hi3Helper.SharpHDiffPatch\bin && rmdir /S /Q Hi3Helper.SharpHDiffPatch\Hi3Helper.SharpHDiffPatch\obj echo Clearing 2nd HDiff cache rmdir /S /Q Hi3Helper.SharpHDiffPatch\SharpHDiffPatch\bin && rmdir /S /Q Hi3Helper.SharpHDiffPatch\SharpHDiffPatch\obj echo Clearing InnoSetupHelper cache rmdir /S /Q InnoSetupHelper\bin && rmdir /S /Q InnoSetupHelper\obj +echo Clearing ImageEx cache +rmdir /S /Q ImageEx\ImageEx\bin && rmdir /S /Q ImageEx\ImageEx\obj echo Clearing 7z cache rmdir /S /Q Hi3Helper.Core\Classes\Data\Tools\SevenZipTool\SevenZipExtractor\SevenZipExtractor\bin && rmdir /S /Q Hi3Helper.Core\Classes\Data\Tools\SevenZipTool\SevenZipExtractor\SevenZipExtractor\obj echo Clearing SharpDiscordRPC cache diff --git a/CollapseLauncher.sln b/CollapseLauncher.sln index aa68aa993..c6045a5c1 100644 --- a/CollapseLauncher.sln +++ b/CollapseLauncher.sln @@ -24,6 +24,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hi3Helper.Sophon", "Hi3Help EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageEx", "ImageEx\ImageEx\ImageEx.csproj", "{A6AF9DE9-1A18-4C2D-B106-B68A0A7CD07D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hi3Helper.TaskScheduler", "Hi3Helper.TaskScheduler\Hi3Helper.TaskScheduler.csproj", "{C9CBAF52-49C7-4B72-A03B-130F596E24CB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hi3Helper.CommunityToolkit.WinUI.Controls.ImageCropper", "Hi3Helper.CommunityToolkit\ImageCropper\Hi3Helper.CommunityToolkit.WinUI.Controls.ImageCropper.csproj", "{558A1D17-BEB4-49DF-A200-15ABE283BDED}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hi3Helper.CommunityToolkit.WinUI.Controls.SettingsControls", "Hi3Helper.CommunityToolkit\SettingsControls\Hi3Helper.CommunityToolkit.WinUI.Controls.SettingsControls.csproj", "{5A1243EC-EFD9-4B55-8F29-D1A91A9B027D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "H.NotifyIcon.WinUI", "H.NotifyIcon\src\libs\H.NotifyIcon.WinUI\H.NotifyIcon.WinUI.csproj", "{141083CC-A924-4E19-904C-AF91361405A5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "H.NotifyIcon", "H.NotifyIcon\src\libs\H.NotifyIcon\H.NotifyIcon.csproj", "{6C8A25FA-BA1C-4EE4-8A9D-2FB4918077FB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "H.GeneratedIcons.System.Drawing", "H.NotifyIcon\src\libs\H.GeneratedIcons.System.Drawing\H.GeneratedIcons.System.Drawing.csproj", "{911C98FD-C64D-4BAC-8EF5-0616F8EFF7B9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -92,10 +104,44 @@ Global {3F87DCD0-39B7-4F8C-8F34-A0FC7C6E65A0}.Release|x64.Build.0 = Release|x64 {A6AF9DE9-1A18-4C2D-B106-B68A0A7CD07D}.Debug|x64.ActiveCfg = Debug|x64 {A6AF9DE9-1A18-4C2D-B106-B68A0A7CD07D}.Debug|x64.Build.0 = Debug|x64 - {A6AF9DE9-1A18-4C2D-B106-B68A0A7CD07D}.Publish|x64.ActiveCfg = Debug|x64 - {A6AF9DE9-1A18-4C2D-B106-B68A0A7CD07D}.Publish|x64.Build.0 = Debug|x64 + {A6AF9DE9-1A18-4C2D-B106-B68A0A7CD07D}.Publish|x64.ActiveCfg = Release|x64 + {A6AF9DE9-1A18-4C2D-B106-B68A0A7CD07D}.Publish|x64.Build.0 = Release|x64 {A6AF9DE9-1A18-4C2D-B106-B68A0A7CD07D}.Release|x64.ActiveCfg = Release|x64 {A6AF9DE9-1A18-4C2D-B106-B68A0A7CD07D}.Release|x64.Build.0 = Release|x64 + {C9CBAF52-49C7-4B72-A03B-130F596E24CB}.Debug|x64.ActiveCfg = Debug|x64 + {C9CBAF52-49C7-4B72-A03B-130F596E24CB}.Debug|x64.Build.0 = Debug|x64 + {C9CBAF52-49C7-4B72-A03B-130F596E24CB}.Publish|x64.ActiveCfg = Release|x64 + {C9CBAF52-49C7-4B72-A03B-130F596E24CB}.Release|x64.ActiveCfg = Release|x64 + {558A1D17-BEB4-49DF-A200-15ABE283BDED}.Debug|x64.ActiveCfg = Debug|x64 + {558A1D17-BEB4-49DF-A200-15ABE283BDED}.Debug|x64.Build.0 = Debug|x64 + {558A1D17-BEB4-49DF-A200-15ABE283BDED}.Publish|x64.ActiveCfg = Release|x64 + {558A1D17-BEB4-49DF-A200-15ABE283BDED}.Publish|x64.Build.0 = Release|x64 + {558A1D17-BEB4-49DF-A200-15ABE283BDED}.Release|x64.ActiveCfg = Release|x64 + {558A1D17-BEB4-49DF-A200-15ABE283BDED}.Release|x64.Build.0 = Release|x64 + {5A1243EC-EFD9-4B55-8F29-D1A91A9B027D}.Debug|x64.ActiveCfg = Debug|x64 + {5A1243EC-EFD9-4B55-8F29-D1A91A9B027D}.Debug|x64.Build.0 = Debug|x64 + {5A1243EC-EFD9-4B55-8F29-D1A91A9B027D}.Publish|x64.ActiveCfg = Release|x64 + {5A1243EC-EFD9-4B55-8F29-D1A91A9B027D}.Publish|x64.Build.0 = Release|x64 + {5A1243EC-EFD9-4B55-8F29-D1A91A9B027D}.Release|x64.ActiveCfg = Release|x64 + {5A1243EC-EFD9-4B55-8F29-D1A91A9B027D}.Release|x64.Build.0 = Release|x64 + {141083CC-A924-4E19-904C-AF91361405A5}.Debug|x64.ActiveCfg = Debug|x64 + {141083CC-A924-4E19-904C-AF91361405A5}.Debug|x64.Build.0 = Debug|x64 + {141083CC-A924-4E19-904C-AF91361405A5}.Publish|x64.ActiveCfg = Release|x64 + {141083CC-A924-4E19-904C-AF91361405A5}.Publish|x64.Build.0 = Release|x64 + {141083CC-A924-4E19-904C-AF91361405A5}.Release|x64.ActiveCfg = Release|x64 + {141083CC-A924-4E19-904C-AF91361405A5}.Release|x64.Build.0 = Release|x64 + {6C8A25FA-BA1C-4EE4-8A9D-2FB4918077FB}.Debug|x64.ActiveCfg = Debug|x64 + {6C8A25FA-BA1C-4EE4-8A9D-2FB4918077FB}.Debug|x64.Build.0 = Debug|x64 + {6C8A25FA-BA1C-4EE4-8A9D-2FB4918077FB}.Publish|x64.ActiveCfg = Release|x64 + {6C8A25FA-BA1C-4EE4-8A9D-2FB4918077FB}.Publish|x64.Build.0 = Release|x64 + {6C8A25FA-BA1C-4EE4-8A9D-2FB4918077FB}.Release|x64.ActiveCfg = Release|x64 + {6C8A25FA-BA1C-4EE4-8A9D-2FB4918077FB}.Release|x64.Build.0 = Release|x64 + {911C98FD-C64D-4BAC-8EF5-0616F8EFF7B9}.Debug|x64.ActiveCfg = Debug|x64 + {911C98FD-C64D-4BAC-8EF5-0616F8EFF7B9}.Debug|x64.Build.0 = Debug|x64 + {911C98FD-C64D-4BAC-8EF5-0616F8EFF7B9}.Publish|x64.ActiveCfg = Release|x64 + {911C98FD-C64D-4BAC-8EF5-0616F8EFF7B9}.Publish|x64.Build.0 = Release|x64 + {911C98FD-C64D-4BAC-8EF5-0616F8EFF7B9}.Release|x64.ActiveCfg = Release|x64 + {911C98FD-C64D-4BAC-8EF5-0616F8EFF7B9}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/CollapseLauncher/App.xaml b/CollapseLauncher/App.xaml index 03ea596c7..29062f0d2 100644 --- a/CollapseLauncher/App.xaml +++ b/CollapseLauncher/App.xaml @@ -1,4 +1,4 @@ - - + + + + + + + + + + + + + + @@ -28,345 +41,359 @@ #ffd52a #ffd52a #ffd52a - - - - - + + + + + + FallbackColor="{ThemeResource SystemAccentColor}" + Opacity="0.8" + TintColor="{ThemeResource SystemAccentColor}" + TintLuminosityOpacity="0.8" + TintOpacity="0.2" /> + Color="{ThemeResource SystemAccentColorLight2}" /> + Color="#FFFFFF" /> + Color="#000000" /> - - - - + Color="#242424" /> + + + + #ffd52a + Color="{ThemeResource DialogTitleColor}" /> - - - + TintOpacity="0.0" /> + + + + TintOpacity="0.0" /> + TintOpacity="0" /> + TintOpacity="0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="1" /> + TintOpacity="0.6" /> + TintOpacity="0.0" /> + TintOpacity="0.75" /> + TintOpacity="0.0" /> + TintLuminosityOpacity="0.2" + TintOpacity="0.2" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="1" /> - + + TintOpacity="0.0" /> + TintLuminosityOpacity="0.5" + TintOpacity="0.0" /> - - - + TintOpacity="0.0" /> + + + + + Color="#00000000" /> + Color="#22000000" /> + Color="#11000000" /> + Color="#0A000000" /> + + Color="#22000000" /> + Color="#11000000" /> + Color="#0A000000" /> + Color="#44000000" /> + Color="#55000000" /> + Color="#33000000" /> + Color="#22000000" /> - - - + + + + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> - + TintOpacity="0.0" /> + + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> - + TintOpacity="0.0" /> + + TintOpacity="0" /> + Opacity="0.9" + TintColor="{ThemeResource SystemAccentColor}" /> + Opacity="0.75" + TintColor="{ThemeResource SystemAccentColor}" /> + FallbackColor="{ThemeResource SystemAccentColor}" + TintColor="{ThemeResource SystemAccentColor}" /> + Opacity="0.75" + Color="#DDFFFFFF" /> + Color="#EE000000" /> + Color="#CC000000" /> + Color="#FF111111" /> + TintOpacity="0" /> + TintOpacity="0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> - + TintOpacity="0.0" /> + + + Color="#00000000" /> + Color="#00000000" /> + + + Color="#A0C00000" /> + + @@ -378,338 +405,350 @@ #693758 #693758 #693758 - - - - - + + + + + + FallbackColor="{ThemeResource SystemAccentColor}" + Opacity="0.8" + TintColor="{ThemeResource SystemAccentColor}" + TintLuminosityOpacity="0.8" + TintOpacity="0.2" /> + Color="{ThemeResource SystemAccentColor}" /> + Color="#000000" /> + Color="#FFFFFF" /> - - - - + Color="#ECECEC" /> + + + + #693758 + Color="{ThemeResource DialogTitleColor}" /> - - - + TintOpacity="0.0" /> + + + + TintOpacity="0.0" /> + TintOpacity="1" /> + TintOpacity="0" /> + TintOpacity="0" /> + TintLuminosityOpacity="0.9" + TintOpacity="0.4" /> + TintOpacity="0.25" /> + TintOpacity="0.25" /> + TintOpacity="0" /> + TintOpacity="0" /> + TintOpacity="0.0" /> + TintOpacity="0.75" /> + TintOpacity="0.7" /> + TintOpacity="0" /> + TintOpacity="0" /> + TintOpacity="0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> - + TintOpacity="1" /> + + TintOpacity="0.0" /> + TintLuminosityOpacity="0.9" + TintOpacity="0.0" /> + - - - + TintOpacity="0.0" /> + + + + Color="#00FFFFFF" /> + Color="#66FFFFFF" /> + Color="#44FFFFFF" /> + Color="#22FFFFFF" /> + Color="#66FFFFFF" /> + Color="#44FFFFFF" /> + Color="#22FFFFFF" /> + Color="#88FFFFFF" /> + Color="#55FFFFFF" /> + Color="#44FFFFFF" /> - - - - + Color="#22FFFFFF" /> + + + + - + + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> - + TintOpacity="0.0" /> + + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> - + TintOpacity="0.0" /> + + FallbackColor="#88FFFFFF" + TintColor="#88FFFFFF" /> + Opacity="0.9" + TintColor="{ThemeResource SystemAccentColor}" /> + Opacity="0.8" + TintColor="{ThemeResource SystemAccentColor}" /> + FallbackColor="{ThemeResource SystemAccentColor}" + TintColor="{ThemeResource SystemAccentColor}" /> + Color="#88000000" /> + Color="#DDFFFFFF" /> + Color="#CCFFFFFF" /> + Color="#FFFFFF" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> + TintOpacity="0.0" /> - + TintOpacity="0.0" /> + + + Color="#00FFFFFF" /> + Color="#00FFFFFF" /> + + + Color="#90FF6666" /> + + @@ -718,38 +757,28 @@ + - + - - - - - - - - - - ms-appx:///Assets/Fonts/FontAwesomeBrand6.otf#Font Awesome 6 Brands diff --git a/CollapseLauncher/App.xaml.cs b/CollapseLauncher/App.xaml.cs index 7159fa386..d6cf12ce2 100644 --- a/CollapseLauncher/App.xaml.cs +++ b/CollapseLauncher/App.xaml.cs @@ -8,7 +8,6 @@ using PhotoSauce.MagicScaler; using PhotoSauce.NativeCodecs.Libwebp; using System; -using System.Collections.Generic; using System.Linq; using Windows.UI; using static CollapseLauncher.InnerLauncherConfig; @@ -134,7 +133,8 @@ protected override void OnLaunched(LaunchActivatedEventArgs args) { LogWriteLine($"FATAL ERROR ON APP INITIALIZER LEVEL!!!\r\n{ex}", LogType.Error, true); LogWriteLine("\r\nIf this is not intended, please report it to: https://github.com/CollapseLauncher/Collapse/issues\r\nPress any key to exit..."); - Console.ReadLine(); + //Console.ReadLine(); + throw; } } @@ -147,14 +147,12 @@ public static void ToggleBlurBackdrop(bool useBackdrop = true) // then select the value, get the type of ResourceDictionary, then enumerate it foreach (ResourceDictionary list in resource! .ThemeDictionaries! - .OfType>() .Select(x => x.Value) .OfType()) { // Parse the dictionary as type of KeyValuePair, // and get the value which has type of AcrylicBrush only, then enumerate it foreach (AcrylicBrush theme in list - .OfType>() .Select(x => x.Value) .OfType()) { diff --git a/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs b/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs index c70224f92..2fae2d352 100644 --- a/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs +++ b/CollapseLauncher/Classes/CachesManagement/Honkai/Fetch.cs @@ -204,7 +204,7 @@ private IEnumerable EnumerateCacheTextAsset(CacheAssetType type, IEn try { // Deserialize the line and set the type - content = line.Deserialize(InternalAppJSONContext.Default); + content = line.Deserialize(InternalAppJSONContext.Default.CacheAsset); } catch (Exception ex) { diff --git a/CollapseLauncher/Classes/Extension/TaskExtensions.cs b/CollapseLauncher/Classes/Extension/TaskExtensions.cs index fb6367896..01ed2a487 100644 --- a/CollapseLauncher/Classes/Extension/TaskExtensions.cs +++ b/CollapseLauncher/Classes/Extension/TaskExtensions.cs @@ -6,18 +6,24 @@ #nullable enable namespace CollapseLauncher.Extension { - internal delegate ValueTask ActionTimeoutValueTaskCallback(CancellationToken token); + internal delegate Task ActionTimeoutValueTaskCallback(CancellationToken token); internal delegate void ActionOnTimeOutRetry(int retryAttemptCount, int retryAttemptTotal, int timeOutSecond, int timeOutStep); internal static class TaskExtensions { internal const int DefaultTimeoutSec = 10; internal const int DefaultRetryAttempt = 5; - internal static async ValueTask WaitForRetryAsync(this ActionTimeoutValueTaskCallback funcCallback, int? timeout = null, + internal static async Task AsTaskAndDoAction(this Task taskResult, Action doAction) + { + TResult? result = await taskResult; + doAction(result); + } + + internal static async Task WaitForRetryAsync(this ActionTimeoutValueTaskCallback funcCallback, int? timeout = null, int? timeoutStep = null, int? retryAttempt = null, ActionOnTimeOutRetry? actionOnRetry = null, CancellationToken fromToken = default) => await WaitForRetryAsync(() => funcCallback, timeout, timeoutStep, retryAttempt, actionOnRetry, fromToken); - internal static async ValueTask WaitForRetryAsync(Func> funcCallback, int? timeout = null, + internal static async Task WaitForRetryAsync(Func> funcCallback, int? timeout = null, int? timeoutStep = null, int? retryAttempt = null, ActionOnTimeOutRetry? actionOnRetry = null, CancellationToken fromToken = default) { timeout ??= DefaultTimeoutSec; @@ -78,7 +84,7 @@ internal static class TaskExtensions } internal static async - ValueTask + Task TimeoutAfter(this Task task, CancellationToken token = default, int timeout = DefaultTimeoutSec) { Task completedTask = await Task.WhenAny(task, ThrowExceptionAfterTimeout(timeout, task, token)); diff --git a/CollapseLauncher/Classes/Extension/UIElementExtensions.cs b/CollapseLauncher/Classes/Extension/UIElementExtensions.cs index cb79a82f3..c53519858 100644 --- a/CollapseLauncher/Classes/Extension/UIElementExtensions.cs +++ b/CollapseLauncher/Classes/Extension/UIElementExtensions.cs @@ -1,12 +1,13 @@ using CommunityToolkit.WinUI; -using CommunityToolkit.WinUI.Controls; using Hi3Helper; +using Hi3Helper.CommunityToolkit.WinUI.Controls; using Microsoft.UI; using Microsoft.UI.Input; using Microsoft.UI.Text; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Data; using Microsoft.UI.Xaml.Documents; using Microsoft.UI.Xaml.Media; using System; @@ -20,6 +21,12 @@ namespace CollapseLauncher.Extension { internal enum CornerRadiusKind { Normal, Rounded } + internal class NavigationViewItemLocaleTextProperty + { + public string LocaleSetName { get; set; } + public string LocalePropertyName { get; set; } + } + internal static class UIElementExtensions { /// @@ -30,6 +37,184 @@ internal static class UIElementExtensions [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "set_ProtectedCursor")] internal static extern void SetCursor(this UIElement element, InputCursor inputCursor); + /// + /// Set the cursor for the element. + /// + /// The member of an element + /// The cursor you want to set. Use to choose the cursor you want to set. + internal static ref T WithCursor(this T element, InputCursor inputCursor) where T : UIElement + { + element.SetCursor(inputCursor); + return ref Unsafe.AsRef(ref element); + } + +#nullable enable + /// + /// Set the initial navigation view item's locale binding before getting set with + /// + /// The instance to set the initial text binding to. + /// The instance to set the initial text binding to. + /// The instance name of a members. + /// Name of the locale property + /// A reference of the + internal static ref T BindNavigationViewItemText(this T element, string localeSetName, string localePropertyName) + where T : NavigationViewItemBase + { + NavigationViewItemLocaleTextProperty property = new NavigationViewItemLocaleTextProperty + { + LocaleSetName = localeSetName, + LocalePropertyName = localePropertyName + }; + + if (element is NavigationViewItemHeader elementAsHeader) + { + elementAsHeader.Tag = property; + } + else + { + TextBlock textBlock = new TextBlock().WithTag(property); + element.Content = textBlock; + } + return ref Unsafe.AsRef(ref element); + } + + internal static void SetAllControlsCursorRecursive(this UIElement element, InputSystemCursor toCursor) + { + if (element == null) + { + return; + } + + if (element is Panel panelKind) + { + foreach (UIElement panelElements in panelKind.Children) + { + SetAllControlsCursorRecursive(panelElements, toCursor); + } + } + + if (element is RadioButtons radioButtonsKind) + { + foreach (UIElement radioButtonContent in radioButtonsKind.Items.OfType()) + { + radioButtonContent.SetCursor(toCursor); + } + } + + if (element is Border borderKind) + { + SetAllControlsCursorRecursive(borderKind.Child, toCursor); + } + + if (element is ComboBox comboBoxKind) + { + comboBoxKind.SetCursor(toCursor); + } + + if (element is UserControl userControlKind) + { + SetAllControlsCursorRecursive(userControlKind.Content, toCursor); + } + + if (element is ContentControl contentControlKind + && contentControlKind.Content is UIElement contentControlKindInner) + { + SetAllControlsCursorRecursive(contentControlKindInner, toCursor); + } + + if (element is NavigationView navigationViewKind) + { + foreach (UIElement navigationViewElements in navigationViewKind.FindDescendants()) + { + if (navigationViewElements is NavigationViewItem) + { + navigationViewElements.SetCursor(toCursor); + continue; + } + SetAllControlsCursorRecursive(navigationViewElements, toCursor); + } + } + + if (element is ButtonBase buttonBaseKind) + { + buttonBaseKind.SetCursor(toCursor); + if (buttonBaseKind is Button buttonKind && buttonKind.Flyout != null && buttonKind.Flyout is Flyout buttonKindFlyout) + { + SetAllControlsCursorRecursive(buttonKindFlyout.Content, toCursor); + } + } + + if (element is ToggleSwitch) + { + element.SetCursor(toCursor); + } + + if (element.ContextFlyout != null && element.ContextFlyout is Flyout elementFlyoutKind) + { + SetAllControlsCursorRecursive(elementFlyoutKind.Content, toCursor); + } + } + + internal static void ApplyNavigationViewItemLocaleTextBindings(this NavigationView navViewControl) + { + foreach (NavigationViewItemBase navItem in navViewControl + .FindDescendants() + .OfType()) + { + string? localeValue = null; + if (navItem.Content is TextBlock navItemTextBlock + && navItemTextBlock.Tag is NavigationViewItemLocaleTextProperty localeProperty) + { + navItemTextBlock.BindProperty( + TextBlock.TextProperty, + Locale.Lang, + $"{localeProperty.LocaleSetName}.{localeProperty.LocalePropertyName}"); + localeValue = navItemTextBlock.GetValue(TextBlock.TextProperty) as string; + } + else if (navItem is NavigationViewItemHeader navItemAsHeader + && navItemAsHeader.Tag is NavigationViewItemLocaleTextProperty localePropertyOnHeader) + { + navItemAsHeader.BindProperty( + ContentControl.ContentProperty, + Locale.Lang, + $"{localePropertyOnHeader.LocaleSetName}.{localePropertyOnHeader.LocalePropertyName}"); + localeValue = navItemAsHeader.GetValue(ContentControl.ContentProperty) as string; + } + + if (!string.IsNullOrEmpty(localeValue)) + { + ToolTipService.SetToolTip(navItem, localeValue); + } + } + + navViewControl.UpdateLayout(); + } + + internal static ref T BindProperty(this T element, DependencyProperty dependencyProperty, object objectToBind, string propertyName, IValueConverter? converter = null, BindingMode bindingMode = BindingMode.OneWay) + where T : FrameworkElement + { + // Create a new binding instance + Binding binding = new Binding + { + Source = objectToBind, + Mode = bindingMode, + Path = new PropertyPath(propertyName), + UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged + }; + + // If the converter is assigned, then add the converter + if (converter != null) + { + binding.Converter = converter; + } + + // Set binding to the element + element.SetBinding(dependencyProperty, binding); + + return ref Unsafe.AsRef(ref element); + } +#nullable restore + internal static TButtonBase CreateButtonWithIcon(string text = null, string iconGlyph = null, string iconFontFamily = "FontAwesome", string buttonStyle = "DefaultButtonStyle", double iconSize = 16d, double? textSize = null, CornerRadius? cornerRadius = null, FontWeight? textWeight = null) where TButtonBase : ButtonBase, new() @@ -704,6 +889,9 @@ void AssignShadowAttachment(FrameworkElement thisElement, bool innerMasked) case Image imageElement: AttachShadow(imageElement, true, offset); break; + case ImageEx.ImageEx imageExElement: + AttachShadow(imageExElement, true, offset); + break; default: AttachShadow(element, innerMasked, offset); break; diff --git a/CollapseLauncher/Classes/FileMigrationProcess/Statics.cs b/CollapseLauncher/Classes/FileMigrationProcess/Statics.cs index 717758e4d..707429ed5 100644 --- a/CollapseLauncher/Classes/FileMigrationProcess/Statics.cs +++ b/CollapseLauncher/Classes/FileMigrationProcess/Statics.cs @@ -21,6 +21,8 @@ internal static async Task CreateJob(UIElement parentUI, s // Check whether the input is a file or not. bool isFileTransfer = File.Exists(inputPath) && !inputPath.StartsWith('\\'); outputPath = await InitializeAndCheckOutputPath(parentUI, dialogTitle, inputPath, outputPath, isFileTransfer); + if (outputPath == null) return null; + if (Helper.FileUtility.IsRootPath(outputPath)) { await SimpleDialogs.SpawnDialog(Lang._HomePage.InstallFolderRootTitle, @@ -30,7 +32,6 @@ await SimpleDialogs.SpawnDialog(Lang._HomePage.InstallFolderRootTitle, null, null, ContentDialogButton.Close, ContentDialogTheme.Error); return null; } - if (outputPath == null) return null; if (showWarningMessage) if (await ShowNotCancellableProcedureMessage(parentUI) == ContentDialogResult.None) diff --git a/CollapseLauncher/Classes/GameManagement/GamePlaytime/Context.cs b/CollapseLauncher/Classes/GameManagement/GamePlaytime/Context.cs new file mode 100644 index 000000000..843d46ae3 --- /dev/null +++ b/CollapseLauncher/Classes/GameManagement/GamePlaytime/Context.cs @@ -0,0 +1,8 @@ +using System.Text.Json.Serialization; + +namespace CollapseLauncher.GamePlaytime +{ + [JsonSourceGenerationOptions(IncludeFields = false, GenerationMode = JsonSourceGenerationMode.Metadata, IgnoreReadOnlyFields = true)] + [JsonSerializable(typeof(CollapsePlaytime))] + internal sealed partial class UniversalPlaytimeJSONContext : JsonSerializerContext { } +} diff --git a/CollapseLauncher/Classes/GameManagement/GamePlaytime/Playtime.cs b/CollapseLauncher/Classes/GameManagement/GamePlaytime/Playtime.cs new file mode 100644 index 000000000..591b9ddd6 --- /dev/null +++ b/CollapseLauncher/Classes/GameManagement/GamePlaytime/Playtime.cs @@ -0,0 +1,136 @@ +using CollapseLauncher.Extension; +using CollapseLauncher.Interfaces; +using Hi3Helper; +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Timers; +using static Hi3Helper.Logger; +using static CollapseLauncher.Dialogs.SimpleDialogs; +using static CollapseLauncher.InnerLauncherConfig; + +namespace CollapseLauncher.GamePlaytime +{ + internal class Playtime : IGamePlaytime + { + #region Properties + public event EventHandler PlaytimeUpdated; +#nullable enable + public CollapsePlaytime CollapsePlaytime => _playtime; + + private static HashSet _activeSessions = []; + private RegistryKey? _registryRoot; + private CollapsePlaytime _playtime; + private IGameVersionCheck _gameVersionManager; + + private CancellationTokenSourceWrapper _token = new(); + #endregion + + public Playtime(IGameVersionCheck GameVersionManager) + { + string registryPath = Path.Combine($"Software\\{GameVersionManager.VendorTypeProp.VendorType}", GameVersionManager.GamePreset.InternalGameNameInConfig!); + _registryRoot = Registry.CurrentUser.OpenSubKey(registryPath, true); + + _registryRoot ??= Registry.CurrentUser.CreateSubKey(registryPath, true, RegistryOptions.None); + + _gameVersionManager = GameVersionManager; + + _playtime = CollapsePlaytime.Load(_registryRoot, _gameVersionManager.GamePreset.HashID); + } +#nullable disable + + public void Update(TimeSpan timeSpan) + { + TimeSpan oldTimeSpan = _playtime.TotalPlaytime; + + _playtime.Update(timeSpan); + PlaytimeUpdated?.Invoke(this, _playtime); + + LogWriteLine($"Playtime counter changed to {TimeSpanToString(timeSpan)}. (Previous value: {TimeSpanToString(oldTimeSpan)})", writeToLog: true); + } + + public void Reset() + { + TimeSpan oldTimeSpan = _playtime.TotalPlaytime; + + _playtime.Reset(); + PlaytimeUpdated?.Invoke(this, _playtime); + + LogWriteLine($"Playtime counter was reset! (Previous value: {TimeSpanToString(oldTimeSpan)})", writeToLog: true); + } + + public async void StartSession(Process proc, DateTime? begin = null) + { + int hashId = _gameVersionManager.GamePreset.HashID; + + // If a playtime HashSet has already tracked, then return (do not track the playtime more than once) + if (_activeSessions.Contains(hashId)) return; + + // Otherwise, add it to track list + _activeSessions.Add(hashId); + + begin ??= DateTime.Now; + + TimeSpan initialTimeSpan = _playtime.TotalPlaytime; + + _playtime.LastPlayed = begin; + _playtime.LastSession = TimeSpan.Zero; + _playtime.Save(); + PlaytimeUpdated?.Invoke(this, _playtime); + +#if DEBUG + LogWriteLine($"{_gameVersionManager.GamePreset.ProfileName} - Started session at {begin.Value.ToLongTimeString()}."); +#endif + int elapsedSeconds = 0; + + using (var inGameTimer = new Timer()) + { + inGameTimer.Interval = 60000; + inGameTimer.Elapsed += (_, _) => + { + elapsedSeconds += 60; + DateTime now = DateTime.Now; + + _playtime.AddMinute(); + PlaytimeUpdated?.Invoke(this, _playtime); +#if DEBUG + LogWriteLine($"{_gameVersionManager.GamePreset.ProfileName} - {elapsedSeconds}s elapsed. ({now.ToLongTimeString()})"); +#endif + }; + + inGameTimer.Start(); + await proc.WaitForExitAsync(_token.Token); + inGameTimer.Stop(); + } + + DateTime end = DateTime.Now; + double totalElapsedSeconds = (end - begin.Value).TotalSeconds; + if (totalElapsedSeconds < 0) + { + LogWriteLine($"[HomePage::StartPlaytimeCounter] Date difference cannot be lower than 0. ({totalElapsedSeconds}s)", LogType.Error); + Dialog_InvalidPlaytime(m_mainPage?.Content, elapsedSeconds); + totalElapsedSeconds = elapsedSeconds; + } + + TimeSpan totalTimeSpan = TimeSpan.FromSeconds(totalElapsedSeconds); + LogWriteLine($"Added {totalElapsedSeconds}s [{totalTimeSpan.Hours}h {totalTimeSpan.Minutes}m {totalTimeSpan.Seconds}s] " + + $"to {_gameVersionManager.GamePreset.ProfileName} playtime.", LogType.Default, true); + + _playtime.Update(initialTimeSpan.Add(totalTimeSpan), false); + PlaytimeUpdated?.Invoke(this, _playtime); + + _activeSessions.Remove(hashId); + } + + private static string TimeSpanToString(TimeSpan timeSpan) => $"{timeSpan.Days * 24 + timeSpan.Hours}h {timeSpan.Minutes}m"; + + public void Dispose() + { + _token.Cancel(); + _playtime.Save(); + _registryRoot = null; + } + } +} diff --git a/CollapseLauncher/Classes/GameManagement/GamePlaytime/RegistryClass/CollapsePlaytime.cs b/CollapseLauncher/Classes/GameManagement/GamePlaytime/RegistryClass/CollapsePlaytime.cs new file mode 100644 index 000000000..b8780e8a5 --- /dev/null +++ b/CollapseLauncher/Classes/GameManagement/GamePlaytime/RegistryClass/CollapsePlaytime.cs @@ -0,0 +1,229 @@ +using Hi3Helper; +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using System.Text.Json.Serialization; +using static Hi3Helper.Logger; + +namespace CollapseLauncher.GamePlaytime +{ + internal class CollapsePlaytime + { + #region Fields + private static DateTime BaseDate => new(2012, 2, 13, 0, 0, 0, DateTimeKind.Utc); + + private const string TotalTimeValueName = "CollapseLauncher_Playtime"; + private const string LastPlayedValueName = "CollapseLauncher_LastPlayed"; + private const string StatsValueName = "CollapseLauncher_PlaytimeStats"; + + private static HashSet _isDeserializing = []; + private RegistryKey _registryRoot; + private int _hashID; + + #endregion + + #region Properties + /// + /// Represents the total time a game was played.

+ /// Default: TimeSpan.Zero + ///
+ [JsonIgnore] + public TimeSpan TotalPlaytime { get; set; } = TimeSpan.Zero; + + /// + /// Represents the daily playtime.
+ /// The ControlDate field is used to check if this value should be reset.

+ /// Default: TimeSpan.Zero + ///
+ public TimeSpan DailyPlaytime { get; set; } = TimeSpan.Zero; + + /// + /// Represents the weekly playtime.
+ /// The ControlDate field is used to check if this value should be reset.

+ /// Default: TimeSpan.Zero + ///
+ public TimeSpan WeeklyPlaytime { get; set; } = TimeSpan.Zero; + + /// + /// Represents the monthly playtime.
+ /// The ControlDate field is used to check if this value should be reset.

+ /// Default: TimeSpan.Zero + ///
+ public TimeSpan MonthlyPlaytime { get; set; } = TimeSpan.Zero; + + /// + /// Represents the total time the last/current session lasted.

+ /// Default: TimeSpan.Zero + ///
+ public TimeSpan LastSession { get; set; } = TimeSpan.Zero; + + /// + /// Represents the last time the game was launched.

+ /// Default: null + ///
+ [JsonIgnore] + public DateTime? LastPlayed { get; set; } + + /// + /// Represents a control date.
+ /// This date is used to check if a specific playtime statistic should be reset.

+ /// Default: DateTime.Today + ///
+ public DateTime ControlDate { get; set; } = DateTime.Today; + #endregion + + #region Methods +#nullable enable + /// + /// Reads from the Registry and deserializes the contents. + /// + public static CollapsePlaytime Load(RegistryKey root, int hashID) + { + try + { + _isDeserializing.Add(hashID); + if (root == null) throw new NullReferenceException($"Cannot load playtime. RegistryKey is unexpectedly not initialized!"); + + int? totalTime = (int?)root.GetValue(TotalTimeValueName,null); + int? lastPlayed = (int?)root.GetValue(LastPlayedValueName,null); + object? stats = root.GetValue(StatsValueName, null); + + CollapsePlaytime playtime; + + if (stats != null) + { + ReadOnlySpan byteStr = (byte[])stats; +#if DEBUG + LogWriteLine($"Loaded Playtime:\r\nTotal: {totalTime}s\r\nLastPlayed: {lastPlayed}\r\nStats: {Encoding.UTF8.GetString(byteStr.TrimEnd((byte)0))}", LogType.Debug, true); +#endif + playtime = byteStr.Deserialize(UniversalPlaytimeJSONContext.Default.CollapsePlaytime, new CollapsePlaytime())!; + } + else + { + playtime = new CollapsePlaytime(); + } + + playtime._registryRoot = root; + playtime._hashID = hashID; + playtime.TotalPlaytime = TimeSpan.FromSeconds(totalTime ?? 0); + playtime.LastPlayed = lastPlayed != null ? BaseDate.AddSeconds((int)lastPlayed) : null; + + return playtime; + } + catch (Exception ex) + { + LogWriteLine($"Failed while reading playtime.\r\n{ex}", LogType.Error, true); + } + finally + { + _isDeserializing.Remove(hashID); + } + + return new CollapsePlaytime() { _hashID = hashID, _registryRoot = root }; + } + + /// + /// Serializes all fields and saves them to the Registry. + /// + public void Save() + { + try + { + if (_registryRoot == null) throw new NullReferenceException($"Cannot save playtime since RegistryKey is unexpectedly not initialized!"); + + string data = this.Serialize(UniversalPlaytimeJSONContext.Default.CollapsePlaytime, true); + byte[] dataByte = Encoding.UTF8.GetBytes(data); +#if DEBUG + LogWriteLine($"Saved Playtime:\r\n{data}", LogType.Debug, true); +#endif + _registryRoot.SetValue(StatsValueName, dataByte, RegistryValueKind.Binary); + _registryRoot.SetValue(TotalTimeValueName, TotalPlaytime.TotalSeconds, RegistryValueKind.DWord); + + double? lastPlayed = (LastPlayed?.ToUniversalTime() - BaseDate)?.TotalSeconds; + if (lastPlayed != null) + _registryRoot.SetValue(LastPlayedValueName, lastPlayed, RegistryValueKind.DWord); + } + catch (Exception ex) + { + LogWriteLine($"Failed to save playtime!\r\n{ex}", LogType.Error, true); + } + } + + /// + /// Resets all fields and saves to the Registry. + /// + public void Reset() + { + TotalPlaytime = TimeSpan.Zero; + LastSession = TimeSpan.Zero; + DailyPlaytime = TimeSpan.Zero; + WeeklyPlaytime = TimeSpan.Zero; + MonthlyPlaytime = TimeSpan.Zero; + ControlDate = DateTime.Today; + LastPlayed = null; + + if (!_isDeserializing.Contains(_hashID)) Save(); + } + + /// + /// Updates the current Playtime TimeSpan to the provided value and saves to the Registry.

+ ///
+ /// New playtime value + /// Reset all other fields + public void Update(TimeSpan timeSpan, bool reset = true) + { + if (reset) + { + LastSession = TimeSpan.Zero; + DailyPlaytime = TimeSpan.Zero; + WeeklyPlaytime = TimeSpan.Zero; + MonthlyPlaytime = TimeSpan.Zero; + ControlDate = DateTime.Today; + } + + TotalPlaytime = timeSpan; + + if (!_isDeserializing.Contains(_hashID)) Save(); + } + + /// + /// Adds a minute to all fields, and checks if any should be reset.
+ /// After it saves to the Registry.

+ ///
+ public void AddMinute() + { + TimeSpan minute = TimeSpan.FromMinutes(1); + DateTime today = DateTime.Today; + + TotalPlaytime = TotalPlaytime.Add(minute); + LastSession = LastSession.Add(minute); + + if (ControlDate == today) + { + DailyPlaytime = DailyPlaytime.Add(minute); + WeeklyPlaytime = WeeklyPlaytime.Add(minute); + MonthlyPlaytime = MonthlyPlaytime.Add(minute); + } + else + { + DailyPlaytime = minute; + WeeklyPlaytime = IsDifferentWeek(ControlDate, today) ? minute : WeeklyPlaytime.Add(minute); + MonthlyPlaytime = IsDifferentMonth(ControlDate, today) ? minute : MonthlyPlaytime.Add(minute); + + ControlDate = today; + } + + if (!_isDeserializing.Contains(_hashID)) Save(); + } + + #endregion + + #region Utility + private static bool IsDifferentMonth(DateTime date1, DateTime date2) => date1.Year != date2.Year || date1.Month != date2.Month; + + private static bool IsDifferentWeek(DateTime date1, DateTime date2) => date1.Year != date2.Year || ISOWeek.GetWeekOfYear(date1) != ISOWeek.GetWeekOfYear(date2); + #endregion + } +} diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/BaseClass/ImportExportBase.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/BaseClass/ImportExportBase.cs index 68cb4f2bd..f03ed7616 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/BaseClass/ImportExportBase.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/BaseClass/ImportExportBase.cs @@ -33,7 +33,7 @@ internal class ImportExportBase using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read)) { byte[] head = new byte[6]; - fs.Read(head, 0, head.Length); + _ = fs.Read(head, 0, head.Length); Logger.LogWriteLine($"Importing registry {RegistryPath}..."); @@ -425,7 +425,7 @@ private string ReadValueName(Stream stream) byte[] buffer = ArrayPool.Shared.Rent(length); try { - stream.Read(buffer, 0, length); + _ = stream.Read(buffer, 0, length); return Encoding.UTF8.GetString(buffer, 0, length); } finally @@ -439,21 +439,21 @@ private string ReadValueName(Stream stream) private long ReadInt64(Stream stream) { Span buffer = stackalloc byte[4]; - stream.Read(buffer); + _ = stream.Read(buffer); return MemoryMarshal.Read(buffer); } private int ReadInt32(Stream stream) { Span buffer = stackalloc byte[4]; - stream.Read(buffer); + _ = stream.Read(buffer); return MemoryMarshal.Read(buffer); } private short ReadInt16(Stream stream) { Span buffer = stackalloc byte[2]; - stream.Read(buffer); + _ = stream.Read(buffer); return MemoryMarshal.Read(buffer); } @@ -481,7 +481,7 @@ private void ReadBinary(EndianBinaryReader reader, string valueName) { int leng = reader.ReadInt32(); byte[] val = new byte[leng]; - reader.Read(val, 0, leng); + _ = reader.Read(val, 0, leng); RegistryRoot?.SetValue(valueName, val, RegistryValueKind.Binary); } } diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/BaseClass/MagicNodeBaseValues.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/BaseClass/MagicNodeBaseValues.cs index 9e1848897..752c72acd 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/BaseClass/MagicNodeBaseValues.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/BaseClass/MagicNodeBaseValues.cs @@ -289,14 +289,14 @@ internal class MagicNodeBaseValues : IGameSettingsValueMagic public JsonNode? SettingsJsonNode { get; protected set; } [JsonIgnore] - public JsonSerializerContext Context { get; protected set; } + public JsonTypeInfo TypeInfo { get; protected set; } #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. [Obsolete("Loading settings with Load() is not supported for IGameSettingsValueMagic member. Use LoadWithMagic() instead!", true)] public static T Load() => throw new NotSupportedException("Loading settings with Load() is not supported for IGameSettingsValueMagic member. Use LoadWithMagic() instead!"); - public static T LoadWithMagic(byte[] magic, SettingsGameVersionManager versionManager, JsonSerializerContext context) + public static T LoadWithMagic(byte[] magic, SettingsGameVersionManager versionManager, JsonTypeInfo typeInfo) { if (magic == null || magic.Length == 0) throw new NullReferenceException($"Magic cannot be an empty array!"); @@ -314,28 +314,28 @@ public static T LoadWithMagic(byte[] magic, SettingsGameVersionManager versionMa #endif JsonNode? node = raw.DeserializeAsJsonNode(); T data = new T(); - data.InjectNodeAndMagic(node, magic, versionManager, context); + data.InjectNodeAndMagic(node, magic, versionManager, typeInfo); return data; } catch (Exception ex) { Logger.LogWriteLine($"Failed to parse MagicNodeBaseValues settings\r\n{ex}", LogType.Error, true); - return DefaultValue(magic, versionManager, context); + return DefaultValue(magic, versionManager, typeInfo); } } - private static T DefaultValue(byte[] magic, SettingsGameVersionManager versionManager, JsonSerializerContext context) + private static T DefaultValue(byte[] magic, SettingsGameVersionManager versionManager, JsonTypeInfo typeInfo) { // Generate dummy data T data = new T(); // Generate raw JSON string - string rawJson = data.Serialize(context, false, false); + string rawJson = data.Serialize(typeInfo, false, false); // Deserialize it back to JSON Node and inject // the node and magic JsonNode? defaultJsonNode = rawJson.DeserializeAsJsonNode(); - data.InjectNodeAndMagic(defaultJsonNode, magic, versionManager, context); + data.InjectNodeAndMagic(defaultJsonNode, magic, versionManager, typeInfo); // Return return data; @@ -352,18 +352,18 @@ public void Save() Directory.CreateDirectory(fileDirPath!); // Write into the file - string jsonString = SettingsJsonNode.SerializeJsonNode(Context, false, false); + string jsonString = SettingsJsonNode.SerializeJsonNode(TypeInfo, false, false); Sleepy.WriteString(filePath, jsonString, Magic); } public bool Equals(T? other) => JsonNode.DeepEquals(this.SettingsJsonNode, other?.SettingsJsonNode); - protected virtual void InjectNodeAndMagic(JsonNode? jsonNode, byte[] magic, SettingsGameVersionManager versionManager, JsonSerializerContext context) + protected virtual void InjectNodeAndMagic(JsonNode? jsonNode, byte[] magic, SettingsGameVersionManager versionManager, JsonTypeInfo typeInfo) { SettingsJsonNode = jsonNode; GameVersionManager = versionManager; Magic = magic; - Context = context; + TypeInfo = typeInfo; } } } diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/Genshin/RegistryClass/GeneralData.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/Genshin/RegistryClass/GeneralData.cs index cefbc2b66..2c4a42d46 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/Genshin/RegistryClass/GeneralData.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/Genshin/RegistryClass/GeneralData.cs @@ -351,15 +351,15 @@ public static GeneralData Load() // Dump GeneralData as indented JSON output using GeneralData properties LogWriteLine($"Deserialized Genshin Settings: {_ValueName}\r\n{byteStr - .Deserialize(GenshinSettingsJSONContext.Default) - .Serialize(GenshinSettingsJSONContext.Default, false, true)}", LogType.Debug, true); + .Deserialize(GenshinSettingsJSONContext.Default.GeneralData) + .Serialize(GenshinSettingsJSONContext.Default.GeneralData, false, true)}", LogType.Debug, true); #endif #if DEBUG LogWriteLine($"Loaded Genshin Settings: {_ValueName}", LogType.Debug, true); #else LogWriteLine($"Loaded Genshin Settings", LogType.Default, true); #endif - GeneralData data = byteStr.Deserialize(GenshinSettingsJSONContext.Default) ?? new GeneralData(); + GeneralData data = byteStr.Deserialize(GenshinSettingsJSONContext.Default.GeneralData) ?? new GeneralData(); if (data._graphicsData != null) data.graphicsData = GraphicsData.Load(data._graphicsData); if (data._globalPerfData != null) data.globalPerfData = GlobalPerfData.Load(data._globalPerfData, data.graphicsData)!; @@ -376,7 +376,7 @@ public static GeneralData Load() $"Unless you have never opened the game (fresh installation), please open the game and change any settings, then safely close the game. If the problem persist, report the issue on our GitHub\r\n\r\n" + $"{ex}", ex)); - GeneralData data = _generalDataDefault!.Deserialize(GenshinSettingsJSONContext.Default) ?? new GeneralData(); + GeneralData data = _generalDataDefault!.Deserialize(GenshinSettingsJSONContext.Default.GeneralData) ?? new GeneralData(); data.graphicsData = GraphicsData.Load(data._graphicsData!); data.globalPerfData = GlobalPerfData.Load(data._globalPerfData!, data.graphicsData)!; return data; @@ -392,13 +392,13 @@ public void Save() _graphicsData = graphicsData!.Create(globalPerfData!); _globalPerfData = globalPerfData!.Save()!; - string data = this.Serialize(GenshinSettingsJSONContext.Default); + string data = this.Serialize(GenshinSettingsJSONContext.Default.GeneralData); byte[] dataByte = Encoding.UTF8.GetBytes(data); RegistryRoot.SetValue(_ValueName, dataByte, RegistryValueKind.Binary); #if DUMPGIJSON //Dump saved GeneralData JSON from Collapse as indented output - LogWriteLine($"Saved Genshin Settings: {_ValueName}\r\n{this.Serialize(GenshinSettingsJSONContext.Default, false, true)}", LogType.Debug, true); + LogWriteLine($"Saved Genshin Settings: {_ValueName}\r\n{this.Serialize(GenshinSettingsJSONContext.Default.GeneralData, false, true)}", LogType.Debug, true); #endif #if DEBUG LogWriteLine($"Saved Genshin Settings: {_ValueName}" + diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/Genshin/RegistryClass/GlobalPerfData.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/Genshin/RegistryClass/GlobalPerfData.cs index a137cf607..7e11a3ace 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/Genshin/RegistryClass/GlobalPerfData.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/Genshin/RegistryClass/GlobalPerfData.cs @@ -307,7 +307,7 @@ public static GlobalPerfData Load(string globalPerfJson, GraphicsData graphics) } else { - tempData = globalPerfJson.Deserialize(GenshinSettingsJSONContext.Default) ?? new GlobalPerfData(); + tempData = globalPerfJson.Deserialize(GenshinSettingsJSONContext.Default.GlobalPerfData) ?? new GlobalPerfData(); } // Initialize globalPerf with a preset @@ -492,7 +492,7 @@ public string Save() new PerfDataItem(19, (int)GlobalIllumination, portedVersion), new PerfDataItem(21, (int)DynamicCharacterResolution, portedVersion), }; - string data = this.Serialize(GenshinSettingsJSONContext.Default, false); + string data = this.Serialize(GenshinSettingsJSONContext.Default.GlobalPerfData, false); #if DEBUG LogWriteLine($"Saved Genshin GlobalPerfData\r\n{data}", LogType.Debug, true); #endif diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/Genshin/RegistryClass/GraphicsData.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/Genshin/RegistryClass/GraphicsData.cs index 773e3256a..7f4bfd12b 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/Genshin/RegistryClass/GraphicsData.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/Genshin/RegistryClass/GraphicsData.cs @@ -17,7 +17,7 @@ internal class GraphicsData #nullable enable public static GraphicsData Load(string graphicsJson) { - return graphicsJson.Deserialize(GenshinSettingsJSONContext.Default) ?? new GraphicsData(); + return graphicsJson.Deserialize(GenshinSettingsJSONContext.Default.GraphicsData) ?? new GraphicsData(); } public string Create(GlobalPerfData globalPerf) @@ -45,7 +45,7 @@ public string Create(GlobalPerfData globalPerf) new GenshinKeyValuePair(21, (int)globalPerf.DynamicCharacterResolution + 1), }; - string data = this.Serialize(GenshinSettingsJSONContext.Default, false); + string data = this.Serialize(GenshinSettingsJSONContext.Default.GraphicsData, false); #if DEBUG LogWriteLine($"Saved Genshin GraphicsData\r\n{data}", LogType.Debug, true); #endif diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/Enums/Enums.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/Enums/Enums.cs index 724fb2bf7..4e58360d1 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/Enums/Enums.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/Enums/Enums.cs @@ -5,10 +5,32 @@ namespace CollapseLauncher.GameSettings.Honkai.Enums { /// - /// This selection has 4 name types: Low (0), Middle (1), High (2), VHigh (3)
+ /// This selection has 10 name types: + /// - 0.6 (Quality06) + /// - 0.8 (Quality08) + /// - 0.9 (Quality09) + /// - 1.0 (Low) + /// - 1.1 (Quality11) + /// - 1.2 (Middle) + /// - 1.3 (Quality13) + /// - 1.4 (Quality14) + /// - 1.5 (High) + /// - 1.6 (VHigh) ///
[JsonConverter(typeof(JsonStringEnumConverter))] - internal enum SelectResolutionQuality { Low, Middle, High, VHigh } + internal enum SelectResolutionQuality + { + Quality06, + Quality08, + Quality09, + Low, + Quality11, + Middle, + Quality13, + Quality14, + High, + VHigh + } /// /// This selection has 5 name types: DISABLED (0), LOW (1), MIDDLE (2), HIGH (3), ULTRA (4)
diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/PersonalAudioSetting.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/PersonalAudioSetting.cs index e70021a9e..34a7253a6 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/PersonalAudioSetting.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/PersonalAudioSetting.cs @@ -187,7 +187,7 @@ public static PersonalAudioSetting Load() #if DEBUG LogWriteLine($"Loaded HI3 Settings: {_ValueName}\r\n{Encoding.UTF8.GetString(byteStr.TrimEnd((byte)0))}", LogType.Debug, true); #endif - return byteStr.Deserialize(HonkaiSettingsJSONContext.Default) ?? new PersonalAudioSetting(); + return byteStr.Deserialize(HonkaiSettingsJSONContext.Default.PersonalAudioSetting) ?? new PersonalAudioSetting(); } } catch (Exception ex) @@ -211,7 +211,7 @@ public void Save() { if (RegistryRoot == null) throw new NullReferenceException($"Cannot save {_ValueName} since RegistryKey is unexpectedly not initialized!"); - string data = this.Serialize(HonkaiSettingsJSONContext.Default); + string data = this.Serialize(HonkaiSettingsJSONContext.Default.PersonalAudioSetting); byte[] dataByte = Encoding.UTF8.GetBytes(data); RegistryRoot.SetValue(_ValueName, dataByte, RegistryValueKind.Binary); diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/PersonalAudioSettingVolume.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/PersonalAudioSettingVolume.cs index 72c515c3d..4ad2f1520 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/PersonalAudioSettingVolume.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/PersonalAudioSettingVolume.cs @@ -81,7 +81,7 @@ public static PersonalAudioSettingVolume Load() #if DEBUG LogWriteLine($"Loaded HI3 Settings: {_ValueName}\r\n{Encoding.UTF8.GetString((byte[])value, 0, ((byte[])value).Length - 1)}", LogType.Debug, true); #endif - return byteStr.Deserialize(HonkaiSettingsJSONContext.Default) ?? new PersonalAudioSettingVolume(); + return byteStr.Deserialize(HonkaiSettingsJSONContext.Default.PersonalAudioSettingVolume) ?? new PersonalAudioSettingVolume(); } } catch (Exception ex) @@ -104,7 +104,7 @@ public void Save() { if (RegistryRoot == null) throw new NullReferenceException($"Cannot save {_ValueName} since RegistryKey is unexpectedly not initialized!"); - string data = this.Serialize(HonkaiSettingsJSONContext.Default); + string data = this.Serialize(HonkaiSettingsJSONContext.Default.PersonalAudioSettingVolume); byte[] dataByte = Encoding.UTF8.GetBytes(data); RegistryRoot.SetValue(_ValueName, dataByte, RegistryValueKind.Binary); diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/PersonalGraphicsSettingV2.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/PersonalGraphicsSettingV2.cs index 1b99d4920..c9c9a4f6e 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/PersonalGraphicsSettingV2.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/PersonalGraphicsSettingV2.cs @@ -23,7 +23,7 @@ internal class PersonalGraphicsSettingV2 : IGameSettingsValue /// This defines "Rendering Accuracy" combobox In-game settings -> Video.
- /// + ///
/// Default: Middle ///
public SelectResolutionQuality ResolutionQuality { get; set; } = SelectResolutionQuality.Middle; @@ -190,7 +190,7 @@ public static PersonalGraphicsSettingV2 Load() #if DEBUG LogWriteLine($"Loaded HI3 Settings: {_ValueName}\r\n{Encoding.UTF8.GetString(byteStr.TrimEnd((byte)0))}", LogType.Debug, true); #endif - return byteStr.Deserialize(HonkaiSettingsJSONContext.Default) ?? new PersonalGraphicsSettingV2(); + return byteStr.Deserialize(HonkaiSettingsJSONContext.Default.PersonalGraphicsSettingV2) ?? new PersonalGraphicsSettingV2(); } } catch (Exception ex) @@ -214,7 +214,7 @@ public void Save() { if (RegistryRoot == null) throw new NullReferenceException($"Cannot save {_ValueName} since RegistryKey is unexpectedly not initialized!"); - string data = this.Serialize(HonkaiSettingsJSONContext.Default); + string data = this.Serialize(HonkaiSettingsJSONContext.Default.PersonalGraphicsSettingV2); byte[] dataByte = Encoding.UTF8.GetBytes(data); RegistryRoot.SetValue(_ValueName, dataByte, RegistryValueKind.Binary); diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/Preset.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/Preset.cs index 3b9a97321..6fb7082dc 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/Preset.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/Preset.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using static CollapseLauncher.GameSettings.Base.SettingsBase; using static Hi3Helper.Shared.Region.LauncherConfig; @@ -16,7 +16,7 @@ internal class PresetConst public const string DefaultPresetName = "Custom"; } - internal class Preset where T1 : IGameSettingsValue where TObjectType : JsonSerializerContext + internal class Preset where T1 : IGameSettingsValue { #nullable enable #region Fields @@ -51,11 +51,11 @@ public Dictionary? Presets #endregion #region Methods - public Preset(string presetJSONPath, TObjectType jsonContext) + public Preset(string presetJSONPath, JsonTypeInfo?> jsonType) { using (FileStream fs = new FileStream(presetJSONPath, FileMode.Open, FileAccess.Read)) { - Presets = fs.Deserialize>(jsonContext); + Presets = fs.Deserialize(jsonType); PresetKeys = GetPresetKeys(); } } @@ -66,10 +66,10 @@ public Preset(string presetJSONPath, TObjectType jsonContext) /// The type of the game /// JSON source generation context /// The instance of preset - public static Preset LoadPreset(GameNameType gameType, TObjectType jsonContext) + public static Preset LoadPreset(GameNameType gameType, JsonTypeInfo?> jsonType) { string presetPath = Path.Combine(AppFolder, $"Assets\\Presets\\{gameType}\\", $"{typeof(T1).Name}.json"); - return new Preset(presetPath, jsonContext); + return new Preset(presetPath, jsonType); } /// The key of the preset diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/ScreenSettingData.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/ScreenSettingData.cs index 566e73fdd..1b22264a0 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/ScreenSettingData.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/RegistryClass/ScreenSettingData.cs @@ -105,7 +105,7 @@ public static ScreenSettingData Load() #if DEBUG LogWriteLine($"Loaded HI3 Settings: {_ValueName}\r\n{Encoding.UTF8.GetString(byteStr.TrimEnd((byte)0))}", LogType.Debug, true); #endif - return byteStr.Deserialize(HonkaiSettingsJSONContext.Default) ?? new ScreenSettingData(); + return byteStr.Deserialize(HonkaiSettingsJSONContext.Default.ScreenSettingData) ?? new ScreenSettingData(); } } catch (Exception ex) @@ -129,7 +129,7 @@ public override void Save() { if (RegistryRoot == null) throw new NullReferenceException($"Cannot save {_ValueName} since RegistryKey is unexpectedly not initialized!"); - string data = this.Serialize(HonkaiSettingsJSONContext.Default); + string data = this.Serialize(HonkaiSettingsJSONContext.Default.ScreenSettingData); byte[] dataByte = Encoding.UTF8.GetBytes(data); RegistryRoot.SetValue(_ValueName, dataByte, RegistryValueKind.Binary); diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/Settings.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/Settings.cs index 1e9e1adaf..c47446cb6 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/Settings.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/Honkai/Settings.cs @@ -38,7 +38,7 @@ public sealed override void InitializeSettings() base.InitializeSettings(); // Load Preset - Preset_SettingsGraphics = Preset.LoadPreset(GameNameType.Honkai, HonkaiSettingsJSONContext.Default); + Preset_SettingsGraphics = Preset.LoadPreset(GameNameType.Honkai, HonkaiSettingsJSONContext.Default.DictionaryStringPersonalGraphicsSettingV2); } public override void ReloadSettings() => InitializeSettings(); diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/StarRail/RegistryClass/Model.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/StarRail/RegistryClass/Model.cs index a6d0521f5..0007a6f1e 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/StarRail/RegistryClass/Model.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/StarRail/RegistryClass/Model.cs @@ -307,7 +307,7 @@ private static Model LoadCustom() LogWriteLine($"Loaded StarRail Settings: {_ValueName}\r\n{Encoding.UTF8.GetString((byte[])value, 0, ((byte[])value).Length - 1)}", LogType.Debug, true); #endif - return byteStr.Deserialize(StarRailSettingsJSONContext.Default) ?? new Model(); + return byteStr.Deserialize(StarRailSettingsJSONContext.Default.Model) ?? new Model(); } } catch (Exception ex) @@ -340,7 +340,7 @@ public void Save() RegistryRoot.SetValue(_GraphicsQuality, Quality.Custom, RegistryValueKind.DWord); - string data = this.Serialize(StarRailSettingsJSONContext.Default); + string data = this.Serialize(StarRailSettingsJSONContext.Default.Model); byte[] dataByte = Encoding.UTF8.GetBytes(data); RegistryRoot.SetValue(_ValueName, dataByte, RegistryValueKind.Binary); #if DEBUG diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/StarRail/RegistryClass/PCResolution.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/StarRail/RegistryClass/PCResolution.cs index 67f9881a3..c763043e9 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/StarRail/RegistryClass/PCResolution.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/StarRail/RegistryClass/PCResolution.cs @@ -115,7 +115,7 @@ public static PCResolution Load() #if DEBUG LogWriteLine($"Loaded StarRail Settings: {_ValueName}\r\n{Encoding.UTF8.GetString(byteStr.TrimEnd((byte)0))}", LogType.Debug, true); #endif - return byteStr.Deserialize(StarRailSettingsJSONContext.Default) ?? new PCResolution(); + return byteStr.Deserialize(StarRailSettingsJSONContext.Default.PCResolution) ?? new PCResolution(); } } catch (Exception ex) @@ -139,7 +139,7 @@ public override void Save() { if (RegistryRoot == null) throw new NullReferenceException($"Cannot save {_ValueName} since RegistryKey is unexpectedly not initialized!"); - string data = this.Serialize(StarRailSettingsJSONContext.Default); + string data = this.Serialize(StarRailSettingsJSONContext.Default.PCResolution); byte[] dataByte = Encoding.UTF8.GetBytes(data); RegistryRoot.SetValue(_ValueName, dataByte, RegistryValueKind.Binary); diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/Universal/RegistryClass/CollapseMiscSetting.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/Universal/RegistryClass/CollapseMiscSetting.cs index 9498d77ca..3413e749d 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/Universal/RegistryClass/CollapseMiscSetting.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/Universal/RegistryClass/CollapseMiscSetting.cs @@ -129,7 +129,7 @@ public static CollapseMiscSetting Load() #if DEBUG LogWriteLine($"Loaded Collapse Misc Settings:\r\n{Encoding.UTF8.GetString(byteStr.TrimEnd((byte)0))}", LogType.Debug, true); #endif - return byteStr.Deserialize(UniversalSettingsJSONContext.Default) ?? new CollapseMiscSetting(); + return byteStr.Deserialize(UniversalSettingsJSONContext.Default.CollapseMiscSetting) ?? new CollapseMiscSetting(); } } catch ( Exception ex ) @@ -150,7 +150,7 @@ public void Save() { if (RegistryRoot == null) throw new NullReferenceException($"Cannot save {_ValueName} since RegistryKey is unexpectedly not initialized!"); - string data = this.Serialize(UniversalSettingsJSONContext.Default, true); + string data = this.Serialize(UniversalSettingsJSONContext.Default.CollapseMiscSetting, true); byte[] dataByte = Encoding.UTF8.GetBytes(data); #if DEBUG LogWriteLine($"Saved Collapse Misc Settings:\r\n{data}", LogType.Debug, true); diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/Universal/RegistryClass/CollapseScreenSetting.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/Universal/RegistryClass/CollapseScreenSetting.cs index 13692b7c4..440d7b646 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/Universal/RegistryClass/CollapseScreenSetting.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/Universal/RegistryClass/CollapseScreenSetting.cs @@ -70,7 +70,7 @@ public static CollapseScreenSetting Load() #if DEBUG LogWriteLine($"Loaded Collapse Screen Settings:\r\n{Encoding.UTF8.GetString(byteStr.TrimEnd((byte)0))}", LogType.Debug, true); #endif - return byteStr.Deserialize(UniversalSettingsJSONContext.Default) ?? new CollapseScreenSetting(); + return byteStr.Deserialize(UniversalSettingsJSONContext.Default.CollapseScreenSetting) ?? new CollapseScreenSetting(); } } catch (Exception ex) @@ -87,7 +87,7 @@ public void Save() { if (RegistryRoot == null) throw new NullReferenceException($"Cannot save {_ValueName} since RegistryKey is unexpectedly not initialized!"); - string data = this.Serialize(UniversalSettingsJSONContext.Default, true); + string data = this.Serialize(UniversalSettingsJSONContext.Default.CollapseScreenSetting, true); byte[] dataByte = Encoding.UTF8.GetBytes(data); #if DEBUG LogWriteLine($"Saved Collapse Screen Settings:\r\n{data}", LogType.Debug, true); diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/Zenless/FileClass/GeneralData.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/Zenless/FileClass/GeneralData.cs index 0530291aa..ad2b16858 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/Zenless/FileClass/GeneralData.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/Zenless/FileClass/GeneralData.cs @@ -5,6 +5,7 @@ using System; using System.Text.Json.Nodes; using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; // ReSharper disable ReturnTypeCanBeNotNullable #nullable enable @@ -569,10 +570,10 @@ public bool Audio_MuteOnMinimize [Obsolete("Loading settings with Load() is not supported for IGameSettingsValueMagic member. Use LoadWithMagic() instead!", true)] public new static GeneralData Load() => throw new NotSupportedException("Loading settings with Load() is not supported for IGameSettingsValueMagic member. Use LoadWithMagic() instead!"); - public new static GeneralData LoadWithMagic(byte[] magic, SettingsGameVersionManager versionManager, - JsonSerializerContext context) + public new static GeneralData LoadWithMagic(byte[] magic, SettingsGameVersionManager versionManager, + JsonTypeInfo typeInfo) { - var returnVal = MagicNodeBaseValues.LoadWithMagic(magic, versionManager, context); + var returnVal = MagicNodeBaseValues.LoadWithMagic(magic, versionManager, typeInfo); #if DEBUG const bool isPrintDebug = true; diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/Zenless/Settings.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/Zenless/Settings.cs index 5fdd3427e..d6cbd0771 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/Zenless/Settings.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/Zenless/Settings.cs @@ -53,7 +53,7 @@ public sealed override void InitializeSettings() GeneralData = GeneralData.LoadWithMagic( MagicReDo, SettingsGameVersionManager.Create(_gameVersionManager, ZZZSettingsConfigFile, "GENERAL_DATA.bin"), - ZenlessSettingsJSONContext.Default); + ZenlessSettingsJSONContext.Default.GeneralData); } public override void ReloadSettings() diff --git a/CollapseLauncher/Classes/GameManagement/GameSettings/Zenless/Sleepy.cs b/CollapseLauncher/Classes/GameManagement/GameSettings/Zenless/Sleepy.cs index a4d0ea4cd..417adbd84 100644 --- a/CollapseLauncher/Classes/GameManagement/GameSettings/Zenless/Sleepy.cs +++ b/CollapseLauncher/Classes/GameManagement/GameSettings/Zenless/Sleepy.cs @@ -166,6 +166,13 @@ private static unsafe int InternalDecode(ReadOnlySpan magic, bool* evil, B internal static void WriteString(string filePath, ReadOnlySpan content, ReadOnlySpan magic) { + // Ensure the folder always exist + string? fileDir = Path.GetDirectoryName(filePath); + if (!string.IsNullOrEmpty(fileDir) && !Directory.Exists(fileDir)) + { + Directory.CreateDirectory(fileDir); + } + // Get the FileInfo FileInfo fileInfo = new FileInfo(filePath); diff --git a/CollapseLauncher/Classes/GameManagement/GameVersion/BaseClass/GameVersionBase.cs b/CollapseLauncher/Classes/GameManagement/GameVersion/BaseClass/GameVersionBase.cs index 7288beb8d..a4af4465f 100644 --- a/CollapseLauncher/Classes/GameManagement/GameVersion/BaseClass/GameVersionBase.cs +++ b/CollapseLauncher/Classes/GameManagement/GameVersion/BaseClass/GameVersionBase.cs @@ -83,7 +83,7 @@ private IniValue GenerateUAPCValue() } } }; - string uapcValue = uapc.Serialize(InternalAppJSONContext.Default, false); + string uapcValue = uapc.Serialize(InternalAppJSONContext.Default.DictionaryStringDictionaryStringString, false); return new IniValue(uapcValue); } @@ -580,7 +580,7 @@ public virtual async ValueTask CheckSdkUpdate(string validatePath) if (string.IsNullOrEmpty(line)) continue; - PkgVersionProperties pkgVersion = line.Deserialize(CoreLibraryJSONContext.Default); + PkgVersionProperties pkgVersion = line.Deserialize(CoreLibraryJSONContext.Default.PkgVersionProperties); if (pkgVersion != null) { diff --git a/CollapseLauncher/Classes/GamePropertyVault.cs b/CollapseLauncher/Classes/GamePropertyVault.cs index 461129a1d..cc02bf1c6 100644 --- a/CollapseLauncher/Classes/GamePropertyVault.cs +++ b/CollapseLauncher/Classes/GamePropertyVault.cs @@ -1,4 +1,5 @@ -using CollapseLauncher.GameSettings.Genshin; +using CollapseLauncher.GamePlaytime; +using CollapseLauncher.GameSettings.Genshin; using CollapseLauncher.GameSettings.Honkai; using CollapseLauncher.GameSettings.StarRail; using CollapseLauncher.GameSettings.Zenless; @@ -65,12 +66,15 @@ internal GamePresetProperty(UIElement UIElementParent, RegionResourceProp APIRes default: throw new NotSupportedException($"[GamePresetProperty.Ctor] Game type: {GamePreset.GameType} ({GamePreset.ProfileName} - {GamePreset.ZoneName}) is not supported!"); } + + _GamePlaytime = new Playtime(_GameVersion); } } internal RegionResourceProp _APIResouceProp { get; set; } internal PresetConfig _GamePreset { get => _GameVersion.GamePreset; } internal IGameSettings _GameSettings { get; set; } + internal IGamePlaytime _GamePlaytime { get; set; } internal IRepair _GameRepair { get; set; } internal ICache _GameCache { get; set; } internal IGameVersionCheck _GameVersion { get; set; } @@ -161,13 +165,15 @@ public void Dispose() _GameRepair?.Dispose(); _GameCache?.Dispose(); _GameInstall?.Dispose(); + _GamePlaytime?.Dispose(); _APIResouceProp = null; - _GameSettings = null; - _GameRepair = null; - _GameCache = null; - _GameVersion = null; - _GameInstall = null; + _GameSettings = null; + _GameRepair = null; + _GameCache = null; + _GameVersion = null; + _GameInstall = null; + _GamePlaytime = null; } } diff --git a/CollapseLauncher/Classes/Helper/Animation/AnimationHelper.cs b/CollapseLauncher/Classes/Helper/Animation/AnimationHelper.cs index ee5329d0f..4f33ad75b 100644 --- a/CollapseLauncher/Classes/Helper/Animation/AnimationHelper.cs +++ b/CollapseLauncher/Classes/Helper/Animation/AnimationHelper.cs @@ -1,5 +1,5 @@ -using CommunityToolkit.WinUI.Controls; -using Hi3Helper; +using Hi3Helper; +using Hi3Helper.CommunityToolkit.WinUI.Controls; using Microsoft.UI.Composition; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -8,6 +8,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; +// ReSharper disable CheckNamespace namespace CollapseLauncher.Helper.Animation { @@ -65,26 +66,26 @@ internal static async Task StartAnimation(this UIElement element, TimeSpan durat { foreach (KeyFrameAnimation anim in animBase!) { - if (element?.DispatcherQueue?.HasThreadAccess ?? false) + if (element.DispatcherQueue?.HasThreadAccess ?? false) { anim.Duration = duration; anim.StopBehavior = AnimationStopBehavior.LeaveCurrentValue; animGroup.Add(anim); } else - element?.DispatcherQueue?.TryEnqueue(() => + element.DispatcherQueue?.TryEnqueue(() => { anim.Duration = duration; anim.StopBehavior = AnimationStopBehavior.LeaveCurrentValue; animGroup.Add(anim); }); } - if (element?.DispatcherQueue?.HasThreadAccess ?? false) + if (element.DispatcherQueue?.HasThreadAccess ?? false) { element.StartAnimation(animGroup); } else - element?.DispatcherQueue?.TryEnqueue(() => + element.DispatcherQueue?.TryEnqueue(() => { element.StartAnimation(animGroup); }); @@ -123,69 +124,85 @@ internal static void EnableSingleImplicitAnimation(this UIElement element, rootFrameVisual.ImplicitAnimations = animationCollection; } - internal static void EnableImplicitAnimation(this UIElement element, - bool recursiveAssignment = false, - CompositionEasingFunction easingFunction = null) + internal static void EnableImplicitAnimation(this UIElement element, bool recursiveAssignment = false, CompositionEasingFunction easingFunction = null) { - try + while (true) { - Visual rootFrameVisual = ElementCompositionPreview.GetElementVisual(element); - Compositor compositor = CompositionTarget.GetCompositorForCurrentThread(); - - ImplicitAnimationCollection animationCollection = - rootFrameVisual.ImplicitAnimations != null ? - rootFrameVisual.ImplicitAnimations : compositor!.CreateImplicitAnimationCollection(); - - foreach (VisualPropertyType type in Enum.GetValues()) + try { - KeyFrameAnimation animation = CreateAnimationByType(compositor, type, 250, 0, easingFunction); + Visual rootFrameVisual = ElementCompositionPreview.GetElementVisual(element); + Compositor compositor = CompositionTarget.GetCompositorForCurrentThread(); - if (animation != null) - { - animationCollection[type.ToString()] = animation; - } - } + ImplicitAnimationCollection animationCollection = rootFrameVisual.ImplicitAnimations != null ? rootFrameVisual.ImplicitAnimations : compositor!.CreateImplicitAnimationCollection(); - rootFrameVisual.ImplicitAnimations = animationCollection; - element.EnableElementVisibilityAnimation(); - } - catch (Exception ex) - { - Logger.LogWriteLine($"[AnimationHelper::EnableImplicitAnimation()] Error has occurred while assigning Implicit Animation to the element!\r\n{ex}", LogType.Error, true); - } + foreach (VisualPropertyType type in Enum.GetValues()) + { + KeyFrameAnimation animation = CreateAnimationByType(compositor, type, 250, 0, easingFunction); - if (!recursiveAssignment) return; + if (animation != null) + { + animationCollection[type.ToString()] = animation; + } + } - if (element is Button button && button.Content is UIElement buttonContent) - buttonContent.EnableImplicitAnimation(recursiveAssignment, easingFunction); + rootFrameVisual.ImplicitAnimations = animationCollection; + element.EnableElementVisibilityAnimation(); + } + catch (Exception ex) + { + Logger.LogWriteLine($"[AnimationHelper::EnableImplicitAnimation()] Error has occurred while assigning Implicit Animation to the element!\r\n{ex}", LogType.Error, true); + } - if (element is Page page && page.Content is UIElement pageContent) - pageContent.EnableImplicitAnimation(recursiveAssignment, easingFunction); + if (!recursiveAssignment) return; - if (element is NavigationView navigationView && navigationView.Content is UIElement navigationViewContent) - navigationViewContent.EnableImplicitAnimation(recursiveAssignment, easingFunction); + switch (element) + { + case Button { Content: UIElement buttonContent }: + buttonContent.EnableImplicitAnimation(true, easingFunction); + break; + case Page { Content: not null } page: + page.Content.EnableImplicitAnimation(true, easingFunction); + break; + case NavigationView { Content: UIElement navigationViewContent }: + navigationViewContent.EnableImplicitAnimation(true, easingFunction); + break; + case Panel panel: + { + foreach (UIElement childrenElement in panel.Children!) childrenElement.EnableImplicitAnimation(true, easingFunction); + break; + } + case ScrollViewer { Content: UIElement elementInner }: + elementInner.EnableImplicitAnimation(true, easingFunction); + break; + } - if (element is Panel panel) - foreach (UIElement childrenElement in panel.Children!) - childrenElement.EnableImplicitAnimation(recursiveAssignment, easingFunction); + switch (element) + { + case ContentControl { Content: UIElement contentControlInner } contentControl and (SettingsCard or Expander): + { + contentControlInner.EnableImplicitAnimation(true, easingFunction); - if (element is ScrollViewer scrollViewer && scrollViewer.Content is UIElement elementInner) - elementInner.EnableImplicitAnimation(recursiveAssignment, easingFunction); + if (contentControl is Expander { Header: UIElement expanderHeader }) + { + element = expanderHeader; + recursiveAssignment = true; + continue; + } - if (element is ContentControl contentControl && (element is SettingsCard || element is Expander) && contentControl.Content is UIElement contentControlInner) - { - contentControlInner.EnableImplicitAnimation(true, easingFunction); + break; + } + case InfoBar { Content: UIElement infoBarInner }: + element = infoBarInner; + recursiveAssignment = true; + continue; + } - if (contentControl is Expander expander && expander.Header is UIElement expanderHeader) - expanderHeader.EnableImplicitAnimation(true, easingFunction); + break; } - - if (element is InfoBar infoBar && infoBar.Content is UIElement infoBarInner) - infoBarInner.EnableImplicitAnimation(true, easingFunction); } private static KeyFrameAnimation CreateAnimationByType(Compositor compositor, VisualPropertyType type, - double duration = 800, double delay = 0, CompositionEasingFunction easing = null) + double duration = 800, double delay = 0, CompositionEasingFunction easing = null) { KeyFrameAnimation animation; @@ -202,6 +219,8 @@ private static KeyFrameAnimation CreateAnimationByType(Compositor compositor, Vi case VisualPropertyType.RotationAngleInDegrees: animation = compositor.CreateScalarKeyFrameAnimation(); break; + case VisualPropertyType.None: + case VisualPropertyType.All: default: return null; } @@ -221,25 +240,25 @@ public static void EnableElementVisibilityAnimation(this UIElement element, Comp ElementCompositionPreview.SetIsTranslationEnabled(element, true); - ScalarKeyFrameAnimation HideOpacityAnimation = compositor.CreateScalarKeyFrameAnimation(); - ScalarKeyFrameAnimation ShowOpacityAnimation = compositor.CreateScalarKeyFrameAnimation(); + ScalarKeyFrameAnimation hideOpacityAnimation = compositor.CreateScalarKeyFrameAnimation(); + ScalarKeyFrameAnimation showOpacityAnimation = compositor.CreateScalarKeyFrameAnimation(); - HideOpacityAnimation.InsertKeyFrame(1.0f, 0.0f); - HideOpacityAnimation.Duration = animDur; - HideOpacityAnimation.Target = "Opacity"; + hideOpacityAnimation.InsertKeyFrame(1.0f, 0.0f); + hideOpacityAnimation.Duration = animDur; + hideOpacityAnimation.Target = "Opacity"; - ShowOpacityAnimation.InsertKeyFrame(1.0f, 1.0f); - ShowOpacityAnimation.Duration = animDur; - ShowOpacityAnimation.Target = "Opacity"; + showOpacityAnimation.InsertKeyFrame(1.0f, 1.0f); + showOpacityAnimation.Duration = animDur; + showOpacityAnimation.Target = "Opacity"; - CompositionAnimationGroup HideAnimationGroup = compositor.CreateAnimationGroup(); - CompositionAnimationGroup ShowAnimationGroup = compositor.CreateAnimationGroup(); + CompositionAnimationGroup hideAnimationGroup = compositor.CreateAnimationGroup(); + CompositionAnimationGroup showAnimationGroup = compositor.CreateAnimationGroup(); - HideAnimationGroup.Add(HideOpacityAnimation); - ShowAnimationGroup.Add(ShowOpacityAnimation); + hideAnimationGroup.Add(hideOpacityAnimation); + showAnimationGroup.Add(showOpacityAnimation); - ElementCompositionPreview.SetImplicitHideAnimation(element, HideAnimationGroup); - ElementCompositionPreview.SetImplicitShowAnimation(element, ShowAnimationGroup); + ElementCompositionPreview.SetImplicitHideAnimation(element, hideAnimationGroup); + ElementCompositionPreview.SetImplicitShowAnimation(element, showAnimationGroup); } internal static Compositor GetElementCompositor(this UIElement element) diff --git a/CollapseLauncher/Classes/Helper/Background/BackgroundMediaUtility.cs b/CollapseLauncher/Classes/Helper/Background/BackgroundMediaUtility.cs index 8455b89dc..f5fcc253d 100644 --- a/CollapseLauncher/Classes/Helper/Background/BackgroundMediaUtility.cs +++ b/CollapseLauncher/Classes/Helper/Background/BackgroundMediaUtility.cs @@ -86,6 +86,14 @@ internal static async Task CreateInstanceAsync(Framework Grid bgOverlayTitleBar, Grid bgImageGridBackground, Grid bgMediaPlayerGrid) { + CurrentAppliedMediaPath = null; + CurrentAppliedMediaType = MediaType.Unknown; + if (_alternativeFileStream != null) + { + await _alternativeFileStream.DisposeAsync(); + _alternativeFileStream = null; + } + // Set the parent UI FrameworkElement? ui = parentUI; @@ -484,4 +492,4 @@ public static MediaType GetMediaType(string mediaPath) return SupportedMediaPlayerExt.Contains(extension, StringComparer.OrdinalIgnoreCase) ? MediaType.Media : MediaType.Unknown; } } -} \ No newline at end of file +} diff --git a/CollapseLauncher/Classes/Helper/HttpClientBuilder.cs b/CollapseLauncher/Classes/Helper/HttpClientBuilder.cs index 14dfcf285..d48104dc3 100644 --- a/CollapseLauncher/Classes/Helper/HttpClientBuilder.cs +++ b/CollapseLauncher/Classes/Helper/HttpClientBuilder.cs @@ -230,6 +230,7 @@ public HttpClient Create() socketsHttpHandler.UseCookies = IsAllowHttpCookies; socketsHttpHandler.AutomaticDecompression = DecompressionMethod; socketsHttpHandler.EnableMultipleHttp2Connections = true; + socketsHttpHandler.EnableMultipleHttp3Connections = true; // Toggle for allowing untrusted cert if (IsAllowUntrustedCert) diff --git a/CollapseLauncher/Classes/Helper/ILoggerHelper.cs b/CollapseLauncher/Classes/Helper/ILoggerHelper.cs new file mode 100644 index 000000000..67b113c53 --- /dev/null +++ b/CollapseLauncher/Classes/Helper/ILoggerHelper.cs @@ -0,0 +1,49 @@ +#if USEVELOPACK +using Hi3Helper; +using Microsoft.Extensions.Logging; +using System; + +namespace CollapseLauncher.Helper +{ + internal static class ILoggerHelper + { + internal static ILogger CreateCollapseILogger() => new CollapseILoggerWrapper(); + } + + public class CollapseILoggerWrapper : ILogger + { + internal CollapseILoggerWrapper() { } + + public IDisposable BeginScope(TState state) + where TState : notnull => default; + + public bool IsEnabled(LogLevel logLevel) => true; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + LogType logType = logLevel switch + { + LogLevel.Trace => LogType.Debug, + LogLevel.Debug => LogType.Debug, + LogLevel.Information => LogType.Default, + LogLevel.Warning => LogType.Warning, + LogLevel.Error => LogType.Error, + LogLevel.Critical => LogType.Error, + LogLevel.None => LogType.NoTag, + _ => LogType.Default + }; + + bool isWriteToLog = logType switch + { + LogType.Error => true, + LogType.Warning => true, + LogType.Debug => true, + _ => false + }; + + string message = formatter(state, exception); + Logger.LogWriteLine(message, logType, isWriteToLog); + } + } +} +#endif \ No newline at end of file diff --git a/CollapseLauncher/Classes/Helper/Image/ImageLoaderHelper.cs b/CollapseLauncher/Classes/Helper/Image/ImageLoaderHelper.cs index 1f6e871c8..fdeae5577 100644 --- a/CollapseLauncher/Classes/Helper/Image/ImageLoaderHelper.cs +++ b/CollapseLauncher/Classes/Helper/Image/ImageLoaderHelper.cs @@ -3,7 +3,6 @@ using CollapseLauncher.Extension; using CollapseLauncher.Helper.Background; using CommunityToolkit.WinUI.Animations; -using CommunityToolkit.WinUI.Controls; using CommunityToolkit.WinUI.Media; using Hi3Helper; using Hi3Helper.Data; @@ -28,6 +27,12 @@ using static Hi3Helper.Shared.Region.LauncherConfig; using Orientation = Microsoft.UI.Xaml.Controls.Orientation; +using ImageBlendBrush = Hi3Helper.CommunityToolkit.WinUI.Media.ImageBlendBrush; +using ImageCropper = Hi3Helper.CommunityToolkit.WinUI.Controls.ImageCropper; +using CropShape = Hi3Helper.CommunityToolkit.WinUI.Controls.CropShape; +using ThumbPlacement = Hi3Helper.CommunityToolkit.WinUI.Controls.ThumbPlacement; +using BitmapFileFormat = Hi3Helper.CommunityToolkit.WinUI.Controls.BitmapFileFormat; + namespace CollapseLauncher.Helper.Image { internal static class ImageLoaderHelper @@ -152,7 +157,7 @@ internal static async Task LoadImage(string path, bool isUseImageCro } private static async Task SpawnImageCropperDialog(string filePath, string cachedFilePath, - uint ToWidth, uint ToHeight) + uint toWidth, uint toHeight) { Grid parentGrid = new() { @@ -169,15 +174,19 @@ private static async Task SpawnImageCropperDialog(string filePath, s imageCropper.HorizontalAlignment = HorizontalAlignment.Stretch; imageCropper.VerticalAlignment = VerticalAlignment.Stretch; imageCropper.Opacity = 0; + + // Path of image + Uri overlayImageUri = new Uri(Path.Combine(AppFolder!, @"Assets\Images\ImageCropperOverlay", + GetAppConfigValue("WindowSizeProfile").ToString() == "Small" ? "small.png" : "normal.png")); + // Why not use ImageBrush? // https://github.com/microsoft/microsoft-ui-xaml/issues/7809 imageCropper.Overlay = new ImageBlendBrush() { - Source = new BitmapImage(new Uri(Path.Combine(AppFolder!, @"Assets\Images\ImageCropperOverlay", - GetAppConfigValue("WindowSizeProfile").ToString() == "Small" ? "small.png" : "normal.png"))), Opacity = 0.5, Stretch = Stretch.Fill, Mode = ImageBlendMode.Multiply, + SourceUri = overlayImageUri }; ContentDialogOverlay dialogOverlay = new ContentDialogOverlay(ContentDialogTheme.Informational) @@ -215,7 +224,7 @@ private static async Task SpawnImageCropperDialog(string filePath, s } FileInfo cachedFileInfo = new FileInfo(cachedFilePath); - return await GenerateCachedStream(cachedFileInfo, ToWidth, ToHeight, true); + return await GenerateCachedStream(cachedFileInfo, toWidth, toHeight, true); } private static async void LoadImageCropperDetached(string filePath, ImageCropper imageCropper, @@ -392,21 +401,25 @@ public static bool IsFileCompletelyDownloaded(FileInfo fileInfo) // Try to get the prop file which includes the filename + the suggested size provided // by the network stream if it has been downloaded before - string propFilePath = Directory.EnumerateFiles(outputParentPath, $"{outputFileName}#*", SearchOption.TopDirectoryOnly).FirstOrDefault(); - // Check if the file is found (not null), then try parse the information - if (string.IsNullOrEmpty(propFilePath)) + if (outputParentPath != null) { - return false; - } + string propFilePath = Directory.EnumerateFiles(outputParentPath, $"{outputFileName}#*", SearchOption.TopDirectoryOnly).FirstOrDefault(); + // Check if the file is found (not null), then try parse the information + if (string.IsNullOrEmpty(propFilePath)) + { + return false; + } - // Try split the filename into a segment by # char - string[] propSegment = Path.GetFileName(propFilePath).Split('#'); - // Assign the check if the condition met and set the file existence status - return propSegment.Length >= 2 - && long.TryParse(propSegment[1], null, out long suggestedSize) - && fileInfo.Exists && fileInfo.Length == suggestedSize; + // Try split the filename into a segment by # char + string[] propSegment = Path.GetFileName(propFilePath).Split('#'); + // Assign the check if the condition met and set the file existence status + return propSegment.Length >= 2 + && long.TryParse(propSegment[1], null, out long suggestedSize) + && fileInfo.Exists && fileInfo.Length == suggestedSize; + } // If the prop doesn't exist, then return false to assume that the file doesn't exist + return false; } public static async void TryDownloadToCompletenessAsync(string url, FileInfo fileInfo, CancellationToken token) @@ -435,8 +448,11 @@ public static async ValueTask TryDownloadToCompleteness(string url, FileInfo fil // Create the prop file for download completeness checking string outputParentPath = Path.GetDirectoryName(fileInfo.FullName); string outputFilename = Path.GetFileName(fileInfo.FullName); - string propFilePath = Path.Combine(outputParentPath, $"{outputFilename}#{netStream.Length}"); - await File.Create(propFilePath).DisposeAsync(); + if (outputParentPath != null) + { + string propFilePath = Path.Combine(outputParentPath, $"{outputFilename}#{netStream.Length}"); + await File.Create(propFilePath).DisposeAsync(); + } // Copy (and download) the remote streams to local int read; @@ -446,6 +462,9 @@ public static async ValueTask TryDownloadToCompleteness(string url, FileInfo fil Logger.LogWriteLine($"Resource download from: {url} has been completed and stored locally into:" + $"\"{fileInfo.FullName}\" with size: {ConverterTool.SummarizeSizeSimple(fileLength)} ({fileLength} bytes)", LogType.Default, true); } + // Ignore cancellation exceptions + catch (TaskCanceledException) { } + catch (OperationCanceledException) { } #if !DEBUG catch (Exception ex) { diff --git a/CollapseLauncher/Classes/Helper/JsonConverter/RegionResourcePluginValidateConverter.cs b/CollapseLauncher/Classes/Helper/JsonConverter/RegionResourcePluginValidateConverter.cs index a1f48a9c2..334ff9b04 100644 --- a/CollapseLauncher/Classes/Helper/JsonConverter/RegionResourcePluginValidateConverter.cs +++ b/CollapseLauncher/Classes/Helper/JsonConverter/RegionResourcePluginValidateConverter.cs @@ -19,7 +19,7 @@ public override List Read( JsonSerializerOptions options) { string valueString = EmptiedBackslash(reader.ValueSpan); - List returnList = valueString.Deserialize>(InternalAppJSONContext.Default); + List returnList = valueString.Deserialize(InternalAppJSONContext.Default.ListRegionResourcePluginValidate); return returnList; } diff --git a/CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HoYoPlayLauncherApiLoader.cs b/CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HoYoPlayLauncherApiLoader.cs index ae917ff1e..a2f22b1c9 100644 --- a/CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HoYoPlayLauncherApiLoader.cs +++ b/CollapseLauncher/Classes/Helper/LauncherApiLoader/HoYoPlay/HoYoPlayLauncherApiLoader.cs @@ -28,31 +28,41 @@ protected override async Task LoadLauncherGameResource(ActionOnTimeOutRetry? onT ActionTimeoutValueTaskCallback hypResourceResponseCallback = async innerToken => - await FallbackCDNUtil.DownloadAsJSONType(PresetConfig?.LauncherResourceURL, InternalAppJSONContext.Default, innerToken); + await FallbackCDNUtil.DownloadAsJSONType(PresetConfig?.LauncherResourceURL, InternalAppJSONContext.Default.HoYoPlayLauncherResources, innerToken); - HoYoPlayLauncherResources? hypResourceResponse = await hypResourceResponseCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, - ExecutionTimeoutAttempt, onTimeoutRoutine, token); + // Assign as 3 Task array + Task[] tasks = [ + Task.CompletedTask, + Task.CompletedTask, + Task.CompletedTask + ]; + // Init as null first before being assigned when the backing task is called + HoYoPlayLauncherResources? hypResourceResponse = null; HoYoPlayLauncherResources? hypPluginResource = null; HoYoPlayLauncherResources? hypSdkResource = null; + + tasks[0] = hypResourceResponseCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, + ExecutionTimeoutAttempt, onTimeoutRoutine, token).AsTaskAndDoAction((result) => hypResourceResponse = result); + if (!string.IsNullOrEmpty(PresetConfig?.LauncherPluginURL) && (PresetConfig.IsPluginUpdateEnabled ?? false)) { ActionTimeoutValueTaskCallback hypPluginResourceCallback = async innerToken => - await FallbackCDNUtil.DownloadAsJSONType(PresetConfig?.LauncherPluginURL, InternalAppJSONContext.Default, innerToken); + await FallbackCDNUtil.DownloadAsJSONType(PresetConfig?.LauncherPluginURL, InternalAppJSONContext.Default.HoYoPlayLauncherResources, innerToken); - hypPluginResource = await hypPluginResourceCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, - ExecutionTimeoutAttempt, onTimeoutRoutine, token); + tasks[1] = hypPluginResourceCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, + ExecutionTimeoutAttempt, onTimeoutRoutine, token).AsTaskAndDoAction((result) => hypPluginResource = result); } if (!string.IsNullOrEmpty(PresetConfig?.LauncherGameChannelSDKURL)) { ActionTimeoutValueTaskCallback hypSdkResourceCallback = async innerToken => - await FallbackCDNUtil.DownloadAsJSONType(PresetConfig?.LauncherGameChannelSDKURL, InternalAppJSONContext.Default, innerToken); + await FallbackCDNUtil.DownloadAsJSONType(PresetConfig?.LauncherGameChannelSDKURL, InternalAppJSONContext.Default.HoYoPlayLauncherResources, innerToken); - hypSdkResource = await hypSdkResourceCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, - ExecutionTimeoutAttempt, onTimeoutRoutine, token); + tasks[2] = hypSdkResourceCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, + ExecutionTimeoutAttempt, onTimeoutRoutine, token).AsTaskAndDoAction((result) => hypSdkResource = result); } RegionResourceLatest sophonResourceCurrentPackage = new RegionResourceLatest(); @@ -67,6 +77,9 @@ protected override async Task LoadLauncherGameResource(ActionOnTimeOutRetry? onT data = sophonResourceData }; + // Await all callbacks + await Task.WhenAll(tasks).ConfigureAwait(false); + ConvertPluginResources(ref sophonResourceData, hypPluginResource); ConvertSdkResources(ref sophonResourceData, hypSdkResource); ConvertPackageResources(sophonResourceData, hypResourceResponse?.Data?.LauncherPackages); @@ -309,7 +322,7 @@ private void ConvertMultiPackageResource(ref RegionResourceVersion sophonPackage } #endregion - protected override async ValueTask LoadLauncherNews(ActionOnTimeOutRetry? onTimeoutRoutine, CancellationToken token) + protected override async Task LoadLauncherNews(ActionOnTimeOutRetry? onTimeoutRoutine, CancellationToken token) { bool isUseMultiLang = PresetConfig?.LauncherSpriteURLMultiLang ?? false; @@ -317,18 +330,28 @@ protected override async ValueTask LoadLauncherNews(ActionOnTimeOutRetry? onTime string launcherSpriteUrl = string.Format(PresetConfig?.LauncherSpriteURL!, localeCode); string launcherNewsUrl = string.Format(PresetConfig?.LauncherNewsURL!, localeCode); + ActionTimeoutValueTaskCallback hypLauncherBackgroundCallback = async innerToken => - await FallbackCDNUtil.DownloadAsJSONType(launcherSpriteUrl, InternalAppJSONContext.Default, innerToken); + await FallbackCDNUtil.DownloadAsJSONType(launcherSpriteUrl, InternalAppJSONContext.Default.HoYoPlayLauncherNews, innerToken); ActionTimeoutValueTaskCallback hypLauncherNewsCallback = async innerToken => - await FallbackCDNUtil.DownloadAsJSONType(launcherNewsUrl, InternalAppJSONContext.Default, innerToken); + await FallbackCDNUtil.DownloadAsJSONType(launcherNewsUrl, InternalAppJSONContext.Default.HoYoPlayLauncherNews, innerToken); - HoYoPlayLauncherNews? hypLauncherBackground = await hypLauncherBackgroundCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, - ExecutionTimeoutAttempt, onTimeoutRoutine, token); - HoYoPlayLauncherNews? hypLauncherNews = await hypLauncherNewsCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, - ExecutionTimeoutAttempt, onTimeoutRoutine, token); + HoYoPlayLauncherNews? hypLauncherBackground = null; + HoYoPlayLauncherNews? hypLauncherNews = null; + + // Load both in parallel + Task[] tasks = [ + hypLauncherBackgroundCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, + ExecutionTimeoutAttempt, onTimeoutRoutine, token).AsTaskAndDoAction((result) => hypLauncherBackground = result), + hypLauncherNewsCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, + ExecutionTimeoutAttempt, onTimeoutRoutine, token).AsTaskAndDoAction((result) => hypLauncherNews = result) + ]; + + // Await the result + await Task.WhenAll(tasks); // Merge background image if (hypLauncherBackground?.Data?.GameInfoList != null && hypLauncherNews?.Data != null) @@ -447,7 +470,7 @@ private void ConvertLauncherSocialMedia(ref LauncherGameNews? sophonLauncherNews } #endregion - protected override async ValueTask LoadLauncherGameInfo(ActionOnTimeOutRetry? onTimeoutRoutine, CancellationToken token) + protected override async Task LoadLauncherGameInfo(ActionOnTimeOutRetry? onTimeoutRoutine, CancellationToken token) { if (PresetConfig?.LauncherGameInfoDisplayURL == null) { @@ -462,7 +485,7 @@ protected override async ValueTask LoadLauncherGameInfo(ActionOnTimeOutRetry? on ActionTimeoutValueTaskCallback hypLauncherGameInfoCallback = async innerToken => - await FallbackCDNUtil.DownloadAsJSONType(launcherGameInfoUrl, InternalAppJSONContext.Default, innerToken); + await FallbackCDNUtil.DownloadAsJSONType(launcherGameInfoUrl, InternalAppJSONContext.Default.HoYoPlayLauncherGameInfo, innerToken); HoYoPlayLauncherGameInfo? hypLauncherGameInfo = await hypLauncherGameInfoCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, ExecutionTimeoutAttempt, onTimeoutRoutine, token); diff --git a/CollapseLauncher/Classes/Helper/LauncherApiLoader/LauncherApiBase.cs b/CollapseLauncher/Classes/Helper/LauncherApiLoader/LauncherApiBase.cs index 8ef1d36f5..b2c8815fb 100644 --- a/CollapseLauncher/Classes/Helper/LauncherApiLoader/LauncherApiBase.cs +++ b/CollapseLauncher/Classes/Helper/LauncherApiLoader/LauncherApiBase.cs @@ -76,9 +76,11 @@ public async Task LoadAsync(OnLoadAction? beforeLoadRoutine, OnLoa protected virtual async ValueTask LoadAsyncInner(ActionOnTimeOutRetry? onTimeoutRoutine, CancellationToken token) { - await LoadLauncherGameResource(onTimeoutRoutine, token); - await LoadLauncherNews(onTimeoutRoutine, token); - await LoadLauncherGameInfo(onTimeoutRoutine, token); + await Task.WhenAll([ + LoadLauncherGameResource(onTimeoutRoutine, token), + LoadLauncherNews(onTimeoutRoutine, token), + LoadLauncherGameInfo(onTimeoutRoutine, token) + ]).ConfigureAwait(false); } protected virtual async Task LoadLauncherGameResource(ActionOnTimeOutRetry? onTimeoutRoutine, @@ -89,33 +91,41 @@ protected virtual async Task LoadLauncherGameResource(ActionOnTimeOutRetry? onTi ActionTimeoutValueTaskCallback launcherGameResourceCallback = async innerToken => - await FallbackCDNUtil.DownloadAsJSONType(PresetConfig?.LauncherResourceURL, InternalAppJSONContext.Default, innerToken); + await FallbackCDNUtil.DownloadAsJSONType(PresetConfig?.LauncherResourceURL, InternalAppJSONContext.Default.RegionResourceProp, innerToken); - LauncherGameResource = await launcherGameResourceCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, - ExecutionTimeoutAttempt, onTimeoutRoutine, token); - - if (LauncherGameResource == null) - { - throw new NullReferenceException("Launcher game resource returns a null!"); - } + Task[] tasks = [ + launcherGameResourceCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, + ExecutionTimeoutAttempt, onTimeoutRoutine, token) + .AsTaskAndDoAction((result) => LauncherGameResource = result), + Task.CompletedTask + ]; + RegionResourceProp? pluginProp = null; if (string.IsNullOrEmpty(PresetConfig?.LauncherPluginURL)) { ActionTimeoutValueTaskCallback launcherPluginPropCallback = async innerToken => - await FallbackCDNUtil.DownloadAsJSONType(string.Format(PresetConfig?.LauncherPluginURL!, GetDeviceId(PresetConfig!)), InternalAppJSONContext.Default, innerToken); + await FallbackCDNUtil.DownloadAsJSONType(string.Format(PresetConfig?.LauncherPluginURL!, GetDeviceId(PresetConfig!)), InternalAppJSONContext.Default.RegionResourceProp, innerToken); - RegionResourceProp? pluginProp = await launcherPluginPropCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, - ExecutionTimeoutAttempt, onTimeoutRoutine, token); + tasks[1] = launcherPluginPropCallback.WaitForRetryAsync(ExecutionTimeout, ExecutionTimeoutStep, + ExecutionTimeoutAttempt, onTimeoutRoutine, token) + .AsTaskAndDoAction((result) => pluginProp = result); + } - if (pluginProp != null && LauncherGameResource.data != null) - { - LauncherGameResource.data.plugins = pluginProp.data?.plugins; - #if DEBUG - Logger.LogWriteLine("[LauncherApiBase::LoadLauncherGameResource] Loading plugin handle!", - LogType.Debug, true); - #endif - } + await Task.WhenAll(tasks).ConfigureAwait(false); + + if (LauncherGameResource == null) + { + throw new NullReferenceException("Launcher game resource returns a null!"); + } + + if (pluginProp != null && LauncherGameResource.data != null) + { + LauncherGameResource.data.plugins = pluginProp.data?.plugins; +#if DEBUG + Logger.LogWriteLine("[LauncherApiBase::LoadLauncherGameResource] Loading plugin handle!", + LogType.Debug, true); +#endif } PerformDebugRoutines(); @@ -207,8 +217,8 @@ protected void EnsureResourceUrlNotNull() } } - protected virtual async ValueTask LoadLauncherNews(ActionOnTimeOutRetry? onTimeoutRoutine, - CancellationToken token) + protected virtual async Task LoadLauncherNews(ActionOnTimeOutRetry? onTimeoutRoutine, + CancellationToken token) { EnsurePresetConfigNotNull(); @@ -252,9 +262,9 @@ protected virtual async ValueTask LoadLauncherNews(ActionOnTimeOutRetry? onTimeo #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - protected virtual async ValueTask LoadLauncherGameInfo(ActionOnTimeOutRetry? onTimeoutRoutine, + protected virtual async Task LoadLauncherGameInfo(ActionOnTimeOutRetry? onTimeoutRoutine, #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously - CancellationToken token) + CancellationToken token) { LauncherGameInfoField = new HoYoPlayGameInfoField(); } @@ -282,8 +292,7 @@ protected virtual async ValueTask LoadLauncherGameInfo(ActionOnTimeOutRetry? onT private static async Task LoadSingleLangLauncherNews( string launcherSpriteUrl, CancellationToken token) { - return await FallbackCDNUtil.DownloadAsJSONType(launcherSpriteUrl, - InternalAppJSONContext.Default, token); + return await FallbackCDNUtil.DownloadAsJSONType(launcherSpriteUrl, InternalAppJSONContext.Default.LauncherGameNews, token); } private static async Task LoadMultiLangLauncherNews(string launcherSpriteUrl, string lang, @@ -291,8 +300,7 @@ protected virtual async ValueTask LoadLauncherGameInfo(ActionOnTimeOutRetry? onT { return await FallbackCDNUtil - .DownloadAsJSONType(string.Format(launcherSpriteUrl, lang), - InternalAppJSONContext.Default, token); + .DownloadAsJSONType(string.Format(launcherSpriteUrl, lang), InternalAppJSONContext.Default.LauncherGameNews, token); } protected virtual string GetDeviceId(PresetConfig preset) diff --git a/CollapseLauncher/Classes/Helper/LauncherApiLoader/Sophon/LauncherGameNews.cs b/CollapseLauncher/Classes/Helper/LauncherApiLoader/Sophon/LauncherGameNews.cs index 00496cc1c..59ac9600b 100644 --- a/CollapseLauncher/Classes/Helper/LauncherApiLoader/Sophon/LauncherGameNews.cs +++ b/CollapseLauncher/Classes/Helper/LauncherApiLoader/Sophon/LauncherGameNews.cs @@ -6,6 +6,8 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; +using WinRT; +// ReSharper disable PartialTypeWithSinglePart namespace CollapseLauncher.Helper.LauncherApiLoader.Sophon { @@ -83,7 +85,7 @@ public List? NewsPostTypeInfo return _newsPostTypeInfo; } - _newsPostTypeInfo = NewsPost.Where(x => x.PostType == LauncherGameNewsPostType.POST_TYPE_INFO)? + _newsPostTypeInfo = NewsPost.Where(x => x.PostType == LauncherGameNewsPostType.POST_TYPE_INFO) .OrderBy(x => x.PostOrder) .ToList(); return _newsPostTypeInfo; @@ -105,7 +107,7 @@ public List? NewsPostTypeActivity return _newsPostTypeActivity; } - _newsPostTypeActivity = NewsPost.Where(x => x.PostType == LauncherGameNewsPostType.POST_TYPE_ACTIVITY)? + _newsPostTypeActivity = NewsPost.Where(x => x.PostType == LauncherGameNewsPostType.POST_TYPE_ACTIVITY) .OrderBy(x => x.PostOrder) .ToList(); return _newsPostTypeActivity; @@ -128,7 +130,7 @@ public List? NewsPostTypeAnnouncement } _newsPostTypeAnnouncement = NewsPost - .Where(x => x.PostType == LauncherGameNewsPostType.POST_TYPE_ANNOUNCE)? + .Where(x => x.PostType == LauncherGameNewsPostType.POST_TYPE_ANNOUNCE) .OrderBy(x => x.PostOrder) .ToList(); return _newsPostTypeAnnouncement; @@ -220,7 +222,8 @@ public string? CarouselImg public int? CarouselOrder { get; init; } } - public class LauncherGameNewsSocialMedia : ILauncherGameNewsDataTokenized + [GeneratedBindableCustomProperty] + public partial class LauncherGameNewsSocialMedia : ILauncherGameNewsDataTokenized { private readonly string? _qrImg; private readonly List? _qrLinks; @@ -303,7 +306,8 @@ public List? QrLinks [JsonIgnore] public bool IsHasQrDescription => !string.IsNullOrEmpty(QrTitle); } - public class LauncherGameNewsSocialMediaQrLinks + [GeneratedBindableCustomProperty] + public partial class LauncherGameNewsSocialMediaQrLinks { [JsonPropertyName("title")] [JsonConverter(typeof(EmptyStringAsNullConverter))] diff --git a/CollapseLauncher/Classes/Helper/Metadata/LauncherMetadataHelper.cs b/CollapseLauncher/Classes/Helper/Metadata/LauncherMetadataHelper.cs index 64b4c4b1b..1096aaa53 100644 --- a/CollapseLauncher/Classes/Helper/Metadata/LauncherMetadataHelper.cs +++ b/CollapseLauncher/Classes/Helper/Metadata/LauncherMetadataHelper.cs @@ -1,5 +1,4 @@ #nullable enable -using CollapseLauncher.Helper.LauncherApiLoader; using CollapseLauncher.Helper.LauncherApiLoader.HoYoPlay; using CollapseLauncher.Helper.LauncherApiLoader.Sophon; using CollapseLauncher.Helper.Loading; @@ -33,8 +32,8 @@ internal static class LauncherMetadataHelper #region Metadata Stamp List and Config Dictionary - internal static List? LauncherMetadataStamp { get; private set; } - internal static List? NewUpdateMetadataStamp { get; private set; } + internal static List? LauncherMetadataStamp { get; private set; } + internal static List? NewUpdateMetadataStamp { get; private set; } internal static List? LauncherGameNameCollection => LauncherGameNameRegionCollection?.Keys.ToList(); private static Dictionary? LauncherMetadataStampDictionary { get; set; } @@ -249,7 +248,7 @@ internal static async ValueTask InitializeStamp(string currentChannel, bool thro // Deserialize the stream LauncherMetadataStamp = - await stampLocalStream.DeserializeAsync>(InternalAppJSONContext.Default); + await stampLocalStream.DeserializeAsListAsync(InternalAppJSONContext.Default.Stamp); // SANITIZE: Check if the stamp is empty, then throw if (LauncherMetadataStamp == null || LauncherMetadataStamp.Count == 0) @@ -258,6 +257,9 @@ internal static async ValueTask InitializeStamp(string currentChannel, bool thro // Load and add stamp into stamp dictionary foreach (Stamp? stamp in LauncherMetadataStamp) { + if (stamp == null) + continue; + string? gameName = string.IsNullOrEmpty(stamp.GameName) ? stamp.MetadataType.ToString() : stamp.GameName; string? gameRegion = string.IsNullOrEmpty(stamp.GameRegion) ? stamp.MetadataPath : stamp.GameRegion; LauncherMetadataStampDictionary?.Add($"{gameName} - {gameRegion}", stamp); @@ -324,7 +326,7 @@ internal static async ValueTask InitializeConfig(string currentChannel, bool isC // Find and iterate the master key first Stamp? masterKeyStamp = - LauncherMetadataStamp.FirstOrDefault(x => x.MetadataType == MetadataType.MasterKey); + LauncherMetadataStamp.FirstOrDefault(x => x?.MetadataType == MetadataType.MasterKey); if (masterKeyStamp == null) { throw new KeyNotFoundException("Master key information is not found in the stamp!"); @@ -333,7 +335,7 @@ internal static async ValueTask InitializeConfig(string currentChannel, bool isC // Iterate the CommunityTools configs Stamp? stampCommunityToolkit = LauncherMetadataStamp - .FirstOrDefault(x => x.MetadataType == MetadataType.CommunityTools); + .FirstOrDefault(x => x?.MetadataType == MetadataType.CommunityTools); if (stampCommunityToolkit != null) { await LoadConfigInner(stampCommunityToolkit, currentChannel, false, false); @@ -341,11 +343,14 @@ internal static async ValueTask InitializeConfig(string currentChannel, bool isC // Iterate the stamp and try to load the configs int index = 1; - List stampList = LauncherMetadataStamp - .Where(x => x.MetadataType == MetadataType.PresetConfigV2) + List stampList = LauncherMetadataStamp + .Where(x => x?.MetadataType == MetadataType.PresetConfigV2) .ToList(); - foreach (Stamp stamp in stampList) + foreach (Stamp? stamp in stampList) { + if (stamp == null) + continue; + if (isShowLoadingMessage) { LoadingMessageHelper.SetMessage(Locale.Lang._MainPage.Initializing, @@ -404,7 +409,7 @@ internal static async ValueTask LoadConfigInner(Stamp stamp, string currentChann { // Deserialize the key config MasterKeyConfig? keyConfig = - await configLocalStream.DeserializeAsync(InternalAppJSONContext.Default); + await configLocalStream.DeserializeAsync(InternalAppJSONContext.Default.MasterKeyConfig); // Assign the key to instance property CurrentMasterKey = keyConfig ?? throw new InvalidDataException("Master key config seems to be empty!"); @@ -421,7 +426,7 @@ internal static async ValueTask LoadConfigInner(Stamp stamp, string currentChann case MetadataType.PresetConfigV2: { PresetConfig? presetConfig = - await configLocalStream.DeserializeAsync(InternalAppJSONContext.Default); + await configLocalStream.DeserializeAsync(InternalAppJSONContext.Default.PresetConfig); if (presetConfig != null) { if (isCacheUpdateModeOnly && (!presetConfig.IsCacheUpdateEnabled ?? false)) return; @@ -526,8 +531,8 @@ internal static async ValueTask IsMetadataHasUpdate() // Check and throw if the stream returns null or empty if (stampRemoteStream != null) { - List? remoteMetadataStampList = - await stampRemoteStream.DeserializeAsync>(InternalAppJSONContext.Default); + List? remoteMetadataStampList = + await stampRemoteStream.DeserializeAsListAsync(InternalAppJSONContext.Default.Stamp); // Check and throw if the metadata stamp returns null or empty if (remoteMetadataStampList == null || remoteMetadataStampList.Count == 0) @@ -545,18 +550,21 @@ internal static async ValueTask IsMetadataHasUpdate() bool isOutdatedStampDetected = false; foreach (Stamp? remoteMetadataStamp in remoteMetadataStampList) { + if (remoteMetadataStamp == null) + continue; + // Check if the local stamp does not have one, then add it to new update stamp list Stamp? localStamp = LauncherMetadataStamp?.FirstOrDefault(x => remoteMetadataStamp.GameRegion == - x.GameRegion + x?.GameRegion && remoteMetadataStamp.GameName == - x.GameName + x?.GameName && remoteMetadataStamp.LastUpdated == - x.LastUpdated + x?.LastUpdated && remoteMetadataStamp.MetadataPath == - x.MetadataPath + x?.MetadataPath && remoteMetadataStamp.MetadataType == - x.MetadataType); + x?.MetadataType); if (localStamp != null) continue; @@ -608,8 +616,10 @@ internal static async ValueTask RunMetadataUpdate() } // Remove the old metadata config file first - foreach (Stamp newUpdateStamp in NewUpdateMetadataStamp) + foreach (Stamp? newUpdateStamp in NewUpdateMetadataStamp) { + if (newUpdateStamp == null) continue; + // Ensure if the MetadataPath is not empty if (string.IsNullOrEmpty(newUpdateStamp.MetadataPath)) { @@ -644,7 +654,7 @@ internal static async ValueTask RunMetadataUpdate() } } - private static async ValueTask UpdateStampContent(string stampPath, List newStampList) + private static async ValueTask UpdateStampContent(string stampPath, List newStampList) { try { @@ -653,26 +663,28 @@ private static async ValueTask UpdateStampContent(string stampPath, List throw new FileNotFoundException($"Unable to update the stamp file because it is not exist! It should have been located here: {stampPath}"); // Read the old stamp list stream - List? oldStampList = null; + List? oldStampList = null; using (FileStream stampStream = File.OpenRead(stampPath)) { // Deserialize and do sanitize if the old stamp list is empty - oldStampList = await stampStream.DeserializeAsync>(InternalAppJSONContext.Default); + oldStampList = await stampStream.DeserializeAsListAsync(InternalAppJSONContext.Default.Stamp); if (oldStampList == null || oldStampList?.Count == 0) throw new NullReferenceException($"The old stamp list contains an empty/null content!"); // Try iterate the new stamp list to replace the old ones or add a new entry - foreach (Stamp newStamp in newStampList) + foreach (Stamp? newStamp in newStampList) { + if (newStamp == null) continue; + // Find the old stamp reference from the old list Stamp? oldStampRef = oldStampList?.FirstOrDefault(x => newStamp.GameRegion == - x.GameRegion + x?.GameRegion && newStamp.GameName == - x.GameName + x?.GameName && newStamp.MetadataPath == - x.MetadataPath + x?.MetadataPath && newStamp.MetadataType == - x.MetadataType); + x?.MetadataType); // Check if the old stamp ref is null or index of old stamp reference returns < 0, then // add it as a new entry. int indexOfOldStamp = 0; @@ -687,7 +699,7 @@ private static async ValueTask UpdateStampContent(string stampPath, List // Now write the updated list to the stamp file using (FileStream updatedStampStream = File.Create(stampPath)) { - await oldStampList.SerializeAsync(updatedStampStream, InternalAppJSONContext.Default); + await oldStampList.SerializeAsync(updatedStampStream, InternalAppJSONContext.Default.ListStamp); return; } } diff --git a/CollapseLauncher/Classes/Helper/Metadata/PresetConfig.cs b/CollapseLauncher/Classes/Helper/Metadata/PresetConfig.cs index b5def426f..e303d79a7 100644 --- a/CollapseLauncher/Classes/Helper/Metadata/PresetConfig.cs +++ b/CollapseLauncher/Classes/Helper/Metadata/PresetConfig.cs @@ -94,7 +94,7 @@ public async ValueTask EnsureReassociated(HttpClient client, string? branchUrl, // Fetch branch info ActionTimeoutValueTaskCallback hypLauncherBranchCallback = async innerToken => - await FallbackCDNUtil.DownloadAsJSONType(branchUrl, InternalAppJSONContext.Default, innerToken); + await FallbackCDNUtil.DownloadAsJSONType(branchUrl, InternalAppJSONContext.Default.HoYoPlayLauncherGameInfo, innerToken); HoYoPlayLauncherGameInfo? hypLauncherBranchInfo = await hypLauncherBranchCallback.WaitForRetryAsync( LauncherApiBase.ExecutionTimeout, diff --git a/CollapseLauncher/Classes/Helper/TaskSchedulerHelper.cs b/CollapseLauncher/Classes/Helper/TaskSchedulerHelper.cs new file mode 100644 index 000000000..664b5a907 --- /dev/null +++ b/CollapseLauncher/Classes/Helper/TaskSchedulerHelper.cs @@ -0,0 +1,220 @@ +using Hi3Helper; +using Hi3Helper.Shared.Region; +using System; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace CollapseLauncher.Helper +{ + internal static class TaskSchedulerHelper + { + private const string CollapseStartupTaskName = "CollapseLauncherStartupTask"; + + internal static bool IsInitialized; + internal static bool CachedIsOnTrayEnabled; + internal static bool CachedIsEnabled; + + internal static bool IsOnTrayEnabled() + { + if (!IsInitialized) + InvokeGetStatusCommand(); + + return CachedIsOnTrayEnabled; + } + + internal static bool IsEnabled() + { + if (!IsInitialized) + InvokeGetStatusCommand(); + + return CachedIsEnabled; + } + + internal static void InvokeGetStatusCommand() + { + // Build the argument and mode to set + StringBuilder argumentBuilder = new StringBuilder(); + argumentBuilder.Append("IsEnabled"); + + // Append task name and stub path + AppendTaskNameAndPathArgument(argumentBuilder); + + // Store argument builder as string + string argumentString = argumentBuilder.ToString(); + + // Invoke command and get return code + int returnCode = GetInvokeCommandReturnCode(argumentString); + + (CachedIsEnabled, CachedIsOnTrayEnabled) = returnCode switch + { + // -1 means task is disabled with tray enabled + -1 => (false, true), + // 0 means task is disabled with tray disabled + 0 => (false, false), + // 1 means task is enabled with tray disabled + 1 => (true, false), + // 2 means task is enabled with tray enabled + 2 => (true, true), + // Otherwise, return both disabled (due to failure) + _ => (false, false) + }; + + // Print init determination + CheckInitDetermination(returnCode); + } + + private static void CheckInitDetermination(int returnCode) + { + // If the return code is within range, then set as initialized + if (returnCode is > -2 and < 3) + { + // Set as initialized + IsInitialized = true; + } + // Otherwise, log the return code + else + { + string reason = returnCode switch + { + int.MaxValue => "ARGUMENT_INVALID", + int.MinValue => "UNHANDLED_ERROR", + short.MaxValue => "INTERNALINVOKE_ERROR", + short.MinValue => "APPLET_NOTFOUND", + _ => $"UNKNOWN_{returnCode}" + }; + Logger.LogWriteLine($"Error while getting task status from applet with reason: {reason}", LogType.Error, true); + } + } + + internal static void ToggleTrayEnabled(bool isEnabled) + { + CachedIsOnTrayEnabled = isEnabled; + InvokeToggleCommand(); + } + + internal static void ToggleEnabled(bool isEnabled) + { + CachedIsEnabled = isEnabled; + InvokeToggleCommand(); + } + + private static void InvokeToggleCommand() + { + // Build the argument and mode to set + StringBuilder argumentBuilder = new StringBuilder(); + argumentBuilder.Append(CachedIsEnabled ? "Enable" : "Disable"); + + // Append argument whether to toggle the tray or not + if (CachedIsOnTrayEnabled) + argumentBuilder.Append("ToTray"); + + // Append task name and stub path + AppendTaskNameAndPathArgument(argumentBuilder); + + // Store argument builder as string + string argumentString = argumentBuilder.ToString(); + + // Invoke applet + int returnCode = GetInvokeCommandReturnCode(argumentString); + + // Print init determination + CheckInitDetermination(returnCode); + } + + private static void AppendTaskNameAndPathArgument(StringBuilder argumentBuilder) + { + // Get current stub or main executable path + string currentExecPath = MainEntryPoint.FindCollapseStubPath(); + + // Build argument to the task name + argumentBuilder.Append(" \""); + argumentBuilder.Append(CollapseStartupTaskName); + argumentBuilder.Append('"'); + + // Build argument to the executable path + argumentBuilder.Append(" \""); + argumentBuilder.Append(currentExecPath); + argumentBuilder.Append('"'); + } + + internal static void RecreateIconShortcuts() + { + // Build the argument and get the current executable path + StringBuilder argumentBuilder = new StringBuilder(); + argumentBuilder.Append("RecreateIcons"); + string currentExecPath = LauncherConfig.AppExecutablePath; + + // Build argument to the executable path + argumentBuilder.Append(" \""); + argumentBuilder.Append(currentExecPath); + argumentBuilder.Append('"'); + + // Store argument builder as string + string argumentString = argumentBuilder.ToString(); + + // Invoke applet + int returnCode = GetInvokeCommandReturnCode(argumentString); + + // Print init determination + CheckInitDetermination(returnCode); + } + + private static int GetInvokeCommandReturnCode(string argument) + { + const string returnvalmark = "RETURNVAL_"; + + // Get the applet path and check if the file exist + string appletPath = Path.Combine(LauncherConfig.AppFolder, "Lib", "win-x64", "Hi3Helper.TaskScheduler.exe"); + if (!File.Exists(appletPath)) + { + Logger.LogWriteLine($"Task Scheduler Applet does not exist in this path: {appletPath}", LogType.Error, true); + return short.MinValue; + } + + // Try to make process instance for the applet + using Process process = new Process(); + process.StartInfo = new ProcessStartInfo + { + FileName = appletPath, + Arguments = argument, + UseShellExecute = false, + RedirectStandardOutput = true, + CreateNoWindow = true + }; + int lastErrCode = short.MaxValue; + try + { + // Start the applet and wait until it exit. + process.Start(); + while (!process.StandardOutput.EndOfStream) + { + string consoleStdOut = process.StandardOutput.ReadLine(); + Logger.LogWriteLine("[TaskScheduler] " + consoleStdOut, LogType.Debug, true); + + // Parse if it has RETURNVAL_ + if (consoleStdOut == null || !consoleStdOut.StartsWith(returnvalmark)) + { + continue; + } + + ReadOnlySpan span = consoleStdOut.AsSpan(returnvalmark.Length); + if (int.TryParse(span, null, out int resultReturnCode)) + { + lastErrCode = resultReturnCode; + } + } + process.WaitForExit(); + } + catch (Exception ex) + { + // If error happened, then return. + Logger.LogWriteLine($"An error has occurred while invoking Task Scheduler applet!\r\n{ex}", LogType.Error, true); + return short.MaxValue; + } + + // Get return code + return lastErrCode; + } + } +} diff --git a/CollapseLauncher/Classes/Helper/Update/LauncherUpdateHelper.cs b/CollapseLauncher/Classes/Helper/Update/LauncherUpdateHelper.cs index 44ab9b118..4bb3619c4 100644 --- a/CollapseLauncher/Classes/Helper/Update/LauncherUpdateHelper.cs +++ b/CollapseLauncher/Classes/Helper/Update/LauncherUpdateHelper.cs @@ -1,14 +1,21 @@ #nullable enable - using Hi3Helper; - using Hi3Helper.Data; - using Hi3Helper.Shared.Region; - using Squirrel; - using Squirrel.Sources; - using System; - using System.Threading.Tasks; +using Hi3Helper; +using Hi3Helper.Data; +using Hi3Helper.Shared.Region; +using System; +using System.Threading.Tasks; +#if !USEVELOPACK +using Squirrel; +using Squirrel.Sources; +#else +using Microsoft.Extensions.Logging; +using Velopack; +using Velopack.Locators; +using Velopack.Sources; +#endif // ReSharper disable CheckNamespace - namespace CollapseLauncher.Helper.Update +namespace CollapseLauncher.Helper.Update { internal static class LauncherUpdateHelper { @@ -42,7 +49,7 @@ internal static async Task RunUpdateCheckDetached() } catch (Exception ex) { - Logger.LogWriteLine($"The squirrel check throws an error, Skipping update check!\r\n{ex}", LogType.Warning, true); + Logger.LogWriteLine($"The update manager check throws an error, Skipping update check!\r\n{ex}", LogType.Warning, true); } } @@ -51,12 +58,65 @@ internal static async Task IsUpdateAvailable(bool isForceCheckUpdate = fal string updateChannel = LauncherConfig.IsPreview ? "preview" : "stable"; CDNURLProperty launcherUpdatePreferredCdn = FallbackCDNUtil.GetPreferredCDN(); - string? launcherUpdateSquirrelBaseUrl = ConverterTool.CombineURLFromString(launcherUpdatePreferredCdn.URLPrefix, "squirrel", updateChannel); + string? launcherUpdateManagerBaseUrl = ConverterTool.CombineURLFromString(launcherUpdatePreferredCdn.URLPrefix, +#if USEVELOPACK + "velopack", + updateChannel +#else + "squirrel", + updateChannel +#endif + ); - IFileDownloader squirrelUpdateManagerHttpAdapter = new UpdateManagerHttpAdapter(); - using (UpdateManager squirrelUpdateManager = new UpdateManager(launcherUpdateSquirrelBaseUrl, null, null, squirrelUpdateManagerHttpAdapter)) + // Register the update manager adapter + IFileDownloader updateManagerHttpAdapter = new UpdateManagerHttpAdapter(); +#if USEVELOPACK + // Initialize update manager logger, locator and options + ILogger velopackLogger = ILoggerHelper.CreateCollapseILogger(); + VelopackLocator updateManagerLocator = VelopackLocator.GetDefault(velopackLogger); + UpdateOptions updateManagerOptions = new UpdateOptions { - UpdateInfo info = await squirrelUpdateManager.CheckForUpdate(); + AllowVersionDowngrade = true, + ExplicitChannel = updateChannel + }; + + // Initialize update manager source + IUpdateSource updateSource = new SimpleWebSource(launcherUpdateManagerBaseUrl, updateManagerHttpAdapter); + + // Initialize the update manager + UpdateManager updateManager = new UpdateManager( + updateSource, + updateManagerOptions, + velopackLogger, + updateManagerLocator); + + // Get the update info. If it's null, then return false (no update) + UpdateInfo? updateInfo = await updateManager.CheckForUpdatesAsync(); + if (updateInfo == null) + { + return false; + } + + // If there's an update, then get the update metadata + GameVersion updateVersion = new GameVersion(updateInfo.TargetFullRelease.Version.ToString()); + AppUpdateVersionProp = await GetUpdateMetadata(updateChannel); + if (AppUpdateVersionProp == null) + { + return false; + } + + // Compare the version + IsLauncherUpdateAvailable = LauncherCurrentVersion.Compare(updateVersion); + + // Get the status if the update is ignorable or forced update. + bool isUserIgnoreUpdate = (LauncherConfig.GetAppConfigValue("DontAskUpdate").ToBoolNullable() ?? false) && !isForceCheckUpdate; + bool isUpdateRoutineSkipped = isUserIgnoreUpdate && !AppUpdateVersionProp.IsForceUpdate; + + return IsLauncherUpdateAvailable && !isUpdateRoutineSkipped; +#else + using (UpdateManager updateManager = new UpdateManager(launcherUpdateManagerBaseUrl, null, null, updateManagerHttpAdapter)) + { + UpdateInfo info = await updateManager.CheckForUpdate(); if (info == null) return false; GameVersion remoteVersion = new GameVersion(info.FutureReleaseEntry.Version.Version); @@ -71,13 +131,14 @@ internal static async Task IsUpdateAvailable(bool isForceCheckUpdate = fal return IsLauncherUpdateAvailable && !isUpdateRoutineSkipped; } +#endif } private static async ValueTask GetUpdateMetadata(string updateChannel) { string relativePath = ConverterTool.CombineURLFromString(updateChannel, "fileindex.json"); await using BridgedNetworkStream ms = await FallbackCDNUtil.TryGetCDNFallbackStream(relativePath); - return await ms.DeserializeAsync(InternalAppJSONContext.Default); + return await ms.DeserializeAsync(InternalAppJSONContext.Default.AppUpdateVersionProp); } } } diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs index 9392901a0..a89b54451 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.PkgVersion.cs @@ -357,7 +357,7 @@ protected virtual async ValueTask InnerParsePkgVersion2FileInfo(string { // Read line and deserialize string? line = await reader.ReadLineAsync(token); - LocalFileInfo? localFileInfo = line?.Deserialize(InternalAppJSONContext.Default); + LocalFileInfo? localFileInfo = line?.Deserialize(InternalAppJSONContext.Default.LocalFileInfo); // Assign the values if (localFileInfo == null) diff --git a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs index 5b2941b2e..09397b7b3 100644 --- a/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs +++ b/CollapseLauncher/Classes/InstallManagement/BaseClass/InstallManagerBase.cs @@ -949,11 +949,7 @@ await _gameVersionManager.GamePreset async ValueTask DelegateAssetDownload(SophonAsset asset, CancellationToken _) { // ReSharper disable once AccessToDisposedClosure - if (httpClient != null) - { - // ReSharper disable once AccessToDisposedClosure - await RunSophonAssetDownloadThread(httpClient, asset, parallelChunksOptions); - } + await RunSophonAssetDownloadThread(httpClient, asset, parallelChunksOptions); } // Declare the rename temp file delegate @@ -1517,9 +1513,13 @@ protected virtual async Task StartPackageInstallationInner(List entriesIndex, List entries, CancellationToken cancellationToken) { - // 16 MB of buffer (hope it's not too big) - byte[] buffer = new byte[16 << 20]; + // 4 MB of buffer + byte[] buffer = GC.AllocateUninitializedArray(4 << 20); foreach (int entryIndex in entriesIndex) { @@ -2309,10 +2309,6 @@ public virtual async ValueTask ApplyHdiffListPatch() { throw ex.Flatten().InnerExceptions.First(); } - catch (Exception) - { - throw; - } finally { EventListener.LoggerEvent -= EventListener_PatchLogEvent; @@ -2387,7 +2383,7 @@ public virtual List TryGetHDiffList() while (!listReader.EndOfStream) { string currentLine = listReader.ReadLine(); - var prop = currentLine?.Deserialize(CoreLibraryJSONContext.Default); + var prop = currentLine?.Deserialize(CoreLibraryJSONContext.Default.PkgVersionProperties); if (prop == null) { @@ -2591,12 +2587,7 @@ protected virtual bool TryGetVoiceOverResourceByLocaleCode(List localeCode) { - // If it's empty or null, return false - if (localeCode == null) - { - return false; - } - + // If it's empty, return false if (localeCode.IsEmpty) { return false; @@ -3067,7 +3058,7 @@ private bool TryGetExistingBHI3LPath(ref string OutputPath) { // Try parsing the config value = Encoding.UTF8.GetString(keyValue); - config = value.Deserialize(InternalAppJSONContext.Default); + config = value.Deserialize(InternalAppJSONContext.Default.BHI3LInfo); } catch (Exception ex) { @@ -3153,9 +3144,9 @@ private async Task GetLatestPackageList(List packageList, Ga if (gameState != GameInstallStateEnum.InstalledHavePlugin) { // Iterate the package resource version and add it into packageList - foreach (RegionResourceVersion asset in usePreload + foreach (RegionResourceVersion asset in (usePreload ? _gameVersionManager.GetGamePreloadZip() - : _gameVersionManager.GetGameLatestZip(gameState)) + : _gameVersionManager.GetGameLatestZip(gameState))!) { if (asset == null) { @@ -3844,17 +3835,20 @@ await Dialog_InsufficientDriveSpace(Content, diskFreeSpace, remainedDownloadUnco private async Task GetPackagesRemoteSize(List packageList, CancellationToken token) { - // Iterate and assign the remote size to each package inside the list - for (int i = 0; i < packageList.Count; i++) + // Iterate and assign the remote size to each package inside the list in parallel + await Parallel.ForEachAsync(packageList, new ParallelOptions { - if (packageList[i].Segments != null) + CancellationToken = token + }, async (package, innerToken) => + { + if (package.Segments != null) { - await TryGetSegmentedPackageRemoteSize(packageList[i], token); - continue; + await TryGetSegmentedPackageRemoteSize(package, token); + return; } - await TryGetPackageRemoteSize(packageList[i], token); - } + await TryGetPackageRemoteSize(package, token); + }); } #endregion @@ -3908,14 +3902,18 @@ protected async Task TryGetPackageRemoteSize(GameInstallPackage asset, Cancellat protected async Task TryGetSegmentedPackageRemoteSize(GameInstallPackage asset, CancellationToken token) { long totalSize = 0; - for (int i = 0; i < asset.Segments.Count; i++) + await Parallel.ForAsync(0, asset.Segments.Count, new ParallelOptions + { + CancellationToken = token + }, + async (i, innerToken) => { long segmentSize = await FallbackCDNUtil.GetContentLength(asset.Segments[i].URL, token); - totalSize += segmentSize; - asset.Segments[i].Size = segmentSize; + totalSize += segmentSize; + asset.Segments[i].Size = segmentSize; LogWriteLine($"Package Segment: [T: {asset.PackageType}] {asset.Segments[i].Name} has {ConverterTool.SummarizeSizeSimple(segmentSize)} in size", LogType.Default, true); - } + }); asset.Size = totalSize; LogWriteLine($"Package Segment (count: {asset.Segments.Count}) has {ConverterTool.SummarizeSizeSimple(asset.Size)} in total size with {ConverterTool.SummarizeSizeSimple(asset.SizeRequired)} of free space required", diff --git a/CollapseLauncher/Classes/InstallManagement/GameConversionManagement.cs b/CollapseLauncher/Classes/InstallManagement/GameConversionManagement.cs index 5e598b558..445b9b9bf 100644 --- a/CollapseLauncher/Classes/InstallManagement/GameConversionManagement.cs +++ b/CollapseLauncher/Classes/InstallManagement/GameConversionManagement.cs @@ -87,7 +87,7 @@ public async Task StartPreparation() ConvertDetail = Lang._InstallConvert.Step2Subtitle; await FallbackCDNUtil.DownloadCDNFallbackContent(downloadClient, buffer, URL, Token); buffer.Position = 0; - SourceFileRemote = await buffer.DeserializeAsync>(CoreLibraryJSONContext.Default, Token); + SourceFileRemote = await buffer.DeserializeAsListAsync(CoreLibraryJSONContext.Default.FilePropertiesRemote, Token); } using (MemoryStream buffer = new MemoryStream()) @@ -96,7 +96,7 @@ public async Task StartPreparation() ConvertDetail = Lang._InstallConvert.Step2Subtitle; await FallbackCDNUtil.DownloadCDNFallbackContent(downloadClient, buffer, URL, Token); buffer.Position = 0; - TargetFileRemote = await buffer.DeserializeAsync>(CoreLibraryJSONContext.Default, Token); + TargetFileRemote = await buffer.DeserializeAsListAsync(CoreLibraryJSONContext.Default.FilePropertiesRemote, Token); } } finally diff --git a/CollapseLauncher/Classes/Interfaces/Class/CommunityToolsProperty.cs b/CollapseLauncher/Classes/Interfaces/Class/CommunityToolsProperty.cs index 80d0c6fb0..4fb8f21d3 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/CommunityToolsProperty.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/CommunityToolsProperty.cs @@ -36,7 +36,7 @@ public static async Task LoadCommunityTools(Stream fileS { try { - CommunityToolsProperty communityToolkitProperty = await fileStream.DeserializeAsync(InternalAppJSONContext.Default); + CommunityToolsProperty communityToolkitProperty = await fileStream.DeserializeAsync(InternalAppJSONContext.Default.CommunityToolsProperty); ResolveCommunityToolkitFontAwesomeGlyph(communityToolkitProperty.OfficialToolsDictionary); ResolveCommunityToolkitFontAwesomeGlyph(communityToolkitProperty.CommunityToolsDictionary); return communityToolkitProperty; diff --git a/CollapseLauncher/Classes/Interfaces/Class/JSONSerializerHelper.cs b/CollapseLauncher/Classes/Interfaces/Class/JSONSerializerHelper.cs index 6214d4e68..609b0e62e 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/JSONSerializerHelper.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/JSONSerializerHelper.cs @@ -5,7 +5,6 @@ using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Nodes; -using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; #nullable enable @@ -46,52 +45,52 @@ internal static partial class JSONSerializerHelper private static readonly Utf8JsonWriter jsonWriter = new Utf8JsonWriter(jsonBufferWriter, jsonWriterOptions); private static readonly Utf8JsonWriter jsonWriterIndented = new Utf8JsonWriter(jsonBufferWriter, jsonWriterOptionsIndented); - internal static T? Deserialize(this string data, JsonSerializerContext context, T? defaultType = null) - where T : class => InnerDeserialize(data, context, defaultType); + internal static T? Deserialize(this string data, JsonTypeInfo typeInfo, T? defaultType = null) + where T : class => InnerDeserialize(data, typeInfo, defaultType); - internal static T? Deserialize(this string data, JsonSerializerContext context, T? defaultType = null) - where T : struct => InnerDeserialize(data, context, defaultType); + internal static T? Deserialize(this string data, JsonTypeInfo typeInfo, T? defaultType = null) + where T : struct => InnerDeserialize(data, typeInfo, defaultType); internal static JsonNode? DeserializeAsJsonNode(this string data) => InnerDeserializeAsJsonNode(data); - internal static T? Deserialize(this ReadOnlySpan data, JsonSerializerContext context, T? defaultType = null) - where T : class => InnerDeserialize(data, context, defaultType); + internal static T? Deserialize(this ReadOnlySpan data, JsonTypeInfo typeInfo, T? defaultType = null) + where T : class => InnerDeserialize(data, typeInfo, defaultType); - internal static T? Deserialize(this ReadOnlySpan data, JsonSerializerContext context, T? defaultType = null) - where T : struct => InnerDeserialize(data, context, defaultType); + internal static T? Deserialize(this ReadOnlySpan data, JsonTypeInfo typeInfo, T? defaultType = null) + where T : struct => InnerDeserialize(data, typeInfo, defaultType); internal static JsonNode? DeserializeAsJsonNode(this ReadOnlySpan data) => InnerDeserializeAsJsonNode(data); - internal static T? Deserialize(this ReadOnlySpan data, JsonSerializerContext context, T? defaultType = null) - where T : class => InnerDeserialize(data, context, defaultType); + internal static T? Deserialize(this ReadOnlySpan data, JsonTypeInfo typeInfo, T? defaultType = null) + where T : class => InnerDeserialize(data, typeInfo, defaultType); - internal static T? Deserialize(this ReadOnlySpan data, JsonSerializerContext context, T? defaultType = null) - where T : struct => InnerDeserialize(data, context, defaultType); + internal static T? Deserialize(this ReadOnlySpan data, JsonTypeInfo typeInfo, T? defaultType = null) + where T : struct => InnerDeserialize(data, typeInfo, defaultType); internal static JsonNode? DeserializeAsJsonNode(this ReadOnlySpan data) => InnerDeserializeAsJsonNode(data); - internal static T? Deserialize(this Stream data, JsonSerializerContext context, T? defaultType = null) - where T : class => InnerDeserializeStream(data, context, defaultType); + internal static T? Deserialize(this Stream data, JsonTypeInfo typeInfo, T? defaultType = null) + where T : class => InnerDeserializeStream(data, typeInfo, defaultType); - internal static T? Deserialize(this Stream data, JsonSerializerContext context, T? defaultType = null) - where T : struct => InnerDeserializeStream(data, context, defaultType); + internal static T? Deserialize(this Stream data, JsonTypeInfo typeInfo, T? defaultType = null) + where T : struct => InnerDeserializeStream(data, typeInfo, defaultType); internal static JsonNode? DeserializeAsJsonNode(this Stream data) => InnerDeserializeStreamAsJsonNode(data); - private static T? InnerDeserializeStream(Stream data, JsonSerializerContext context, T? defaultType) + private static T? InnerDeserializeStream(Stream data, JsonTypeInfo typeInfo, T? defaultType) { // Check if the data length is 0, then return default value if (data.Length == 0) return defaultType ?? default; // Try deserialize. If it returns a null, then return the default value - return (T?)JsonSerializer.Deserialize(data, typeof(T), context) ?? defaultType ?? default; + return JsonSerializer.Deserialize(data, typeInfo) ?? defaultType ?? default; } - private static T? InnerDeserialize(ReadOnlySpan data, JsonSerializerContext context, T? defaultType) + private static T? InnerDeserialize(ReadOnlySpan data, JsonTypeInfo typeInfo, T? defaultType) { // Check if the data length is less than 2 bytes (assuming the buffer is "{}"), then return default value if (data.Length <= MIN_ALLOWED_CHAR_LENGTH) return defaultType ?? default; @@ -109,7 +108,7 @@ internal static partial class JSONSerializerHelper // Convert the char[] buffer into byte[] int bufferWritten = Encoding.UTF8.GetBytes(dataTrimmed, tempBuffer); // Start deserialize and return - return InnerDeserialize(tempBuffer.AsSpan(0, bufferWritten), context, defaultType); + return InnerDeserialize(tempBuffer.AsSpan(0, bufferWritten), typeInfo, defaultType); } finally { @@ -118,7 +117,7 @@ internal static partial class JSONSerializerHelper } } - private static T? InnerDeserialize(ReadOnlySpan data, JsonSerializerContext context, T? defaultType) + private static T? InnerDeserialize(ReadOnlySpan data, JsonTypeInfo typeInfo, T? defaultType) { // Check if the data length is less than 2 bytes (assuming the buffer is "{}"), then return default value if (data.Length <= MIN_ALLOWED_CHAR_LENGTH) return defaultType ?? default; @@ -128,7 +127,6 @@ internal static partial class JSONSerializerHelper Utf8JsonReader jsonReader = new Utf8JsonReader(dataTrimmed, jsonReaderOptions); // Try deserialize. If it returns a null, then return the default value - JsonTypeInfo typeInfo = (JsonTypeInfo?)context.GetTypeInfo(typeof(T)) ?? throw new NullReferenceException($"The type info of {typeof(T)} is null!"); return JsonSerializer.Deserialize(ref jsonReader, typeInfo) ?? defaultType ?? default; } @@ -181,13 +179,13 @@ internal static partial class JSONSerializerHelper return JsonNode.Parse(ref jsonReader, jsonNodeOptions); } - internal static string Serialize(this T? value, JsonSerializerContext context, bool isIncludeNullEndChar = true, bool isWriteIndented = false) - => InnerSerialize(value, context, isIncludeNullEndChar, isWriteIndented); + internal static string Serialize(this T? value, JsonTypeInfo typeInfo, bool isIncludeNullEndChar = true, bool isWriteIndented = false) + => InnerSerialize(value, typeInfo, isIncludeNullEndChar, isWriteIndented); - internal static string SerializeJsonNode(this JsonNode? node, JsonSerializerContext context, bool isIncludeNullEndChar = true, bool isWriteIndented = false) - => InnerSerializeJsonNode(node, context, isIncludeNullEndChar, isWriteIndented); + internal static string SerializeJsonNode(this JsonNode? node, JsonTypeInfo typeInfo, bool isIncludeNullEndChar = true, bool isWriteIndented = false) + => InnerSerializeJsonNode(node, typeInfo, isIncludeNullEndChar, isWriteIndented); - private static string InnerSerialize(this T? data, JsonSerializerContext context, bool isIncludeNullEndChar, bool isWriteIndented) + private static string InnerSerialize(this T? data, JsonTypeInfo typeInfo, bool isIncludeNullEndChar, bool isWriteIndented) { const string _defaultValue = "{}"; // Check if the data is null, then return default value @@ -212,13 +210,10 @@ private static string InnerSerialize(this T? data, JsonSerializerContext cont // Lock the writer lock (writer) { - // Try get the JsonTypeInfo - JsonTypeInfo typeInfo = (JsonTypeInfo?)context.GetTypeInfo(typeof(T)) ?? throw new NullReferenceException($"The type info of {typeof(T)} is null!"); - // Try serialize the type into JSON string JsonSerializer.Serialize(writer, data, typeInfo); - // Flush the writter + // Flush the writer writer.Flush(); // Write the buffer to string @@ -232,7 +227,7 @@ private static string InnerSerialize(this T? data, JsonSerializerContext cont } } - private static string InnerSerializeJsonNode(this JsonNode? node, JsonSerializerContext context, bool isIncludeNullEndChar, bool isWriteIndented) + private static string InnerSerializeJsonNode(this JsonNode? node, JsonTypeInfo typeInfo, bool isIncludeNullEndChar, bool isWriteIndented) { const string _defaultValue = "{}"; // Check if the node is null, then return default value @@ -257,13 +252,10 @@ private static string InnerSerializeJsonNode(this JsonNode? node, JsonSerializer // Lock the writer lock (writer) { - // Try get the JsonSerializerOptions - JsonSerializerOptions jsonOptions = context.Options; - // Try serialize the JSON Node into JSON string - node.WriteTo(writer, jsonOptions); + node.WriteTo(writer, typeInfo.Options); - // Flush the writter + // Flush the writer writer.Flush(); // Write the buffer to string diff --git a/CollapseLauncher/Classes/Interfaces/Class/JSONSerializerHelperAsync.cs b/CollapseLauncher/Classes/Interfaces/Class/JSONSerializerHelperAsync.cs index f902b1f53..53b623205 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/JSONSerializerHelperAsync.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/JSONSerializerHelperAsync.cs @@ -1,8 +1,8 @@ using System; +using System.Collections.Generic; using System.IO; using System.Text.Json; using System.Text.Json.Nodes; -using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; using System.Threading; using System.Threading.Tasks; @@ -12,20 +12,39 @@ namespace CollapseLauncher { internal static partial class JSONSerializerHelper { - internal static async ValueTask DeserializeAsync(this Stream data, JsonSerializerContext context, CancellationToken token = default, T? defaultType = null) - where T : class => await InnerDeserializeStreamAsync(data, context, token, defaultType); + internal static async ValueTask DeserializeAsync(this Stream data, JsonTypeInfo typeInfo, CancellationToken token = default, T? defaultType = null) + where T : class => await InnerDeserializeStreamAsync(data, typeInfo, token, defaultType); - internal static async ValueTask DeserializeAsync(this Stream data, JsonSerializerContext context, CancellationToken token = default, T? defaultType = null) - where T : struct => await InnerDeserializeStreamAsync(data, context, token, defaultType); + internal static async ValueTask DeserializeAsync(this Stream data, JsonTypeInfo typeInfo, CancellationToken token = default, T? defaultType = null) + where T : struct => await InnerDeserializeStreamAsync(data, typeInfo, token, defaultType); internal static async Task DeserializeAsNodeAsync(this Stream data, CancellationToken token = default) => await InnerDeserializeStreamAsNodeAsync(data, token); - private static async ValueTask InnerDeserializeStreamAsync(Stream data, JsonSerializerContext context, CancellationToken token, T? defaultType) => + internal static IAsyncEnumerable DeserializeAsEnumerable(this Stream data, JsonTypeInfo typeInfo, CancellationToken token = default) + => JsonSerializer.DeserializeAsyncEnumerable(data, typeInfo, token); + + internal static async Task> DeserializeAsListAsync(this Stream data, JsonTypeInfo typeInfo, CancellationToken token = default) + { + // Create List of T + List listItem = new List(); + + // Enumerate in async + await foreach (T? item in data.DeserializeAsEnumerable(typeInfo, token)) + { + // Add an item to List + listItem.Add(item); + } + + // Return the list + return listItem; + } + + private static async ValueTask InnerDeserializeStreamAsync(Stream data, JsonTypeInfo typeInfo, CancellationToken token, T? defaultType) => // Check if the data cannot be read, then throw !data.CanRead ? throw new NotSupportedException("Stream is not readable! Cannot deserialize the stream to JSON!") : // Try deserialize. If it returns a null, then return the default value - (T?)await JsonSerializer.DeserializeAsync(data, typeof(T), context, token) ?? defaultType; + await JsonSerializer.DeserializeAsync(data, typeInfo, token) ?? defaultType; private static async Task InnerDeserializeStreamAsNodeAsync(Stream data, CancellationToken token) => // Check if the data cannot be read, then throw @@ -33,18 +52,17 @@ internal static partial class JSONSerializerHelper // Try deserialize to JSON Node await JsonNode.ParseAsync(data, jsonNodeOptions, jsonDocumentOptions, token); - internal static async ValueTask SerializeAsync(this T? value, Stream targetStream, JsonSerializerContext context, CancellationToken token = default) - where T : class => await InnerSerializeStreamAsync(value, targetStream, context, token); + internal static async ValueTask SerializeAsync(this T? value, Stream targetStream, JsonTypeInfo typeInfo, CancellationToken token = default) + where T : class => await InnerSerializeStreamAsync(value, targetStream, typeInfo, token); - internal static async ValueTask SerializeAsync(this T? value, Stream targetStream, JsonSerializerContext context, CancellationToken token = default) - where T : struct => await InnerSerializeStreamAsync(value, targetStream, context, token); + internal static async ValueTask SerializeAsync(this T? value, Stream targetStream, JsonTypeInfo typeInfo, CancellationToken token = default) + where T : struct => await InnerSerializeStreamAsync(value, targetStream, typeInfo, token); - private static async ValueTask InnerSerializeStreamAsync(this T? value, Stream targetStream, JsonSerializerContext context, CancellationToken token) + private static async ValueTask InnerSerializeStreamAsync(this T? value, Stream targetStream, JsonTypeInfo typeInfo, CancellationToken token) { if (!targetStream.CanWrite) throw new NotSupportedException("Stream is not writeable! Cannot serialize the object into Stream!"); - JsonTypeInfo? typeInfo = (JsonTypeInfo?)context.GetTypeInfo(typeof(T)); if (typeInfo == null) throw new NotSupportedException($"Context does not contain a type info of type {typeof(T).Name}!"); diff --git a/CollapseLauncher/Classes/Interfaces/Class/Structs.cs b/CollapseLauncher/Classes/Interfaces/Class/Structs.cs index 418654861..a783e09f3 100644 --- a/CollapseLauncher/Classes/Interfaces/Class/Structs.cs +++ b/CollapseLauncher/Classes/Interfaces/Class/Structs.cs @@ -111,7 +111,7 @@ public static bool Equals(this GameVersion? fromVersion, GameVersion? toVersion) } } - public struct GameVersion + public record struct GameVersion { public GameVersion(int major, int minor, int build, int revision = 0) { diff --git a/CollapseLauncher/Classes/Interfaces/IGamePlaytime.cs b/CollapseLauncher/Classes/Interfaces/IGamePlaytime.cs new file mode 100644 index 000000000..537fe81a0 --- /dev/null +++ b/CollapseLauncher/Classes/Interfaces/IGamePlaytime.cs @@ -0,0 +1,16 @@ +using CollapseLauncher.GamePlaytime; +using System; +using System.Diagnostics; + +namespace CollapseLauncher.Interfaces +{ + internal interface IGamePlaytime : IDisposable + { + event EventHandler PlaytimeUpdated; + CollapsePlaytime CollapsePlaytime { get; } + + void Reset(); + void Update(TimeSpan timeSpan); + void StartSession(Process proc, DateTime? begin = null); + } +} diff --git a/CollapseLauncher/Classes/Interfaces/IGameSettingsValue.cs b/CollapseLauncher/Classes/Interfaces/IGameSettingsValue.cs index 2b06b2a87..8d5d67e93 100644 --- a/CollapseLauncher/Classes/Interfaces/IGameSettingsValue.cs +++ b/CollapseLauncher/Classes/Interfaces/IGameSettingsValue.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics; using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; namespace CollapseLauncher.Interfaces { @@ -16,6 +17,8 @@ internal interface IGameSettingsValueMagic : IGameSettingsValue [DebuggerBrowsable(DebuggerBrowsableState.Never)] byte[] Magic { get; } - abstract static T LoadWithMagic(byte[] magic, SettingsGameVersionManager versionManager, JsonSerializerContext context); +#nullable enable + abstract static T LoadWithMagic(byte[] magic, SettingsGameVersionManager versionManager, JsonTypeInfo typeInfo); +#nullable restore } } diff --git a/CollapseLauncher/Classes/Properties/InnerLauncherConfig.cs b/CollapseLauncher/Classes/Properties/InnerLauncherConfig.cs index 6cb98d87b..b6f284202 100644 --- a/CollapseLauncher/Classes/Properties/InnerLauncherConfig.cs +++ b/CollapseLauncher/Classes/Properties/InnerLauncherConfig.cs @@ -232,7 +232,7 @@ public static void SaveLocalNotificationData() RegionPushIgnoreMsgIds = NotificationData?.RegionPushIgnoreMsgIds }; File.WriteAllText(AppNotifIgnoreFile, - localNotificationData.Serialize(InternalAppJSONContext.Default)); + localNotificationData.Serialize(InternalAppJSONContext.Default.NotificationPush)); } public static void LoadLocalNotificationData() @@ -241,12 +241,12 @@ public static void LoadLocalNotificationData() { File.WriteAllText(AppNotifIgnoreFile, new NotificationPush() - .Serialize(InternalAppJSONContext.Default)); + .Serialize(InternalAppJSONContext.Default.NotificationPush)); } string data = File.ReadAllText(AppNotifIgnoreFile); NotificationPush? localNotificationData = - data.Deserialize(InternalAppJSONContext.Default); + data.Deserialize(InternalAppJSONContext.Default.NotificationPush); if (NotificationData == null) { return; diff --git a/CollapseLauncher/Classes/Properties/WindowSizeProp/WindowSizeProp.cs b/CollapseLauncher/Classes/Properties/WindowSizeProp/WindowSizeProp.cs index 38e542342..2aac761a1 100644 --- a/CollapseLauncher/Classes/Properties/WindowSizeProp/WindowSizeProp.cs +++ b/CollapseLauncher/Classes/Properties/WindowSizeProp/WindowSizeProp.cs @@ -31,11 +31,11 @@ Dictionary WindowSizeProfiles EventPostCarouselBounds = new Size(340, 158), PostPanelBounds = new Size(340, 84), PostPanelBottomMargin = new Thickness(0, 0, 0, 20), - PostPanelPaimonHeight = 138, - PostPanelPaimonMargin = new Thickness(0, -48, -64, 0), + PostPanelPaimonHeight = 110, + PostPanelPaimonMargin = new Thickness(0, -48, -56, 0), PostPanelPaimonInnerMargin = new Thickness(0, 0, 0, 0), - PostPanelPaimonTextMargin = new Thickness(0, 0, 8, 28), - PostPanelPaimonTextSize = 14, + PostPanelPaimonTextMargin = new Thickness(0, 0, 0, 18), + PostPanelPaimonTextSize = 11, BannerIconWidth = 136, BannerIconWidthHYP = 111, BannerIconMargin = new Thickness(0, 0, 94, 84), diff --git a/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs b/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs index 8bff6178f..8b9b283bf 100644 --- a/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs +++ b/CollapseLauncher/Classes/RegionManagement/FallbackCDNUtil.cs @@ -4,7 +4,6 @@ using Hi3Helper.Http; using Hi3Helper.Http.Legacy; using Hi3Helper.Shared.Region; -using Squirrel.Sources; using System; using System.Diagnostics; using System.IO; @@ -12,18 +11,30 @@ using System.Net; using System.Net.Http; using System.Net.Http.Json; -using System.Text; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using System.Threading; using System.Threading.Tasks; using static Hi3Helper.Logger; using static Hi3Helper.Shared.Region.LauncherConfig; +#if !USEVELOPACK +using System.Text; +using Squirrel.Sources; +#else +using System.Text; +using Velopack.Sources; +#endif + namespace CollapseLauncher { public class UpdateManagerHttpAdapter : IFileDownloader { +#if !USEVELOPACK public async Task DownloadFile(string url, string targetFile, Action progress, string authorization = null, string accept = null) +#else +#nullable enable + public async Task DownloadFile(string url, string targetFile, Action progress, string? authorization = null, string? accept = null, CancellationToken cancelToken = default) +#endif { // Initialize new proxy-aware HttpClient using HttpClient client = new HttpClientBuilder() @@ -36,7 +47,13 @@ public async Task DownloadFile(string url, string targetFile, Action progre try { FallbackCDNUtil.DownloadProgress += progressEvent; - await FallbackCDNUtil.DownloadCDNFallbackContent(downloadClient, targetFile, AppCurrentDownloadThread, GetRelativePathOnly(url), default); + await FallbackCDNUtil.DownloadCDNFallbackContent(downloadClient, targetFile, AppCurrentDownloadThread, GetRelativePathOnly(url), +#if !USEVELOPACK + default +#else + cancelToken +#endif + ); } catch { throw; } finally @@ -45,7 +62,11 @@ public async Task DownloadFile(string url, string targetFile, Action progre } } +#if !USEVELOPACK public async Task DownloadBytes(string url, string authorization = null, string accept = null) +#else + public async Task DownloadBytes(string url, string? authorization = null, string? accept = null) +#endif { await using BridgedNetworkStream stream = await FallbackCDNUtil.TryGetCDNFallbackStream(GetRelativePathOnly(url), default, true); byte[] buffer = new byte[stream.Length]; @@ -53,7 +74,11 @@ public async Task DownloadBytes(string url, string authorization = null, return buffer; } +#if !USEVELOPACK public async Task DownloadString(string url, string authorization = null, string accept = null) +#else + public async Task DownloadString(string url, string? authorization = null, string? accept = null) +#endif { await using BridgedNetworkStream stream = await FallbackCDNUtil.TryGetCDNFallbackStream(GetRelativePathOnly(url), default, true); byte[] buffer = new byte[stream.Length]; @@ -68,6 +93,10 @@ private string GetRelativePathOnly(string url) } } +#if USEVELOPACK +#nullable restore +#endif + internal readonly struct CDNUtilHTTPStatus { internal readonly HttpStatusCode StatusCode; @@ -80,7 +109,7 @@ internal CDNUtilHTTPStatus(HttpResponseMessage message) : this(false) Message = message; StatusCode = Message.StatusCode; IsSuccessStatusCode = Message.IsSuccessStatusCode; - AbsoluteURL = Message.RequestMessage.RequestUri; + AbsoluteURL = Message.RequestMessage?.RequestUri; } private CDNUtilHTTPStatus(bool isInitializationError) => IsInitializationError = isInitializationError; @@ -133,34 +162,6 @@ public static void InitializeHttpClient() public static event EventHandler DownloadProgress; - public static async Task DownloadCDNFallbackContent(Http httpInstance, string outputPath, int parallelThread, string relativeURL, CancellationToken token) - { - // Get the preferred CDN first and try get the content - CDNURLProperty preferredCDN = GetPreferredCDN(); - bool isSuccess = await TryGetCDNContent(preferredCDN, httpInstance, outputPath, relativeURL, parallelThread, token); - - // If successful, then return - if (isSuccess) return; - - // If the fail return code occurred by the token, then throw cancellation exception - token.ThrowIfCancellationRequested(); - - // If not, then continue to get the content from another CDN - foreach (CDNURLProperty fallbackCDN in CDNList.Where(x => !x.Equals(preferredCDN))) - { - isSuccess = await TryGetCDNContent(fallbackCDN, httpInstance, outputPath, relativeURL, parallelThread, token); - - // If successful, then return - if (isSuccess) return; - } - - // If all of them failed, then throw an exception - if (!isSuccess) - { - throw new AggregateException($"All available CDNs aren't reachable for your network while getting content: {relativeURL}. Please check your internet!"); - } - } - public static async Task DownloadCDNFallbackContent(DownloadClient downloadClient, string outputPath, int parallelThread, string relativeURL, CancellationToken token) { // Get the preferred CDN first and try get the content @@ -189,37 +190,6 @@ public static async Task DownloadCDNFallbackContent(DownloadClient downloadClien } } - public static async Task DownloadCDNFallbackContent(Http httpInstance, Stream outputStream, string relativeURL, CancellationToken token) - { - // Argument check - PerformStreamCheckAndSeek(outputStream); - - // Get the preferred CDN first and try get the content - CDNURLProperty preferredCDN = GetPreferredCDN(); - bool isSuccess = await TryGetCDNContent(preferredCDN, httpInstance, outputStream, relativeURL, token); - - // If successful, then return - if (isSuccess) return; - - // If the fail return code occurred by the token, then throw cancellation exception - token.ThrowIfCancellationRequested(); - - // If not, then continue to get the content from another CDN - foreach (CDNURLProperty fallbackCDN in CDNList.Where(x => !x.Equals(preferredCDN))) - { - isSuccess = await TryGetCDNContent(fallbackCDN, httpInstance, outputStream, relativeURL, token); - - // If successful, then return - if (isSuccess) return; - } - - // If all of them failed, then throw an exception - if (!isSuccess) - { - throw new AggregateException($"All available CDNs aren't reachable for your network while getting content: {relativeURL}. Please check your internet!"); - } - } - public static async Task DownloadCDNFallbackContent(DownloadClient downloadClient, Stream outputStream, string relativeURL, CancellationToken token) { // Argument check @@ -251,7 +221,7 @@ public static async Task DownloadCDNFallbackContent(DownloadClient downloadClien } } - public static async ValueTask TryGetCDNFallbackStream(string relativeURL, CancellationToken token = default, bool isForceUncompressRequest = false) + public static async Task TryGetCDNFallbackStream(string relativeURL, CancellationToken token = default, bool isForceUncompressRequest = false) { // Get the preferred CDN first and try get the content CDNURLProperty preferredCDN = GetPreferredCDN(); @@ -290,7 +260,7 @@ private static void PerformStreamCheckAndSeek(Stream outputStream) outputStream.Position = 0; } - private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, string relativeURL, CancellationToken token, bool isForceUncompressRequest) + private static async Task TryGetCDNContent(CDNURLProperty cdnProp, string relativeURL, CancellationToken token, bool isForceUncompressRequest) { try { @@ -311,58 +281,24 @@ private static async ValueTask TryGetCDNContent(CDNURLProp } } - private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, Http httpInstance, Stream outputStream, string relativeURL, CancellationToken token) + private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, DownloadClient downloadClient, Stream outputStream, string relativeURL, CancellationToken token) { - try - { - // Subscribe the progress to the adapter - httpInstance.DownloadProgress += HttpInstance_DownloadProgressAdapter; - - // Get the URL Status then return boolean and and URLStatus - (bool, string) urlStatus = await TryGetURLStatus(cdnProp, httpInstance, relativeURL, token); - - // If URL status is false, then return false - if (!urlStatus.Item1) return false; - - // Continue to get the content and return true if successful - await httpInstance.Download(urlStatus.Item2, outputStream, null, null, token); - return true; - } - // Handle the error and log it. If fails, then log it and return false - catch (Exception ex) - { - LogWriteLine($"Failed while getting CDN content from: {cdnProp.Name} (prefix: {cdnProp.URLPrefix}) (relPath: {relativeURL})\r\n{ex}", LogType.Error, true); - return false; - } - // Finally, unsubscribe the progress from the adapter - finally - { - httpInstance.DownloadProgress -= HttpInstance_DownloadProgressAdapter; - } - } + DownloadEvent DownloadClientAdapter = new DownloadEvent(); + Stopwatch stopwatch = new Stopwatch(); - private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, Http httpInstance, string outputPath, string relativeURL, int parallelThread, CancellationToken token) - { try { - // Subscribe the progress to the adapter - httpInstance.DownloadProgress += HttpInstance_DownloadProgressAdapter; - // Get the URL Status then return boolean and and URLStatus - (bool, string) urlStatus = await TryGetURLStatus(cdnProp, httpInstance, relativeURL, token); + (bool, string) urlStatus = await TryGetURLStatus(cdnProp, downloadClient, relativeURL, token); // If URL status is false, then return false if (!urlStatus.Item1) return false; + // Start stopwatch + stopwatch.Start(); + // Continue to get the content and return true if successful - if (!cdnProp.PartialDownloadSupport) - { - // If the CDN marked to not supporting the partial download, then use single thread mode download. - await httpInstance.Download(urlStatus.Item2, outputPath, true, null, null, token); - return true; - } - await httpInstance.Download(urlStatus.Item2, outputPath, (byte)parallelThread, true, token); - await httpInstance.Merge(token); + await downloadClient.DownloadAsync(urlStatus.Item2, outputStream, false, HttpInstanceDownloadProgressAdapter, null, null, cancelToken:token); return true; } // Handle the error and log it. If fails, then log it and return false @@ -371,37 +307,29 @@ private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, Ht LogWriteLine($"Failed while getting CDN content from: {cdnProp.Name} (prefix: {cdnProp.URLPrefix}) (relPath: {relativeURL})\r\n{ex}", LogType.Error, true); return false; } - // Finally, unsubscribe the progress from the adapter finally { - httpInstance.DownloadProgress -= HttpInstance_DownloadProgressAdapter; + stopwatch.Stop(); } - } - - private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, DownloadClient downloadClient, Stream outputStream, string relativeURL, CancellationToken token) - { - try - { - // Get the URL Status then return boolean and and URLStatus - (bool, string) urlStatus = await TryGetURLStatus(cdnProp, downloadClient, relativeURL, token); - - // If URL status is false, then return false - if (!urlStatus.Item1) return false; - // Continue to get the content and return true if successful - await downloadClient.DownloadAsync(urlStatus.Item2, outputStream, false, HttpInstance_DownloadProgressAdapter, null, null, cancelToken:token); - return true; - } - // Handle the error and log it. If fails, then log it and return false - catch (Exception ex) + void HttpInstanceDownloadProgressAdapter(int read, DownloadProgress downloadProgress) { - LogWriteLine($"Failed while getting CDN content from: {cdnProp.Name} (prefix: {cdnProp.URLPrefix}) (relPath: {relativeURL})\r\n{ex}", LogType.Error, true); - return false; + DownloadClientAdapter.SizeToBeDownloaded = downloadProgress.BytesTotal; + DownloadClientAdapter.SizeDownloaded = downloadProgress.BytesDownloaded; + DownloadClientAdapter.Read = read; + + long speed = (long)(downloadProgress.BytesDownloaded / stopwatch.Elapsed.TotalSeconds); + DownloadClientAdapter.Speed = speed; + + DownloadProgress?.Invoke(null, DownloadClientAdapter); } } private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, DownloadClient downloadClient, string outputPath, string relativeURL, int parallelThread, CancellationToken token) { + DownloadEvent DownloadClientAdapter = new DownloadEvent(); + Stopwatch stopwatch = new Stopwatch(); + try { // Get the URL Status then return boolean and and URLStatus @@ -410,15 +338,18 @@ private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, Do // If URL status is false, then return false if (!urlStatus.Item1) return false; + // Start stopwatch + stopwatch.Start(); + // Continue to get the content and return true if successful if (!cdnProp.PartialDownloadSupport) { // If the CDN marked to not supporting the partial download, then use single thread mode download. using FileStream stream = File.Create(outputPath); - await downloadClient.DownloadAsync(urlStatus.Item2, stream, false, HttpInstance_DownloadProgressAdapter, null, null, cancelToken:token); + await downloadClient.DownloadAsync(urlStatus.Item2, stream, false, HttpInstanceDownloadProgressAdapter, null, null, cancelToken:token); return true; } - await downloadClient.DownloadAsync(urlStatus.Item2, outputPath, true, progressDelegateAsync: HttpInstance_DownloadProgressAdapter, maxConnectionSessions: parallelThread, cancelToken: token); + await downloadClient.DownloadAsync(urlStatus.Item2, outputPath, true, progressDelegateAsync: HttpInstanceDownloadProgressAdapter, maxConnectionSessions: parallelThread, cancelToken: token); return true; } // Handle the error and log it. If fails, then log it and return false @@ -427,30 +358,25 @@ private static async ValueTask TryGetCDNContent(CDNURLProperty cdnProp, Do LogWriteLine($"Failed while getting CDN content from: {cdnProp.Name} (prefix: {cdnProp.URLPrefix}) (relPath: {relativeURL})\r\n{ex}", LogType.Error, true); return false; } - } - - private static async Task<(bool, string)> TryGetURLStatus(CDNURLProperty cdnProp, Http httpInstance, string relativeURL, CancellationToken token) - { - // Concat the URL Prefix and Relative URL - string absoluteURL = ConverterTool.CombineURLFromString(cdnProp.URLPrefix, relativeURL); + finally + { + stopwatch.Stop(); + } - LogWriteLine($"Getting CDN Content from: {cdnProp.Name} at URL: {absoluteURL}", LogType.Default, true); + void HttpInstanceDownloadProgressAdapter(int read, DownloadProgress downloadProgress) + { + DownloadClientAdapter.SizeToBeDownloaded = downloadProgress.BytesTotal; + DownloadClientAdapter.SizeDownloaded = downloadProgress.BytesDownloaded; + DownloadClientAdapter.Read = read; - // Try check the status of the URL - Tuple returnCode = await httpInstance.GetURLStatus(absoluteURL, token); + long speed = (long)(downloadProgress.BytesDownloaded / stopwatch.Elapsed.TotalSeconds); + DownloadClientAdapter.Speed = speed; - // If it's not a successful code, then return false - if (!returnCode.Item2) - { - LogWriteLine($"CDN content from: {cdnProp.Name} (prefix: {cdnProp.URLPrefix}) (relPath: {relativeURL}) has returned error code: {returnCode.Item1}", LogType.Error, true); - return (false, absoluteURL); + DownloadProgress?.Invoke(null, DownloadClientAdapter); } - - // Otherwise, return true - return (true, absoluteURL); } - private static async Task<(bool, string)> TryGetURLStatus(CDNURLProperty cdnProp, DownloadClient downloadClient, string relativeURL, CancellationToken token) + private static async ValueTask<(bool, string)> TryGetURLStatus(CDNURLProperty cdnProp, DownloadClient downloadClient, string relativeURL, CancellationToken token) { // Concat the URL Prefix and Relative URL string absoluteURL = ConverterTool.CombineURLFromString(cdnProp.URLPrefix, relativeURL); @@ -513,22 +439,16 @@ public static CDNURLProperty GetPreferredCDN() return CDNList[cdnIndex]; } - public static async Task DownloadAsJSONType(string URL, JsonSerializerContext context, CancellationToken token) - => (T)await _client.GetFromJsonAsync(URL, typeof(T), context, token) ?? default; - - public static async ValueTask GetURLHttpResponse(string URL, CancellationToken token, bool isForceUncompressRequest = false) - { - return isForceUncompressRequest ? await _clientNoCompression.GetURLHttpResponse(URL, HttpMethod.Get, token) - : await _client.GetURLHttpResponse(URL, HttpMethod.Get, token); - } + public static async Task GetURLHttpResponse(string URL, CancellationToken token, bool isForceUncompressRequest = false) + => isForceUncompressRequest ? await _clientNoCompression.GetURLHttpResponse(URL, HttpMethod.Get, token) + : await _client.GetURLHttpResponse(URL, HttpMethod.Get, token); #nullable enable - public static async ValueTask GetURLHttpResponse(this HttpClient client, string url, HttpMethod? httpMethod = null, CancellationToken token = default) - { - httpMethod ??= HttpMethod.Get; - using HttpRequestMessage requestMsg = new HttpRequestMessage(httpMethod, url); - return await client.SendAsync(requestMsg, HttpCompletionOption.ResponseHeadersRead, token); - } + public static async Task DownloadAsJSONType(string? URL, JsonTypeInfo typeInfo, CancellationToken token) + => await _client.GetFromJsonAsync(URL, typeInfo, token); + + public static async Task GetURLHttpResponse(this HttpClient client, string url, HttpMethod? httpMethod = null, CancellationToken token = default) + => await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token); public static async ValueTask GetURLStatusCode(string URL, CancellationToken token) => await _client.GetURLStatusCode(URL, token); @@ -540,13 +460,13 @@ public static async ValueTask GetURLStatusCode(this HttpClient client } #nullable restore - public static async ValueTask GetHttpStreamFromResponse(string URL, CancellationToken token) + public static async Task GetHttpStreamFromResponse(string URL, CancellationToken token) { HttpResponseMessage responseMsg = await GetURLHttpResponse(URL, token); return await GetHttpStreamFromResponse(responseMsg, token); } - public static async ValueTask GetHttpStreamFromResponse(HttpResponseMessage responseMsg, CancellationToken token) + public static async Task GetHttpStreamFromResponse(HttpResponseMessage responseMsg, CancellationToken token) => await BridgedNetworkStream.CreateStream(responseMsg, token); public static async ValueTask GetCDNLatencies(CancellationTokenSource tokenSource, int pingCount = 1) @@ -591,17 +511,12 @@ public static async ValueTask GetCDNLatencies(CancellationTokenSource to return latencies; } - public static async ValueTask GetContentLength(string url, CancellationToken token) - { - using HttpRequestMessage message = new HttpRequestMessage() { RequestUri = new Uri(url) }; - using HttpResponseMessage response = await _clientNoCompression.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, token); - long Length = response.Content.Headers.ContentLength ?? 0; - return Length; - } + public static async ValueTask GetContentLength(string url, CancellationToken token) => + (await _clientNoCompression.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token)).Content.Headers.ContentLength ?? 0; public static string TryGetAbsoluteToRelativeCDNURL(string URL, string searchIndexStr) { - int indexOf = URL.IndexOf(searchIndexStr); + int indexOf = URL.IndexOf(searchIndexStr, StringComparison.Ordinal); if (indexOf < 0) return URL; @@ -612,17 +527,5 @@ public static string TryGetAbsoluteToRelativeCDNURL(string URL, string searchInd URL = ConverterTool.CombineURLFromString(cdnParentURL, relativeURL); return URL; } - - // Re-send the events to the static DownloadProgress - private static void HttpInstance_DownloadProgressAdapter(object sender, DownloadEvent e) => DownloadProgress?.Invoke(sender, e); - - private static DownloadEvent DownloadClientAdapter = new DownloadEvent(); - - private static void HttpInstance_DownloadProgressAdapter(int read, DownloadProgress downloadProgress) - { - DownloadClientAdapter.SizeToBeDownloaded = downloadProgress.BytesTotal; - DownloadClientAdapter.SizeDownloaded = downloadProgress.BytesDownloaded; - DownloadClientAdapter.Read = read; - } } } diff --git a/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs b/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs index ad6095bc5..d23ce8c8f 100644 --- a/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/Genshin/Fetch.cs @@ -333,7 +333,7 @@ private void ParsePersistentManifest(string localManifestPath, while (!reader.EndOfStream) { string manifestLine = reader.ReadLine(); - PkgVersionProperties manifestEntry = manifestLine.Deserialize(CoreLibraryJSONContext.Default); + PkgVersionProperties manifestEntry = manifestLine.Deserialize(CoreLibraryJSONContext.Default.PkgVersionProperties); // Ignore if the remote name is "svc_catalog" or "ctable.dat" if (Path.GetFileName(manifestEntry.remoteName).Equals("svc_catalog", StringComparison.OrdinalIgnoreCase) || @@ -529,7 +529,7 @@ private void ParseManifestToAssetIndex(string manifestPath, List x.EndsWith(acceptedExtension, StringComparison.OrdinalIgnoreCase))) { // Deserialize JSON line into local entry. - entry = data.Deserialize(CoreLibraryJSONContext.Default); + entry = data.Deserialize(CoreLibraryJSONContext.Default.PkgVersionProperties); // If the parent path is not defined, then use already-defined parent path from JSON and append it as remote name. if (!string.IsNullOrEmpty(parentPath)) diff --git a/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs b/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs index 337dd4598..653ee1cf5 100644 --- a/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/Honkai/Fetch.cs @@ -181,9 +181,9 @@ await FetchXMFFile(client, assetIndex, blocksPlatformManifestSenadinaFileIdentif using StreamReader rd = new StreamReader(fileIdentifierStreamDecoder); string response = await rd.ReadToEndAsync(); LogWriteLine($"[HonkaiRepair::GetSenadinaIdentifierDictionary() Dictionary Response:\r\n{response}", LogType.Debug, true); - return response.Deserialize>(SenadinaJSONContext.Default); + return response.Deserialize(SenadinaJSONContext.Default.DictionaryStringSenadinaFileIdentifier); #else - return await fileIdentifierStreamDecoder.DeserializeAsync>(SenadinaJSONContext.Default, token); + return await fileIdentifierStreamDecoder.DeserializeAsync(SenadinaJSONContext.Default.DictionaryStringSenadinaFileIdentifier, token); #endif } @@ -220,7 +220,7 @@ private async ValueTask ThrowIfFileIsNotSenadina(Stream stream, CancellationToke ArgumentNullException.ThrowIfNull(stream); Memory header = new byte[_collapseHeader.Length]; - await stream.ReadAsync(header, token)!; + _ = await stream.ReadAsync(header, token)!; if (!header.Span.SequenceEqual(_collapseHeader)) throw new InvalidDataException($"Daftar pustaka file is corrupted! Expecting header: 0x{BinaryPrimitives.ReadInt64LittleEndian(_collapseHeader):x8} but got: 0x{BinaryPrimitives.ReadInt64LittleEndian(header.Span):x8} instead!"); } @@ -241,19 +241,19 @@ private void EliminatePluginAssetIndex(List assetIndex) private HonkaiRepairAssetIgnore GetIgnoredAssetsProperty() { // Try get the parent registry key - RegistryKey? keys = Registry.CurrentUser.OpenSubKey(_gameVersionManager!.GamePreset!.ConfigRegistryLocation!); + RegistryKey? keys = Registry.CurrentUser.OpenSubKey(_gameVersionManager!.GamePreset!.ConfigRegistryLocation); if (keys == null) return HonkaiRepairAssetIgnore.CreateEmpty(); // Return an empty property if the parent key doesn't exist // Initialize the property - AudioPCKType[] IgnoredAudioPCKTypes = Array.Empty(); - int[] IgnoredVideoCGSubCategory = Array.Empty(); + AudioPCKType[] IgnoredAudioPCKTypes = []; + int[] IgnoredVideoCGSubCategory = []; // Try get the values of the registry key of the Audio ignored list object? objIgnoredAudioPCKTypes = keys.GetValue("GENERAL_DATA_V2_DeletedAudioTypes_h214176984"); if (objIgnoredAudioPCKTypes != null) { ReadOnlySpan bytesIgnoredAudioPckTypes = (byte[])objIgnoredAudioPCKTypes; - IgnoredAudioPCKTypes = bytesIgnoredAudioPckTypes.Deserialize(InternalAppJSONContext.Default) ?? IgnoredAudioPCKTypes; + IgnoredAudioPCKTypes = bytesIgnoredAudioPckTypes.Deserialize(InternalAppJSONContext.Default.AudioPCKTypeArray) ?? IgnoredAudioPCKTypes; } // Try get the values of the registry key of the Video CG ignored list @@ -261,7 +261,7 @@ private HonkaiRepairAssetIgnore GetIgnoredAssetsProperty() if (objIgnoredVideoCGSubCategory != null) { ReadOnlySpan bytesIgnoredVideoCGSubCategory = (byte[])objIgnoredVideoCGSubCategory; - IgnoredVideoCGSubCategory = bytesIgnoredVideoCGSubCategory.Deserialize(InternalAppJSONContext.Default) ?? IgnoredVideoCGSubCategory; + IgnoredVideoCGSubCategory = bytesIgnoredVideoCGSubCategory.Deserialize(InternalAppJSONContext.Default.Int32Array) ?? IgnoredVideoCGSubCategory; } // Return the property value @@ -310,14 +310,13 @@ private async Task BuildVideoIndex(DownloadClient downloadClient, CacheAsset cac private async Task BuildAndEnumerateVideoVersioningFile(CancellationToken token, IEnumerable enumEntry, List assetIndex, HonkaiRepairAssetIgnore ignoredAssetIDs, string assetBundleURL) { - ArgumentNullException.ThrowIfNull(token); ArgumentNullException.ThrowIfNull(assetIndex); // Get the base URL string baseURL = CombineURLFromString((GetAppConfigValue("EnableHTTPRepairOverride").ToBool() ? "http://" : "https://") + assetBundleURL, "/Video/"); // Build video versioning file - using (StreamWriter sw = new StreamWriter(Path.Combine(_gamePath!, NormalizePath(_videoBaseLocalPath)!, "Version.txt"), false)) + await using (StreamWriter sw = new StreamWriter(Path.Combine(_gamePath!, NormalizePath(_videoBaseLocalPath)!, "Version.txt"), false)) { // Iterate the metadata to be converted into asset index in parallel await Parallel.ForEachAsync(enumEntry!, new ParallelOptions @@ -340,10 +339,10 @@ private async Task BuildAndEnumerateVideoVersioningFile(CancellationToken token, } } - #if DEBUG + #if DEBUG if (isCGIgnored) LogWriteLine($"Ignoring CG Category: {metadata.CgSubCategory} {(_audioLanguage == AudioLanguageType.Japanese ? metadata.CgPathHighBitrateJP : metadata.CgPathHighBitrateCN)}", LogType.Debug, true); - #endif + #endif if (!metadata.InStreamingAssets && isCGAvailable && !isCGIgnored) { @@ -534,7 +533,7 @@ private async Task> FetchMetadata(CancellationToken t // Start downloading metadata using FallbackCDNUtil await using BridgedNetworkStream stream = await FallbackCDNUtil.TryGetCDNFallbackStream(urlMetadata, token); - return await stream!.DeserializeAsync>(CoreLibraryJSONContext.Default, token); + return await stream!.DeserializeAsync(CoreLibraryJSONContext.Default.DictionaryStringString, token); } private async Task FetchAssetIndex(List assetIndex, CancellationToken token) @@ -603,101 +602,100 @@ private async Task FetchXMFFile(HttpClient _httpClient, List FetchPatchConfigXMFFile(Stream xmfStream, SenadinaFileIdentifier patchConfigFileIdentifier, - HttpClient _httpClient, CancellationToken token) + HttpClient httpClient, CancellationToken token) { // Start downloading XMF and load it to MemoryStream first - using (MemoryStream mfs = new MemoryStream()) - { - // Copy the remote stream of Patch Config to temporal mfs - await patchConfigFileIdentifier!.fileStream!.CopyToAsync(mfs, token); - // Reset the MemoryStream position - mfs.Position = 0; - -#nullable enable - // Get the version provided by the XMF - int[]? gameVersion = XMFUtility.GetXMFVersion(xmfStream); - if (gameVersion == null) return null; -#nullable disable - - // Initialize and parse the manifest, then return the Patch Asset - return new BlockPatchManifest(mfs, gameVersion); - } + using MemoryStream mfs = new MemoryStream(); + // Copy the remote stream of Patch Config to temporal mfs + await patchConfigFileIdentifier!.fileStream!.CopyToAsync(mfs, token); + // Reset the MemoryStream position + mfs.Position = 0; + + #nullable enable + // Get the version provided by the XMF + int[]? gameVersion = XMFUtility.GetXMFVersion(xmfStream); + // Initialize and parse the manifest, then return the Patch Asset + return gameVersion == null ? null : new BlockPatchManifest(mfs, gameVersion); } -#nullable enable private void BuildBlockIndex(List assetIndex, BlockPatchManifest? patchInfo, string xmfPath, Stream xmfStream, bool isMeta) { // Reset the temporal stream pos. diff --git a/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs b/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs index c89e51829..8931ea0f8 100644 --- a/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs +++ b/CollapseLauncher/Classes/RepairManagement/StarRail/Fetch.cs @@ -202,7 +202,7 @@ private async Task> FetchMetadata(CancellationToken t // Start downloading metadata using FallbackCDNUtil await using BridgedNetworkStream stream = await FallbackCDNUtil.TryGetCDNFallbackStream(urlMetadata, token); - return await stream.DeserializeAsync>(CoreLibraryJSONContext.Default, token); + return await stream.DeserializeAsync(CoreLibraryJSONContext.Default.DictionaryStringString, token); } private void ConvertPkgVersionToAssetIndex(List pkgVersion, List assetIndex) diff --git a/CollapseLauncher/CollapseLauncher.csproj b/CollapseLauncher/CollapseLauncher.csproj index ec4c2f4df..fb21f9ccd 100644 --- a/CollapseLauncher/CollapseLauncher.csproj +++ b/CollapseLauncher/CollapseLauncher.csproj @@ -11,26 +11,33 @@ Collapse Collapse Collapse Launcher - Collapse Launcher + Collapse Launcher Collapse Launcher Team $(Company). neon-nyan, Cry0, bagusnl, shatyuka, gablm. Copyright 2022-2024 $(Company) - 1.81.6 + 1.81.7 preview x64 - net8.0-windows10.0.22621.0 - 10.0.22621.41 + net9.0-windows10.0.22621.0 + 10.0.22621.48 10.0.17763.0 win-x64 true + true + true portable false + + false true true + + true + false true true @@ -39,7 +46,11 @@ false true true - false + false + + + + true - DISABLE_XAML_GENERATED_MAIN;USENEWZIPDECOMPRESS;ENABLEHTTPREPAIR;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;PREVIEW;DUMPGIJSON;SIMULATEGIHDR;GSPBYPASSGAMERUNNING;MHYPLUGINSUPPORT - + DISABLE_XAML_GENERATED_MAIN;USEVELOPACK;USENEWZIPDECOMPRESS;ENABLEHTTPREPAIR;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;PREVIEW;DUMPGIJSON;SIMULATEGIHDR;GSPBYPASSGAMERUNNING;MHYPLUGINSUPPORT + full + true + - DISABLE_XAML_GENERATED_MAIN;USENEWZIPDECOMPRESS;ENABLEHTTPREPAIR;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;PREVIEW;MHYPLUGINSUPPORT + DISABLE_XAML_GENERATED_MAIN;USEVELOPACK;USENEWZIPDECOMPRESS;ENABLEHTTPREPAIR;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;PREVIEW;MHYPLUGINSUPPORT True true - DISABLE_XAML_GENERATED_MAIN;ENABLEHTTPREPAIR;USENEWZIPDECOMPRESS;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;MHYPLUGINSUPPORT + DISABLE_XAML_GENERATED_MAIN;USEVELOPACK;USENEWZIPDECOMPRESS;ENABLEHTTPREPAIR;DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION;MHYPLUGINSUPPORT true true - + + + - - - - - - - - - - + + + + + + + + - - - + + + - - - + + + + + + + + + + + + diff --git a/CollapseLauncher/Program.cs b/CollapseLauncher/Program.cs index 9f4224b56..b7661d278 100644 --- a/CollapseLauncher/Program.cs +++ b/CollapseLauncher/Program.cs @@ -1,10 +1,16 @@ -using CollapseLauncher.Helper.Update; +using CollapseLauncher.Helper; +using CollapseLauncher.Helper.Update; using Hi3Helper; using Hi3Helper.Http.Legacy; using Hi3Helper.Shared.ClassStruct; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; +#if !USEVELOPACK using Squirrel; +#else +using NuGet.Versioning; +using Velopack; +#endif using System; using System.Diagnostics; using System.Globalization; @@ -19,15 +25,16 @@ using static Hi3Helper.Locale; using static Hi3Helper.Logger; using static Hi3Helper.Shared.Region.LauncherConfig; +using InnoSetupHelper; namespace CollapseLauncher; public static class MainEntryPoint { - #nullable enable +#nullable enable public static int InstanceCount; public static App? CurrentAppInstance; - #nullable restore +#nullable restore [DllImport("Microsoft.ui.xaml.dll")] private static extern void XamlCheckProcessRequirements(); @@ -35,14 +42,12 @@ public static class MainEntryPoint [STAThread] public static void Main(params string[] args) { - #if PREVIEW +#if PREVIEW IsPreview = true; - #endif +#endif try { - StartSquirrelHook(); - AppCurrentArgument = args; // Extract icons from the executable file @@ -69,6 +74,8 @@ public static void Main(params string[] args) Directory.SetCurrentDirectory(AppFolder); } + StartUpdaterHook(); + LogWriteLine(string.Format("Running Collapse Launcher [{0}], [{3}], under {1}, as {2}", LauncherUpdateHelper.LauncherCurrentVersionString, GetVersionString(), @@ -88,6 +95,9 @@ public static void Main(params string[] args) ParseArguments(args); InitializeAppSettings(); + // Initiate InnoSetupHelper's log event + InnoSetupLogUpdate.LoggerEvent += InnoSetupLogUpdate_LoggerEvent; + HttpLogInvoker.DownloadLog += HttpClientLogWatcher!; switch (m_appMode) @@ -142,6 +152,17 @@ public static void Main(params string[] args) } } + private static void InnoSetupLogUpdate_LoggerEvent(object sender, InnoSetupLogStruct e) => + LogWriteLine( + e.Message, + e.LogType switch + { + InnoSetupLogType.Warning => LogType.Warning, + InnoSetupLogType.Error => LogType.Error, + _ => LogType.Default + }, + e.IsWriteToLog); + public static void SpawnFatalErrorConsole(Exception ex) { CurrentAppInstance?.Exit(); @@ -151,7 +172,7 @@ public static void SpawnFatalErrorConsole(Exception ex) "please report it to: https://github.com/CollapseLauncher/Collapse/issues\r\n" + "Press any key to exit or Press 'R' to restart the main thread app..."); - #if !DEBUG +#if !DEBUG try { if (ConsoleKey.R == Console.ReadKey().Key) @@ -162,7 +183,7 @@ public static void SpawnFatalErrorConsole(Exception ex) Console.WriteLine(e); throw; } - #endif +#endif } public static void StartMainApplication() @@ -196,8 +217,9 @@ private static void OnProcessExit(object sender, EventArgs e) App.IsAppKilled = true; } - private static void StartSquirrelHook() + private static void StartUpdaterHook() { +#if !USEVELOPACK // Add Squirrel Hooks SquirrelAwareApp.HandleEvents( // Add shortcut and uninstaller entry on first start-up @@ -221,7 +243,93 @@ private static void StartSquirrelHook() // ReSharper restore UnusedParameter.Local onEveryRun: (_, _, _) => { } ); +#else + VelopackApp.Build() + .WithRestarted(TryCleanupFallbackUpdate) + .WithAfterUpdateFastCallback(TryCleanupFallbackUpdate) + .WithFirstRun(TryCleanupFallbackUpdate) + .Run(ILoggerHelper.CreateCollapseILogger()); +#endif + } + +#if USEVELOPACK + public static void TryCleanupFallbackUpdate(SemanticVersion newVersion) + { + string currentExecutedAppFolder = AppFolder.TrimEnd('\\'); + + // If the path is not actually running under "current" velopack folder, then return + if (!currentExecutedAppFolder.EndsWith("current", StringComparison.OrdinalIgnoreCase)) // Expecting "current" + { + Logger.LogWriteLine("[TryCleanupFallbackUpdate] The launcher does not run from \"current\" folder"); + return; + } + + try + { + // Otherwise, start cleaning-up process + string currentExecutedParentFolder = Path.GetDirectoryName(currentExecutedAppFolder); + if (currentExecutedParentFolder != null) + { + DirectoryInfo directoryInfo = new DirectoryInfo(currentExecutedParentFolder); + foreach (DirectoryInfo childLegacyAppSemVerFolder in directoryInfo.EnumerateDirectories("app-*", SearchOption.TopDirectoryOnly)) + { + // Removing the "app-*" folder + childLegacyAppSemVerFolder.Delete(true); + Logger.LogWriteLine($"[TryCleanupFallbackUpdate] Removed {childLegacyAppSemVerFolder.FullName} folder!", LogType.Default, true); + } + + // Try to remove squirrel temp clowd folder + string squirrelTempPackagesFolder = Path.Combine(currentExecutedParentFolder, "SquirrelClowdTemp"); + DirectoryInfo squirrelTempPackagesFolderInfo = new DirectoryInfo(squirrelTempPackagesFolder); + if (squirrelTempPackagesFolderInfo.Exists) + { + squirrelTempPackagesFolderInfo.Delete(true); + Logger.LogWriteLine($"[TryCleanupFallbackUpdate] Removed package temp folder: {squirrelTempPackagesFolder}!", LogType.Default, true); + } + + // Try to remove stub executable + string squirrelLegacyStubPath = Path.Combine(currentExecutedParentFolder, "CollapseLauncher.exe"); + RemoveSquirrelFilePath(squirrelLegacyStubPath); + + // Try to remove createdump executable + string squirrelLegacyDumpPath = Path.Combine(currentExecutedParentFolder, "createdump.exe"); + RemoveSquirrelFilePath(squirrelLegacyDumpPath); + + // Try to remove RestartAgent executable + string squirrelLegacyRestartAgentPath = Path.Combine(currentExecutedParentFolder, "RestartAgent.exe"); + RemoveSquirrelFilePath(squirrelLegacyRestartAgentPath); + } + + // Try to remove legacy shortcuts + string currentWindowsPathDrive = Path.GetPathRoot(Environment.SystemDirectory); + if (currentWindowsPathDrive != null) + { + string squirrelLegacyStartMenuGlobal = Path.Combine(currentWindowsPathDrive, @"ProgramData\Microsoft\Windows\Start Menu\Programs\Collapse\Collapse Launcher"); + string squirrelLegacyStartMenuGlobalParent = Path.GetDirectoryName(squirrelLegacyStartMenuGlobal); + if (Directory.Exists(squirrelLegacyStartMenuGlobalParent) && Directory.Exists(squirrelLegacyStartMenuGlobal)) + { + Directory.Delete(squirrelLegacyStartMenuGlobalParent, true); + } + } + + // Try to recreate shortcuts + TaskSchedulerHelper.RecreateIconShortcuts(); + } + catch (Exception ex) + { + Logger.LogWriteLine($"[TryCleanupFallbackUpdate] Failed while operating clean-up routines...\r\n{ex}"); + } + + void RemoveSquirrelFilePath(string filePath) + { + if (File.Exists(filePath)) + { + File.Delete(filePath); + Logger.LogWriteLine($"[TryCleanupFallbackUpdate] Removed old squirrel executables: {filePath}!", LogType.Default, true); + } + } } +#endif public static string FindCollapseStubPath() { diff --git a/CollapseLauncher/Properties/PublishProfiles/Publish-DebugCIRelease.pubxml b/CollapseLauncher/Properties/PublishProfiles/Publish-DebugCIRelease.pubxml new file mode 100644 index 000000000..fda0e2e90 --- /dev/null +++ b/CollapseLauncher/Properties/PublishProfiles/Publish-DebugCIRelease.pubxml @@ -0,0 +1,56 @@ + + + + + + Debug + x64 + + + ..\..\CollapseLauncher-ReleaseRepo\preview-ci-build + FileSystem + + + net9.0-windows10.0.22621.0 + win-x64 + + + true + false + true + full + true + false + false + false + + + true + true + Speed + Speed + true + true + true + true + + + true + false + true + false + + + false + false + false + false + false + true + + + true + + \ No newline at end of file diff --git a/CollapseLauncher/Properties/PublishProfiles/Publish-DebugCIReleaseAOT.pubxml b/CollapseLauncher/Properties/PublishProfiles/Publish-DebugCIReleaseAOT.pubxml new file mode 100644 index 000000000..132d1e2e4 --- /dev/null +++ b/CollapseLauncher/Properties/PublishProfiles/Publish-DebugCIReleaseAOT.pubxml @@ -0,0 +1,56 @@ + + + + + + Debug + x64 + + + ..\..\CollapseLauncher-ReleaseRepo\preview-ci-build + FileSystem + + + net9.0-windows10.0.22621.0 + win-x64 + + + true + false + true + full + true + false + true + true + + + true + true + Speed + Speed + true + true + true + true + + + true + false + true + false + + + false + false + false + false + false + true + + + true + + \ No newline at end of file diff --git a/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewRelease.pubxml b/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewRelease.pubxml index 54ccea8cb..dbc990be9 100644 --- a/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewRelease.pubxml +++ b/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewRelease.pubxml @@ -3,30 +3,53 @@ https://go.microsoft.com/fwlink/?LinkID=208121. --> - - Release - x64 - ..\..\CollapseLauncher-ReleaseRepo\preview-build - FileSystem - net8.0-windows10.0.22621.0 - win-x64 - true - false - true - partial - true - false - false - false - false - false - false - true - false - false - false - true - true - Speed - + + + Release + x64 + + + ..\..\CollapseLauncher-ReleaseRepo\preview-build + FileSystem + + + net9.0-windows10.0.22621.0 + win-x64 + + + true + false + true + full + true + false + false + false + + + true + Speed + Speed + true + true + true + true + + + true + false + true + false + + + false + false + false + false + false + true + + + true + \ No newline at end of file diff --git a/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewReleaseAOT.pubxml b/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewReleaseAOT.pubxml new file mode 100644 index 000000000..8bd01850c --- /dev/null +++ b/CollapseLauncher/Properties/PublishProfiles/Publish-PreviewReleaseAOT.pubxml @@ -0,0 +1,55 @@ + + + + + + Release + x64 + + + ..\..\CollapseLauncher-ReleaseRepo\preview-build + FileSystem + + + net9.0-windows10.0.22621.0 + win-x64 + + + true + false + true + full + true + false + true + true + + + true + Speed + Speed + true + true + true + true + + + true + false + true + false + + + false + false + false + false + false + true + + + true + + \ No newline at end of file diff --git a/CollapseLauncher/Properties/PublishProfiles/Publish-StableRelease.pubxml b/CollapseLauncher/Properties/PublishProfiles/Publish-StableRelease.pubxml index b76b45239..80accc03f 100644 --- a/CollapseLauncher/Properties/PublishProfiles/Publish-StableRelease.pubxml +++ b/CollapseLauncher/Properties/PublishProfiles/Publish-StableRelease.pubxml @@ -3,30 +3,53 @@ https://go.microsoft.com/fwlink/?LinkID=208121. --> - - Publish - x64 - ..\..\CollapseLauncher-ReleaseRepo\stable-build - FileSystem - net8.0-windows10.0.22621.0 - win-x64 - true - false - true - partial - true - false - false - false - false - false - false - true - false - false - false - true - true - Speed - + + + Publish + x64 + + + ..\..\CollapseLauncher-ReleaseRepo\stable-build + FileSystem + + + net9.0-windows10.0.22621.0 + win-x64 + + + true + false + true + full + true + false + false + false + + + true + Speed + Speed + true + true + true + true + + + true + false + true + false + + + false + false + false + false + false + true + + + true + \ No newline at end of file diff --git a/CollapseLauncher/Properties/PublishProfiles/Publish-StableReleaseAOT.pubxml b/CollapseLauncher/Properties/PublishProfiles/Publish-StableReleaseAOT.pubxml new file mode 100644 index 000000000..240fca77c --- /dev/null +++ b/CollapseLauncher/Properties/PublishProfiles/Publish-StableReleaseAOT.pubxml @@ -0,0 +1,55 @@ + + + + + + Publish + x64 + + + ..\..\CollapseLauncher-ReleaseRepo\stable-build + FileSystem + + + net9.0-windows10.0.22621.0 + win-x64 + + + true + false + true + full + true + false + true + true + + + true + Speed + Speed + true + true + true + true + + + true + false + true + false + + + false + false + false + false + false + true + + + true + + \ No newline at end of file diff --git a/CollapseLauncher/XAMLs/Invoker/Classes/Migrate.cs b/CollapseLauncher/XAMLs/Invoker/Classes/Migrate.cs index 242c8d877..2a2e68233 100644 --- a/CollapseLauncher/XAMLs/Invoker/Classes/Migrate.cs +++ b/CollapseLauncher/XAMLs/Invoker/Classes/Migrate.cs @@ -66,7 +66,7 @@ public void DoMigrationBHI3L(string version, string registryName, string sourceG Registry.CurrentUser.OpenSubKey(@"Software\Bp\Better HI3 Launcher", true) .SetValue(registryName, - Encoding.UTF8.GetBytes(info.Serialize(CoreLibraryJSONContext.Default)), + Encoding.UTF8.GetBytes(info.Serialize(InternalAppJSONContext.Default.BHI3LInfo)), RegistryValueKind.Binary); } catch (Exception ex) diff --git a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml index 3a305e1e5..321534145 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/MainPage.xaml @@ -7,7 +7,6 @@ xmlns:extension="using:CollapseLauncher.Extension" xmlns:helper="using:Hi3Helper" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:p="using:CollapseLauncher.Helper.Metadata" Unloaded="Page_Unloaded" mc:Ignorable="d"> @@ -20,8 +19,7 @@ Background="{ThemeResource WindowBackground}"> - @@ -45,7 +43,7 @@ CornerRadius="0,8,8,0" Opacity="0" Shadow="{ThemeResource SharedShadow}" - Translation="0,0,16" /> + Translation="-48,0,0" /> - + @@ -128,7 +125,6 @@ CornerRadius="15,0,0,15" DropDownClosed="GameComboBox_OnDropDownClosed" DropDownOpened="GameComboBox_OnDropDownOpened" - ItemsSource="{x:Bind p:LauncherMetadataHelper.LauncherGameNameCollection, Mode=OneWay}" PlaceholderText="{x:Bind helper:Locale.Lang._GameClientTitles['Honkai Impact 3rd']}" SelectionChanged="SetGameCategoryChange" Style="{ThemeResource AcrylicComboBoxStyle}" /> @@ -234,8 +230,8 @@ + Text="{x:Bind helper:Locale.Lang._MainPage.RegionChangeConfirm}" + TextWrapping="Wrap" /> - - + + - + HorizontalAlignment="Center" + VerticalAlignment="Center" + RenderTransformOrigin="0.5, 0.5"> + Width="18" + Height="18"> - + - + - @@ -344,7 +341,6 @@ Grid.Column="0" Height="33" Margin="58,8,0,0" - HorizontalAlignment="Left" VerticalAlignment="Top" Click="GridBG_Icon_Click" PointerEntered="GridBG_Icon_PointerEntered" @@ -395,8 +391,6 @@ Visibility="Visible" /> + - ? gameNameCollection = LauncherMetadataHelper.GetGameNameCollection()!; List? gameRegionCollection = LauncherMetadataHelper.GetGameRegionCollection(lastName!)!; int indexOfName = gameNameCollection.IndexOf(lastName!); int indexOfRegion = gameRegionCollection.IndexOf(lastRegion!); - #nullable restore +#nullable restore // Rebuild Game Titles and Regions ComboBox items ComboBoxGameCategory.ItemsSource = BuildGameTitleListUI(); ComboBoxGameCategory.SelectedIndex = indexOfName; ComboBoxGameRegion.SelectedIndex = indexOfRegion; - InitializeNavigationItems(false); ChangeTitleDragArea.Change(DragAreaTemplate.Default); + + UpdateLayout(); + Bindings.Update(); } private void ShowLoadingPageInvoker_PageEvent(object sender, ShowLoadingPageProperty e) @@ -487,13 +485,6 @@ private async void CustomBackgroundChanger_Event(object sender, BackgroundImgPro switch (mType) { case BackgroundMediaUtility.MediaType.Media: - MediaPlayerFrame = new MediaPlayerElement - { - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center, - Stretch = Stretch.UniformToFill, - Tag = "MediaPlayer" - }; BackgroundNewMediaPlayerGrid.Visibility = Visibility.Visible; BackgroundNewBackGrid.Visibility = Visibility.Collapsed; break; @@ -502,8 +493,8 @@ private async void CustomBackgroundChanger_Event(object sender, BackgroundImgPro BackgroundMediaUtility.SetAlternativeFileStream(imgStream); BackgroundNewMediaPlayerGrid.Visibility = Visibility.Collapsed; BackgroundNewBackGrid.Visibility = Visibility.Visible; - MediaPlayerFrame = null; break; + case BackgroundMediaUtility.MediaType.Unknown: default: throw new InvalidCastException(); } @@ -701,7 +692,7 @@ private async Task FetchNotificationFeed() RunTimeoutCancel(TokenSource); await using BridgedNetworkStream networkStream = await FallbackCDNUtil.TryGetCDNFallbackStream(string.Format(AppNotifURLPrefix, IsPreview ? "preview" : "stable"), TokenSource.Token); - NotificationData = await networkStream.DeserializeAsync(InternalAppJSONContext.Default, TokenSource.Token); + NotificationData = await networkStream.DeserializeAsync(InternalAppJSONContext.Default.NotificationPush, TokenSource.Token); IsLoadNotifComplete = true; NotificationData?.EliminatePushList(); @@ -1261,57 +1252,71 @@ private void InitializeNavigationItems(bool ResetSelection = true) if (CurrentGameVersionCheck.GamePreset.IsCacheUpdateEnabled ?? false) { NavigationViewControl.MenuItems.Add(new NavigationViewItem() - { Content = Lang._CachesPage.PageTitle, Icon = IconCaches, Tag = "caches" }); + { Icon = IconCaches, Tag = "caches" } + .BindNavigationViewItemText("_CachesPage", "PageTitle")); } return; } NavigationViewControl.MenuItems.Add(new NavigationViewItem() - { Content = Lang._HomePage.PageTitle, Icon = IconLauncher, Tag = "launcher" }); + { Icon = IconLauncher, Tag = "launcher" } + .BindNavigationViewItemText("_HomePage", "PageTitle")); - NavigationViewControl.MenuItems.Add(new NavigationViewItemHeader() { Content = Lang._MainPage.NavigationUtilities }); + NavigationViewControl.MenuItems.Add(new NavigationViewItemHeader() + .BindNavigationViewItemText("_MainPage", "NavigationUtilities")); if (CurrentGameVersionCheck.GamePreset.IsRepairEnabled ?? false) { NavigationViewControl.MenuItems.Add(new NavigationViewItem() - { Content = Lang._GameRepairPage.PageTitle, Icon = IconRepair, Tag = "repair" }); + { Icon = IconRepair, Tag = "repair" } + .BindNavigationViewItemText("_GameRepairPage", "PageTitle")); } if (CurrentGameVersionCheck.GamePreset.IsCacheUpdateEnabled ?? false) { NavigationViewControl.MenuItems.Add(new NavigationViewItem() - { Content = Lang._CachesPage.PageTitle, Icon = IconCaches, Tag = "caches" }); + { Icon = IconCaches, Tag = "caches" } + .BindNavigationViewItemText("_CachesPage", "PageTitle")); } switch (CurrentGameVersionCheck.GameType) { case GameNameType.Honkai: NavigationViewControl.FooterMenuItems.Add(new NavigationViewItem() - { Content = Lang._GameSettingsPage.PageTitle, Icon = IconGameSettings, Tag = "honkaigamesettings" }); + { Icon = IconGameSettings, Tag = "honkaigamesettings" } + .BindNavigationViewItemText("_GameSettingsPage", "PageTitle")); break; case GameNameType.StarRail: NavigationViewControl.FooterMenuItems.Add(new NavigationViewItem() - { Content = Lang._StarRailGameSettingsPage.PageTitle, Icon = IconGameSettings, Tag = "starrailgamesettings" }); + { Icon = IconGameSettings, Tag = "starrailgamesettings" } + .BindNavigationViewItemText("_StarRailGameSettingsPage", "PageTitle")); break; case GameNameType.Genshin: NavigationViewControl.FooterMenuItems.Add(new NavigationViewItem() - { Content = Lang._GenshinGameSettingsPage.PageTitle, Icon = IconGameSettings, Tag = "genshingamesettings" }); + { Icon = IconGameSettings, Tag = "genshingamesettings" } + .BindNavigationViewItemText("_GenshinGameSettingsPage", "PageTitle")); break; case GameNameType.Zenless: NavigationViewControl.FooterMenuItems.Add(new NavigationViewItem() - {Content = Lang._GameSettingsPage.PageTitle, Icon = IconGameSettings, Tag = "zenlessgamesettings"}); + { Icon = IconGameSettings, Tag = "zenlessgamesettings" } + .BindNavigationViewItemText("_GameSettingsPage", "PageTitle")); break; } if (NavigationViewControl.SettingsItem is not null && NavigationViewControl.SettingsItem is NavigationViewItem SettingsItem) { - SettingsItem.Content = Lang._SettingsPage.PageTitle; SettingsItem.Icon = IconAppSettings; - ToolTipService.SetToolTip(SettingsItem, Lang._SettingsPage.PageTitle); + _ = SettingsItem.BindNavigationViewItemText("_SettingsPage", "PageTitle"); } - foreach (var deps in NavigationViewControl.FindDescendants()) + foreach (var deps in NavigationViewControl.FindDescendants().OfType()) { + // Avoid any icons to have shadow attached if it's not from this page + if (deps.BaseUri.AbsolutePath != this.BaseUri.AbsolutePath) + { + continue; + } + if (deps is FontIcon icon) AttachShadowNavigationPanelItem(icon); if (deps is AnimatedIcon animIcon) @@ -1323,6 +1328,11 @@ private void InitializeNavigationItems(bool ResetSelection = true) { NavigationViewControl.SelectedItem = (NavigationViewItem)NavigationViewControl.MenuItems[0]; } + + NavigationViewControl.ApplyNavigationViewItemLocaleTextBindings(); + + InputSystemCursor handCursor = InputSystemCursor.Create(InputSystemCursorShape.Hand); + MainPageGrid.SetAllControlsCursorRecursive(handCursor); } public static void AttachShadowNavigationPanelItem(FrameworkElement element) @@ -1346,11 +1356,20 @@ private void NavView_Loaded(object sender, RoutedEventArgs e) } } - var paneRoot = (Grid)NavigationViewControl.FindDescendant("PaneRoot"); - if (paneRoot != null) + NavViewPaneBackground.OpacityTransition = new ScalarTransition() + { + Duration = TimeSpan.FromMilliseconds(150) + }; + NavViewPaneBackground.TranslationTransition = new Vector3Transition() { - paneRoot.PointerEntered += NavView_PanePointerEntered; - paneRoot.PointerExited += NavView_PanePointerExited; + Duration = TimeSpan.FromMilliseconds(150) + }; + + var paneMainGrid = NavigationViewControl.FindDescendant("PaneContentGrid"); + if (paneMainGrid is Grid paneMainGridAsGrid) + { + paneMainGridAsGrid.PointerEntered += NavView_PanePointerEntered; + paneMainGridAsGrid.PointerExited += NavView_PanePointerExited; } // The toggle button is not a part of pane. Why Microsoft!!! @@ -1358,7 +1377,7 @@ private void NavView_Loaded(object sender, RoutedEventArgs e) if (paneToggleButtonGrid != null) { paneToggleButtonGrid.PointerEntered += NavView_PanePointerEntered; - paneToggleButtonGrid.PointerExited += NavView_PanePointerEntered; + paneToggleButtonGrid.PointerExited += NavView_PanePointerExited; } // var backIcon = NavigationViewControl.FindDescendant("NavigationViewBackButton")?.FindDescendant(); @@ -1368,8 +1387,12 @@ private void NavView_Loaded(object sender, RoutedEventArgs e) toggleIcon?.ApplyDropShadow(Colors.Gray, 20); } - private async void NavView_PanePointerEntered(object sender, PointerRoutedEventArgs e) + private void NavView_PanePointerEntered(object sender, PointerRoutedEventArgs e) { + IsCursorInNavBarHoverArea = true; + NavViewPaneBackground.Opacity = 1; + NavViewPaneBackground.Translation = new System.Numerics.Vector3(0, 0, 32); + /* if (!NavigationViewControl.IsPaneOpen) { var duration = TimeSpan.FromSeconds(0.25); @@ -1378,15 +1401,34 @@ private async void NavView_PanePointerEntered(object sender, PointerRoutedEventA .CreateScalarKeyFrameAnimation("Opacity", 1, current); await NavViewPaneBackground.StartAnimation(duration, animation); } + */ } - private async void NavView_PanePointerExited(object sender, PointerRoutedEventArgs e) + private bool IsCursorInNavBarHoverArea; + + private void NavView_PanePointerExited(object sender, PointerRoutedEventArgs e) { + PointerPoint pointerPoint = e.GetCurrentPoint(NavViewPaneBackgroundHoverArea); + IsCursorInNavBarHoverArea = pointerPoint.Position.X <= NavViewPaneBackgroundHoverArea.Width - 8 && pointerPoint.Position.X > 4; + + if (!IsCursorInNavBarHoverArea && !NavigationViewControl.IsPaneOpen) + { + NavViewPaneBackground.Opacity = 0; + NavViewPaneBackground.Translation = new System.Numerics.Vector3(-48, 0, 0); + } + + if (IsCursorInNavBarHoverArea && !NavigationViewControl.IsPaneOpen) + { + NavViewPaneBackground.Opacity = 1; + NavViewPaneBackground.Translation = new System.Numerics.Vector3(0, 0, 32); + } + /* var duration = TimeSpan.FromSeconds(0.25); var current = (float)NavViewPaneBackground.Opacity; var animation = NavViewPaneBackground.GetElementCompositor()! .CreateScalarKeyFrameAnimation("Opacity", 0, current); await NavViewPaneBackground.StartAnimation(duration, animation); + */ } private void NavView_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) @@ -1395,8 +1437,8 @@ private void NavView_ItemInvoked(NavigationView sender, NavigationViewItemInvoke if (args.IsSettingsInvoked && PreviousTag != "settings") Navigate(typeof(SettingsPage), "settings"); #nullable enable - NavigationViewItem? item = sender.MenuItems.OfType().FirstOrDefault(x => (string)x.Content == (string)args.InvokedItem); - item ??= sender.FooterMenuItems.OfType().FirstOrDefault(x => (string)x.Content == (string)args.InvokedItem); + NavigationViewItem? item = sender.MenuItems.OfType().FirstOrDefault(x => (x.Content as TextBlock)?.Text == (args.InvokedItem as TextBlock)?.Text); + item ??= sender.FooterMenuItems.OfType().FirstOrDefault(x => (x.Content as TextBlock)?.Text == (args.InvokedItem as TextBlock)?.Text); if (item == null) return; #nullable restore @@ -1554,15 +1596,26 @@ private void NavigationPanelOpening_Event(NavigationView sender, object args) GridBG_Icon.Margin = curMargin; IsTitleIconForceShow = true; ToggleTitleIcon(false); + + NavViewPaneBackgroundHoverArea.Width = NavigationViewControl.OpenPaneLength; } - private void NavigationPanelClosing_Event(NavigationView sender, NavigationViewPaneClosingEventArgs args) + private async void NavigationPanelClosing_Event(NavigationView sender, NavigationViewPaneClosingEventArgs args) { Thickness curMargin = GridBG_Icon.Margin; curMargin.Left = 58; GridBG_Icon.Margin = curMargin; IsTitleIconForceShow = false; ToggleTitleIcon(true); + + NavViewPaneBackgroundHoverArea.Width = NavViewPaneBackground.Width; + + await Task.Delay(200); + if (!IsCursorInNavBarHoverArea) + { + NavViewPaneBackground.Opacity = 0; + NavViewPaneBackground.Translation = new System.Numerics.Vector3(-48, 0, 0); + } } #endregion diff --git a/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml b/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml index f5e05593d..e3f39c4e0 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml +++ b/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml @@ -163,54 +163,75 @@ Grid.Row="0"> - + + + + + + + + + + + diff --git a/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml.cs b/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml.cs index 81a41b0a9..42a956721 100644 --- a/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml.cs +++ b/CollapseLauncher/XAMLs/MainApp/MainWindow.xaml.cs @@ -1,4 +1,5 @@ using CollapseLauncher.AnimatedVisuals.Lottie; +using CollapseLauncher.Extension; using CollapseLauncher.Helper; using CollapseLauncher.Helper.Animation; using CollapseLauncher.Helper.Image; @@ -9,6 +10,7 @@ using Hi3Helper; using Hi3Helper.Shared.Region; using Microsoft.UI.Composition; +using Microsoft.UI.Input; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -20,13 +22,13 @@ using static Hi3Helper.InvokeProp; using static Hi3Helper.Logger; using static Hi3Helper.Shared.Region.LauncherConfig; +// ReSharper disable RedundantExtendsListEntry namespace CollapseLauncher { public sealed partial class MainWindow : Window { private static bool _isForceDisableIntro; - public MainWindow() { } public void InitializeWindowProperties(bool startOOBE = false) { @@ -56,7 +58,8 @@ public void InitializeWindowProperties(bool startOOBE = false) catch (Exception ex) { LogWriteLine($"Failure while initializing window properties!!!\r\n{ex}", LogType.Error, true); - Console.ReadLine(); + //Console.ReadLine(); + throw; } } @@ -89,7 +92,6 @@ private async void RunIntroSequence() IntroAnimation.Stop(); } IntroAnimation.Source = null; - newIntro = null; GC.Collect(); GC.WaitForPendingFinalizers(); @@ -162,8 +164,13 @@ public void InitializeWindowSettings() WindowUtility.CurrentWindowIsResizable = false; WindowUtility.CurrentWindowIsMaximizable = false; - WindowUtility.CurrentAppWindow.TitleBar.ButtonBackgroundColor = new Color { A = 0, B = 0, G = 0, R = 0 }; - WindowUtility.CurrentAppWindow.TitleBar.ButtonInactiveBackgroundColor = new Color { A = 0, B = 0, G = 0, R = 0 }; + if (WindowUtility.CurrentAppWindow != null) + { + WindowUtility.CurrentAppWindow.TitleBar.ButtonBackgroundColor = + new Color { A = 0, B = 0, G = 0, R = 0 }; + WindowUtility.CurrentAppWindow.TitleBar.ButtonInactiveBackgroundColor = + new Color { A = 0, B = 0, G = 0, R = 0 }; + } // Hide system menu var controlsHwnd = FindWindowEx(WindowUtility.CurrentWindowPtr, 0, "ReunionWindowingCaptionControls", "ReunionCaptionControlsWindow"); @@ -270,5 +277,8 @@ private void IntroSequenceToggle_PointerExited(object sender, Microsoft.UI.Xaml. curCompositor.CreateScalarKeyFrameAnimation("Opacity", 0.25f) ); } + + private void SetWindowCaptionLoadedCursor(object sender, RoutedEventArgs e) + => (sender as UIElement)?.SetCursor(InputSystemCursor.Create(InputSystemCursorShape.Hand)); } } diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/CachesPage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/CachesPage.xaml index 92fb5d17e..42c261386 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/CachesPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/CachesPage.xaml @@ -123,7 +123,6 @@ - @@ -182,6 +180,7 @@ + ToolTipService.ToolTip="{x:Bind helper:Locale.Lang._CachesPage.CachesBtn2QuickDesc}"> - - + + - + @@ -258,8 +259,7 @@ Text="{x:Bind helper:Locale.Lang._Misc.Cancel}" /> - FetchDataIntegrityURL(PresetConfig Profile) string repoListURL = string.Format(AppGameRepoIndexURLPrefix, Profile.ProfileName); await FallbackCDNUtil.DownloadCDNFallbackContent(downloadClient, s, repoListURL, tokenSource.Token); s.Position = 0; - _RepoList = await s.DeserializeAsync>(CoreLibraryJSONContext.Default, tokenSource.Token); + _RepoList = await s.DeserializeAsync(CoreLibraryJSONContext.Default.DictionaryStringString, tokenSource.Token); } } finally @@ -181,7 +181,7 @@ private async Task FetchDataIntegrityURL(PresetConfig Profile) RegionResourceProp _Entry; - _Entry = await FallbackCDNUtil.DownloadAsJSONType(Profile.LauncherResourceURL, InternalAppJSONContext.Default, tokenSource.Token); + _Entry = await FallbackCDNUtil.DownloadAsJSONType(Profile.LauncherResourceURL, InternalAppJSONContext.Default.RegionResourceProp, tokenSource.Token); GameVersion = _Entry.data?.game?.latest?.version; return _RepoList[GameVersion ?? throw new InvalidOperationException()]; diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/SimpleDialogs.cs b/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/SimpleDialogs.cs index 481bada19..19986181c 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/SimpleDialogs.cs +++ b/CollapseLauncher/XAMLs/MainApp/Pages/Dialogs/SimpleDialogs.cs @@ -9,6 +9,7 @@ using CollapseLauncher.Statics; using CommunityToolkit.WinUI; using Hi3Helper; +using Microsoft.UI.Input; using Microsoft.UI.Text; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -149,6 +150,7 @@ await SpawnDialog( XamlRoot = (WindowUtility.CurrentWindow is MainWindow mainWindow) ? mainWindow.Content.XamlRoot : Content.XamlRoot }; + InputCursor inputCursor = InputSystemCursor.Create(InputSystemCursorShape.Hand); for (int i = 0; i < langlist.Count; i++) { Grid checkBoxGrid = CollapseUIExt.CreateGrid() @@ -168,7 +170,7 @@ await SpawnDialog( HorizontalAlignment = HorizontalAlignment.Right, HorizontalTextAlignment = TextAlignment.Right, VerticalAlignment = VerticalAlignment.Top, - Opacity = 0.25, + Opacity = 0.5, Name = "UseAsDefaultLabel" }; useAsDefaultText.EnableSingleImplicitAnimation(VisualPropertyType.Opacity); @@ -178,7 +180,7 @@ await SpawnDialog( iconSize: 14, textSize: 14, iconFontFamily: "FontAwesomeSolid") - .WithOpacity(0.25); + .WithOpacity(0.5); iconTextGrid.Name = "IconText"; iconTextGrid.EnableSingleImplicitAnimation(VisualPropertyType.Opacity); iconTextGrid.VerticalAlignment = VerticalAlignment.Center; @@ -193,7 +195,8 @@ await SpawnDialog( Background = CollapseUIExt.GetApplicationResource("AudioLanguageSelectionRadioButtonBrush") } .WithHorizontalAlignment(HorizontalAlignment.Stretch) - .WithVerticalAlignment(VerticalAlignment.Center); + .WithVerticalAlignment(VerticalAlignment.Center) + .WithCursor(inputCursor); defaultChoiceRadioButton.Items.Add(radioButton); @@ -216,7 +219,35 @@ await SpawnDialog( RadioButton radioButtonLocal = sender as RadioButton; TextBlock textBlockLocal = (TextBlock)radioButtonLocal.FindDescendant("UseAsDefaultLabel"); if (textBlockLocal != null) - textBlockLocal.Opacity = 0.25; + textBlockLocal.Opacity = 0.5; + }; + + radioButton.PointerEntered += (sender, _) => + { + RadioButton radioButtonLocal = sender as RadioButton; + TextBlock textBlockLocal = (TextBlock)radioButtonLocal.FindDescendant("UseAsDefaultLabel"); + + CheckBox thisCheckBox = radioButtonLocal.Content as CheckBox; + Grid thisIconText = (Grid)thisCheckBox.FindDescendant("IconText"); + if (textBlockLocal != null && thisIconText != null && !(thisCheckBox.IsChecked ?? false)) + { + textBlockLocal.Opacity = 1; + thisIconText.Opacity = 1; + } + }; + + radioButton.PointerExited += (sender, _) => + { + RadioButton radioButtonLocal = sender as RadioButton; + TextBlock textBlockLocal = (TextBlock)radioButtonLocal.FindDescendant("UseAsDefaultLabel"); + + CheckBox thisCheckBox = radioButtonLocal.Content as CheckBox; + Grid thisIconText = (Grid)thisCheckBox.FindDescendant("IconText"); + if (textBlockLocal != null && thisIconText != null && !(thisCheckBox.IsChecked ?? false)) + { + textBlockLocal.Opacity = 0.5; + thisIconText.Opacity = 0.5; + } }; if (i == defaultIndex) @@ -252,7 +283,7 @@ await SpawnDialog( Grid thisIconText = (Grid)thisCheckBox.FindDescendant("IconText"); if (thisIconText != null) - thisIconText.Opacity = 0.25; + thisIconText.Opacity = 0.5; bool isHasAnyChoices = choices.Any(x => x); dialog.IsPrimaryButtonEnabled = isHasAnyChoices; @@ -320,6 +351,7 @@ await SpawnDialog( XamlRoot = (WindowUtility.CurrentWindow is MainWindow mainWindow) ? mainWindow.Content.XamlRoot : Content.XamlRoot }; + InputCursor inputCursor = InputSystemCursor.Create(InputSystemCursorShape.Hand); for (int i = 0; i < langlist.Count; i++) { Grid checkBoxGrid = CollapseUIExt.CreateGrid() @@ -339,7 +371,7 @@ await SpawnDialog( HorizontalAlignment = HorizontalAlignment.Right, HorizontalTextAlignment = TextAlignment.Right, VerticalAlignment = VerticalAlignment.Top, - Opacity = 0.25, + Opacity = 0.5, Name = "UseAsDefaultLabel" }; useAsDefaultText.EnableSingleImplicitAnimation(VisualPropertyType.Opacity); @@ -349,7 +381,7 @@ await SpawnDialog( iconSize: 14, textSize: 14, iconFontFamily: "FontAwesomeSolid") - .WithOpacity(0.25); + .WithOpacity(0.5); iconTextGrid.Name = "IconText"; iconTextGrid.EnableSingleImplicitAnimation(VisualPropertyType.Opacity); iconTextGrid.VerticalAlignment = VerticalAlignment.Center; @@ -364,7 +396,8 @@ await SpawnDialog( Background = CollapseUIExt.GetApplicationResource("AudioLanguageSelectionRadioButtonBrush") } .WithHorizontalAlignment(HorizontalAlignment.Stretch) - .WithVerticalAlignment(VerticalAlignment.Center); + .WithVerticalAlignment(VerticalAlignment.Center) + .WithCursor(inputCursor); defaultChoiceRadioButton.Items.Add(radioButton); @@ -387,7 +420,35 @@ await SpawnDialog( RadioButton radioButtonLocal = sender as RadioButton; TextBlock textBlockLocal = (TextBlock)radioButtonLocal.FindDescendant("UseAsDefaultLabel"); if (textBlockLocal != null) - textBlockLocal.Opacity = 0.25; + textBlockLocal.Opacity = 0.5; + }; + + radioButton.PointerEntered += (sender, _) => + { + RadioButton radioButtonLocal = sender as RadioButton; + TextBlock textBlockLocal = (TextBlock)radioButtonLocal.FindDescendant("UseAsDefaultLabel"); + + CheckBox thisCheckBox = radioButtonLocal.Content as CheckBox; + Grid thisIconText = (Grid)thisCheckBox.FindDescendant("IconText"); + if (textBlockLocal != null && thisIconText != null && !(thisCheckBox.IsChecked ?? false)) + { + textBlockLocal.Opacity = 1; + thisIconText.Opacity = 1; + } + }; + + radioButton.PointerExited += (sender, _) => + { + RadioButton radioButtonLocal = sender as RadioButton; + TextBlock textBlockLocal = (TextBlock)radioButtonLocal.FindDescendant("UseAsDefaultLabel"); + + CheckBox thisCheckBox = radioButtonLocal.Content as CheckBox; + Grid thisIconText = (Grid)thisCheckBox.FindDescendant("IconText"); + if (textBlockLocal != null && thisIconText != null && !(thisCheckBox.IsChecked ?? false)) + { + textBlockLocal.Opacity = 0.5; + thisIconText.Opacity = 0.5; + } }; if (i == defaultIndex) @@ -423,7 +484,7 @@ await SpawnDialog( Grid thisIconText = (Grid)thisCheckBox.FindDescendant("IconText"); if (thisIconText != null) - thisIconText.Opacity = 0.25; + thisIconText.Opacity = 0.5; bool isHasAnyChoices = choices.Any(x => x); dialog.IsPrimaryButtonEnabled = isHasAnyChoices; diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml index 537731e9f..4a2fe1c32 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/FileCleanupPage.xaml @@ -10,6 +10,9 @@ xmlns:page="using:CollapseLauncher.Pages" xmlns:type="using:CollapseLauncher.InstallManager.Base" mc:Ignorable="d"> + + + @@ -68,9 +71,6 @@ Padding="16" Background="{ThemeResource AcrylicBackgroundFillColorDefaultBrush}" CornerRadius="8"> - - - @@ -129,11 +129,11 @@ diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/GenshinGameSettingsPage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/GenshinGameSettingsPage.xaml index 1018f0351..c00b2a2bd 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/GenshinGameSettingsPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/GenshinGameSettingsPage.xaml @@ -91,7 +91,6 @@ Height="24" Margin="16,0,8,0" Padding="0" - HorizontalAlignment="Left" CornerRadius="4" Style="{ThemeResource AcrylicButtonStyle}"> @@ -268,15 +267,15 @@ - + - + @@ -314,7 +313,6 @@ MaxWidth="514" Margin="0,8" HorizontalAlignment="Center" - IsThumbToolTipEnabled="False" Maximum="2000.0" Minimum="300.0" StepFrequency="50" @@ -344,7 +342,6 @@ MinWidth="350" Margin="0,8" HorizontalAlignment="Center" - IsThumbToolTipEnabled="False" Maximum="500.0" Minimum="100.0" StepFrequency="5" @@ -371,7 +368,6 @@ MinWidth="350" Margin="0,8,0,0" HorizontalAlignment="Center" - IsThumbToolTipEnabled="False" Maximum="550.0" Minimum="150.0" StepFrequency="5" @@ -408,7 +404,6 @@ prevGraphSelect = (int)Settings.SettingsGraphics.ResolutionQuality; - set => TryChallengeRenderingAccuracySet(value, value < 3); + set => TryChallengeRenderingAccuracySet(value, value < 9); } private async void TryChallengeRenderingAccuracySet(int value, bool BypassChallenge = false) diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/HonkaiGameSettingsPage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/HonkaiGameSettingsPage.xaml index 19f897afe..f229594b9 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/HonkaiGameSettingsPage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/GameSettingsPages/HonkaiGameSettingsPage.xaml @@ -292,10 +292,16 @@ HorizontalAlignment="Stretch" CornerRadius="14" SelectedIndex="{x:Bind GraphicsRenderingAccuracy, Mode=TwoWay}"> - - - - + + + + + + + + + + (); + if (childGrid == null) return; + + // Bind the QR properties + ImageEx.ImageEx? qrImageInstance = childGrid.FindChild(); + qrImageInstance?.BindProperty(ImageExBase.SourceProperty, dataBind, "QrImg"); + + TextBlock? textBlockInstance = childGrid.FindChild(); + textBlockInstance?.BindProperty(TextBlock.TextProperty, dataBind, "QrTitle"); + textBlockInstance?.BindProperty(VisibilityProperty, dataBind, "IsHasQrDescription", HomePageExtension.BooleanVisibilityConverter); + } + + private static void BindSocialMediaLinks(Panel parentPanel, LauncherGameNewsSocialMedia dataBind) + { + // Bind visibility if dataBind has Links + parentPanel.BindProperty(VisibilityProperty, dataBind, "IsHasLinks", HomePageExtension.BooleanVisibilityConverter); + + // Find the ItemsControl + ItemsControl? itemsControl = parentPanel.FindChild(); + if (itemsControl == null) return; + + // If dataBind has links, then assign the ItemsSource + if (dataBind.IsHasLinks) + { + itemsControl.ItemsSource = dataBind.QrLinks; + } + } + + private static void BindSocialMediaDescription(TextBlock parentPanel, LauncherGameNewsSocialMedia dataBind) + { + // Bind visibility if dataBind has Description + parentPanel.BindProperty(VisibilityProperty, dataBind, "IsHasDescription", HomePageExtension.BooleanVisibilityConverter); + + // Bind description text + parentPanel.BindProperty(TextBlock.TextProperty, dataBind, "Title"); + } + } +} diff --git a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml index 09f68b11e..2176eef65 100644 --- a/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml +++ b/CollapseLauncher/XAMLs/MainApp/Pages/HomePage.xaml @@ -5,19 +5,18 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:animatedvisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals" - xmlns:conv="using:CollapseLauncher.Pages" xmlns:customcontrol="using:CollapseLauncher.CustomControls" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:extension="using:CollapseLauncher.Extension" xmlns:helper="using:Hi3Helper" xmlns:imageex="using:ImageEx" xmlns:local="using:CollapseLauncher" - xmlns:localPage="using:CollapseLauncher.Pages" xmlns:localStatic="using:CollapseLauncher.Statics" xmlns:localWindowSize="using:CollapseLauncher.WindowSize" xmlns:lottie="using:CollapseLauncher.AnimatedVisuals.Lottie" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:s="using:CommunityToolkit.WinUI" + xmlns:sophonTypes="using:CollapseLauncher.Helper.LauncherApiLoader.Sophon" x:Name="HomePage_Page" x:FieldModifier="public" Unloaded="Page_Unloaded" @@ -29,7 +28,6 @@ CastTo="{x:Bind ImageEventImgShadow}" Opacity="0.3" Offset="0,4,0" /> - - - - + - - - @@ -442,7 +440,7 @@ - + @@ -588,7 +592,6 @@ - + + + + + - + + Tag="{x:Bind CarouselUrl}" + ToolTipService.ToolTip="{x:Bind CarouselTitle}" /> - - - - + + + + + + + + + + \ No newline at end of file diff --git a/CollapseLauncher/XAMLs/Updater/Classes/Updater.cs b/CollapseLauncher/XAMLs/Updater/Classes/Updater.cs index 91cbbd0d7..d67d1a521 100644 --- a/CollapseLauncher/XAMLs/Updater/Classes/Updater.cs +++ b/CollapseLauncher/XAMLs/Updater/Classes/Updater.cs @@ -3,12 +3,21 @@ using Hi3Helper; using Hi3Helper.Http; using Hi3Helper.Http.Legacy; +#if !USEVELOPACK using Squirrel; using Squirrel.Sources; +#else +using Microsoft.Extensions.Logging; +using Velopack; +using Velopack.Locators; +using Velopack.Sources; +#endif using System; using System.Diagnostics; using System.IO; +#if !USEVELOPACK using System.Linq; +#endif using System.Net; using System.Net.Http; using System.Threading; @@ -16,6 +25,8 @@ using static Hi3Helper.Data.ConverterTool; using static Hi3Helper.Locale; using static Hi3Helper.Shared.Region.LauncherConfig; +using Hi3Helper.Shared.Region; +using Hi3Helper.Data; namespace CollapseLauncher; @@ -27,6 +38,10 @@ public class Updater : IDisposable private Stopwatch UpdateStopwatch; private UpdateManager UpdateManager; private IFileDownloader UpdateDownloader; +#if USEVELOPACK + private ILogger UpdateManagerLogger; + private VelopackAsset VelopackVersionToUpdate; +#endif private GameVersion NewVersionTag; // ReSharper disable once RedundantDefaultMemberInitializer @@ -46,9 +61,34 @@ public class Updater : IDisposable public Updater(string ChannelName) { this.ChannelName = ChannelName; - ChannelURL = CombineURLFromString(FallbackCDNUtil.GetPreferredCDN().URLPrefix, "squirrel", this.ChannelName); + ChannelURL = CombineURLFromString(FallbackCDNUtil.GetPreferredCDN().URLPrefix, +#if USEVELOPACK + "velopack", +#else + "squirrel", +#endif + this.ChannelName + ); UpdateDownloader = new UpdateManagerHttpAdapter(); +#if USEVELOPACK + UpdateManagerLogger = ILoggerHelper.CreateCollapseILogger(); + VelopackLocator updateManagerLocator = VelopackLocator.GetDefault(UpdateManagerLogger); + UpdateOptions updateManagerOptions = new UpdateOptions + { + AllowVersionDowngrade = true, + ExplicitChannel = this.ChannelName + }; + + // Initialize update manager source + IUpdateSource updateSource = new SimpleWebSource(ChannelURL, UpdateDownloader); + UpdateManager = new UpdateManager( + updateSource, + updateManagerOptions, + UpdateManagerLogger, + updateManagerLocator); +#else UpdateManager = new UpdateManager(ChannelURL, null, null, UpdateDownloader); +#endif UpdateStopwatch = Stopwatch.StartNew(); Status = new UpdaterStatus(); Progress = new UpdaterProgress(UpdateStopwatch, 0, 100); @@ -61,19 +101,33 @@ public Updater(string ChannelName) public void Dispose() { +#if !USEVELOPACK UpdateManager?.Dispose(); +#endif } public async Task StartCheck() { +#if !USEVELOPACK return await UpdateManager.CheckForUpdate(); +#else + return await UpdateManager.CheckForUpdatesAsync(); +#endif } public bool IsUpdateAvailable(UpdateInfo info) { +#if !USEVELOPACK if (!info.ReleasesToApply.Any()) +#else + if (info.DeltasToTarget.Length != 0) +#endif { +#if !USEVELOPACK NewVersionTag = new GameVersion(info.FutureReleaseEntry.Version.Version); +#else + NewVersionTag = new GameVersion(info.TargetFullRelease.Version.ToString()); +#endif return DoesLatestVersionExist(NewVersionTag.VersionString); } @@ -91,9 +145,17 @@ public async Task StartUpdate(UpdateInfo UpdateInfo, CancellationToken tok try { +#if !USEVELOPACK if (!UpdateInfo.ReleasesToApply.Any()) +#else + if (UpdateInfo.DeltasToTarget.Length != 0) +#endif { +#if !USEVELOPACK NewVersionTag = new GameVersion(UpdateInfo.FutureReleaseEntry.Version.Version); +#else + NewVersionTag = new GameVersion(UpdateInfo.TargetFullRelease.Version.ToString()); +#endif if (DoesLatestVersionExist(NewVersionTag.VersionString)) { Progress = new UpdaterProgress(UpdateStopwatch, 100, 100); @@ -109,23 +171,38 @@ public async Task StartUpdate(UpdateInfo UpdateInfo, CancellationToken tok return false; } +#if !USEVELOPACK NewVersionTag = new GameVersion(UpdateInfo.ReleasesToApply.FirstOrDefault()!.Version.Version); +#else + NewVersionTag = new GameVersion(UpdateInfo.TargetFullRelease.Version.ToString()); +#endif + +#if !USEVELOPACK + await UpdateManager.DownloadReleases(UpdateInfo.ReleasesToApply, InvokeDownloadUpdateProgress); + + await UpdateManager.ApplyReleases(UpdateInfo, InvokeApplyUpdateProgress); +#else + await UpdateManager.DownloadUpdatesAsync(UpdateInfo, InvokeDownloadUpdateProgress, false, token); + VelopackVersionToUpdate = UpdateInfo.TargetFullRelease; +#endif + + void InvokeDownloadUpdateProgress(int progress) + { + Progress = new UpdaterProgress(UpdateStopwatch, progress +#if !USEVELOPACK + / 2 +#endif + , 100); + UpdateProgress(); + } - await UpdateManager.DownloadReleases(UpdateInfo.ReleasesToApply, (progress) => - { - Progress = - new - UpdaterProgress(UpdateStopwatch, - progress / 2, 100); - UpdateProgress(); - }); - - await UpdateManager.ApplyReleases(UpdateInfo, (progress) => - { - Progress = new UpdaterProgress(UpdateStopwatch, - progress / 2 + 50, 100); - UpdateProgress(); - }); +#if !USEVELOPACK + void InvokeApplyUpdateProgress(int progress) + { + Progress = new UpdaterProgress(UpdateStopwatch, progress / 2 + 50, 100); + UpdateProgress(); + } +#endif } catch (Exception ex) { @@ -150,9 +227,11 @@ private async Task StartLegacyUpdate() UpdateStopwatch = Stopwatch.StartNew(); - AppUpdateVersionProp updateInfo = await FallbackCDNUtil - .DownloadAsJSONType($"{ChannelName.ToLower()}/fileindex.json", - InternalAppJSONContext.Default, default); + CDNURLProperty preferredCdn = FallbackCDNUtil.GetPreferredCDN(); + string updateFileIndexUrl = ConverterTool.CombineURLFromString(preferredCdn.URLPrefix, ChannelName.ToLower(), "fileindex.json"); + + AppUpdateVersionProp updateInfo = await FallbackCDNUtil.DownloadAsJSONType(updateFileIndexUrl, + InternalAppJSONContext.Default.AppUpdateVersionProp, default)!; GameVersion? gameVersion = updateInfo!.Version; @@ -179,12 +258,40 @@ private void FallbackCDNUtil_DownloadProgress(object sender, DownloadEvent e) private bool DoesLatestVersionExist(string versionString) { + // Check legacy version first var filePath = Path.Combine(AppFolder, $"..\\app-{versionString}\\{Path.GetFileName(AppExecutablePath)}"); + if (File.Exists(filePath)) return true; + + // If none does not exist, then check the latest version + filePath = Path.Combine(AppFolder, $"..\\current\\{Path.GetFileName(AppExecutablePath)}"); + if (!Version.TryParse(versionString, out Version currentVersion)) + { + Logger.LogWriteLine($"[Updater::DoesLatestVersionExist] versionString is not valid! {versionString}", LogType.Error, true); + return false; + } + + // If the Velopack current folder doesn't exist, then return false + if (!File.Exists(filePath)) + { + return false; + } + + // Try get the version info and if the version info is null, return false + FileVersionInfo toCheckVersionInfo = FileVersionInfo.GetVersionInfo(filePath); - return File.Exists(filePath); + // Otherwise, try compare the version info. + if (!Version.TryParse(toCheckVersionInfo.ProductVersion, out Version latestVersion)) + { + Logger.LogWriteLine($"[Updater::DoesLatestVersionExist] toCheckVersionInfo.ProductVersion is not valid! {versionString}", LogType.Error, true); + return false; + } + + // Try compare. If latestVersion is more or equal to current version, return true. + // Otherwise, return false. + return latestVersion >= currentVersion; } - public async Task FinishUpdate(bool NoSuicide = false) + public async Task FinishUpdate(bool noSuicide = false) { var newVerTagPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "AppData", "LocalLow", "CollapseLauncher", "_NewVer"); @@ -198,8 +305,8 @@ public async Task FinishUpdate(bool NoSuicide = false) Progress = new UpdaterProgress(UpdateStopwatch, 100, 100); UpdateProgress(); - File.WriteAllText(newVerTagPath, NewVersionTag.VersionString); - File.WriteAllText(needInnoLogUpdatePath, NewVersionTag.VersionString); + await File.WriteAllTextAsync(newVerTagPath, NewVersionTag.VersionString); + await File.WriteAllTextAsync(needInnoLogUpdatePath, NewVersionTag.VersionString); if (IsUseLegacyDownload) { @@ -207,17 +314,28 @@ public async Task FinishUpdate(bool NoSuicide = false) return; } - if (!NoSuicide) + if (!noSuicide) await Suicide(); } private async Task Suicide() { await Task.Delay(3000); +#if !USEVELOPACK UpdateManager.RestartApp(); +#else + if (VelopackVersionToUpdate != null) + { + string currentAppPath = Path.Combine(Path.GetDirectoryName(AppFolder) ?? string.Empty, "current"); + if (!Directory.Exists(currentAppPath)) + Directory.CreateDirectory(currentAppPath); + + UpdateManager.ApplyUpdatesAndRestart(VelopackVersionToUpdate); + } +#endif } - private void SuicideLegacy() + private static void SuicideLegacy() { var applyUpdate = new Process() { diff --git a/CollapseLauncher/XAMLs/Updater/UpdaterWindow.xaml.cs b/CollapseLauncher/XAMLs/Updater/UpdaterWindow.xaml.cs index c587c9d02..bdfe669b9 100644 --- a/CollapseLauncher/XAMLs/Updater/UpdaterWindow.xaml.cs +++ b/CollapseLauncher/XAMLs/Updater/UpdaterWindow.xaml.cs @@ -68,7 +68,7 @@ private async void StartAsyncRoutine() .TryGetCDNFallbackStream($"{m_arguments.Updater.UpdateChannel.ToString().ToLower()}/fileindex.json", default); var updateInfo = - await metadataStream.DeserializeAsync(InternalAppJSONContext.Default, default); + await metadataStream.DeserializeAsync(InternalAppJSONContext.Default.AppUpdateVersionProp, default); NewVersionLabel.Text = updateInfo!.VersionString; // Initialize new proxy-aware HttpClient diff --git a/CollapseLauncher/packages.lock.json b/CollapseLauncher/packages.lock.json index 4eee338ee..eeddf8cc7 100644 --- a/CollapseLauncher/packages.lock.json +++ b/CollapseLauncher/packages.lock.json @@ -1,88 +1,59 @@ { "version": 1, "dependencies": { - "net8.0-windows10.0.22621": { - "Clowd.Squirrel": { - "type": "Direct", - "requested": "[2.11.1, )", - "resolved": "2.11.1", - "contentHash": "tregtapTyVXjwhgyc4llUNh8HX3008yrglakUWQocgtGf2bOBHcibgixakkb2yB1FrM/gPDpxykg4sWtFzjpVg==" - }, + "net9.0-windows10.0.22621": { "CommunityToolkit.Common": { "type": "Direct", - "requested": "[8.3.1, )", - "resolved": "8.3.1", - "contentHash": "r8jWhfnwSfn7/xPjh3Aom6gAGkENUMrr+FmZJOzOvRWBgXJ/latl8b8iCx1F+GHiS/0f5zK4zWZM18P0DgNNiw==" + "requested": "[8.3.2, )", + "resolved": "8.3.2", + "contentHash": "DFk1MUDzCUZHWFGl33HQkD1XtHHKmgfnjJ6sD2JICDc6et4DaCxf6GBPCzAZ7jcgyy/BdUDk0DUztu1K+61Q6w==" }, "CommunityToolkit.Mvvm": { "type": "Direct", - "requested": "[8.3.1, )", - "resolved": "8.3.1", - "contentHash": "Fuqjp5uYzHEdrRxsZlY0EqvFX9pF2v0cZN+zfso44lVLI3wZa8Nb9jdcBoXbHxiRSU3hEDhHUqh2fM8znzodzg==" + "requested": "[8.3.2, )", + "resolved": "8.3.2", + "contentHash": "m8EolE1A0Updj68WTsZSGI6VWb6mUqHPh7QFo0kt7+JPhYMNXRS1ch8TS/oITAdcxTLrwMOp3ku1KjeG1/Zdpg==" }, "CommunityToolkit.WinUI.Behaviors": { "type": "Direct", - "requested": "[8.1.240821, )", - "resolved": "8.1.240821", - "contentHash": "nc6MZdYp7ee1f8Qu8JO1LtbOuzAMtm79QN9073YMLTLZliCWVijfW+OFIVRHsFcCxpv6Ep4ii63x/OjXZIuWAQ==", + "requested": "[8.1.240916, )", + "resolved": "8.1.240916", + "contentHash": "HJ/fJmxXj/KmrPCtDUmkKUf0govYesCuYFEKtuTigzcFyAwiNIZzXRgNxMU5UBrdOYYt+9zFY7ScczEzj4FB9w==", "dependencies": { - "CommunityToolkit.WinUI.Animations": "8.1.240821", - "CommunityToolkit.WinUI.Extensions": "8.1.240821", + "CommunityToolkit.WinUI.Animations": "8.1.240916", + "CommunityToolkit.WinUI.Extensions": "8.1.240916", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", "Microsoft.WindowsAppSDK": "1.5.240311000", "Microsoft.Xaml.Behaviors.WinUI.Managed": "2.0.9" } }, - "CommunityToolkit.WinUI.Controls.ImageCropper": { - "type": "Direct", - "requested": "[8.1.240821, )", - "resolved": "8.1.240821", - "contentHash": "nafCnTNqXbI47xJRGsQpli0dBUvQ93O6c9AEUfuBdxCYcA6uwdNUiBW9Gq7piXyQo6yjM8G5pRyprxyVi+nwaQ==", - "dependencies": { - "CommunityToolkit.WinUI.Extensions": "8.1.240821", - "Microsoft.Graphics.Win2D": "1.0.0.30", - "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", - "Microsoft.WindowsAppSDK": "1.5.240311000" - } - }, "CommunityToolkit.WinUI.Controls.Primitives": { "type": "Direct", - "requested": "[8.1.240821, )", - "resolved": "8.1.240821", - "contentHash": "upwSObO113gfSMjMGTYGZs2zjK8smtkzaH1bSKYAau4vk4gVu3Ildnk8dMFfp5lkZKs+6xON2jbj/gfUFC2Xiw==", - "dependencies": { - "CommunityToolkit.WinUI.Extensions": "8.1.240821", - "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", - "Microsoft.WindowsAppSDK": "1.5.240311000" - } - }, - "CommunityToolkit.WinUI.Controls.SettingsControls": { - "type": "Direct", - "requested": "[8.1.240821, )", - "resolved": "8.1.240821", - "contentHash": "a5eEsR1wQSuVUsQALEpZiq6dNrTuw0POQCD6NXMBID+mY0g672Pg8OPG4MyTbwOgj+1NPua51WM22WFVGteRlQ==", + "requested": "[8.1.240916, )", + "resolved": "8.1.240916", + "contentHash": "ULEQhMGFX9YEwq9lF7XdiWAPtrXntVFqvt7HdGfeYjU6+JdW4zO/38dCMCuKJES7cG5sWWNrsntsOWUONFfVaA==", "dependencies": { - "CommunityToolkit.WinUI.Triggers": "8.1.240821", + "CommunityToolkit.WinUI.Extensions": "8.1.240916", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", "Microsoft.WindowsAppSDK": "1.5.240311000" } }, "CommunityToolkit.WinUI.Controls.Sizers": { "type": "Direct", - "requested": "[8.1.240821, )", - "resolved": "8.1.240821", - "contentHash": "qnJNGocX/XoaT1yADeG75Hb4JOIh5VcmO3j6EeE1iY6gq1+Z4LVBREVC6Dz7/notBPlEErysMgbJVKURimRKsw==", + "requested": "[8.1.240916, )", + "resolved": "8.1.240916", + "contentHash": "FD6ccqMnQc1urdEmTupd59mGnTOxKqf9UvjF2dheHz61F5hlj3y+Ngq/H9jgWw5VwoDV+AV4J40d/BjmCvBLYQ==", "dependencies": { - "CommunityToolkit.WinUI.Extensions": "8.1.240821", + "CommunityToolkit.WinUI.Extensions": "8.1.240916", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", "Microsoft.WindowsAppSDK": "1.5.240311000" } }, "CommunityToolkit.WinUI.Converters": { "type": "Direct", - "requested": "[8.1.240821, )", - "resolved": "8.1.240821", - "contentHash": "Fz8uewTFTrtj3qsPrgRQ2Ld7efjdGb7EVYLxGoNuii9pS4SiKqGC2KEUJqZ8oQlXX4eUFiO5pqOeE5SC/fQoPw==", + "requested": "[8.1.240916, )", + "resolved": "8.1.240916", + "contentHash": "UELI/UNyc9lf8NqKfuBZJGnrpUgXqqRZ3L/fP8RTRP/t8DWDr9JTdhgPUbXaF/Z16DVPZRId9c+nRfVINbme4Q==", "dependencies": { "CommunityToolkit.Common": "8.2.1", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", @@ -91,9 +62,9 @@ }, "CommunityToolkit.WinUI.Extensions": { "type": "Direct", - "requested": "[8.1.240821, )", - "resolved": "8.1.240821", - "contentHash": "mYVvEWQYF6keFQ0X2yEcTFNCarhw9fTSqMDFM00/fxWmGG2RvyyC0t8k/BeqAuV9azM9ZkRNTCqRt36usUsdFg==", + "requested": "[8.1.240916, )", + "resolved": "8.1.240916", + "contentHash": "y8FEzoy5HF3dBkxe/pU87hlh0QACtGZhnv881x2SFq9fvDjukLQz6VpoOkjxSZjEILmYu1NoI/tYjqXlETRGlw==", "dependencies": { "CommunityToolkit.Common": "8.2.1", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", @@ -102,12 +73,12 @@ }, "CommunityToolkit.WinUI.Media": { "type": "Direct", - "requested": "[8.1.240821, )", - "resolved": "8.1.240821", - "contentHash": "BriTsGK69Kw71OLCbVCTZXe5hEKk1xfahZccl7PTuQYg6wo7k5mQDEOKnVtJqynW2Yd/IQznU9qEJ+t7jizWOA==", + "requested": "[8.1.240916, )", + "resolved": "8.1.240916", + "contentHash": "ghd4u81dNf5F5knVkww9QDUy03L05TvMHPlUXKOVFER0bl8A3K6I0eYi0bUNtR8VTB7giPy8DFh50t3SJjswrA==", "dependencies": { - "CommunityToolkit.WinUI.Animations": "8.1.240821", - "CommunityToolkit.WinUI.Extensions": "8.1.240821", + "CommunityToolkit.WinUI.Animations": "8.1.240916", + "CommunityToolkit.WinUI.Extensions": "8.1.240916", "Microsoft.Graphics.Win2D": "1.1.1", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", "Microsoft.WindowsAppSDK": "1.5.240311000" @@ -122,21 +93,11 @@ "ThisAssembly.Constants": "1.4.1" } }, - "H.NotifyIcon.WinUI": { - "type": "Direct", - "requested": "[2.1.3, )", - "resolved": "2.1.3", - "contentHash": "1tAYbvV2vyQ0lXKrMmNvpIiP5tXZibtyXCqlmH2Wjcc3i/W/EatihtVATpeuWZpxM6RA9T1H5pTZhT8+5y7wcQ==", - "dependencies": { - "H.NotifyIcon": "2.1.3", - "Microsoft.WindowsAppSDK": "1.5.240627000" - } - }, "HtmlAgilityPack": { "type": "Direct", - "requested": "[1.11.65, )", - "resolved": "1.11.65", - "contentHash": "Skse4MVlRcHHyP88x+v/uQrMxTPaBIwQodiQ/y59jxs8vHlqTR5M3jt5Wk+csYFnR4rX6I4ndqDji2M8p9kAzw==" + "requested": "[1.11.67, )", + "resolved": "1.11.67", + "contentHash": "xnt6f8E56oK9nLqgkF42bsVSAkUuIuJnSfvzimK7GwvbhdXWRmGsx4A8AKmLOlCUq1rVSUADKalxnSMizHt4Mg==" }, "Markdig.Signed": { "type": "Direct", @@ -144,6 +105,12 @@ "resolved": "0.37.0", "contentHash": "J7Mt1QjKijE7aAK81u5nt4SYPYEvt1odfr4ddMvyOscyNT3PoGfHKKTBB5VBSXJrWZWhfJlcG7hHBLiqawg+nw==" }, + "Microsoft.DotNet.ILCompiler": { + "type": "Direct", + "requested": "[9.0.0-rc.2.24473.5, )", + "resolved": "9.0.0-rc.2.24473.5", + "contentHash": "+4KH+fbuWXX+I1nM+iP84N0nnorwxY3N8SrNLDNdWdblCPcWXSZnnst/IS+NEHTavnoTErwFNwk7IP8rPrNAQA==" + }, "Microsoft.Graphics.Win2D": { "type": "Direct", "requested": "[1.2.1-experimental2, )", @@ -153,11 +120,11 @@ "Microsoft.WindowsAppSDK": "1.6.240531000-experimental1" } }, - "Microsoft.NETCore.Platforms": { + "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.0-preview.7.23375.6, )", - "resolved": "8.0.0-preview.7.23375.6", - "contentHash": "ym5B/aglUc4K5kkiBOOgsm7EETjenJBbMchAHvcXpfFPmrdTrhnLKC1Tvx5Qnz5kHvhloyCNTpGQvWIpxvINHg==" + "requested": "[9.0.0-rc.2.24473.5, )", + "resolved": "9.0.0-rc.2.24473.5", + "contentHash": "mXyCgKqBIo/KOpY09FqjOU8d+LME2qowzzvBd1/viYmSpAKYfQ2w1WDOlh0FniY4xnrB/wmDiAo4frJvfCqT0w==" }, "Microsoft.NETCore.Targets": { "type": "Direct", @@ -165,23 +132,29 @@ "resolved": "6.0.0-preview.4.21253.7", "contentHash": "ipvxo/6FRdrmcsAtCGI9QPeaZZSyeCk9LiSVIJ4AN36IZQG7KFtk9ZbbYgvy4wzCzHkbBmTWaf2ESBqrYEtCRg==" }, + "Microsoft.Web.WebView2": { + "type": "Direct", + "requested": "[1.0.2839-prerelease, )", + "resolved": "1.0.2839-prerelease", + "contentHash": "3o8szcERAskJmZl/YFSNhUaFL27Ba8MBpD/XSq+MG+vdiU7dmtp6msk8TPB6FuIvbLrzc0meiO21dtX4o2Mzwg==" + }, "Microsoft.Windows.CsWinRT": { "type": "Direct", - "requested": "[2.1.3, )", - "resolved": "2.1.3", - "contentHash": "Nl8A4rQ4l2GNj703GvLSbr0Vo++FjxKxU7CIj1pcKz/sN8XSvD4dIvUCYYgD16o2pG4PSSXNgAxfwDUwLGHLPA==" + "requested": "[2.1.5, )", + "resolved": "2.1.5", + "contentHash": "PG0uVrpPTVEhqu70YhGMTyRKZXNgygjIIwdjAmg2hhHkmm6367TafBEdzIU/TgMXy2+x5Lv/Z9MKJehwkQXEvw==" }, "Microsoft.Windows.SDK.BuildTools": { "type": "Direct", - "requested": "[10.0.26100.1, )", - "resolved": "10.0.26100.1", - "contentHash": "ju02pHSp1LDEIvFgLIDbCbXGPpTTrXJrH90HxC6neclpbRpdS8RxeRbYOzPQFByUPCgT/Egbz4s/cpNMamW1Sg==" + "requested": "[10.0.26100.1742, )", + "resolved": "10.0.26100.1742", + "contentHash": "ypcHjr4KEi6xQhgClnbXoANHcyyX/QsC4Rky4igs6M4GiDa+weegPo8JuV/VMxqrZCV4zlqDsp2krgkN7ReAAg==" }, "Microsoft.WindowsAppSDK": { "type": "Direct", - "requested": "[1.6.240829007, )", - "resolved": "1.6.240829007", - "contentHash": "Ij0jXARFOlRY+n7M32ZS9Kbf+x7gmnhWVnMP3eKHe83eu/2pzh9f4gjmoZ5fAgoZaKWCa2PvcxzHx0gtRXcKRQ==", + "requested": "[1.6.240923002, )", + "resolved": "1.6.240923002", + "contentHash": "7PfOz2scXU+AAM/GYge+f6s7k3DVI+R1P8MNPZQr56GOPCGw+csvcg3S5KZg47z/o04kNvWH3GKtWT1ML9tpZw==", "dependencies": { "Microsoft.Web.WebView2": "1.0.2651.64", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" @@ -217,6 +190,12 @@ "resolved": "2.0.1", "contentHash": "Xvhd0IPCKhdYppa9RhDr4fkwhoMMWfahwCfyVtRgt6TRlbUw0gOmSjJR/+L+/s0QNhLTK93C+2pSlrkDgsoY2w==" }, + "runtime.win-x64.Microsoft.DotNet.ILCompiler": { + "type": "Direct", + "requested": "[9.0.0-rc.2.24473.5, )", + "resolved": "9.0.0-rc.2.24473.5", + "contentHash": "gtZORh0O2hlNn8D+ezseJs1SEAI83Ca+LC8vmAIqaYUbiAn5+6iqD+ytuztGo4Thjr/2qWXKn3l7GGQEQiCJMg==" + }, "SharpCompress": { "type": "Direct", "requested": "[0.38.0, )", @@ -228,9 +207,9 @@ }, "SharpHDiffPatch.Core": { "type": "Direct", - "requested": "[2.2.7, )", - "resolved": "2.2.7", - "contentHash": "rrvU2dpWXhxYcBtvd/nQJyoQFiZZ5YwB1oKQYA5JzNan1Iq1AZTJNnLuBCG4r56J6MAJpsDqB2o7ktJWYiAhrg==", + "requested": "[2.2.8, )", + "resolved": "2.2.8", + "contentHash": "wKUdKb753eUc4oP098Z7ROHNU0WAmW94m46infYyPVGRJ/0zlNWcuEqdpB0S1dKNv2hgLovJcbUOybEXRz536Q==", "dependencies": { "Hi3Helper.ZstdNet": "1.6.2", "ZstdSharp.Port": "0.8.1" @@ -251,46 +230,6 @@ "System.CommandLine": "2.0.0-beta4.22272.1" } }, - "System.Diagnostics.EventLog": { - "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "fdYxcRjQqTTacKId/2IECojlDSFvp7LP5N78+0z/xH7v/Tuw5ZAxu23Y6PTCRinqyu2ePx+Gn1098NC6jM6d+A==" - }, - "System.Net.Http": { - "type": "Direct", - "requested": "[4.3.4, )", - "resolved": "4.3.4", - "contentHash": "aOa2d51SEbmM+H+Csw7yJOuNZoHkrP2XnAurye5HWYgGVVU54YZDvsLUYRv6h18X3sPnjNCANmN7ZhIPiqMcjA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.1", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.DiagnosticSource": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2" - } - }, "System.Security.AccessControl": { "type": "Direct", "requested": "[6.0.1, )", @@ -299,656 +238,112 @@ }, "System.Security.Cryptography.ProtectedData": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "+TUFINV2q2ifyXauQXRwy4CiBhqvDEDZeVJU7qfxya4aRYOKzVBpN+4acx25VcPB9ywUN6C0n8drWl110PhZEg==" + "requested": "[9.0.0-rc.2.24473.5, )", + "resolved": "9.0.0-rc.2.24473.5", + "contentHash": "WmEKZcNdOoUSEVQVTs8LsD0WdnliOij0G4s9ljfVTFekI82JWk3WfvlpFzDsc7sXO12w/ckXrkaaRkiUBT8Gyw==" }, "System.Text.Encoding.CodePages": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==" + "requested": "[9.0.0-rc.2.24473.5, )", + "resolved": "9.0.0-rc.2.24473.5", + "contentHash": "H1e56lFcgmKStcHlfRZQHn0Ck4Lb2BQIMbTrIq+5CT3LLn8uyL9R7kSM0RtYaIoOl5bD5NvGXjNQvReNY1siAA==" }, "System.Text.Json": { "type": "Direct", - "requested": "[8.0.4, )", - "resolved": "8.0.4", - "contentHash": "bAkhgDJ88XTsqczoxEMliSrpijKZHhbJQldhAmObj/RbrN3sU5dcokuXmWJWsdQAhiMJ9bTayWsL1C9fbbCRhw==", - "dependencies": { - "System.Text.Encodings.Web": "8.0.0" - } - }, - "System.Text.RegularExpressions": { - "type": "Direct", - "requested": "[4.3.1, )", - "resolved": "4.3.1", - "contentHash": "N0kNRrWe4+nXOWlpLT4LAY5brb8caNFlUuIRpraCVMDLYutKkol1aV079rQjLuSxKMJT2SpBQsYX9xbcTMmzwg==", - "dependencies": { - "System.Runtime": "4.3.1" - } + "requested": "[9.0.0-rc.2.24473.5, )", + "resolved": "9.0.0-rc.2.24473.5", + "contentHash": "IsQCD+zBcFhteX7fUrS4cU/GvfLPy8F4oLtC9VBcF1U1qu1gZB/zlAxW8G0kqmAiXI84/gowZtcX1MjLk2QWoQ==" }, - "TaskScheduler": { + "Velopack": { "type": "Direct", - "requested": "[2.11.0, )", - "resolved": "2.11.0", - "contentHash": "p9wH58XSNIyUtO7PIFAEldaKUzpYmlj+YWAfnUqBKnGxIZRY51I9BrsBGJijUVwlxrgmLLPUigRIv2ZTD4uPJA==", + "requested": "[0.0.626, )", + "resolved": "0.0.626", + "contentHash": "el6qqNyJM3GA11j6SFZgcQJ/AFg7ocQQSu+Tk/+OIzejZlROGLK+RDWPb4tcp9E9ug9aT4HV0m35zISMSC++/Q==", "dependencies": { - "Microsoft.Win32.Registry": "5.0.0", - "System.Diagnostics.EventLog": "8.0.0", - "System.Security.AccessControl": "6.0.1" + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "NuGet.Versioning": "6.10.1" } }, "CommunityToolkit.WinUI.Animations": { "type": "Transitive", - "resolved": "8.1.240821", - "contentHash": "ehqSIkYUE8bauxUTp/q3aI6VrX7os/8Bc+8+FFnpErI0u/aeBI6CYZe+WgiLGefxy8/hNHPrtJBbczah7o0y6g==", + "resolved": "8.1.240916", + "contentHash": "jHYltimsKSSrYAij1TJC4VXzWZ6RaKCOWzkdDPenkjAcvE6lhSdX68eH6nNRIwZZC9lMpDy2Fba4uAJ1vMt9Ew==", "dependencies": { - "CommunityToolkit.WinUI.Extensions": "8.1.240821", + "CommunityToolkit.WinUI.Extensions": "8.1.240916", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", "Microsoft.WindowsAppSDK": "1.5.240311000" } }, "CommunityToolkit.WinUI.Helpers": { "type": "Transitive", - "resolved": "8.1.240821", - "contentHash": "/h4p1+5Hv9j61mp++IfiWxVihq0yb6maRcIRrehzB4YTXFU60P3pZwQShNytTVnGMDRm6AgupbtnBoG+38PBnA==", + "resolved": "8.1.240916", + "contentHash": "Q2eipdR9ntktYT1b3Dn927lZjdwQsmSU9cq6Pf2GwXW+R2IwkbGn7uYKJhmr5athOT6mnNgc7pMmfwFHIsFPLQ==", "dependencies": { - "CommunityToolkit.WinUI.Extensions": "8.1.240821", + "CommunityToolkit.WinUI.Extensions": "8.1.240916", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", "Microsoft.WindowsAppSDK": "1.5.240311000" } }, "CommunityToolkit.WinUI.Triggers": { "type": "Transitive", - "resolved": "8.1.240821", - "contentHash": "D5Xr3MsJX1n3iIi7SnAW29Nec6K1rQDSfuFNZGHrdIsqnVw5Tu8ITmWjv3oWQAdEWql7vcyY/Hezb7l4uFXP0w==", + "resolved": "8.1.240916", + "contentHash": "4XUtIdhjTj9mTWgqUvnVIa3Ox+CW+fBks94DUOndAuxtHlDf9Oc8EguHXp4W8qxp7x4tUeYDoRPmcsnXgjt3Sw==", "dependencies": { - "CommunityToolkit.WinUI.Helpers": "8.1.240821", + "CommunityToolkit.WinUI.Helpers": "8.1.240916", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", "Microsoft.WindowsAppSDK": "1.5.240311000" } }, "Google.Protobuf": { "type": "Transitive", - "resolved": "3.28.1", - "contentHash": "i4EN7Z+OUdoRBNiVMIG6CfMh6UowXiUx+BKgE+GHLbAX5ArSmpUTFUDwgRNwNfYdosl6GXuBlDiHCcXSHw43+A==" - }, - "H.GeneratedIcons.System.Drawing": { - "type": "Transitive", - "resolved": "2.1.3", - "contentHash": "GH6MR2Qc/yyNaCj1205v2J3jT4siDmg5W+jlbJzxDuMZZtX20pEIb/QBABDS+l9brHlwPs7WygboRNeMii40Qw==", - "dependencies": { - "System.Drawing.Common": "8.0.7" - } - }, - "H.NotifyIcon": { - "type": "Transitive", - "resolved": "2.1.3", - "contentHash": "aiCL1gjfKWh/jRlV4oDOycOqRaRj/C9E5cwHL2KCc+jre5vyUw5lHcHW6t4e/U3ssDmBKJgwhcgAW3JwMUP3ow==", - "dependencies": { - "H.GeneratedIcons.System.Drawing": "2.1.3" - } + "resolved": "3.28.2", + "contentHash": "Z86ZKAB+v1B/m0LTM+EVamvZlYw/g3VND3/Gs4M/+aDIxa2JE9YPKjDxTpf0gv2sh26hrve3eI03brxBmzn92g==" }, "Hi3Helper.ZstdNet": { "type": "Transitive", - "resolved": "1.6.2", - "contentHash": "8fx2KaLe9SAAODCNY0qXHIFNbloELXIIPUPGquxjIBj6YIznhTAynzH3RJeOQSFrtkdH37BwEFY2YAw4fW9ghQ==" + "resolved": "1.6.3", + "contentHash": "NbVTqRjA7QrXj5lS9bIy111LtEKqrESOXAjC4e4gliuq/cyeEIIDS9DaqXYO8oLJL7xzMHtMHBjxjXI8l8N7jg==" }, "Microsoft.CSharp": { "type": "Transitive", "resolved": "4.7.0", "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" }, - "Microsoft.Web.WebView2": { - "type": "Transitive", - "resolved": "1.0.2651.64", - "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg==" - }, - "Microsoft.Win32.Registry": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", - "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" - } - }, - "Microsoft.Win32.SystemEvents": { + "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", "resolved": "8.0.0", - "contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw==" - }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "7VSGO0URRKoMEAq0Sc9cRz8mb6zbyx/BZDEWhgPdzzpmFhkam3fJ1DAGWFXBI4nGlma+uPKpfuMQP5LXRnOH5g==" - }, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "0oAaTAm6e2oVH+/Zttt0cuhGaePQYKII1dY8iaqP7CvOpVKgLybKRFvQjXR2LtxXOXTVPNv14j0ot8uV+HrUmw==" - }, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "G24ibsCNi5Kbz0oXWynBoRgtGvsw5ZSVEWjv13/KiCAM8C6wz9zzcCniMeQFIkJ2tasjo2kXlvlBZhplL51kGg==" - }, - "runtime.native.System": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Net.Http": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Security.Cryptography.Apple": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", - "dependencies": { - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" - } - }, - "runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "QR1OwtwehHxSeQvZKXe+iSd+d3XZNkEcuWMFYa2i0aG1l+lR739HPicKMlTbJst3spmeekDVBUS7SeS26s4U/g==", - "dependencies": { - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2" - } - }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "I+GNKGg2xCHueRd1m9PzeEW7WLbNNLznmTuEi8/vZX71HudUbx1UTwlGkiwMri7JLl8hGaIAWnA/GONhu+LOyQ==" - }, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "1Z3TAq1ytS1IBRtPXJvEUZdVsfWfeNEhBkbiOCGEl9wwAfsjP2lz3ZFDx5tq8p60/EqbS0HItG5piHuB71RjoA==" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "6mU/cVmmHtQiDXhnzUImxIcDL48GbTk+TsptXyJA+MIOG9LRjPoAQC/qBFB7X+UNyK86bmvGwC8t+M66wsYC8w==" - }, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "vjwG0GGcTW/PPg6KVud8F9GLWYuAV1rrw1BKAqY0oh4jcUqg15oYF1+qkGR2x2ZHM4DQnWKQ7cJgYbfncz/lYg==" - }, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "7KMFpTkHC/zoExs+PwP8jDCWcrK9H6L7soowT80CUx3e+nxP/AFnq0AQAW5W76z2WYbLAYCRyPfwYFG6zkvQRw==" - }, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "xrlmRCnKZJLHxyyLIqkZjNXqgxnKdZxfItrPkjI+6pkRo5lHX8YvSZlWrSI5AVwLMi4HbNWP7064hcAWeZKp5w==" - }, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg==" - }, - "System.Collections": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Collections.Concurrent": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } + "contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg==" }, - "System.Diagnostics.Debug": { + "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", + "resolved": "8.0.0", + "contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==", "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" } }, - "System.Diagnostics.DiagnosticSource": { + "Microsoft.Win32.SystemEvents": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - } + "resolved": "9.0.0-rc.2.24473.5", + "contentHash": "is9hwbUxr4o0jPDY5uRJTmq5RqqZVrV/OvZTt/sEC4bekBf/r1un1+SY1L9KE87QkeIkFbFzKomLivEvJ8vCbg==" }, - "System.Diagnostics.Tracing": { + "NuGet.Versioning": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } + "resolved": "6.10.1", + "contentHash": "tovHZ3OlMVmsTdhv2z5nwnnhoA1ryhfJMyVQ9/+iv6d3h78fp230XaGy3K/iVcLwB50DdfNfIsitW97KSOWDFg==" }, "System.Drawing.Common": { "type": "Transitive", - "resolved": "8.0.8", - "contentHash": "4ZM1wvLjz9nVVExsfPAaSl/qOvU+QNedJL5rQ+2Wbow+iGeyO0e7XN07707rMBgaffEeeLrCZBwC0oHUuvRdPw==", - "dependencies": { - "Microsoft.Win32.SystemEvents": "8.0.0" - } - }, - "System.Globalization": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Calendars": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", + "resolved": "9.0.0-rc.2.24474.1", + "contentHash": "a+wqXfOnCuBc7ql6xsCr9IJh5g4Hmm6HBOtQIW/QEuTPHc/R/zMNe6b13FSHN7goEd/0tea7p6EH0OsH3iBKCQ==", "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0" - } - }, - "System.IO": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", - "dependencies": { - "System.Runtime": "4.3.0" + "Microsoft.Win32.SystemEvents": "9.0.0-rc.2.24473.5" } }, "System.IO.Hashing": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "ne1843evDugl0md7Fjzy6QjJrzsjh46ZKbhf8GwBXb5f/gw97J4bxMs0NQKifDuThh/f0bZ0e62NPl1jzTuRqA==" - }, - "System.Linq": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - } - }, - "System.Net.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Reflection": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Resources.ResourceManager": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime": { - "type": "Transitive", - "resolved": "4.3.1", - "contentHash": "abhfv1dTK6NXOmu4bgHIONxHyEqFjW8HwXPmpY9gmll+ix9UNo4XDcmzJn6oLooftxNssVHdJC1pGT9jkSynQg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.1", - "Microsoft.NETCore.Targets": "1.1.3" - } - }, - "System.Runtime.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.Handles": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.InteropServices": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Runtime.Numerics": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", - "dependencies": { - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - } - }, - "System.Security.Cryptography.Algorithms": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.Apple": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Cng": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Security.Cryptography.Csp": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Security.Cryptography.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Linq": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", - "dependencies": { - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Security.Cryptography.X509Certificates": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Calendars": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Cng": "4.3.0", - "System.Security.Cryptography.Csp": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Principal.Windows": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" - }, - "System.Text.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==" - }, - "System.Threading": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", - "dependencies": { - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Threading.Tasks": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } + "resolved": "9.0.0-rc.2.24473.5", + "contentHash": "BWkkIwLhG75+RnyBHGQd0Vrti8wqIBeNiAmHQrb6UiFny6qEQ+z6r61bFAOubw8dbp5S8nQrTS/wjJtpolkYTA==" }, "System.Threading.Tasks.Extensions": { "type": "Transitive", @@ -972,28 +367,49 @@ "colorthief": { "type": "Project", "dependencies": { - "System.Drawing.Common": "[8.0.8, )" + "System.Drawing.Common": "[9.0.0-rc.2.24474.1, )" } }, "discordrpc": { "type": "Project", "dependencies": { - "System.Text.Json": "[8.0.4, )" + "System.Text.Json": "[9.0.0-rc.2.24473.5, )" + } + }, + "h.generatedicons.system.drawing": { + "type": "Project", + "dependencies": { + "System.Drawing.Common": "[9.0.0-rc.2.24474.1, )" + } + }, + "h.notifyicon": { + "type": "Project", + "dependencies": { + "H.GeneratedIcons.System.Drawing": "[1.0.0, )" + } + }, + "h.notifyicon.winui": { + "type": "Project", + "dependencies": { + "H.NotifyIcon": "[1.0.0, )", + "Microsoft.Web.WebView2": "[1.0.2839-prerelease, )", + "Microsoft.Windows.SDK.BuildTools": "[10.0.26100.1742, )", + "Microsoft.WindowsAppSDK": "[1.6.240923002, )" } }, "hi3helper.core": { "type": "Project", "dependencies": { "Hi3Helper.EncTool": "[1.0.0, )", - "Hi3Helper.Http": "[2.0.0, )" + "Microsoft.Windows.CsWinRT": "[2.1.5, )" } }, "hi3helper.enctool": { "type": "Project", "dependencies": { - "Google.Protobuf": "[3.28.1, )", + "Google.Protobuf": "[3.28.2, )", "Hi3Helper.Http": "[2.0.0, )", - "System.IO.Hashing": "[8.0.0, )" + "System.IO.Hashing": "[9.0.0-rc.1.24431.7, )" } }, "hi3helper.http": { @@ -1004,28 +420,60 @@ "dependencies": { "Google.Protobuf": "[*, )", "Hi3Helper.ZstdNet": "[*, )", - "System.IO.Hashing": "[*, )" + "System.IO.Hashing": "[9.0.0-rc.2.24473.5, )" + } + }, + "ImageCropper": { + "type": "Project", + "dependencies": { + "CommunityToolkit.WinUI.Extensions": "[8.1.240916, )", + "CommunityToolkit.WinUI.Media": "[8.1.240916, )", + "Microsoft.Graphics.Win2D": "[1.2.1-experimental2, )", + "Microsoft.Web.WebView2": "[1.0.2783-prerelease, )", + "Microsoft.Windows.CsWinRT": "[2.1.5, )", + "Microsoft.Windows.SDK.BuildTools": "[10.0.26100.1742, )", + "Microsoft.WindowsAppSDK": "[1.6.240923002, )" } }, "imageex": { "type": "Project", "dependencies": { - "CommunityToolkit.WinUI.Extensions": "[8.1.240821, )", - "Microsoft.WindowsAppSDK": "[1.6.240829007, )" + "CommunityToolkit.WinUI.Extensions": "[8.1.240916, )", + "Microsoft.Web.WebView2": "[1.0.2839-prerelease, )", + "Microsoft.Windows.SDK.BuildTools": "[10.0.26100.1742, )", + "Microsoft.WindowsAppSDK": "[1.6.240923002, )" } }, "innosetuphelper": { "type": "Project", "dependencies": { - "Hi3Helper.Core": "[1.0.0, )", - "System.IO.Hashing": "[8.0.0, )" + "System.IO.Hashing": "[9.0.0-rc.2.24473.5, )" + } + }, + "SettingsControls": { + "type": "Project", + "dependencies": { + "CommunityToolkit.WinUI.Triggers": "[8.1.240916, )", + "Microsoft.Web.WebView2": "[1.0.2783-prerelease, )", + "Microsoft.Windows.CsWinRT": "[2.1.5, )", + "Microsoft.Windows.SDK.BuildTools": "[10.0.26100.1742, )", + "Microsoft.WindowsAppSDK": "[1.6.240923002, )" } }, "sevenzipextractor": { "type": "Project" } }, - "net8.0-windows10.0.22621/win-x64": { + "net9.0-windows10.0.22621/win-x64": { + "Microsoft.DotNet.ILCompiler": { + "type": "Direct", + "requested": "[9.0.0-rc.2.24473.5, )", + "resolved": "9.0.0-rc.2.24473.5", + "contentHash": "+4KH+fbuWXX+I1nM+iP84N0nnorwxY3N8SrNLDNdWdblCPcWXSZnnst/IS+NEHTavnoTErwFNwk7IP8rPrNAQA==", + "dependencies": { + "runtime.win-x64.Microsoft.DotNet.ILCompiler": "9.0.0-rc.2.24473.5" + } + }, "Microsoft.Graphics.Win2D": { "type": "Direct", "requested": "[1.2.1-experimental2, )", @@ -1035,11 +483,17 @@ "Microsoft.WindowsAppSDK": "1.6.240531000-experimental1" } }, + "Microsoft.Web.WebView2": { + "type": "Direct", + "requested": "[1.0.2839-prerelease, )", + "resolved": "1.0.2839-prerelease", + "contentHash": "3o8szcERAskJmZl/YFSNhUaFL27Ba8MBpD/XSq+MG+vdiU7dmtp6msk8TPB6FuIvbLrzc0meiO21dtX4o2Mzwg==" + }, "Microsoft.WindowsAppSDK": { "type": "Direct", - "requested": "[1.6.240829007, )", - "resolved": "1.6.240829007", - "contentHash": "Ij0jXARFOlRY+n7M32ZS9Kbf+x7gmnhWVnMP3eKHe83eu/2pzh9f4gjmoZ5fAgoZaKWCa2PvcxzHx0gtRXcKRQ==", + "requested": "[1.6.240923002, )", + "resolved": "1.6.240923002", + "contentHash": "7PfOz2scXU+AAM/GYge+f6s7k3DVI+R1P8MNPZQr56GOPCGw+csvcg3S5KZg47z/o04kNvWH3GKtWT1ML9tpZw==", "dependencies": { "Microsoft.Web.WebView2": "1.0.2651.64", "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" @@ -1054,46 +508,6 @@ "PhotoSauce.MagicScaler": "[0.14.2]" } }, - "System.Diagnostics.EventLog": { - "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "fdYxcRjQqTTacKId/2IECojlDSFvp7LP5N78+0z/xH7v/Tuw5ZAxu23Y6PTCRinqyu2ePx+Gn1098NC6jM6d+A==" - }, - "System.Net.Http": { - "type": "Direct", - "requested": "[4.3.4, )", - "resolved": "4.3.4", - "contentHash": "aOa2d51SEbmM+H+Csw7yJOuNZoHkrP2XnAurye5HWYgGVVU54YZDvsLUYRv6h18X3sPnjNCANmN7ZhIPiqMcjA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.1", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.DiagnosticSource": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2" - } - }, "System.Security.AccessControl": { "type": "Direct", "requested": "[6.0.1, )", @@ -1102,236 +516,14 @@ }, "System.Text.Encoding.CodePages": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg==" - }, - "Microsoft.Web.WebView2": { - "type": "Transitive", - "resolved": "1.0.2651.64", - "contentHash": "f5sc/vcAoTCTEW7Nqzp4galAuTRguZViw8ksn+Nx2uskEBPm0/ubzy6gVjvXS/P96jLS89C8T9I0hPc417xpNg==" - }, - "Microsoft.Win32.Registry": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", - "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" - } + "requested": "[9.0.0-rc.2.24473.5, )", + "resolved": "9.0.0-rc.2.24473.5", + "contentHash": "H1e56lFcgmKStcHlfRZQHn0Ck4Lb2BQIMbTrIq+5CT3LLn8uyL9R7kSM0RtYaIoOl5bD5NvGXjNQvReNY1siAA==" }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw==" - }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "7VSGO0URRKoMEAq0Sc9cRz8mb6zbyx/BZDEWhgPdzzpmFhkam3fJ1DAGWFXBI4nGlma+uPKpfuMQP5LXRnOH5g==" - }, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "0oAaTAm6e2oVH+/Zttt0cuhGaePQYKII1dY8iaqP7CvOpVKgLybKRFvQjXR2LtxXOXTVPNv14j0ot8uV+HrUmw==" - }, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "G24ibsCNi5Kbz0oXWynBoRgtGvsw5ZSVEWjv13/KiCAM8C6wz9zzcCniMeQFIkJ2tasjo2kXlvlBZhplL51kGg==" - }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "I+GNKGg2xCHueRd1m9PzeEW7WLbNNLznmTuEi8/vZX71HudUbx1UTwlGkiwMri7JLl8hGaIAWnA/GONhu+LOyQ==" - }, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "1Z3TAq1ytS1IBRtPXJvEUZdVsfWfeNEhBkbiOCGEl9wwAfsjP2lz3ZFDx5tq8p60/EqbS0HItG5piHuB71RjoA==" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "6mU/cVmmHtQiDXhnzUImxIcDL48GbTk+TsptXyJA+MIOG9LRjPoAQC/qBFB7X+UNyK86bmvGwC8t+M66wsYC8w==" - }, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "vjwG0GGcTW/PPg6KVud8F9GLWYuAV1rrw1BKAqY0oh4jcUqg15oYF1+qkGR2x2ZHM4DQnWKQ7cJgYbfncz/lYg==" - }, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "7KMFpTkHC/zoExs+PwP8jDCWcrK9H6L7soowT80CUx3e+nxP/AFnq0AQAW5W76z2WYbLAYCRyPfwYFG6zkvQRw==" - }, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "xrlmRCnKZJLHxyyLIqkZjNXqgxnKdZxfItrPkjI+6pkRo5lHX8YvSZlWrSI5AVwLMi4HbNWP7064hcAWeZKp5w==" - }, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.2", - "contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg==" - }, - "System.Globalization.Extensions": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0" - } - }, - "System.Security.Cryptography.Algorithms": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.Apple": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Cng": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Security.Cryptography.Csp": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Security.Cryptography.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Linq": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.OpenSsl": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", - "dependencies": { - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.X509Certificates": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Calendars": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Cng": "4.3.0", - "System.Security.Cryptography.Csp": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Principal.Windows": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" - }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==" + "resolved": "9.0.0-rc.2.24473.5", + "contentHash": "is9hwbUxr4o0jPDY5uRJTmq5RqqZVrV/OvZTt/sEC4bekBf/r1un1+SY1L9KE87QkeIkFbFzKomLivEvJ8vCbg==" } } } diff --git a/ColorThief b/ColorThief index 0d73e7cce..26307cc7c 160000 --- a/ColorThief +++ b/ColorThief @@ -1 +1 @@ -Subproject commit 0d73e7cce1013b9415c44c911993c494b54eac70 +Subproject commit 26307cc7cc4f3d79db5066f8aba5adf15b1051e5 diff --git a/H.NotifyIcon b/H.NotifyIcon new file mode 160000 index 000000000..5a9d4e555 --- /dev/null +++ b/H.NotifyIcon @@ -0,0 +1 @@ +Subproject commit 5a9d4e555457d115e4116e19deae7ed4d62a046d diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/BitmapFileFormat.cs b/Hi3Helper.CommunityToolkit/ImageCropper/BitmapFileFormat.cs new file mode 100644 index 000000000..b4aa7d9ff --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/BitmapFileFormat.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// This denotes the format used when saving a bitmap to a file. +/// +public enum BitmapFileFormat +{ + /// + /// Indicates Windows Imaging Component's bitmap encoder. + /// + Bmp, + + /// + /// Indicates Windows Imaging Component's PNG encoder. + /// + Png, + + /// + /// Indicates Windows Imaging Component's bitmap JPEG encoder. + /// + Jpeg, + + /// + /// Indicates Windows Imaging Component's TIFF encoder. + /// + Tiff, + + /// + /// Indicates Windows Imaging Component's GIF encoder. + /// + Gif, + + /// + /// Indicates Windows Imaging Component's JPEGXR encoder. + /// + JpegXR +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/CropShape.cs b/Hi3Helper.CommunityToolkit/ImageCropper/CropShape.cs new file mode 100644 index 000000000..1a1c4ac42 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/CropShape.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// Crop shape enumeration. +/// Default is +/// +public enum CropShape +{ + /// + /// Use rectangular shape to crop image. + /// + Rectangular, + + /// + /// Use circular shape to crop image. + /// + Circular +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/Extension.cs b/Hi3Helper.CommunityToolkit/ImageCropper/Extension.cs new file mode 100644 index 000000000..5614a0a23 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/Extension.cs @@ -0,0 +1,33 @@ +namespace Hi3Helper.CommunityToolkit.WinUI.Controls +{ + internal static class Extension + { + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rect ToRect(this Size size) + { + return new Rect(0, 0, size.Width, size.Height); + } + + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rect ToRect(this Point point, double width, double height) + { + return new Rect(point.X, point.Y, width, height); + } + + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rect ToRect(this Point point, Point end) + { + return new Rect(point, end); + } + + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rect ToRect(this Point point, Size size) + { + return new Rect(point, size); + } + } +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/Hi3Helper.CommunityToolkit.WinUI.Controls.ImageCropper.csproj b/Hi3Helper.CommunityToolkit/ImageCropper/Hi3Helper.CommunityToolkit.WinUI.Controls.ImageCropper.csproj new file mode 100644 index 000000000..29511abdf --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/Hi3Helper.CommunityToolkit.WinUI.Controls.ImageCropper.csproj @@ -0,0 +1,82 @@ + + + + + x64 + net9.0-windows10.0.22621.0 + 10.0.22621.48 + 10.0.17763.0 + win-x64 + true + true + Library + 12.0 + portable + enable + + Hi3Helper.CommunityToolkit.WinUI.Controls + Hi3Helper.CommunityToolkit.WinUI.Controls.ImageCropper + ImageCropper + ImageCropper + 1.0.0 + https://github.com/CollapseLauncher/Collapse + enable + disable + WINAPPSDK;WINUI3 + Debug;Release + true + win-x64 + + + + full + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(DefaultXamlRuntime) + Designer + + + $(DefaultXamlRuntime) + Designer + + + + \ No newline at end of file diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageBlendBrush.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ImageBlendBrush.cs new file mode 100644 index 000000000..0bb3fcf57 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageBlendBrush.cs @@ -0,0 +1,216 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +//// Image loading reference from https://blogs.windows.com/buildingapps/2017/07/18/working-brushes-content-xaml-visual-layer-interop-part-one/#MA0k4EYWzqGKV501.97 + +// ReSharper disable RedundantExtendsListEntry +// ReSharper disable PartialTypeWithSinglePart +using CommunityToolkit.WinUI.Media; +using CanvasBlendEffect = Microsoft.Graphics.Canvas.Effects.BlendEffect; + +#if WINUI2 +using Windows.UI.Composition; +using Windows.UI.Xaml.Media.Imaging; +#endif + +#nullable enable +namespace Hi3Helper.CommunityToolkit.WinUI.Media; + +/// +/// Brush which blends a to the Backdrop in a given mode. See http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_BlendEffect.htm. +/// +public partial class ImageBlendBrush : XamlCompositionBrushBase +{ + private LoadedImageSurface? _surface; + private CompositionSurfaceBrush? _surfaceBrush; + + /// + /// Gets or sets the source of the image to composite. + /// + public Uri SourceUri + { + get => (Uri)GetValue(SourceUriProperty); + set => SetValue(SourceUriProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty SourceUriProperty = DependencyProperty.Register( + nameof(SourceUri), + typeof(Uri), // We use ImageSource type so XAML engine will automatically construct proper object from String. + typeof(ImageBlendBrush), + new PropertyMetadata(null, OnImageSourceChanged)); + + /// + /// Gets or sets how to stretch the image within the brush. + /// + public Stretch Stretch + { + get => (Stretch)GetValue(StretchProperty); + set => SetValue(StretchProperty, value); + } + + /// + /// Identifies the dependency property. + /// Requires 16299 or higher for modes other than None. + /// + public static readonly DependencyProperty StretchProperty = DependencyProperty.Register( + nameof(Stretch), + typeof(Stretch), + typeof(ImageBlendBrush), + new PropertyMetadata(Stretch.None, OnStretchChanged)); + + /// + /// Gets or sets how to blend the image with the backdrop. + /// + public ImageBlendMode Mode + { + get => (ImageBlendMode)GetValue(ModeProperty); + set => SetValue(ModeProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ModeProperty = DependencyProperty.Register( + nameof(Mode), + typeof(ImageBlendMode), + typeof(ImageBlendBrush), + new PropertyMetadata(ImageBlendMode.Multiply, OnModeChanged)); + + private static void OnImageSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var brush = (ImageBlendBrush)d; + + // Unbox and update surface if CompositionBrush exists + if (brush._surfaceBrush != null) + { + // If UriSource is invalid, StartLoadFromUri will return a blank texture. + var uri = (e.NewValue as BitmapImage)?.UriSource ?? new Uri("ms-appx:///"); + var newSurface = LoadedImageSurface.StartLoadFromUri(uri); + + brush._surface = newSurface; + brush._surfaceBrush.Surface = newSurface; + } + else + { + // If we didn't initially have a valid surface, we need to recreate our effect now. + brush.OnDisconnected(); + brush.OnConnected(); + } + } + + private static void OnStretchChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var brush = (ImageBlendBrush)d; + + // Unbox and update surface if CompositionBrush exists + if (brush._surfaceBrush != null) + { + // Modify the stretch property on our brush. + brush._surfaceBrush.Stretch = CompositionStretchFromStretch((Stretch)e.NewValue); + } + } + + private static void OnModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var brush = (ImageBlendBrush)d; + + // We can't animate our enum properties so recreate our internal brush. + brush.OnDisconnected(); + brush.OnConnected(); + } + + /// + protected override void OnConnected() + { +#if WINUI2 + var compositor = Window.Current.Compositor; +#elif WINUI3 + var compositor = CompositionTarget.GetCompositorForCurrentThread(); +#endif + + // Delay creating composition resources until they're required. + if (CompositionBrush != null || SourceUri is not { } bitmapUri) + { + return; + } + + // Use LoadedImageSurface API to get ICompositionSurface from image uri provided + // If UriSource is invalid, StartLoadFromUri will return a blank texture. + _surface = LoadedImageSurface.StartLoadFromUri(bitmapUri); + + // Load Surface onto SurfaceBrush + _surfaceBrush = compositor.CreateSurfaceBrush(_surface); + _surfaceBrush.Stretch = CompositionStretchFromStretch(Stretch); + + #if WINUI2 + var compositionCapabilities = CompositionCapabilities.GetForCurrentView(); + #else + var compositionCapabilities = new CompositionCapabilities(); + #endif + // Abort if effects aren't supported. + if (!compositionCapabilities.AreEffectsSupported()) + { + // Just use image straight-up, if we don't support effects. + CompositionBrush = _surfaceBrush; + return; + } + + var backdrop = compositor.CreateBackdropBrush(); + + // Use a Win2D invert affect applied to a CompositionBackdropBrush. + var graphicsEffect = new CanvasBlendEffect + { + Name = "Invert", + Mode = (BlendEffectMode)(int)Mode, + Background = new CompositionEffectSourceParameter("backdrop"), + Foreground = new CompositionEffectSourceParameter("image") + }; + + var effectFactory = compositor.CreateEffectFactory(graphicsEffect); + var effectBrush = effectFactory.CreateBrush(); + + effectBrush.SetSourceParameter("backdrop", backdrop); + effectBrush.SetSourceParameter("image", _surfaceBrush); + + CompositionBrush = effectBrush; + } + + /// + protected override void OnDisconnected() + { + // Dispose of composition resources when no longer in use. + if (CompositionBrush != null) + { + CompositionBrush.Dispose(); + CompositionBrush = null; + } + + if (_surfaceBrush != null) + { + _surfaceBrush.Dispose(); + _surfaceBrush = null; + } + + if (_surface == null) + { + return; + } + + _surface.Dispose(); + _surface = null; + } + + //// Helper to allow XAML developer to use XAML stretch property rather than another enum. + private static CompositionStretch CompositionStretchFromStretch(Stretch value) => value switch + { + Stretch.None => CompositionStretch.None, + Stretch.Fill => CompositionStretch.Fill, + Stretch.Uniform => CompositionStretch.Uniform, + Stretch.UniformToFill => CompositionStretch.UniformToFill, + _ => CompositionStretch.None + }; +} \ No newline at end of file diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Animations.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Animations.cs new file mode 100644 index 000000000..0a7ab44ec --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Animations.cs @@ -0,0 +1,126 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#if !WINAPPSDK +using Windows.UI.Composition; +using Windows.UI.Xaml.Hosting; +#endif + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// The control allows user to crop image freely. +/// +public partial class ImageCropper +{ + private static void AnimateUIElementOffset(Point to, TimeSpan duration, UIElement target) + { + var targetVisual = ElementCompositionPreview.GetElementVisual(target); + var compositor = targetVisual.Compositor; + var linear = compositor.CreateLinearEasingFunction(); + var offsetAnimation = compositor.CreateVector3KeyFrameAnimation(); + offsetAnimation.Duration = duration; + offsetAnimation.Target = nameof(Visual.Offset); + offsetAnimation.InsertKeyFrame(1.0f, new Vector3((float)to.X, (float)to.Y, 0), linear); + targetVisual.StartAnimation(nameof(Visual.Offset), offsetAnimation); + } + + private static void AnimateUIElementScale(double to, TimeSpan duration, UIElement target) + { + var targetVisual = ElementCompositionPreview.GetElementVisual(target); + var compositor = targetVisual.Compositor; + var linear = compositor.CreateLinearEasingFunction(); + var scaleAnimation = compositor.CreateVector3KeyFrameAnimation(); + scaleAnimation.Duration = duration; + scaleAnimation.Target = nameof(Visual.Scale); + scaleAnimation.InsertKeyFrame(1.0f, new Vector3((float)to), linear); + targetVisual.StartAnimation(nameof(Visual.Scale), scaleAnimation); + } + + private static DoubleAnimation CreateDoubleAnimation(double to, TimeSpan duration, DependencyObject target, string propertyName, bool enableDependentAnimation) + { + var animation = new DoubleAnimation() + { + To = to, + Duration = duration, + EnableDependentAnimation = enableDependentAnimation + }; + + Storyboard.SetTarget(animation, target); + Storyboard.SetTargetProperty(animation, propertyName); + + return animation; + } + + private static PointAnimation CreatePointAnimation(Point to, TimeSpan duration, DependencyObject target, string propertyName, bool enableDependentAnimation) + { + var animation = new PointAnimation() + { + To = to, + Duration = duration, + EnableDependentAnimation = enableDependentAnimation + }; + + Storyboard.SetTarget(animation, target); + Storyboard.SetTargetProperty(animation, propertyName); + + return animation; + } + + private static ObjectAnimationUsingKeyFrames CreateRectangleAnimation(Rect to, TimeSpan duration, RectangleGeometry rectangle, bool enableDependentAnimation) + { + var animation = new ObjectAnimationUsingKeyFrames + { + Duration = duration, + EnableDependentAnimation = enableDependentAnimation + }; + + var frames = GetRectKeyframes(rectangle.Rect, to, duration); + foreach (var item in frames) + { + animation.KeyFrames.Add(item); + } + + Storyboard.SetTarget(animation, rectangle); + Storyboard.SetTargetProperty(animation, nameof(RectangleGeometry.Rect)); + + return animation; + } + + private static List GetRectKeyframes(Rect from, Rect to, TimeSpan duration) + { + var rectKeyframes = new List(); + var step = TimeSpan.FromMilliseconds(10); + var startPointFrom = new Point(from.X, from.Y); + var endPointFrom = new Point(from.X + from.Width, from.Y + from.Height); + var startPointTo = new Point(to.X, to.Y); + var endPointTo = new Point(to.X + to.Width, to.Y + to.Height); + for (var time = default(TimeSpan); time < duration; time += step) + { + var progress = time.TotalMilliseconds / duration.TotalMilliseconds; + var startPoint = new Point + { + X = startPointFrom.X + (progress * (startPointTo.X - startPointFrom.X)), + Y = startPointFrom.Y + (progress * (startPointTo.Y - startPointFrom.Y)), + }; + var endPoint = new Point + { + X = endPointFrom.X + (progress * (endPointTo.X - endPointFrom.X)), + Y = endPointFrom.Y + (progress * (endPointTo.Y - endPointFrom.Y)), + }; + rectKeyframes.Add(new DiscreteObjectKeyFrame + { + KeyTime = KeyTime.FromTimeSpan(time), + Value = startPoint.ToRect(endPoint) + }); + } + + rectKeyframes.Add(new DiscreteObjectKeyFrame + { + KeyTime = duration, + Value = to + }); + return rectKeyframes; + } +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Constants.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Constants.cs new file mode 100644 index 000000000..65cdb450e --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Constants.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// The control allows user to crop image freely. +/// + +public partial class ImageCropper +{ + /// + /// Key of the root layout container. + /// + internal const string LayoutGridName = "PART_LayoutGrid"; + + /// + /// Key of the Canvas that contains the image and ImageCropperThumbs. + /// + internal const string ImageCanvasPartName = "PART_ImageCanvas"; + + /// + /// Key of the Image Control inside the ImageCropper Control. + /// + internal const string SourceImagePartName = "PART_SourceImage"; + + /// + /// Key of the mask layer. + /// + internal const string MaskAreaPathPartName = "PART_MaskAreaPath"; + + /// + /// Key of the overlay layer. + /// + internal const string OverlayAreaPathPartName = "PART_OverlayAreaPath"; + + /// + /// Key of the ImageCropperThumb that on the top. + /// + internal const string TopThumbPartName = "PART_TopThumb"; + + /// + /// Key of the ImageCropperThumb on the bottom. + /// + internal const string BottomThumbPartName = "PART_BottomThumb"; + + /// + /// Key of the ImageCropperThumb on the left. + /// + internal const string LeftThumbPartName = "PART_LeftThumb"; + + /// + /// Key of the ImageCropperThumb on the right. + /// + internal const string RightThumbPartName = "PART_RightThumb"; + + /// + /// Key of the ImageCropperThumb that on the upper left. + /// + internal const string UpperLeftThumbPartName = "PART_UpperLeftThumb"; + + /// + /// Key of the ImageCropperThumb that on the upper right. + /// + internal const string UpperRightThumbPartName = "PART_UpperRightThumb"; + + /// + /// Key of the ImageCropperThumb that on the lower left. + /// + internal const string LowerLeftThumbPartName = "PART_LowerLeftThumb"; + + /// + /// Key of the ImageCropperThumb that on the lower right. + /// + internal const string LowerRightThumbPartName = "PART_LowerRightThumb"; +} \ No newline at end of file diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Events.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Events.cs new file mode 100644 index 000000000..2cbcf0ebb --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Events.cs @@ -0,0 +1,217 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// The control allows user to crop image freely. +/// +public partial class ImageCropper +{ + private void ImageCropperThumb_KeyDown(object sender, KeyRoutedEventArgs e) + { + var changed = false; + var diffPos = default(Point); + if (e.Key == VirtualKey.Left) + { + diffPos.X--; +#if WINAPPSDK + var upKeyState = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Up); + var downKeyState = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Down); +#else + var upKeyState = Window.Current.CoreWindow.GetAsyncKeyState(VirtualKey.Up); + var downKeyState = Window.Current.CoreWindow.GetAsyncKeyState(VirtualKey.Down); +#endif + if (upKeyState == CoreVirtualKeyStates.Down) + { + diffPos.Y--; + } + + if (downKeyState == CoreVirtualKeyStates.Down) + { + diffPos.Y++; + } + + changed = true; + } + else if (e.Key == VirtualKey.Right) + { + diffPos.X++; + +#if WINAPPSDK + var upKeyState = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Up); + var downKeyState = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Down); +#else + var upKeyState = Window.Current.CoreWindow.GetAsyncKeyState(VirtualKey.Up); + var downKeyState = Window.Current.CoreWindow.GetAsyncKeyState(VirtualKey.Down); +#endif + + if (upKeyState == CoreVirtualKeyStates.Down) + { + diffPos.Y--; + } + + if (downKeyState == CoreVirtualKeyStates.Down) + { + diffPos.Y++; + } + + changed = true; + } + else if (e.Key == VirtualKey.Up) + { + diffPos.Y--; + +#if WINAPPSDK + var leftKeyState = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Left); + var rightKeyState = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Right); +#else + var leftKeyState = Window.Current.CoreWindow.GetAsyncKeyState(VirtualKey.Left); + var rightKeyState = Window.Current.CoreWindow.GetAsyncKeyState(VirtualKey.Right); +#endif + + if (leftKeyState == CoreVirtualKeyStates.Down) + { + diffPos.X--; + } + + if (rightKeyState == CoreVirtualKeyStates.Down) + { + diffPos.X++; + } + + changed = true; + } + else if (e.Key == VirtualKey.Down) + { + diffPos.Y++; + +#if WINAPPSDK + var leftKeyState = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Left); + var rightKeyState = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Right); +#else + var leftKeyState = Window.Current.CoreWindow.GetAsyncKeyState(VirtualKey.Left); + var rightKeyState = Window.Current.CoreWindow.GetAsyncKeyState(VirtualKey.Right); +#endif + + if (leftKeyState == CoreVirtualKeyStates.Down) + { + diffPos.X--; + } + + if (rightKeyState == CoreVirtualKeyStates.Down) + { + diffPos.X++; + } + + changed = true; + } + + if (changed) + { + var imageCropperThumb = (ImageCropperThumb)sender; + UpdateCroppedRect(imageCropperThumb.Position, diffPos); + } + } + + private void ImageCropperThumb_KeyUp(object sender, KeyRoutedEventArgs e) + { + var selectedRect = new Point(_startX, _startY).ToRect(new Point(_endX, _endY)); + var croppedRect = _inverseImageTransform.TransformBounds(selectedRect); + if (croppedRect.Width > MinCropSize.Width && croppedRect.Height > MinCropSize.Height) + { + croppedRect.Intersect(_restrictedCropRect); + _currentCroppedRect = croppedRect; + } + + if (TryUpdateImageLayout(true)) + { + UpdateSelectionThumbs(true); + UpdateMaskArea(true); + } + } + + private void ImageCropperThumb_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e) + { + var selectedRect = new Point(_startX, _startY).ToRect(new Point(_endX, _endY)); + var croppedRect = _inverseImageTransform.TransformBounds(selectedRect); + if (croppedRect.Width > MinCropSize.Width && croppedRect.Height > MinCropSize.Height) + { + croppedRect.Intersect(_restrictedCropRect); + _currentCroppedRect = croppedRect; + } + + if (TryUpdateImageLayout(true)) + { + UpdateSelectionThumbs(true); + UpdateMaskArea(true); + } + } + + private void ImageCropperThumb_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e) + { + var imageCropperThumb = (ImageCropperThumb)sender; + var currentPointerPosition = new Point( + imageCropperThumb.X + e.Position.X + e.Delta.Translation.X - (imageCropperThumb.ActualWidth / 2), + imageCropperThumb.Y + e.Position.Y + e.Delta.Translation.Y - (imageCropperThumb.ActualHeight / 2)); + var safePosition = GetSafePoint(_restrictedSelectRect, currentPointerPosition); + var safeDiffPoint = new Point(safePosition.X - imageCropperThumb.X, safePosition.Y - imageCropperThumb.Y); + UpdateCroppedRect(imageCropperThumb.Position, safeDiffPoint); + } + + private void SourceImage_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e) + { + var offsetX = -e.Delta.Translation.X; + var offsetY = -e.Delta.Translation.Y; + if (offsetX > 0) + { + offsetX = Math.Min(offsetX, _restrictedSelectRect.X + _restrictedSelectRect.Width - _endX); + } + else + { + offsetX = Math.Max(offsetX, _restrictedSelectRect.X - _startX); + } + + if (offsetY > 0) + { + offsetY = Math.Min(offsetY, _restrictedSelectRect.Y + _restrictedSelectRect.Height - _endY); + } + else + { + offsetY = Math.Max(offsetY, _restrictedSelectRect.Y - _startY); + } + + var selectedRect = new Point(_startX, _startY).ToRect(new Point(_endX, _endY)); + selectedRect.X += offsetX; + selectedRect.Y += offsetY; + var croppedRect = _inverseImageTransform.TransformBounds(selectedRect); + croppedRect.Intersect(_restrictedCropRect); + _currentCroppedRect = croppedRect; + + if (TryUpdateImageLayout()) + { + UpdateSelectionThumbs(); + UpdateMaskArea(); + } + } + + private void ImageCanvas_SizeChanged(object sender, SizeChangedEventArgs e) + { + if (Source == null) + { + return; + } + + if (TryUpdateImageLayout()) + { + UpdateSelectionThumbs(); + } + + if (TryUpdateAspectRatio()) + { + UpdateSelectionThumbs(); + UpdateMaskArea(); + } + } +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Helpers.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Helpers.cs new file mode 100644 index 000000000..d8dcb1fe6 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Helpers.cs @@ -0,0 +1,440 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#if WINAPPSDK +using Colors = Microsoft.UI.Colors; +#else +using Windows.UI.Xaml.Media.Imaging; +using Colors = Windows.UI.Colors; +#endif + +#nullable enable +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// The control allows user to crop image freely. +/// +public partial class ImageCropper +{ + private const double ThresholdValue = 0.001; + + private static async Task CropImageAsync(WriteableBitmap writeableBitmap, IRandomAccessStream stream, Rect croppedRect, BitmapFileFormat bitmapFileFormat) + { + croppedRect.X = Math.Max(croppedRect.X, 0); + croppedRect.Y = Math.Max(croppedRect.Y, 0); + var x = (uint)Math.Floor(croppedRect.X); + var y = (uint)Math.Floor(croppedRect.Y); + var width = (uint)Math.Floor(croppedRect.Width); + var height = (uint)Math.Floor(croppedRect.Height); + using (var sourceStream = writeableBitmap.PixelBuffer.AsStream()) + { + var buffer = new byte[sourceStream.Length]; + _ = await sourceStream.ReadAsync(buffer); + var bitmapEncoder = await BitmapEncoder.CreateAsync(GetEncoderId(bitmapFileFormat), stream); + bitmapEncoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied, (uint)writeableBitmap.PixelWidth, (uint)writeableBitmap.PixelHeight, 96.0, 96.0, buffer); + bitmapEncoder.BitmapTransform.Bounds = new BitmapBounds + { + X = x, + Y = y, + Width = width, + Height = height + }; + await bitmapEncoder.FlushAsync(); + } + } + + private static async Task CropImageWithShapeAsync(WriteableBitmap writeableBitmap, IRandomAccessStream stream, Rect croppedRect, BitmapFileFormat bitmapFileFormat, CropShape cropShape) + { + var device = CanvasDevice.GetSharedDevice(); + var clipGeometry = CreateClipGeometry(device, cropShape, new Size(croppedRect.Width, croppedRect.Height)); + if (clipGeometry == null) + { + return; + } + + // WinUI3/Win2D bug: switch back to CanvasBitmap once it works. + CanvasVirtualBitmap? sourceBitmap; + using (var randomAccessStream = new InMemoryRandomAccessStream()) + { + await CropImageAsync(writeableBitmap, randomAccessStream, croppedRect, bitmapFileFormat); + sourceBitmap = await CanvasVirtualBitmap.LoadAsync(device, randomAccessStream); + } + + using (var offScreen = new CanvasRenderTarget(device, (float)croppedRect.Width, (float)croppedRect.Height, 96f)) + { + using (var drawingSession = offScreen.CreateDrawingSession()) + using (var markCommandList = new CanvasCommandList(device)) + { + using (var markDrawingSession = markCommandList.CreateDrawingSession()) + { + markDrawingSession.FillGeometry(clipGeometry, Colors.Black); + } + + var alphaMaskEffect = new AlphaMaskEffect + { + Source = sourceBitmap, + AlphaMask = markCommandList + }; + drawingSession.DrawImage(alphaMaskEffect); + alphaMaskEffect.Dispose(); + } + + clipGeometry.Dispose(); + sourceBitmap.Dispose(); + var pixelBytes = offScreen.GetPixelBytes(); + var bitmapEncoder = await BitmapEncoder.CreateAsync(GetEncoderId(bitmapFileFormat), stream); + bitmapEncoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied, offScreen.SizeInPixels.Width, offScreen.SizeInPixels.Height, 96.0, 96.0, pixelBytes); + await bitmapEncoder.FlushAsync(); + } + } + + private static CanvasGeometry? CreateClipGeometry(ICanvasResourceCreator resourceCreator, CropShape cropShape, Size croppedSize) + { + switch (cropShape) + { + case CropShape.Rectangular: + break; + case CropShape.Circular: + var radiusX = croppedSize.Width / 2; + var radiusY = croppedSize.Height / 2; + var center = new Point(radiusX, radiusY); + return CanvasGeometry.CreateEllipse(resourceCreator, center.ToVector2(), (float)radiusX, (float)radiusY); + default: + throw new ArgumentOutOfRangeException(nameof(cropShape), cropShape, null); + } + + return null; + } + + private static Guid GetEncoderId(BitmapFileFormat bitmapFileFormat) => bitmapFileFormat switch + { + BitmapFileFormat.Bmp => BitmapEncoder.BmpEncoderId, + BitmapFileFormat.Png => BitmapEncoder.PngEncoderId, + BitmapFileFormat.Jpeg => BitmapEncoder.JpegEncoderId, + BitmapFileFormat.Tiff => BitmapEncoder.TiffEncoderId, + BitmapFileFormat.Gif => BitmapEncoder.GifEncoderId, + BitmapFileFormat.JpegXR => BitmapEncoder.JpegXREncoderId, + _ => BitmapEncoder.PngEncoderId + }; + + /// + /// Gets the closest point in the rectangle to a given point. + /// + /// The rectangle. + /// The test point. + /// A point within a rectangle. + private static Point GetSafePoint(Rect targetRect, Point point) + { + var safePoint = new Point(point.X, point.Y); + if (safePoint.X < targetRect.X) + { + safePoint.X = targetRect.X; + } + + if (safePoint.X > targetRect.X + targetRect.Width) + { + safePoint.X = targetRect.X + targetRect.Width; + } + + if (safePoint.Y < targetRect.Y) + { + safePoint.Y = targetRect.Y; + } + + if (safePoint.Y > targetRect.Y + targetRect.Height) + { + safePoint.Y = targetRect.Y + targetRect.Height; + } + + return safePoint; + } + + /// + /// Test whether the point is in the rectangle. + /// Similar to the Rect.Contains method, this method adds redundancy. + /// + /// the rectangle. + /// The test point. + /// bool + private static bool IsSafePoint(Rect targetRect, Point point) + { + if (point.X - targetRect.X < -ThresholdValue) + { + return false; + } + + if (point.X - (targetRect.X + targetRect.Width) > ThresholdValue) + { + return false; + } + + if (point.Y - targetRect.Y < -ThresholdValue) + { + return false; + } + + if (point.Y - (targetRect.Y + targetRect.Height) > ThresholdValue) + { + return false; + } + + return true; + } + + /// + /// Determines whether a rectangle satisfies the minimum size limit. + /// + /// The point in the upper left corner. + /// The point in the lower right corner. + /// The minimum size. + /// bool + private static bool IsSafeRect(Point startPoint, Point endPoint, Size minSize) + { + var checkPoint = new Point(startPoint.X + minSize.Width, startPoint.Y + minSize.Height); + return checkPoint.X - endPoint.X < ThresholdValue + && checkPoint.Y - endPoint.Y < ThresholdValue; + } + + /// + /// Gets a rectangle with a minimum size limit. + /// + /// The point in the upper left corner. + /// The point in the lower right corner. + /// The minimum size. + /// The control point. + /// The right rectangle. + private static Rect GetSafeRect(Point startPoint, Point endPoint, Size minSize, ThumbPosition position) + { + var checkPoint = new Point(startPoint.X + minSize.Width, startPoint.Y + minSize.Height); + switch (position) + { + case ThumbPosition.Top: + if (checkPoint.Y > endPoint.Y) + { + startPoint.Y = endPoint.Y - minSize.Height; + } + + break; + case ThumbPosition.Bottom: + if (checkPoint.Y > endPoint.Y) + { + endPoint.Y = startPoint.Y + minSize.Height; + } + + break; + case ThumbPosition.Left: + if (checkPoint.X > endPoint.X) + { + startPoint.X = endPoint.X - minSize.Width; + } + + break; + case ThumbPosition.Right: + if (checkPoint.X > endPoint.X) + { + endPoint.X = startPoint.X + minSize.Width; + } + + break; + case ThumbPosition.UpperLeft: + if (checkPoint.X > endPoint.X) + { + startPoint.X = endPoint.X - minSize.Width; + } + + if (checkPoint.Y > endPoint.Y) + { + startPoint.Y = endPoint.Y - minSize.Height; + } + + break; + case ThumbPosition.UpperRight: + if (checkPoint.X > endPoint.X) + { + endPoint.X = startPoint.X + minSize.Width; + } + + if (checkPoint.Y > endPoint.Y) + { + startPoint.Y = endPoint.Y - minSize.Height; + } + + break; + case ThumbPosition.LowerLeft: + if (checkPoint.X > endPoint.X) + { + startPoint.X = endPoint.X - minSize.Width; + } + + if (checkPoint.Y > endPoint.Y) + { + endPoint.Y = startPoint.Y + minSize.Height; + } + + break; + case ThumbPosition.LowerRight: + if (checkPoint.X > endPoint.X) + { + endPoint.X = startPoint.X + minSize.Width; + } + + if (checkPoint.Y > endPoint.Y) + { + endPoint.Y = startPoint.Y + minSize.Height; + } + + break; + default: + throw new ArgumentOutOfRangeException(nameof(position), position, null); + } + + return startPoint.ToRect(endPoint); + } + + /// + /// Gets the maximum rectangle embedded in the rectangle by a given aspect ratio. + /// + /// The rectangle. + /// The aspect ratio. + /// The right rectangle. + private static Rect GetUniformRect(Rect targetRect, double aspectRatio) + { + var ratio = targetRect.Width / targetRect.Height; + var cx = targetRect.X + (targetRect.Width / 2); + var cy = targetRect.Y + (targetRect.Height / 2); + double width, height; + if (aspectRatio > ratio) + { + width = targetRect.Width; + height = width / aspectRatio; + } + else + { + height = targetRect.Height; + width = height * aspectRatio; + } + + return new Rect(cx - (width / 2f), cy - (height / 2f), width, height); + } + + private static bool IsValidRect(Rect targetRect) + { + return targetRect is { IsEmpty: false, Width: > 0, Height: > 0 }; + } + + private static Point GetSafeSizeChangeWhenKeepAspectRatio(Rect targetRect, ThumbPosition thumbPosition, Rect selectedRect, Point originSizeChange, double aspectRatio) + { + var safeWidthChange = originSizeChange.X; + var safeHeightChange = originSizeChange.Y; + double maxWidthChange; + double maxHeightChange; + switch (thumbPosition) + { + case ThumbPosition.Top: + maxWidthChange = targetRect.Width - selectedRect.Width; + maxHeightChange = selectedRect.Top - targetRect.Top; + break; + case ThumbPosition.Bottom: + maxWidthChange = targetRect.Width - selectedRect.Width; + maxHeightChange = targetRect.Bottom - selectedRect.Bottom; + break; + case ThumbPosition.Left: + maxWidthChange = selectedRect.Left - targetRect.Left; + maxHeightChange = targetRect.Height - selectedRect.Height; + break; + case ThumbPosition.Right: + maxWidthChange = targetRect.Right - selectedRect.Right; + maxHeightChange = targetRect.Height - selectedRect.Height; + break; + case ThumbPosition.UpperLeft: + maxWidthChange = selectedRect.Left - targetRect.Left; + maxHeightChange = selectedRect.Top - targetRect.Top; + break; + case ThumbPosition.UpperRight: + maxWidthChange = targetRect.Right - selectedRect.Right; + maxHeightChange = selectedRect.Top - targetRect.Top; + break; + case ThumbPosition.LowerLeft: + maxWidthChange = selectedRect.Left - targetRect.Left; + maxHeightChange = targetRect.Bottom - selectedRect.Bottom; + break; + case ThumbPosition.LowerRight: + maxWidthChange = targetRect.Right - selectedRect.Right; + maxHeightChange = targetRect.Bottom - selectedRect.Bottom; + break; + default: + throw new ArgumentOutOfRangeException(nameof(thumbPosition), thumbPosition, null); + } + + if (originSizeChange.X > maxWidthChange) + { + safeWidthChange = maxWidthChange; + safeHeightChange = safeWidthChange / aspectRatio; + } + + if (!(originSizeChange.Y > maxHeightChange)) + { + return new Point(safeWidthChange, safeHeightChange); + } + + safeHeightChange = maxHeightChange; + safeWidthChange = safeHeightChange * aspectRatio; + + return new Point(safeWidthChange, safeHeightChange); + } + + private static bool CanContains(Rect targetRect, Rect testRect) + { + return targetRect.Width - testRect.Width > -ThresholdValue && targetRect.Height - testRect.Height > -ThresholdValue; + } + + private static bool TryGetContainedRect(Rect targetRect, ref Rect testRect) + { + if (!CanContains(targetRect, testRect)) + { + return false; + } + + if (targetRect.Left > testRect.Left) + { + testRect.X += targetRect.Left - testRect.Left; + } + + if (targetRect.Top > testRect.Top) + { + testRect.Y += targetRect.Top - testRect.Top; + } + + if (targetRect.Right < testRect.Right) + { + testRect.X += targetRect.Right - testRect.Right; + } + + if (targetRect.Bottom < testRect.Bottom) + { + testRect.Y += targetRect.Bottom - testRect.Bottom; + } + + return true; + } + + private static bool IsCornerThumb(ThumbPosition thumbPosition) + { + switch (thumbPosition) + { + case ThumbPosition.Top: + case ThumbPosition.Bottom: + case ThumbPosition.Left: + case ThumbPosition.Right: + return false; + case ThumbPosition.UpperLeft: + case ThumbPosition.UpperRight: + case ThumbPosition.LowerLeft: + case ThumbPosition.LowerRight: + return true; + } + + return false; + } +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Logic.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Logic.cs new file mode 100644 index 000000000..044433e14 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Logic.cs @@ -0,0 +1,688 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#if !WINAPPSDK +using Windows.UI.Xaml.Hosting; +#endif + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// The control allows user to crop image freely. +/// +public partial class ImageCropper +{ + /// + /// Initializes image source transform. + /// + /// Whether animation is enabled. + private void InitImageLayout(bool animate = false) + { + if (Source != null) + { + _restrictedCropRect = new Rect(0, 0, Source.PixelWidth, Source.PixelHeight); + if (IsValidRect(_restrictedCropRect)) + { + _currentCroppedRect = KeepAspectRatio ? GetUniformRect(_restrictedCropRect, ActualAspectRatio) : _restrictedCropRect; + + if (TryUpdateImageLayout(animate)) + { + UpdateSelectionThumbs(animate); + UpdateMaskArea(animate); + } + } + } + + UpdateThumbsVisibility(); + } + + /// + /// Update image source transform. + /// + /// Whether animation is enabled. + private bool TryUpdateImageLayout(bool animate = false) + { + if (Source != null && IsValidRect(CanvasRect)) + { + var uniformSelectedRect = GetUniformRect(CanvasRect, _currentCroppedRect.Width / _currentCroppedRect.Height); + + return TryUpdateImageLayoutWithViewport(uniformSelectedRect, _currentCroppedRect, animate); + } + + return false; + } + + /// + /// Update image source transform. + /// + /// Viewport + /// The real image area of viewport. + /// Whether animation is enabled. + private bool TryUpdateImageLayoutWithViewport(Rect viewport, Rect viewportImageRect, bool animate = false) + { + if (!IsValidRect(viewport) || !IsValidRect(viewportImageRect)) + { + return false; + } + + var imageScale = viewport.Width / viewportImageRect.Width; + _imageTransform.ScaleX = _imageTransform.ScaleY = imageScale; + _imageTransform.TranslateX = viewport.X - (viewportImageRect.X * imageScale); + _imageTransform.TranslateY = viewport.Y - (viewportImageRect.Y * imageScale); + _inverseImageTransform.ScaleX = _inverseImageTransform.ScaleY = 1 / imageScale; + _inverseImageTransform.TranslateX = -_imageTransform.TranslateX / imageScale; + _inverseImageTransform.TranslateY = -_imageTransform.TranslateY / imageScale; + _restrictedSelectRect = _imageTransform.TransformBounds(_restrictedCropRect); + + if (animate) + { + AnimateUIElementOffset(new Point(_imageTransform.TranslateX, _imageTransform.TranslateY), _animationDuration, _sourceImage!); + AnimateUIElementScale(imageScale, _animationDuration, _sourceImage!); + } + else + { + var targetVisual = ElementCompositionPreview.GetElementVisual(_sourceImage); + targetVisual.Offset = new Vector3((float)_imageTransform.TranslateX, (float)_imageTransform.TranslateY, 0); + targetVisual.Scale = new Vector3((float)imageScale); + } + + return true; + } + + /// + /// Update cropped area. + /// + /// The control point + /// Position offset + private void UpdateCroppedRect(ThumbPosition position, Point diffPos) + { + if (diffPos == default(Point) || !IsValidRect(CanvasRect)) + { + return; + } + + double radian = 0d, diffPointRadian = 0d; + if (KeepAspectRatio) + { + radian = Math.Atan(ActualAspectRatio); + diffPointRadian = Math.Atan(diffPos.X / diffPos.Y); + } + + var startPoint = new Point(_startX, _startY); + var endPoint = new Point(_endX, _endY); + var currentSelectedRect = startPoint.ToRect(endPoint); + switch (position) + { + case ThumbPosition.Top: + if (KeepAspectRatio) + { + var originSizeChange = new Point(-diffPos.Y * ActualAspectRatio, -diffPos.Y); + var safeChange = GetSafeSizeChangeWhenKeepAspectRatio(_restrictedSelectRect, position, currentSelectedRect, originSizeChange, ActualAspectRatio); + startPoint.X += -safeChange.X / 2; + endPoint.X += safeChange.X / 2; + startPoint.Y += -safeChange.Y; + } + else + { + startPoint.Y += diffPos.Y; + } + + break; + case ThumbPosition.Bottom: + if (KeepAspectRatio) + { + var originSizeChange = new Point(diffPos.Y * ActualAspectRatio, diffPos.Y); + var safeChange = GetSafeSizeChangeWhenKeepAspectRatio(_restrictedSelectRect, position, currentSelectedRect, originSizeChange, ActualAspectRatio); + startPoint.X += -safeChange.X / 2; + endPoint.X += safeChange.X / 2; + endPoint.Y += safeChange.Y; + } + else + { + endPoint.Y += diffPos.Y; + } + + break; + case ThumbPosition.Left: + if (KeepAspectRatio) + { + var originSizeChange = new Point(-diffPos.X, -diffPos.X / ActualAspectRatio); + var safeChange = GetSafeSizeChangeWhenKeepAspectRatio(_restrictedSelectRect, position, currentSelectedRect, originSizeChange, ActualAspectRatio); + startPoint.Y += -safeChange.Y / 2; + endPoint.Y += safeChange.Y / 2; + startPoint.X += -safeChange.X; + } + else + { + startPoint.X += diffPos.X; + } + + break; + case ThumbPosition.Right: + if (KeepAspectRatio) + { + var originSizeChange = new Point(diffPos.X, diffPos.X / ActualAspectRatio); + var safeChange = GetSafeSizeChangeWhenKeepAspectRatio(_restrictedSelectRect, position, currentSelectedRect, originSizeChange, ActualAspectRatio); + startPoint.Y += -safeChange.Y / 2; + endPoint.Y += safeChange.Y / 2; + endPoint.X += safeChange.X; + } + else + { + endPoint.X += diffPos.X; + } + + break; + case ThumbPosition.UpperLeft: + if (KeepAspectRatio) + { + var effectiveLength = diffPos.Y / Math.Cos(diffPointRadian) * Math.Cos(diffPointRadian - radian); + var originSizeChange = new Point(-effectiveLength * Math.Sin(radian), -effectiveLength * Math.Cos(radian)); + var safeChange = GetSafeSizeChangeWhenKeepAspectRatio(_restrictedSelectRect, position, currentSelectedRect, originSizeChange, ActualAspectRatio); + diffPos.X = -safeChange.X; + diffPos.Y = -safeChange.Y; + } + + startPoint.X += diffPos.X; + startPoint.Y += diffPos.Y; + break; + case ThumbPosition.UpperRight: + if (KeepAspectRatio) + { + diffPointRadian = -diffPointRadian; + var effectiveLength = diffPos.Y / Math.Cos(diffPointRadian) * Math.Cos(diffPointRadian - radian); + var originSizeChange = new Point(-effectiveLength * Math.Sin(radian), -effectiveLength * Math.Cos(radian)); + var safeChange = GetSafeSizeChangeWhenKeepAspectRatio(_restrictedSelectRect, position, currentSelectedRect, originSizeChange, ActualAspectRatio); + diffPos.X = safeChange.X; + diffPos.Y = -safeChange.Y; + } + + endPoint.X += diffPos.X; + startPoint.Y += diffPos.Y; + break; + case ThumbPosition.LowerLeft: + if (KeepAspectRatio) + { + diffPointRadian = -diffPointRadian; + var effectiveLength = diffPos.Y / Math.Cos(diffPointRadian) * Math.Cos(diffPointRadian - radian); + var originSizeChange = new Point(effectiveLength * Math.Sin(radian), effectiveLength * Math.Cos(radian)); + var safeChange = GetSafeSizeChangeWhenKeepAspectRatio(_restrictedSelectRect, position, currentSelectedRect, originSizeChange, ActualAspectRatio); + diffPos.X = -safeChange.X; + diffPos.Y = safeChange.Y; + } + + startPoint.X += diffPos.X; + endPoint.Y += diffPos.Y; + break; + case ThumbPosition.LowerRight: + if (KeepAspectRatio) + { + var effectiveLength = diffPos.Y / Math.Cos(diffPointRadian) * Math.Cos(diffPointRadian - radian); + var originSizeChange = new Point(effectiveLength * Math.Sin(radian), effectiveLength * Math.Cos(radian)); + var safeChange = GetSafeSizeChangeWhenKeepAspectRatio(_restrictedSelectRect, position, currentSelectedRect, originSizeChange, ActualAspectRatio); + diffPos.X = safeChange.X; + diffPos.Y = safeChange.Y; + } + + endPoint.X += diffPos.X; + endPoint.Y += diffPos.Y; + break; + } + + if (!IsSafeRect(startPoint, endPoint, MinSelectSize)) + { + if (KeepAspectRatio) + { + if ((endPoint.Y - startPoint.Y) < (_endY - _startY) || + (endPoint.X - startPoint.X) < (_endX - _startX)) + { + return; + } + } + else + { + var safeRect = GetSafeRect(startPoint, endPoint, MinSelectSize, position); + safeRect.Intersect(_restrictedSelectRect); + startPoint = new Point(safeRect.X, safeRect.Y); + endPoint = new Point(safeRect.X + safeRect.Width, safeRect.Y + safeRect.Height); + } + } + + var isEffectiveRegion = IsSafePoint(_restrictedSelectRect, startPoint) && + IsSafePoint(_restrictedSelectRect, endPoint); + var selectedRect = startPoint.ToRect(endPoint); + if (!isEffectiveRegion) + { + if (!IsCornerThumb(position) && TryGetContainedRect(_restrictedSelectRect, ref selectedRect)) + { + startPoint = new Point(selectedRect.Left, selectedRect.Top); + endPoint = new Point(selectedRect.Right, selectedRect.Bottom); + } + else + { + return; + } + } + + selectedRect.Union(CanvasRect); + if (selectedRect != CanvasRect) + { + var croppedRect = _inverseImageTransform.TransformBounds(startPoint.ToRect(endPoint)); + croppedRect.Intersect(_restrictedCropRect); + _currentCroppedRect = croppedRect; + var viewportRect = GetUniformRect(CanvasRect, selectedRect.Width / selectedRect.Height); + var viewportImgRect = _inverseImageTransform.TransformBounds(selectedRect); + + if (TryUpdateImageLayoutWithViewport(viewportRect, viewportImgRect)) + { + UpdateSelectionThumbs(); + UpdateMaskArea(); + } + } + else + { + UpdateSelectionThumbs(startPoint, endPoint); + UpdateMaskArea(); + } + } + + private void UpdateSelectionThumbs(bool animate = false) + { + var selectedRect = _imageTransform.TransformBounds(_currentCroppedRect); + var startPoint = GetSafePoint(_restrictedSelectRect, new Point(selectedRect.X, selectedRect.Y)); + var endPoint = GetSafePoint(_restrictedSelectRect, new Point(selectedRect.X + selectedRect.Width, selectedRect.Y + selectedRect.Height)); + + UpdateSelectionThumbs(startPoint, endPoint, animate); + } + + #nullable enable + /// + /// Positions the thumbs for the selection rectangle. + /// + /// The point on the upper left corner. + /// The point on the lower right corner. + /// Whether animation is enabled. + private void UpdateSelectionThumbs(Point startPoint, Point endPoint, bool animate = false) + { + _startX = startPoint.X; + _startY = startPoint.Y; + _endX = endPoint.X; + _endY = endPoint.Y; + var center = SelectionAreaCenter; + + Storyboard? storyboard = null; + if (animate) + { + storyboard = new Storyboard(); + } + + if (_topThumb != null) + { + if (animate) + { + storyboard?.Children.Add(CreateDoubleAnimation(center.X, _animationDuration, _topThumb, nameof(ImageCropperThumb.X), true)); + storyboard?.Children.Add(CreateDoubleAnimation(_startY, _animationDuration, _topThumb, nameof(ImageCropperThumb.Y), true)); + } + else + { + _topThumb.X = center.X; + _topThumb.Y = _startY; + } + } + + if (_bottomThumb != null) + { + if (animate) + { + storyboard?.Children.Add(CreateDoubleAnimation(center.X, _animationDuration, _bottomThumb, nameof(ImageCropperThumb.X), true)); + storyboard?.Children.Add(CreateDoubleAnimation(_endY, _animationDuration, _bottomThumb, nameof(ImageCropperThumb.Y), true)); + } + else + { + _bottomThumb.X = center.X; + _bottomThumb.Y = _endY; + } + } + + if (_leftThumb != null) + { + if (animate) + { + storyboard?.Children.Add(CreateDoubleAnimation(_startX, _animationDuration, _leftThumb, nameof(ImageCropperThumb.X), true)); + storyboard?.Children.Add(CreateDoubleAnimation(center.Y, _animationDuration, _leftThumb, nameof(ImageCropperThumb.Y), true)); + } + else + { + _leftThumb.X = _startX; + _leftThumb.Y = center.Y; + } + } + + if (_rightThumb != null) + { + if (animate) + { + storyboard?.Children.Add(CreateDoubleAnimation(_endX, _animationDuration, _rightThumb, nameof(ImageCropperThumb.X), true)); + storyboard?.Children.Add(CreateDoubleAnimation(center.Y, _animationDuration, _rightThumb, nameof(ImageCropperThumb.Y), true)); + } + else + { + _rightThumb.X = _endX; + _rightThumb.Y = center.Y; + } + } + + if (_upperLeftThumb != null) + { + if (animate) + { + storyboard?.Children.Add(CreateDoubleAnimation(_startX, _animationDuration, _upperLeftThumb, nameof(ImageCropperThumb.X), true)); + storyboard?.Children.Add(CreateDoubleAnimation(_startY, _animationDuration, _upperLeftThumb, nameof(ImageCropperThumb.Y), true)); + } + else + { + _upperLeftThumb.X = _startX; + _upperLeftThumb.Y = _startY; + } + } + + if (_upperRightThumb != null) + { + if (animate) + { + storyboard?.Children.Add(CreateDoubleAnimation(_endX, _animationDuration, _upperRightThumb, nameof(ImageCropperThumb.X), true)); + storyboard?.Children.Add(CreateDoubleAnimation(_startY, _animationDuration, _upperRightThumb, nameof(ImageCropperThumb.Y), true)); + } + else + { + _upperRightThumb.X = _endX; + _upperRightThumb.Y = _startY; + } + } + + if (_lowerLeftThumb != null) + { + if (animate) + { + storyboard?.Children.Add(CreateDoubleAnimation(_startX, _animationDuration, _lowerLeftThumb, nameof(ImageCropperThumb.X), true)); + storyboard?.Children.Add(CreateDoubleAnimation(_endY, _animationDuration, _lowerLeftThumb, nameof(ImageCropperThumb.Y), true)); + } + else + { + _lowerLeftThumb.X = _startX; + _lowerLeftThumb.Y = _endY; + } + } + + if (_lowerRigthThumb != null) + { + if (animate) + { + storyboard?.Children.Add(CreateDoubleAnimation(_endX, _animationDuration, _lowerRigthThumb, nameof(ImageCropperThumb.X), true)); + storyboard?.Children.Add(CreateDoubleAnimation(_endY, _animationDuration, _lowerRigthThumb, nameof(ImageCropperThumb.Y), true)); + } + else + { + _lowerRigthThumb.X = _endX; + _lowerRigthThumb.Y = _endY; + } + } + + if (animate) + { + storyboard?.Begin(); + } + } + #nullable restore + + /// + /// Update crop shape. + /// + private void UpdateCropShape() + { + _maskAreaGeometryGroup.Children.Clear(); + _outerGeometry = new RectangleGeometry(); + switch (CropShape) + { + case CropShape.Rectangular: + _innerGeometry = new RectangleGeometry(); + _overlayGeometry = new RectangleGeometry(); + break; + case CropShape.Circular: + _innerGeometry = new EllipseGeometry(); + _overlayGeometry = new EllipseGeometry(); + break; + } + + _maskAreaGeometryGroup.Children.Add(_outerGeometry); + _maskAreaGeometryGroup.Children.Add(_innerGeometry); + if (_overlayAreaPath != null) + { + _overlayAreaPath.Data = _overlayGeometry; + } + } + + /// + /// Update the mask layer. + /// + private void UpdateMaskArea(bool animate = false) + { + if (_layoutGrid == null || _maskAreaGeometryGroup.Children.Count < 2) + { + return; + } + + _outerGeometry.Rect = new Rect(-_layoutGrid.Padding.Left, -_layoutGrid.Padding.Top, _layoutGrid.ActualWidth, _layoutGrid.ActualHeight); + + switch (CropShape) + { + case CropShape.Rectangular: + var updateRectangleGeometry = (RectangleGeometry rectangleGeometry) => + { + var to = new Point(_startX, _startY).ToRect(new Point(_endX, _endY)); + if (animate) + { + var storyboard = new Storyboard(); + storyboard.Children.Add(CreateRectangleAnimation(to, _animationDuration, rectangleGeometry, true)); + storyboard.Begin(); + } + else + { + rectangleGeometry.Rect = to; + } + }; + if (_innerGeometry is RectangleGeometry innerRectangleGeometry) + { + updateRectangleGeometry(innerRectangleGeometry); + } + if (_overlayGeometry is RectangleGeometry overlayRectangleGeometry) + { + updateRectangleGeometry(overlayRectangleGeometry); + } + + break; + case CropShape.Circular: + var updateEllipseGeometry = (EllipseGeometry ellipseGeometry) => + { + var center = new Point(((_endX - _startX) / 2) + _startX, ((_endY - _startY) / 2) + _startY); + var radiusX = (_endX - _startX) / 2; + var radiusY = (_endY - _startY) / 2; + if (animate) + { + var storyboard = new Storyboard(); + storyboard.Children.Add(CreatePointAnimation(center, _animationDuration, ellipseGeometry, nameof(EllipseGeometry.Center), true)); + storyboard.Children.Add(CreateDoubleAnimation(radiusX, _animationDuration, ellipseGeometry, nameof(EllipseGeometry.RadiusX), true)); + storyboard.Children.Add(CreateDoubleAnimation(radiusY, _animationDuration, ellipseGeometry, nameof(EllipseGeometry.RadiusY), true)); + storyboard.Begin(); + } + else + { + ellipseGeometry.Center = center; + ellipseGeometry.RadiusX = radiusX; + ellipseGeometry.RadiusY = radiusY; + } + }; + if (_innerGeometry is EllipseGeometry innerEllipseGeometry) + { + updateEllipseGeometry(innerEllipseGeometry); + } + if (_overlayGeometry is EllipseGeometry overlayEllipseGeometry) + { + updateEllipseGeometry(overlayEllipseGeometry); + } + + break; + } + + _layoutGrid.Clip = new RectangleGeometry + { + Rect = new Rect(0, 0, _layoutGrid.ActualWidth, _layoutGrid.ActualHeight) + }; + } + + /// + /// Update image aspect ratio. + /// + private bool TryUpdateAspectRatio() + { + if (KeepAspectRatio is false || Source is null || IsValidRect(_restrictedSelectRect) is false) + { + return false; + } + + var center = SelectionAreaCenter; + var restrictedMinLength = MinCroppedPixelLength * _imageTransform.ScaleX; + var maxSelectedLength = Math.Max(_endX - _startX, _endY - _startY); + var viewRect = new Rect(center.X - (maxSelectedLength / 2), center.Y - (maxSelectedLength / 2), maxSelectedLength, maxSelectedLength); + + var uniformSelectedRect = GetUniformRect(viewRect, ActualAspectRatio); + if (uniformSelectedRect.Width > _restrictedSelectRect.Width || uniformSelectedRect.Height > _restrictedSelectRect.Height) + { + uniformSelectedRect = GetUniformRect(_restrictedSelectRect, ActualAspectRatio); + } + + // If selection area is smaller than allowed. + if (uniformSelectedRect.Width < restrictedMinLength || uniformSelectedRect.Height < restrictedMinLength) + { + // Scale selection area to fit. + var scale = restrictedMinLength / Math.Min(uniformSelectedRect.Width, uniformSelectedRect.Height); + uniformSelectedRect.Width *= scale; + uniformSelectedRect.Height *= scale; + + // If selection area is larger than allowed. + if (uniformSelectedRect.Width > _restrictedSelectRect.Width || uniformSelectedRect.Height > _restrictedSelectRect.Height) + { + // Sentinal value. Equivelant to setting KeepAspectRatio to false. Causes AspectRatio to be recalculated. + AspectRatio = -1; + return false; + } + } + + // Fix positioning + if (_restrictedSelectRect.X > uniformSelectedRect.X) + { + uniformSelectedRect.X += _restrictedSelectRect.X - uniformSelectedRect.X; + } + + if (_restrictedSelectRect.Y > uniformSelectedRect.Y) + { + uniformSelectedRect.Y += _restrictedSelectRect.Y - uniformSelectedRect.Y; + } + + // Fix size + if ((_restrictedSelectRect.X + _restrictedSelectRect.Width) < (uniformSelectedRect.X + uniformSelectedRect.Width)) + { + uniformSelectedRect.X += (_restrictedSelectRect.X + _restrictedSelectRect.Width) - (uniformSelectedRect.X + uniformSelectedRect.Width); + } + + if ((_restrictedSelectRect.Y + _restrictedSelectRect.Height) < (uniformSelectedRect.Y + uniformSelectedRect.Height)) + { + uniformSelectedRect.Y += (_restrictedSelectRect.Y + _restrictedSelectRect.Height) - (uniformSelectedRect.Y + uniformSelectedRect.Height); + } + + // Apply transformation + var croppedRect = _inverseImageTransform.TransformBounds(uniformSelectedRect); + croppedRect.Intersect(_restrictedCropRect); + _currentCroppedRect = croppedRect; + + return true; + } + + /// + /// Update the visibility of the thumbs. + /// + private void UpdateThumbsVisibility() + { + var cornerThumbsVisibility = Visibility.Visible; + var otherThumbsVisibility = Visibility.Visible; + switch (ThumbPlacement) + { + case ThumbPlacement.All: + break; + case ThumbPlacement.Corners: + otherThumbsVisibility = Visibility.Collapsed; + break; + } + + switch (CropShape) + { + case CropShape.Rectangular: + break; + case CropShape.Circular: + cornerThumbsVisibility = Visibility.Collapsed; + otherThumbsVisibility = Visibility.Visible; + break; + } + + if (Source == null) + { + cornerThumbsVisibility = otherThumbsVisibility = Visibility.Collapsed; + } + + if (_topThumb != null) + { + _topThumb.Visibility = otherThumbsVisibility; + } + + if (_bottomThumb != null) + { + _bottomThumb.Visibility = otherThumbsVisibility; + } + + if (_leftThumb != null) + { + _leftThumb.Visibility = otherThumbsVisibility; + } + + if (_rightThumb != null) + { + _rightThumb.Visibility = otherThumbsVisibility; + } + + if (_upperLeftThumb != null) + { + _upperLeftThumb.Visibility = cornerThumbsVisibility; + } + + if (_upperRightThumb != null) + { + _upperRightThumb.Visibility = cornerThumbsVisibility; + } + + if (_lowerLeftThumb != null) + { + _lowerLeftThumb.Visibility = cornerThumbsVisibility; + } + + if (_lowerRigthThumb != null) + { + _lowerRigthThumb.Visibility = cornerThumbsVisibility; + } + } + + /// + /// Gets a value that indicates the center of the visible selection rectangle. + /// + private Point SelectionAreaCenter => new Point(((_endX - _startX) / 2) + _startX, ((_endY - _startY) / 2) + _startY); +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Properties.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Properties.cs new file mode 100644 index 000000000..dafeca090 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.Properties.cs @@ -0,0 +1,210 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#if !WINAPPSDK +using Windows.UI.Xaml.Media.Imaging; +#endif + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// The control allows user to crop image freely. +/// +public partial class ImageCropper +{ + /// + /// Gets or sets the minimum cropped length(in pixel). + /// + public double MinCroppedPixelLength { get; set; } = 40; + + /// + /// Gets or sets the minimum selectable length. + /// + public double MinSelectedLength { get; set; } = 40; + + /// + /// Gets the current cropped region. + /// + public Rect CroppedRegion => _currentCroppedRect; + + private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var target = (ImageCropper)d; + if (e.NewValue is WriteableBitmap bitmap) + { + if (bitmap.PixelWidth < target.MinCropSize.Width || bitmap.PixelHeight < target.MinCropSize.Height) + { + target.Source = null; + throw new ArgumentException("The resolution of the image is too small!"); + } + } + + target.InvalidateMeasure(); + target.UpdateCropShape(); + target.InitImageLayout(); + } + + private static void OnAspectRatioChanged( + DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var target = (ImageCropper)d; + + if (target.TryUpdateAspectRatio()) + { + if (target.TryUpdateImageLayout(true)) + { + target.UpdateSelectionThumbs(true); + target.UpdateMaskArea(true); + } + } + } + + private static void OnCropShapeChanged( + DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var target = (ImageCropper)d; + target.UpdateCropShape(); + target.UpdateThumbsVisibility(); + + if (target.TryUpdateAspectRatio()) + { + if (target.TryUpdateImageLayout()) + { + target.UpdateSelectionThumbs(); + } + } + + target.UpdateMaskArea(); + } + + private static void OnThumbPlacementChanged( + DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var target = (ImageCropper)d; + target.UpdateThumbsVisibility(); + } + + #nullable enable + /// + /// Gets or sets the source of the cropped image. + /// + public WriteableBitmap? Source + { + get { return (WriteableBitmap)GetValue(SourceProperty); } + set { SetValue(SourceProperty, value); } + } + #nullable restore + + /// + /// Gets or sets the aspect ratio of the cropped image, the default value is null. + /// Only works when = . + /// + public double? AspectRatio + { + get { return (double?)GetValue(AspectRatioProperty); } + set { SetValue(AspectRatioProperty, value); } + } + + /// + /// Gets or sets the shape to use when cropping. + /// + public CropShape CropShape + { + get { return (CropShape)GetValue(CropShapeProperty); } + set { SetValue(CropShapeProperty, value); } + } + + /// + /// Gets or sets the mask on the cropped image. + /// + public Brush Mask + { + get { return (Brush)GetValue(MaskProperty); } + set { SetValue(MaskProperty, value); } + } + + /// + /// Gets or sets the overlay on the cropped image. + /// + public Brush Overlay + { + get { return (Brush)GetValue(OverlayProperty); } + set { SetValue(OverlayProperty, value); } + } + + /// + /// Gets or sets a value for the style to use for the primary thumbs of the ImageCropper. + /// + public Style PrimaryThumbStyle + { + get { return (Style)GetValue(PrimaryThumbStyleProperty); } + set { SetValue(PrimaryThumbStyleProperty, value); } + } + + /// + /// Gets or sets a value for the style to use for the secondary thumbs of the ImageCropper. + /// + public Style SecondaryThumbStyle + { + get { return (Style)GetValue(SecondaryThumbStyleProperty); } + set { SetValue(SecondaryThumbStyleProperty, value); } + } + + /// + /// Gets or sets a value for thumb placement. + /// + public ThumbPlacement ThumbPlacement + { + get { return (ThumbPlacement)GetValue(ThumbPlacementProperty); } + set { SetValue(ThumbPlacementProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty AspectRatioProperty = + DependencyProperty.Register(nameof(AspectRatio), typeof(double?), typeof(ImageCropper), new PropertyMetadata(null, OnAspectRatioChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty SourceProperty = + DependencyProperty.Register(nameof(Source), typeof(WriteableBitmap), typeof(ImageCropper), new PropertyMetadata(null, OnSourceChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty CropShapeProperty = + DependencyProperty.Register(nameof(CropShape), typeof(CropShape), typeof(ImageCropper), new PropertyMetadata(default(CropShape), OnCropShapeChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty MaskProperty = + DependencyProperty.Register(nameof(Mask), typeof(Brush), typeof(ImageCropper), new PropertyMetadata(default(Brush))); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty OverlayProperty = + DependencyProperty.Register(nameof(Overlay), typeof(Brush), typeof(ImageCropper), new PropertyMetadata(default(Brush))); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty PrimaryThumbStyleProperty = + DependencyProperty.Register(nameof(PrimaryThumbStyle), typeof(Style), typeof(ImageCropper), new PropertyMetadata(default(Style))); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty SecondaryThumbStyleProperty = + DependencyProperty.Register(nameof(SecondaryThumbStyle), typeof(Style), typeof(ImageCropper), new PropertyMetadata(default(Style))); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ThumbPlacementProperty = + DependencyProperty.Register(nameof(ThumbPlacement), typeof(ThumbPlacement), typeof(ImageCropper), new PropertyMetadata(default(ThumbPlacement), OnThumbPlacementChanged)); +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.cs new file mode 100644 index 000000000..0f01bbd4c --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.cs @@ -0,0 +1,474 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#if WINAPPSDK +using Path = Microsoft.UI.Xaml.Shapes.Path; +// ReSharper disable CompareOfFloatsByEqualityOperator +#else +using Windows.UI.Xaml.Media.Imaging; +using Path = Windows.UI.Xaml.Shapes.Path; +#endif + +#nullable enable +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// The control allows user to crop image freely. +/// +[TemplatePart(Name = LayoutGridName, Type = typeof(Grid))] +[TemplatePart(Name = ImageCanvasPartName, Type = typeof(Canvas))] +[TemplatePart(Name = SourceImagePartName, Type = typeof(Image))] +[TemplatePart(Name = MaskAreaPathPartName, Type = typeof(Path))] +[TemplatePart(Name = OverlayAreaPathPartName, Type = typeof(Path))] +[TemplatePart(Name = TopThumbPartName, Type = typeof(ImageCropperThumb))] +[TemplatePart(Name = BottomThumbPartName, Type = typeof(ImageCropperThumb))] +[TemplatePart(Name = LeftThumbPartName, Type = typeof(ImageCropperThumb))] +[TemplatePart(Name = RightThumbPartName, Type = typeof(ImageCropperThumb))] +[TemplatePart(Name = UpperLeftThumbPartName, Type = typeof(ImageCropperThumb))] +[TemplatePart(Name = UpperRightThumbPartName, Type = typeof(ImageCropperThumb))] +[TemplatePart(Name = LowerLeftThumbPartName, Type = typeof(ImageCropperThumb))] +[TemplatePart(Name = LowerRightThumbPartName, Type = typeof(ImageCropperThumb))] +public partial class ImageCropper : Control +{ + private readonly CompositeTransform _imageTransform = new CompositeTransform(); + private readonly CompositeTransform _inverseImageTransform = new CompositeTransform(); + private readonly GeometryGroup _maskAreaGeometryGroup = new GeometryGroup { FillRule = FillRule.EvenOdd }; + + private Grid? _layoutGrid; + private Canvas? _imageCanvas; + private Image? _sourceImage; + private Path? _maskAreaPath; + private Path? _overlayAreaPath; + private ImageCropperThumb? _topThumb; + private ImageCropperThumb? _bottomThumb; + private ImageCropperThumb? _leftThumb; + private ImageCropperThumb? _rightThumb; + private ImageCropperThumb? _upperLeftThumb; + private ImageCropperThumb? _upperRightThumb; + private ImageCropperThumb? _lowerLeftThumb; + private ImageCropperThumb? _lowerRigthThumb; + + // Selection area + private double _startX; + private double _startY; + private double _endX; + private double _endY; + + private Rect _currentCroppedRect = Rect.Empty; + private Rect _restrictedCropRect = Rect.Empty; + private Rect _restrictedSelectRect = Rect.Empty; + private RectangleGeometry _outerGeometry; + private Geometry _innerGeometry; + private Geometry _overlayGeometry; + private TimeSpan _animationDuration = TimeSpan.FromSeconds(0.3); + + /// + /// Initializes a new instance of the class. + /// +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + public ImageCropper() +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + { + DefaultStyleKey = typeof(ImageCropper); + } + + private Rect CanvasRect => new Rect(0, 0, _imageCanvas?.ActualWidth ?? 0, _imageCanvas?.ActualHeight ?? 0); + + /// + /// Gets a value indicating whether the user-provided is valid and should be kept during manipulation of the image cropper. + /// + private bool KeepAspectRatio => ActualAspectRatio > 0; + + /// + /// Gets the internally used aspect ratio, rather than the user-provided value. Adjusted to handle crop shape and invalid values. + /// + private double ActualAspectRatio + { + get + { + var aspectRatio = CropShape switch + { + CropShape.Rectangular => AspectRatio, + CropShape.Circular => 1, + _ => AspectRatio, + }; + + if (aspectRatio is not null && aspectRatio > 0) + { + // When not null or 0. + return aspectRatio.Value; + } + else + { + // Fallback to sentinal value. + // Used to indicate aspect ratio should be discarded and reset during manipulation of the image cropper. + return -1; + } + } + } + + /// + /// Gets the minimum cropped size. + /// + private Size MinCropSize + { + get + { + var aspectRatio = KeepAspectRatio ? ActualAspectRatio : 1; + var size = new Size(MinCroppedPixelLength, MinCroppedPixelLength); + if (aspectRatio >= 1) + { + size.Width = size.Height * aspectRatio; + } + else + { + size.Height = size.Width / aspectRatio; + } + + return size; + } + } + + /// + /// Gets the minimum selectable size. + /// + private Size MinSelectSize + { + get + { + var realMinSelectSize = _imageTransform.TransformBounds(MinCropSize.ToRect()); + var minLength = Math.Min(realMinSelectSize.Width, realMinSelectSize.Height); + if (minLength < MinSelectedLength) + { + var aspectRatio = KeepAspectRatio ? ActualAspectRatio : 1; + var minSelectSize = new Size(MinSelectedLength, MinSelectedLength); + if (aspectRatio >= 1) + { + minSelectSize.Width = minSelectSize.Height * aspectRatio; + } + else + { + minSelectSize.Height = minSelectSize.Width / aspectRatio; + } + + return minSelectSize; + } + + return new Size(realMinSelectSize.Width, realMinSelectSize.Height); + } + } + + /// + protected override void OnApplyTemplate() + { + UnhookEvents(); + _layoutGrid = GetTemplateChild(LayoutGridName) as Grid; + _imageCanvas = GetTemplateChild(ImageCanvasPartName) as Canvas; + _sourceImage = GetTemplateChild(SourceImagePartName) as Image; + _maskAreaPath = GetTemplateChild(MaskAreaPathPartName) as Path; + _overlayAreaPath = GetTemplateChild(OverlayAreaPathPartName) as Path; + _topThumb = GetTemplateChild(TopThumbPartName) as ImageCropperThumb; + _bottomThumb = GetTemplateChild(BottomThumbPartName) as ImageCropperThumb; + _leftThumb = GetTemplateChild(LeftThumbPartName) as ImageCropperThumb; + _rightThumb = GetTemplateChild(RightThumbPartName) as ImageCropperThumb; + _upperLeftThumb = GetTemplateChild(UpperLeftThumbPartName) as ImageCropperThumb; + _upperRightThumb = GetTemplateChild(UpperRightThumbPartName) as ImageCropperThumb; + _lowerLeftThumb = GetTemplateChild(LowerLeftThumbPartName) as ImageCropperThumb; + _lowerRigthThumb = GetTemplateChild(LowerRightThumbPartName) as ImageCropperThumb; + HookUpEvents(); + UpdateThumbsVisibility(); + } + + private void HookUpEvents() + { + if (_imageCanvas != null) + { + _imageCanvas.SizeChanged += ImageCanvas_SizeChanged; + } + + if (_sourceImage != null) + { + _sourceImage.ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY; + _sourceImage.ManipulationDelta += SourceImage_ManipulationDelta; + } + + if (_maskAreaPath != null) + { + _maskAreaPath.Data = _maskAreaGeometryGroup; + } + + if (_overlayAreaPath != null) + { + _overlayAreaPath.Data = _overlayGeometry; + } + + if (_topThumb != null) + { + _topThumb.Position = ThumbPosition.Top; + _topThumb.ManipulationDelta += ImageCropperThumb_ManipulationDelta; + _topThumb.ManipulationCompleted += ImageCropperThumb_ManipulationCompleted; + _topThumb.KeyDown += ImageCropperThumb_KeyDown; + _topThumb.KeyUp += ImageCropperThumb_KeyUp; + } + + if (_bottomThumb != null) + { + _bottomThumb.Position = ThumbPosition.Bottom; + _bottomThumb.ManipulationDelta += ImageCropperThumb_ManipulationDelta; + _bottomThumb.ManipulationCompleted += ImageCropperThumb_ManipulationCompleted; + _bottomThumb.KeyDown += ImageCropperThumb_KeyDown; + _bottomThumb.KeyUp += ImageCropperThumb_KeyUp; + } + + if (_leftThumb != null) + { + _leftThumb.Position = ThumbPosition.Left; + _leftThumb.ManipulationDelta += ImageCropperThumb_ManipulationDelta; + _leftThumb.ManipulationCompleted += ImageCropperThumb_ManipulationCompleted; + _leftThumb.KeyDown += ImageCropperThumb_KeyDown; + _leftThumb.KeyUp += ImageCropperThumb_KeyUp; + } + + if (_rightThumb != null) + { + _rightThumb.Position = ThumbPosition.Right; + _rightThumb.ManipulationDelta += ImageCropperThumb_ManipulationDelta; + _rightThumb.ManipulationCompleted += ImageCropperThumb_ManipulationCompleted; + _rightThumb.KeyDown += ImageCropperThumb_KeyDown; + _rightThumb.KeyUp += ImageCropperThumb_KeyUp; + } + + if (_upperLeftThumb != null) + { + _upperLeftThumb.Position = ThumbPosition.UpperLeft; + _upperLeftThumb.ManipulationDelta += ImageCropperThumb_ManipulationDelta; + _upperLeftThumb.ManipulationCompleted += ImageCropperThumb_ManipulationCompleted; + _upperLeftThumb.KeyDown += ImageCropperThumb_KeyDown; + _upperLeftThumb.KeyUp += ImageCropperThumb_KeyUp; + } + + if (_upperRightThumb != null) + { + _upperRightThumb.Position = ThumbPosition.UpperRight; + _upperRightThumb.ManipulationDelta += ImageCropperThumb_ManipulationDelta; + _upperRightThumb.ManipulationCompleted += ImageCropperThumb_ManipulationCompleted; + _upperRightThumb.KeyDown += ImageCropperThumb_KeyDown; + _upperRightThumb.KeyUp += ImageCropperThumb_KeyUp; + } + + if (_lowerLeftThumb != null) + { + _lowerLeftThumb.Position = ThumbPosition.LowerLeft; + _lowerLeftThumb.ManipulationDelta += ImageCropperThumb_ManipulationDelta; + _lowerLeftThumb.ManipulationCompleted += ImageCropperThumb_ManipulationCompleted; + _lowerLeftThumb.KeyDown += ImageCropperThumb_KeyDown; + _lowerLeftThumb.KeyUp += ImageCropperThumb_KeyUp; + } + + if (_lowerRigthThumb != null) + { + _lowerRigthThumb.Position = ThumbPosition.LowerRight; + _lowerRigthThumb.ManipulationDelta += ImageCropperThumb_ManipulationDelta; + _lowerRigthThumb.ManipulationCompleted += ImageCropperThumb_ManipulationCompleted; + _lowerRigthThumb.KeyDown += ImageCropperThumb_KeyDown; + _lowerRigthThumb.KeyUp += ImageCropperThumb_KeyUp; + } + } + + private void UnhookEvents() + { + if (_imageCanvas != null) + { + _imageCanvas.SizeChanged -= ImageCanvas_SizeChanged; + } + + if (_sourceImage != null) + { + _sourceImage.ManipulationDelta -= SourceImage_ManipulationDelta; + } + + if (_maskAreaPath != null) + { + _maskAreaPath.Data = null; + } + + if (_topThumb != null) + { + _topThumb.ManipulationDelta -= ImageCropperThumb_ManipulationDelta; + _topThumb.ManipulationCompleted -= ImageCropperThumb_ManipulationCompleted; + _topThumb.KeyDown -= ImageCropperThumb_KeyDown; + _topThumb.KeyUp -= ImageCropperThumb_KeyUp; + } + + if (_bottomThumb != null) + { + _bottomThumb.ManipulationDelta -= ImageCropperThumb_ManipulationDelta; + _bottomThumb.ManipulationCompleted -= ImageCropperThumb_ManipulationCompleted; + _bottomThumb.KeyDown -= ImageCropperThumb_KeyDown; + _bottomThumb.KeyUp -= ImageCropperThumb_KeyUp; + } + + if (_leftThumb != null) + { + _leftThumb.ManipulationDelta -= ImageCropperThumb_ManipulationDelta; + _leftThumb.ManipulationCompleted += ImageCropperThumb_ManipulationCompleted; + _leftThumb.KeyDown -= ImageCropperThumb_KeyDown; + _leftThumb.KeyUp -= ImageCropperThumb_KeyUp; + } + + if (_rightThumb != null) + { + _rightThumb.ManipulationDelta -= ImageCropperThumb_ManipulationDelta; + _rightThumb.ManipulationCompleted -= ImageCropperThumb_ManipulationCompleted; + _rightThumb.KeyDown -= ImageCropperThumb_KeyDown; + _rightThumb.KeyUp -= ImageCropperThumb_KeyUp; + } + + if (_upperLeftThumb != null) + { + _upperLeftThumb.ManipulationDelta -= ImageCropperThumb_ManipulationDelta; + _upperLeftThumb.ManipulationCompleted -= ImageCropperThumb_ManipulationCompleted; + _upperLeftThumb.KeyDown -= ImageCropperThumb_KeyDown; + _upperLeftThumb.KeyUp -= ImageCropperThumb_KeyUp; + } + + if (_upperRightThumb != null) + { + _upperRightThumb.ManipulationDelta -= ImageCropperThumb_ManipulationDelta; + _upperRightThumb.ManipulationCompleted -= ImageCropperThumb_ManipulationCompleted; + _upperRightThumb.KeyDown -= ImageCropperThumb_KeyDown; + _upperRightThumb.KeyUp -= ImageCropperThumb_KeyUp; + } + + if (_lowerLeftThumb != null) + { + _lowerLeftThumb.ManipulationDelta -= ImageCropperThumb_ManipulationDelta; + _lowerLeftThumb.ManipulationCompleted -= ImageCropperThumb_ManipulationCompleted; + _lowerLeftThumb.KeyDown -= ImageCropperThumb_KeyDown; + _lowerLeftThumb.KeyUp -= ImageCropperThumb_KeyUp; + } + + if (_lowerRigthThumb != null) + { + _lowerRigthThumb.ManipulationDelta -= ImageCropperThumb_ManipulationDelta; + _lowerRigthThumb.ManipulationCompleted -= ImageCropperThumb_ManipulationCompleted; + _lowerRigthThumb.KeyDown -= ImageCropperThumb_KeyDown; + _lowerRigthThumb.KeyUp -= ImageCropperThumb_KeyUp; + } + } + + /// + protected override Size MeasureOverride(Size availableSize) + { + if (Source == null || Source.PixelWidth == 0 || Source.PixelHeight == 0) + { + return base.MeasureOverride(availableSize); + } + + if (double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height)) + { + if (!double.IsInfinity(availableSize.Width)) + { + availableSize.Height = availableSize.Width / Source.PixelWidth * Source.PixelHeight; + } + else if (!double.IsInfinity(availableSize.Height)) + { + availableSize.Width = availableSize.Height / Source.PixelHeight * Source.PixelWidth; + } + else + { + availableSize.Width = Source.PixelWidth; + availableSize.Height = Source.PixelHeight; + } + + base.MeasureOverride(availableSize); + return availableSize; + } + + return base.MeasureOverride(availableSize); + } + + /// + /// Load an image from a file. + /// + /// The image file. + /// Task + public async Task LoadImageFromFile(StorageFile imageFile) + { + var writeableBitmap = new WriteableBitmap(1, 1); + using (var stream = await imageFile.OpenReadAsync()) + { + await writeableBitmap.SetSourceAsync(stream); + } + + Source = writeableBitmap; + } + + /// + /// Saves the cropped image to a stream with the specified format. + /// + /// The target stream. + /// the specified format. + /// Whether to always keep rectangular output. + /// Task + public async Task SaveAsync(IRandomAccessStream stream, BitmapFileFormat bitmapFileFormat, bool keepRectangularOutput = false) + { + if (Source == null) + { + return; + } + + if (keepRectangularOutput || CropShape == CropShape.Rectangular) + { + await CropImageAsync(Source, stream, _currentCroppedRect, bitmapFileFormat); + return; + } + + await CropImageWithShapeAsync(Source, stream, _currentCroppedRect, bitmapFileFormat, CropShape); + } + + /// + /// Reset the cropped area. + /// + public void Reset() + { + InitImageLayout(true); + } + + /// + /// Tries to set a new value for the cropped region, returns true if it succeeded, false if the region is invalid + /// + /// The new cropped region. + /// bool + public bool TrySetCroppedRegion(Rect rect) + { + // Reject regions smaller than the minimum size + if (rect.Width < MinCropSize.Width || rect.Height < MinCropSize.Height) + { + return false; + } + + // Reject regions that are not contained in the original picture + if (rect.Left < _restrictedCropRect.Left || rect.Top < _restrictedCropRect.Top || rect.Right > _restrictedCropRect.Right || rect.Bottom > _restrictedCropRect.Bottom) + { + return false; + } + + // If an aspect ratio is set, reject regions that don't respect it + // If cropping a circle, reject regions where the aspect ratio is not 1 + if (KeepAspectRatio && ActualAspectRatio != rect.Width / rect.Height) + { + return false; + } + + _currentCroppedRect = rect; + if (TryUpdateImageLayout(true)) + { + UpdateSelectionThumbs(true); + UpdateMaskArea(true); + } + + return true; + } +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.xaml b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.xaml new file mode 100644 index 000000000..01db97744 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropper.xaml @@ -0,0 +1,74 @@ + + + + + + + + + + diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropperThumb.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropperThumb.cs new file mode 100644 index 000000000..0b04df02c --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropperThumb.cs @@ -0,0 +1,137 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ReSharper disable PartialTypeWithSinglePart +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// The control is used for . +/// +public partial class ImageCropperThumb : Control +{ + private readonly TranslateTransform _layoutTransform = new(); + internal const string NormalState = "Normal"; + internal const string PointerOverState = "PointerOver"; + internal const string PressedState = "Pressed"; + internal const string DisabledState = "Disabled"; + internal ThumbPosition Position { get; set; } + + /// + /// Gets or sets the X coordinate of the ImageCropperThumb. + /// + public double X + { + get { return (double)GetValue(XProperty); } + set { SetValue(XProperty, value); } + } + + /// + /// Gets or sets the Y coordinate of the ImageCropperThumb. + /// + public double Y + { + get { return (double)GetValue(YProperty); } + set { SetValue(YProperty, value); } + } + + /// + /// Initializes a new instance of the class. + /// + public ImageCropperThumb() + { + DefaultStyleKey = typeof(ImageCropperThumb); + RenderTransform = _layoutTransform; + ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY; + SizeChanged += ImageCropperThumb_SizeChanged; + } + + protected override void OnApplyTemplate() + { + PointerEntered -= Control_PointerEntered; + PointerExited -= Control_PointerExited; + PointerCaptureLost -= Control_PointerCaptureLost; + PointerCanceled -= Control_PointerCanceled; + + PointerEntered += Control_PointerEntered; + PointerExited += Control_PointerExited; + PointerCaptureLost += Control_PointerCaptureLost; + PointerCanceled += Control_PointerCanceled; + } + + private void ImageCropperThumb_SizeChanged(object sender, SizeChangedEventArgs e) + { + UpdatePosition(); + } + + private void UpdatePosition() + { + if (_layoutTransform != null) + { + _layoutTransform.X = X - (this.ActualWidth / 2); + _layoutTransform.Y = Y - (this.ActualHeight / 2); + } + } + + private static void OnXChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var target = (ImageCropperThumb)d; + target.UpdatePosition(); + } + + + + private static void OnYChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var target = (ImageCropperThumb)d; + target.UpdatePosition(); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty XProperty = + DependencyProperty.Register(nameof(X), typeof(double), typeof(ImageCropperThumb), new PropertyMetadata(0d, OnXChanged)); + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty YProperty = + DependencyProperty.Register(nameof(Y), typeof(double), typeof(ImageCropperThumb), new PropertyMetadata(0d, OnYChanged)); + + public void Control_PointerEntered(object sender, PointerRoutedEventArgs e) + { + base.OnPointerEntered(e); + VisualStateManager.GoToState(this, PointerOverState, true); + } + + public void Control_PointerExited(object sender, PointerRoutedEventArgs e) + { + base.OnPointerExited(e); + VisualStateManager.GoToState(this, NormalState, true); + } + + private void Control_PointerCaptureLost(object sender, PointerRoutedEventArgs e) + { + base.OnPointerCaptureLost(e); + VisualStateManager.GoToState(this, NormalState, true); + } + + private void Control_PointerCanceled(object sender, PointerRoutedEventArgs e) + { + base.OnPointerCanceled(e); + VisualStateManager.GoToState(this, NormalState, true); + } + + protected override void OnPointerPressed(PointerRoutedEventArgs e) + { + base.OnPointerPressed(e); + VisualStateManager.GoToState(this, PressedState, true); + } + + protected override void OnPointerReleased(PointerRoutedEventArgs e) + { + base.OnPointerReleased(e); + VisualStateManager.GoToState(this, NormalState, true); + } +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropperThumb.xaml b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropperThumb.xaml new file mode 100644 index 000000000..6a5398703 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ImageCropperThumb.xaml @@ -0,0 +1,67 @@ + + + + + + + + + + diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ReadMe.md b/Hi3Helper.CommunityToolkit/ImageCropper/ReadMe.md new file mode 100644 index 000000000..07942198d --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ReadMe.md @@ -0,0 +1,38 @@ + +# Windows Community Toolkit - ImageCropper + +This package is part of the [Windows Community Toolkit](https://aka.ms/toolkit/windows) from the [.NET Foundation](https://dotnetfoundation.org). + +## Package Contents + +This package contains the following controls in the `CommunityToolkit.WinUI.Controls` namespace: + +- ImageCropper + +## Which Package is for me? + +If you're developing with _UWP/WinUI 2 or Uno.UI_ you should be using the `CommunityToolkit.Uwp.Controls.ImageCropper` package. + +If you're developing with _WindowsAppSDK/WinUI 3 or Uno.WinUI_ you should be using the `CommunityToolkit.WinUI.Controls.ImageCropper` package. + +## WinUI Resources (UWP) + +For UWP projects, the WinUI 2 reference requires you include the WinUI XAML Resources in your App.xaml file: + +```xml + + + +``` + +See [Getting Started in WinUI 2](https://learn.microsoft.com/windows/apps/winui/winui2/getting-started) for more information. + +## Documentation + +Further documentation about these components can be found at: https://aka.ms/windowstoolkitdocs + +## License + +MIT + +See License.md in package for more details. diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/Themes/Generic.xaml b/Hi3Helper.CommunityToolkit/ImageCropper/Themes/Generic.xaml new file mode 100644 index 000000000..d39cc8fd0 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/Themes/Generic.xaml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ThumbPlacement.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ThumbPlacement.cs new file mode 100644 index 000000000..021365593 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ThumbPlacement.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// Thumb placement enumeration. +/// Default is +/// +public enum ThumbPlacement +{ + /// + /// Shows the thumbs in all four corners, top, bottom, left and right. + /// + All, + + /// + /// Shows the thumbs in all four corners. + /// + Corners +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/ThumbPosition.cs b/Hi3Helper.CommunityToolkit/ImageCropper/ThumbPosition.cs new file mode 100644 index 000000000..0f73feada --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/ThumbPosition.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// Thumb position enumeration. +/// +internal enum ThumbPosition +{ + Top, + Bottom, + Left, + Right, + UpperLeft, + UpperRight, + LowerLeft, + LowerRight +} diff --git a/Hi3Helper.CommunityToolkit/ImageCropper/packages.lock.json b/Hi3Helper.CommunityToolkit/ImageCropper/packages.lock.json new file mode 100644 index 000000000..bbf4825d1 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/ImageCropper/packages.lock.json @@ -0,0 +1,116 @@ +{ + "version": 1, + "dependencies": { + "net9.0-windows10.0.22621": { + "CommunityToolkit.WinUI.Extensions": { + "type": "Direct", + "requested": "[8.1.240916, )", + "resolved": "8.1.240916", + "contentHash": "y8FEzoy5HF3dBkxe/pU87hlh0QACtGZhnv881x2SFq9fvDjukLQz6VpoOkjxSZjEILmYu1NoI/tYjqXlETRGlw==", + "dependencies": { + "CommunityToolkit.Common": "8.2.1", + "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", + "Microsoft.WindowsAppSDK": "1.5.240311000" + } + }, + "CommunityToolkit.WinUI.Media": { + "type": "Direct", + "requested": "[8.1.240916, )", + "resolved": "8.1.240916", + "contentHash": "ghd4u81dNf5F5knVkww9QDUy03L05TvMHPlUXKOVFER0bl8A3K6I0eYi0bUNtR8VTB7giPy8DFh50t3SJjswrA==", + "dependencies": { + "CommunityToolkit.WinUI.Animations": "8.1.240916", + "CommunityToolkit.WinUI.Extensions": "8.1.240916", + "Microsoft.Graphics.Win2D": "1.1.1", + "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", + "Microsoft.WindowsAppSDK": "1.5.240311000" + } + }, + "Microsoft.Graphics.Win2D": { + "type": "Direct", + "requested": "[1.2.1-experimental2, )", + "resolved": "1.2.1-experimental2", + "contentHash": "jcihghoq78imitWG52m5k3gkPt+GrWTU/CkujU8o2bBHJY982/MG7YpWVOsuA+RKGAn+WIm/OklR99bXZkJjUw==", + "dependencies": { + "Microsoft.WindowsAppSDK": "1.6.240531000-experimental1" + } + }, + "Microsoft.NET.ILLink.Tasks": { + "type": "Direct", + "requested": "[9.0.0-rc.2.24473.5, )", + "resolved": "9.0.0-rc.2.24473.5", + "contentHash": "mXyCgKqBIo/KOpY09FqjOU8d+LME2qowzzvBd1/viYmSpAKYfQ2w1WDOlh0FniY4xnrB/wmDiAo4frJvfCqT0w==" + }, + "Microsoft.Web.WebView2": { + "type": "Direct", + "requested": "[1.0.2783-prerelease, )", + "resolved": "1.0.2783-prerelease", + "contentHash": "sPxd49RLVnOyzplXTvdU9z6NIx0t56MridYoZhw8BOBZt564Ufi168Wfg5G0rIc5EKWmrGihLeZ1OfmjQiqWTw==" + }, + "Microsoft.Windows.CsWinRT": { + "type": "Direct", + "requested": "[2.1.5, )", + "resolved": "2.1.5", + "contentHash": "PG0uVrpPTVEhqu70YhGMTyRKZXNgygjIIwdjAmg2hhHkmm6367TafBEdzIU/TgMXy2+x5Lv/Z9MKJehwkQXEvw==" + }, + "Microsoft.Windows.SDK.BuildTools": { + "type": "Direct", + "requested": "[10.0.26100.1742, )", + "resolved": "10.0.26100.1742", + "contentHash": "ypcHjr4KEi6xQhgClnbXoANHcyyX/QsC4Rky4igs6M4GiDa+weegPo8JuV/VMxqrZCV4zlqDsp2krgkN7ReAAg==" + }, + "Microsoft.WindowsAppSDK": { + "type": "Direct", + "requested": "[1.6.240923002, )", + "resolved": "1.6.240923002", + "contentHash": "7PfOz2scXU+AAM/GYge+f6s7k3DVI+R1P8MNPZQr56GOPCGw+csvcg3S5KZg47z/o04kNvWH3GKtWT1ML9tpZw==", + "dependencies": { + "Microsoft.Web.WebView2": "1.0.2651.64", + "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" + } + }, + "CommunityToolkit.Common": { + "type": "Transitive", + "resolved": "8.2.1", + "contentHash": "LWuhy8cQKJ/MYcy3XafJ916U3gPH/YDvYoNGWyQWN11aiEKCZszzPOTJAOvBjP9yG8vHmIcCyPUt4L82OK47Iw==" + }, + "CommunityToolkit.WinUI.Animations": { + "type": "Transitive", + "resolved": "8.1.240916", + "contentHash": "jHYltimsKSSrYAij1TJC4VXzWZ6RaKCOWzkdDPenkjAcvE6lhSdX68eH6nNRIwZZC9lMpDy2Fba4uAJ1vMt9Ew==", + "dependencies": { + "CommunityToolkit.WinUI.Extensions": "8.1.240916", + "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", + "Microsoft.WindowsAppSDK": "1.5.240311000" + } + } + }, + "net9.0-windows10.0.22621/win-x64": { + "Microsoft.Graphics.Win2D": { + "type": "Direct", + "requested": "[1.2.1-experimental2, )", + "resolved": "1.2.1-experimental2", + "contentHash": "jcihghoq78imitWG52m5k3gkPt+GrWTU/CkujU8o2bBHJY982/MG7YpWVOsuA+RKGAn+WIm/OklR99bXZkJjUw==", + "dependencies": { + "Microsoft.WindowsAppSDK": "1.6.240531000-experimental1" + } + }, + "Microsoft.Web.WebView2": { + "type": "Direct", + "requested": "[1.0.2783-prerelease, )", + "resolved": "1.0.2783-prerelease", + "contentHash": "sPxd49RLVnOyzplXTvdU9z6NIx0t56MridYoZhw8BOBZt564Ufi168Wfg5G0rIc5EKWmrGihLeZ1OfmjQiqWTw==" + }, + "Microsoft.WindowsAppSDK": { + "type": "Direct", + "requested": "[1.6.240923002, )", + "resolved": "1.6.240923002", + "contentHash": "7PfOz2scXU+AAM/GYge+f6s7k3DVI+R1P8MNPZQr56GOPCGw+csvcg3S5KZg47z/o04kNvWH3GKtWT1ML9tpZw==", + "dependencies": { + "Microsoft.Web.WebView2": "1.0.2651.64", + "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" + } + } + } + } +} \ No newline at end of file diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/ControlHelper.cs b/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/ControlHelper.cs new file mode 100644 index 000000000..7beb6139b --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/ControlHelper.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; +internal static class ControlHelpers +{ + internal static bool IsXamlRootAvailable { get; } = Windows.Foundation.Metadata.ApiInformation.IsPropertyPresent("Microsoft.UI.Xaml.UIElement", nameof(UIElement.XamlRoot)); +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/CornerRadiusConverter.cs b/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/CornerRadiusConverter.cs new file mode 100644 index 000000000..9ce6bf9da --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/CornerRadiusConverter.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ReSharper disable PartialTypeWithSinglePart +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; +internal partial class CornerRadiusConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is CornerRadius cornerRadius) + { + return new CornerRadius(0, 0, cornerRadius.BottomRight, cornerRadius.BottomLeft); + } + else + { + return value; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + return value; + } +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/ResourceDictionaryExtensions.cs b/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/ResourceDictionaryExtensions.cs new file mode 100644 index 000000000..8cee3d9ed --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/ResourceDictionaryExtensions.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +// Adapted from https://github.com/rudyhuyn/XamlPlus +internal static class ResourceDictionaryExtensions +{ + /// + /// Copies the provided as a parameter into the calling dictionary, includes overwriting the source location, theme dictionaries, and merged dictionaries. + /// + /// ResourceDictionary to copy values to. + /// ResourceDictionary to copy values from. + internal static void CopyFrom(this ResourceDictionary destination, ResourceDictionary source) + { + if (source.Source != null) + { + destination.Source = source.Source; + } + else + { + // Clone theme dictionaries + if (source.ThemeDictionaries != null) + { + foreach (var theme in source.ThemeDictionaries) + { + if (theme.Value is ResourceDictionary themedResource) + { + var themeDictionary = new ResourceDictionary(); + themeDictionary.CopyFrom(themedResource); + destination.ThemeDictionaries[theme.Key] = themeDictionary; + } + else + { + destination.ThemeDictionaries[theme.Key] = theme.Value; + } + } + } + + // Clone merged dictionaries + if (source.MergedDictionaries != null) + { + foreach (var mergedResource in source.MergedDictionaries) + { + var themeDictionary = new ResourceDictionary(); + themeDictionary.CopyFrom(mergedResource); + destination.MergedDictionaries.Add(themeDictionary); + } + } + + // Clone all contents + foreach (var item in source) + { + destination[item.Key] = item.Value; + } + } + } +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/StyleExtensions.cs b/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/StyleExtensions.cs new file mode 100644 index 000000000..d678c15a3 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/Helpers/StyleExtensions.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +// Adapted from https://github.com/rudyhuyn/XamlPlus +public static class StyleExtensions +{ + // Used to distinct normal ResourceDictionary and the one we add. + private sealed class StyleExtensionResourceDictionary : ResourceDictionary + { + } + + public static ResourceDictionary GetResources(Style obj) + { + return (ResourceDictionary)obj.GetValue(ResourcesProperty); + } + + public static void SetResources(Style obj, ResourceDictionary value) + { + obj.SetValue(ResourcesProperty, value); + } + + public static readonly DependencyProperty ResourcesProperty = + DependencyProperty.RegisterAttached("Resources", typeof(ResourceDictionary), typeof(StyleExtensions), new PropertyMetadata(null, ResourcesChanged)); + + private static void ResourcesChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) + { + if (!(sender is FrameworkElement frameworkElement)) + { + return; + } + + var mergedDictionaries = frameworkElement.Resources?.MergedDictionaries; + if (mergedDictionaries == null) + { + return; + } + + var existingResourceDictionary = + mergedDictionaries.FirstOrDefault(c => c is StyleExtensionResourceDictionary); + if (existingResourceDictionary != null) + { + // Remove the existing resource dictionary + mergedDictionaries.Remove(existingResourceDictionary); + } + + if (e.NewValue is ResourceDictionary resource) + { + var clonedResources = new StyleExtensionResourceDictionary(); + clonedResources.CopyFrom(resource); + mergedDictionaries.Add(clonedResources); + } + + if (frameworkElement.IsLoaded) + { + // Only force if the style was applied after the control was loaded + ForceControlToReloadThemeResources(frameworkElement); + } + } + + private static void ForceControlToReloadThemeResources(FrameworkElement frameworkElement) + { + // To force the refresh of all resource references. + // Note: Doesn't work when in high-contrast. + var currentRequestedTheme = frameworkElement.RequestedTheme; + frameworkElement.RequestedTheme = currentRequestedTheme == ElementTheme.Dark + ? ElementTheme.Light + : ElementTheme.Dark; + frameworkElement.RequestedTheme = currentRequestedTheme; + } +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/Hi3Helper.CommunityToolkit.WinUI.Controls.SettingsControls.csproj b/Hi3Helper.CommunityToolkit/SettingsControls/Hi3Helper.CommunityToolkit.WinUI.Controls.SettingsControls.csproj new file mode 100644 index 000000000..4a2b0d3e0 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/Hi3Helper.CommunityToolkit.WinUI.Controls.SettingsControls.csproj @@ -0,0 +1,71 @@ + + + + + x64 + net9.0-windows10.0.22621.0 + 10.0.22621.48 + 10.0.17763.0 + win-x64 + true + true + Library + 12.0 + portable + enable + + Hi3Helper.CommunityToolkit.WinUI.Controls + Hi3Helper.CommunityToolkit.WinUI.Controls.SettingsControls + SettingsControls + SettingsControls + 1.0.0 + https://github.com/CollapseLauncher/Collapse + enable + disable + WINAPPSDK;WINUI3 + Debug;Release + true + win-x64 + + + + full + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/MultiTarget.props b/Hi3Helper.CommunityToolkit/SettingsControls/MultiTarget.props new file mode 100644 index 000000000..fb4d71c7c --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/MultiTarget.props @@ -0,0 +1,9 @@ + + + + wasdk; + + \ No newline at end of file diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/ReadMe.md b/Hi3Helper.CommunityToolkit/SettingsControls/ReadMe.md new file mode 100644 index 000000000..a12ae1fa5 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/ReadMe.md @@ -0,0 +1,39 @@ + +# Windows Community Toolkit - SettingsControls + +This package is part of the [Windows Community Toolkit](https://aka.ms/toolkit/windows) from the [.NET Foundation](https://dotnetfoundation.org). + +## Package Contents + +This package contains the following controls in the `CommunityToolkit.WinUI.Controls` namespace: + +- SettingsCard +- SettingsExpander + +## Which Package is for me? + +If you're developing with _UWP/WinUI 2 or Uno.UI_ you should be using the `CommunityToolkit.Uwp.Controls.SettingsControls` package. + +If you're developing with _WindowsAppSDK/WinUI 3 or Uno.WinUI_ you should be using the `CommunityToolkit.WinUI.Controls.SettingsControls` package. + +## WinUI Resources (UWP) + +For UWP projects, the WinUI 2 reference requires you include the WinUI XAML Resources in your App.xaml file: + +```xml + + + +``` + +See [Getting Started in WinUI 2](https://learn.microsoft.com/windows/apps/winui/winui2/getting-started) for more information. + +## Documentation + +Further documentation about these components can be found at: https://aka.ms/windowstoolkitdocs + +## License + +MIT + +See License.md in package for more details. diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCard.Properties.cs b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCard.Properties.cs new file mode 100644 index 000000000..d057a8319 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCard.Properties.cs @@ -0,0 +1,182 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ReSharper disable RedundantExtendsListEntry +// ReSharper disable PartialTypeWithSinglePart +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +public partial class SettingsCard : ButtonBase +{ + /// + /// The backing for the property. + /// + public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register( + nameof(Header), + typeof(object), + typeof(SettingsCard), + new PropertyMetadata(defaultValue: null, (d, e) => ((SettingsCard)d).OnHeaderPropertyChanged(e.OldValue, e.NewValue))); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register( + nameof(Description), + typeof(object), + typeof(SettingsCard), + new PropertyMetadata(defaultValue: null, (d, e) => ((SettingsCard)d).OnDescriptionPropertyChanged(e.OldValue, e.NewValue))); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty HeaderIconProperty = DependencyProperty.Register( + nameof(HeaderIcon), + typeof(IconElement), + typeof(SettingsCard), + new PropertyMetadata(defaultValue: null, (d, e) => ((SettingsCard)d).OnHeaderIconPropertyChanged((IconElement)e.OldValue, (IconElement)e.NewValue))); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty ActionIconProperty = DependencyProperty.Register( + nameof(ActionIcon), + typeof(IconElement), + typeof(SettingsCard), + new PropertyMetadata(defaultValue: "\ue974")); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty ActionIconToolTipProperty = DependencyProperty.Register( + nameof(ActionIconToolTip), + typeof(string), + typeof(SettingsCard), + new PropertyMetadata(defaultValue: null)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty IsClickEnabledProperty = DependencyProperty.Register( + nameof(IsClickEnabled), + typeof(bool), + typeof(SettingsCard), + new PropertyMetadata(defaultValue: false, (d, e) => ((SettingsCard)d).OnIsClickEnabledPropertyChanged((bool)e.OldValue, (bool)e.NewValue))); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty ContentAlignmentProperty = DependencyProperty.Register( + nameof(ContentAlignment), + typeof(ContentAlignment), + typeof(SettingsCard), + new PropertyMetadata(defaultValue: ContentAlignment.Right)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty IsActionIconVisibleProperty = DependencyProperty.Register( + nameof(IsActionIconVisible), + typeof(bool), + typeof(SettingsCard), + new PropertyMetadata(defaultValue: true, (d, e) => ((SettingsCard)d).OnIsActionIconVisiblePropertyChanged((bool)e.OldValue, (bool)e.NewValue))); + + /// + /// Gets or sets the Header. + /// + public object Header + { + get => GetValue(HeaderProperty); + set => SetValue(HeaderProperty, value); + } + + /// + /// Gets or sets the description. + /// +#pragma warning disable CS0109 // Member does not hide an inherited member; new keyword is not required + public new object Description +#pragma warning restore CS0109 // Member does not hide an inherited member; new keyword is not required + { + get => GetValue(DescriptionProperty); + set => SetValue(DescriptionProperty, value); + } + + /// + /// Gets or sets the icon on the left. + /// + public IconElement HeaderIcon + { + get => (IconElement)GetValue(HeaderIconProperty); + set => SetValue(HeaderIconProperty, value); + } + + /// + /// Gets or sets the icon that is shown when IsClickEnabled is set to true. + /// + public IconElement ActionIcon + { + get => (IconElement)GetValue(ActionIconProperty); + set => SetValue(ActionIconProperty, value); + } + + /// + /// Gets or sets the tooltip of the ActionIcon. + /// + public string ActionIconToolTip + { + get => (string)GetValue(ActionIconToolTipProperty); + set => SetValue(ActionIconToolTipProperty, value); + } + + /// + /// Gets or sets if the card can be clicked. + /// + public bool IsClickEnabled + { + get => (bool)GetValue(IsClickEnabledProperty); + set => SetValue(IsClickEnabledProperty, value); + } + + /// + /// Gets or sets the alignment of the Content + /// + public ContentAlignment ContentAlignment + { + get => (ContentAlignment)GetValue(ContentAlignmentProperty); + set => SetValue(ContentAlignmentProperty, value); + } + + /// + /// Gets or sets if the ActionIcon is shown. + /// + public bool IsActionIconVisible + { + get => (bool)GetValue(IsActionIconVisibleProperty); + set => SetValue(IsActionIconVisibleProperty, value); + } + + protected virtual void OnIsClickEnabledPropertyChanged(bool oldValue, bool newValue) => OnIsClickEnabledChanged(); + + protected virtual void OnHeaderIconPropertyChanged(IconElement oldValue, IconElement newValue) => OnHeaderIconChanged(); + + protected virtual void OnHeaderPropertyChanged(object oldValue, object newValue) => OnHeaderChanged(); + + protected virtual void OnDescriptionPropertyChanged(object oldValue, object newValue) => OnDescriptionChanged(); + + protected virtual void OnIsActionIconVisiblePropertyChanged(bool oldValue, bool newValue) => OnActionIconChanged(); +} + +public enum ContentAlignment +{ + /// + /// The Content is aligned to the right. Default state. + /// + Right, + /// + /// The Content is left-aligned while the Header, HeaderIcon and Description are collapsed. This is commonly used for Content types such as CheckBoxes, RadioButtons and custom layouts. + /// + Left, + /// + /// The Content is vertically aligned. + /// + Vertical +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCard.cs b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCard.cs new file mode 100644 index 000000000..a3f6ea647 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCard.cs @@ -0,0 +1,294 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ReSharper disable RedundantExtendsListEntry +// ReSharper disable PartialTypeWithSinglePart +#nullable enable +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// This is the base control to create consistent settings experiences, inline with the Windows 11 design language. +/// A SettingsCard can also be hosted within a SettingsExpander. +/// + +[TemplatePart(Name = ActionIconPresenterHolder, Type = typeof(Viewbox))] +[TemplatePart(Name = HeaderPresenter, Type = typeof(ContentPresenter))] +[TemplatePart(Name = DescriptionPresenter, Type = typeof(ContentPresenter))] +[TemplatePart(Name = HeaderIconPresenterHolder, Type = typeof(Viewbox))] + +[TemplateVisualState(Name = NormalState, GroupName = CommonStates)] +[TemplateVisualState(Name = PointerOverState, GroupName = CommonStates)] +[TemplateVisualState(Name = PressedState, GroupName = CommonStates)] +[TemplateVisualState(Name = DisabledState, GroupName = CommonStates)] + +[TemplateVisualState(Name = RightState, GroupName = ContentAlignmentStates)] +[TemplateVisualState(Name = RightWrappedState, GroupName = ContentAlignmentStates)] +[TemplateVisualState(Name = RightWrappedNoIconState, GroupName = ContentAlignmentStates)] +[TemplateVisualState(Name = LeftState, GroupName = ContentAlignmentStates)] +[TemplateVisualState(Name = VerticalState, GroupName = ContentAlignmentStates)] + +[TemplateVisualState(Name = NoContentSpacingState, GroupName = ContentSpacingStates)] +[TemplateVisualState(Name = ContentSpacingState, GroupName = ContentSpacingStates)] + +public partial class SettingsCard : ButtonBase +{ + internal const string CommonStates = "CommonStates"; + internal const string NormalState = "Normal"; + internal const string PointerOverState = "PointerOver"; + internal const string PressedState = "Pressed"; + internal const string DisabledState = "Disabled"; + + internal const string ContentAlignmentStates = "ContentAlignmentStates"; + internal const string RightState = "Right"; + internal const string RightWrappedState = "RightWrapped"; + internal const string RightWrappedNoIconState = "RightWrappedNoIcon"; + internal const string LeftState = "Left"; + internal const string VerticalState = "Vertical"; + + internal const string ContentSpacingStates = "ContentSpacingStates"; + internal const string NoContentSpacingState = "NoContentSpacing"; + internal const string ContentSpacingState = "ContentSpacing"; + + internal const string ActionIconPresenterHolder = "PART_ActionIconPresenterHolder"; + internal const string HeaderPresenter = "PART_HeaderPresenter"; + internal const string DescriptionPresenter = "PART_DescriptionPresenter"; + internal const string HeaderIconPresenterHolder = "PART_HeaderIconPresenterHolder"; + + + /// + /// Creates a new instance of the class. + /// + public SettingsCard() + { + DefaultStyleKey = typeof(SettingsCard); + } + + /// + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + IsEnabledChanged -= OnIsEnabledChanged; + OnActionIconChanged(); + OnHeaderChanged(); + OnHeaderIconChanged(); + OnDescriptionChanged(); + OnIsClickEnabledChanged(); + CheckInitialVisualState(); + SetAccessibleContentName(); + RegisterPropertyChangedCallback(ContentProperty, OnContentChanged); + IsEnabledChanged += OnIsEnabledChanged; + } + + private void CheckInitialVisualState() + { + VisualStateManager.GoToState(this, IsEnabled ? NormalState : DisabledState, true); + + if (GetTemplateChild("ContentAlignmentStates") is VisualStateGroup contentAlignmentStatesGroup) + { + contentAlignmentStatesGroup.CurrentStateChanged -= ContentAlignmentStates_Changed; + CheckVerticalSpacingState(contentAlignmentStatesGroup.CurrentState); + contentAlignmentStatesGroup.CurrentStateChanged += ContentAlignmentStates_Changed; + } + } + + // We automatically set the AutomationProperties.Name of the Content if not configured. + private void SetAccessibleContentName() + { + if (Header is string headerString && headerString != string.Empty) + { + // We don't want to override an AutomationProperties.Name that is manually set, or if the Content basetype is of type ButtonBase (the ButtonBase.Content will be used then) + if (Content is UIElement element && string.IsNullOrEmpty(AutomationProperties.GetName(element)) && element.GetType().BaseType != typeof(ButtonBase) && element.GetType() != typeof(TextBlock)) + { + AutomationProperties.SetName(element, headerString); + } + } + } + + private void EnableButtonInteraction() + { + DisableButtonInteraction(); + + IsTabStop = true; + PointerEntered += Control_PointerEntered; + PointerExited += Control_PointerExited; + PointerCaptureLost += Control_PointerCaptureLost; + PointerCanceled += Control_PointerCanceled; + PreviewKeyDown += Control_PreviewKeyDown; + PreviewKeyUp += Control_PreviewKeyUp; + } + + private void DisableButtonInteraction() + { + IsTabStop = false; + PointerEntered -= Control_PointerEntered; + PointerExited -= Control_PointerExited; + PointerCaptureLost -= Control_PointerCaptureLost; + PointerCanceled -= Control_PointerCanceled; + PreviewKeyDown -= Control_PreviewKeyDown; + PreviewKeyUp -= Control_PreviewKeyUp; + } + + private void Control_PreviewKeyUp(object sender, KeyRoutedEventArgs e) + { + if (e.Key == VirtualKey.Enter || e.Key == VirtualKey.Space || e.Key == VirtualKey.GamepadA) + { + VisualStateManager.GoToState(this, NormalState, true); + } + } + + private void Control_PreviewKeyDown(object sender, KeyRoutedEventArgs e) + { + if (e.Key == VirtualKey.Enter || e.Key == VirtualKey.Space || e.Key == VirtualKey.GamepadA) + { + // Check if the active focus is on the card itself - only then we show the pressed state. + if (GetFocusedElement() is SettingsCard) + { + VisualStateManager.GoToState(this, PressedState, true); + } + } + } + + public void Control_PointerEntered(object sender, PointerRoutedEventArgs e) + { + base.OnPointerEntered(e); + VisualStateManager.GoToState(this, PointerOverState, true); + } + + public void Control_PointerExited(object sender, PointerRoutedEventArgs e) + { + base.OnPointerExited(e); + VisualStateManager.GoToState(this, NormalState, true); + } + + private void Control_PointerCaptureLost(object sender, PointerRoutedEventArgs e) + { + base.OnPointerCaptureLost(e); + VisualStateManager.GoToState(this, NormalState, true); + } + + private void Control_PointerCanceled(object sender, PointerRoutedEventArgs e) + { + base.OnPointerCanceled(e); + VisualStateManager.GoToState(this, NormalState, true); + } + + protected override void OnPointerPressed(PointerRoutedEventArgs e) + { + // e.Handled = true; + if (IsClickEnabled) + { + base.OnPointerPressed(e); + VisualStateManager.GoToState(this, PressedState, true); + } + } + + protected override void OnPointerReleased(PointerRoutedEventArgs e) + { + if (IsClickEnabled) + { + base.OnPointerReleased(e); + VisualStateManager.GoToState(this, NormalState, true); + } + } + + /// + /// Creates AutomationPeer + /// + /// An automation peer for . + protected override AutomationPeer OnCreateAutomationPeer() => new SettingsCardAutomationPeer(this); + + private void OnIsClickEnabledChanged() + { + OnActionIconChanged(); + if (IsClickEnabled) + { + EnableButtonInteraction(); + } + else + { + DisableButtonInteraction(); + } + } + + private void OnIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) + { + VisualStateManager.GoToState(this, IsEnabled ? NormalState : DisabledState, true); + } + + private void OnActionIconChanged() + { + if (GetTemplateChild(ActionIconPresenterHolder) is FrameworkElement actionIconPresenter) + { + if (IsClickEnabled && IsActionIconVisible) + { + actionIconPresenter.Visibility = Visibility.Visible; + } + else + { + actionIconPresenter.Visibility =Visibility.Collapsed; + } + } + } + + private void OnHeaderIconChanged() + { + if (GetTemplateChild(HeaderIconPresenterHolder) is FrameworkElement headerIconPresenter) + { + headerIconPresenter.Visibility = HeaderIcon != null + ? Visibility.Visible + : Visibility.Collapsed; + } + } + + private void OnDescriptionChanged() + { + if (GetTemplateChild(DescriptionPresenter) is FrameworkElement descriptionPresenter) + { + descriptionPresenter.Visibility = IsNullOrEmptyString(Description) + ? Visibility.Collapsed + : Visibility.Visible; + } + + } + + private void OnHeaderChanged() + { + if (GetTemplateChild(HeaderPresenter) is FrameworkElement headerPresenter) + { + headerPresenter.Visibility = IsNullOrEmptyString(Header) + ? Visibility.Collapsed + : Visibility.Visible; + } + + } + + private void ContentAlignmentStates_Changed(object sender, VisualStateChangedEventArgs e) => CheckVerticalSpacingState(e.NewState); + + private void CheckVerticalSpacingState(VisualState? s) + { + // On state change, checking if the Content should be wrapped (e.g. when the card is made smaller or the ContentAlignment is set to Vertical). If the Content and the Header or Description are not null, we add spacing between the Content and the Header/Description. + + if (s != null && s.Name is RightWrappedState or RightWrappedNoIconState or VerticalState && Content != null && (!IsNullOrEmptyString(Header) || !IsNullOrEmptyString(Description))) + { + VisualStateManager.GoToState(this, ContentSpacingState, true); + } + else + { + VisualStateManager.GoToState(this, NoContentSpacingState, true); + } + } + + private FrameworkElement? GetFocusedElement() + { + if (ControlHelpers.IsXamlRootAvailable && XamlRoot != null) + { + return FocusManager.GetFocusedElement(XamlRoot) as FrameworkElement; + } + + return FocusManager.GetFocusedElement() as FrameworkElement; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsNullOrEmptyString(object obj) => obj is string objString && string.IsNullOrEmpty(objString); +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCard.xaml b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCard.xaml new file mode 100644 index 000000000..7c9d2d9b0 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCard.xaml @@ -0,0 +1,1080 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 16,16,16,16 + 148 + 68 + 12 + 20 + 0 + 120 + 2,0,20,0 + 14,0,0,0 + 13 + 8 + 476 + 286 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCardAutomationPeer.cs b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCardAutomationPeer.cs new file mode 100644 index 000000000..f59bf8308 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsCard/SettingsCardAutomationPeer.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ReSharper disable RedundantExtendsListEntry +// ReSharper disable PartialTypeWithSinglePart +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// AutomationPeer for SettingsCard +/// +public partial class SettingsCardAutomationPeer : FrameworkElementAutomationPeer +{ + /// + /// Initializes a new instance of the class. + /// + /// SettingsCard + public SettingsCardAutomationPeer(SettingsCard owner) + : base(owner) + { + } + + /// + /// Gets the control type for the element that is associated with the UI Automation peer. + /// + /// The control type. + protected override AutomationControlType GetAutomationControlTypeCore() + { + if (Owner is SettingsCard settingsCard && settingsCard.IsClickEnabled) + { + return AutomationControlType.Button; + } + else + { + return AutomationControlType.Group; + } + } + + /// + /// Called by GetClassName that gets a human readable name that, in addition to AutomationControlType, + /// differentiates the control represented by this AutomationPeer. + /// + /// The string that contains the name. + protected override string GetClassNameCore() + { + return Owner.GetType().Name; + } + + protected override string GetNameCore() + { + // We only want to announce the button card name if it is clickable, else it's just a regular card that does not receive focus + if (Owner is SettingsCard owner && owner.IsClickEnabled) + { + string name = AutomationProperties.GetName(owner); + if (!string.IsNullOrEmpty(name)) + { + return name; + } + else + { + if (owner.Header is string headerString && !string.IsNullOrEmpty(headerString)) + { + return headerString; + } + } + } + + return base.GetNameCore(); + } +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.Events.cs b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.Events.cs new file mode 100644 index 000000000..d1a50dbad --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.Events.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +public partial class SettingsExpander +{ + /// + /// Fires when the SettingsExpander is opened + /// + public event EventHandler? Expanded; + + /// + /// Fires when the expander is closed + /// + public event EventHandler? Collapsed; +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.ItemsControl.cs b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.ItemsControl.cs new file mode 100644 index 000000000..1eab5c08f --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.ItemsControl.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +//// Implement properties for ItemsControl like behavior. +public partial class SettingsExpander +{ + public IList Items + { + get { return (IList)GetValue(ItemsProperty); } + set { SetValue(ItemsProperty, value); } + } + + public static readonly DependencyProperty ItemsProperty = + DependencyProperty.Register(nameof(Items), typeof(IList), typeof(SettingsExpander), new PropertyMetadata(null, OnItemsConnectedPropertyChanged)); + + public object ItemsSource + { + get { return GetValue(ItemsSourceProperty); } + set { SetValue(ItemsSourceProperty, value); } + } + + public static readonly DependencyProperty ItemsSourceProperty = + DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(SettingsExpander), new PropertyMetadata(null, OnItemsConnectedPropertyChanged)); + + public object ItemTemplate + { + get { return GetValue(ItemTemplateProperty); } + set { SetValue(ItemTemplateProperty, value); } + } + + public static readonly DependencyProperty ItemTemplateProperty = + DependencyProperty.Register(nameof(ItemTemplate), typeof(object), typeof(SettingsExpander), new PropertyMetadata(null)); + + public StyleSelector ItemContainerStyleSelector + { + get { return (StyleSelector)GetValue(ItemContainerStyleSelectorProperty); } + set { SetValue(ItemContainerStyleSelectorProperty, value); } + } + + public static readonly DependencyProperty ItemContainerStyleSelectorProperty = + DependencyProperty.Register(nameof(ItemContainerStyleSelector), typeof(StyleSelector), typeof(SettingsExpander), new PropertyMetadata(null)); + + private static void OnItemsConnectedPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args) + { + if (dependencyObject is not SettingsExpander { _itemsRepeater: not null } expander) + { + return; + } + + var datasource = expander.ItemsSource ?? expander.Items; + + expander._itemsRepeater.ItemsSource = datasource; + } + + private void ItemsRepeater_ElementPrepared(ItemsRepeater sender, ItemsRepeaterElementPreparedEventArgs args) + { + if (ItemContainerStyleSelector != null && + args.Element is FrameworkElement element && + element.ReadLocalValue(StyleProperty) == DependencyProperty.UnsetValue) + { + // TODO: Get item from args.Index? + element.Style = ItemContainerStyleSelector.SelectStyle(null, element); + } + } +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.Properties.cs b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.Properties.cs new file mode 100644 index 000000000..e806ea7a4 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.Properties.cs @@ -0,0 +1,151 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +[ContentProperty(Name = nameof(Content))] +public partial class SettingsExpander +{ + /// + /// The backing for the property. + /// + public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register( + nameof(Header), + typeof(object), + typeof(SettingsExpander), + new PropertyMetadata(defaultValue: null)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register( + nameof(Description), + typeof(object), + typeof(SettingsExpander), + new PropertyMetadata(defaultValue: null)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty HeaderIconProperty = DependencyProperty.Register( + nameof(HeaderIcon), + typeof(IconElement), + typeof(SettingsExpander), + new PropertyMetadata(defaultValue: null)); + + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty ContentProperty = DependencyProperty.Register( + nameof(Content), + typeof(object), + typeof(SettingsExpander), + new PropertyMetadata(defaultValue: null)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty ItemsHeaderProperty = DependencyProperty.Register( + nameof(ItemsHeader), + typeof(UIElement), + typeof(SettingsExpander), + new PropertyMetadata(defaultValue: null)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty ItemsFooterProperty = DependencyProperty.Register( + nameof(ItemsFooter), + typeof(UIElement), + typeof(SettingsExpander), + new PropertyMetadata(defaultValue: null)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register( + nameof(IsExpanded), + typeof(bool), + typeof(SettingsExpander), + new PropertyMetadata(defaultValue: false, (d, e) => ((SettingsExpander)d).OnIsExpandedPropertyChanged((bool)e.OldValue, (bool)e.NewValue))); + + /// + /// Gets or sets the Header. + /// + public object Header + { + get => GetValue(HeaderProperty); + set => SetValue(HeaderProperty, value); + } + + /// + /// Gets or sets the Description. + /// +#pragma warning disable CS0109 // Member does not hide an inherited member; new keyword is not required + public new object Description +#pragma warning restore CS0109 // Member does not hide an inherited member; new keyword is not required + { + get => GetValue(DescriptionProperty); + set => SetValue(DescriptionProperty, value); + } + + /// + /// Gets or sets the HeaderIcon. + /// + public IconElement HeaderIcon + { + get => (IconElement)GetValue(HeaderIconProperty); + set => SetValue(HeaderIconProperty, value); + } + + /// + /// Gets or sets the Content. + /// + public object Content + { + get => GetValue(ContentProperty); + set => SetValue(ContentProperty, value); + } + + /// + /// Gets or sets the ItemsFooter. + /// + public UIElement ItemsHeader + { + get => (UIElement)GetValue(ItemsHeaderProperty); + set => SetValue(ItemsHeaderProperty, value); + } + + /// + /// Gets or sets the ItemsFooter. + /// + public UIElement ItemsFooter + { + get => (UIElement)GetValue(ItemsFooterProperty); + set => SetValue(ItemsFooterProperty, value); + } + + /// + /// Gets or sets the IsExpanded state. + /// + public bool IsExpanded + { + get => (bool)GetValue(IsExpandedProperty); + set => SetValue(IsExpandedProperty, value); + } + protected virtual void OnIsExpandedPropertyChanged(bool oldValue, bool newValue) + { + OnIsExpandedChanged(oldValue, newValue); + + if (newValue) + { + Expanded?.Invoke(this, EventArgs.Empty); + } + else + { + Collapsed?.Invoke(this, EventArgs.Empty); + } + } +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.cs b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.cs new file mode 100644 index 000000000..2ac2787e8 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +//// Note: ItemsRepeater will request all the available horizontal space: https://github.com/microsoft/microsoft-ui-xaml/issues/3842 +[TemplatePart(Name = PART_ItemsRepeater, Type = typeof(ItemsRepeater))] +public partial class SettingsExpander : Control +{ + private const string PART_ItemsRepeater = "PART_ItemsRepeater"; + + private ItemsRepeater? _itemsRepeater; + + /// + /// The SettingsExpander is a collapsable control to host multiple SettingsCards. + /// + public SettingsExpander() + { + this.DefaultStyleKey = typeof(SettingsExpander); + Items = new List(); + } + + /// + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + SetAccessibleName(); + + if (_itemsRepeater != null) + { + _itemsRepeater.ElementPrepared -= this.ItemsRepeater_ElementPrepared; + } + + _itemsRepeater = GetTemplateChild(PART_ItemsRepeater) as ItemsRepeater; + + if (_itemsRepeater != null) + { + _itemsRepeater.ElementPrepared += this.ItemsRepeater_ElementPrepared; + + // Update it's source based on our current items properties. + OnItemsConnectedPropertyChanged(this, null!); // Can't get it to accept type here? (DependencyPropertyChangedEventArgs)EventArgs.Empty + } + } + + private void SetAccessibleName() + { + if (string.IsNullOrEmpty(AutomationProperties.GetName(this))) + { + if (Header is string headerString && !string.IsNullOrEmpty(headerString)) + { + AutomationProperties.SetName(this, headerString); + } + } + } + + /// + /// Creates AutomationPeer + /// + /// An automation peer for . + protected override AutomationPeer OnCreateAutomationPeer() + { + return new SettingsExpanderAutomationPeer(this); + } + + private void OnIsExpandedChanged(bool oldValue, bool newValue) + { + var peer = FrameworkElementAutomationPeer.FromElement(this) as SettingsExpanderAutomationPeer; + peer?.RaiseExpandedChangedEvent(newValue); + } +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.xaml b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.xaml new file mode 100644 index 000000000..368b0f105 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpander.xaml @@ -0,0 +1,583 @@ + + + + + + + + + + Show all settings + 16,16,4,16 + 58,8,44,8 + 0,1,0,0 + 58,8,16,8 + 16 + 32 + 32 + + + + + + + + + + + + + + + + + diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpanderAutomationPeer.cs b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpanderAutomationPeer.cs new file mode 100644 index 000000000..ca2de796a --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpanderAutomationPeer.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ReSharper disable RedundantExtendsListEntry +// ReSharper disable PartialTypeWithSinglePart +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// AutomationPeer for SettingsExpander +/// +public partial class SettingsExpanderAutomationPeer : FrameworkElementAutomationPeer +{ + /// + /// Initializes a new instance of the class. + /// + /// SettingsExpander + public SettingsExpanderAutomationPeer(SettingsExpander owner) + : base(owner) + { + } + + /// + /// Gets the control type for the element that is associated with the UI Automation peer. + /// + /// The control type. + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Group; + } + + /// + /// Called by GetClassName that gets a human-readable name that, in addition to AutomationControlType, + /// differentiates the control represented by this AutomationPeer. + /// + /// The string that contains the name. + protected override string GetClassNameCore() + { + return Owner.GetType().Name; + } + + protected override string GetNameCore() + { + string name = base.GetNameCore(); + + if (Owner is SettingsExpander owner) + { + if (!string.IsNullOrEmpty(AutomationProperties.GetName(owner))) + { + name = AutomationProperties.GetName(owner); + } + else + { + if (owner.Header is string headerString && !string.IsNullOrEmpty(headerString)) + { + name = headerString; + } + } + } + return name; + } + + /// + /// Raises the property changed event for this AutomationPeer for the provided identifier. + /// Narrator does not announce this due to: https://github.com/microsoft/microsoft-ui-xaml/issues/3469 + /// + /// New Expanded state + public void RaiseExpandedChangedEvent(bool newValue) + { + ExpandCollapseState newState = newValue ? + ExpandCollapseState.Expanded : + ExpandCollapseState.Collapsed; + + ExpandCollapseState oldState = newState == ExpandCollapseState.Expanded ? + ExpandCollapseState.Collapsed : + ExpandCollapseState.Expanded; + + #if !HAS_UNO + RaisePropertyChangedEvent(ExpandCollapsePatternIdentifiers.ExpandCollapseStateProperty, oldState, newState); + #endif + } +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpanderItemStyleSelector.cs b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpanderItemStyleSelector.cs new file mode 100644 index 000000000..462e3c36f --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/SettingsExpander/SettingsExpanderItemStyleSelector.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// ReSharper disable PartialTypeWithSinglePart +namespace Hi3Helper.CommunityToolkit.WinUI.Controls; + +/// +/// used by to choose the proper container style (clickable or not). +/// +public partial class SettingsExpanderItemStyleSelector : StyleSelector +{ + /// + /// Gets or sets the default . + /// + public Style DefaultStyle { get; set; } + + /// + /// Gets or sets the when clickable. + /// + public Style ClickableStyle { get; set; } + + /// + /// Initializes a new instance of the class. + /// +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + public SettingsExpanderItemStyleSelector() +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + { + } + + /// + protected override Style SelectStyleCore(object item, DependencyObject container) + { + if (container is SettingsCard card && card.IsClickEnabled) + { + return ClickableStyle; + } + else + { + return DefaultStyle; + } + } +} diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/Themes/Generic.xaml b/Hi3Helper.CommunityToolkit/SettingsControls/Themes/Generic.xaml new file mode 100644 index 000000000..177830969 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/Themes/Generic.xaml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/Hi3Helper.CommunityToolkit/SettingsControls/packages.lock.json b/Hi3Helper.CommunityToolkit/SettingsControls/packages.lock.json new file mode 100644 index 000000000..cd3418c61 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/SettingsControls/packages.lock.json @@ -0,0 +1,95 @@ +{ + "version": 1, + "dependencies": { + "net9.0-windows10.0.22621": { + "CommunityToolkit.WinUI.Triggers": { + "type": "Direct", + "requested": "[8.1.240916, )", + "resolved": "8.1.240916", + "contentHash": "4XUtIdhjTj9mTWgqUvnVIa3Ox+CW+fBks94DUOndAuxtHlDf9Oc8EguHXp4W8qxp7x4tUeYDoRPmcsnXgjt3Sw==", + "dependencies": { + "CommunityToolkit.WinUI.Helpers": "8.1.240916", + "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", + "Microsoft.WindowsAppSDK": "1.5.240311000" + } + }, + "Microsoft.NET.ILLink.Tasks": { + "type": "Direct", + "requested": "[9.0.0-rc.2.24473.5, )", + "resolved": "9.0.0-rc.2.24473.5", + "contentHash": "mXyCgKqBIo/KOpY09FqjOU8d+LME2qowzzvBd1/viYmSpAKYfQ2w1WDOlh0FniY4xnrB/wmDiAo4frJvfCqT0w==" + }, + "Microsoft.Web.WebView2": { + "type": "Direct", + "requested": "[1.0.2783-prerelease, )", + "resolved": "1.0.2783-prerelease", + "contentHash": "sPxd49RLVnOyzplXTvdU9z6NIx0t56MridYoZhw8BOBZt564Ufi168Wfg5G0rIc5EKWmrGihLeZ1OfmjQiqWTw==" + }, + "Microsoft.Windows.CsWinRT": { + "type": "Direct", + "requested": "[2.1.5, )", + "resolved": "2.1.5", + "contentHash": "PG0uVrpPTVEhqu70YhGMTyRKZXNgygjIIwdjAmg2hhHkmm6367TafBEdzIU/TgMXy2+x5Lv/Z9MKJehwkQXEvw==" + }, + "Microsoft.Windows.SDK.BuildTools": { + "type": "Direct", + "requested": "[10.0.26100.1742, )", + "resolved": "10.0.26100.1742", + "contentHash": "ypcHjr4KEi6xQhgClnbXoANHcyyX/QsC4Rky4igs6M4GiDa+weegPo8JuV/VMxqrZCV4zlqDsp2krgkN7ReAAg==" + }, + "Microsoft.WindowsAppSDK": { + "type": "Direct", + "requested": "[1.6.240923002, )", + "resolved": "1.6.240923002", + "contentHash": "7PfOz2scXU+AAM/GYge+f6s7k3DVI+R1P8MNPZQr56GOPCGw+csvcg3S5KZg47z/o04kNvWH3GKtWT1ML9tpZw==", + "dependencies": { + "Microsoft.Web.WebView2": "1.0.2651.64", + "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" + } + }, + "CommunityToolkit.Common": { + "type": "Transitive", + "resolved": "8.2.1", + "contentHash": "LWuhy8cQKJ/MYcy3XafJ916U3gPH/YDvYoNGWyQWN11aiEKCZszzPOTJAOvBjP9yG8vHmIcCyPUt4L82OK47Iw==" + }, + "CommunityToolkit.WinUI.Extensions": { + "type": "Transitive", + "resolved": "8.1.240916", + "contentHash": "y8FEzoy5HF3dBkxe/pU87hlh0QACtGZhnv881x2SFq9fvDjukLQz6VpoOkjxSZjEILmYu1NoI/tYjqXlETRGlw==", + "dependencies": { + "CommunityToolkit.Common": "8.2.1", + "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", + "Microsoft.WindowsAppSDK": "1.5.240311000" + } + }, + "CommunityToolkit.WinUI.Helpers": { + "type": "Transitive", + "resolved": "8.1.240916", + "contentHash": "Q2eipdR9ntktYT1b3Dn927lZjdwQsmSU9cq6Pf2GwXW+R2IwkbGn7uYKJhmr5athOT6mnNgc7pMmfwFHIsFPLQ==", + "dependencies": { + "CommunityToolkit.WinUI.Extensions": "8.1.240916", + "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756", + "Microsoft.WindowsAppSDK": "1.5.240311000" + } + } + }, + "net9.0-windows10.0.22621/win-x64": { + "Microsoft.Web.WebView2": { + "type": "Direct", + "requested": "[1.0.2783-prerelease, )", + "resolved": "1.0.2783-prerelease", + "contentHash": "sPxd49RLVnOyzplXTvdU9z6NIx0t56MridYoZhw8BOBZt564Ufi168Wfg5G0rIc5EKWmrGihLeZ1OfmjQiqWTw==" + }, + "Microsoft.WindowsAppSDK": { + "type": "Direct", + "requested": "[1.6.240923002, )", + "resolved": "1.6.240923002", + "contentHash": "7PfOz2scXU+AAM/GYge+f6s7k3DVI+R1P8MNPZQr56GOPCGw+csvcg3S5KZg47z/o04kNvWH3GKtWT1ML9tpZw==", + "dependencies": { + "Microsoft.Web.WebView2": "1.0.2651.64", + "Microsoft.Windows.SDK.BuildTools": "10.0.22621.756" + } + } + } + } +} \ No newline at end of file diff --git a/Hi3Helper.CommunityToolkit/commitinfo.txt b/Hi3Helper.CommunityToolkit/commitinfo.txt new file mode 100644 index 000000000..c4d6cb691 --- /dev/null +++ b/Hi3Helper.CommunityToolkit/commitinfo.txt @@ -0,0 +1 @@ +https://github.com/CommunityToolkit/Windows/commit/44d3d9d3601bb543625078fbbab55a2a867b0662 \ No newline at end of file diff --git a/Hi3Helper.Core/Classes/Data/Tools/GenshinDispatchHelper/GenshinDispatchHelper.cs b/Hi3Helper.Core/Classes/Data/Tools/GenshinDispatchHelper/GenshinDispatchHelper.cs index 24ec6a515..b9aed9243 100644 --- a/Hi3Helper.Core/Classes/Data/Tools/GenshinDispatchHelper/GenshinDispatchHelper.cs +++ b/Hi3Helper.Core/Classes/Data/Tools/GenshinDispatchHelper/GenshinDispatchHelper.cs @@ -90,10 +90,7 @@ private void ParseDesignDataURL(QueryProperty valProp) foreach (string data in dataList) { (valProp.ClientGameRes as List)? - .Add( - (PkgVersionProperties?)JsonSerializer.Deserialize(data, typeof(PkgVersionProperties), - CoreLibraryJSONContext.Default) - ); + .Add(JsonSerializer.Deserialize(data, CoreLibraryJSONContext.Default.PkgVersionProperties)); } } @@ -132,8 +129,7 @@ private void ParseGameResPkgProp(QueryProperty valProp) if (jsonDesignDataSil != null) { valProp.ClientDesignDataSil = - JsonSerializer.Deserialize(jsonDesignDataSil, typeof(PkgVersionProperties), - CoreLibraryJSONContext.Default) as PkgVersionProperties; + JsonSerializer.Deserialize(jsonDesignDataSil, CoreLibraryJSONContext.Default.PkgVersionProperties); } else { diff --git a/Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs b/Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs index 0fe8a523a..a3ea3dfb0 100644 --- a/Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs +++ b/Hi3Helper.Core/Classes/Shared/Region/LauncherConfig.cs @@ -137,7 +137,7 @@ private static void InitScreenResSettings() new CDNURLProperty { Name = "GitHub", - URLPrefix = "https://github.com/neon-nyan/CollapseLauncher-ReleaseRepo/raw/main", + URLPrefix = "https://github.com/CollapseLauncher/CollapseLauncher-ReleaseRepo/raw/main", Description = Lang!._Misc!.CDNDescription_Github, PartialDownloadSupport = true }, @@ -347,6 +347,12 @@ public static int DownloadChunkSize set => SetAndSaveConfigValue("DownloadChunkSize", value); } + public static bool IsEnforceToUse7zipOnExtract + { + get => GetAppConfigValue("EnforceToUse7zipOnExtract").ToBool(); + set => SetAndSaveConfigValue("EnforceToUse7zipOnExtract", value); + } + private static long _downloadSpeedLimitCached = 0; // Default: 0 == Unlimited public static long DownloadSpeedLimitCached { @@ -441,6 +447,8 @@ public static Guid GetGuid(int sessionNum) { "SophonHttpConnInt", 0}, { "SophonPreloadApplyPerfMode", false }, + { "EnforceToUse7zipOnExtract", false }, + { "IsUseProxy", false }, { "IsAllowHttpRedirections", true }, { "IsAllowHttpCookies", false }, diff --git a/Hi3Helper.Core/Hi3Helper.Core.csproj b/Hi3Helper.Core/Hi3Helper.Core.csproj index bba442d8b..26402b286 100644 --- a/Hi3Helper.Core/Hi3Helper.Core.csproj +++ b/Hi3Helper.Core/Hi3Helper.Core.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0-windows10.0.22621.0 x64 Debug;Release;Publish 6 @@ -18,6 +18,7 @@ Collapse Launcher Team $(Company). neon-nyan, Cry0, bagusnl, shatyuka, gablm. Copyright 2022-2024 $(Company) + true @@ -44,9 +45,12 @@ + + + + - diff --git a/Hi3Helper.Core/Lang/Locale/LangAppNotification.cs b/Hi3Helper.Core/Lang/Locale/LangAppNotification.cs index dd7031aa6..fbe6e1960 100644 --- a/Hi3Helper.Core/Lang/Locale/LangAppNotification.cs +++ b/Hi3Helper.Core/Lang/Locale/LangAppNotification.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,10 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangAppNotification _AppNotification { get; set; } = LangFallback?._AppNotification; - public sealed class LangAppNotification + + [GeneratedBindableCustomProperty] + // ReSharper disable once PartialTypeWithSinglePart + public sealed partial class LangAppNotification { public string NotifMetadataUpdateTitle { get; set; } = LangFallback?._AppNotification.NotifMetadataUpdateTitle; public string NotifMetadataUpdateSubtitle { get; set; } = LangFallback?._AppNotification.NotifMetadataUpdateSubtitle; diff --git a/Hi3Helper.Core/Lang/Locale/LangBackgroundNotification.cs b/Hi3Helper.Core/Lang/Locale/LangBackgroundNotification.cs index e77808a03..7c791428a 100644 --- a/Hi3Helper.Core/Lang/Locale/LangBackgroundNotification.cs +++ b/Hi3Helper.Core/Lang/Locale/LangBackgroundNotification.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangBackgroundNotification _BackgroundNotification { get; set; } = LangFallback?._BackgroundNotification; - public sealed class LangBackgroundNotification + + [GeneratedBindableCustomProperty] + public sealed partial class LangBackgroundNotification { public string LoadingTitle { get; set; } = LangFallback?._BackgroundNotification.LoadingTitle; public string Placeholder { get; set; } = LangFallback?._BackgroundNotification.Placeholder; diff --git a/Hi3Helper.Core/Lang/Locale/LangCachesPage.cs b/Hi3Helper.Core/Lang/Locale/LangCachesPage.cs index 13b84e519..3f27c41aa 100644 --- a/Hi3Helper.Core/Lang/Locale/LangCachesPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangCachesPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangCachesPage _CachesPage { get; set; } = LangFallback?._CachesPage; - public sealed class LangCachesPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangCachesPage { public string PageTitle { get; set; } = LangFallback?._CachesPage.PageTitle; public string ListCol1 { get; set; } = LangFallback?._CachesPage.ListCol1; diff --git a/Hi3Helper.Core/Lang/Locale/LangCutscenesPage.cs b/Hi3Helper.Core/Lang/Locale/LangCutscenesPage.cs index 122766d05..19907e237 100644 --- a/Hi3Helper.Core/Lang/Locale/LangCutscenesPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangCutscenesPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangCutscenesPage _CutscenesPage { get; set; } = LangFallback?._CutscenesPage; - public sealed class LangCutscenesPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangCutscenesPage { public string PageTitle { get; set; } = LangFallback?._CutscenesPage.PageTitle; } diff --git a/Hi3Helper.Core/Lang/Locale/LangDialogs.cs b/Hi3Helper.Core/Lang/Locale/LangDialogs.cs index 1f3fab129..0e8761c50 100644 --- a/Hi3Helper.Core/Lang/Locale/LangDialogs.cs +++ b/Hi3Helper.Core/Lang/Locale/LangDialogs.cs @@ -1,3 +1,5 @@ +using WinRT; + namespace Hi3Helper { public sealed partial class Locale @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangDialogs _Dialogs { get; set; } = LangFallback?._Dialogs; - public sealed class LangDialogs + + [GeneratedBindableCustomProperty] + public sealed partial class LangDialogs { public string DeltaPatchDetectedTitle { get; set; } = LangFallback?._Dialogs.PreloadVerifiedTitle; public string DeltaPatchDetectedSubtitle { get; set; } = LangFallback?._Dialogs.PreloadVerifiedTitle; diff --git a/Hi3Helper.Core/Lang/Locale/LangDisconnectedPage.cs b/Hi3Helper.Core/Lang/Locale/LangDisconnectedPage.cs index 45c652e1b..40a78d6a0 100644 --- a/Hi3Helper.Core/Lang/Locale/LangDisconnectedPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangDisconnectedPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangDisconnectedPage _DisconnectedPage { get; set; } = LangFallback?._DisconnectedPage; - public sealed class LangDisconnectedPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangDisconnectedPage { public string PageTitle { get; set; } = LangFallback?._DisconnectedPage.PageTitle; public string Header1 { get; set; } = LangFallback?._DisconnectedPage.Header1; diff --git a/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs b/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs index 875538170..ee12fa153 100644 --- a/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangFileCleanupPage.cs @@ -1,3 +1,5 @@ +using WinRT; + namespace Hi3Helper { public sealed partial class Locale @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangFileCleanupPage _FileCleanupPage { get; set; } = LangFallback?._FileCleanupPage; - public sealed class LangFileCleanupPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangFileCleanupPage { public string Title { get; set; } = LangFallback?._FileCleanupPage.Title; public string TopButtonRescan { get; set; } = LangFallback?._FileCleanupPage.TopButtonRescan; diff --git a/Hi3Helper.Core/Lang/Locale/LangFileMigrationProcess.cs b/Hi3Helper.Core/Lang/Locale/LangFileMigrationProcess.cs index 41ec1e3b7..f896f0fe6 100644 --- a/Hi3Helper.Core/Lang/Locale/LangFileMigrationProcess.cs +++ b/Hi3Helper.Core/Lang/Locale/LangFileMigrationProcess.cs @@ -1,3 +1,5 @@ +using WinRT; + namespace Hi3Helper { public sealed partial class Locale @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangFileMigrationProcess _FileMigrationProcess { get; set; } = LangFallback?._FileMigrationProcess; - public sealed class LangFileMigrationProcess + + [GeneratedBindableCustomProperty] + public sealed partial class LangFileMigrationProcess { public string PathActivityPanelTitle { get; set; } = LangFallback?._FileMigrationProcess.PathActivityPanelTitle; public string SpeedIndicatorTitle { get; set; } = LangFallback?._FileMigrationProcess.SpeedIndicatorTitle; diff --git a/Hi3Helper.Core/Lang/Locale/LangGameRepairPage.cs b/Hi3Helper.Core/Lang/Locale/LangGameRepairPage.cs index fae1f8789..79f61a1b8 100644 --- a/Hi3Helper.Core/Lang/Locale/LangGameRepairPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangGameRepairPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangGameRepairPage _GameRepairPage { get; set; } = LangFallback?._GameRepairPage; - public sealed class LangGameRepairPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangGameRepairPage { public string PageTitle { get; set; } = LangFallback?._GameRepairPage.PageTitle; public string ListCol1 { get; set; } = LangFallback?._GameRepairPage.ListCol1; diff --git a/Hi3Helper.Core/Lang/Locale/LangGameSettingsPage.cs b/Hi3Helper.Core/Lang/Locale/LangGameSettingsPage.cs index fe305638a..cf57547eb 100644 --- a/Hi3Helper.Core/Lang/Locale/LangGameSettingsPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangGameSettingsPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangGameSettingsPage _GameSettingsPage { get; set; } = LangFallback?._GameSettingsPage; - public sealed class LangGameSettingsPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangGameSettingsPage { public string PageTitle { get; set; } = LangFallback?._GameSettingsPage.PageTitle; public string Graphics_Title { get; set; } = LangFallback?._GameSettingsPage.Graphics_Title; diff --git a/Hi3Helper.Core/Lang/Locale/LangGenshinGameSettingsPage.cs b/Hi3Helper.Core/Lang/Locale/LangGenshinGameSettingsPage.cs index b87994065..217926040 100644 --- a/Hi3Helper.Core/Lang/Locale/LangGenshinGameSettingsPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangGenshinGameSettingsPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangGenshinGameSettingsPage _GenshinGameSettingsPage { get; set; } = LangFallback?._GenshinGameSettingsPage; - public sealed class LangGenshinGameSettingsPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangGenshinGameSettingsPage { public string PageTitle { get; set; } = LangFallback?._GenshinGameSettingsPage.PageTitle; #region Overlay diff --git a/Hi3Helper.Core/Lang/Locale/LangHomePage.cs b/Hi3Helper.Core/Lang/Locale/LangHomePage.cs index 006281cb0..657341a2a 100644 --- a/Hi3Helper.Core/Lang/Locale/LangHomePage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangHomePage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangHomePage _HomePage { get; set; } = LangFallback?._HomePage; - public sealed class LangHomePage + + [GeneratedBindableCustomProperty] + public sealed partial class LangHomePage { public string PageTitle { get; set; } = LangFallback?._HomePage.PageTitle; public string PreloadTitle { get; set; } = LangFallback?._HomePage.PreloadTitle; @@ -45,48 +49,51 @@ public sealed class LangHomePage public string GameSettings_Panel2ConvertVersion { get; set; } = LangFallback?._HomePage.GameSettings_Panel2ConvertVersion; public string GameSettings_Panel2MoveGameLocationGame { get; set; } = LangFallback?._HomePage.GameSettings_Panel2MoveGameLocationGame; public string GameSettings_Panel2MoveGameLocationGame_SamePath { get; set; } = LangFallback?._HomePage.GameSettings_Panel2MoveGameLocationGame_SamePath; - public string GameSettings_Panel2StopGame { get; set; } = LangFallback?._HomePage.GameSettings_Panel2StopGame; - public string GameSettings_Panel3 { get; set; } = LangFallback?._HomePage.GameSettings_Panel3; - public string GameSettings_Panel3CustomBGRegion { get; set; } = LangFallback?._HomePage.GameSettings_Panel3CustomBGRegion; - public string GameSettings_Panel3CustomBGRegionSectionTitle { get; set; } = LangFallback?._HomePage.GameSettings_Panel3CustomBGRegionSectionTitle; - public string GameSettings_Panel4 { get; set; } = LangFallback?._HomePage.GameSettings_Panel4; - public string GameSettings_Panel4ShowEventsPanel { get; set; } = LangFallback?._HomePage.GameSettings_Panel4ShowEventsPanel; - public string GameSettings_Panel4ShowSocialMediaPanel { get; set; } = LangFallback?._HomePage.GameSettings_Panel4ShowSocialMediaPanel; - public string GameSettings_Panel4ShowPlaytimeButton { get; set; } = LangFallback?._HomePage.GameSettings_Panel4ShowPlaytimeButton; - public string GameSettings_Panel4CreateShortcutBtn { get; set; } = LangFallback?._HomePage.GameSettings_Panel4CreateShortcutBtn; - public string GameSettings_Panel4AddToSteamBtn { get; set; } = LangFallback?._HomePage.GameSettings_Panel4AddToSteamBtn; - public string CreateShortcut_FolderPicker { get; set; } = LangFallback?._HomePage.CreateShortcut_FolderPicker; - public string GamePlaytime_Panel1 { get; set; } = LangFallback?._HomePage.GamePlaytime_Panel1; - public string GamePlaytime_Idle_Panel1Hours { get; set; } = LangFallback?._HomePage.GamePlaytime_Idle_Panel1Hours; - public string GamePlaytime_Idle_Panel1Minutes { get; set; } = LangFallback?._HomePage.GamePlaytime_Idle_Panel1Minutes; - public string GamePlaytime_Idle_ResetBtn { get; set; } = LangFallback?._HomePage.GamePlaytime_Idle_ResetBtn; - public string GamePlaytime_Idle_ChangeBtn { get; set; } = LangFallback?._HomePage.GamePlaytime_Idle_ChangeBtn; - public string GamePlaytime_Running_Info1 { get; set; } = LangFallback?._HomePage.GamePlaytime_Running_Info1; - public string GamePlaytime_Running_Info2 { get; set; } = LangFallback?._HomePage.GamePlaytime_Running_Info2; - public string GamePlaytime_Display { get; set; } = LangFallback?._HomePage.GamePlaytime_Display; - public string GamePlaytime_ToolTipDisplay { get; set; } = LangFallback?._HomePage.GamePlaytime_ToolTipDisplay; - public string PostPanel_Events { get; set; } = LangFallback?._HomePage.PostPanel_Events; - public string PostPanel_Notices { get; set; } = LangFallback?._HomePage.PostPanel_Notices; - public string PostPanel_Info { get; set; } = LangFallback?._HomePage.PostPanel_Info; - public string PostPanel_NoNews { get; set; } = LangFallback?._HomePage.PostPanel_NoNews; - public string CommunityToolsBtn { get; set; } = LangFallback?._HomePage.CommunityToolsBtn; - public string CommunityToolsBtn_OfficialText { get; set; } = LangFallback?._HomePage.CommunityToolsBtn_OfficialText; - public string CommunityToolsBtn_CommunityText { get; set; } = LangFallback?._HomePage.CommunityToolsBtn_CommunityText; - public string CommunityToolsBtn_OpenExecutableAppDialogTitle { get; set; } = LangFallback?._HomePage.CommunityToolsBtn_OpenExecutableAppDialogTitle; - public string Exception_DownloadTimeout1 { get; set; } = - LangFallback?._HomePage.Exception_DownloadTimeout1; - public string Exception_DownloadTimeout2 { get; set; } = - LangFallback?._HomePage.Exception_DownloadTimeout2; - public string Exception_DownloadTimeout3 { get; set; } = - LangFallback?._HomePage.Exception_DownloadTimeout3; - + public string GameSettings_Panel2StopGame { get; set; } = LangFallback?._HomePage.GameSettings_Panel2StopGame; + public string GameSettings_Panel3 { get; set; } = LangFallback?._HomePage.GameSettings_Panel3; + public string GameSettings_Panel3CustomBGRegion { get; set; } = LangFallback?._HomePage.GameSettings_Panel3CustomBGRegion; + public string GameSettings_Panel3CustomBGRegionSectionTitle { get; set; } = LangFallback?._HomePage.GameSettings_Panel3CustomBGRegionSectionTitle; + public string GameSettings_Panel4 { get; set; } = LangFallback?._HomePage.GameSettings_Panel4; + public string GameSettings_Panel4ShowEventsPanel { get; set; } = LangFallback?._HomePage.GameSettings_Panel4ShowEventsPanel; + public string GameSettings_Panel4ShowSocialMediaPanel { get; set; } = LangFallback?._HomePage.GameSettings_Panel4ShowSocialMediaPanel; + public string GameSettings_Panel4ShowPlaytimeButton { get; set; } = LangFallback?._HomePage.GameSettings_Panel4ShowPlaytimeButton; + public string GameSettings_Panel4CreateShortcutBtn { get; set; } = LangFallback?._HomePage.GameSettings_Panel4CreateShortcutBtn; + public string GameSettings_Panel4AddToSteamBtn { get; set; } = LangFallback?._HomePage.GameSettings_Panel4AddToSteamBtn; + public string CreateShortcut_FolderPicker { get; set; } = LangFallback?._HomePage.CreateShortcut_FolderPicker; + public string GamePlaytime_Panel1 { get; set; } = LangFallback?._HomePage.GamePlaytime_Panel1; + public string GamePlaytime_Idle_Panel1Hours { get; set; } = LangFallback?._HomePage.GamePlaytime_Idle_Panel1Hours; + public string GamePlaytime_Idle_Panel1Minutes { get; set; } = LangFallback?._HomePage.GamePlaytime_Idle_Panel1Minutes; + public string GamePlaytime_Idle_ResetBtn { get; set; } = LangFallback?._HomePage.GamePlaytime_Idle_ResetBtn; + public string GamePlaytime_Idle_ChangeBtn { get; set; } = LangFallback?._HomePage.GamePlaytime_Idle_ChangeBtn; + public string GamePlaytime_Running_Info1 { get; set; } = LangFallback?._HomePage.GamePlaytime_Running_Info1; + public string GamePlaytime_Running_Info2 { get; set; } = LangFallback?._HomePage.GamePlaytime_Running_Info2; + public string GamePlaytime_Display { get; set; } = LangFallback?._HomePage.GamePlaytime_Display; + public string GamePlaytime_DateDisplay { get; set; } = LangFallback?._HomePage.GamePlaytime_DateDisplay; + public string GamePlaytime_Stats_Title { get; set; } = LangFallback?._HomePage.GamePlaytime_Stats_Title; + public string GamePlaytime_Stats_NeverPlayed { get; set; } = LangFallback?._HomePage.GamePlaytime_Stats_NeverPlayed; + public string GamePlaytime_Stats_LastSession { get; set; } = LangFallback?._HomePage.GamePlaytime_Stats_LastSession; + public string GamePlaytime_Stats_LastSession_StartTime { get; set; } = LangFallback?._HomePage.GamePlaytime_Stats_LastSession_StartTime; + public string GamePlaytime_Stats_LastSession_Duration { get; set; } = LangFallback?._HomePage.GamePlaytime_Stats_LastSession_Duration; + public string GamePlaytime_Stats_Daily { get; set; } = LangFallback?._HomePage.GamePlaytime_Stats_Daily; + public string GamePlaytime_Stats_Weekly { get; set; } = LangFallback?._HomePage.GamePlaytime_Stats_Weekly; + public string GamePlaytime_Stats_Monthly { get; set; } = LangFallback?._HomePage.GamePlaytime_Stats_Monthly; + public string PostPanel_Events { get; set; } = LangFallback?._HomePage.PostPanel_Events; + public string PostPanel_Notices { get; set; } = LangFallback?._HomePage.PostPanel_Notices; + public string PostPanel_Info { get; set; } = LangFallback?._HomePage.PostPanel_Info; + public string PostPanel_NoNews { get; set; } = LangFallback?._HomePage.PostPanel_NoNews; + public string CommunityToolsBtn { get; set; } = LangFallback?._HomePage.CommunityToolsBtn; + public string CommunityToolsBtn_OfficialText { get; set; } = LangFallback?._HomePage.CommunityToolsBtn_OfficialText; + public string CommunityToolsBtn_CommunityText { get; set; } = LangFallback?._HomePage.CommunityToolsBtn_CommunityText; + public string CommunityToolsBtn_OpenExecutableAppDialogTitle { get; set; } = LangFallback?._HomePage.CommunityToolsBtn_OpenExecutableAppDialogTitle; + public string Exception_DownloadTimeout1 { get; set; } = LangFallback?._HomePage.Exception_DownloadTimeout1; + public string Exception_DownloadTimeout2 { get; set; } = LangFallback?._HomePage.Exception_DownloadTimeout2; + public string Exception_DownloadTimeout3 { get; set; } = LangFallback?._HomePage.Exception_DownloadTimeout3; public string GameStateInvalid_Title { get; set; } = LangFallback?._HomePage.GameStateInvalid_Title; public string GameStateInvalid_Subtitle1 { get; set; } = LangFallback?._HomePage.GameStateInvalid_Subtitle1; public string GameStateInvalid_Subtitle2 { get; set; } = LangFallback?._HomePage.GameStateInvalid_Subtitle2; public string GameStateInvalid_Subtitle3 { get; set; } = LangFallback?._HomePage.GameStateInvalid_Subtitle3; public string GameStateInvalid_Subtitle4 { get; set; } = LangFallback?._HomePage.GameStateInvalid_Subtitle4; public string GameStateInvalid_Subtitle5 { get; set; } = LangFallback?._HomePage.GameStateInvalid_Subtitle5; - public string GameStateInvalidFixed_Title { get; set; } = LangFallback?._HomePage.GameStateInvalidFixed_Title; public string GameStateInvalidFixed_Subtitle1 { get; set; } = LangFallback?._HomePage.GameStateInvalidFixed_Subtitle1; public string GameStateInvalidFixed_Subtitle2 { get; set; } = LangFallback?._HomePage.GameStateInvalidFixed_Subtitle2; diff --git a/Hi3Helper.Core/Lang/Locale/LangInstallConvert.cs b/Hi3Helper.Core/Lang/Locale/LangInstallConvert.cs index 26a6346e4..9a2db987e 100644 --- a/Hi3Helper.Core/Lang/Locale/LangInstallConvert.cs +++ b/Hi3Helper.Core/Lang/Locale/LangInstallConvert.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangInstallConvert _InstallConvert { get; set; } = LangFallback?._InstallConvert; - public sealed class LangInstallConvert + + [GeneratedBindableCustomProperty] + public sealed partial class LangInstallConvert { public string PageTitle { get; set; } = LangFallback?._InstallConvert.PageTitle; public string Step1Title { get; set; } = LangFallback?._InstallConvert.Step1Title; diff --git a/Hi3Helper.Core/Lang/Locale/LangInstallManagement.cs b/Hi3Helper.Core/Lang/Locale/LangInstallManagement.cs index ef84b3d90..3dc6bad6f 100644 --- a/Hi3Helper.Core/Lang/Locale/LangInstallManagement.cs +++ b/Hi3Helper.Core/Lang/Locale/LangInstallManagement.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangInstallManagement _InstallMgmt { get; set; } = LangFallback?._InstallMgmt; - public sealed class LangInstallManagement + + [GeneratedBindableCustomProperty] + public sealed partial class LangInstallManagement { public string IntegrityCheckTitle { get; set; } = LangFallback?._InstallMgmt.IntegrityCheckTitle; public string PreparePatchTitle { get; set; } = LangFallback?._InstallMgmt.PreparePatchTitle; diff --git a/Hi3Helper.Core/Lang/Locale/LangInstallMigrateSteam.cs b/Hi3Helper.Core/Lang/Locale/LangInstallMigrateSteam.cs index 02411e8eb..f1eb255c2 100644 --- a/Hi3Helper.Core/Lang/Locale/LangInstallMigrateSteam.cs +++ b/Hi3Helper.Core/Lang/Locale/LangInstallMigrateSteam.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangInstallMigrateSteam _InstallMigrateSteam { get; set; } = LangFallback?._InstallMigrateSteam; - public sealed class LangInstallMigrateSteam + + [GeneratedBindableCustomProperty] + public sealed partial class LangInstallMigrateSteam { public string PageTitle { get; set; } = LangFallback?._InstallMigrateSteam.PageTitle; public string Step1Title { get; set; } = LangFallback?._InstallMigrateSteam.Step1Title; diff --git a/Hi3Helper.Core/Lang/Locale/LangKeyboardShortcuts.cs b/Hi3Helper.Core/Lang/Locale/LangKeyboardShortcuts.cs index b7e172f40..c02d84448 100644 --- a/Hi3Helper.Core/Lang/Locale/LangKeyboardShortcuts.cs +++ b/Hi3Helper.Core/Lang/Locale/LangKeyboardShortcuts.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangKeyboardShortcuts _KbShortcuts { get; set; } = LangFallback?._KbShortcuts; - public sealed class LangKeyboardShortcuts + + [GeneratedBindableCustomProperty] + public sealed partial class LangKeyboardShortcuts { public string DialogTitle { get; set; } = LangFallback?._KbShortcuts.DialogTitle; diff --git a/Hi3Helper.Core/Lang/Locale/LangMainPage.cs b/Hi3Helper.Core/Lang/Locale/LangMainPage.cs index 4cc2772f8..d4f749b2e 100644 --- a/Hi3Helper.Core/Lang/Locale/LangMainPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangMainPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangMainPage _MainPage { get; set; } = LangFallback?._MainPage; - public sealed class LangMainPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangMainPage { public string PageTitle { get; set; } = LangFallback?._MainPage.PageTitle; public string RegionChangeConfirm { get; set; } = LangFallback?._MainPage.RegionChangeConfirm; diff --git a/Hi3Helper.Core/Lang/Locale/LangMisc.cs b/Hi3Helper.Core/Lang/Locale/LangMisc.cs index 5f78d227b..89202459f 100644 --- a/Hi3Helper.Core/Lang/Locale/LangMisc.cs +++ b/Hi3Helper.Core/Lang/Locale/LangMisc.cs @@ -1,3 +1,5 @@ +using WinRT; + namespace Hi3Helper { public sealed partial class Locale @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangMisc _Misc { get; set; } = LangFallback?._Misc; - public sealed class LangMisc + + [GeneratedBindableCustomProperty] + public sealed partial class LangMisc { public string SizePrefixes1000U { get; set; } = LangFallback?._Misc.SizePrefixes1000U; public string UpdateCompleteTitle { get; set; } = LangFallback?._Misc.UpdateCompleteTitle; diff --git a/Hi3Helper.Core/Lang/Locale/LangNotificationToast.cs b/Hi3Helper.Core/Lang/Locale/LangNotificationToast.cs index bd53a561b..5cd978cf3 100644 --- a/Hi3Helper.Core/Lang/Locale/LangNotificationToast.cs +++ b/Hi3Helper.Core/Lang/Locale/LangNotificationToast.cs @@ -1,3 +1,5 @@ +using WinRT; + namespace Hi3Helper { public sealed partial class Locale @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangNotificationToast _NotificationToast { get; set; } = LangFallback?._NotificationToast; - public sealed class LangNotificationToast + + [GeneratedBindableCustomProperty] + public sealed partial class LangNotificationToast { public string WindowHiddenToTray_Title { get; set; } = LangFallback?._NotificationToast.WindowHiddenToTray_Title; public string WindowHiddenToTray_Subtitle { get; set; } = LangFallback?._NotificationToast.WindowHiddenToTray_Subtitle; diff --git a/Hi3Helper.Core/Lang/Locale/LangOOBEAgreementMenu.cs b/Hi3Helper.Core/Lang/Locale/LangOOBEAgreementMenu.cs index 19e31b6b7..3f80ea9d0 100644 --- a/Hi3Helper.Core/Lang/Locale/LangOOBEAgreementMenu.cs +++ b/Hi3Helper.Core/Lang/Locale/LangOOBEAgreementMenu.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangOOBEAgreementMenu _OOBEAgreementMenu { get; set; } = LangFallback?._OOBEAgreementMenu; - public sealed class LangOOBEAgreementMenu + + [GeneratedBindableCustomProperty] + public sealed partial class LangOOBEAgreementMenu { public string AgreementTitle { get; set; } = LangFallback?._OOBEAgreementMenu.AgreementTitle; } diff --git a/Hi3Helper.Core/Lang/Locale/LangOOBEStartUpMenu.cs b/Hi3Helper.Core/Lang/Locale/LangOOBEStartUpMenu.cs index 1133dd900..47db25cc9 100644 --- a/Hi3Helper.Core/Lang/Locale/LangOOBEStartUpMenu.cs +++ b/Hi3Helper.Core/Lang/Locale/LangOOBEStartUpMenu.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using WinRT; namespace Hi3Helper { @@ -8,7 +9,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangOOBEStartUpMenu _OOBEStartUpMenu { get; set; } = LangFallback?._OOBEStartUpMenu; - public sealed class LangOOBEStartUpMenu + + [GeneratedBindableCustomProperty] + public sealed partial class LangOOBEStartUpMenu { public Dictionary WelcomeTitleString { get; set; } = LangFallback?._OOBEStartUpMenu.WelcomeTitleString; public string SetupNextButton { get; set; } = LangFallback?._OOBEStartUpMenu.SetupNextButton; diff --git a/Hi3Helper.Core/Lang/Locale/LangSettingsPage.cs b/Hi3Helper.Core/Lang/Locale/LangSettingsPage.cs index 8a6efdb0b..e45272013 100644 --- a/Hi3Helper.Core/Lang/Locale/LangSettingsPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangSettingsPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangSettingsPage _SettingsPage { get; set; } = LangFallback?._SettingsPage; - public sealed class LangSettingsPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangSettingsPage { public string PageTitle { get; set; } = LangFallback?._SettingsPage.PageTitle; public string Debug { get; set; } = LangFallback?._SettingsPage.Debug; @@ -97,10 +101,11 @@ public sealed class LangSettingsPage public string AppChangeReleaseChannel { get; set; } = LangFallback?._SettingsPage.AppChangeReleaseChannel; public string EnableAcrylicEffect { get; set; } = LangFallback?._SettingsPage.EnableAcrylicEffect; public string EnableDownloadChunksMerging { get; set; } = LangFallback?._SettingsPage.EnableDownloadChunksMerging; + public string Enforce7ZipExtract { get; set; } = LangFallback?._SettingsPage.Enforce7ZipExtract; public string LowerCollapsePrioOnGameLaunch { get; set; } = LangFallback?._SettingsPage.LowerCollapsePrioOnGameLaunch; public string LowerCollapsePrioOnGameLaunch_Tooltip { get; set; } = LangFallback?._SettingsPage.LowerCollapsePrioOnGameLaunch_Tooltip; public string UseExternalBrowser { get; set; } = LangFallback?._SettingsPage.UseExternalBrowser; - public string KbShortcuts_Title { get; set; } = LangFallback?._SettingsPage.KbShortcuts_Title; + public string KbShortcuts_Title { get; set; } = LangFallback?._SettingsPage.KbShortcuts_Title; public string KbShortcuts_ShowBtn { get; set; } = LangFallback?._SettingsPage.KbShortcuts_ShowBtn; public string KbShortcuts_ResetBtn { get; set; } = LangFallback?._SettingsPage.KbShortcuts_ResetBtn; public string AppBehavior_Title { get; set; } = LangFallback?._SettingsPage.AppBehavior_Title; diff --git a/Hi3Helper.Core/Lang/Locale/LangStarRailGameSettingsPage.cs b/Hi3Helper.Core/Lang/Locale/LangStarRailGameSettingsPage.cs index 4d97495b7..c39a0f939 100644 --- a/Hi3Helper.Core/Lang/Locale/LangStarRailGameSettingsPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangStarRailGameSettingsPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangStarRailGameSettingsPage _StarRailGameSettingsPage { get; set; } = LangFallback?._StarRailGameSettingsPage; - public sealed class LangStarRailGameSettingsPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangStarRailGameSettingsPage { public string PageTitle { get; set; } = LangFallback?._StarRailGameSettingsPage.PageTitle; public string Graphics_Title { get; set; } = LangFallback?._StarRailGameSettingsPage.Graphics_Title; diff --git a/Hi3Helper.Core/Lang/Locale/LangStartupPage.cs b/Hi3Helper.Core/Lang/Locale/LangStartupPage.cs index 0ee2c2473..63f5defab 100644 --- a/Hi3Helper.Core/Lang/Locale/LangStartupPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangStartupPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangStartupPage _StartupPage { get; set; } = LangFallback?._StartupPage; - public sealed class LangStartupPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangStartupPage { public string SelectLang { get; set; } = LangFallback?._StartupPage.SelectLang; public string SelectLangDesc { get; set; } = LangFallback?._StartupPage.SelectLangDesc; diff --git a/Hi3Helper.Core/Lang/Locale/LangUnhandledExceptionPage.cs b/Hi3Helper.Core/Lang/Locale/LangUnhandledExceptionPage.cs index dcc6238e1..df01b1be1 100644 --- a/Hi3Helper.Core/Lang/Locale/LangUnhandledExceptionPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangUnhandledExceptionPage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangUnhandledExceptionPage _UnhandledExceptionPage { get; set; } = LangFallback?._UnhandledExceptionPage; - public sealed class LangUnhandledExceptionPage + + [GeneratedBindableCustomProperty] + public sealed partial class LangUnhandledExceptionPage { public string UnhandledTitle1 { get; set; } = LangFallback?._UnhandledExceptionPage.UnhandledTitle1; public string UnhandledSubtitle1 { get; set; } = LangFallback?._UnhandledExceptionPage.UnhandledSubtitle1; diff --git a/Hi3Helper.Core/Lang/Locale/LangUpdatePage.cs b/Hi3Helper.Core/Lang/Locale/LangUpdatePage.cs index d7e5561b2..2dbb3100e 100644 --- a/Hi3Helper.Core/Lang/Locale/LangUpdatePage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangUpdatePage.cs @@ -1,4 +1,6 @@ -namespace Hi3Helper +using WinRT; + +namespace Hi3Helper { public sealed partial class Locale { @@ -6,7 +8,9 @@ public sealed partial class Locale public sealed partial class LocalizationParams { public LangUpdatePage _UpdatePage { get; set; } = LangFallback?._UpdatePage; - public sealed class LangUpdatePage + + [GeneratedBindableCustomProperty] + public sealed partial class LangUpdatePage { public string PageTitle1 { get; set; } = LangFallback?._UpdatePage.PageTitle1; public string PageTitle2 { get; set; } = LangFallback?._UpdatePage.PageTitle2; diff --git a/Hi3Helper.Core/Lang/Locale/LangZenlessGameSettingsPage.cs b/Hi3Helper.Core/Lang/Locale/LangZenlessGameSettingsPage.cs index 58c09a10a..10b16ce56 100644 --- a/Hi3Helper.Core/Lang/Locale/LangZenlessGameSettingsPage.cs +++ b/Hi3Helper.Core/Lang/Locale/LangZenlessGameSettingsPage.cs @@ -1,4 +1,6 @@ // ReSharper disable InconsistentNaming +using WinRT; + namespace Hi3Helper { public sealed partial class Locale @@ -8,7 +10,8 @@ public sealed partial class LocalizationParams public LangZenlessGameSettingsPage _ZenlessGameSettingsPage { get; set; } = LangFallback?._ZenlessGameSettingsPage; - public sealed class LangZenlessGameSettingsPage + [GeneratedBindableCustomProperty] + public sealed partial class LangZenlessGameSettingsPage { public string Graphics_ColorFilter { get; set; } = LangFallback?._ZenlessGameSettingsPage.Graphics_ColorFilter; diff --git a/Hi3Helper.Core/Lang/Localization.cs b/Hi3Helper.Core/Lang/Localization.cs index 37dfe374f..6f4fb6adb 100644 --- a/Hi3Helper.Core/Lang/Localization.cs +++ b/Hi3Helper.Core/Lang/Localization.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Text.Json; +using WinRT; using static Hi3Helper.Locale; using static Hi3Helper.Logger; #if !APPLYUPDATE @@ -89,7 +90,7 @@ public LocalizationParams LoadLang(Uri langUri) public LocalizationParams LoadLang(Stream langStream) { - LocalizationParams _langData = (LocalizationParams)JsonSerializer.Deserialize(langStream!, typeof(LocalizationParams), CoreLibraryFieldsJSONContext.Default); + LocalizationParams _langData = JsonSerializer.Deserialize(langStream!, CoreLibraryFieldsJSONContext.Default.LocalizationParams); this.LangAuthor = _langData!.Author; this.LangID = _langData.LanguageID.ToLower(); this.LangName = _langData.LanguageName; @@ -106,6 +107,7 @@ public LocalizationParams LoadLang(Stream langStream) public bool LangIsLoaded; } + [GeneratedBindableCustomProperty] public sealed partial class Locale { public const string FallbackLangID = "en-us"; @@ -226,6 +228,8 @@ private static void TryLoadSizePrefix(LocalizationParams langData) public static LocalizationParams Lang; #nullable enable public static LocalizationParams? LangFallback; + + [GeneratedBindableCustomProperty] public sealed partial class LocalizationParams { public string LanguageName { get; set; } = ""; diff --git a/Hi3Helper.Core/Lang/de_DE.json b/Hi3Helper.Core/Lang/de_DE.json index bab574cb4..8a7f34698 100644 --- a/Hi3Helper.Core/Lang/de_DE.json +++ b/Hi3Helper.Core/Lang/de_DE.json @@ -163,7 +163,7 @@ "GamePlaytime_Running_Info1": "Eine Instanz des Spiels wird derzeit ausgeführt, daher kann die Spielzeit nicht bearbeitet werden.", "GamePlaytime_Running_Info2": "Bitte beachte, dass das vollständige Schließen von Collapse die Aufzeichnung der Spielzeit beendet (und die bis dahin gespielte Zeit speichert) und nur die mit Collapse begonnenen Sitzungen aufgezeichnet werden.", "GamePlaytime_Display": "{0} Std. {1} Min.", - "GamePlaytime_ToolTipDisplay": "Zuletzt geöffnet am {0:00}.{1:00}.{2:0000} um {3:00}:{4:00}", + "GamePlaytime_DateDisplay": "{0:00}.{1:00}.{2:0000} {3:00}:{4:00}", "PostPanel_Events": "Aktionen", "PostPanel_Notices": "Ankündigungen", diff --git a/Hi3Helper.Core/Lang/en_US.json b/Hi3Helper.Core/Lang/en_US.json index 8383a3828..2575b4527 100644 --- a/Hi3Helper.Core/Lang/en_US.json +++ b/Hi3Helper.Core/Lang/en_US.json @@ -166,9 +166,17 @@ "GamePlaytime_Idle_ResetBtn": "Reset", "GamePlaytime_Idle_ChangeBtn": "Change", "GamePlaytime_Running_Info1": "An instance of this game is currently running, therefore playtime can't be edited.", - "GamePlaytime_Running_Info2": "Please be aware that fully closing Collapse will stop playtime tracking (saving what was played until that point) & only sessions started using Collapse will be tracked.", + "GamePlaytime_Running_Info2": "Please be aware that fully closing Collapse will stop playtime tracking (saving what was played until that point).", "GamePlaytime_Display": "{0}h {1}m", - "GamePlaytime_ToolTipDisplay": "Last opened in {0:00}/{1:00}/{2:0000} at {3:00}:{4:00}", + "GamePlaytime_DateDisplay": "{0:00}/{1:00}/{2:0000} {3:00}:{4:00}", + "GamePlaytime_Stats_Title": "Playtime Statistics", + "GamePlaytime_Stats_NeverPlayed": "Never Played", + "GamePlaytime_Stats_LastSession": "Most Recent Session", + "GamePlaytime_Stats_LastSession_StartTime": "Start Time", + "GamePlaytime_Stats_LastSession_Duration": "Duration", + "GamePlaytime_Stats_Daily": "Today", + "GamePlaytime_Stats_Weekly": "Week", + "GamePlaytime_Stats_Monthly": "Month", "PostPanel_Events": "Events", "PostPanel_Notices": "Notices", @@ -534,6 +542,7 @@ "EnableAcrylicEffect": "Use Acrylic Blur Effect", "EnableDownloadChunksMerging": "Merge Downloaded Package Chunks", + "Enforce7ZipExtract": "Always Use 7-zip for Game Installation/Update", "UseExternalBrowser": "Always Use External Browser", "LowerCollapsePrioOnGameLaunch": "Lower Collapse process resource usage when a game is launched", @@ -805,7 +814,7 @@ "RepairCompletedSubtitle": "{0} file(s) have been repaired.", "RepairCompletedSubtitleNoBroken": "No broken files found.", "ExtremeGraphicsSettingsWarnTitle": "Very High Setting Preset Selected!", - "ExtremeGraphicsSettingsWarnSubtitle": "You are about to set the setting preset to Very High!\r\nVery High setting preset is essentially a 2x Render Scale with MSAA Enabled and is VERY UNOPTIMIZED!\r\n\r\nAre you sure you want to use this setting?", + "ExtremeGraphicsSettingsWarnSubtitle": "You are about to set the setting preset to Very High!\r\nVery High setting preset is essentially a 1.6x Render Scale with MSAA Enabled and is VERY UNOPTIMIZED!\r\n\r\nAre you sure you want to use this setting?", "MigrateExistingMoveDirectoryTitle": "Moving Existing Installation for: {0}", "MigrateExistingInstallChoiceTitle": "An Existing Installation of {0} is Detected!", "MigrateExistingInstallChoiceSubtitle1": "You have an existing installation of the game using {0} on this directory:", @@ -870,7 +879,7 @@ "ChangePlaytimeTitle": "Are you sure you want to change your playtime?", "ChangePlaytimeSubtitle": "Changing your playtime means overwriting the current value with the one you just inputted. \n\nDo you wish to proceed?\n\nNote: This has no impact on how Collapse operates and you can change this value again at any time when not playing the game.", "ResetPlaytimeTitle": "Are you sure you want to reset your playtime?", - "ResetPlaytimeSubtitle": "Resetting your playtime means setting the playtime counter back to 0. This is a ", + "ResetPlaytimeSubtitle": "Resetting your playtime means setting the playtime counter and all related statistics back to 0. This is a ", "ResetPlaytimeSubtitle2": "destructive", "ResetPlaytimeSubtitle3": " action, meaning that you cannot undo this once you confirm. \n\nDo you wish to proceed?\n\nNote: This has no impact on how Collapse operates and you can change this value again at any time when not playing the game.", "InvalidPlaytimeTitle": "There was a problem saving this session's playtime", diff --git a/Hi3Helper.Core/Lang/es_419.json b/Hi3Helper.Core/Lang/es_419.json index 8533db080..036a37e34 100644 --- a/Hi3Helper.Core/Lang/es_419.json +++ b/Hi3Helper.Core/Lang/es_419.json @@ -166,9 +166,17 @@ "GamePlaytime_Idle_ResetBtn": "Restablecer", "GamePlaytime_Idle_ChangeBtn": "Cambiar", "GamePlaytime_Running_Info1": "El juego se está ejecutando, por lo tanto no se puede editar el tiempo de juego.", - "GamePlaytime_Running_Info2": "Por favor ten en cuenta que al cerrar Collapse se detendra el seguimiento del tiempo de juego (guardando lo jugado hasta ese punto) y solo los juegos iniciados usando Collapse tendran seguimiento.", + "GamePlaytime_Running_Info2": "Por favor ten en cuenta que al cerrar completamente Collapse se detendrá el seguimiento del tiempo de juego (guardando lo jugado hasta ese punto)", "GamePlaytime_Display": "{0}h {1}m", - "GamePlaytime_ToolTipDisplay": "Última Sesión: {0:00}/{1:00}/{2:0000} a las {3:00}:{4:00}", + "GamePlaytime_DateDisplay": "{0:00}/{1:00}/{2:0000} {3:00}:{4:00}", + "GamePlaytime_Stats_Title": "Estadísticas de Juego", + "GamePlaytime_Stats_NeverPlayed": "Sin jugar", + "GamePlaytime_Stats_LastSession": "Sesión más reciente", + "GamePlaytime_Stats_LastSession_StartTime": "Hora de inicio", + "GamePlaytime_Stats_LastSession_Duration": "Duración", + "GamePlaytime_Stats_Daily": "Hoy", + "GamePlaytime_Stats_Weekly": "Semana", + "GamePlaytime_Stats_Monthly": "Mes", "PostPanel_Events": "Eventos", "PostPanel_Notices": "Anuncios", @@ -534,6 +542,7 @@ "EnableAcrylicEffect": "Usar Efecto de Desenfoque", "EnableDownloadChunksMerging": "Combinar Paquetes Descargados", + "Enforce7ZipExtract": "Usar siempre 7-zip para Instalación/Actualización del Juego", "UseExternalBrowser": "Usar Navegador Web Externo ", "LowerCollapsePrioOnGameLaunch": "Reducir el uso de recursos de Collapse cuando se inicia un juego", @@ -805,7 +814,7 @@ "RepairCompletedSubtitle": "Se repararon {0} archivo(s).", "RepairCompletedSubtitleNoBroken": "No se encontraron archivos rotos.", "ExtremeGraphicsSettingsWarnTitle": "¡Preajuste Muy Alto Seleccionado!", - "ExtremeGraphicsSettingsWarnSubtitle": "Estas por seleccionar la opción grafica \"Muy Alto\" \nEsta configuración prestablecida es básicamente 2x Escalado de renderizado con MSAA activado y NO ESTA OPTIMIZADO!\n\n¿Estas seguro que quieres usar esta configuración?", + "ExtremeGraphicsSettingsWarnSubtitle": "Estas por seleccionar la opción grafica \"Muy Alto\" \nEsta configuración prestablecida es básicamente 1.6x Escalado de renderizado con MSAA activado y NO ESTA OPTIMIZADO!\n\n¿Estas seguro que quieres usar esta configuración?", "MigrateExistingMoveDirectoryTitle": "Moviendo la instalación existente para: {0}", "MigrateExistingInstallChoiceTitle": "¡Se a detectado una instalación existente de {0} !", "MigrateExistingInstallChoiceSubtitle1": "Tienes una instalación del juego usando {0} en esta ubicación:", @@ -870,7 +879,7 @@ "ChangePlaytimeTitle": "¿Estás seguro de que quieres cambiar tu tiempo de juego?", "ChangePlaytimeSubtitle": "Cambiar el tiempo de juego significa reemplazar los valores actuales con los que acabas de ingresar.\n\n¿Deseas continuar?\n\nNota: Esto no afectara como funciona Collapse y cuando no estés jugando, puedes volver a cambiar los valores.", "ResetPlaytimeTitle": "¿Estás seguro de que quieres restablecer tu tiempo de juego?", - "ResetPlaytimeSubtitle": "Restablecer el tiempo de juego significa volver a 0 el contador. Esta es una acción ", + "ResetPlaytimeSubtitle": "Restablecer el tiempo de juego significa volver a poner a 0 el contador de tiempo de juego y todas las estadísticas relacionadas. Esta es una acción ", "ResetPlaytimeSubtitle2": "destructiva", "ResetPlaytimeSubtitle3": ", lo que significa que no puedes deshacer esto una vez confirmado.\n\n¿Deseas continuar?\n\nNota: Esto no afectara como funciona Collapse y cuando no estés jugando, puedes volver a cambiar los valores.", "InvalidPlaytimeTitle": "Hubo un problema al guardar el tiempo de esta sesión ", diff --git a/Hi3Helper.Core/Lang/fr_FR.json b/Hi3Helper.Core/Lang/fr_FR.json index c125e6b1e..8497d55c0 100644 --- a/Hi3Helper.Core/Lang/fr_FR.json +++ b/Hi3Helper.Core/Lang/fr_FR.json @@ -163,7 +163,7 @@ "GamePlaytime_Running_Info1": "Une instance de ce jeu est actuellement en cours, la durée de jeu ne peut donc pas être modifiée.", "GamePlaytime_Running_Info2": "Veuillez noter que la fermeture complète de Collapse arrêtera le suivi du temps de jeu (en sauvegardant ce qui a été joué jusqu'à maintenant) et que seules les sessions commencées à l'aide de Collapse seront suivies.", "GamePlaytime_Display": "{0} h {1:00} min", - "GamePlaytime_ToolTipDisplay": "Dernière exécution le {0:00}/{1:00}/{2:0000} à {3:00}:{4:00}", + "GamePlaytime_DateDisplay": "{0:00}/{1:00}/{2:0000} {3:00}:{4:00}", "PostPanel_Events": "Évènements", "PostPanel_Notices": "Annonces", diff --git a/Hi3Helper.Core/Lang/id_ID.json b/Hi3Helper.Core/Lang/id_ID.json index 9a375cc88..28d063d07 100644 --- a/Hi3Helper.Core/Lang/id_ID.json +++ b/Hi3Helper.Core/Lang/id_ID.json @@ -166,9 +166,17 @@ "GamePlaytime_Idle_ResetBtn": "Atur Ulang", "GamePlaytime_Idle_ChangeBtn": "Ubah", "GamePlaytime_Running_Info1": "Game ini sedang berjalan, maka dari itu waktu main tidak dapat diubah.", - "GamePlaytime_Running_Info2": "Perlu diketahui bahwa dengan menutup Collapse akan memberhentikan tracking dari waktu main (untuk disimpan pada saat dimainkan) dan hanya sesi yang dijalankan dari Collapse saja yang dapat ditrack.", + "GamePlaytime_Running_Info2": "Perlu diingat bahwa menutup Collapse Launcher akan menghentikan penghitungan waktu main (selama game dimainkan dari awal sampai akhir).", "GamePlaytime_Display": "{0}j {1}m", - "GamePlaytime_ToolTipDisplay": "Terakhir dibuka pada {0:00}/{1:00}/{2:0000} pukul {3:00}:{4:00}", + "GamePlaytime_DateDisplay": "{0:00}/{1:00}/{2:0000} {3:00}:{4:00}", + "GamePlaytime_Stats_Title": "Statistik Waktu Main", + "GamePlaytime_Stats_NeverPlayed": "Tidak Pernah Dimainkan", + "GamePlaytime_Stats_LastSession": "Waktu Sesi Terakhir", + "GamePlaytime_Stats_LastSession_StartTime": "Waktu Mulai", + "GamePlaytime_Stats_LastSession_Duration": "Durasi", + "GamePlaytime_Stats_Daily": "Hari Ini", + "GamePlaytime_Stats_Weekly": "Minggu", + "GamePlaytime_Stats_Monthly": "Bulan", "PostPanel_Events": "Event", "PostPanel_Notices": "Pengumuman", @@ -534,6 +542,7 @@ "EnableAcrylicEffect": "Gunakan Efek Blur Akrilik", "EnableDownloadChunksMerging": "Gabung Potongan Unduhan Package", + "Enforce7ZipExtract": "Selalu Gunakan 7-zip untuk Pemasangan atau Pembaruan Game", "UseExternalBrowser": "Selalu Gunakan Browser Eksternal", "LowerCollapsePrioOnGameLaunch": "Turunkan Prioritas Collapse saat Game Berjalan", @@ -805,7 +814,7 @@ "RepairCompletedSubtitle": "{0} file telah diperbaiki.", "RepairCompletedSubtitleNoBroken": "Tidak ada file yang rusak.", "ExtremeGraphicsSettingsWarnTitle": "Pengaturan \"Sangat Tinggi\" Terdeteksi!", - "ExtremeGraphicsSettingsWarnSubtitle": "Kamu telah memilih pengaturan \"Sangat Tinggi\"!\r\nPerlu diingat bahwa pengaturan ini adalah 2x Render Scale dengan MSAA dinyalakan dan pengaturan ini SANGAT TIDAK OPTIMAL!\r\n\r\nYakin ingin pilih pengaturan ini?", + "ExtremeGraphicsSettingsWarnSubtitle": "Kamu telah memilih pengaturan \"Sangat Tinggi\"!\n\rPerlu diingat bahwa pengaturan ini adalah 1.6x Render Scale dengan MSAA dinyalakan dan pengaturan ini SANGAT TIDAK OPTIMAL!\n\r\nYakin ingin pilih pengaturan ini?", "MigrateExistingMoveDirectoryTitle": "Memindahkan Lokasi Pemasangan untuk: {0}", "MigrateExistingInstallChoiceTitle": "Pemasangan untuk {0} yang sudah ada, terdeteksi!", "MigrateExistingInstallChoiceSubtitle1": "Kamu memiliki instalasi game yang sudah terpasang menggunakan {0} pada direktori ini:", @@ -870,7 +879,7 @@ "ChangePlaytimeTitle": "Apakah anda yakin untuk mengubah waktu main?", "ChangePlaytimeSubtitle": "Mengubah waktu main anda berarti menimpa nilai saat ini dengan yang sudah anda masukkan.\n\nLanjut untuk mengubah?\n\nCatatan: Hal ini tidak akan mempengaruhi cara Collapse berjalan dan anda dapat mengubah nilai kembali kapan saja saat anda tidak sedang memainkan gamenya.", "ResetPlaytimeTitle": "Apakah anda yakin untuk setel ulang waktu main?", - "ResetPlaytimeSubtitle": "Menyetel ulang waktu main anda akan mengatur ulang hitungan menjadi 0. Hal ini bersifat ", + "ResetPlaytimeSubtitle": "Mengatur ulang waktu permainan akan mengembalikan perhitungan waktu bermain dan statistik lainnya ke 0. Hal ini bersifat ", "ResetPlaytimeSubtitle2": "\"destructive\"", "ResetPlaytimeSubtitle3": "yang berarti anda tidak akan bisa mengembalikannya setelah anda mengkonfirmasi.\n\nLanjut untuk mengubah?\n\nCatatan: Hal ini tidak akan mempengaruhi cara Collapse berjalan dan anda dapat mengubah nilai kembali kapan saja saat anda tidak sedang memainkan gamenya.", "InvalidPlaytimeTitle": "Terjadi masalah saat menyimpan sesi bermain", diff --git a/Hi3Helper.Core/Lang/ja_JP.json b/Hi3Helper.Core/Lang/ja_JP.json index a912e83a4..0f5ea1859 100644 --- a/Hi3Helper.Core/Lang/ja_JP.json +++ b/Hi3Helper.Core/Lang/ja_JP.json @@ -166,9 +166,17 @@ "GamePlaytime_Idle_ResetBtn": "リセット", "GamePlaytime_Idle_ChangeBtn": "変更", "GamePlaytime_Running_Info1": "ゲームが実行中なため、プレイ時間を編集できません。", - "GamePlaytime_Running_Info2": "Collapseを完全に閉じると、プレイ時間の記録が中断される(閉じた瞬間までのプレイ時間が記録されます)ことと、Collapseから起動したプレイ時間のみが加算されることに注意してください。", + "GamePlaytime_Running_Info2": "Collapseを完全に閉じると、プレイ時間の記録が中断されます。(閉じた瞬間までのプレイ時間が記録されます)", "GamePlaytime_Display": "{0}時間 {1}分", - "GamePlaytime_ToolTipDisplay": "前回の起動は{2:0000}年{1:00}月{0:00}日 {3:00}時{4:00}分", + "GamePlaytime_DateDisplay": "{2:0000}/{1:00}/{0:00} {3:00}:{4:00}", + "GamePlaytime_Stats_Title": "プレイ時間の統計", + "GamePlaytime_Stats_NeverPlayed": "未プレイ", + "GamePlaytime_Stats_LastSession": "前回の起動", + "GamePlaytime_Stats_LastSession_StartTime": "開始時刻", + "GamePlaytime_Stats_LastSession_Duration": "プレイ時間", + "GamePlaytime_Stats_Daily": "今日", + "GamePlaytime_Stats_Weekly": "今週", + "GamePlaytime_Stats_Monthly": "今月", "PostPanel_Events": "イベント", "PostPanel_Notices": "お知らせ", @@ -534,6 +542,7 @@ "EnableAcrylicEffect": "ランチャーUIに透明効果を付ける", "EnableDownloadChunksMerging": "分割されたダウンロードパッケージを結合", + "Enforce7ZipExtract": "ゲームのインストール/更新で常に7-zipを使用する", "UseExternalBrowser": "リンクを常に外部ブラウザで開く", "LowerCollapsePrioOnGameLaunch": "ゲーム起動中のランチャーの使用リソースを削減", @@ -805,7 +814,7 @@ "RepairCompletedSubtitle": "{0}個のファイルが修復されました。", "RepairCompletedSubtitleNoBroken": "壊れたファイルは見つかりませんでした。", "ExtremeGraphicsSettingsWarnTitle": "超すごい設定のプリセットが選択されました!", - "ExtremeGraphicsSettingsWarnSubtitle": "超すごい設定プリセットを使用しようとしています!\n超すごい設定プリセットはデフォルトでMSAA有効・レンダリングスケール2倍で全く最適化されていません!\n\n本当にこの設定を使用しますか?", + "ExtremeGraphicsSettingsWarnSubtitle": "超すごい設定プリセットを使用しようとしています!\n超すごい設定プリセットはMSAA有効・レンダリング精度1.6で全く最適化されていません!\n\n本当にこの設定を使用しますか?", "MigrateExistingMoveDirectoryTitle": "現在のインストールを移動させる:{0}", "MigrateExistingInstallChoiceTitle": "{0}の既存のインストールを検出しました!", "MigrateExistingInstallChoiceSubtitle1": "ゲームは既に{0}と紐づけされた状態で以下のディレクトリにインストールされています:", @@ -870,7 +879,7 @@ "ChangePlaytimeTitle": "本当にプレイ時間を編集しますか?", "ChangePlaytimeSubtitle": "プレイ時間を編集すると、現在の値が入力した値で上書きされます。\n\n続行しますか?\n\n注: 変更してもCollapseの動作には影響しません。ゲームを起動していない時なら、またいつでも編集できます。", "ResetPlaytimeTitle": "本当にプレイ時間をリセットしますか?", - "ResetPlaytimeSubtitle": "プレイ時間をリセットすると、記録されたプレイ時間が0になります。これは", + "ResetPlaytimeSubtitle": "プレイ時間をリセットすると、プレイ時間の記録と統計が削除されます。これは", "ResetPlaytimeSubtitle2": "元に戻せない", "ResetPlaytimeSubtitle3": "変更です。\n\n続行しますか?\n\n注: 変更してもCollapseの動作には影響しません。ゲームを起動していない時なら、またいつでも編集できます。", "InvalidPlaytimeTitle": "プレイ時間の保存中に問題が発生しました", diff --git a/Hi3Helper.Core/Lang/ko_KR.json b/Hi3Helper.Core/Lang/ko_KR.json index c4e2a4806..e5d85d8b6 100644 --- a/Hi3Helper.Core/Lang/ko_KR.json +++ b/Hi3Helper.Core/Lang/ko_KR.json @@ -163,7 +163,7 @@ "GamePlaytime_Running_Info1": "게임이 실행 중이므로 플레이 시간을 수정할 수 없어요.", "GamePlaytime_Running_Info2": "Collapse를 완전히 닫으면 플레이 시간 추적이 중지되고 (그 시점까지 플레이된 시간을 저장), Collapse를 통해 시작한 세션만 추적돼요.", "GamePlaytime_Display": "{0}시간 {1}분", - "GamePlaytime_ToolTipDisplay": "{2:0000}/{1:00}/{0:00} {3:00}:{4:00}에 마지막으로 열림", + "GamePlaytime_DateDisplay": "{2:0000}/{1:00}/{0:00} {3:00}:{4:00}", "PostPanel_Events": "이벤트", "PostPanel_Notices": "공지사항", diff --git a/Hi3Helper.Core/Lang/pt_BR.json b/Hi3Helper.Core/Lang/pt_BR.json index 6b609f310..cce479af3 100644 --- a/Hi3Helper.Core/Lang/pt_BR.json +++ b/Hi3Helper.Core/Lang/pt_BR.json @@ -163,7 +163,7 @@ "GamePlaytime_Running_Info1": "Uma instância desse jogo está em execução, portanto o tempo de jogo não pode ser editado.", "GamePlaytime_Running_Info2": "Por favor, esteja ciente que fechar completamente o Collapse irá parar a contagem de tempo de jogo (com exceção do que foi jogado até o fechamento) e apenas as sessões iniciadas usando o Collapse serão contadas.", "GamePlaytime_Display": "{0}h {1}m", - "GamePlaytime_ToolTipDisplay": "Aberto pela última vez em {0:00}/{1:00}/{2:0000} às {3:00}:{4:00}", + "GamePlaytime_DateDisplay": "{0:00}/{1:00}/{2:0000} {3:00}:{4:00}", "PostPanel_Events": "Eventos", "PostPanel_Notices": "Notícias", diff --git a/Hi3Helper.Core/Lang/pt_PT.json b/Hi3Helper.Core/Lang/pt_PT.json index 29d7a3d30..38b0823b8 100644 --- a/Hi3Helper.Core/Lang/pt_PT.json +++ b/Hi3Helper.Core/Lang/pt_PT.json @@ -163,7 +163,7 @@ "GamePlaytime_Running_Info1": "Uma instância deste jogo está aberta, deste modo o tempo de jogo não pode ser editado.", "GamePlaytime_Running_Info2": "Tem em consideração que o tempo de jogo deixará de ser contado se fechares totalmente o Collapse (salvando o tempo decorrido até este ponto) & apenas sessões iniciadas pelo Collapse irão ser contadas.", "GamePlaytime_Display": "{0}h {1}m", - "GamePlaytime_ToolTipDisplay": "Aberto pela última vez em {0:00}/{1:00}/{2:0000} às {3:00}:{4:00}", + "GamePlaytime_DateDisplay": "{0:00}/{1:00}/{2:0000} {3:00}:{4:00}", "PostPanel_Events": "Eventos", "PostPanel_Notices": "Notícias", diff --git a/Hi3Helper.Core/Lang/ru_RU.json b/Hi3Helper.Core/Lang/ru_RU.json index 2d90d155e..a8187d092 100644 --- a/Hi3Helper.Core/Lang/ru_RU.json +++ b/Hi3Helper.Core/Lang/ru_RU.json @@ -163,7 +163,7 @@ "GamePlaytime_Running_Info1": "Эта игра сейчас запущена, поэтому нельзя изменить время игры.", "GamePlaytime_Running_Info2": "Учтите что полное закрытие Collapse перестанет отслеживать время в игре (сохранится то, что было до запуска), будут отслеживаться только те сеансы, которые запущены с помощью Collapse.", "GamePlaytime_Display": "{0}ч {1}м", - "GamePlaytime_ToolTipDisplay": "Последнее открытие {0:00}/{1:00}/{2:0000} в {3:00}:{4:00}", + "GamePlaytime_DateDisplay": "{0:00}/{1:00}/{2:0000} {3:00}:{4:00}", "PostPanel_Events": "События", "PostPanel_Notices": "Примечания", diff --git a/Hi3Helper.Core/Lang/th_TH.json b/Hi3Helper.Core/Lang/th_TH.json index 036e80e32..678a2d4ca 100644 --- a/Hi3Helper.Core/Lang/th_TH.json +++ b/Hi3Helper.Core/Lang/th_TH.json @@ -159,7 +159,7 @@ "GamePlaytime_Running_Info1": "อินสแตนซ์ของเกมนี้กำลังทำงานอยู่ ดังนั้นเวลาที่เล่นจึงไม่สามารถแก้ไขได้", "GamePlaytime_Running_Info2": "โปรดทราบว่าการปิด Collapse จะหยุดการติดตามเวลาที่เล่น (จะบันทึกแค่ตอนที่กำลังทำงานอยู่) และจะติดตามเฉพาะเปิดด้วย Collapse เท่านั้น", "GamePlaytime_Display": "{0}h {1}m", - "GamePlaytime_ToolTipDisplay": "Last opened in {0:00}/{1:00}/{2:0000} at {3:00}:{4:00}", + "GamePlaytime_DateDisplay": "{0:00}/{1:00}/{2:0000} {3:00}:{4:00}", "PostPanel_Events": "กิจกรรม", "PostPanel_Notices": "การ​แจ้งเตือน", diff --git a/Hi3Helper.Core/Lang/uk_UA.json b/Hi3Helper.Core/Lang/uk_UA.json index a1e23125f..56ecd3a95 100644 --- a/Hi3Helper.Core/Lang/uk_UA.json +++ b/Hi3Helper.Core/Lang/uk_UA.json @@ -81,6 +81,8 @@ "UnhandledSubtitle3": "Вилетіла гра з деталями про помилку знизу:", "UnhandledTitle4": "Увага", "UnhandledSubtitle4": "Це не велика проблема, але думаємо, що краще вас повідомити:\r\nЧерез тестування A/B miHoYo, Collapse не підтримує читання цієї клавіші: App_Settings_h2319593470ю\r\nПросимо вибачення за незручність та дякуємо за ваше розуміння.", + "UnhandledTitleDiskCrc": "Виявлено пошкодження диска!", + "UnhandledSubDiskCrc": "Виявлено пошкодження диска під час доступу до файлу. Будь ласка, перевірте диск за допомогою команди chkdsk.", "CopyClipboardBtn1": "Скопіювати все", "CopyClipboardBtn2": "Скопійовано в буфер обміну!", "GoBackPageBtn1": "Повернутися до минулої сторінки" @@ -146,6 +148,7 @@ "GameSettings_Panel2UninstallGame": "Видалити гру", "GameSettings_Panel2ConvertVersion": "Змінити версію гри", "GameSettings_Panel2MoveGameLocationGame": "Перемістити гру", + "GameSettings_Panel2MoveGameLocationGame_SamePath": "Не вдається перемістити гру в кореневий каталог вашого диска!\r\nБудь ласка, створіть папку та спробуйте знову.", "GameSettings_Panel2StopGame": "Примусово закрити гру", "GameSettings_Panel3": "Кастомні команди запуску", "GameSettings_Panel3CustomBGRegion": "Змінити фон регіону", @@ -163,9 +166,17 @@ "GamePlaytime_Idle_ResetBtn": "Скинути", "GamePlaytime_Idle_ChangeBtn": "Змінити", "GamePlaytime_Running_Info1": "Ця гра вже запущена, тому час гри змінити не можна.", - "GamePlaytime_Running_Info2": "Будь ласка, пам'ятайте, що повне закриття Collapse зупинить відслідження часу гри (збережеться те, що було дограно до того часу), і відсліджуватися будуть лише сесії запущені за допомогою Collapse.", + "GamePlaytime_Running_Info2": "Зверніть увагу, що повне закриття Collapse зупинить відстеження часу гри (збереження того, що було зіграно до цього моменту).", "GamePlaytime_Display": "{0}г {1}хв", - "GamePlaytime_ToolTipDisplay": "Останнє відкриття в {0:00}/{1:00}/{2:0000} о {3:00}:{4:00}", + "GamePlaytime_DateDisplay": "{0:00}/{1:00}/{2:0000} {3:00}:{4:00}", + "GamePlaytime_Stats_Title": "Статистика ігрового часу", + "GamePlaytime_Stats_NeverPlayed": "Ніколи не грав", + "GamePlaytime_Stats_LastSession": "Останній сеанс", + "GamePlaytime_Stats_LastSession_StartTime": "Час початку", + "GamePlaytime_Stats_LastSession_Duration": "Тривалість", + "GamePlaytime_Stats_Daily": "Сьогодні", + "GamePlaytime_Stats_Weekly": "Тиждень", + "GamePlaytime_Stats_Monthly": "Місяць", "PostPanel_Events": "Події", "PostPanel_Notices": "Примітки", @@ -194,7 +205,10 @@ "GameStateInvalidFixed_Subtitle1": "Виправлено властивості файлів гри для регіону:", "GameStateInvalidFixed_Subtitle2": "Будь ласка, виконайте \"", "GameStateInvalidFixed_Subtitle3": "\" в \"", - "GameStateInvalidFixed_Subtitle4": "\", щоб переконатися, що всі файли перевірено." + "GameStateInvalidFixed_Subtitle4": "\", щоб переконатися, що всі файли перевірено.", + + "InstallFolderRootTitle": "Кореневий каталог диска надано!", + "InstallFolderRootSubtitle": "Не вдається встановити гру в кореневий каталог вашого диска!\nБудь ласка, створіть нову папку та спробуйте знову." }, "_GameRepairPage": { @@ -458,6 +472,8 @@ "SophonHttpNumberBox": "Максимальная кількість HTTP-з'єднань", "SophonHelp_Http": "Це налаштовує максимальну кількість мережевих з'єднань, які встановлює Collapse для завантаження частин.", "SophonToggle": "Увімкнути Sophon у підтримуваних регіонах", + "SophonPredownPerfMode_Toggle": "[ЕКСПЕРИМЕНТАЛЬНО] Використовуйте всі ядра процесора під час застосування попереднього завантаження.", + "SophonPredownPerfMode_Tooltip": "Увімкнення цього параметра встановить максимальну кількість потоків процесора для вашої системи. Вимкніть, якщо виникають проблеми.", "AppThreads_Attention": "Увага!", "AppThreads_Attention1": "Перед тим, як змінити", @@ -466,14 +482,18 @@ "AppThreads_Attention4": "значення, якщо у вас є активне завантаження, оскільки це може", "AppThreads_Attention5": "ПЕРЕЗАВАНТАЖИТИ ВСЕ ЦЕ", "AppThreads_Attention6": "через те, що кількість сеансів, необхідних для завантаження, не збігається.", + "AppThreads_AttentionTop1": "Наведені нижче проблеми більше не виникнуть, якщо у вас є", + "AppThreads_AttentionTop2": "включене налаштування.", "DiscordRPC": "Discord Rich Presense", "DiscordRPC_Toggle": "Показувати Discord Rich Presence", "DiscordRPC_GameStatusToggle": "Показувати поточну гру в статусі Discord", "DiscordRPC_IdleStatusToggle": "Показувати RPC в режимі очікування", + "ImageBackground": "Налаштування фону зображення.", "VideoBackground": "Налаштування фонового відео", "VideoBackground_IsEnableAudio": "Увімкнути аудіо", + "VideoBackground_IsEnableAcrylicBackground": "Використовувати акриловий ефект під час використання відео фону.", "VideoBackground_AudioVolume": "Гучність звуку", "Update": "Перевірити оновлення", @@ -522,6 +542,7 @@ "EnableAcrylicEffect": "Використовувати ефект розмиття", "EnableDownloadChunksMerging": "Об'єднати частини завантаженого пакету", + "Enforce7ZipExtract": "Завжди використовуйте 7-zip для встановлення/оновлення гри.", "UseExternalBrowser": "Завжди використовувати зовнішній браузер", "LowerCollapsePrioOnGameLaunch": "Знизити використані процесом Collapse ресурси, коли гра запущена", @@ -581,7 +602,40 @@ "NetworkSettings_Http_Redirect": "Дозволити перенаправлення HTTP", "NetworkSettings_Http_SimulateCookies": "Дозволити імітувати HTTP файли cookie", "NetworkSettings_Http_UntrustedHttps": "Дозволити ненадійний сертифікат HTTPS", - "NetworkSettings_Http_Timeout": "Тайм-аут клієнта HTTP (у секундах)" + "NetworkSettings_Http_Timeout": "Час очікування HTTP-клієнта (в секундах).", + + "FileDownloadSettings_Title": "Налаштування завантаження файлів.", + "FileDownloadSettings_SpeedLimit_Title": "Обмежити швидкість завантаження.", + "FileDownloadSettings_SpeedLimit_NumBox": "Обмеження швидкості (в МіБ).", + "FileDownloadSettings_SpeedLimitHelp1": "За замовчуванням: ", + "FileDownloadSettings_SpeedLimitHelp2": "Діапазон значень обмеження швидкості:", + "FileDownloadSettings_SpeedLimitHelp3": "1 - 1000 МіБ/с", + "FileDownloadSettings_SpeedLimitHelp4": "Обмежує максимальну дозволену пропускну здатність для завантаження. Це налаштування не можна використовувати разом з", + "FileDownloadSettings_SpeedLimitHelp5": "Налаштування.", + + "FileDownloadSettings_NewPreallocChunk_Title": "Новий попередньо виділений завантажувач", + "FileDownloadSettings_NewPreallocChunk_Subtitle": "Тільки для встановлення гри, ремонту гри та оновлень кешу.", + "FileDownloadSettings_NewPreallocChunk_NumBox": "Розмір фрагмента (в МіБ)", + "FileDownloadSettings_NewPreallocChunkHelp1": "За замовчуванням:", + "FileDownloadSettings_NewPreallocChunkHelp2": "Діапазон значень розміру фрагмента:", + "FileDownloadSettings_NewPreallocChunkHelp3": "1 - 32 МіБ", + "FileDownloadSettings_NewPreallocChunkHelp4": "Якщо увімкнено, завантажувач динамічно розподілятиме розмір файлу під час його завантаження.", + "FileDownloadSettings_NewPreallocChunkHelp5": "Це дозволяє завантажувачу безпосередньо записувати фрагменти даних у файл без розділення їх на окремі файли.", + "FileDownloadSettings_NewPreallocChunkHelp6": "Якщо цей параметр вимкнено, програма запуску використовуватиме старий метод розподілу, коли фрагменти даних записуватимуться в окремі файли. Також буде вимкнено", + "FileDownloadSettings_NewPreallocChunkHelp7": "також", + "FileDownloadSettings_NewPreallocChunkHelp8": "Налаштування.", + "FileDownloadSettings_NewPreallocChunkHelp9": "Примітка:", + "FileDownloadSettings_NewPreallocChunkHelp10": "Ця функція доступна лише для встановлення гри (таких як первинне встановлення, оновлення та попереднє завантаження), ремонту гри та етапів оновлення кешу.", + + "FileDownloadSettings_BurstDownload_Title": "Режим завантаження файлів з максимальним потоком", + "FileDownloadSettings_BurstDownload_Subtitle": "Лише для відновлення гри та оновлення кешу.", + "FileDownloadSettings_BurstDownloadHelp1": "За замовчуванням:", + "FileDownloadSettings_BurstDownloadHelp2": "Якщо увімкнено, ця функція дозволить процес завантаження на", + "FileDownloadSettings_BurstDownloadHelp3": "Також", + "FileDownloadSettings_BurstDownloadHelp4": "функції для паралельного запуску, щоб зробити процес завантаження більш ефективним.", + "FileDownloadSettings_BurstDownloadHelp5": "Коли вимкнено, процес завантаження для", + "FileDownloadSettings_BurstDownloadHelp6": "Також", + "FileDownloadSettings_BurstDownloadHelp7": "будуть використовувати послідовний процес завантаження." }, "_Misc": { @@ -709,7 +763,11 @@ "LauncherNameSteam": "Steam", "LauncherNameUnknown": "(Назва лаунчера невідома)", - "ImageCropperTitle": "Обрізати зображення" + "ImageCropperTitle": "Обрізати зображення", + + "IsBytesMoreThanBytes": "= {0} байт (-/+ {1})", + "IsBytesUnlimited": "= Необмежено", + "IsBytesNotANumber": "= NaN" }, "_BackgroundNotification": { @@ -756,7 +814,7 @@ "RepairCompletedSubtitle": "{0} файл(ів) було відновлено.", "RepairCompletedSubtitleNoBroken": "Пошкоджених файлів не знайдено.", "ExtremeGraphicsSettingsWarnTitle": "Пресет дуже високої графіки обрано!", - "ExtremeGraphicsSettingsWarnSubtitle": "Ви намагаєтеся встановити пресет на дуже високу графіку!\r\nДуже висока графіка - це практично 2х-кратний масштаб рендерингу з ввівкненим MSAA і вона ДУЖЕ НЕОПТИМІЗОВАНА!\r\n\r\nВи впевнені, що хочете використати це налаштування?", + "ExtremeGraphicsSettingsWarnSubtitle": "Ви збираєтеся встановити налаштування параметра на \"Дуже високий\"!\r\nналаштування параметра на \"Дуже високий\" - це, по суті, 1,6-кратний масштаб рендерингу з увімкненим MSAA, який є ДУЖЕ неоптимізованим!\r\n\r\nВи впевнені, що хочете використовувати цей параметр?", "MigrateExistingMoveDirectoryTitle": "Переміщаємо існуюче встановлення для: {0}", "MigrateExistingInstallChoiceTitle": "Знайдено існуюче встановлення {0}!", "MigrateExistingInstallChoiceSubtitle1": "Ви маєте існуюче встановлення гри використовуючи {0} у цій директорії:", @@ -821,7 +879,7 @@ "ChangePlaytimeTitle": "Ви впевнені, що бажаєте змінити час гри?", "ChangePlaytimeSubtitle": "Зміна часу гри означає перезапис поточного значення на введене вами. \n\nЧи бажаєте ви продовжити?\n\nПримітка: Це не впливає на роботу Collapse, і ви можете змінити це значення знову в будь-який час, коли не граєте в гру.", "ResetPlaytimeTitle": "Ви впевнені, що бажаєте скинути час гри?", - "ResetPlaytimeSubtitle": "Скидання часу гри означає встановлення часу гри назад на 0. Це", + "ResetPlaytimeSubtitle": "Скидання часу гри означає встановлення лічильника часу гри та всіх пов'язаних статистичних даних на 0. Це", "ResetPlaytimeSubtitle2": "руйнівна", "ResetPlaytimeSubtitle3": "дія, що означає що ви не можете скасувати зміни після підтвердження.\n\nЧи бажаєте ви продовжити?\n\nПримітка: Це не впливає на роботу Collapse, і ви можете змінити це значення знову в будь-який час, коли не граєте в гру.", "InvalidPlaytimeTitle": "Виникла проблема із збереженням ігрового часу цієї сесії", @@ -837,7 +895,7 @@ "StopGameTitle": "Примусово зупинити гру", "StopGameSubtitle": "Ви впевнені, що хочете примусово зупинити поточну гру?\r\nВи можете втратити деякий прогрес в грі.", - + "MeteredConnectionWarningTitle": "Виявлено обмежене підключення!", "MeteredConnectionWarningSubtitle": "Ваше поточне Інтернет-підключення було виявлено як \"обмежене\"! Продовження процесу оновлення може призвести до додаткових витрат від вашого Інтернет провайдеру. Чи бажаєте ви продовжити?", @@ -886,7 +944,7 @@ "DownloadSettingsTitle": "Завантажити налаштування", "DownloadSettingsOption1": "Запустіть гру після встановлення", - + "OpenInExternalBrowser": "Відкрити в зовнішньому браузері", "CloseOverlay": "Закрити накладання" }, @@ -1412,5 +1470,34 @@ "Audio_PlaybackDev_Headphones": "Навушники", "Audio_PlaybackDev_Speakers": "Колонки", "Audio_PlaybackDev_TV": "ТБ" + }, + + "_NotificationToast": { + "WindowHiddenToTray_Title": "Згорнути програму запуску в системний трей.", + "WindowHiddenToTray_Subtitle": "Тепер програма запуску працює у фоновому режимі.\r\nНатисніть на це повідомлення або на іконку в треї, щоб відновити вікно.", + + "GameInstallCompleted_Title": "{0} готовий до гри.", + "GameInstallCompleted_Subtitle": "{0} було успішно встановлено!", + + "GameUpdateCompleted_Title": "{0} було оновлено", + "GameUpdateCompleted_Subtitle": "{0} успішно оновлено до v{1}!", + + "GamePreloadCompleted_Title": "Попереднє завантаження для {0} завантажено", + + "GameRepairCheckCompleted_Title": "Перевірку відновлення гри завершено", + "GameRepairCheckCompletedFound_Subtitle": "{0} файл(и) потрібно оновити/виправити. Натисніть це сповіщення, щоб повернутися до панелі запуску.", + "GameRepairCheckCompletedNotFound_Subtitle": "Файли не потрібно оновлювати/виправляти.", + + "GameRepairDownloadCompleted_Title": "Завантаження відновлення гри завершено", + "GameRepairDownloadCompleted_Subtitle": "{0} файл(и) успішно оновлено/виправлено.", + + "CacheUpdateCheckCompleted_Title": "Перевірка оновлення кешу завершена", + "CacheUpdateCheckCompletedFound_Subtitle": "{0} файл(и) кешу потрібно оновити. Натисніть на це сповіщення, щоб повернутися до панелі запуску.", + "CacheUpdateCheckCompletedNotFound_Subtitle": "Файли кешу не потрібно оновлювати.", + + "CacheUpdateDownloadCompleted_Title": "Завантаження оновлення кешу завершено", + "CacheUpdateDownloadCompleted_Subtitle": "{0} файл(и) кешу успішно оновлено.", + + "GenericClickNotifToGoBack_Subtitle": "Натисніть на це сповіщення, щоб повернутися до панелі запуску." } } diff --git a/Hi3Helper.Core/Lang/vi_VN.json b/Hi3Helper.Core/Lang/vi_VN.json index 0c684663b..7ac2cf19d 100644 --- a/Hi3Helper.Core/Lang/vi_VN.json +++ b/Hi3Helper.Core/Lang/vi_VN.json @@ -168,7 +168,7 @@ "GamePlaytime_Running_Info1": "Tiến trình của trò chơi này hiện đang chạy, do đó không thể chỉnh sửa thời gian chơi.", "GamePlaytime_Running_Info2": "Xin lưu ý rằng việc đóng hoàn toàn Collapse sẽ tạm dừng theo dõi thời gian chơi (thời gian chơi cho đến thời điểm đóng lại) & Collapse sẽ tiếp tục theo dõi khi được mở lại.", "GamePlaytime_Display": "{0}h {1}m", - "GamePlaytime_ToolTipDisplay": "Lần cuối mở vào lúc {3:00}:{4:00} ngày {0:00}/{1:00}/{2:0000}", + "GamePlaytime_DateDisplay": "{3:00}:{4:00} {0:00}/{1:00}/{2:0000}", "PostPanel_Events": "Sự kiện", "PostPanel_Notices": "Thông báo", diff --git a/Hi3Helper.Core/Lang/zh_CN.json b/Hi3Helper.Core/Lang/zh_CN.json index c76276546..dc92de8fa 100644 --- a/Hi3Helper.Core/Lang/zh_CN.json +++ b/Hi3Helper.Core/Lang/zh_CN.json @@ -168,7 +168,7 @@ "GamePlaytime_Running_Info1": "目前游戏有一个进程在运行,所以无法编辑游戏时间。", "GamePlaytime_Running_Info2": "请注意,完全关闭 Collapse 会导致游戏时间记录停止(之前的记录会被保存),并且时间会在下次 Collapse 启动时再次开始记录。", "GamePlaytime_Display": "{0}小时 {1}分钟", - "GamePlaytime_ToolTipDisplay": "最后打开于 {2:0000}/{1:00}/{0:00}-{3:00}:{4:00}", + "GamePlaytime_DateDisplay": "{2:0000}/{1:00}/{0:00}-{3:00}:{4:00}", "PostPanel_Events": "活动", "PostPanel_Notices": "公告", diff --git a/Hi3Helper.Core/Lang/zh_TW.json b/Hi3Helper.Core/Lang/zh_TW.json index e7afad87c..efb9ecbc9 100644 --- a/Hi3Helper.Core/Lang/zh_TW.json +++ b/Hi3Helper.Core/Lang/zh_TW.json @@ -163,7 +163,7 @@ "GamePlaytime_Running_Info1": "此遊戲的一個實例正在執行中,因此無法更改遊玩時數。", "GamePlaytime_Running_Info2": "請注意,完全關閉 Collapse 將停止遊玩時數追蹤(先前的時數將會儲存),且只有由 Collapse 啟動的遊戲實例會被追蹤。", "GamePlaytime_Display": "{0}時 {1}分", - "GamePlaytime_ToolTipDisplay": "最後打開於 {2:0000}/{1:00}/{0:00}-{3:00}:{4:00}", + "GamePlaytime_DateDisplay": "{2:0000}/{1:00}/{0:00}-{3:00}:{4:00}", "PostPanel_Events": "活動", "PostPanel_Notices": "公告", diff --git a/Hi3Helper.Core/packages.lock.json b/Hi3Helper.Core/packages.lock.json index dbd9fe431..4a7380f45 100644 --- a/Hi3Helper.Core/packages.lock.json +++ b/Hi3Helper.Core/packages.lock.json @@ -1,23 +1,35 @@ { "version": 1, "dependencies": { - "net8.0": { + "net9.0-windows10.0.22621": { + "Microsoft.NET.ILLink.Tasks": { + "type": "Direct", + "requested": "[9.0.0-rc.2.24473.5, )", + "resolved": "9.0.0-rc.2.24473.5", + "contentHash": "mXyCgKqBIo/KOpY09FqjOU8d+LME2qowzzvBd1/viYmSpAKYfQ2w1WDOlh0FniY4xnrB/wmDiAo4frJvfCqT0w==" + }, + "Microsoft.Windows.CsWinRT": { + "type": "Direct", + "requested": "[2.1.5, )", + "resolved": "2.1.5", + "contentHash": "PG0uVrpPTVEhqu70YhGMTyRKZXNgygjIIwdjAmg2hhHkmm6367TafBEdzIU/TgMXy2+x5Lv/Z9MKJehwkQXEvw==" + }, "Google.Protobuf": { "type": "Transitive", - "resolved": "3.28.1", - "contentHash": "i4EN7Z+OUdoRBNiVMIG6CfMh6UowXiUx+BKgE+GHLbAX5ArSmpUTFUDwgRNwNfYdosl6GXuBlDiHCcXSHw43+A==" + "resolved": "3.28.2", + "contentHash": "Z86ZKAB+v1B/m0LTM+EVamvZlYw/g3VND3/Gs4M/+aDIxa2JE9YPKjDxTpf0gv2sh26hrve3eI03brxBmzn92g==" }, "System.IO.Hashing": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "ne1843evDugl0md7Fjzy6QjJrzsjh46ZKbhf8GwBXb5f/gw97J4bxMs0NQKifDuThh/f0bZ0e62NPl1jzTuRqA==" + "resolved": "9.0.0-rc.1.24431.7", + "contentHash": "giMiDNlne8WcLG7rsEfrETaukDUTBPn8a97QRo9LYR71O0Rb4SJ6TbfGdQ9oiBYldVPSAbSQANb5ThLZOo408Q==" }, "hi3helper.enctool": { "type": "Project", "dependencies": { - "Google.Protobuf": "[3.28.1, )", + "Google.Protobuf": "[3.28.2, )", "Hi3Helper.Http": "[2.0.0, )", - "System.IO.Hashing": "[8.0.0, )" + "System.IO.Hashing": "[9.0.0-rc.1.24431.7, )" } }, "hi3helper.http": { diff --git a/Hi3Helper.EncTool b/Hi3Helper.EncTool index f90f1f961..d692d38e9 160000 --- a/Hi3Helper.EncTool +++ b/Hi3Helper.EncTool @@ -1 +1 @@ -Subproject commit f90f1f961b2fe5e47383d5f797c6ab48382a9c3b +Subproject commit d692d38e9770e75a2b11c1cee5253f9be128517f diff --git a/Hi3Helper.EncTool.Test/Hi3Helper.EncTool.Test.csproj b/Hi3Helper.EncTool.Test/Hi3Helper.EncTool.Test.csproj index 7b361f868..06bde6394 100644 --- a/Hi3Helper.EncTool.Test/Hi3Helper.EncTool.Test.csproj +++ b/Hi3Helper.EncTool.Test/Hi3Helper.EncTool.Test.csproj @@ -2,15 +2,11 @@ Exe - net8.0 + net9.0 enable enable x64 + true - - - - - diff --git a/Hi3Helper.EncTool.Test/Program.cs b/Hi3Helper.EncTool.Test/Program.cs index 4ef7d0905..141a21edb 100644 --- a/Hi3Helper.EncTool.Test/Program.cs +++ b/Hi3Helper.EncTool.Test/Program.cs @@ -1,5 +1,4 @@ -using Hi3Helper.EncTool.Parser.AssetMetadata; -using System.IO.Compression; +using System.IO.Compression; namespace Hi3Helper.EncTool.Test { diff --git a/Hi3Helper.EncTool.Test/packages.lock.json b/Hi3Helper.EncTool.Test/packages.lock.json index dbd9fe431..6ab45918f 100644 --- a/Hi3Helper.EncTool.Test/packages.lock.json +++ b/Hi3Helper.EncTool.Test/packages.lock.json @@ -1,27 +1,12 @@ { "version": 1, "dependencies": { - "net8.0": { - "Google.Protobuf": { - "type": "Transitive", - "resolved": "3.28.1", - "contentHash": "i4EN7Z+OUdoRBNiVMIG6CfMh6UowXiUx+BKgE+GHLbAX5ArSmpUTFUDwgRNwNfYdosl6GXuBlDiHCcXSHw43+A==" - }, - "System.IO.Hashing": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "ne1843evDugl0md7Fjzy6QjJrzsjh46ZKbhf8GwBXb5f/gw97J4bxMs0NQKifDuThh/f0bZ0e62NPl1jzTuRqA==" - }, - "hi3helper.enctool": { - "type": "Project", - "dependencies": { - "Google.Protobuf": "[3.28.1, )", - "Hi3Helper.Http": "[2.0.0, )", - "System.IO.Hashing": "[8.0.0, )" - } - }, - "hi3helper.http": { - "type": "Project" + "net9.0": { + "Microsoft.NET.ILLink.Tasks": { + "type": "Direct", + "requested": "[9.0.0-rc.2.24473.5, )", + "resolved": "9.0.0-rc.2.24473.5", + "contentHash": "mXyCgKqBIo/KOpY09FqjOU8d+LME2qowzzvBd1/viYmSpAKYfQ2w1WDOlh0FniY4xnrB/wmDiAo4frJvfCqT0w==" } } } diff --git a/Hi3Helper.Http b/Hi3Helper.Http index bc6c11a16..3227ed8de 160000 --- a/Hi3Helper.Http +++ b/Hi3Helper.Http @@ -1 +1 @@ -Subproject commit bc6c11a161fdee7f9c18616d6e1ef5a6d3dbbd55 +Subproject commit 3227ed8deab3dc1bdfa0149a40e75189d5b7cee4 diff --git a/Hi3Helper.SharpDiscordRPC b/Hi3Helper.SharpDiscordRPC index c9a5866a3..53cc86899 160000 --- a/Hi3Helper.SharpDiscordRPC +++ b/Hi3Helper.SharpDiscordRPC @@ -1 +1 @@ -Subproject commit c9a5866a38c59b81d40311683890052df78f3166 +Subproject commit 53cc86899ac81bff70628d6631558baf533d6775 diff --git a/Hi3Helper.Sophon b/Hi3Helper.Sophon index a13c3ed0a..05d54fc51 160000 --- a/Hi3Helper.Sophon +++ b/Hi3Helper.Sophon @@ -1 +1 @@ -Subproject commit a13c3ed0abb418060afe9069b7a163c9d0b3450e +Subproject commit 05d54fc5144c85c2c3012fd9a45629e747b6ce4f diff --git a/Hi3Helper.TaskScheduler/FodyWeavers.xml b/Hi3Helper.TaskScheduler/FodyWeavers.xml new file mode 100644 index 000000000..5029e7060 --- /dev/null +++ b/Hi3Helper.TaskScheduler/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Hi3Helper.TaskScheduler/FodyWeavers.xsd b/Hi3Helper.TaskScheduler/FodyWeavers.xsd new file mode 100644 index 000000000..05e92c114 --- /dev/null +++ b/Hi3Helper.TaskScheduler/FodyWeavers.xsd @@ -0,0 +1,141 @@ + + + + + + + + + + + + A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks + + + + + A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. + + + + + A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks + + + + + A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. + + + + + A list of unmanaged 32 bit assembly names to include, delimited with line breaks. + + + + + A list of unmanaged 64 bit assembly names to include, delimited with line breaks. + + + + + The order of preloaded assemblies, delimited with line breaks. + + + + + + This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file. + + + + + Controls if .pdbs for reference assemblies are also embedded. + + + + + Controls if runtime assemblies are also embedded. + + + + + Controls whether the runtime assemblies are embedded with their full path or only with their assembly name. + + + + + Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option. + + + + + As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off. + + + + + Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code. + + + + + Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior. + + + + + A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with | + + + + + A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |. + + + + + A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with | + + + + + A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |. + + + + + A list of unmanaged 32 bit assembly names to include, delimited with |. + + + + + A list of unmanaged 64 bit assembly names to include, delimited with |. + + + + + The order of preloaded assemblies, delimited with |. + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/Hi3Helper.TaskScheduler/Hi3Helper.TaskScheduler.csproj b/Hi3Helper.TaskScheduler/Hi3Helper.TaskScheduler.csproj new file mode 100644 index 000000000..365f8fce4 --- /dev/null +++ b/Hi3Helper.TaskScheduler/Hi3Helper.TaskScheduler.csproj @@ -0,0 +1,34 @@ + + + + Exe + netframework462 + disable + x64 + en + portable + Task Scheduler Shell + Task Scheduler Shell + Collapse Launcher's Task Scheduler Shell + Collapse Launcher's Task Scheduler Shell + Collapse Launcher Team + $(Company). neon-nyan, Cry0, bagusnl, shatyuka, gablm. + Copyright 2022-2024 $(Company) + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/Hi3Helper.TaskScheduler/Program.cs b/Hi3Helper.TaskScheduler/Program.cs new file mode 100644 index 000000000..8fc3fc71c --- /dev/null +++ b/Hi3Helper.TaskScheduler/Program.cs @@ -0,0 +1,340 @@ +using Microsoft.Win32.TaskScheduler; +using NuGet.Versioning; +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using Velopack; +using Velopack.Locators; +using Velopack.NuGet; +using Velopack.Windows; +using TaskSched = Microsoft.Win32.TaskScheduler.Task; + +namespace Hi3Helper.TaskScheduler +{ + public static class ReturnExtension + { + public static int ReturnValAsConsole(this int returnVal) + { + Console.WriteLine($"RETURNVAL_{returnVal}"); + return returnVal; + } + } + + public class Program + { + static int PrintUsage() + { + string executableName = Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().MainModule.FileName); + Console.WriteLine($"Usage:\r\n{executableName} [IsEnabled] \"Scheduler name\" \"Executable path\""); + Console.WriteLine($"{executableName} [Enable] \"Scheduler name\" \"Executable path\""); + Console.WriteLine($"{executableName} [EnableToTray] \"Scheduler name\" \"Executable path\""); + Console.WriteLine($"{executableName} [Disable] \"Scheduler name\" \"Executable path\""); + Console.WriteLine($"{executableName} [DisableToTray] \"Scheduler name\" \"Executable path\""); + Console.WriteLine($"{executableName} [RecreateIcons] \"Executable path\""); + return int.MaxValue; + } + + static int Main(string[] args) + { + try + { + if (args.Length == 2 && args[0].ToLower() == "recreateicons") + return RecreateIcons(args[1]); + + if (args.Length < 3) + return PrintUsage().ReturnValAsConsole(); + + string action = args[0].ToLower(); + string schedName = args[1]; + string execPath = args[2]; + + switch (action) + { + case "isenabled": + return IsEnabled(schedName, execPath).ReturnValAsConsole(); + case "enable": + ToggleTask(true, false, schedName, execPath); + break; + case "enabletotray": + ToggleTask(true, true, schedName, execPath); + break; + case "disable": + ToggleTask(false, false, schedName, execPath); + break; + case "disabletotray": + ToggleTask(false, true, schedName, execPath); + break; + default: + return PrintUsage().ReturnValAsConsole(); + } + + WriteConsole($"Operation: {action} \"{schedName}\" \"{execPath}\" has been executed!"); + } + catch (Exception ex) + { + WriteConsole($"An unexpected error has occurred!\r\n{ex}"); + return int.MinValue.ReturnValAsConsole(); + } + + return 0.ReturnValAsConsole(); + } + + static int RecreateIcons(string executablePath) + { +#pragma warning disable CS0618 // Type or member is obsolete + new Shortcuts(null, new CollapseVelopackLocator(executablePath)).CreateShortcut(executablePath, ShortcutLocation.Desktop | ShortcutLocation.StartMenuRoot, false, null); +#pragma warning restore CS0618 // Type or member is obsolete + return 0; + } + + static TaskSched Create(TaskService taskService, string schedName, string execPath) + { + using (TaskDefinition taskDefinition = TaskService.Instance.NewTask()) + { + taskDefinition.RegistrationInfo.Author = "CollapseLauncher"; + taskDefinition.RegistrationInfo.Description = "Run Collapse Launcher automatically when computer starts"; + taskDefinition.Principal.LogonType = TaskLogonType.InteractiveToken; + taskDefinition.Principal.RunLevel = TaskRunLevel.Highest; + taskDefinition.Settings.Enabled = false; + taskDefinition.Triggers.Add(new LogonTrigger()); + taskDefinition.Actions.Add(new ExecAction(execPath)); + + TaskSched task = taskService.RootFolder.RegisterTaskDefinition(schedName, taskDefinition); + WriteConsole($"New task schedule has been created!"); + return task; + } + } + + static void TryDelete(TaskService taskService, string schedName) + { + // Try get the tasks + TaskSched[] tasks = taskService.FindAllTasks(new System.Text.RegularExpressions.Regex(schedName), false); + + // If null, then ignore + if (tasks == null || tasks.Length == 0) + { + WriteConsole($"None of the existing task: {schedName} exist but trying to delete, ignoring!"); + return; + } + + // Remove possible tasks + foreach (TaskSched task in tasks) + { + using (task) + { + WriteConsole($"Deleting redundant task: {task.Name}"); + taskService.RootFolder.DeleteTask(task.Name); + } + } + } + + static TaskSched GetExistingTask(TaskService taskService, string schedName, string execPath) + { + // Try get the tasks + TaskSched[] tasks = taskService.FindAllTasks(new System.Text.RegularExpressions.Regex(schedName), false); + + // Try get the first task + TaskSched task = tasks? + .FirstOrDefault(x => + x.Name.Equals(schedName, StringComparison.OrdinalIgnoreCase)); + + // Return null as empty + if (task == null) + { + return null; + } + + // Get actionPath + string actionPath = task.Definition.Actions?.FirstOrDefault()?.ToString(); + + // If actionPath is null, then return null as empty + if (string.IsNullOrEmpty(actionPath)) + { + return null; + } + + // if actionPath isn't matched, then replace with current executable path + if (!actionPath.StartsWith(execPath, StringComparison.OrdinalIgnoreCase)) + { + // Check if the last action path runs on tray + bool isLastHasTray = actionPath.EndsWith("tray", StringComparison.OrdinalIgnoreCase); + + // Register changes + task.Definition.Actions.Clear(); + task.Definition.Actions.Add(new ExecAction(execPath, isLastHasTray ? "tray" : null)); + task.RegisterChanges(); + } + + // If the task matches, then return the task + return task; + } + + static int IsEnabled(string schedName, string execPath) + { + using (TaskService taskService = new TaskService()) + { + // Get the task + TaskSched task = GetExistingTask(taskService, schedName, execPath); + + // If the task is not null, then do further check + if (task != null) + { + // Check if it's enabled with tray + bool isOnTray = task.Definition?.Actions?.FirstOrDefault()?.ToString()?.EndsWith("tray", StringComparison.OrdinalIgnoreCase) ?? false; + + // If the task definition is enabled, then return 1 (true) or 2 (true with tray) + if (task.Definition.Settings.Enabled) + return isOnTray ? 2 : 1; + + // Otherwise, if the task exist but not enabled, then return 0 (false) or -1 (false with tray) + return isOnTray ? -1 : 0; + } + } + + // Otherwise, return 0 + return 0; + } + + static void ToggleTask(bool isEnabled, bool isStartupToTray, string schedName, string execPath) + { + using (TaskService taskService = new TaskService()) + { + // Try get existing task + TaskSched task = GetExistingTask(taskService, schedName, execPath); + + try + { + // If the task is null due to its' non existence or + // there are some unmatched tasks, then try recreate the task + // by try deleting and create a new one. + if (task == null) + { + TryDelete(taskService, schedName); + task = Create(taskService, schedName, execPath); + } + + // Try clear the existing actions and set the new one + task.Definition.Actions.Clear(); + task.Definition.Actions.Add(new ExecAction(execPath, isStartupToTray ? "tray" : null)); + task.Definition.Settings.Enabled = isEnabled; + } + finally + { + // Register the changes if it's not null. + if (task != null) + { + task.RegisterChanges(); + task.Dispose(); + WriteConsole($"ToggledStatus: isEnabled -> {isEnabled} & isStartupToTray -> {isStartupToTray}"); + } + } + } + } + + static void WriteConsole(string message) => + Console.WriteLine(message); + } + + internal class CollapseVelopackLocator : VelopackLocator + { + const string SpecVersionFileName = "sq.version"; + + /// + public override string AppId { get; } + + /// + public override string RootAppDir { get; } + + /// + public override string UpdateExePath { get; } + + /// + public override string AppContentDir { get; } + + /// + public override SemanticVersion CurrentlyInstalledVersion { get; } + + /// + public override string PackagesDir => CreateSubDirIfDoesNotExist(RootAppDir, "packages"); + + /// + public override bool IsPortable => + RootAppDir != null ? File.Exists(Path.Combine(RootAppDir, ".portable")) : false; + + /// + public override string Channel { get; } + + /// + /// Internal use only. Auto detect app details from the specified EXE path. + /// + internal CollapseVelopackLocator(string ourExePath) + : base(null) + { + if (!VelopackRuntimeInfo.IsWindows) + throw new NotSupportedException("Cannot instantiate WindowsLocator on a non-Windows system."); + + // We try various approaches here. Firstly, if Update.exe is in the parent directory, + // we use that. If it's not present, we search for a parent "current" or "app-{ver}" directory, + // which could designate that this executable is running in a nested sub-directory. + // There is some legacy code here, because it's possible that we're running in an "app-{ver}" + // directory which is NOT containing a sq.version, in which case we need to infer a lot of info. + + ourExePath = Path.GetFullPath(ourExePath); + string myDirPath = Path.GetDirectoryName(ourExePath); + var myDirName = Path.GetFileName(myDirPath); + var possibleUpdateExe = Path.GetFullPath(Path.Combine(myDirPath, "..", "Update.exe")); + var ixCurrent = ourExePath.LastIndexOf("/current/", StringComparison.InvariantCultureIgnoreCase); + + Console.WriteLine($"Initializing {nameof(CollapseVelopackLocator)}"); + + if (File.Exists(possibleUpdateExe)) + { + Console.WriteLine("Update.exe found in parent directory"); + // we're running in a directory with an Update.exe in the parent directory + var manifestFile = Path.Combine(myDirPath, SpecVersionFileName); + if (PackageManifest.TryParseFromFile(manifestFile, out var manifest)) + { + // ideal, the info we need is in a manifest file. + Console.WriteLine("Located valid manifest file at: " + manifestFile); + AppId = manifest.Id; + CurrentlyInstalledVersion = manifest.Version; + RootAppDir = Path.GetDirectoryName(possibleUpdateExe); + UpdateExePath = possibleUpdateExe; + AppContentDir = myDirPath; + Channel = manifest.Channel; + } + else if (myDirName.StartsWith("app-", StringComparison.OrdinalIgnoreCase) && NuGetVersion.TryParse(myDirName.Substring(4), out var version)) + { + // this is a legacy case, where we're running in an 'root/app-*/' directory, and there is no manifest. + Console.WriteLine("Legacy app-* directory detected, sq.version not found. Using directory name for AppId and Version."); + AppId = Path.GetFileName(Path.GetDirectoryName(possibleUpdateExe)); + CurrentlyInstalledVersion = version; + RootAppDir = Path.GetDirectoryName(possibleUpdateExe); + UpdateExePath = possibleUpdateExe; + AppContentDir = myDirPath; + } + } + else if (ixCurrent > 0) + { + // this is an attempt to handle the case where we are running in a nested current directory. + var rootDir = ourExePath.Substring(0, ixCurrent); + var currentDir = Path.Combine(rootDir, "current"); + var manifestFile = Path.Combine(currentDir, SpecVersionFileName); + possibleUpdateExe = Path.GetFullPath(Path.Combine(rootDir, "Update.exe")); + // we only support parsing a manifest when we're in a nested current directory. no legacy fallback. + if (File.Exists(possibleUpdateExe) && PackageManifest.TryParseFromFile(manifestFile, out var manifest)) + { + Console.WriteLine("Running in deeply nested directory. This is not an advised use-case."); + Console.WriteLine("Located valid manifest file at: " + manifestFile); + RootAppDir = Path.GetDirectoryName(possibleUpdateExe); + UpdateExePath = possibleUpdateExe; + AppId = manifest.Id; + CurrentlyInstalledVersion = manifest.Version; + AppContentDir = currentDir; + Channel = manifest.Channel; + } + } + } + } +} diff --git a/Hi3Helper.TaskScheduler/Properties/PublishProfiles/AsRelease.pubxml b/Hi3Helper.TaskScheduler/Properties/PublishProfiles/AsRelease.pubxml new file mode 100644 index 000000000..7d3019a67 --- /dev/null +++ b/Hi3Helper.TaskScheduler/Properties/PublishProfiles/AsRelease.pubxml @@ -0,0 +1,15 @@ + + + + + Release + x64 + bin\publish + FileSystem + <_TargetId>Folder + netframework462 + win-x64 + + \ No newline at end of file diff --git a/Hi3Helper.TaskScheduler/Properties/launchSettings.json b/Hi3Helper.TaskScheduler/Properties/launchSettings.json new file mode 100644 index 000000000..633b3ccd4 --- /dev/null +++ b/Hi3Helper.TaskScheduler/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Hi3Helper.TaskScheduler": { + "commandName": "Project", + "commandLineArgs": "IsEnabled \"CollapseLauncherStartupTask\" \"E:\\myGit\\Collapse\\CollapseLauncher\\bin\\x64\\Debug\\net9.0-windows10.0.22621.0\\win-x64\\CollapseLauncher.exe\"" + } + } +} \ No newline at end of file diff --git a/Hi3Helper.TaskScheduler/packages.lock.json b/Hi3Helper.TaskScheduler/packages.lock.json new file mode 100644 index 000000000..37ea088e2 --- /dev/null +++ b/Hi3Helper.TaskScheduler/packages.lock.json @@ -0,0 +1,401 @@ +{ + "version": 1, + "dependencies": { + ".NETFramework,Version=v4.6.2": { + "Costura.Fody": { + "type": "Direct", + "requested": "[5.8.0-alpha0098, )", + "resolved": "5.8.0-alpha0098", + "contentHash": "/waTTBqcZioV+2Fn2fWBO/EfyGhYN4xpBB828hfzdjkq8WJE3iiwm30cFAKJWYrwhCSD3piHasDGfBMGg6Gvqw==", + "dependencies": { + "Fody": "6.6.0", + "NETStandard.Library": "1.6.1" + } + }, + "Fody": { + "type": "Direct", + "requested": "[6.8.2, )", + "resolved": "6.8.2", + "contentHash": "sjGHrtGS1+kcrv99WXCvujOFBTQp4zCH3ZC9wo2LAtVaJkuLpHghQx3y4k1Q8ZKuDAbEw+HE6ZjPUJQK3ejepQ==" + }, + "Microsoft.NETFramework.ReferenceAssemblies": { + "type": "Direct", + "requested": "[1.0.3, )", + "resolved": "1.0.3", + "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", + "dependencies": { + "Microsoft.NETFramework.ReferenceAssemblies.net462": "1.0.3" + } + }, + "System.Net.Http": { + "type": "Direct", + "requested": "[4.3.4, )", + "resolved": "4.3.4", + "contentHash": "aOa2d51SEbmM+H+Csw7yJOuNZoHkrP2XnAurye5HWYgGVVU54YZDvsLUYRv6h18X3sPnjNCANmN7ZhIPiqMcjA==", + "dependencies": { + "System.Security.Cryptography.X509Certificates": "4.3.0" + } + }, + "System.Text.RegularExpressions": { + "type": "Direct", + "requested": "[4.3.1, )", + "resolved": "4.3.1", + "contentHash": "N0kNRrWe4+nXOWlpLT4LAY5brb8caNFlUuIRpraCVMDLYutKkol1aV079rQjLuSxKMJT2SpBQsYX9xbcTMmzwg==" + }, + "TaskScheduler": { + "type": "Direct", + "requested": "[2.11.0, )", + "resolved": "2.11.0", + "contentHash": "p9wH58XSNIyUtO7PIFAEldaKUzpYmlj+YWAfnUqBKnGxIZRY51I9BrsBGJijUVwlxrgmLLPUigRIv2ZTD4uPJA==" + }, + "Velopack": { + "type": "Direct", + "requested": "[0.0.626, )", + "resolved": "0.0.626", + "contentHash": "el6qqNyJM3GA11j6SFZgcQJ/AFg7ocQQSu+Tk/+OIzejZlROGLK+RDWPb4tcp9E9ug9aT4HV0m35zISMSC++/Q==", + "dependencies": { + "Microsoft.Extensions.Logging.Abstractions": "2.2.0", + "Newtonsoft.Json": "13.0.1", + "NuGet.Versioning": "6.10.1" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A==" + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + }, + "Microsoft.NETFramework.ReferenceAssemblies.net462": { + "type": "Transitive", + "resolved": "1.0.3", + "contentHash": "IzAV30z22ESCeQfxP29oVf4qEo8fBGXLXSU6oacv/9Iqe6PzgHDKCaWfwMBak7bSJQM0F5boXWoZS+kChztRIQ==" + }, + "Microsoft.Win32.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==" + }, + "NETStandard.Library": { + "type": "Transitive", + "resolved": "1.6.1", + "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.Win32.Primitives": "4.3.0", + "System.AppContext": "4.3.0", + "System.Collections": "4.3.0", + "System.Collections.Concurrent": "4.3.0", + "System.Console": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.Tools": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Calendars": "4.3.0", + "System.IO": "4.3.0", + "System.IO.Compression": "4.3.0", + "System.IO.Compression.ZipFile": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Linq": "4.3.0", + "System.Linq.Expressions": "4.3.0", + "System.Net.Http": "4.3.0", + "System.Net.Primitives": "4.3.0", + "System.Net.Sockets": "4.3.0", + "System.ObjectModel": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Extensions": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Security.Cryptography.X509Certificates": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Text.Encoding.Extensions": "4.3.0", + "System.Text.RegularExpressions": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "System.Threading.Timer": "4.3.0", + "System.Xml.ReaderWriter": "4.3.0", + "System.Xml.XDocument": "4.3.0" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + }, + "NuGet.Versioning": { + "type": "Transitive", + "resolved": "6.10.1", + "contentHash": "tovHZ3OlMVmsTdhv2z5nwnnhoA1ryhfJMyVQ9/+iv6d3h78fp230XaGy3K/iVcLwB50DdfNfIsitW97KSOWDFg==" + }, + "System.AppContext": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==" + }, + "System.Collections": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==" + }, + "System.Collections.Concurrent": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==" + }, + "System.Console": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==" + }, + "System.Diagnostics.Debug": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==" + }, + "System.Diagnostics.Tools": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==" + }, + "System.Diagnostics.Tracing": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==" + }, + "System.Globalization": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==" + }, + "System.Globalization.Calendars": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==" + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==" + }, + "System.IO.Compression": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==" + }, + "System.IO.Compression.ZipFile": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==" + }, + "System.IO.FileSystem": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", + "dependencies": { + "System.IO.FileSystem.Primitives": "4.3.0" + } + }, + "System.IO.FileSystem.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==" + }, + "System.Linq": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==" + }, + "System.Linq.Expressions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==" + }, + "System.Net.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==" + }, + "System.Net.Sockets": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==" + }, + "System.ObjectModel": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==" + }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==" + }, + "System.Reflection.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==" + }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==" + }, + "System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==" + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==" + }, + "System.Runtime.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==" + }, + "System.Runtime.Handles": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==" + }, + "System.Runtime.InteropServices": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.InteropServices.RuntimeInformation": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==" + }, + "System.Runtime.Numerics": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==" + }, + "System.Security.Cryptography.Algorithms": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", + "dependencies": { + "System.Security.Cryptography.Primitives": "4.3.0" + } + }, + "System.Security.Cryptography.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==" + }, + "System.Security.Cryptography.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" + }, + "System.Security.Cryptography.X509Certificates": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", + "dependencies": { + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0" + } + }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==" + }, + "System.Text.Encoding.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==" + }, + "System.Threading": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==" + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==" + }, + "System.Threading.Timer": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==" + }, + "System.Xml.ReaderWriter": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==" + }, + "System.Xml.XDocument": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==" + } + }, + ".NETFramework,Version=v4.6.2/win-x64": { + "System.Net.Http": { + "type": "Direct", + "requested": "[4.3.4, )", + "resolved": "4.3.4", + "contentHash": "aOa2d51SEbmM+H+Csw7yJOuNZoHkrP2XnAurye5HWYgGVVU54YZDvsLUYRv6h18X3sPnjNCANmN7ZhIPiqMcjA==", + "dependencies": { + "System.Security.Cryptography.X509Certificates": "4.3.0" + } + }, + "System.IO.Compression": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==" + }, + "System.Runtime.InteropServices.RuntimeInformation": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==" + }, + "System.Security.Cryptography.Algorithms": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", + "dependencies": { + "System.Security.Cryptography.Primitives": "4.3.0" + } + }, + "System.Security.Cryptography.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==" + }, + "System.Security.Cryptography.X509Certificates": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", + "dependencies": { + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0" + } + } + } + } +} \ No newline at end of file diff --git a/ImageEx b/ImageEx index 84a9ecb92..0c4dfc972 160000 --- a/ImageEx +++ b/ImageEx @@ -1 +1 @@ -Subproject commit 84a9ecb92bc11ed08f14a4906644cd1b55fb5b72 +Subproject commit 0c4dfc972686635ecbc3ce9046562aecc8f14770 diff --git a/InnoSetupHelper/InnoSetupHelper.csproj b/InnoSetupHelper/InnoSetupHelper.csproj index a71a6a376..ca6074357 100644 --- a/InnoSetupHelper/InnoSetupHelper.csproj +++ b/InnoSetupHelper/InnoSetupHelper.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 disable enable true @@ -15,14 +15,11 @@ Collapse Launcher Team $(Company). neon-nyan, Cry0, bagusnl, shatyuka, gablm. Copyright 2022-2024 $(Company) + true - - - - - + diff --git a/InnoSetupHelper/InnoSetupLogUpdate.cs b/InnoSetupHelper/InnoSetupLogUpdate.cs index b42409722..bd6ddd9ff 100644 --- a/InnoSetupHelper/InnoSetupLogUpdate.cs +++ b/InnoSetupHelper/InnoSetupLogUpdate.cs @@ -1,17 +1,29 @@ -using Hi3Helper; -using Hi3Helper.EncTool.Parser.InnoUninstallerLog; +using Hi3Helper.EncTool.Parser.InnoUninstallerLog; using LibISULR; using LibISULR.Flags; using LibISULR.Records; using System; using System.IO; using System.Linq; -using static Hi3Helper.Logger; namespace InnoSetupHelper { + public enum InnoSetupLogType + { + Default, Warning, Error + } + + public struct InnoSetupLogStruct + { + public InnoSetupLogType LogType; + public bool IsWriteToLog; + public string Message; + } + public class InnoSetupLogUpdate { + public static event EventHandler? LoggerEvent; + private static readonly string[] excludeDeleteFile = new string[] { // Generic Files @@ -38,10 +50,10 @@ public class InnoSetupLogUpdate public static void UpdateInnoSetupLog(string path) { - string directoryPath = Path.GetDirectoryName(path)!; - string searchValue = GetPathWithoutDriveLetter(directoryPath); + string directoryPath = Path.GetDirectoryName(path)!; + string searchValue = GetPathWithoutDriveLetter(directoryPath); - LogWriteLine($"[InnoSetupLogUpdate::UpdateInnoSetupLog()] Updating Inno Setup file located at: {path}", LogType.Default, true); + LogWriteLine($"[InnoSetupLogUpdate::UpdateInnoSetupLog()] Updating Inno Setup file located at: {path}", InnoSetupLogType.Default, true); try { using InnoUninstallLog innoLog = InnoUninstallLog.Load(path, true); @@ -56,11 +68,11 @@ public static void UpdateInnoSetupLog(string path) // Save the Inno Setup log innoLog.Save(path); - LogWriteLine($"[InnoSetupLogUpdate::UpdateInnoSetupLog()] Inno Setup file: {path} has been successfully updated!", LogType.Default, true); + LogWriteLine($"[InnoSetupLogUpdate::UpdateInnoSetupLog()] Inno Setup file: {path} has been successfully updated!", InnoSetupLogType.Default, true); } catch (Exception ex) { - LogWriteLine($"[InnoSetupLogUpdate::UpdateInnoSetupLog()] Inno Setup file: {path} was failed due to an error: {ex}", LogType.Warning, true); + LogWriteLine($"[InnoSetupLogUpdate::UpdateInnoSetupLog()] Inno Setup file: {path} was failed due to an error: {ex}", InnoSetupLogType.Warning, true); } } @@ -75,7 +87,7 @@ private static void RegisterDirOrFilesRecord(InnoUninstallLog innoLog, string pa DirectoryInfo currentDirectory = new DirectoryInfo(pathToRegister); if (innoLog.Records == null) { - LogWriteLine("[InnoSetupLogUpdate::RegisterDirOrFilesRecord()] Records is uninitialized!", LogType.Error, true); + LogWriteLine("[InnoSetupLogUpdate::RegisterDirOrFilesRecord()] Records is uninitialized!", InnoSetupLogType.Error, true); } else { @@ -84,14 +96,14 @@ private static void RegisterDirOrFilesRecord(InnoUninstallLog innoLog, string pa if (excludeDeleteFile.Any(x => x.IndexOf(fileInfo.FullName, StringComparison.OrdinalIgnoreCase) > -1)) continue; fileInfo.IsReadOnly = false; LogWriteLine($"[InnoSetupLogUpdate::RegisterDirOrFilesRecord()] " + - $"Registering Inno Setup record: (DeleteFileRecord){fileInfo.FullName}", LogType.Default, true); + $"Registering Inno Setup record: (DeleteFileRecord){fileInfo.FullName}", InnoSetupLogType.Default, true); innoLog.Records.Add(DeleteFileRecord.Create(fileInfo.FullName)); } LogWriteLine($"[InnoSetupLogUpdate::RegisterDirOrFilesRecord()] " + - $"Registering Inno Setup record: (DeleteDirOrFilesRecord){pathToRegister}", LogType.Default, true); + $"Registering Inno Setup record: (DeleteDirOrFilesRecord){pathToRegister}", InnoSetupLogType.Default, true); innoLog.Records.Add(DeleteDirOrFilesRecord.Create(pathToRegister)); } - + foreach (DirectoryInfo subdirectories in currentDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)) { RegisterDirOrFilesRecord(innoLog, subdirectories.FullName); @@ -106,7 +118,7 @@ private static void CleanUpInnoDirOrFilesRecord(InnoUninstallLog innoLog, string if (innoLog.Records == null) { LogWriteLine("[InnoSetupLogUpdate::RegisterDirOrFilesRecord()] Records is uninitialized!", - LogType.Error, true); + InnoSetupLogType.Error, true); return; } BaseRecord baseRecord = innoLog.Records[index]; @@ -116,12 +128,12 @@ private static void CleanUpInnoDirOrFilesRecord(InnoUninstallLog innoLog, string case RecordType.DeleteDirOrFiles: isRecordValid = IsInnoRecordContainsPath(baseRecord, searchValue) && IsDeleteDirOrFilesFlagsValid((DeleteDirOrFilesRecord)baseRecord); - LogWriteLine($"[InnoSetupLogUpdate::CleanUpInnoDirOrFilesRecord()] Removing outdated Inno Setup record: (DeleteDirOrFilesRecord){((DeleteDirOrFilesRecord)baseRecord).Paths[0]}", LogType.Default, true); + LogWriteLine($"[InnoSetupLogUpdate::CleanUpInnoDirOrFilesRecord()] Removing outdated Inno Setup record: (DeleteDirOrFilesRecord){((DeleteDirOrFilesRecord)baseRecord).Paths[0]}", InnoSetupLogType.Default, true); break; case RecordType.DeleteFile: isRecordValid = IsInnoRecordContainsPath(baseRecord, searchValue) && IsDeleteFileFlagsValid((DeleteFileRecord)baseRecord); - LogWriteLine($"[InnoSetupLogUpdate::CleanUpInnoDirOrFilesRecord()] Removing outdated Inno Setup record: (DeleteFileRecord){((DeleteFileRecord)baseRecord).Paths[0]}", LogType.Default, true); + LogWriteLine($"[InnoSetupLogUpdate::CleanUpInnoDirOrFilesRecord()] Removing outdated Inno Setup record: (DeleteFileRecord){((DeleteFileRecord)baseRecord).Paths[0]}", InnoSetupLogType.Default, true); break; } if (isRecordValid) @@ -139,5 +151,8 @@ private static bool IsInnoRecordContainsPath(BaseRecord record, string? where TFlags : Enum => ((BasePathListRecord)record) .Paths[0]! .IndexOf(searchValue!, StringComparison.OrdinalIgnoreCase) > -1; + + private static void LogWriteLine(string message, InnoSetupLogType logType, bool isWriteLog) + => LoggerEvent?.Invoke(null, new InnoSetupLogStruct { IsWriteToLog = isWriteLog, LogType = logType, Message = message }); } } diff --git a/InnoSetupHelper/packages.lock.json b/InnoSetupHelper/packages.lock.json index 8b552740e..3a864d87f 100644 --- a/InnoSetupHelper/packages.lock.json +++ b/InnoSetupHelper/packages.lock.json @@ -1,35 +1,18 @@ { "version": 1, "dependencies": { - "net8.0": { - "System.IO.Hashing": { + "net9.0": { + "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "ne1843evDugl0md7Fjzy6QjJrzsjh46ZKbhf8GwBXb5f/gw97J4bxMs0NQKifDuThh/f0bZ0e62NPl1jzTuRqA==" - }, - "Google.Protobuf": { - "type": "Transitive", - "resolved": "3.28.1", - "contentHash": "i4EN7Z+OUdoRBNiVMIG6CfMh6UowXiUx+BKgE+GHLbAX5ArSmpUTFUDwgRNwNfYdosl6GXuBlDiHCcXSHw43+A==" - }, - "hi3helper.core": { - "type": "Project", - "dependencies": { - "Hi3Helper.EncTool": "[1.0.0, )", - "Hi3Helper.Http": "[2.0.0, )" - } + "requested": "[9.0.0-rc.2.24473.5, )", + "resolved": "9.0.0-rc.2.24473.5", + "contentHash": "mXyCgKqBIo/KOpY09FqjOU8d+LME2qowzzvBd1/viYmSpAKYfQ2w1WDOlh0FniY4xnrB/wmDiAo4frJvfCqT0w==" }, - "hi3helper.enctool": { - "type": "Project", - "dependencies": { - "Google.Protobuf": "[3.28.1, )", - "Hi3Helper.Http": "[2.0.0, )", - "System.IO.Hashing": "[8.0.0, )" - } - }, - "hi3helper.http": { - "type": "Project" + "System.IO.Hashing": { + "type": "Direct", + "requested": "[9.0.0-rc.2.24473.5, )", + "resolved": "9.0.0-rc.2.24473.5", + "contentHash": "BWkkIwLhG75+RnyBHGQd0Vrti8wqIBeNiAmHQrb6UiFny6qEQ+z6r61bFAOubw8dbp5S8nQrTS/wjJtpolkYTA==" } } } diff --git a/README.md b/README.md index 5864629a8..284d91a78 100644 --- a/README.md +++ b/README.md @@ -230,8 +230,8 @@ Not only that, this launcher also has some advanced features for **Genshin Impac [](https://github.com/CollapseLauncher/Collapse/releases/download/CL-v1.80.19/CL-1.80.19-stable_Installer.exe) > **Note**: The version for this build is `1.80.19` (Released on: August 13rd, 2024). -[](https://github.com/CollapseLauncher/Collapse/releases/download/CL-v1.81.5-pre/CL-1.81.5-preview_Installer.exe) -> **Note**: The version for this build is `1.81.5` (Released on: September 8th, 2024). +[](https://github.com/CollapseLauncher/Collapse/releases/download/CL-v1.81.6-pre/CL-1.81.6-preview_Installer.exe) +> **Note**: The version for this build is `1.81.6` (Released on: September 16th, 2024). To view all releases, [**click here**](https://github.com/neon-nyan/CollapseLauncher/releases). diff --git a/SevenZipExtractor b/SevenZipExtractor index e649b7466..add7bd39e 160000 --- a/SevenZipExtractor +++ b/SevenZipExtractor @@ -1 +1 @@ -Subproject commit e649b7466464a7ba9d5b478ea945575c75f7190f +Subproject commit add7bd39e54c92a6a3e154faf77bb8f1335ee015 diff --git a/XamlStyler.bat b/XamlStyler.bat index 509aff430..273851b66 100644 --- a/XamlStyler.bat +++ b/XamlStyler.bat @@ -11,4 +11,4 @@ if %ERRORLEVEL% neq 0 ( echo This tool will apply XamlStyler configured in settings.xamlstyler file for all Xamls in ./CollapseLauncher/XAMLs/ directory. pause -xstyler --config ./settings.xamlstyler --directory ./CollapseLauncher/XAMLs/ --recursive +xstyler --config ./settings.xamlstyler --directory ./CollapseLauncher/ --recursive diff --git a/qodana.yaml b/qodana.yaml index 20f536c8a..5cd0e0c45 100644 --- a/qodana.yaml +++ b/qodana.yaml @@ -16,6 +16,8 @@ exclude: - name: Xaml.ConstructorWarning - name: Xaml.InvalidResourceType - name: Xaml.StaticResourceNotResolved + - name: RedundantExtendsListEntry + - name: PartialTypeWithSinglePart include: - name: CheckDependencyLicenses licenseRules: