diff --git a/StabilityMatrix/CheckpointBrowserPage.xaml b/StabilityMatrix/CheckpointBrowserPage.xaml index 1be92033..fd43c2f0 100644 --- a/StabilityMatrix/CheckpointBrowserPage.xaml +++ b/StabilityMatrix/CheckpointBrowserPage.xaml @@ -1,20 +1,19 @@  @@ -24,8 +23,7 @@ - - + @@ -45,7 +43,7 @@ FontSize="11" Foreground="{DynamicResource TextFillColorTertiaryBrush}" Margin="0,2,0,0" - Text="{Binding CivitModel.ModelVersions[0].Name}" + Text="{Binding CivitModel.ModelVersions[0].Name, FallbackValue=''}" VerticalAlignment="Center" /> { - new(null!, null!, null!, null!) + new(null!, null!, null!, null!, + fixedImage: new BitmapImage(new Uri("https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/01bb1e4b-f88b-441a-b2bf-c1ec0bd91727/width=450/00015-1007989405.jpeg"))) { - CivitModel = new() + CivitModel = new CivitModel { - Name = "bb95 Furry Mix", - ModelVersions = new() + Name = "Ghibli Background", + Type = CivitModelType.Checkpoint, + ModelVersions = new List { - new CivitModelVersion + new() { - Name = "v7.0", - Images = new() + Name = "v5.0", + Files = new List { - new CivitImage + new() { - Url = - "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/1547f350-461a-4cd0-a753-0544aa81e4fc/width=450/00000-4137473915.jpeg" + Name = "File", + SizeKb = 844110, + } + } + } + } + }, + }, + new(null!, null!, null!, null!, + fixedImage: new BitmapImage(new Uri("https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/fcb56b61-2cd6-40f4-a75d-e9448c0822d6/width=1152/29933-2136606019-masterpiece.jpeg"))) + { + CivitModel = new CivitModel + { + Name = "Indigo Furry mix", + Type = CivitModelType.Checkpoint, + Nsfw = false, + ModelVersions = new List + { + new() + { + Name = "v45_hybrid", + Files = new List + { + new() + { + Name = "Pruned Model fp16", + SizeKb = 1990000, } } } } }, - CardImage = new BitmapImage(new Uri( - "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/1547f350-461a-4cd0-a753-0544aa81e4fc/width=450/00000-4137473915.jpeg")) } }; + ModelCardsView = CollectionViewSource.GetDefaultView(ModelCards); } } diff --git a/StabilityMatrix/Helper/Expressions.cs b/StabilityMatrix/Helper/Expressions.cs new file mode 100644 index 00000000..1d433b2a --- /dev/null +++ b/StabilityMatrix/Helper/Expressions.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq.Expressions; +using System.Reflection; + +namespace StabilityMatrix.Helper; + +public static class Expressions +{ + public static (string propertyName, Expression> assigner) + GetAssigner(Expression> propertyAccessor) + { + if (propertyAccessor.Body is not MemberExpression memberExpression) + { + throw new ArgumentException( + $"Expression must be a member expression, not {propertyAccessor.Body.NodeType}"); + } + + var propertyInfo = memberExpression.Member as PropertyInfo; + if (propertyInfo == null) + { + throw new ArgumentException( + $"Expression member must be a property, not {memberExpression.Member.MemberType}"); + } + + var propertyName = propertyInfo.Name; + var typeParam = Expression.Parameter(typeof(T)); + var valueParam = Expression.Parameter(typeof(TValue)); + var expr = Expression.Lambda>( + Expression.Assign( + Expression.MakeMemberAccess(typeParam, propertyInfo), + valueParam), typeParam, valueParam); + return (propertyName, expr); + } +} diff --git a/StabilityMatrix/Helper/ISettingsManager.cs b/StabilityMatrix/Helper/ISettingsManager.cs index 638558a9..1e625b85 100644 --- a/StabilityMatrix/Helper/ISettingsManager.cs +++ b/StabilityMatrix/Helper/ISettingsManager.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Linq.Expressions; using StabilityMatrix.Models; using StabilityMatrix.Models.Settings; using Wpf.Ui.Controls.Window; @@ -11,28 +13,56 @@ public interface ISettingsManager Settings Settings { get; } // Events - event EventHandler? ModelBrowserNsfwEnabledChanged; event EventHandler? LibraryDirChanged; // Library settings bool IsPortableMode { get; } string LibraryDir { get; } + bool IsLibraryDirSet { get; } bool TryFindLibrary(); // Dynamic paths from library string DatabasePath { get; } string ModelsDirectory { get; } + + /// + /// Return a SettingsTransaction that can be used to modify Settings + /// Saves on Dispose. + /// + public SettingsTransaction BeginTransaction(); + + /// + /// Execute a function that modifies Settings + /// Commits changes after the function returns. + /// + /// Function accepting Settings to modify + public void Transaction(Action func); + + /// + /// Modify a settings property by expression and commit changes. + /// This will notify listeners of SettingsPropertyChanged. + /// + public void Transaction(Expression> expression, TValue value); + + /// + /// Register a source observable object and property to be relayed to Settings + /// + public void RelayPropertyFor( + T source, + Expression> sourceProperty, + Expression> settingsProperty) where T : INotifyPropertyChanged; + + /// + /// Register an Action to be called on change of the settings property. + /// + public void RegisterPropertyChangedHandler( + Expression> settingsProperty, + Action onPropertyChanged); // Migration IEnumerable GetOldInstalledPackages(); - - void SetTheme(string theme); - void AddInstalledPackage(InstalledPackage p); - void RemoveInstalledPackage(InstalledPackage p); - void SetActiveInstalledPackage(InstalledPackage? p); - void SetNavExpanded(bool navExpanded); + void AddPathExtension(string pathExtension); - string GetPathExtensionsAsString(); /// /// Insert path extensions to the front of the PATH environment variable @@ -43,14 +73,8 @@ public interface ISettingsManager void SetLastUpdateCheck(InstalledPackage package); List GetLaunchArgs(Guid packageId); void SaveLaunchArgs(Guid packageId, List launchArgs); - void SetWindowBackdropType(WindowBackdropType backdropType); - void SetHasSeenWelcomeNotification(bool hasSeenWelcomeNotification); string? GetActivePackageHost(); string? GetActivePackagePort(); - void SetWebApiHost(string? host); - void SetWebApiPort(string? port); - void SetFirstLaunchSetupComplete(bool firstLaunchSetupCompleted); - void SetModelBrowserNsfwEnabled(bool value); void SetSharedFolderCategoryVisible(SharedFolderType type, bool visible); bool IsSharedFolderCategoryVisible(SharedFolderType type); bool IsEulaAccepted(); @@ -66,10 +90,5 @@ public interface ISettingsManager /// Creates the ./Data directory and the `.sm-portable` marker file /// void SetPortableMode(); - void SetPlacement(string placementStr); - void SetSearchOptions(ModelSearchOptions options); Guid GetOldActivePackageId(); - void SetActiveInstalledPackage(Guid guid); - void SetIsImportAsConnected(bool value); - void SetKeepFolderLinksOnShutdown(bool value); } diff --git a/StabilityMatrix/Helper/SettingsManager.cs b/StabilityMatrix/Helper/SettingsManager.cs index 6d9c5715..6cb349d9 100644 --- a/StabilityMatrix/Helper/SettingsManager.cs +++ b/StabilityMatrix/Helper/SettingsManager.cs @@ -1,10 +1,15 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Linq; +using System.Linq.Expressions; +using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; +using System.Threading.Tasks; +using AsyncAwaitBestPractices; using NLog; using Refit; using StabilityMatrix.Models; @@ -44,6 +49,7 @@ public class SettingsManager : ISettingsManager LibraryDirChanged?.Invoke(this, value); } } + public bool IsLibraryDirSet => !string.IsNullOrWhiteSpace(libraryDir); // Dynamic paths from library public string DatabasePath => Path.Combine(LibraryDir, "StabilityMatrix.db"); @@ -51,11 +57,102 @@ public class SettingsManager : ISettingsManager public string ModelsDirectory => Path.Combine(LibraryDir, "Models"); public Settings Settings { get; private set; } = new(); - - // Events - public event EventHandler? ModelBrowserNsfwEnabledChanged; + public event EventHandler? LibraryDirChanged; + public event EventHandler? SettingsPropertyChanged; + + /// + public SettingsTransaction BeginTransaction() + { + return new SettingsTransaction(this, SaveSettingsAsync); + } + + /// + public void Transaction(Action func) + { + using var transaction = BeginTransaction(); + func(transaction.Settings); + transaction.Dispose(); + } + + /// + public void Transaction(Expression> expression, TValue value) + { + if (expression.Body is not MemberExpression memberExpression) + { + throw new ArgumentException( + $"Expression must be a member expression, not {expression.Body.NodeType}"); + } + + var propertyInfo = memberExpression.Member as PropertyInfo; + if (propertyInfo == null) + { + throw new ArgumentException( + $"Expression member must be a property, not {memberExpression.Member.MemberType}"); + } + + var name = propertyInfo.Name; + + // Set value + using var transaction = BeginTransaction(); + propertyInfo.SetValue(transaction.Settings, value); + + // Invoke property changed event + SettingsPropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + } + /// + public void RelayPropertyFor( + T source, + Expression> sourceProperty, + Expression> settingsProperty) where T : INotifyPropertyChanged + { + var sourceGetter = sourceProperty.Compile(); + var (propertyName, assigner) = Expressions.GetAssigner(sourceProperty); + var sourceSetter = assigner.Compile(); + + var settingsGetter = settingsProperty.Compile(); + var (_, settingsAssigner) = Expressions.GetAssigner(settingsProperty); + var settingsSetter = settingsAssigner.Compile(); + + // Update source when settings change + SettingsPropertyChanged += (_, args) => + { + if (args.PropertyName != propertyName) return; + + sourceSetter(source, settingsGetter(Settings)); + }; + + // Set and Save settings when source changes + source.PropertyChanged += (_, args) => + { + if (args.PropertyName != propertyName) return; + + settingsSetter(Settings, sourceGetter(source)); + SaveSettingsAsync().SafeFireAndForget(); + + // Invoke property changed event + SettingsPropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + }; + } + + /// + public void RegisterPropertyChangedHandler( + Expression> settingsProperty, + Action onPropertyChanged) + { + var settingsGetter = settingsProperty.Compile(); + var (propertyName, _) = Expressions.GetAssigner(settingsProperty); + + // Invoke handler when settings change + SettingsPropertyChanged += (_, args) => + { + if (args.PropertyName != propertyName) return; + + onPropertyChanged(settingsGetter(Settings)); + }; + } + /// /// Attempts to locate and set the library path /// Return true if found, false otherwise @@ -190,54 +287,6 @@ public class SettingsManager : ISettingsManager return oldSettings.ActiveInstalledPackage ?? default; } - - public void SetTheme(string theme) - { - Settings.Theme = theme; - SaveSettings(); - } - - public void AddInstalledPackage(InstalledPackage p) - { - Settings.InstalledPackages.Add(p); - SaveSettings(); - } - - public void RemoveInstalledPackage(InstalledPackage p) - { - Settings.InstalledPackages.Remove(p); - SetActiveInstalledPackage(Settings.InstalledPackages.Any() ? Settings.InstalledPackages.First() : null); - } - - public void SetActiveInstalledPackage(InstalledPackage? p) - { - Settings.ActiveInstalledPackage = p?.Id; - SaveSettings(); - } - - public void SetActiveInstalledPackage(Guid guid) - { - Settings.ActiveInstalledPackage = guid; - SaveSettings(); - } - - public void SetNavExpanded(bool navExpanded) - { - Settings.IsNavExpanded = navExpanded; - SaveSettings(); - } - - public void SetIsImportAsConnected(bool value) - { - Settings.IsImportAsConnected = value; - SaveSettings(); - } - - public void SetKeepFolderLinksOnShutdown(bool value) - { - Settings.KeepFolderLinksOnShutdown = value; - SaveSettings(); - } public void AddPathExtension(string pathExtension) { @@ -250,7 +299,7 @@ public class SettingsManager : ISettingsManager { return string.Join(";", Settings.PathExtensions ?? new List()); } - + /// /// Insert path extensions to the front of the PATH environment variable /// @@ -308,18 +357,6 @@ public class SettingsManager : ISettingsManager packageData.LaunchArgs = toSave; SaveSettings(); } - - public void SetWindowBackdropType(WindowBackdropType backdropType) - { - Settings.WindowBackdropType = backdropType; - SaveSettings(); - } - - public void SetHasSeenWelcomeNotification(bool hasSeenWelcomeNotification) - { - Settings.HasSeenWelcomeNotification = hasSeenWelcomeNotification; - SaveSettings(); - } public string? GetActivePackageHost() { @@ -344,31 +381,6 @@ public class SettingsManager : ISettingsManager } return portOption?.DefaultValue as string; } - - public void SetWebApiHost(string? host) - { - Settings.WebApiHost = host; - SaveSettings(); - } - - public void SetWebApiPort(string? port) - { - Settings.WebApiPort = port; - SaveSettings(); - } - - public void SetFirstLaunchSetupComplete(bool value) - { - Settings.FirstLaunchSetupComplete = value; - SaveSettings(); - } - - public void SetModelBrowserNsfwEnabled(bool value) - { - Settings.ModelBrowserNsfwEnabled = value; - ModelBrowserNsfwEnabledChanged?.Invoke(this, value); - SaveSettings(); - } public void SetSharedFolderCategoryVisible(SharedFolderType type, bool visible) { @@ -413,18 +425,6 @@ public class SettingsManager : ISettingsManager File.WriteAllText(GlobalSettingsPath, json); } - public void SetPlacement(string placementStr) - { - Settings.Placement = placementStr; - SaveSettings(); - } - - public void SetSearchOptions(ModelSearchOptions options) - { - Settings.ModelSearchOptions = options; - SaveSettings(); - } - /// /// Loads settings from the settings file /// If the settings file does not exist, it will be created with default values @@ -480,4 +480,9 @@ public class SettingsManager : ISettingsManager FileLock.ExitWriteLock(); } } + + private Task SaveSettingsAsync() + { + return Task.Run(SaveSettings); + } } diff --git a/StabilityMatrix/MainWindow.xaml.cs b/StabilityMatrix/MainWindow.xaml.cs index 5d9b37db..c5c8f793 100644 --- a/StabilityMatrix/MainWindow.xaml.cs +++ b/StabilityMatrix/MainWindow.xaml.cs @@ -61,17 +61,17 @@ namespace StabilityMatrix private void RootNavigation_OnPaneOpened(NavigationView sender, RoutedEventArgs args) { - if (settingsManager.TryFindLibrary()) + if (settingsManager.IsLibraryDirSet) { - settingsManager.SetNavExpanded(true); + settingsManager.Transaction(s => s.IsNavExpanded = true); } } private void RootNavigation_OnPaneClosed(NavigationView sender, RoutedEventArgs args) { - if (settingsManager.TryFindLibrary()) + if (settingsManager.IsLibraryDirSet) { - settingsManager.SetNavExpanded(false); + settingsManager.Transaction(s => s.IsNavExpanded = false); } } @@ -97,7 +97,7 @@ namespace StabilityMatrix var interopHelper = new WindowInteropHelper(this); var placement = ScreenExtensions.GetPlacement(interopHelper.Handle); - settingsManager.SetPlacement(placement.ToString()); + settingsManager.Transaction(s => s.Placement = placement.ToString()); }); } @@ -106,9 +106,9 @@ namespace StabilityMatrix var interopHelper = new WindowInteropHelper(this); var placement = ScreenExtensions.GetPlacement(interopHelper.Handle); - if (settingsManager.TryFindLibrary()) + if (settingsManager.IsLibraryDirSet) { - settingsManager.SetPlacement(placement.ToString()); + settingsManager.Transaction(s => s.Placement = placement.ToString()); } } } diff --git a/StabilityMatrix/Models/Settings/Settings.cs b/StabilityMatrix/Models/Settings/Settings.cs index fdc39be3..69060db9 100644 --- a/StabilityMatrix/Models/Settings/Settings.cs +++ b/StabilityMatrix/Models/Settings/Settings.cs @@ -39,4 +39,31 @@ public class Settings { return InstalledPackages.FirstOrDefault(x => x.Id == ActiveInstalledPackage); } + + public void RemoveInstalledPackageAndUpdateActive(InstalledPackage package) + { + RemoveInstalledPackageAndUpdateActive(package.Id); + } + + public void RemoveInstalledPackageAndUpdateActive(Guid id) + { + InstalledPackages.RemoveAll(x => x.Id == id); + UpdateActiveInstalledPackage(); + } + + // Update ActiveInstalledPackage if not valid + // uses first package or null if no packages + private void UpdateActiveInstalledPackage() + { + // Empty packages - set to null + if (InstalledPackages.Count == 0) + { + ActiveInstalledPackage = null; + } + // Active package is not in package - set to first package + else if (InstalledPackages.All(x => x.Id != ActiveInstalledPackage)) + { + ActiveInstalledPackage = InstalledPackages[0].Id; + } + } } diff --git a/StabilityMatrix/Models/Settings/SettingsTransaction.cs b/StabilityMatrix/Models/Settings/SettingsTransaction.cs new file mode 100644 index 00000000..5936bc3d --- /dev/null +++ b/StabilityMatrix/Models/Settings/SettingsTransaction.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading.Tasks; +using AsyncAwaitBestPractices; +using StabilityMatrix.Helper; + +namespace StabilityMatrix.Models.Settings; + +/// +/// Transaction object which saves settings manager changes when disposed. +/// +public class SettingsTransaction : IDisposable, IAsyncDisposable +{ + private readonly ISettingsManager settingsManager; + private readonly Func onCommit; + + public Settings Settings => settingsManager.Settings; + + public SettingsTransaction(ISettingsManager settingsManager, Func onCommit) + { + this.settingsManager = settingsManager; + this.onCommit = onCommit; + } + + public void Dispose() + { + onCommit().SafeFireAndForget(); + GC.SuppressFinalize(this); + } + + public async ValueTask DisposeAsync() + { + await onCommit(); + GC.SuppressFinalize(this); + } +} diff --git a/StabilityMatrix/Services/NotificationBarService.cs b/StabilityMatrix/Services/NotificationBarService.cs index 40107ac5..ce803d51 100644 --- a/StabilityMatrix/Services/NotificationBarService.cs +++ b/StabilityMatrix/Services/NotificationBarService.cs @@ -27,7 +27,7 @@ public class NotificationBarService : SnackbarService, INotificationBarService "Welcome to StabilityMatrix!", "You can join our Discord server for support and feedback.", linkIcon, ControlAppearance.Info); snackbar.SafeFireAndForget(); - - settingsManager.SetHasSeenWelcomeNotification(true); + + settingsManager.Transaction(s => s.HasSeenWelcomeNotification = true); } } diff --git a/StabilityMatrix/ViewModels/CheckpointBrowserCardViewModel.cs b/StabilityMatrix/ViewModels/CheckpointBrowserCardViewModel.cs index 5fd8c5f2..5a5768aa 100644 --- a/StabilityMatrix/ViewModels/CheckpointBrowserCardViewModel.cs +++ b/StabilityMatrix/ViewModels/CheckpointBrowserCardViewModel.cs @@ -33,15 +33,29 @@ public partial class CheckpointBrowserCardViewModel : ProgressViewModel public override Visibility ProgressVisibility => Value > 0 ? Visibility.Visible : Visibility.Collapsed; public override Visibility TextVisibility => Value > 0 ? Visibility.Visible : Visibility.Collapsed; - public CheckpointBrowserCardViewModel(CivitModel civitModel, IDownloadService downloadService, ISnackbarService snackbarService, ISettingsManager settingsManager) + public CheckpointBrowserCardViewModel( + CivitModel civitModel, + IDownloadService downloadService, + ISnackbarService snackbarService, + ISettingsManager settingsManager, + BitmapImage? fixedImage = null) { this.downloadService = downloadService; this.snackbarService = snackbarService; this.settingsManager = settingsManager; CivitModel = civitModel; + + if (fixedImage != null) + { + CardImage = fixedImage; + return; + } CardImage = GetImage(); - this.settingsManager.ModelBrowserNsfwEnabledChanged += OnNsfwModeChanged; + // Update image when nsfw setting changes + settingsManager.RegisterPropertyChangedHandler( + s => s.ModelBrowserNsfwEnabled, + _ => CardImage = GetImage()); } // Choose and load image based on nsfw setting diff --git a/StabilityMatrix/ViewModels/CheckpointBrowserViewModel.cs b/StabilityMatrix/ViewModels/CheckpointBrowserViewModel.cs index 9af2acf9..2f0edeff 100644 --- a/StabilityMatrix/ViewModels/CheckpointBrowserViewModel.cs +++ b/StabilityMatrix/ViewModels/CheckpointBrowserViewModel.cs @@ -35,12 +35,12 @@ public partial class CheckpointBrowserViewModel : ObservableObject [ObservableProperty] private ObservableCollection? modelCards; [ObservableProperty] private ICollectionView? modelCardsView; - [ObservableProperty] private string? searchQuery; + [ObservableProperty] private string searchQuery = string.Empty; [ObservableProperty] private bool showNsfw; [ObservableProperty] private bool showMainLoadingSpinner; - [ObservableProperty] private CivitPeriod selectedPeriod; - [ObservableProperty] private CivitSortMode sortMode; - [ObservableProperty] private CivitModelType selectedModelType; + [ObservableProperty] private CivitPeriod selectedPeriod = CivitPeriod.Month; + [ObservableProperty] private CivitSortMode sortMode = CivitSortMode.HighestRated; + [ObservableProperty] private CivitModelType selectedModelType = CivitModelType.Checkpoint; [ObservableProperty] private int currentPageNumber; [ObservableProperty] private int totalPages; [ObservableProperty] private bool hasSearched; @@ -70,17 +70,23 @@ public partial class CheckpointBrowserViewModel : ObservableObject this.snackbarService = snackbarService; this.settingsManager = settingsManager; this.liteDbContext = liteDbContext; + + CurrentPageNumber = 1; + CanGoToNextPage = true; + } + public void OnLoaded() + { var searchOptions = settingsManager.Settings.ModelSearchOptions; - - ShowNsfw = settingsManager.Settings.ModelBrowserNsfwEnabled; + SelectedPeriod = searchOptions?.SelectedPeriod ?? CivitPeriod.Month; SortMode = searchOptions?.SortMode ?? CivitSortMode.HighestRated; SelectedModelType = searchOptions?.SelectedModelType ?? CivitModelType.Checkpoint; - HasSearched = false; - CurrentPageNumber = 1; - CanGoToPreviousPage = false; - CanGoToNextPage = true; + + ShowNsfw = settingsManager.Settings.ModelBrowserNsfwEnabled; + + settingsManager.RelayPropertyFor(this, model => model.ShowNsfw, + settings => settings.ModelBrowserNsfwEnabled); } /// @@ -209,7 +215,6 @@ public partial class CheckpointBrowserViewModel : ObservableObject [RelayCommand] private async Task SearchModels() { - if (string.IsNullOrWhiteSpace(SearchQuery)) return; var timer = Stopwatch.StartNew(); if (SearchQuery != previousSearchQuery) @@ -314,7 +319,7 @@ public partial class CheckpointBrowserViewModel : ObservableObject partial void OnShowNsfwChanged(bool value) { - settingsManager.SetModelBrowserNsfwEnabled(value); + settingsManager.Transaction(s => s.ModelBrowserNsfwEnabled = value); ModelCardsView?.Refresh(); if (!HasSearched) @@ -326,22 +331,22 @@ public partial class CheckpointBrowserViewModel : ObservableObject partial void OnSelectedPeriodChanged(CivitPeriod oldValue, CivitPeriod newValue) { TrySearchAgain().SafeFireAndForget(); - settingsManager.SetSearchOptions(new ModelSearchOptions(newValue, SortMode, - SelectedModelType)); + settingsManager.Transaction(s => s.ModelSearchOptions = new ModelSearchOptions( + newValue, SortMode, SelectedModelType)); } partial void OnSortModeChanged(CivitSortMode oldValue, CivitSortMode newValue) { TrySearchAgain().SafeFireAndForget(); - settingsManager.SetSearchOptions(new ModelSearchOptions(SelectedPeriod, newValue, - SelectedModelType)); + settingsManager.Transaction(s => s.ModelSearchOptions = new ModelSearchOptions( + SelectedPeriod, newValue, SelectedModelType)); } partial void OnSelectedModelTypeChanged(CivitModelType oldValue, CivitModelType newValue) { TrySearchAgain().SafeFireAndForget(); - settingsManager.SetSearchOptions(new ModelSearchOptions(SelectedPeriod, SortMode, - newValue)); + settingsManager.Transaction(s => s.ModelSearchOptions = new ModelSearchOptions( + SelectedPeriod, SortMode, newValue)); } private async Task TrySearchAgain(bool shouldUpdatePageNumber = true) diff --git a/StabilityMatrix/ViewModels/CheckpointManagerViewModel.cs b/StabilityMatrix/ViewModels/CheckpointManagerViewModel.cs index d28d8f99..b5639dab 100644 --- a/StabilityMatrix/ViewModels/CheckpointManagerViewModel.cs +++ b/StabilityMatrix/ViewModels/CheckpointManagerViewModel.cs @@ -25,9 +25,10 @@ public partial class CheckpointManagerViewModel : ObservableObject partial void OnIsImportAsConnectedChanged(bool value) { - if (value != settingsManager.Settings.IsImportAsConnected) + if (settingsManager.IsLibraryDirSet && + value != settingsManager.Settings.IsImportAsConnected) { - settingsManager.SetIsImportAsConnected(value); + settingsManager.Transaction(s => s.IsImportAsConnected = value); } } diff --git a/StabilityMatrix/ViewModels/DataDirectoryMigrationViewModel.cs b/StabilityMatrix/ViewModels/DataDirectoryMigrationViewModel.cs index cc033e7b..c8123203 100644 --- a/StabilityMatrix/ViewModels/DataDirectoryMigrationViewModel.cs +++ b/StabilityMatrix/ViewModels/DataDirectoryMigrationViewModel.cs @@ -155,9 +155,12 @@ public partial class DataDirectoryMigrationViewModel : ObservableObject var oldPackages = settingsManager.GetOldInstalledPackages().ToArray(); MigrateProgressText = "Migrating Packages..."; + + await using var st = settingsManager.BeginTransaction(); + foreach (var package in oldPackages) { - settingsManager.RemoveInstalledPackage(package); + st.Settings.RemoveInstalledPackageAndUpdateActive(package); #pragma warning disable CS0618 Logger.Info($"Migrating package {MigrateProgressCount} of {oldPackages.Length} at path {package.Path}"); @@ -165,16 +168,19 @@ public partial class DataDirectoryMigrationViewModel : ObservableObject await package.MigratePath(); MigrateProgressCount++; - settingsManager.AddInstalledPackage(package); - settingsManager.SetActiveInstalledPackage(package); + st.Settings.InstalledPackages.Add(package); + st.Settings.ActiveInstalledPackage = package.Id; if (oldLibraryPath != libraryPath) { // setup model links again - var basePackage = packageFactory.FindPackageByName(package.PackageName); - if (basePackage != default) + if (!string.IsNullOrWhiteSpace(package.PackageName)) { - sharedFolders.SetupLinksForPackage(basePackage, package.FullPath!); + var basePackage = packageFactory.FindPackageByName(package.PackageName); + if (basePackage != default) + { + sharedFolders.SetupLinksForPackage(basePackage, package.FullPath!); + } } } diff --git a/StabilityMatrix/ViewModels/InstallerViewModel.cs b/StabilityMatrix/ViewModels/InstallerViewModel.cs index 7bf9e219..fe14b650 100644 --- a/StabilityMatrix/ViewModels/InstallerViewModel.cs +++ b/StabilityMatrix/ViewModels/InstallerViewModel.cs @@ -339,9 +339,10 @@ public partial class InstallerViewModel : ObservableObject LaunchCommand = SelectedPackage.LaunchCommand, LastUpdateCheck = DateTimeOffset.Now }; - settingsManager.AddInstalledPackage(package); - settingsManager.SetActiveInstalledPackage(package); - + await using var st = settingsManager.BeginTransaction(); + st.Settings.InstalledPackages.Add(package); + st.Settings.ActiveInstalledPackage = package.Id; + ProgressValue = 0; } diff --git a/StabilityMatrix/ViewModels/LaunchViewModel.cs b/StabilityMatrix/ViewModels/LaunchViewModel.cs index a2848dd5..227bb51d 100644 --- a/StabilityMatrix/ViewModels/LaunchViewModel.cs +++ b/StabilityMatrix/ViewModels/LaunchViewModel.cs @@ -33,7 +33,8 @@ public partial class LaunchViewModel : ObservableObject private readonly ISharedFolders sharedFolders; private BasePackage? runningPackage; - private bool clearingPackages = false; + private bool clearingPackages; + private string webUiUrl = string.Empty; [ObservableProperty] private string consoleInput = ""; [ObservableProperty] private Visibility launchButtonVisibility; @@ -41,29 +42,10 @@ public partial class LaunchViewModel : ObservableObject [ObservableProperty] private bool isLaunchTeachingTipsOpen = false; [ObservableProperty] private bool showWebUiButton; [ObservableProperty] private ObservableCollection? consoleHistory; - - private string webUiUrl = string.Empty; - private InstalledPackage? selectedPackage; - - public InstalledPackage? SelectedPackage - { - get => selectedPackage; - set - { - if (value == selectedPackage) return; - selectedPackage = value; - - if (!clearingPackages) - { - settingsManager.SetActiveInstalledPackage(value); - } - - OnPropertyChanged(); - } - } - + + [ObservableProperty] private InstalledPackage? selectedPackage; [ObservableProperty] private ObservableCollection installedPackages = new(); - + public LaunchViewModel(ISettingsManager settingsManager, IPackageFactory packageFactory, IContentDialogService contentDialogService, @@ -90,6 +72,12 @@ public partial class LaunchViewModel : ObservableObject ToastNotificationManagerCompat.OnActivated += ToastNotificationManagerCompatOnOnActivated; } + + partial void OnSelectedPackageChanged(InstalledPackage? value) + { + if (clearingPackages) return; + settingsManager.Transaction(s => s.ActiveInstalledPackage = value?.Id); + } private void OnTeachingTooltipNeeded(object? sender, EventArgs e) { diff --git a/StabilityMatrix/ViewModels/OneClickInstallViewModel.cs b/StabilityMatrix/ViewModels/OneClickInstallViewModel.cs index e7175070..41bdfad2 100644 --- a/StabilityMatrix/ViewModels/OneClickInstallViewModel.cs +++ b/StabilityMatrix/ViewModels/OneClickInstallViewModel.cs @@ -137,8 +137,9 @@ public partial class OneClickInstallViewModel : ObservableObject LaunchCommand = a1111.LaunchCommand, LastUpdateCheck = DateTimeOffset.Now }; - settingsManager.AddInstalledPackage(package); - settingsManager.SetActiveInstalledPackage(package); + await using var st = settingsManager.BeginTransaction(); + st.Settings.InstalledPackages.Add(package); + st.Settings.ActiveInstalledPackage = package.Id; EventManager.Instance.OnInstalledPackagesChanged(); HeaderText = "Installation complete!"; diff --git a/StabilityMatrix/ViewModels/PackageManagerViewModel.cs b/StabilityMatrix/ViewModels/PackageManagerViewModel.cs index 4c0673a4..00e73544 100644 --- a/StabilityMatrix/ViewModels/PackageManagerViewModel.cs +++ b/StabilityMatrix/ViewModels/PackageManagerViewModel.cs @@ -185,7 +185,11 @@ public partial class PackageManagerViewModel : ObservableObject { snackbarService.ShowSnackbarAsync($"Package {SelectedPackage.DisplayName} uninstalled", "Success", ControlAppearance.Success).SafeFireAndForget(); - settingsManager.RemoveInstalledPackage(SelectedPackage); + + settingsManager.Transaction(settings => + { + settings.RemoveInstalledPackageAndUpdateActive(SelectedPackage); + }); } await OnLoaded(); IsUninstalling = false; diff --git a/StabilityMatrix/ViewModels/SettingsViewModel.cs b/StabilityMatrix/ViewModels/SettingsViewModel.cs index 7951a3b5..421c682a 100644 --- a/StabilityMatrix/ViewModels/SettingsViewModel.cs +++ b/StabilityMatrix/ViewModels/SettingsViewModel.cs @@ -74,13 +74,13 @@ public partial class SettingsViewModel : ObservableObject partial void OnWebApiHostChanged(string? value) { - settingsManager.SetWebApiHost(value); + settingsManager.Transaction(s => s.WebApiHost = value); a3WebApiManager.ResetClient(); } partial void OnWebApiPortChanged(string? value) { - settingsManager.SetWebApiPort(value); + settingsManager.Transaction(s => s.WebApiPort = value); a3WebApiManager.ResetClient(); } @@ -90,7 +90,7 @@ public partial class SettingsViewModel : ObservableObject { if (value != settingsManager.Settings.KeepFolderLinksOnShutdown) { - settingsManager.SetKeepFolderLinksOnShutdown(value); + settingsManager.Transaction(s => s.KeepFolderLinksOnShutdown = value); } } @@ -142,13 +142,14 @@ public partial class SettingsViewModel : ObservableObject partial void OnSelectedThemeChanged(string value) { - settingsManager.SetTheme(value); + using var st = settingsManager.BeginTransaction(); + st.Settings.Theme = value; ApplyTheme(value); } partial void OnWindowBackdropTypeChanged(WindowBackdropType oldValue, WindowBackdropType newValue) { - settingsManager.SetWindowBackdropType(newValue); + settingsManager.Transaction(s => s.WindowBackdropType = newValue); if (Application.Current.MainWindow != null) { WindowBackdrop.ApplyBackdrop(Application.Current.MainWindow, newValue); @@ -221,13 +222,15 @@ public partial class SettingsViewModel : ObservableObject { Id = Guid.NewGuid(), DisplayName = Path.GetFileName(path), +#pragma warning disable CS0618 // Type or member is obsolete Path = path, +#pragma warning restore CS0618 // Type or member is obsolete PackageName = "dank-diffusion", PackageVersion = "v1.0.0", }; // Add package to settings - settingsManager.AddInstalledPackage(package); + settingsManager.Transaction(s => s.InstalledPackages.Add(package)); }); // Debug card commands