|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.IO;
|
|
|
|
using System.Reflection;
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
using Avalonia;
|
|
|
|
using Avalonia.Controls.Notifications;
|
|
|
|
using Avalonia.Styling;
|
|
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
|
|
using CommunityToolkit.Mvvm.Input;
|
|
|
|
using FluentAvalonia.UI.Controls;
|
|
|
|
using NLog;
|
|
|
|
using StabilityMatrix.Avalonia.Services;
|
|
|
|
using StabilityMatrix.Avalonia.Views;
|
|
|
|
using StabilityMatrix.Core.Attributes;
|
|
|
|
using StabilityMatrix.Core.Extensions;
|
|
|
|
using StabilityMatrix.Core.Helper;
|
|
|
|
using StabilityMatrix.Core.Python;
|
|
|
|
using StabilityMatrix.Core.Services;
|
|
|
|
using Symbol = FluentIcons.Common.Symbol;
|
|
|
|
using SymbolIconSource = FluentIcons.FluentAvalonia.SymbolIconSource;
|
|
|
|
|
|
|
|
namespace StabilityMatrix.Avalonia.ViewModels;
|
|
|
|
|
|
|
|
[View(typeof(SettingsPage))]
|
|
|
|
public partial class SettingsViewModel : PageViewModelBase
|
|
|
|
{
|
|
|
|
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
|
|
|
|
|
|
|
private readonly INotificationService notificationService;
|
|
|
|
private readonly ISettingsManager settingsManager;
|
|
|
|
private readonly IPrerequisiteHelper prerequisiteHelper;
|
|
|
|
private readonly IPyRunner pyRunner;
|
|
|
|
|
|
|
|
public override string Title => "Settings";
|
|
|
|
public override IconSource IconSource => new SymbolIconSource {Symbol = Symbol.Settings, IsFilled = true};
|
|
|
|
|
|
|
|
// ReSharper disable once MemberCanBeMadeStatic.Global
|
|
|
|
public string AppVersion => $"Version {Compat.AppVersion}";
|
|
|
|
|
|
|
|
// Theme section
|
|
|
|
[ObservableProperty] private string? selectedTheme;
|
|
|
|
|
|
|
|
// Shared folder options
|
|
|
|
[ObservableProperty] private bool removeSymlinksOnShutdown;
|
|
|
|
|
|
|
|
// Debug section
|
|
|
|
[ObservableProperty] private string? debugPaths;
|
|
|
|
[ObservableProperty] private string? debugCompatInfo;
|
|
|
|
[ObservableProperty] private string? debugGpuInfo;
|
|
|
|
|
|
|
|
public IReadOnlyList<string> AvailableThemes { get; } = new[]
|
|
|
|
{
|
|
|
|
"Light",
|
|
|
|
"Dark",
|
|
|
|
"System",
|
|
|
|
};
|
|
|
|
|
|
|
|
public SettingsViewModel(
|
|
|
|
INotificationService notificationService,
|
|
|
|
ISettingsManager settingsManager,
|
|
|
|
IPrerequisiteHelper prerequisiteHelper,
|
|
|
|
IPyRunner pyRunner)
|
|
|
|
{
|
|
|
|
this.notificationService = notificationService;
|
|
|
|
this.settingsManager = settingsManager;
|
|
|
|
this.prerequisiteHelper = prerequisiteHelper;
|
|
|
|
this.pyRunner = pyRunner;
|
|
|
|
|
|
|
|
SelectedTheme = AvailableThemes[1];
|
|
|
|
RemoveSymlinksOnShutdown = settingsManager.Settings.RemoveFolderLinksOnShutdown;
|
|
|
|
}
|
|
|
|
|
|
|
|
partial void OnSelectedThemeChanged(string? value)
|
|
|
|
{
|
|
|
|
// In case design / tests
|
|
|
|
if (Application.Current is null) return;
|
|
|
|
// Change theme
|
|
|
|
Application.Current.RequestedThemeVariant = value switch
|
|
|
|
{
|
|
|
|
"Dark" => ThemeVariant.Dark,
|
|
|
|
"Light" => ThemeVariant.Light,
|
|
|
|
_ => ThemeVariant.Default
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
private async Task CheckPythonVersion()
|
|
|
|
{
|
|
|
|
var isInstalled = prerequisiteHelper.IsPythonInstalled;
|
|
|
|
Logger.Debug($"Check python installed: {isInstalled}");
|
|
|
|
// Ensure python installed
|
|
|
|
if (!prerequisiteHelper.IsPythonInstalled)
|
|
|
|
{
|
|
|
|
// Need 7z as well for site packages repack
|
|
|
|
Logger.Debug("Python not installed, unpacking resources...");
|
|
|
|
await prerequisiteHelper.UnpackResourcesIfNecessary();
|
|
|
|
Logger.Debug("Unpacked resources, installing python...");
|
|
|
|
await prerequisiteHelper.InstallPythonIfNecessary();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get python version
|
|
|
|
await pyRunner.Initialize();
|
|
|
|
var result = await pyRunner.GetVersionInfo();
|
|
|
|
// Show dialog box
|
|
|
|
var dialog = new ContentDialog
|
|
|
|
{
|
|
|
|
Title = "Python version info",
|
|
|
|
Content = result,
|
|
|
|
PrimaryButtonText = "Ok",
|
|
|
|
IsPrimaryButtonEnabled = true
|
|
|
|
};
|
|
|
|
dialog.Title = "Python version info";
|
|
|
|
dialog.Content = result;
|
|
|
|
dialog.PrimaryButtonText = "Ok";
|
|
|
|
await dialog.ShowAsync();
|
|
|
|
}
|
|
|
|
|
|
|
|
#region Debug Section
|
|
|
|
public void LoadDebugInfo()
|
|
|
|
{
|
|
|
|
var assembly = Assembly.GetExecutingAssembly();
|
|
|
|
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
|
|
|
DebugPaths = $"""
|
|
|
|
Current Working Directory [Environment.CurrentDirectory]
|
|
|
|
"{Environment.CurrentDirectory}"
|
|
|
|
App Directory [Assembly.GetExecutingAssembly().Location]
|
|
|
|
"{assembly.Location}"
|
|
|
|
App Directory [AppContext.BaseDirectory]
|
|
|
|
"{AppContext.BaseDirectory}"
|
|
|
|
AppData Directory [SpecialFolder.ApplicationData]
|
|
|
|
"{appData}"
|
|
|
|
""";
|
|
|
|
|
|
|
|
// 1. Check portable mode
|
|
|
|
var appDir = Compat.AppCurrentDir;
|
|
|
|
var expectedPortableFile = Path.Combine(appDir, "Data", ".sm-portable");
|
|
|
|
var isPortableMode = File.Exists(expectedPortableFile);
|
|
|
|
|
|
|
|
DebugCompatInfo = $"""
|
|
|
|
Platform: {Compat.Platform}
|
|
|
|
AppData: {Compat.AppData}
|
|
|
|
AppDataHome: {Compat.AppDataHome}
|
|
|
|
AppCurrentDir: {Compat.AppCurrentDir}
|
|
|
|
ExecutableName: {Compat.GetExecutableName()}
|
|
|
|
-- Settings --
|
|
|
|
Expected Portable Marker file: {expectedPortableFile}
|
|
|
|
Portable Marker file exists: {isPortableMode}
|
|
|
|
IsLibraryDirSet = {settingsManager.IsLibraryDirSet}
|
|
|
|
IsPortableMode = {settingsManager.IsPortableMode}
|
|
|
|
""";
|
|
|
|
|
|
|
|
// Get Gpu info
|
|
|
|
var gpuInfo = "";
|
|
|
|
foreach (var (i, gpu) in HardwareHelper.IterGpuInfo().Enumerate())
|
|
|
|
{
|
|
|
|
gpuInfo += $"[{i+1}] {gpu}\n";
|
|
|
|
}
|
|
|
|
DebugGpuInfo = gpuInfo;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Debug buttons
|
|
|
|
[RelayCommand]
|
|
|
|
private void DebugNotification()
|
|
|
|
{
|
|
|
|
notificationService.Show(new Notification(
|
|
|
|
title: "Test Notification",
|
|
|
|
message: "Here is some message",
|
|
|
|
type: NotificationType.Information));
|
|
|
|
}
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
private async Task DebugContentDialog()
|
|
|
|
{
|
|
|
|
var dialog = new ContentDialog
|
|
|
|
{
|
|
|
|
DefaultButton = ContentDialogButton.Primary,
|
|
|
|
Title = "Test title",
|
|
|
|
PrimaryButtonText = "OK",
|
|
|
|
CloseButtonText = "Close"
|
|
|
|
};
|
|
|
|
|
|
|
|
var result = await dialog.ShowAsync();
|
|
|
|
notificationService.Show(new Notification("Content dialog closed",
|
|
|
|
$"Result: {result}"));
|
|
|
|
}
|
|
|
|
|
|
|
|
[RelayCommand]
|
|
|
|
private void DebugThrowException()
|
|
|
|
{
|
|
|
|
// Use try-catch to generate traceback information
|
|
|
|
throw new OperationCanceledException("Example Message");
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
}
|