diff --git a/README.md b/README.md index 61473ba3..6215ef24 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ - [Getting started](Documentation/GettingStarted.md) - [Download Libation](Documentation/GettingStarted.md#download-libation-1) - [Installation](Documentation/GettingStarted.md#installation) + - [Installation on Ubuntu](Source/LibationAvalonia/README.md) - [Create Accounts](Documentation/GettingStarted.md#create-accounts) - [Import your library](Documentation/GettingStarted.md#import-your-library) - [Download your books -- DRM-free!](Documentation/GettingStarted.md#download-your-books----drm-free) diff --git a/Source/AaxDecrypter/AaxDecrypter.csproj b/Source/AaxDecrypter/AaxDecrypter.csproj index c53c1baf..2d0b5b39 100644 --- a/Source/AaxDecrypter/AaxDecrypter.csproj +++ b/Source/AaxDecrypter/AaxDecrypter.csproj @@ -1,13 +1,9 @@  - net6.0-windows + net6.0 - - - - embedded @@ -16,6 +12,9 @@ embedded + + + diff --git a/Source/AppScaffolding/AppScaffolding.csproj b/Source/AppScaffolding/AppScaffolding.csproj index edc6deec..86a61359 100644 --- a/Source/AppScaffolding/AppScaffolding.csproj +++ b/Source/AppScaffolding/AppScaffolding.csproj @@ -1,7 +1,7 @@ - net6.0-windows + net6.0 8.2.5.1 diff --git a/Source/ApplicationServices/ApplicationServices.csproj b/Source/ApplicationServices/ApplicationServices.csproj index 439fc19f..8045df40 100644 --- a/Source/ApplicationServices/ApplicationServices.csproj +++ b/Source/ApplicationServices/ApplicationServices.csproj @@ -1,7 +1,7 @@  - net6.0-windows + net6.0 diff --git a/Source/AudibleUtilities/AudibleUtilities.csproj b/Source/AudibleUtilities/AudibleUtilities.csproj index b835a2e6..e7cc2d81 100644 --- a/Source/AudibleUtilities/AudibleUtilities.csproj +++ b/Source/AudibleUtilities/AudibleUtilities.csproj @@ -1,7 +1,7 @@  - net6.0-windows + net6.0 diff --git a/Source/DataLayer/DataLayer.csproj b/Source/DataLayer/DataLayer.csproj index 9ee149f7..3bfe8ee2 100644 --- a/Source/DataLayer/DataLayer.csproj +++ b/Source/DataLayer/DataLayer.csproj @@ -1,7 +1,7 @@  - net6.0-windows + net6.0 diff --git a/Source/DtoImporterService/DtoImporterService.csproj b/Source/DtoImporterService/DtoImporterService.csproj index d784be6f..aba836a6 100644 --- a/Source/DtoImporterService/DtoImporterService.csproj +++ b/Source/DtoImporterService/DtoImporterService.csproj @@ -1,7 +1,7 @@  - net6.0-windows + net6.0 diff --git a/Source/FileLiberator/FileLiberator.csproj b/Source/FileLiberator/FileLiberator.csproj index 65b461a1..524efe12 100644 --- a/Source/FileLiberator/FileLiberator.csproj +++ b/Source/FileLiberator/FileLiberator.csproj @@ -1,7 +1,7 @@  - net6.0-windows + net6.0 diff --git a/Source/FileManager/LongPath.cs b/Source/FileManager/LongPath.cs index 1c1bd08a..01a391a9 100644 --- a/Source/FileManager/LongPath.cs +++ b/Source/FileManager/LongPath.cs @@ -20,8 +20,13 @@ namespace FileManager public string Path { get; init; } public override string ToString() => Path; + private static readonly PlatformID PlatformID = Environment.OSVersion.Platform; + + public static implicit operator LongPath(string path) { + if (PlatformID is PlatformID.Unix) return new LongPath { Path = path }; + if (path is null) return null; //File I/O functions in the Windows API convert "/" to "\" as part of converting @@ -58,6 +63,8 @@ namespace FileManager { get { + if (PlatformID is PlatformID.Unix) return Path; + //Short Path names are useful for navigating to the file in windows explorer, //which will not recognize paths longer than MAX_PATH. Short path names are not //always enabled on every volume. So to check if a volume enables short path @@ -96,6 +103,7 @@ namespace FileManager { get { + if (PlatformID is PlatformID.Unix) return Path; if (Path is null) return null; StringBuilder longPathBuffer = new(MaxPathLength); @@ -106,15 +114,21 @@ namespace FileManager [JsonIgnore] public string PathWithoutPrefix - => Path?.StartsWith(LONG_PATH_PREFIX) == true ? - Path.Remove(0, LONG_PATH_PREFIX.Length) : - Path; - + { + get + { + if (PlatformID is PlatformID.Unix) return Path; + return + Path?.StartsWith(LONG_PATH_PREFIX) == true ? Path.Remove(0, LONG_PATH_PREFIX.Length) + :Path; + } + } [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] private static extern int GetShortPathName([MarshalAs(UnmanagedType.LPWStr)] string path, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder shortPath, int shortPathLength); [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] private static extern int GetLongPathName([MarshalAs(UnmanagedType.LPWStr)] string lpszShortPath, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpszLongPath, int cchBuffer); + } } diff --git a/Source/FileManager/ReplacementCharacters.cs b/Source/FileManager/ReplacementCharacters.cs index 53fd9517..550a54c6 100644 --- a/Source/FileManager/ReplacementCharacters.cs +++ b/Source/FileManager/ReplacementCharacters.cs @@ -14,7 +14,7 @@ namespace FileManager internal const char QUOTE_MARK = '"'; [JsonIgnore] public bool Mandatory { get; internal set; } [JsonProperty] public char CharacterToReplace { get; private set; } - [JsonProperty] public string ReplacementString { get; private set; } + [JsonProperty] public string ReplacementString { get; set; } [JsonProperty] public string Description { get; private set; } public override string ToString() => $"{CharacterToReplace} → {ReplacementString} ({Description})"; @@ -168,8 +168,10 @@ namespace FileManager } - public static bool ContainsInvalid(string path) + public static bool ContainsInvalidPathChar(string path) => path.Any(c => invalidChars.Contains(c)); + public static bool ContainsInvalidFilenameChar(string path) + => path.Any(c => invalidChars.Concat(new char[] { '\\', '/' }).Contains(c)); public string ReplaceInvalidFilenameChars(string fileName) { @@ -246,7 +248,7 @@ namespace FileManager dict[3].CharacterToReplace != default3.CharacterToReplace || dict[3].Description != default3.Description || dict[4].CharacterToReplace != default4.CharacterToReplace || dict[4].Description != default4.Description || dict[5].CharacterToReplace != default5.CharacterToReplace || dict[5].Description != default5.Description || - dict.Any(r => ReplacementCharacters.ContainsInvalid(r.ReplacementString)) + dict.Any(r => ReplacementCharacters.ContainsInvalidPathChar(r.ReplacementString)) ) { dict = ReplacementCharacters.Default.Replacements; diff --git a/Source/Libation.sln b/Source/Libation.sln index e15d074a..c5d96dd1 100644 --- a/Source/Libation.sln +++ b/Source/Libation.sln @@ -66,6 +66,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibationFileManager.Tests", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hangover", "Hangover\Hangover.csproj", "{40C67036-C1A7-4FDF-AA83-8EC902E257F3}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibationAvalonia", "LibationAvalonia\LibationAvalonia.csproj", "{F612D06F-3134-4B9B-95CD-EB3FC798AE60}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -144,6 +146,10 @@ Global {40C67036-C1A7-4FDF-AA83-8EC902E257F3}.Debug|Any CPU.Build.0 = Debug|Any CPU {40C67036-C1A7-4FDF-AA83-8EC902E257F3}.Release|Any CPU.ActiveCfg = Release|Any CPU {40C67036-C1A7-4FDF-AA83-8EC902E257F3}.Release|Any CPU.Build.0 = Release|Any CPU + {F612D06F-3134-4B9B-95CD-EB3FC798AE60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F612D06F-3134-4B9B-95CD-EB3FC798AE60}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F612D06F-3134-4B9B-95CD-EB3FC798AE60}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F612D06F-3134-4B9B-95CD-EB3FC798AE60}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -167,6 +173,7 @@ Global {F2E04270-4551-41C4-99FF-E7125BED708C} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53} {EB781571-8548-477E-82AD-FB9FAB548D2F} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53} {40C67036-C1A7-4FDF-AA83-8EC902E257F3} = {8679CAC8-9164-4007-BDD2-F004810EDA14} + {F612D06F-3134-4B9B-95CD-EB3FC798AE60} = {8679CAC8-9164-4007-BDD2-F004810EDA14} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {615E00ED-BAEF-4E8E-A92A-9B82D87942A9} diff --git a/Source/LibationWinForms/AvaloniaUI/App.axaml b/Source/LibationAvalonia/App.axaml similarity index 66% rename from Source/LibationWinForms/AvaloniaUI/App.axaml rename to Source/LibationAvalonia/App.axaml index f8ef3650..cbfd5ab2 100644 --- a/Source/LibationWinForms/AvaloniaUI/App.axaml +++ b/Source/LibationAvalonia/App.axaml @@ -1,7 +1,7 @@  + xmlns:local="using:LibationAvalonia" + x:Class="LibationAvalonia.App"> @@ -11,7 +11,7 @@ - - + + \ No newline at end of file diff --git a/Source/LibationAvalonia/App.axaml.cs b/Source/LibationAvalonia/App.axaml.cs new file mode 100644 index 00000000..8d806fc8 --- /dev/null +++ b/Source/LibationAvalonia/App.axaml.cs @@ -0,0 +1,228 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using Avalonia.Media; +using LibationFileManager; +using LibationAvalonia.Views; +using System; +using Avalonia.Platform; +using LibationAvalonia.Dialogs; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.IO; +using ApplicationServices; +using Dinah.Core; + +namespace LibationAvalonia +{ + public class App : Application + { + public static bool IsWindows => PlatformID is PlatformID.Win32NT; + public static bool IsUnix => PlatformID is PlatformID.Unix; + + public static readonly PlatformID PlatformID = Environment.OSVersion.Platform; + public static IBrush ProcessQueueBookFailedBrush { get; private set; } + public static IBrush ProcessQueueBookCompletedBrush { get; private set; } + public static IBrush ProcessQueueBookCancelledBrush { get; private set; } + public static IBrush ProcessQueueBookDefaultBrush { get; private set; } + public static IBrush SeriesEntryGridBackgroundBrush { get; private set; } + + public static IAssetLoader AssetLoader { get; private set; } + + public static readonly Uri AssetUriBase = new Uri("avares://Libation/Assets/"); + public static Stream OpenAsset(string assetRelativePath) + => AssetLoader.Open(new Uri(AssetUriBase, assetRelativePath)); + + + public static bool GoToFile(string path) + => PlatformID is PlatformID.Win32NT ? Go.To.File(path) + : GoToFolder(path is null ? string.Empty : Path.GetDirectoryName(path)); + + public static bool GoToFolder(string path) + { + if (PlatformID is PlatformID.Win32NT) + return Go.To.Folder(path); + else + { + var startInfo = new System.Diagnostics.ProcessStartInfo() + { + FileName = "/bin/xdg-open", + Arguments = path is null ? string.Empty : $"\"{path}\"", + UseShellExecute = false, //Import in Linux environments + CreateNoWindow = false, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + System.Diagnostics.Process.Start(startInfo); + return true; + } + } + + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + AssetLoader = AvaloniaLocator.Current.GetService(); + } + + public static Task> LibraryTask; + public static bool SetupRequired; + + public override void OnFrameworkInitializationCompleted() + { + LoadStyles(); + + var SEGOEUI = new Typeface(new FontFamily(new Uri("avares://Libation/Assets/WINGDING.TTF"), "SEGOEUI_Local")); + var gtf = FontManager.Current.GetOrAddGlyphTypeface(SEGOEUI); + + + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + if (SetupRequired) + { + var config = Configuration.Instance; + + var defaultLibationFilesDir = Configuration.UserProfile; + + // check for existing settings in default location + var defaultSettingsFile = Path.Combine(defaultLibationFilesDir, "Settings.json"); + if (Configuration.SettingsFileIsValid(defaultSettingsFile)) + config.SetLibationFiles(defaultLibationFilesDir); + + if (config.LibationSettingsAreValid) + { + LibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true)); + ShowMainWindow(desktop); + } + else + { + var setupDialog = new SetupDialog { Config = config }; + setupDialog.Closing += Setup_Closing; + desktop.MainWindow = setupDialog; + } + } + else + ShowMainWindow(desktop); + } + + base.OnFrameworkInitializationCompleted(); + } + + private void Setup_Closing(object sender, System.ComponentModel.CancelEventArgs e) + { + var setupDialog = sender as SetupDialog; + var desktop = ApplicationLifetime as IClassicDesktopStyleApplicationLifetime; + + try + { + // all returns should be preceded by either: + // - if config.LibationSettingsAreValid + // - error message, Exit() + + if ((!setupDialog.IsNewUser + && !setupDialog.IsReturningUser) || + !RunInstall(setupDialog)) + { + CancelInstallation(); + return; + } + + + // most migrations go in here + AppScaffolding.LibationScaffolding.RunPostConfigMigrations(setupDialog.Config); + + MessageBox.VerboseLoggingWarning_ShowIfTrue(); + +#if !DEBUG + //AutoUpdater.NET only works for WinForms or WPF application projects. + //checkForUpdate(); +#endif + // logging is init'd here + AppScaffolding.LibationScaffolding.RunPostMigrationScaffolding(setupDialog.Config); + + } + catch (Exception ex) + { + var title = "Fatal error, pre-logging"; + var body = "An unrecoverable error occurred. Since this error happened before logging could be initialized, this error can not be written to the log file."; + try + { + MessageBox.ShowAdminAlert(null, body, title, ex); + } + catch + { + MessageBox.Show($"{body}\r\n\r\n{ex.Message}\r\n\r\n{ex.StackTrace}", title, MessageBoxButtons.OK, MessageBoxIcon.Error); + } + return; + } + + LibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true)); + AudibleUtilities.AudibleApiStorage.EnsureAccountsSettingsFileExists(); + ShowMainWindow(desktop); + } + + private static bool RunInstall(SetupDialog setupDialog) + { + var config = setupDialog.Config; + + if (setupDialog.IsNewUser) + { + config.SetLibationFiles(Configuration.UserProfile); + } + else if (setupDialog.IsReturningUser) + { + + var libationFilesDialog = new LibationFilesDialog(); + + if (libationFilesDialog.ShowDialogSynchronously(setupDialog) != DialogResult.OK) + return false; + + config.SetLibationFiles(libationFilesDialog.SelectedDirectory); + if (config.LibationSettingsAreValid) + return true; + + // path did not result in valid settings + var continueResult = MessageBox.Show( + $"No valid settings were found at this location.\r\nWould you like to create a new install settings in this folder?\r\n\r\n{libationFilesDialog.SelectedDirectory}", + "New install?", + MessageBoxButtons.YesNo, + MessageBoxIcon.Question); + + if (continueResult != DialogResult.Yes) + return false; + } + + // INIT DEFAULT SETTINGS + // if 'new user' was clicked, or if 'returning user' chose new install: show basic settings dialog + config.Books ??= Path.Combine(Configuration.UserProfile, "Books"); + + AppScaffolding.LibationScaffolding.PopulateMissingConfigValues(config); + return new SettingsDialog().ShowDialogSynchronously(setupDialog) == DialogResult.OK + && config.LibationSettingsAreValid; + } + + static void CancelInstallation() + { + MessageBox.Show("Initial set up cancelled.", "Cancelled", MessageBoxButtons.OK, MessageBoxIcon.Warning); + Environment.Exit(0); + } + + private static void ShowMainWindow(IClassicDesktopStyleApplicationLifetime desktop) + { + var mainWindow = new MainWindow(); + desktop.MainWindow = mainWindow; + mainWindow.RestoreSizeAndLocation(Configuration.Instance); + mainWindow.OnLoad(); + mainWindow.OnLibraryLoaded(LibraryTask.GetAwaiter().GetResult()); + mainWindow.Show(); + } + + private static void LoadStyles() + { + ProcessQueueBookFailedBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookFailedBrush"); + ProcessQueueBookCompletedBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookCompletedBrush"); + ProcessQueueBookCancelledBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookCancelledBrush"); + ProcessQueueBookDefaultBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookDefaultBrush"); + SeriesEntryGridBackgroundBrush = AvaloniaUtils.GetBrushFromResources("SeriesEntryGridBackgroundBrush"); + } + } +} \ No newline at end of file diff --git a/Source/LibationWinForms/AvaloniaUI/Assets/1x1.png b/Source/LibationAvalonia/Assets/1x1.png similarity index 100% rename from Source/LibationWinForms/AvaloniaUI/Assets/1x1.png rename to Source/LibationAvalonia/Assets/1x1.png diff --git a/Source/LibationWinForms/AvaloniaUI/Assets/DataGridTheme.xaml b/Source/LibationAvalonia/Assets/DataGridTheme.xaml similarity index 100% rename from Source/LibationWinForms/AvaloniaUI/Assets/DataGridTheme.xaml rename to Source/LibationAvalonia/Assets/DataGridTheme.xaml diff --git a/Source/LibationWinForms/AvaloniaUI/Assets/LibationStyles.xaml b/Source/LibationAvalonia/Assets/LibationStyles.xaml similarity index 100% rename from Source/LibationWinForms/AvaloniaUI/Assets/LibationStyles.xaml rename to Source/LibationAvalonia/Assets/LibationStyles.xaml diff --git a/Source/LibationWinForms/AvaloniaUI/Assets/MBIcons/Asterisk.png b/Source/LibationAvalonia/Assets/MBIcons/Asterisk.png similarity index 100% rename from Source/LibationWinForms/AvaloniaUI/Assets/MBIcons/Asterisk.png rename to Source/LibationAvalonia/Assets/MBIcons/Asterisk.png diff --git a/Source/LibationWinForms/AvaloniaUI/Assets/MBIcons/Exclamation.png b/Source/LibationAvalonia/Assets/MBIcons/Exclamation.png similarity index 100% rename from Source/LibationWinForms/AvaloniaUI/Assets/MBIcons/Exclamation.png rename to Source/LibationAvalonia/Assets/MBIcons/Exclamation.png diff --git a/Source/LibationWinForms/AvaloniaUI/Assets/MBIcons/Question.png b/Source/LibationAvalonia/Assets/MBIcons/Question.png similarity index 100% rename from Source/LibationWinForms/AvaloniaUI/Assets/MBIcons/Question.png rename to Source/LibationAvalonia/Assets/MBIcons/Question.png diff --git a/Source/LibationWinForms/AvaloniaUI/Assets/MBIcons/error.png b/Source/LibationAvalonia/Assets/MBIcons/error.png similarity index 100% rename from Source/LibationWinForms/AvaloniaUI/Assets/MBIcons/error.png rename to Source/LibationAvalonia/Assets/MBIcons/error.png diff --git a/Source/LibationAvalonia/Assets/SEGOEUI.TTF b/Source/LibationAvalonia/Assets/SEGOEUI.TTF new file mode 100644 index 00000000..0f52cbd9 Binary files /dev/null and b/Source/LibationAvalonia/Assets/SEGOEUI.TTF differ diff --git a/Source/LibationAvalonia/Assets/WINGDING.TTF b/Source/LibationAvalonia/Assets/WINGDING.TTF new file mode 100644 index 00000000..6e38f7fd Binary files /dev/null and b/Source/LibationAvalonia/Assets/WINGDING.TTF differ diff --git a/Source/LibationWinForms/AvaloniaUI/Assets/cancel.png b/Source/LibationAvalonia/Assets/cancel.png similarity index 100% rename from Source/LibationWinForms/AvaloniaUI/Assets/cancel.png rename to Source/LibationAvalonia/Assets/cancel.png diff --git a/Source/LibationWinForms/AvaloniaUI/Assets/completed.png b/Source/LibationAvalonia/Assets/completed.png similarity index 100% rename from Source/LibationWinForms/AvaloniaUI/Assets/completed.png rename to Source/LibationAvalonia/Assets/completed.png diff --git a/Source/LibationWinForms/AvaloniaUI/Assets/down.png b/Source/LibationAvalonia/Assets/down.png similarity index 100% rename from Source/LibationWinForms/AvaloniaUI/Assets/down.png rename to Source/LibationAvalonia/Assets/down.png diff --git a/Source/LibationAvalonia/Assets/download-arrow.png b/Source/LibationAvalonia/Assets/download-arrow.png new file mode 100644 index 00000000..16617998 Binary files /dev/null and b/Source/LibationAvalonia/Assets/download-arrow.png differ diff --git a/Source/LibationAvalonia/Assets/edit-tags-25x25.png b/Source/LibationAvalonia/Assets/edit-tags-25x25.png new file mode 100644 index 00000000..82b24209 Binary files /dev/null and b/Source/LibationAvalonia/Assets/edit-tags-25x25.png differ diff --git a/Source/LibationAvalonia/Assets/edit-tags-50x50.png b/Source/LibationAvalonia/Assets/edit-tags-50x50.png new file mode 100644 index 00000000..7b0043ac Binary files /dev/null and b/Source/LibationAvalonia/Assets/edit-tags-50x50.png differ diff --git a/Source/LibationWinForms/AvaloniaUI/Assets/edit_25x25.png b/Source/LibationAvalonia/Assets/edit_25x25.png similarity index 100% rename from Source/LibationWinForms/AvaloniaUI/Assets/edit_25x25.png rename to Source/LibationAvalonia/Assets/edit_25x25.png diff --git a/Source/LibationAvalonia/Assets/edit_64x64.png b/Source/LibationAvalonia/Assets/edit_64x64.png new file mode 100644 index 00000000..1d9e5f83 Binary files /dev/null and b/Source/LibationAvalonia/Assets/edit_64x64.png differ diff --git a/Source/LibationAvalonia/Assets/error.png b/Source/LibationAvalonia/Assets/error.png new file mode 100644 index 00000000..700ce41e Binary files /dev/null and b/Source/LibationAvalonia/Assets/error.png differ diff --git a/Source/LibationWinForms/AvaloniaUI/Assets/errored.png b/Source/LibationAvalonia/Assets/errored.png similarity index 100% rename from Source/LibationWinForms/AvaloniaUI/Assets/errored.png rename to Source/LibationAvalonia/Assets/errored.png diff --git a/Source/LibationWinForms/AvaloniaUI/Assets/first.png b/Source/LibationAvalonia/Assets/first.png similarity index 100% rename from Source/LibationWinForms/AvaloniaUI/Assets/first.png rename to Source/LibationAvalonia/Assets/first.png diff --git a/Source/LibationWinForms/AvaloniaUI/Assets/glass-with-glow_16.png b/Source/LibationAvalonia/Assets/glass-with-glow_16.png similarity index 100% rename from Source/LibationWinForms/AvaloniaUI/Assets/glass-with-glow_16.png rename to Source/LibationAvalonia/Assets/glass-with-glow_16.png diff --git a/Source/LibationAvalonia/Assets/img-coverart-prod-unavailable_300x300.jpg b/Source/LibationAvalonia/Assets/img-coverart-prod-unavailable_300x300.jpg new file mode 100644 index 00000000..9b3d864f Binary files /dev/null and b/Source/LibationAvalonia/Assets/img-coverart-prod-unavailable_300x300.jpg differ diff --git a/Source/LibationAvalonia/Assets/img-coverart-prod-unavailable_500x500.jpg b/Source/LibationAvalonia/Assets/img-coverart-prod-unavailable_500x500.jpg new file mode 100644 index 00000000..16486568 Binary files /dev/null and b/Source/LibationAvalonia/Assets/img-coverart-prod-unavailable_500x500.jpg differ diff --git a/Source/LibationAvalonia/Assets/img-coverart-prod-unavailable_80x80.jpg b/Source/LibationAvalonia/Assets/img-coverart-prod-unavailable_80x80.jpg new file mode 100644 index 00000000..7c49cd3e Binary files /dev/null and b/Source/LibationAvalonia/Assets/img-coverart-prod-unavailable_80x80.jpg differ diff --git a/Source/LibationWinForms/AvaloniaUI/Assets/import_16x16.png b/Source/LibationAvalonia/Assets/import_16x16.png similarity index 100% rename from Source/LibationWinForms/AvaloniaUI/Assets/import_16x16.png rename to Source/LibationAvalonia/Assets/import_16x16.png diff --git a/Source/LibationWinForms/AvaloniaUI/Assets/last.png b/Source/LibationAvalonia/Assets/last.png similarity index 100% rename from Source/LibationWinForms/AvaloniaUI/Assets/last.png rename to Source/LibationAvalonia/Assets/last.png diff --git a/Source/LibationWinForms/AvaloniaUI/Assets/libation.ico b/Source/LibationAvalonia/Assets/libation.ico similarity index 100% rename from Source/LibationWinForms/AvaloniaUI/Assets/libation.ico rename to Source/LibationAvalonia/Assets/libation.ico diff --git a/Source/LibationAvalonia/Assets/liberate_green.png b/Source/LibationAvalonia/Assets/liberate_green.png new file mode 100644 index 00000000..86171e0c Binary files /dev/null and b/Source/LibationAvalonia/Assets/liberate_green.png differ diff --git a/Source/LibationAvalonia/Assets/liberate_green_pdf_no.png b/Source/LibationAvalonia/Assets/liberate_green_pdf_no.png new file mode 100644 index 00000000..a128c088 Binary files /dev/null and b/Source/LibationAvalonia/Assets/liberate_green_pdf_no.png differ diff --git a/Source/LibationAvalonia/Assets/liberate_green_pdf_yes.png b/Source/LibationAvalonia/Assets/liberate_green_pdf_yes.png new file mode 100644 index 00000000..baac0151 Binary files /dev/null and b/Source/LibationAvalonia/Assets/liberate_green_pdf_yes.png differ diff --git a/Source/LibationAvalonia/Assets/liberate_red.png b/Source/LibationAvalonia/Assets/liberate_red.png new file mode 100644 index 00000000..8e4b34e4 Binary files /dev/null and b/Source/LibationAvalonia/Assets/liberate_red.png differ diff --git a/Source/LibationAvalonia/Assets/liberate_red_pdf_no.png b/Source/LibationAvalonia/Assets/liberate_red_pdf_no.png new file mode 100644 index 00000000..6506603c Binary files /dev/null and b/Source/LibationAvalonia/Assets/liberate_red_pdf_no.png differ diff --git a/Source/LibationAvalonia/Assets/liberate_red_pdf_yes.png b/Source/LibationAvalonia/Assets/liberate_red_pdf_yes.png new file mode 100644 index 00000000..0d5b5eb6 Binary files /dev/null and b/Source/LibationAvalonia/Assets/liberate_red_pdf_yes.png differ diff --git a/Source/LibationAvalonia/Assets/liberate_yellow.png b/Source/LibationAvalonia/Assets/liberate_yellow.png new file mode 100644 index 00000000..8b3e8aab Binary files /dev/null and b/Source/LibationAvalonia/Assets/liberate_yellow.png differ diff --git a/Source/LibationAvalonia/Assets/liberate_yellow_pdf_no.png b/Source/LibationAvalonia/Assets/liberate_yellow_pdf_no.png new file mode 100644 index 00000000..2bddcffd Binary files /dev/null and b/Source/LibationAvalonia/Assets/liberate_yellow_pdf_no.png differ diff --git a/Source/LibationAvalonia/Assets/liberate_yellow_pdf_yes.png b/Source/LibationAvalonia/Assets/liberate_yellow_pdf_yes.png new file mode 100644 index 00000000..b51a28ad Binary files /dev/null and b/Source/LibationAvalonia/Assets/liberate_yellow_pdf_yes.png differ diff --git a/Source/LibationAvalonia/Assets/minus.png b/Source/LibationAvalonia/Assets/minus.png new file mode 100644 index 00000000..c0c5d15c Binary files /dev/null and b/Source/LibationAvalonia/Assets/minus.png differ diff --git a/Source/LibationAvalonia/Assets/plus.png b/Source/LibationAvalonia/Assets/plus.png new file mode 100644 index 00000000..1cd1c630 Binary files /dev/null and b/Source/LibationAvalonia/Assets/plus.png differ diff --git a/Source/LibationWinForms/AvaloniaUI/Assets/queued.png b/Source/LibationAvalonia/Assets/queued.png similarity index 100% rename from Source/LibationWinForms/AvaloniaUI/Assets/queued.png rename to Source/LibationAvalonia/Assets/queued.png diff --git a/Source/LibationWinForms/AvaloniaUI/Assets/up.png b/Source/LibationAvalonia/Assets/up.png similarity index 100% rename from Source/LibationWinForms/AvaloniaUI/Assets/up.png rename to Source/LibationAvalonia/Assets/up.png diff --git a/Source/LibationAvalonia/AvaloniaUtils.cs b/Source/LibationAvalonia/AvaloniaUtils.cs new file mode 100644 index 00000000..7ade8b9a --- /dev/null +++ b/Source/LibationAvalonia/AvaloniaUtils.cs @@ -0,0 +1,28 @@ +using Avalonia.Media; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace LibationAvalonia +{ + internal static class AvaloniaUtils + { + public static IBrush GetBrushFromResources(string name) + => GetBrushFromResources(name, Brushes.Transparent); + public static IBrush GetBrushFromResources(string name, IBrush defaultBrush) + { + if (App.Current.Styles.TryGetResource(name, out var value) && value is IBrush brush) + return brush; + return defaultBrush; + } + + public static T ShowDialogSynchronously(this Avalonia.Controls.Window window, Avalonia.Controls.Window owner) + { + using var source = new CancellationTokenSource(); + var dialogTask = window.ShowDialog(owner); + dialogTask.ContinueWith(t => source.Cancel(), TaskScheduler.FromCurrentSynchronizationContext()); + Avalonia.Threading.Dispatcher.UIThread.MainLoop(source.Token); + return dialogTask.Result; + } + } +} diff --git a/Source/LibationWinForms/AvaloniaUI/Controls/DataGridCheckBoxColumnExt.axaml b/Source/LibationAvalonia/Controls/DataGridCheckBoxColumnExt.axaml similarity index 65% rename from Source/LibationWinForms/AvaloniaUI/Controls/DataGridCheckBoxColumnExt.axaml rename to Source/LibationAvalonia/Controls/DataGridCheckBoxColumnExt.axaml index d67603fe..d51e730d 100644 --- a/Source/LibationWinForms/AvaloniaUI/Controls/DataGridCheckBoxColumnExt.axaml +++ b/Source/LibationAvalonia/Controls/DataGridCheckBoxColumnExt.axaml @@ -1,5 +1,5 @@ + x:Class="LibationAvalonia.Controls.DataGridCheckBoxColumnExt"> diff --git a/Source/LibationWinForms/AvaloniaUI/Controls/DataGridCheckBoxColumnExt.axaml.cs b/Source/LibationAvalonia/Controls/DataGridCheckBoxColumnExt.axaml.cs similarity index 83% rename from Source/LibationWinForms/AvaloniaUI/Controls/DataGridCheckBoxColumnExt.axaml.cs rename to Source/LibationAvalonia/Controls/DataGridCheckBoxColumnExt.axaml.cs index 2c7ca7be..2c686498 100644 --- a/Source/LibationWinForms/AvaloniaUI/Controls/DataGridCheckBoxColumnExt.axaml.cs +++ b/Source/LibationAvalonia/Controls/DataGridCheckBoxColumnExt.axaml.cs @@ -1,8 +1,8 @@ using Avalonia.Controls; -using LibationWinForms.AvaloniaUI.ViewModels; +using LibationAvalonia.ViewModels; using System; -namespace LibationWinForms.AvaloniaUI.Controls +namespace LibationAvalonia.Controls { public partial class DataGridCheckBoxColumnExt : DataGridCheckBoxColumn { diff --git a/Source/LibationAvalonia/Controls/DirectoryOrCustomSelectControl.axaml b/Source/LibationAvalonia/Controls/DirectoryOrCustomSelectControl.axaml new file mode 100644 index 00000000..f10b1767 --- /dev/null +++ b/Source/LibationAvalonia/Controls/DirectoryOrCustomSelectControl.axaml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + diff --git a/Source/LibationAvalonia/Dialogs/SetupDialog.axaml.cs b/Source/LibationAvalonia/Dialogs/SetupDialog.axaml.cs new file mode 100644 index 00000000..4733c449 --- /dev/null +++ b/Source/LibationAvalonia/Dialogs/SetupDialog.axaml.cs @@ -0,0 +1,39 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using LibationFileManager; + +namespace LibationAvalonia.Dialogs +{ + public partial class SetupDialog : Window + { + public bool IsNewUser { get;private set; } + public bool IsReturningUser { get;private set; } + public Configuration Config { get; init; } + public SetupDialog() + { + InitializeComponent(); + +#if DEBUG + this.AttachDevTools(); +#endif + } + + public void NewUser_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) + { + IsNewUser = true; + Close(DialogResult.OK); + } + + public void ReturningUser_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) + { + IsReturningUser = true; + Close(DialogResult.OK); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/Source/LibationWinForms/AvaloniaUI/Views/Dialogs/TagsBatchDialog.axaml b/Source/LibationAvalonia/Dialogs/TagsBatchDialog.axaml similarity index 89% rename from Source/LibationWinForms/AvaloniaUI/Views/Dialogs/TagsBatchDialog.axaml rename to Source/LibationAvalonia/Dialogs/TagsBatchDialog.axaml index f42d346e..d501024b 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/Dialogs/TagsBatchDialog.axaml +++ b/Source/LibationAvalonia/Dialogs/TagsBatchDialog.axaml @@ -3,12 +3,12 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="630" d:DesignHeight="110" - x:Class="LibationWinForms.AvaloniaUI.Views.Dialogs.TagsBatchDialog" + x:Class="LibationAvalonia.Dialogs.TagsBatchDialog" MinWidth="630" MinHeight="110" MaxWidth="630" MaxHeight="110" Title="Replace Tags" WindowStartupLocation="CenterOwner" - Icon="/AvaloniaUI/Assets/libation.ico"> + Icon="/Assets/libation.ico"> (form.GetType().Name); + + if (savedState is null) + return; + + // too small -- something must have gone wrong. use defaults + if (savedState.Width < form.MinWidth || savedState.Height < form.MinHeight) + { + savedState.Width = (int)form.Width; + savedState.Height = (int)form.Height; + } + + // Fit to the current screen size in case the screen resolution changed since the size was last persisted + if (savedState.Width > form.Screens.Primary.WorkingArea.Width) + savedState.Width = form.Screens.Primary.WorkingArea.Width; + if (savedState.Height > form.Screens.Primary.WorkingArea.Height) + savedState.Height = form.Screens.Primary.WorkingArea.Height; + + var rect = new PixelRect(savedState.X, savedState.Y, savedState.Width, savedState.Height); + + form.Width = savedState.Width; + form.Height = savedState.Height; + + // is proposed rect on a screen? + if (form.Screens.All.Any(screen => screen.WorkingArea.Contains(rect))) + { + form.WindowStartupLocation = WindowStartupLocation.Manual; + form.Position = new PixelPoint(savedState.X, savedState.Y); + } + else + { + form.WindowStartupLocation = WindowStartupLocation.CenterScreen; + } + + // FINAL: for Maximized: start normal state, set size and location, THEN set max state + form.WindowState = savedState.IsMaximized ? WindowState.Maximized : WindowState.Normal; + } + catch (Exception ex) + { + Serilog.Log.Logger.Error(ex, "Failed to save {form} size and location", form.GetType().Name); + } + } + public static void SaveSizeAndLocation(this Window form, Configuration config) + { + if (Design.IsDesignMode) return; + + try + { + var saveState = new FormSizeAndPosition(); + + saveState.IsMaximized = form.WindowState == WindowState.Maximized; + + // restore normal state to get real window size. + if (form.WindowState != WindowState.Normal) + { + form.WindowState = WindowState.Normal; + } + + saveState.X = form.Position.X; + saveState.Y = form.Position.Y; + + saveState.Width = (int)form.Bounds.Size.Width; + saveState.Height = (int)form.Bounds.Size.Height; + + config.SetObject(form.GetType().Name, saveState); + } + catch (Exception ex) + { + Serilog.Log.Logger.Error(ex, "Failed to save {form} size and location", form.GetType().Name); + } + } + + class FormSizeAndPosition + { + public int X; + public int Y; + public int Height; + public int Width; + public bool IsMaximized; + } + + + public static void HideMinMaxBtns(this Window form) + { + if (Design.IsDesignMode || App.PlatformID is not PlatformID.Win32NT) + return; + var handle = form.PlatformImpl.Handle.Handle; + var currentStyle = GetWindowLong(handle, GWL_STYLE); + + SetWindowLong(handle, GWL_STYLE, currentStyle & ~WS_MAXIMIZEBOX & ~WS_MINIMIZEBOX); + } + + const long WS_MINIMIZEBOX = 0x00020000L; + const long WS_MAXIMIZEBOX = 0x10000L; + const int GWL_STYLE = -16; + [System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "GetWindowLong")] + static extern long GetWindowLong(IntPtr hWnd, int nIndex); + [System.Runtime.InteropServices.DllImport("user32.dll")] + static extern int SetWindowLong(IntPtr hWnd, int nIndex, long dwNewLong); + } +} diff --git a/Source/LibationAvalonia/Libation.desktop b/Source/LibationAvalonia/Libation.desktop new file mode 100644 index 00000000..ad1b12f4 --- /dev/null +++ b/Source/LibationAvalonia/Libation.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Name=Libation +Exec=Libation +Icon=libation +Comment=Liberate your Audiobooks +Terminal=false +Type=Application +MimeType=x-content/unix-software; diff --git a/Source/LibationAvalonia/LibationAvalonia.csproj b/Source/LibationAvalonia/LibationAvalonia.csproj new file mode 100644 index 00000000..572e61f6 --- /dev/null +++ b/Source/LibationAvalonia/LibationAvalonia.csproj @@ -0,0 +1,134 @@ + + + + + WinExe + net6.0 + + copyused + true + libation.ico + Libation + + true + false + false + + + + + ..\bin-Avalonia\Debug + embedded + + + + ..\bin-Avalonia\Release + embedded + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ProcessBookControl.axaml + + + ProcessQueueControl.axaml + + + ProductsDisplay.axaml + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + + \ No newline at end of file diff --git a/Source/LibationAvalonia/LogMe.cs b/Source/LibationAvalonia/LogMe.cs new file mode 100644 index 00000000..19d1304c --- /dev/null +++ b/Source/LibationAvalonia/LogMe.cs @@ -0,0 +1,61 @@ +using System; +using System.Threading.Tasks; + +namespace LibationAvalonia +{ + public interface ILogForm + { + void WriteLine(string text); + } + + // decouple serilog and form. include convenience factory method + public class LogMe + { + public event EventHandler LogInfo; + public event EventHandler LogErrorString; + public event EventHandler<(Exception, string)> LogError; + + private LogMe() + { + LogInfo += (_, text) => Serilog.Log.Logger.Information($"Automated backup: {text}"); + LogErrorString += (_, text) => Serilog.Log.Logger.Error(text); + LogError += (_, tuple) => Serilog.Log.Logger.Error(tuple.Item1, tuple.Item2 ?? "Automated backup: error"); + } + private static ILogForm LogForm; + public static LogMe RegisterForm(T form) where T : ILogForm + { + var logMe = new LogMe(); + + if (form is null) + return logMe; + + LogForm = form; + + logMe.LogInfo += LogMe_LogInfo; + logMe.LogErrorString += LogMe_LogErrorString; + logMe.LogError += LogMe_LogError; + + return logMe; + } + + private static async void LogMe_LogError(object sender, (Exception, string) tuple) + { + await Task.Run(() => LogForm?.WriteLine(tuple.Item2 ?? "Automated backup: error")); + await Task.Run(() => LogForm?.WriteLine("ERROR: " + tuple.Item1.Message)); + } + + private static async void LogMe_LogErrorString(object sender, string text) + { + await Task.Run(() => LogForm?.WriteLine(text)); + } + + private static async void LogMe_LogInfo(object sender, string text) + { + await Task.Run(() => LogForm?.WriteLine(text)); + } + + public void Info(string text) => LogInfo?.Invoke(this, text); + public void Error(string text) => LogErrorString?.Invoke(this, text); + public void Error(Exception ex, string text = null) => LogError?.Invoke(this, (ex, text)); + } +} \ No newline at end of file diff --git a/Source/LibationAvalonia/MessageBox.cs b/Source/LibationAvalonia/MessageBox.cs new file mode 100644 index 00000000..3d25be05 --- /dev/null +++ b/Source/LibationAvalonia/MessageBox.cs @@ -0,0 +1,221 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using DataLayer; +using Dinah.Core.Logging; +using LibationAvalonia.ViewModels.Dialogs; +using LibationAvalonia.Dialogs; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace LibationAvalonia +{ + public enum DialogResult + { + None = 0, + OK = 1, + Cancel = 2, + Abort = 3, + Retry = 4, + Ignore = 5, + Yes = 6, + No = 7, + TryAgain = 10, + Continue = 11 + } + + public enum MessageBoxIcon + { + None = 0, + Error = 16, + Hand = 16, + Stop = 16, + Question = 32, + Exclamation = 48, + Warning = 48, + Asterisk = 64, + Information = 64 + } + + public enum MessageBoxButtons + { + OK, + OKCancel, + AbortRetryIgnore, + YesNoCancel, + YesNo, + RetryCancel, + CancelTryContinue + } + + public enum MessageBoxDefaultButton + { + Button1, + Button2 = 256, + Button3 = 512, + } + + public class MessageBox + { + + public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton) + => ShowCoreAsync(null, text, caption, buttons, icon, defaultButton); +public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, bool saveAndRestorePosition = true) + => ShowCoreAsync(null, text, caption, buttons, icon, MessageBoxDefaultButton.Button1, saveAndRestorePosition); +public static DialogResult Show(string text, string caption, MessageBoxButtons buttons) + => ShowCoreAsync(null, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1); + public static DialogResult Show(string text, string caption) + => ShowCoreAsync(null, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1); + public static DialogResult Show(string text) + => ShowCoreAsync(null, text, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1); +public static DialogResult Show(Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton) + => ShowCoreAsync(owner, text, caption, buttons, icon, defaultButton); + +public static DialogResult Show(Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon) + => ShowCoreAsync(owner, text, caption, buttons, icon, MessageBoxDefaultButton.Button1); + public static DialogResult Show(Window owner, string text, string caption, MessageBoxButtons buttons) + => ShowCoreAsync(owner, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1); + public static DialogResult Show(Window owner, string text, string caption) + => ShowCoreAsync(owner, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1); + public static DialogResult Show(Window owner, string text) + => ShowCoreAsync(owner, text, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1); + + + public static void VerboseLoggingWarning_ShowIfTrue() + { + // when turning on debug (and especially Verbose) to share logs, some privacy settings may not be obscured + if (Serilog.Log.Logger.IsVerboseEnabled()) + Show(@" +Warning: verbose logging is enabled. + +This should be used for debugging only. It creates many +more logs and debug files, neither of which are as +strictly anonymous. + +When you are finished debugging, it's highly recommended +to set your debug MinimumLevel to Information and restart +Libation. +".Trim(), "Verbose logging enabled", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + + public static DialogResult ShowConfirmationDialog(Window owner, IEnumerable libraryBooks, string format, string title, MessageBoxDefaultButton defaultButton = MessageBoxDefaultButton.Button1) + { + if (libraryBooks is null || !libraryBooks.Any()) + return DialogResult.Cancel; + + var count = libraryBooks.Count(); + + string thisThese = count > 1 ? "these" : "this"; + string bookBooks = count > 1 ? "books" : "book"; + string titlesAgg = libraryBooks.AggregateTitles(); + + var message + = string.Format(format, $"{thisThese} {count} {bookBooks}") + + $"\r\n\r\n{titlesAgg}"; + + return ShowCoreAsync(owner, + message, + title, + MessageBoxButtons.YesNo, + MessageBoxIcon.Question, + defaultButton); + } + + /// + /// Logs error. Displays a message box dialog with specified text and caption. + /// + /// Form calling this method. + /// The text to display in the message box. + /// The text to display in the title bar of the message box. + /// Exception to log. + public static void ShowAdminAlert(Window owner, string text, string caption, Exception exception) + { + // for development and debugging, show me what broke! + if (System.Diagnostics.Debugger.IsAttached) + throw exception; + + try + { + Serilog.Log.Logger.Error(exception, "Alert admin error: {@DebugText}", new { text, caption }); + } + catch { } + + var form = new MessageBoxAlertAdminDialog(text, caption, exception); + + DisplayWindow(form, owner); + } + + + private static DialogResult ShowCoreAsync(Window owner, string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, bool saveAndRestorePosition = true) + { + owner ??= (Application.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime).MainWindow; + + var dialog = new MessageBoxWindow(saveAndRestorePosition); + + dialog.HideMinMaxBtns(); + + var vm = new MessageBoxViewModel(message, caption, buttons, icon, defaultButton); + dialog.DataContext = vm; + dialog.ControlToFocusOnShow = dialog.FindControl(defaultButton.ToString()); + dialog.CanResize = false; + dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner; + var tbx = dialog.FindControl("messageTextBlock"); + + tbx.MinWidth = vm.TextBlockMinWidth; + tbx.Text = message; + + var thisScreen = owner.Screens.ScreenFromVisual(owner); + + var maxSize = new Size(0.20 * thisScreen.Bounds.Width, 0.9 * thisScreen.Bounds.Height - 55); + + var desiredMax = new Size(maxSize.Width, maxSize.Height); + + tbx.Measure(desiredMax); + + tbx.Height = tbx.DesiredSize.Height; + tbx.Width = tbx.DesiredSize.Width; + dialog.MinHeight = vm.FormHeightFromTboxHeight((int)tbx.DesiredSize.Height); + dialog.MinWidth = vm.FormWidthFromTboxWidth((int)tbx.DesiredSize.Width); + dialog.MaxHeight = dialog.MinHeight; + dialog.MaxWidth = dialog.MinWidth; + dialog.Height = dialog.MinHeight; + dialog.Width = dialog.MinWidth; + + return DisplayWindow(dialog, owner); + } + private static DialogResult DisplayWindow(Window toDisplay, Window owner) + { + if (owner is null) + { + if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + return toDisplay.ShowDialogSynchronously(desktop.MainWindow); + } + else + { + var window = new Window + { + IsVisible = false, + Height = 1, + Width = 1, + SystemDecorations = SystemDecorations.None, + ShowInTaskbar = false + }; + + window.Show(); + var result = toDisplay.ShowDialogSynchronously(window); + window.Close(); + return result; + } + + } + else + { + return toDisplay.ShowDialogSynchronously(owner); + } + } + + } +} diff --git a/Source/LibationAvalonia/ObjectComparer[T].cs b/Source/LibationAvalonia/ObjectComparer[T].cs new file mode 100644 index 00000000..5e3b23bb --- /dev/null +++ b/Source/LibationAvalonia/ObjectComparer[T].cs @@ -0,0 +1,10 @@ +using System; +using System.Collections; + +namespace LibationAvalonia +{ + internal class ObjectComparer : IComparer where T : IComparable + { + public int Compare(object x, object y) => ((T)x).CompareTo(y); + } +} diff --git a/Source/LibationAvalonia/Program.cs b/Source/LibationAvalonia/Program.cs new file mode 100644 index 00000000..8410798f --- /dev/null +++ b/Source/LibationAvalonia/Program.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using ApplicationServices; +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.ReactiveUI; +using LibationFileManager; + +namespace LibationAvalonia +{ + static class Program + { + private static string EXE_DIR = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); + + static async Task Main() + { + //***********************************************// + // // + // do not use Configuration before this line // + // // + //***********************************************// + // Migrations which must occur before configuration is loaded for the first time. Usually ones which alter the Configuration + var config = AppScaffolding.LibationScaffolding.RunPreConfigMigrations(); + + App.SetupRequired = !config.LibationSettingsAreValid; + + //Start as much work in parallel as possible. + var classicLifetimeTask = Task.Run(() => new ClassicDesktopStyleApplicationLifetime()); + var appBuilderTask = Task.Run(BuildAvaloniaApp); + + + if (!App.SetupRequired) + { + if (!RunDbMigrations(config)) + return; + + App.LibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true)); + } + + + + (await appBuilderTask).SetupWithLifetime(await classicLifetimeTask); + + classicLifetimeTask.Result.Start(null); + } + + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .LogToTrace() + .UseReactiveUI(); + + public static bool RunDbMigrations(Configuration config) + { + try + { + // most migrations go in here + AppScaffolding.LibationScaffolding.RunPostConfigMigrations(config); + AppScaffolding.LibationScaffolding.RunPostMigrationScaffolding(config); + + return true; + } + catch (Exception ex) + { + return false; + } + } + } +} diff --git a/Source/LibationAvalonia/Properties/PublishProfiles/FolderProfile.pubxml b/Source/LibationAvalonia/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 00000000..7a5531a3 --- /dev/null +++ b/Source/LibationAvalonia/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,17 @@ + + + + + Release + Any CPU + ..\bin\publish\linux-x64\ + FileSystem + net6.0 + win-x64 + true + false + false + + \ No newline at end of file diff --git a/Source/LibationAvalonia/Properties/PublishProfiles/LinuxProfile.pubxml b/Source/LibationAvalonia/Properties/PublishProfiles/LinuxProfile.pubxml new file mode 100644 index 00000000..4f12172b --- /dev/null +++ b/Source/LibationAvalonia/Properties/PublishProfiles/LinuxProfile.pubxml @@ -0,0 +1,16 @@ + + + + + Release + Any CPU + ..\bin-Avalonia\publish\linux-x64\ + FileSystem + net6.0 + linux-x64 + false + false + + \ No newline at end of file diff --git a/Source/LibationAvalonia/README.md b/Source/LibationAvalonia/README.md new file mode 100644 index 00000000..2b916b64 --- /dev/null +++ b/Source/LibationAvalonia/README.md @@ -0,0 +1,138 @@ +# Run Libation on Ubuntu +This walkthrough should get you up and running with Libation on your Ubuntu machine. + +Some limitations of the linux release are: +- Cannot customize how illegial filename characters are replaced. +- The Auto-update function is unavailable +- The "Hangover" app for debugging is not yet available. + +## Dependencies +### Dotnet Runtime +You must install the dotnet 6.0 runtime on your machine. + +First, add the Microsoft package signing key to your list of trusted keys and add the package repository. + +
+ Ubuntu 22.04 + + ```console + wget https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb + sudo dpkg -i packages-microsoft-prod.deb + rm packages-microsoft-prod.deb + ``` +
+ +
+ Ubuntu 21.10 + + ```console + wget https://packages.microsoft.com/config/ubuntu/21.10/packages-microsoft-prod.deb -O packages-microsoft-prod.deb + sudo dpkg -i packages-microsoft-prod.deb + rm packages-microsoft-prod.deb + ``` +
+ +
+ Ubuntu 20.04 + + ```console + wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb + sudo dpkg -i packages-microsoft-prod.deb + rm packages-microsoft-prod.deb + ``` +
+ +For other distributions, see [Microsoft's instructions for installing .NET on Linux](https://docs.microsoft.com/en-us/dotnet/core/install/linux). + +Then install the dotnet 6.0 runtime + +```console +sudo apt-get update; \ + sudo apt-get install -y apt-transport-https && \ + sudo apt-get update && \ + sudo apt-get install -y dotnet-runtime-6.0 +``` +### FFMpeg (Optional) +If you want to convert your audiobooks to mp3, install FFMpeg using the following command: + +```console +sudo apt-get install -y ffmpeg +``` + +## Install Libation + +Download the most recent linux-64 binaries zip file and save it as `libation-linux-bin.zip`. Save the 'install-libation.sh' bash script to a file. From the terminal make the script file executable: + +
+ install-libation.sh + + ```BASH + #!/bin/bash + + + FILE=$1 + + if [ -z "$FILE" ] + then echo "This script must be called with a the Libation Linux bin zip file as an argument." + exit + fi + +if [ "$EUID" -ne 0 ] + then echo "Please run as root" + exit +fi + + if [ ! -f "$FILE" ] + then echo "The file \"$FILE\" does not exist." + exit + fi + + echo "Extracting $FILE" + + FOLDER="$(dirname "$FILE")/libation_src" + echo "$FOLDER" + + sudo -u $SUDO_USER unzip -q -o ${FILE} -d ${FOLDER} + + if [ $? -ne 0 ] + then echo "Error unzipping ${FILE}" + exit + fi + + + sudo -u $SUDO_USER chmod +700 ${FOLDER}/Libation + sudo -u $SUDO_USER chmod +700 ${FOLDER}/LibationCli + + #Remove previous installation program files and sym link + rm /usr/bin/Libation + rm /usr/bin/LibationCli + rm /usr/bin/libationcli + rm /usr/lib/libation -r + + #Copy install files, icon and desktop file + cp ${FOLDER}/glass-with-glow_256.svg /usr/share/icons/hicolor/scalable/apps/libation.svg + cp ${FOLDER}/Libation.desktop /usr/share/applications/Libation.desktop + mv ${FOLDER}/ /usr/lib/libation + + chmod +666 /usr/share/icons/hicolor/scalable/apps/libation.svg + gtk-update-icon-cache -f /usr/share/icons/hicolor/ + ln -s /usr/lib/libation/Libation /usr/bin/Libation + ln -s /usr/lib/libation/LibationCli /usr/bin/LibationCli + ln -s /usr/lib/libation/LibationCli /usr/bin/libationcli + + echo "Done!" + + ``` +
+ +```console +chmod +700 install-libation.sh +``` +Then run the script with the libation binaries zipfile as an argument. +```console +sudo ./install-libation.sh libation-linux-bin.zip +``` + +You should now see Libation among your applications. + +Report bugs to https://github.com/rmcrackan/Libation/issues diff --git a/Source/LibationWinForms/AvaloniaUI/ViewLocator.cs b/Source/LibationAvalonia/ViewLocator.cs similarity index 85% rename from Source/LibationWinForms/AvaloniaUI/ViewLocator.cs rename to Source/LibationAvalonia/ViewLocator.cs index a17b206c..c972d6f1 100644 --- a/Source/LibationWinForms/AvaloniaUI/ViewLocator.cs +++ b/Source/LibationAvalonia/ViewLocator.cs @@ -1,9 +1,9 @@ using Avalonia.Controls; using Avalonia.Controls.Templates; -using LibationWinForms.AvaloniaUI.ViewModels; +using LibationAvalonia.ViewModels; using System; -namespace LibationWinForms.AvaloniaUI +namespace LibationAvalonia { public class ViewLocator : IDataTemplate { diff --git a/Source/LibationWinForms/AvaloniaUI/ViewModels/BookTags.cs b/Source/LibationAvalonia/ViewModels/BookTags.cs similarity index 79% rename from Source/LibationWinForms/AvaloniaUI/ViewModels/BookTags.cs rename to Source/LibationAvalonia/ViewModels/BookTags.cs index cf2c7664..0f24941d 100644 --- a/Source/LibationWinForms/AvaloniaUI/ViewModels/BookTags.cs +++ b/Source/LibationAvalonia/ViewModels/BookTags.cs @@ -1,4 +1,4 @@ -namespace LibationWinForms.AvaloniaUI.ViewModels +namespace LibationAvalonia.ViewModels { public class BookTags { diff --git a/Source/LibationWinForms/AvaloniaUI/ViewModels/Dialogs/MessageBoxViewModel.cs b/Source/LibationAvalonia/ViewModels/Dialogs/MessageBoxViewModel.cs similarity index 97% rename from Source/LibationWinForms/AvaloniaUI/ViewModels/Dialogs/MessageBoxViewModel.cs rename to Source/LibationAvalonia/ViewModels/Dialogs/MessageBoxViewModel.cs index bb3a2b45..c28bccb0 100644 --- a/Source/LibationWinForms/AvaloniaUI/ViewModels/Dialogs/MessageBoxViewModel.cs +++ b/Source/LibationAvalonia/ViewModels/Dialogs/MessageBoxViewModel.cs @@ -1,6 +1,6 @@ using System; -namespace LibationWinForms.AvaloniaUI.ViewModels.Dialogs +namespace LibationAvalonia.ViewModels.Dialogs { public class MessageBoxViewModel { diff --git a/Source/LibationWinForms/AvaloniaUI/ViewModels/GridEntry.cs b/Source/LibationAvalonia/ViewModels/GridEntry.cs similarity index 97% rename from Source/LibationWinForms/AvaloniaUI/ViewModels/GridEntry.cs rename to Source/LibationAvalonia/ViewModels/GridEntry.cs index 991ca7ee..4f055750 100644 --- a/Source/LibationWinForms/AvaloniaUI/ViewModels/GridEntry.cs +++ b/Source/LibationAvalonia/ViewModels/GridEntry.cs @@ -1,19 +1,15 @@ using Avalonia.Media; using DataLayer; using Dinah.Core; -using Dinah.Core.DataBinding; -using Dinah.Core.Drawing; using LibationFileManager; -using LibationWinForms.GridView; using ReactiveUI; using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; -using System.Drawing; using System.Linq; -namespace LibationWinForms.AvaloniaUI.ViewModels +namespace LibationAvalonia.ViewModels { public enum RemoveStatus { diff --git a/Source/LibationWinForms/AvaloniaUI/ViewModels/GridEntryCollection.cs b/Source/LibationAvalonia/ViewModels/GridEntryCollection.cs similarity index 99% rename from Source/LibationWinForms/AvaloniaUI/ViewModels/GridEntryCollection.cs rename to Source/LibationAvalonia/ViewModels/GridEntryCollection.cs index f7a66be2..38427e3e 100644 --- a/Source/LibationWinForms/AvaloniaUI/ViewModels/GridEntryCollection.cs +++ b/Source/LibationAvalonia/ViewModels/GridEntryCollection.cs @@ -6,7 +6,7 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; -namespace LibationWinForms.AvaloniaUI.ViewModels +namespace LibationAvalonia.ViewModels { /* * Allows filtering of the underlying ObservableCollection diff --git a/Source/LibationWinForms/AvaloniaUI/ViewModels/LiberateButtonStatus.cs b/Source/LibationAvalonia/ViewModels/LiberateButtonStatus.cs similarity index 91% rename from Source/LibationWinForms/AvaloniaUI/ViewModels/LiberateButtonStatus.cs rename to Source/LibationAvalonia/ViewModels/LiberateButtonStatus.cs index cd82e706..6d2e4f58 100644 --- a/Source/LibationWinForms/AvaloniaUI/ViewModels/LiberateButtonStatus.cs +++ b/Source/LibationAvalonia/ViewModels/LiberateButtonStatus.cs @@ -4,7 +4,7 @@ using ReactiveUI; using System; using System.Collections.Generic; -namespace LibationWinForms.AvaloniaUI.ViewModels +namespace LibationAvalonia.ViewModels { public class LiberateButtonStatus : ViewModelBase, IComparable { @@ -112,11 +112,7 @@ namespace LibationWinForms.AvaloniaUI.ViewModels { if (iconCache.ContainsKey(rescName)) return iconCache[rescName]; - var memoryStream = new System.IO.MemoryStream(); - - ((System.Drawing.Bitmap)Properties.Resources.ResourceManager.GetObject(rescName)).Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png); - memoryStream.Position = 0; - iconCache[rescName] = new Bitmap(memoryStream); + iconCache[rescName] = new Bitmap(App.OpenAsset(rescName + ".png")); return iconCache[rescName]; } } diff --git a/Source/LibationWinForms/AvaloniaUI/ViewModels/LibraryBookEntry.cs b/Source/LibationAvalonia/ViewModels/LibraryBookEntry.cs similarity index 99% rename from Source/LibationWinForms/AvaloniaUI/ViewModels/LibraryBookEntry.cs rename to Source/LibationAvalonia/ViewModels/LibraryBookEntry.cs index ac8140d1..54dc7fb8 100644 --- a/Source/LibationWinForms/AvaloniaUI/ViewModels/LibraryBookEntry.cs +++ b/Source/LibationAvalonia/ViewModels/LibraryBookEntry.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; -namespace LibationWinForms.AvaloniaUI.ViewModels +namespace LibationAvalonia.ViewModels { /// The View Model for a LibraryBook that is ContentType.Product or ContentType.Episode public class LibraryBookEntry : GridEntry diff --git a/Source/LibationWinForms/AvaloniaUI/ViewModels/MainWindowViewModel.cs b/Source/LibationAvalonia/ViewModels/MainWindowViewModel.cs similarity index 99% rename from Source/LibationWinForms/AvaloniaUI/ViewModels/MainWindowViewModel.cs rename to Source/LibationAvalonia/ViewModels/MainWindowViewModel.cs index 9e688a2f..f1656ae7 100644 --- a/Source/LibationWinForms/AvaloniaUI/ViewModels/MainWindowViewModel.cs +++ b/Source/LibationAvalonia/ViewModels/MainWindowViewModel.cs @@ -3,7 +3,7 @@ using Dinah.Core; using LibationFileManager; using ReactiveUI; -namespace LibationWinForms.AvaloniaUI.ViewModels +namespace LibationAvalonia.ViewModels { public class MainWindowViewModel : ViewModelBase { diff --git a/Source/LibationWinForms/AvaloniaUI/ViewModels/ProcessBookViewModel.cs b/Source/LibationAvalonia/ViewModels/ProcessBookViewModel.cs similarity index 95% rename from Source/LibationWinForms/AvaloniaUI/ViewModels/ProcessBookViewModel.cs rename to Source/LibationAvalonia/ViewModels/ProcessBookViewModel.cs index 2e785533..c2b673a0 100644 --- a/Source/LibationWinForms/AvaloniaUI/ViewModels/ProcessBookViewModel.cs +++ b/Source/LibationAvalonia/ViewModels/ProcessBookViewModel.cs @@ -11,7 +11,7 @@ using FileLiberator; using LibationFileManager; using ReactiveUI; -namespace LibationWinForms.AvaloniaUI.ViewModels +namespace LibationAvalonia.ViewModels { public enum ProcessBookResult { @@ -89,9 +89,9 @@ namespace LibationWinForms.AvaloniaUI.ViewModels private Processable NextProcessable() => _currentProcessable = null; private Processable _currentProcessable; private readonly Queue> Processes = new(); - private readonly ProcessQueue.LogMe Logger; + private readonly LogMe Logger; - public ProcessBookViewModel(LibraryBook libraryBook, ProcessQueue.LogMe logme) + public ProcessBookViewModel(LibraryBook libraryBook, LogMe logme) { LibraryBook = libraryBook; Logger = logme; @@ -152,7 +152,7 @@ namespace LibationWinForms.AvaloniaUI.ViewModels finally { if (Result == ProcessBookResult.None) - Result = await showRetry(LibraryBook); + Result = showRetry(LibraryBook); Status = Result switch { @@ -313,7 +313,7 @@ namespace LibationWinForms.AvaloniaUI.ViewModels #region Failure Handler - private async Task showRetry(LibraryBook libraryBook) + private ProcessBookResult showRetry(LibraryBook libraryBook) { Logger.Error("ERROR. All books have not been processed. Most recent book: processing failed"); @@ -346,7 +346,7 @@ $@" Title: {libraryBook.Book.Title} } // if null then ask user - dialogResult ??= await MessageBox.Show(string.Format(SkipDialogText + "\r\n\r\nSee Settings to avoid this box in the future.", details), "Skip importing this book?", SkipDialogButtons, MessageBoxIcon.Question, SkipDialogDefaultButton); + dialogResult ??= MessageBox.Show(string.Format(SkipDialogText + "\r\n\r\nSee Settings to avoid this box in the future.", details), "Skip importing this book?", SkipDialogButtons, MessageBoxIcon.Question, SkipDialogDefaultButton); if (dialogResult == DialogResult.Abort) return ProcessBookResult.FailedAbort; diff --git a/Source/LibationWinForms/AvaloniaUI/ViewModels/ProcessQueueViewModel.cs b/Source/LibationAvalonia/ViewModels/ProcessQueueViewModel.cs similarity index 95% rename from Source/LibationWinForms/AvaloniaUI/ViewModels/ProcessQueueViewModel.cs rename to Source/LibationAvalonia/ViewModels/ProcessQueueViewModel.cs index 15521a96..93521f6d 100644 --- a/Source/LibationWinForms/AvaloniaUI/ViewModels/ProcessQueueViewModel.cs +++ b/Source/LibationAvalonia/ViewModels/ProcessQueueViewModel.cs @@ -9,9 +9,9 @@ using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; -namespace LibationWinForms.AvaloniaUI.ViewModels +namespace LibationAvalonia.ViewModels { - public class ProcessQueueViewModel : ViewModelBase, ProcessQueue.ILogForm + public class ProcessQueueViewModel : ViewModelBase, ILogForm { public ObservableCollection LogEntries { get; } = new(); public TrackedQueue Items { get; } = new(); @@ -21,13 +21,13 @@ namespace LibationWinForms.AvaloniaUI.ViewModels public Task QueueRunner { get; private set; } public bool Running => !QueueRunner?.IsCompleted ?? false; - private readonly ProcessQueue.LogMe Logger; + private readonly LogMe Logger; public ProcessQueueViewModel() { Queue.QueuededCountChanged += Queue_QueuededCountChanged; Queue.CompletedCountChanged += Queue_CompletedCountChanged; - Logger = ProcessQueue.LogMe.RegisterForm(this); + Logger = LogMe.RegisterForm(this); } private int _completedCount; @@ -191,7 +191,7 @@ namespace LibationWinForms.AvaloniaUI.ViewModels } } - private void CounterTimer_Tick(object? state) + private void CounterTimer_Tick(object state) { string timeToStr(TimeSpan time) { diff --git a/Source/LibationWinForms/AvaloniaUI/ViewModels/ProductsDisplayViewModel.cs b/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs similarity index 96% rename from Source/LibationWinForms/AvaloniaUI/ViewModels/ProductsDisplayViewModel.cs rename to Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs index c5f884af..26858ca4 100644 --- a/Source/LibationWinForms/AvaloniaUI/ViewModels/ProductsDisplayViewModel.cs +++ b/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs @@ -11,9 +11,10 @@ using System.Collections; using Avalonia.Threading; using ApplicationServices; using AudibleUtilities; -using LibationWinForms.AvaloniaUI.Views; +using LibationAvalonia.Views; +using LibationAvalonia.Dialogs.Login; -namespace LibationWinForms.AvaloniaUI.ViewModels +namespace LibationAvalonia.ViewModels { public class ProductsDisplayViewModel : ViewModelBase { @@ -243,7 +244,7 @@ namespace LibationWinForms.AvaloniaUI.ViewModels return; var libraryBooks = selectedBooks.Select(rge => rge.LibraryBook).ToList(); - var result = await MessageBox.ShowConfirmationDialog( + var result = MessageBox.ShowConfirmationDialog( null, libraryBooks, $"Are you sure you want to remove {selectedBooks.Count} books from Libation's library?", @@ -307,7 +308,7 @@ namespace LibationWinForms.AvaloniaUI.ViewModels .Select(lbe => lbe.LibraryBook) .Where(lb => !lb.Book.HasLiberated()); - var removedBooks = await LibraryCommands.FindInactiveBooks(Login.WinformLoginChoiceEager.ApiExtendedFunc, lib, accounts); + var removedBooks = await LibraryCommands.FindInactiveBooks(AvaloniaLoginChoiceEager.ApiExtendedFunc, lib, accounts); var removable = allBooks.Where(lbe => removedBooks.Any(rb => rb.Book.AudibleProductId == lbe.AudibleProductId)).ToList(); @@ -316,7 +317,7 @@ namespace LibationWinForms.AvaloniaUI.ViewModels } catch (Exception ex) { - await MessageBox.ShowAdminAlert( + MessageBox.ShowAdminAlert( null, "Error scanning library. You may still manually select books to remove from Libation's library.", "Error scanning library", diff --git a/Source/LibationWinForms/AvaloniaUI/ViewModels/QueryExtensions.cs b/Source/LibationAvalonia/ViewModels/QueryExtensions.cs similarity index 96% rename from Source/LibationWinForms/AvaloniaUI/ViewModels/QueryExtensions.cs rename to Source/LibationAvalonia/ViewModels/QueryExtensions.cs index 073bc91f..91357a45 100644 --- a/Source/LibationWinForms/AvaloniaUI/ViewModels/QueryExtensions.cs +++ b/Source/LibationAvalonia/ViewModels/QueryExtensions.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; -namespace LibationWinForms.AvaloniaUI.ViewModels +namespace LibationAvalonia.ViewModels { #nullable enable internal static class QueryExtensions diff --git a/Source/LibationWinForms/AvaloniaUI/ViewModels/RowComparer.cs b/Source/LibationAvalonia/ViewModels/RowComparer.cs similarity index 98% rename from Source/LibationWinForms/AvaloniaUI/ViewModels/RowComparer.cs rename to Source/LibationAvalonia/ViewModels/RowComparer.cs index 2551d04e..73effeea 100644 --- a/Source/LibationWinForms/AvaloniaUI/ViewModels/RowComparer.cs +++ b/Source/LibationAvalonia/ViewModels/RowComparer.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Reflection; -namespace LibationWinForms.AvaloniaUI.ViewModels +namespace LibationAvalonia.ViewModels { /// /// This compare class ensures that all top-level grid entries (standalone books or series parents) diff --git a/Source/LibationWinForms/AvaloniaUI/ViewModels/SeriesEntry.cs b/Source/LibationAvalonia/ViewModels/SeriesEntry.cs similarity index 98% rename from Source/LibationWinForms/AvaloniaUI/ViewModels/SeriesEntry.cs rename to Source/LibationAvalonia/ViewModels/SeriesEntry.cs index c2ec3177..7589b3a2 100644 --- a/Source/LibationWinForms/AvaloniaUI/ViewModels/SeriesEntry.cs +++ b/Source/LibationAvalonia/ViewModels/SeriesEntry.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; -namespace LibationWinForms.AvaloniaUI.ViewModels +namespace LibationAvalonia.ViewModels { /// The View Model for a LibraryBook that is ContentType.Parent public class SeriesEntry : GridEntry diff --git a/Source/LibationWinForms/AvaloniaUI/ViewModels/TrackedQueue[T].cs b/Source/LibationAvalonia/ViewModels/TrackedQueue[T].cs similarity index 99% rename from Source/LibationWinForms/AvaloniaUI/ViewModels/TrackedQueue[T].cs rename to Source/LibationAvalonia/ViewModels/TrackedQueue[T].cs index dd2ff3a7..f4889a77 100644 --- a/Source/LibationWinForms/AvaloniaUI/ViewModels/TrackedQueue[T].cs +++ b/Source/LibationAvalonia/ViewModels/TrackedQueue[T].cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -namespace LibationWinForms.AvaloniaUI.ViewModels +namespace LibationAvalonia.ViewModels { public enum QueuePosition { diff --git a/Source/LibationWinForms/AvaloniaUI/ViewModels/ViewModelBase.cs b/Source/LibationAvalonia/ViewModels/ViewModelBase.cs similarity index 77% rename from Source/LibationWinForms/AvaloniaUI/ViewModels/ViewModelBase.cs rename to Source/LibationAvalonia/ViewModels/ViewModelBase.cs index 060934f0..4dd106c2 100644 --- a/Source/LibationWinForms/AvaloniaUI/ViewModels/ViewModelBase.cs +++ b/Source/LibationAvalonia/ViewModels/ViewModelBase.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.Text; -namespace LibationWinForms.AvaloniaUI.ViewModels +namespace LibationAvalonia.ViewModels { public class ViewModelBase : ReactiveObject { diff --git a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.BackupCounts.axaml.cs b/Source/LibationAvalonia/Views/MainWindow/MainWindow.BackupCounts.axaml.cs similarity index 96% rename from Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.BackupCounts.axaml.cs rename to Source/LibationAvalonia/Views/MainWindow/MainWindow.BackupCounts.axaml.cs index e7e57006..50521969 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.BackupCounts.axaml.cs +++ b/Source/LibationAvalonia/Views/MainWindow/MainWindow.BackupCounts.axaml.cs @@ -4,7 +4,7 @@ using System.Linq; using Avalonia.Threading; using Dinah.Core; -namespace LibationWinForms.AvaloniaUI.Views +namespace LibationAvalonia.Views { //DONE public partial class MainWindow diff --git a/Source/LibationAvalonia/Views/MainWindow/MainWindow.Export.axaml.cs b/Source/LibationAvalonia/Views/MainWindow/MainWindow.Export.axaml.cs new file mode 100644 index 00000000..0c2e925a --- /dev/null +++ b/Source/LibationAvalonia/Views/MainWindow/MainWindow.Export.axaml.cs @@ -0,0 +1,52 @@ +using ApplicationServices; +using Avalonia.Controls; +using System; +using System.Linq; + +namespace LibationAvalonia.Views +{ + //DONE + public partial class MainWindow + { + private void Configure_Export() { } + + public async void exportLibraryToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) + { + try + { + var saveFileDialog = new SaveFileDialog + { + Title = "Where to export Library", + }; + saveFileDialog.Filters.Add(new FileDialogFilter { Name = "Excel Workbook (*.xlsx)", Extensions = new() { "xlsx" } }); + saveFileDialog.Filters.Add(new FileDialogFilter { Name = "CSV files (*.csv)", Extensions = new() { "csv" } }); + saveFileDialog.Filters.Add(new FileDialogFilter { Name = "JSON files (*.json)", Extensions = new() { "json" } }); + saveFileDialog.Filters.Add(new FileDialogFilter { Name = "All files (*.*)", Extensions = new() { "*" } }); + + var fileName = await saveFileDialog.ShowAsync(this); + if (fileName is null) return; + + var ext = System.IO.Path.GetExtension(fileName); + switch (ext) + { + case "xlsx": // xlsx + default: + LibraryExporter.ToXlsx(fileName); + break; + case "csv": // csv + LibraryExporter.ToCsv(fileName); + break; + case "json": // json + LibraryExporter.ToJson(fileName); + break; + } + + MessageBox.Show("Library exported to:\r\n" + fileName, "Library Exported"); + } + catch (Exception ex) + { + MessageBox.ShowAdminAlert(this, "Error attempting to export your library.", "Error exporting", ex); + } + } + } +} diff --git a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.Filter.axaml.cs b/Source/LibationAvalonia/Views/MainWindow/MainWindow.Filter.axaml.cs similarity index 76% rename from Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.Filter.axaml.cs rename to Source/LibationAvalonia/Views/MainWindow/MainWindow.Filter.axaml.cs index 67fb8891..ea4ba592 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.Filter.axaml.cs +++ b/Source/LibationAvalonia/Views/MainWindow/MainWindow.Filter.axaml.cs @@ -1,10 +1,9 @@ using Avalonia.Input; -using LibationWinForms.Dialogs; using System; using System.Linq; using System.Threading.Tasks; -namespace LibationWinForms.AvaloniaUI.Views +namespace LibationAvalonia.Views { //DONE public partial class MainWindow @@ -12,7 +11,7 @@ namespace LibationWinForms.AvaloniaUI.Views protected void Configure_Filter() { } public async void filterHelpBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) - => await (new LibationWinForms.AvaloniaUI.Views.Dialogs.SearchSyntaxDialog()).ShowDialog(this); + => await (new LibationAvalonia.Dialogs.SearchSyntaxDialog()).ShowDialog(this); public async void filterSearchTb_KeyPress(object sender, KeyEventArgs e) { @@ -40,7 +39,7 @@ namespace LibationWinForms.AvaloniaUI.Views } catch (Exception ex) { - await MessageBox.Show($"Bad filter string:\r\n\r\n{ex.Message}", "Bad filter string", MessageBoxButtons.OK, MessageBoxIcon.Error); + MessageBox.Show($"Bad filter string:\r\n\r\n{ex.Message}", "Bad filter string", MessageBoxButtons.OK, MessageBoxIcon.Error); // re-apply last good filter await performFilter(lastGoodFilter); diff --git a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.Liberate.axaml.cs b/Source/LibationAvalonia/Views/MainWindow/MainWindow.Liberate.axaml.cs similarity index 96% rename from Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.Liberate.axaml.cs rename to Source/LibationAvalonia/Views/MainWindow/MainWindow.Liberate.axaml.cs index d021f762..171f4c55 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.Liberate.axaml.cs +++ b/Source/LibationAvalonia/Views/MainWindow/MainWindow.Liberate.axaml.cs @@ -3,7 +3,7 @@ using System; using System.Linq; using System.Threading.Tasks; -namespace LibationWinForms.AvaloniaUI.Views +namespace LibationAvalonia.Views { //DONE public partial class MainWindow @@ -40,7 +40,7 @@ namespace LibationWinForms.AvaloniaUI.Views public async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args) { - var result = await MessageBox.Show( + var result = MessageBox.Show( "This converts all m4b titles in your library to mp3 files. Original files are not deleted." + "\r\nFor large libraries this will take a long time and will take up more disk space." + "\r\n\r\nContinue?" diff --git a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.ProcessQueue.axaml.cs b/Source/LibationAvalonia/Views/MainWindow/MainWindow.ProcessQueue.axaml.cs similarity index 88% rename from Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.ProcessQueue.axaml.cs rename to Source/LibationAvalonia/Views/MainWindow/MainWindow.ProcessQueue.axaml.cs index c334dbff..71d77635 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.ProcessQueue.axaml.cs +++ b/Source/LibationAvalonia/Views/MainWindow/MainWindow.ProcessQueue.axaml.cs @@ -4,7 +4,7 @@ using LibationFileManager; using System; using System.Linq; -namespace LibationWinForms.AvaloniaUI.Views +namespace LibationAvalonia.Views { //DONE public partial class MainWindow @@ -15,7 +15,7 @@ namespace LibationWinForms.AvaloniaUI.Views SetQueueCollapseState(collapseState); } - public async void ProductsDisplay_LiberateClicked(object sender, LibraryBook libraryBook) + public void ProductsDisplay_LiberateClicked(object sender, LibraryBook libraryBook) { try { @@ -35,10 +35,11 @@ namespace LibationWinForms.AvaloniaUI.Views { // liberated: open explorer to file var filePath = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId); - if (!Go.To.File(filePath?.ShortPathName)) + + if (!App.GoToFile(filePath?.ShortPathName)) { var suffix = string.IsNullOrWhiteSpace(filePath) ? "" : $":\r\n{filePath}"; - await MessageBox.Show($"File not found" + suffix); + MessageBox.Show($"File not found" + suffix); } } } diff --git a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.QuickFilters.axaml.cs b/Source/LibationAvalonia/Views/MainWindow/MainWindow.QuickFilters.axaml.cs similarity index 96% rename from Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.QuickFilters.axaml.cs rename to Source/LibationAvalonia/Views/MainWindow/MainWindow.QuickFilters.axaml.cs index d1c3408a..9a4ddb5a 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.QuickFilters.axaml.cs +++ b/Source/LibationAvalonia/Views/MainWindow/MainWindow.QuickFilters.axaml.cs @@ -1,10 +1,9 @@ using Avalonia.Controls; using LibationFileManager; -using LibationWinForms.Dialogs; using System; using System.Linq; -namespace LibationWinForms.AvaloniaUI.Views +namespace LibationAvalonia.Views { //DONE public partial class MainWindow diff --git a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.RemoveBooks.axaml.cs b/Source/LibationAvalonia/Views/MainWindow/MainWindow.RemoveBooks.axaml.cs similarity index 97% rename from Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.RemoveBooks.axaml.cs rename to Source/LibationAvalonia/Views/MainWindow/MainWindow.RemoveBooks.axaml.cs index 5d0c4cbc..85e3b135 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.RemoveBooks.axaml.cs +++ b/Source/LibationAvalonia/Views/MainWindow/MainWindow.RemoveBooks.axaml.cs @@ -1,9 +1,8 @@ using AudibleUtilities; -using LibationWinForms.Dialogs; using System; using System.Linq; -namespace LibationWinForms.AvaloniaUI.Views +namespace LibationAvalonia.Views { //DONE public partial class MainWindow diff --git a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.ScanAuto.axaml.cs b/Source/LibationAvalonia/Views/MainWindow/MainWindow.ScanAuto.axaml.cs similarity index 95% rename from Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.ScanAuto.axaml.cs rename to Source/LibationAvalonia/Views/MainWindow/MainWindow.ScanAuto.axaml.cs index a441a441..37cb09a2 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.ScanAuto.axaml.cs +++ b/Source/LibationAvalonia/Views/MainWindow/MainWindow.ScanAuto.axaml.cs @@ -7,7 +7,7 @@ using System; using System.Collections.Generic; using System.Linq; -namespace LibationWinForms.AvaloniaUI.Views +namespace LibationAvalonia.Views { //DONE public partial class MainWindow @@ -35,7 +35,7 @@ namespace LibationWinForms.AvaloniaUI.Views // in autoScan, new books SHALL NOT show dialog try { - await LibraryCommands.ImportAccountAsync(Login.WinformLoginChoiceEager.ApiExtendedFunc, accounts); + await LibraryCommands.ImportAccountAsync(Dialogs.Login.AvaloniaLoginChoiceEager.ApiExtendedFunc, accounts); } catch (Exception ex) { diff --git a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.ScanManual.axaml.cs b/Source/LibationAvalonia/Views/MainWindow/MainWindow.ScanManual.axaml.cs similarity index 86% rename from Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.ScanManual.axaml.cs rename to Source/LibationAvalonia/Views/MainWindow/MainWindow.ScanManual.axaml.cs index 254ed86d..d42c983c 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.ScanManual.axaml.cs +++ b/Source/LibationAvalonia/Views/MainWindow/MainWindow.ScanManual.axaml.cs @@ -2,13 +2,12 @@ using AudibleUtilities; using Avalonia.Controls; using LibationFileManager; -using LibationWinForms.Dialogs; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -namespace LibationWinForms.AvaloniaUI.Views +namespace LibationAvalonia.Views { //DONE public partial class MainWindow @@ -27,7 +26,7 @@ namespace LibationWinForms.AvaloniaUI.Views public async void noAccountsYetAddAccountToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) { - await MessageBox.Show("To load your Audible library, come back here to the Import menu after adding your account"); + MessageBox.Show("To load your Audible library, come back here to the Import menu after adding your account"); await new Dialogs.AccountsDialog().ShowDialog(this); } @@ -63,15 +62,15 @@ namespace LibationWinForms.AvaloniaUI.Views { try { - var (totalProcessed, newAdded) = await LibraryCommands.ImportAccountAsync(Login.WinformLoginChoiceEager.ApiExtendedFunc, accounts); + var (totalProcessed, newAdded) = await LibraryCommands.ImportAccountAsync(Dialogs.Login.AvaloniaLoginChoiceEager.ApiExtendedFunc, accounts); // this is here instead of ScanEnd so that the following is only possible when it's user-initiated, not automatic loop if (Configuration.Instance.ShowImportedStats && newAdded > 0) - await MessageBox.Show($"Total processed: {totalProcessed}\r\nNew: {newAdded}"); + MessageBox.Show($"Total processed: {totalProcessed}\r\nNew: {newAdded}"); } catch (Exception ex) { - await MessageBox.ShowAdminAlert( + MessageBox.ShowAdminAlert( this, "Error importing library. Please try again. If this still happens after 2 or 3 tries, stop and contact administrator", "Error importing library", diff --git a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.ScanNotification.axaml.cs b/Source/LibationAvalonia/Views/MainWindow/MainWindow.ScanNotification.axaml.cs similarity index 92% rename from Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.ScanNotification.axaml.cs rename to Source/LibationAvalonia/Views/MainWindow/MainWindow.ScanNotification.axaml.cs index 09c7bfd4..08f39e8d 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.ScanNotification.axaml.cs +++ b/Source/LibationAvalonia/Views/MainWindow/MainWindow.ScanNotification.axaml.cs @@ -2,7 +2,7 @@ using System; using System.Linq; -namespace LibationWinForms.AvaloniaUI.Views +namespace LibationAvalonia.Views { //DONE public partial class MainWindow diff --git a/Source/LibationAvalonia/Views/MainWindow/MainWindow.Settings.axaml.cs b/Source/LibationAvalonia/Views/MainWindow/MainWindow.Settings.axaml.cs new file mode 100644 index 00000000..d59ba480 --- /dev/null +++ b/Source/LibationAvalonia/Views/MainWindow/MainWindow.Settings.axaml.cs @@ -0,0 +1,18 @@ +using System; +using System.Linq; + +namespace LibationAvalonia.Views +{ + //DONE + public partial class MainWindow + { + private void Configure_Settings() { } + + public async void accountsToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) => await new Dialogs.AccountsDialog().ShowDialog(this); + + public async void basicSettingsToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) => await new Dialogs.SettingsDialog().ShowDialog(this); + + public void aboutToolStripMenuItem_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) + => MessageBox.Show($"Running Libation version {AppScaffolding.LibationScaffolding.BuildVersion}", $"Libation v{AppScaffolding.LibationScaffolding.BuildVersion}"); + } +} diff --git a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.VisibleBooks.axaml.cs b/Source/LibationAvalonia/Views/MainWindow/MainWindow.VisibleBooks.axaml.cs similarity index 92% rename from Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.VisibleBooks.axaml.cs rename to Source/LibationAvalonia/Views/MainWindow/MainWindow.VisibleBooks.axaml.cs index a416d895..18cc6068 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.VisibleBooks.axaml.cs +++ b/Source/LibationAvalonia/Views/MainWindow/MainWindow.VisibleBooks.axaml.cs @@ -1,12 +1,11 @@ using ApplicationServices; using Avalonia.Threading; using DataLayer; -using LibationWinForms.Dialogs; using System; using System.Linq; using System.Threading.Tasks; -namespace LibationWinForms.AvaloniaUI.Views +namespace LibationAvalonia.Views { //DONE public partial class MainWindow @@ -48,7 +47,7 @@ namespace LibationWinForms.AvaloniaUI.Views var visibleLibraryBooks = _viewModel.ProductsDisplay.GetVisibleBookEntries(); - var confirmationResult = await MessageBox.ShowConfirmationDialog( + var confirmationResult = MessageBox.ShowConfirmationDialog( this, visibleLibraryBooks, "Are you sure you want to replace tags in {0}?", @@ -71,7 +70,7 @@ namespace LibationWinForms.AvaloniaUI.Views var visibleLibraryBooks = _viewModel.ProductsDisplay.GetVisibleBookEntries(); - var confirmationResult = await MessageBox.ShowConfirmationDialog( + var confirmationResult = MessageBox.ShowConfirmationDialog( this, visibleLibraryBooks, "Are you sure you want to replace downloaded status in {0}?", @@ -89,7 +88,7 @@ namespace LibationWinForms.AvaloniaUI.Views { var visibleLibraryBooks = _viewModel.ProductsDisplay.GetVisibleBookEntries(); - var confirmationResult = await MessageBox.ShowConfirmationDialog( + var confirmationResult = MessageBox.ShowConfirmationDialog( this, visibleLibraryBooks, "Are you sure you want to remove {0} from Libation's library?", diff --git a/Source/LibationAvalonia/Views/MainWindow/MainWindow._NoUI.axaml.cs b/Source/LibationAvalonia/Views/MainWindow/MainWindow._NoUI.axaml.cs new file mode 100644 index 00000000..d04623e5 --- /dev/null +++ b/Source/LibationAvalonia/Views/MainWindow/MainWindow._NoUI.axaml.cs @@ -0,0 +1,26 @@ +using LibationFileManager; +using System; +using System.IO; +using System.Linq; + +namespace LibationAvalonia.Views +{ + public partial class MainWindow + { + private void Configure_NonUI() + { + using var ms1 = new MemoryStream(); + App.OpenAsset("img-coverart-prod-unavailable_80x80.jpg").CopyTo(ms1); + PictureStorage.SetDefaultImage(PictureSize._80x80, ms1.ToArray()); + + using var ms2 = new MemoryStream(); + App.OpenAsset("img-coverart-prod-unavailable_300x300.jpg").CopyTo(ms2); + PictureStorage.SetDefaultImage(PictureSize._300x300, ms2.ToArray()); + + using var ms3 = new MemoryStream(); + App.OpenAsset("img-coverart-prod-unavailable_500x500.jpg").CopyTo(ms3); + PictureStorage.SetDefaultImage(PictureSize._500x500, ms3.ToArray()); + PictureStorage.SetDefaultImage(PictureSize.Native, ms3.ToArray()); + } + } +} diff --git a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.axaml b/Source/LibationAvalonia/Views/MainWindow/MainWindow.axaml similarity index 95% rename from Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.axaml rename to Source/LibationAvalonia/Views/MainWindow/MainWindow.axaml index ed5f4aae..3b1f59dc 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.axaml +++ b/Source/LibationAvalonia/Views/MainWindow/MainWindow.axaml @@ -2,15 +2,15 @@ + Icon="/Assets/libation.ico"> @@ -130,7 +130,7 @@ - + diff --git a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.axaml.cs b/Source/LibationAvalonia/Views/MainWindow/MainWindow.axaml.cs similarity index 93% rename from Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.axaml.cs rename to Source/LibationAvalonia/Views/MainWindow/MainWindow.axaml.cs index fd049dd8..f8f0d155 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/MainWindow/MainWindow.axaml.cs +++ b/Source/LibationAvalonia/Views/MainWindow/MainWindow.axaml.cs @@ -4,14 +4,14 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml; using System; using Avalonia.ReactiveUI; -using LibationWinForms.AvaloniaUI.ViewModels; +using LibationAvalonia.ViewModels; using LibationFileManager; using DataLayer; using System.Collections.Generic; using System.Linq; -using LibationWinForms.AvaloniaUI.Views.Dialogs; +using LibationAvalonia.Dialogs; -namespace LibationWinForms.AvaloniaUI.Views +namespace LibationAvalonia.Views { public partial class MainWindow : ReactiveWindow { @@ -67,8 +67,11 @@ namespace LibationWinForms.AvaloniaUI.Views private async void MainWindow_Opened(object sender, EventArgs e) { - //var settings = new SettingsDialog(); - //settings.Show(); + /* + var charReplace = new EditReplacementChars(); + + await charReplace.ShowDialog(this); + */ } public void ProductsDisplay_Initialized1(object sender, EventArgs e) diff --git a/Source/LibationWinForms/AvaloniaUI/Views/ProcessBookControl.axaml b/Source/LibationAvalonia/Views/ProcessBookControl.axaml similarity index 79% rename from Source/LibationWinForms/AvaloniaUI/Views/ProcessBookControl.axaml rename to Source/LibationAvalonia/Views/ProcessBookControl.axaml index 4e4b63ce..e9bf30ac 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/ProcessBookControl.axaml +++ b/Source/LibationAvalonia/Views/ProcessBookControl.axaml @@ -3,7 +3,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="90" MaxHeight="90" MinHeight="90" MinWidth="300" - x:Class="LibationWinForms.AvaloniaUI.Views.ProcessBookControl" Background="{Binding BackgroundColor}"> + x:Class="LibationAvalonia.Views.ProcessBookControl" Background="{Binding BackgroundColor}"> @@ -30,21 +30,21 @@ diff --git a/Source/LibationWinForms/AvaloniaUI/Views/ProcessBookControl.axaml.cs b/Source/LibationAvalonia/Views/ProcessBookControl.axaml.cs similarity index 91% rename from Source/LibationWinForms/AvaloniaUI/Views/ProcessBookControl.axaml.cs rename to Source/LibationAvalonia/Views/ProcessBookControl.axaml.cs index 01376344..62ec2c25 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/ProcessBookControl.axaml.cs +++ b/Source/LibationAvalonia/Views/ProcessBookControl.axaml.cs @@ -2,11 +2,11 @@ using Avalonia; using System; using Avalonia.Controls; using Avalonia.Markup.Xaml; -using LibationWinForms.AvaloniaUI.ViewModels; +using LibationAvalonia.ViewModels; using ApplicationServices; using DataLayer; -namespace LibationWinForms.AvaloniaUI.Views +namespace LibationAvalonia.Views { public delegate void QueueItemPositionButtonClicked(ProcessBookViewModel item, QueuePosition queueButton); public delegate void QueueItemCancelButtonClicked(ProcessBookViewModel item); @@ -23,7 +23,7 @@ namespace LibationWinForms.AvaloniaUI.Views using var context = DbContexts.GetContext(); DataContext = new ProcessBookViewModel( context.GetLibraryBook_Flat_NoTracking("B017V4IM1G"), - ProcessQueue.LogMe.RegisterForm(default(ProcessQueue.ILogForm)) + LogMe.RegisterForm(default(ILogForm)) ); return; } diff --git a/Source/LibationWinForms/AvaloniaUI/Views/ProcessQueueControl.axaml b/Source/LibationAvalonia/Views/ProcessQueueControl.axaml similarity index 94% rename from Source/LibationWinForms/AvaloniaUI/Views/ProcessQueueControl.axaml rename to Source/LibationAvalonia/Views/ProcessQueueControl.axaml index d57b3002..e090119c 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/ProcessQueueControl.axaml +++ b/Source/LibationAvalonia/Views/ProcessQueueControl.axaml @@ -3,10 +3,10 @@ xmlns="https://github.com/avaloniaui" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:views="clr-namespace:LibationWinForms.AvaloniaUI.Views" + xmlns:views="clr-namespace:LibationAvalonia.Views" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" mc:Ignorable="d" d:DesignWidth="450" d:DesignHeight="850" - x:Class="LibationWinForms.AvaloniaUI.Views.ProcessQueueControl"> + x:Class="LibationAvalonia.Views.ProcessQueueControl"> @@ -95,15 +95,15 @@ - + - + - + diff --git a/Source/LibationWinForms/AvaloniaUI/Views/ProcessQueueControl.axaml.cs b/Source/LibationAvalonia/Views/ProcessQueueControl.axaml.cs similarity index 96% rename from Source/LibationWinForms/AvaloniaUI/Views/ProcessQueueControl.axaml.cs rename to Source/LibationAvalonia/Views/ProcessQueueControl.axaml.cs index 0df50ef4..7a4c3aef 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/ProcessQueueControl.axaml.cs +++ b/Source/LibationAvalonia/Views/ProcessQueueControl.axaml.cs @@ -3,12 +3,12 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; using DataLayer; -using LibationWinForms.AvaloniaUI.ViewModels; +using LibationAvalonia.ViewModels; using System; using System.Collections.Generic; using System.Linq; -namespace LibationWinForms.AvaloniaUI.Views +namespace LibationAvalonia.Views { public partial class ProcessQueueControl : UserControl { @@ -26,7 +26,7 @@ namespace LibationWinForms.AvaloniaUI.Views if (Design.IsDesignMode) { var vm = new ProcessQueueViewModel(); - var Logger = ProcessQueue.LogMe.RegisterForm(vm); + var Logger = LogMe.RegisterForm(vm); DataContext = vm; using var context = DbContexts.GetContext(); List testList = new() diff --git a/Source/LibationWinForms/AvaloniaUI/Views/ProductsDisplay.axaml b/Source/LibationAvalonia/Views/ProductsDisplay.axaml similarity index 96% rename from Source/LibationWinForms/AvaloniaUI/Views/ProductsDisplay.axaml rename to Source/LibationAvalonia/Views/ProductsDisplay.axaml index 99f2dd8f..65fade99 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/ProductsDisplay.axaml +++ b/Source/LibationAvalonia/Views/ProductsDisplay.axaml @@ -2,10 +2,10 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:views="clr-namespace:LibationWinForms.AvaloniaUI.Views" - xmlns:controls="clr-namespace:LibationWinForms.AvaloniaUI.Controls" + xmlns:views="clr-namespace:LibationAvalonia.Views" + xmlns:controls="clr-namespace:LibationAvalonia.Controls" mc:Ignorable="d" d:DesignWidth="1560" d:DesignHeight="400" - x:Class="LibationWinForms.AvaloniaUI.Views.ProductsDisplay"> + x:Class="LibationAvalonia.Views.ProductsDisplay"> @@ -189,7 +189,7 @@ diff --git a/Source/LibationWinForms/AvaloniaUI/Views/ProductsDisplay.axaml.cs b/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs similarity index 98% rename from Source/LibationWinForms/AvaloniaUI/Views/ProductsDisplay.axaml.cs rename to Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs index 33426bd3..b5a7c532 100644 --- a/Source/LibationWinForms/AvaloniaUI/Views/ProductsDisplay.axaml.cs +++ b/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs @@ -5,13 +5,13 @@ using Avalonia.Markup.Xaml; using DataLayer; using FileLiberator; using LibationFileManager; -using LibationWinForms.AvaloniaUI.ViewModels; -using LibationWinForms.AvaloniaUI.Views.Dialogs; +using LibationAvalonia.ViewModels; +using LibationAvalonia.Dialogs; using System; using System.Collections.Generic; using System.Linq; -namespace LibationWinForms.AvaloniaUI.Views +namespace LibationAvalonia.Views { public partial class ProductsDisplay : UserControl { diff --git a/Source/LibationAvalonia/glass-with-glow_256.svg b/Source/LibationAvalonia/glass-with-glow_256.svg new file mode 100644 index 00000000..df935c14 --- /dev/null +++ b/Source/LibationAvalonia/glass-with-glow_256.svg @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/Source/LibationAvalonia/libation.ico b/Source/LibationAvalonia/libation.ico new file mode 100644 index 00000000..d3e00443 Binary files /dev/null and b/Source/LibationAvalonia/libation.ico differ diff --git a/Source/LibationCli/LibationCli.csproj b/Source/LibationCli/LibationCli.csproj index 035b0aa6..9aa6ab5f 100644 --- a/Source/LibationCli/LibationCli.csproj +++ b/Source/LibationCli/LibationCli.csproj @@ -3,7 +3,7 @@ Exe - net6.0-windows + net6.0 true win-x64 false @@ -46,6 +46,7 @@ + diff --git a/Source/LibationCli/Properties/PublishProfiles/FolderProfile.pubxml b/Source/LibationCli/Properties/PublishProfiles/FolderProfile.pubxml index 796e806b..257cfc2b 100644 --- a/Source/LibationCli/Properties/PublishProfiles/FolderProfile.pubxml +++ b/Source/LibationCli/Properties/PublishProfiles/FolderProfile.pubxml @@ -8,9 +8,10 @@ https://go.microsoft.com/fwlink/?LinkID=208121. Any CPU ..\bin\publish\ FileSystem - net6.0-windows + net6.0 win-x64 true false + false \ No newline at end of file diff --git a/Source/LibationCli/Properties/PublishProfiles/LinuxProfile.pubxml b/Source/LibationCli/Properties/PublishProfiles/LinuxProfile.pubxml new file mode 100644 index 00000000..fe716d12 --- /dev/null +++ b/Source/LibationCli/Properties/PublishProfiles/LinuxProfile.pubxml @@ -0,0 +1,16 @@ + + + + + Release + Any CPU + ..\bin-Avalonia\publish\linux-x64 + FileSystem + net6.0 + linux-x64 + false + false + + \ No newline at end of file diff --git a/Source/LibationFileManager/Configuration.cs b/Source/LibationFileManager/Configuration.cs index cf4d0a3b..a3cb7ad3 100644 --- a/Source/LibationFileManager/Configuration.cs +++ b/Source/LibationFileManager/Configuration.cs @@ -345,7 +345,7 @@ namespace LibationFileManager #endregion #region known directories - public static string AppDir_Relative => $@".\{LIBATION_FILES_KEY}"; + public static string AppDir_Relative => $@".{Path.PathSeparator}{LIBATION_FILES_KEY}"; public static string AppDir_Absolute => Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Exe.FileLocationOnDisk), LIBATION_FILES_KEY)); public static string MyDocs => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Libation")); public static string WinTemp => Path.GetFullPath(Path.Combine(Path.GetTempPath(), "Libation")); @@ -451,7 +451,7 @@ namespace LibationFileManager #endregion #region LibationFiles - private static string APPSETTINGS_JSON { get; } = Path.Combine(Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName), "appsettings.json"); + private static string APPSETTINGS_JSON { get; } = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "appsettings.json"); private const string LIBATION_FILES_KEY = "LibationFiles"; [Description("Location for storage of program-created files")] @@ -522,8 +522,6 @@ namespace LibationFileManager public void SetLibationFiles(string directory) { - libationFilesPathCache = null; - // ensure exists if (!File.Exists(APPSETTINGS_JSON)) { @@ -532,6 +530,8 @@ namespace LibationFileManager System.Threading.Thread.Sleep(100); } + libationFilesPathCache = null; + var startingContents = File.ReadAllText(APPSETTINGS_JSON); var jObj = JObject.Parse(startingContents); diff --git a/Source/LibationFileManager/LibationFileManager.csproj b/Source/LibationFileManager/LibationFileManager.csproj index 808f9029..cc29e0b3 100644 --- a/Source/LibationFileManager/LibationFileManager.csproj +++ b/Source/LibationFileManager/LibationFileManager.csproj @@ -1,7 +1,7 @@  - net6.0-windows + net6.0 diff --git a/Source/LibationFileManager/Templates.cs b/Source/LibationFileManager/Templates.cs index 69c992c9..2d2e5313 100644 --- a/Source/LibationFileManager/Templates.cs +++ b/Source/LibationFileManager/Templates.cs @@ -54,7 +54,7 @@ namespace LibationFileManager if (template is null) return new[] { ERROR_NULL_IS_INVALID }; - if (ReplacementCharacters.ContainsInvalid(template.Replace("<","").Replace(">",""))) + if (ReplacementCharacters.ContainsInvalidFilenameChar(template.Replace("<","").Replace(">",""))) return new[] { ERROR_INVALID_FILE_NAME_CHAR }; return Valid; @@ -201,7 +201,7 @@ namespace LibationFileManager return new[] { ERROR_FULL_PATH_IS_INVALID }; // must be relative. no colons. all other path chars are valid enough to pass this check and will be handled on final save. - if (ReplacementCharacters.ContainsInvalid(template.Replace("<", "").Replace(">", ""))) + if (ReplacementCharacters.ContainsInvalidPathChar(template.Replace("<", "").Replace(">", ""))) return new[] { ERROR_INVALID_FILE_NAME_CHAR }; return Valid; diff --git a/Source/LibationSearchEngine/LibationSearchEngine.csproj b/Source/LibationSearchEngine/LibationSearchEngine.csproj index 5e811683..5ad8dd69 100644 --- a/Source/LibationSearchEngine/LibationSearchEngine.csproj +++ b/Source/LibationSearchEngine/LibationSearchEngine.csproj @@ -1,7 +1,7 @@  - net6.0-windows + net6.0 diff --git a/Source/LibationWinForms/AvaloniaUI/App.axaml.cs b/Source/LibationWinForms/AvaloniaUI/App.axaml.cs deleted file mode 100644 index 1f0642bc..00000000 --- a/Source/LibationWinForms/AvaloniaUI/App.axaml.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Avalonia; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Markup.Xaml; -using Avalonia.Media; -using LibationFileManager; -using LibationWinForms.AvaloniaUI.Views; - -namespace LibationWinForms.AvaloniaUI -{ - public class App : Application - { - public static IBrush ProcessQueueBookFailedBrush { get; private set; } - public static IBrush ProcessQueueBookCompletedBrush { get; private set; } - public static IBrush ProcessQueueBookCancelledBrush { get; private set; } - public static IBrush ProcessQueueBookDefaultBrush { get; private set; } - public static IBrush SeriesEntryGridBackgroundBrush { get; private set; } - - public override void Initialize() - { - AvaloniaXamlLoader.Load(this); - } - - public override void OnFrameworkInitializationCompleted() - { - LoadStyles(); - - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - var mainWindow = new MainWindow(); - desktop.MainWindow = mainWindow; - mainWindow.RestoreSizeAndLocation(Configuration.Instance); - mainWindow.OnLoad(); - } - - base.OnFrameworkInitializationCompleted(); - } - - private void LoadStyles() - { - ProcessQueueBookFailedBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookFailedBrush"); - ProcessQueueBookCompletedBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookCompletedBrush"); - ProcessQueueBookCancelledBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookCancelledBrush"); - ProcessQueueBookDefaultBrush = AvaloniaUtils.GetBrushFromResources("ProcessQueueBookDefaultBrush"); - SeriesEntryGridBackgroundBrush = AvaloniaUtils.GetBrushFromResources("SeriesEntryGridBackgroundBrush"); - } - } -} \ No newline at end of file diff --git a/Source/LibationWinForms/AvaloniaUI/AvaloniaUtils.cs b/Source/LibationWinForms/AvaloniaUI/AvaloniaUtils.cs deleted file mode 100644 index 30ef7dd2..00000000 --- a/Source/LibationWinForms/AvaloniaUI/AvaloniaUtils.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Avalonia.Media; -using System; - -namespace LibationWinForms.AvaloniaUI -{ - internal static class AvaloniaUtils - { - public static IBrush GetBrushFromResources(string name) - => GetBrushFromResources(name, Brushes.Transparent); - public static IBrush GetBrushFromResources(string name, IBrush defaultBrush) - { - if (App.Current.Styles.TryGetResource(name, out var value) && value is IBrush brush) - return brush; - return defaultBrush; - } - } -} diff --git a/Source/LibationWinForms/AvaloniaUI/Controls/DirectorySelectControl.axaml b/Source/LibationWinForms/AvaloniaUI/Controls/DirectorySelectControl.axaml deleted file mode 100644 index cc93bafa..00000000 --- a/Source/LibationWinForms/AvaloniaUI/Controls/DirectorySelectControl.axaml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Source/LibationWinForms/AvaloniaUI/Controls/DirectorySelectControl.axaml.cs b/Source/LibationWinForms/AvaloniaUI/Controls/DirectorySelectControl.axaml.cs deleted file mode 100644 index ded5f227..00000000 --- a/Source/LibationWinForms/AvaloniaUI/Controls/DirectorySelectControl.axaml.cs +++ /dev/null @@ -1,146 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; -using Dinah.Core; -using LibationFileManager; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using ReactiveUI; -using Avalonia.Controls.Primitives; -using System.Collections; -using Avalonia.Data.Converters; -using System; -using System.Globalization; -using Avalonia.Data; - -namespace LibationWinForms.AvaloniaUI.Controls -{ - public class TextCaseConverter : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value is Configuration.KnownDirectories dir) - { - - } - return new BindingNotification(new InvalidCastException(), BindingErrorType.Error); - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } - } - public partial class DirectorySelectControl : TemplatedControl - { - private static readonly List defaultList = new List() - { - Configuration.KnownDirectories.WinTemp, - Configuration.KnownDirectories.UserProfile, - Configuration.KnownDirectories.AppDir, - Configuration.KnownDirectories.MyDocs, - Configuration.KnownDirectories.LibationFiles - }; - public static readonly StyledProperty SelectedirectoryProperty = - AvaloniaProperty.Register(nameof(Selectedirectory), defaultList[0]); - - public static readonly StyledProperty> KnownDirectoriesProperty = - AvaloniaProperty.Register>(nameof(KnownDirectories), defaultList); - - public static readonly StyledProperty SubdirectoryProperty = - AvaloniaProperty.Register(nameof(Subdirectory), "subdir"); - - DirectorySelectViewModel DirectorySelect { get; } = new(); - public DirectorySelectControl() - { - InitializeComponent(); - } - - protected override void OnInitialized() - { - DirectorySelect.Directories.Clear(); - - int insertIndex = 0; - foreach (var kd in KnownDirectories.Distinct()) - DirectorySelect.Directories.Insert(insertIndex++, new(this, kd)); - - DataContext = DirectorySelect; - base.OnInitialized(); - } - - public List KnownDirectories - { - get { return GetValue(KnownDirectoriesProperty); } - set - { - SetValue(KnownDirectoriesProperty, value); - //SetDirectoryItems(KnownDirectories); - } - } - - - public Configuration.KnownDirectories? Selectedirectory - { - get { return GetValue(SelectedirectoryProperty); } - set - { - SetValue(SelectedirectoryProperty, value); - - if (value is null or Configuration.KnownDirectories.None) - return; - - // set default - var item = DirectorySelect.Directories.SingleOrDefault(item => item.Value == value.Value); - if (item is null) - return; - - DirectorySelect.SelectedDirectory = item; - } - } - - - public string? Subdirectory - { - get { return GetValue(SubdirectoryProperty); } - set - { - SetValue(SubdirectoryProperty, value); - } - } - - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - } - } - - public class DirectorySelectViewModel : ViewModels.ViewModelBase - { - public class DirectoryComboBoxItem - { - private readonly DirectorySelectControl _parentControl; - public string Description { get; } - public Configuration.KnownDirectories Value { get; } - - public string FullPath => AddSubDirectoryToPath(Configuration.GetKnownDirectoryPath(Value)); - - /// Displaying relative paths is confusing. UI should display absolute equivalent - public string UiDisplayPath => Value == Configuration.KnownDirectories.AppDir ? AddSubDirectoryToPath(Configuration.AppDir_Absolute) : FullPath; - - public DirectoryComboBoxItem(DirectorySelectControl parentControl, Configuration.KnownDirectories knownDirectory) - { - _parentControl = parentControl; - Value = knownDirectory; - Description = Value.GetDescription(); - } - - internal string AddSubDirectoryToPath(string path) => string.IsNullOrWhiteSpace(_parentControl.Subdirectory) ? path : System.IO.Path.Combine(path, _parentControl.Subdirectory); - - public override string ToString() => Description; - } - public ObservableCollection Directories { get; } = new(new()); - private DirectoryComboBoxItem _selectedDirectory; - public DirectoryComboBoxItem SelectedDirectory { get => _selectedDirectory; set => this.RaiseAndSetIfChanged(ref _selectedDirectory, value); } - } -} diff --git a/Source/LibationWinForms/AvaloniaUI/Controls/WheelComboBox.axaml b/Source/LibationWinForms/AvaloniaUI/Controls/WheelComboBox.axaml deleted file mode 100644 index 635aa2a8..00000000 --- a/Source/LibationWinForms/AvaloniaUI/Controls/WheelComboBox.axaml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/Source/LibationWinForms/AvaloniaUI/FormSaveExtension2.cs b/Source/LibationWinForms/AvaloniaUI/FormSaveExtension2.cs deleted file mode 100644 index 1edcf955..00000000 --- a/Source/LibationWinForms/AvaloniaUI/FormSaveExtension2.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System; -using System.Linq; -using Avalonia; -using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; -using LibationFileManager; - -namespace LibationWinForms.AvaloniaUI -{ - public static class FormSaveExtension2 - { - static readonly WindowIcon WindowIcon; - static FormSaveExtension2() - { - if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - WindowIcon = desktop.MainWindow.Icon; - else - WindowIcon = null; - } - - public static void SetLibationIcon(this Window form) - { - form.Icon = WindowIcon; - } - - public static void RestoreSizeAndLocation(this Window form, Configuration config) - { - if (Design.IsDesignMode) return; - - FormSizeAndPosition savedState = config.GetNonString(form.GetType().Name); - - if (savedState is null) - return; - - // too small -- something must have gone wrong. use defaults - if (savedState.Width < form.MinWidth || savedState.Height < form.MinHeight) - { - savedState.Width = (int)form.Width; - savedState.Height = (int)form.Height; - } - - // Fit to the current screen size in case the screen resolution changed since the size was last persisted - if (savedState.Width > form.Screens.Primary.WorkingArea.Width) - savedState.Width = form.Screens.Primary.WorkingArea.Width; - if (savedState.Height > form.Screens.Primary.WorkingArea.Height) - savedState.Height = form.Screens.Primary.WorkingArea.Height; - - var rect = new PixelRect(savedState.X, savedState.Y, savedState.Width, savedState.Height); - - form.Width = savedState.Width; - form.Height = savedState.Height; - - // is proposed rect on a screen? - if (form.Screens.All.Any(screen => screen.WorkingArea.Contains(rect))) - { - form.WindowStartupLocation = WindowStartupLocation.Manual; - form.Position = new PixelPoint(savedState.X, savedState.Y); - } - else - { - form.WindowStartupLocation = WindowStartupLocation.CenterScreen; - } - - // FINAL: for Maximized: start normal state, set size and location, THEN set max state - form.WindowState = savedState.IsMaximized ? WindowState.Maximized : WindowState.Normal; - } - public static void SaveSizeAndLocation(this Window form, Configuration config) - { - if (Design.IsDesignMode) return; - - var saveState = new FormSizeAndPosition(); - - saveState.IsMaximized = form.WindowState == WindowState.Maximized; - - // restore normal state to get real window size. - if (form.WindowState != WindowState.Normal) - { - form.WindowState = WindowState.Normal; - } - - saveState.X = form.Position.X; - saveState.Y = form.Position.Y; - - saveState.Width = (int)form.Bounds.Size.Width; - saveState.Height = (int)form.Bounds.Size.Height; - - config.SetObject(form.GetType().Name, saveState); - } - - class FormSizeAndPosition - { - public int X; - public int Y; - public int Height; - public int Width; - public bool IsMaximized; - } - - - public static void HideMinMaxBtns(this Window form) - { - - if (Design.IsDesignMode) - return; -#if WINDOWS7_0_OR_GREATER - var handle = form.PlatformImpl.Handle.Handle; - var currentStyle = GetWindowLong(handle, GWL_STYLE); - - SetWindowLong(handle, GWL_STYLE, currentStyle & ~WS_MAXIMIZEBOX & ~WS_MINIMIZEBOX); -#endif - } - -#if WINDOWS7_0_OR_GREATER - const long WS_MINIMIZEBOX = 0x00020000L; - const long WS_MAXIMIZEBOX = 0x10000L; - const int GWL_STYLE = -16; - [System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "GetWindowLong")] - static extern long GetWindowLong(IntPtr hWnd, int nIndex); - [System.Runtime.InteropServices.DllImport("user32.dll")] - static extern int SetWindowLong(IntPtr hWnd, int nIndex, long dwNewLong); -#endif - } -} diff --git a/Source/LibationWinForms/AvaloniaUI/MessageBox.cs b/Source/LibationWinForms/AvaloniaUI/MessageBox.cs deleted file mode 100644 index 97020c89..00000000 --- a/Source/LibationWinForms/AvaloniaUI/MessageBox.cs +++ /dev/null @@ -1,327 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; -using DataLayer; -using LibationWinForms.AvaloniaUI.ViewModels.Dialogs; -using LibationWinForms.AvaloniaUI.Views.Dialogs; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace LibationWinForms.AvaloniaUI -{ - public enum DialogResult - { - None = 0, - OK = 1, - Cancel = 2, - Abort = 3, - Retry = 4, - Ignore = 5, - Yes = 6, - No = 7, - TryAgain = 10, - Continue = 11 - } - - - public enum MessageBoxIcon - { - None = 0, - Error = 16, - Hand = 16, - Stop = 16, - Question = 32, - Exclamation = 48, - Warning = 48, - Asterisk = 64, - Information = 64 - } - public enum MessageBoxButtons - { - OK, - OKCancel, - AbortRetryIgnore, - YesNoCancel, - YesNo, - RetryCancel, - CancelTryContinue - } - - public enum MessageBoxDefaultButton - { - Button1, - Button2 = 256, - Button3 = 512, - } - - public class MessageBox - { - - /// Displays a message box with the specified text, caption, buttons, icon, and default button. - /// The text to display in the message box. - /// The text to display in the title bar of the message box. - /// One of the values that specifies which buttons to display in the message box. - /// One of the values that specifies which icon to display in the message box. - /// One of the values that specifies the default button for the message box. - /// One of the values. - /// - /// is not a member of . - /// -or- - /// is not a member of . - /// -or- - /// is not a member of . - /// An attempt was made to display the in a process that is not running in User Interactive mode. This is specified by the property. - public static async Task Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton) - { - return await ShowCore(null, text, caption, buttons, icon, defaultButton); - } - - - /// Displays a message box with specified text, caption, buttons, and icon. - /// The text to display in the message box. - /// The text to display in the title bar of the message box. - /// One of the values that specifies which buttons to display in the message box. - /// One of the values that specifies which icon to display in the message box. - /// One of the values. - /// The parameter specified is not a member of . - /// -or- - /// The parameter specified is not a member of . - /// An attempt was made to display the in a process that is not running in User Interactive mode. This is specified by the property. - public static async Task Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon) - { - return await ShowCore(null, text, caption, buttons, icon, MessageBoxDefaultButton.Button1); - } - - - /// Displays a message box with specified text, caption, and buttons. - /// The text to display in the message box. - /// The text to display in the title bar of the message box. - /// One of the values that specifies which buttons to display in the message box. - /// One of the values. - /// The parameter specified is not a member of . - /// An attempt was made to display the in a process that is not running in User Interactive mode. This is specified by the property. - public static async Task Show(string text, string caption, MessageBoxButtons buttons) - { - return await ShowCore(null, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1); - } - - - /// Displays a message box with specified text and caption. - /// The text to display in the message box. - /// The text to display in the title bar of the message box. - /// One of the values. - public static async Task Show(string text, string caption) - { - return await ShowCore(null, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1); - } - - /// Displays a message box with specified text. - /// The text to display in the message box. - /// One of the values. - public static async Task Show(string text) - { - return await ShowCore(null, text, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1); - } - - - /// Displays a message box in front of the specified object and with the specified text, caption, buttons, icon, default button, and options. - /// An implementation of that will own the modal dialog box. - /// The text to display in the message box. - /// The text to display in the title bar of the message box. - /// One of the values that specifies which buttons to display in the message box. - /// One of the values that specifies which icon to display in the message box. - /// One of the values the specifies the default button for the message box. - /// One of the values that specifies which display and association options will be used for the message box. You may pass in 0 if you wish to use the defaults. - /// One of the values. - /// - /// is not a member of . - /// -or- - /// is not a member of . - /// -or- - /// is not a member of . - /// An attempt was made to display the in a process that is not running in User Interactive mode. This is specified by the property. - /// - /// -or- - /// specified an invalid combination of . - public static async Task Show(Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton) - { - return await ShowCore(owner, text, caption, buttons, icon, defaultButton); - } - - - /// Displays a message box in front of the specified object and with the specified text, caption, buttons, and icon. - /// An implementation of that will own the modal dialog box. - /// The text to display in the message box. - /// The text to display in the title bar of the message box. - /// One of the values that specifies which buttons to display in the message box. - /// One of the values that specifies which icon to display in the message box. - /// One of the values. - /// - /// is not a member of . - /// -or- - /// is not a member of . - /// An attempt was made to display the in a process that is not running in User Interactive mode. This is specified by the property. - public static async Task Show(Window owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon) - { - return await ShowCore(owner, text, caption, buttons, icon, MessageBoxDefaultButton.Button1); - } - - /// Displays a message box in front of the specified object and with the specified text, caption, and buttons. - /// An implementation of that will own the modal dialog box. - /// The text to display in the message box. - /// The text to display in the title bar of the message box. - /// One of the values that specifies which buttons to display in the message box. - /// One of the values. - /// - /// is not a member of . - /// An attempt was made to display the in a process that is not running in User Interactive mode. This is specified by the property. - public static async Task Show(Window owner, string text, string caption, MessageBoxButtons buttons) - { - return await ShowCore(owner, text, caption, buttons, MessageBoxIcon.None, MessageBoxDefaultButton.Button1); - } - - /// Displays a message box in front of the specified object and with the specified text and caption. - /// An implementation of that will own the modal dialog box. - /// The text to display in the message box. - /// The text to display in the title bar of the message box. - /// One of the values. - public static async Task Show(Window owner, string text, string caption) - { - return await ShowCore(owner, text, caption, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1); - } - - /// Displays a message box in front of the specified object and with the specified text. - /// An implementation of that will own the modal dialog box. - /// The text to display in the message box. - /// One of the values. - public static async Task Show(Window owner, string text) - { - return await ShowCore(owner, text, string.Empty, MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1); - } - - public static async Task ShowConfirmationDialog(Window owner, IEnumerable libraryBooks, string format, string title, MessageBoxDefaultButton defaultButton = MessageBoxDefaultButton.Button1) - { - if (libraryBooks is null || !libraryBooks.Any()) - return DialogResult.Cancel; - - var count = libraryBooks.Count(); - - string thisThese = count > 1 ? "these" : "this"; - string bookBooks = count > 1 ? "books" : "book"; - string titlesAgg = libraryBooks.AggregateTitles(); - - var message - = string.Format(format, $"{thisThese} {count} {bookBooks}") - + $"\r\n\r\n{titlesAgg}"; - - return await ShowCore(owner, - message, - title, - MessageBoxButtons.YesNo, - MessageBoxIcon.Question, - defaultButton); - } - - /// - /// Logs error. Displays a message box dialog with specified text and caption. - /// - /// Form calling this method. - /// The text to display in the message box. - /// The text to display in the title bar of the message box. - /// Exception to log. - public static async Task ShowAdminAlert(Window owner, string text, string caption, Exception exception) - { - // for development and debugging, show me what broke! - if (System.Diagnostics.Debugger.IsAttached) - throw exception; - - try - { - Serilog.Log.Logger.Error(exception, "Alert admin error: {@DebugText}", new { text, caption }); - } - catch { } - - var form = new MessageBoxAlertAdminDialog(text, caption, exception); - - await DisplayWindow(form, owner); - } - - - private static async Task ShowCore(Window owner, string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton) - { - if (Avalonia.Threading.Dispatcher.UIThread.CheckAccess()) - return await ShowCore2(owner, message, caption, buttons, icon, defaultButton); - else - return await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => ShowCore2(owner, message, caption, buttons, icon, defaultButton)); - } - private static async Task ShowCore2(Window owner, string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton) - { - var dialog = new MessageBoxWindow(); - - dialog.HideMinMaxBtns(); - - var vm = new MessageBoxViewModel(message, caption, buttons, icon, defaultButton); - dialog.DataContext = vm; - dialog.ControlToFocusOnShow = dialog.FindControl(defaultButton.ToString()); - dialog.CanResize = false; - dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner; - var tbx = dialog.FindControl("messageTextBlock"); - - tbx.MinWidth = vm.TextBlockMinWidth; - tbx.Text = message; - - var thisScreen = (owner ?? dialog).Screens.ScreenFromVisual(owner ?? dialog); - - var maxSize = new Size(0.20 * thisScreen.Bounds.Width, 0.9 * thisScreen.Bounds.Height - 55); - - var desiredMax = new Size(maxSize.Width, maxSize.Height); - - tbx.Measure(desiredMax); - - tbx.Height = tbx.DesiredSize.Height; - tbx.Width = tbx.DesiredSize.Width; - dialog.MinHeight = vm.FormHeightFromTboxHeight((int)tbx.DesiredSize.Height); - dialog.MinWidth = vm.FormWidthFromTboxWidth((int)tbx.DesiredSize.Width); - dialog.MaxHeight = dialog.MinHeight; - dialog.MaxWidth = dialog.MinWidth; - dialog.Height = dialog.MinHeight; - dialog.Width = dialog.MinWidth; - - return await DisplayWindow(dialog, owner); - } - private static async Task DisplayWindow(Window toDisplay, Window owner) - { - if (owner is null) - { - if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - return await toDisplay.ShowDialog(desktop.MainWindow); - } - else - { - var window = new Window - { - IsVisible = false, - Height = 1, - Width = 1, - SystemDecorations = SystemDecorations.None, - ShowInTaskbar = false - }; - - window.Show(); - var result = await toDisplay.ShowDialog(window); - window.Close(); - return result; - } - - } - else - { - return await toDisplay.ShowDialog(owner); - } - } - - } -} diff --git a/Source/LibationWinForms/AvaloniaUI/Views/Dialogs/SettingsDialog.axaml b/Source/LibationWinForms/AvaloniaUI/Views/Dialogs/SettingsDialog.axaml deleted file mode 100644 index 1acda062..00000000 --- a/Source/LibationWinForms/AvaloniaUI/Views/Dialogs/SettingsDialog.axaml +++ /dev/null @@ -1,406 +0,0 @@ - - - - -