插件系统试验
This commit is contained in:
lincube
2026-03-09 12:27:33 +08:00
parent c9f92a4755
commit cab35f4c22
49 changed files with 3355 additions and 158 deletions

View File

@@ -7,24 +7,65 @@ using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views.Components;
public sealed record DesktopComponentRuntimeRegistration(
string ComponentId,
string DisplayNameLocalizationKey,
Func<Control> ControlFactory,
Func<double, double>? CornerRadiusResolver = null);
public sealed record DesktopComponentControlFactoryContext(
DesktopComponentDefinition Definition,
double CellSize,
TimeZoneService TimeZoneService,
IWeatherInfoService WeatherInfoService,
IRecommendationInfoService RecommendationInfoService,
ICalculatorDataService CalculatorDataService,
IComponentInstanceSettingsStore ComponentSettingsStore,
string? PlacementId = null);
public sealed class DesktopComponentRuntimeRegistration
{
public DesktopComponentRuntimeRegistration(
string componentId,
string? displayNameLocalizationKey,
Func<Control> controlFactory,
Func<double, double>? cornerRadiusResolver = null)
: this(componentId, displayNameLocalizationKey, _ => controlFactory(), cornerRadiusResolver)
{
}
public DesktopComponentRuntimeRegistration(
string componentId,
string? displayNameLocalizationKey,
Func<DesktopComponentControlFactoryContext, Control> controlFactory,
Func<double, double>? cornerRadiusResolver = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(componentId);
ArgumentNullException.ThrowIfNull(controlFactory);
ComponentId = componentId.Trim();
DisplayNameLocalizationKey = string.IsNullOrWhiteSpace(displayNameLocalizationKey)
? null
: displayNameLocalizationKey.Trim();
ControlFactory = controlFactory;
CornerRadiusResolver = cornerRadiusResolver;
}
public string ComponentId { get; }
public string? DisplayNameLocalizationKey { get; }
public Func<DesktopComponentControlFactoryContext, Control> ControlFactory { get; }
public Func<double, double>? CornerRadiusResolver { get; }
}
public sealed class DesktopComponentRuntimeDescriptor
{
private static readonly Func<double, double> DefaultCornerRadiusResolver =
cellSize => Math.Clamp(cellSize * 0.22, 8, 18);
private readonly Func<Control> _controlFactory;
private readonly Func<DesktopComponentControlFactoryContext, Control> _controlFactory;
private readonly Func<double, double> _cornerRadiusResolver;
internal DesktopComponentRuntimeDescriptor(
DesktopComponentDefinition definition,
string displayNameLocalizationKey,
Func<Control> controlFactory,
string? displayNameLocalizationKey,
Func<DesktopComponentControlFactoryContext, Control> controlFactory,
Func<double, double>? cornerRadiusResolver)
{
Definition = definition;
@@ -35,7 +76,7 @@ public sealed class DesktopComponentRuntimeDescriptor
public DesktopComponentDefinition Definition { get; }
public string DisplayNameLocalizationKey { get; }
public string? DisplayNameLocalizationKey { get; }
public Control CreateControl(
double cellSize,
@@ -46,7 +87,15 @@ public sealed class DesktopComponentRuntimeDescriptor
IComponentInstanceSettingsStore componentSettingsStore,
string? placementId = null)
{
var control = _controlFactory();
var control = _controlFactory(new DesktopComponentControlFactoryContext(
Definition,
cellSize,
timeZoneService,
weatherInfoService,
recommendationInfoService,
calculatorDataService,
componentSettingsStore,
placementId));
var runtimeContext = new DesktopComponentRuntimeContext(
Definition.Id,
placementId,
@@ -133,12 +182,10 @@ public sealed class DesktopComponentRuntimeRegistry
StringComparer.OrdinalIgnoreCase);
}
public static DesktopComponentRuntimeRegistry CreateDefault(ComponentRegistry componentRegistry)
public static IReadOnlyList<DesktopComponentRuntimeRegistration> GetDefaultRegistrations()
{
return new DesktopComponentRuntimeRegistry(
componentRegistry,
new[]
{
return
[
new DesktopComponentRuntimeRegistration(
BuiltInComponentIds.Date,
"component.date",
@@ -319,7 +366,12 @@ public sealed class DesktopComponentRuntimeRegistry
"component.holiday_calendar",
() => new HolidayCalendarWidget(),
cellSize => Math.Clamp(cellSize * 0.32, 12, 28))
});
];
}
public static DesktopComponentRuntimeRegistry CreateDefault(ComponentRegistry componentRegistry)
{
return new DesktopComponentRuntimeRegistry(componentRegistry, GetDefaultRegistrations());
}
public bool TryGetDescriptor(string componentId, out DesktopComponentRuntimeDescriptor descriptor)

View File

@@ -3216,7 +3216,9 @@ public partial class MainWindow
private string GetLocalizedComponentDisplayName(DesktopComponentRuntimeDescriptor descriptor)
{
return L(descriptor.DisplayNameLocalizationKey, descriptor.Definition.DisplayName);
return string.IsNullOrWhiteSpace(descriptor.DisplayNameLocalizationKey)
? descriptor.Definition.DisplayName
: L(descriptor.DisplayNameLocalizationKey, descriptor.Definition.DisplayName);
}
private void OnComponentLibraryComponentPreviewPointerPressed(object? sender, PointerPressedEventArgs e)

View File

@@ -280,13 +280,22 @@ public partial class MainWindow
PluginSystemSettingsExpander.Header = L("settings.plugins.runtime_header", "Plugin Runtime");
PluginSystemSettingsExpander.Description = L(
"settings.plugins.runtime_desc",
"Manage plugin loading and backend isolation.");
"Review plugin runtime state and load results.");
PluginSystemDescriptionTextBlock.Text = L(
"settings.plugins.runtime_hint",
"This page will host installed plugin management, permission review, and sandboxed backend runtime controls.");
"This page shows discovery status, load results, and runtime diagnostics for installed plugins.");
PluginSystemStatusTextBlock.Text = L(
"settings.plugins.runtime_status",
"Plugin management UI is not connected yet. Next step is wiring the loader, permissions, and worker isolation state into this panel.");
"Plugin runtime status will appear here after plugin discovery completes.");
InstalledPluginsSettingsExpander.Header = L("settings.plugins.installed_header", "Installed Plugins");
InstalledPluginsSettingsExpander.Description = L(
"settings.plugins.installed_desc",
"Enable or disable plugins here. Detailed plugin settings appear as separate settings pages.");
PluginRestartHintTextBlock.Text = L(
"settings.plugins.restart_hint",
"Plugin enable state changes take effect after restarting the app.");
PluginCatalogEmptyTextBlock.Text = L("settings.plugins.empty", "No plugins found.");
PluginSettingsPanel.RefreshFromRuntime();
SettingsNavAboutItem.Content = L("settings.nav.about", "About");
AboutPanelTitleTextBlock.Text = L("settings.about.title", "About");
@@ -428,3 +437,4 @@ public partial class MainWindow
: _weatherLocationKey);
}
}

View File

@@ -0,0 +1,193 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using FluentAvalonia.UI.Controls;
using FluentIcons.Avalonia.Fluent;
using FluentIcons.Common;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views;
public partial class MainWindow
{
private readonly Dictionary<string, Control> _pluginSettingsPageHosts = new(StringComparer.OrdinalIgnoreCase);
private void InitializePluginSettingsNavigation()
{
if (_pluginSettingsPageHosts.Count > 0 || SettingsNavView?.MenuItems is null)
{
return;
}
var runtime = (Application.Current as App)?.PluginRuntimeService;
var contributions = runtime?.SettingsPages
.OrderBy(contribution => contribution.Registration.SortOrder)
.ThenBy(contribution => contribution.Plugin.Manifest.Name, StringComparer.OrdinalIgnoreCase)
.ThenBy(contribution => contribution.Registration.Title, StringComparer.OrdinalIgnoreCase)
.ToArray();
if (contributions is not { Length: > 0 })
{
return;
}
var pageCountsByPluginId = contributions
.GroupBy(contribution => contribution.Plugin.Manifest.Id, StringComparer.OrdinalIgnoreCase)
.ToDictionary(group => group.Key, group => group.Count(), StringComparer.OrdinalIgnoreCase);
var insertIndex = SettingsNavView.MenuItems.IndexOf(SettingsNavPluginsItem) + 1;
foreach (var contribution in contributions)
{
var tag = BuildPluginSettingsTag(contribution);
var navigationTitle = BuildPluginSettingsNavigationTitle(contribution, pageCountsByPluginId);
var navItem = new NavigationViewItem
{
Content = navigationTitle,
Tag = tag,
IconSource = new FluentIcons.Avalonia.Fluent.SymbolIconSource
{
Symbol = FluentIcons.Common.Symbol.PuzzlePiece,
IconVariant = FluentIcons.Common.IconVariant.Regular
}
};
ToolTip.SetTip(navItem, $"{contribution.Plugin.Manifest.Name} - {contribution.Registration.Title}");
SettingsNavView.MenuItems.Insert(insertIndex++, navItem);
var pageHost = CreatePluginSettingsPageHost(contribution);
pageHost.IsVisible = false;
SettingsContentPagesHost.Children.Add(pageHost);
_pluginSettingsPageHosts[tag] = pageHost;
}
}
private static string BuildPluginSettingsTag(PluginSettingsPageContribution contribution)
{
return $"PluginPage:{contribution.Plugin.Manifest.Id}:{contribution.Registration.Id}";
}
private static string BuildPluginSettingsNavigationTitle(
PluginSettingsPageContribution contribution,
IReadOnlyDictionary<string, int> pageCountsByPluginId)
{
return pageCountsByPluginId.TryGetValue(contribution.Plugin.Manifest.Id, out var pageCount) && pageCount > 1
? $"{contribution.Plugin.Manifest.Name} - {contribution.Registration.Title}"
: contribution.Plugin.Manifest.Name;
}
private Control CreatePluginSettingsPageHost(PluginSettingsPageContribution contribution)
{
Control content;
try
{
content = contribution.Registration.ContentFactory();
}
catch (Exception ex)
{
content = CreatePluginPageErrorContent(ex);
}
return new StackPanel
{
Spacing = 16,
Children =
{
new TextBlock
{
Text = contribution.Registration.Title,
FontSize = 24,
FontWeight = FontWeight.SemiBold,
Foreground = GetThemeBrush("AdaptiveTextPrimaryBrush")
},
new TextBlock
{
Text = contribution.Plugin.Manifest.Name,
Foreground = GetThemeBrush("AdaptiveTextSecondaryBrush")
},
content
}
};
}
private Control CreatePluginPageErrorContent(Exception exception)
{
return new Border
{
Background = new SolidColorBrush(Color.Parse("#332B0F16")),
BorderBrush = new SolidColorBrush(Color.Parse("#66F97316")),
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(16),
Padding = new Thickness(16),
Child = new TextBlock
{
Text = exception.Message,
TextWrapping = TextWrapping.Wrap
}
};
}
private void UpdatePluginSettingsPageVisibility(string? selectedTag)
{
foreach (var pair in _pluginSettingsPageHosts)
{
pair.Value.IsVisible = string.Equals(pair.Key, selectedTag, StringComparison.OrdinalIgnoreCase);
}
}
private string? GetSelectedSettingsTabTag()
{
return (SettingsNavView?.SelectedItem as NavigationViewItem)?.Tag?.ToString();
}
private int ResolveSelectedSettingsTabIndex()
{
if (SettingsNavView?.SelectedItem is null || SettingsNavView.MenuItems is null)
{
return 0;
}
for (var i = 0; i < SettingsNavView.MenuItems.Count; i++)
{
if (ReferenceEquals(SettingsNavView.MenuItems[i], SettingsNavView.SelectedItem))
{
return i;
}
}
return 0;
}
private void RestoreSettingsTabSelection(AppSettingsSnapshot snapshot)
{
if (SettingsNavView?.MenuItems is null || SettingsNavView.MenuItems.Count == 0)
{
return;
}
if (!string.IsNullOrWhiteSpace(snapshot.SettingsTabTag))
{
var taggedItem = SettingsNavView.MenuItems
.OfType<NavigationViewItem>()
.FirstOrDefault(item => string.Equals(item.Tag?.ToString(), snapshot.SettingsTabTag, StringComparison.OrdinalIgnoreCase));
if (taggedItem is not null)
{
SettingsNavView.SelectedItem = taggedItem;
return;
}
}
var safeIndex = Math.Clamp(snapshot.SettingsTabIndex, 0, Math.Max(0, SettingsNavView.MenuItems.Count - 1));
if (SettingsNavView.MenuItems[safeIndex] is NavigationViewItem navItem)
{
SettingsNavView.SelectedItem = navItem;
}
}
}

View File

@@ -100,24 +100,7 @@ public partial class MainWindow
private int GetSettingsTabIndex()
{
if (SettingsNavView?.SelectedItem is FluentAvalonia.UI.Controls.NavigationViewItem item)
{
return item.Tag?.ToString() switch
{
"Wallpaper" => 0,
"Grid" => 1,
"Color" => 2,
"StatusBar" => 3,
"Weather" => 4,
"Region" => 5,
"Update" => 6,
"About" => 7,
"Launcher" => 8,
"Plugins" => 9,
_ => 0
};
}
return 0;
return ResolveSelectedSettingsTabIndex();
}
private void UpdateSettingsTabContent()
@@ -150,6 +133,7 @@ public partial class MainWindow
AboutSettingsPanel.IsVisible = tag == "About";
LauncherSettingsPanel.IsVisible = tag == "Launcher";
PluginSettingsPanel.IsVisible = tag == "Plugins";
UpdatePluginSettingsPageVisibility(tag);
if (tag == "Launcher")
{
@@ -984,10 +968,7 @@ public partial class MainWindow
GridSizeNumberBox.Value = _targetShortSideCells;
GridSizeSlider.Value = _targetShortSideCells;
if (SettingsNavView.MenuItems.ElementAtOrDefault(Math.Clamp(snapshot.SettingsTabIndex, 0, 9)) is FluentAvalonia.UI.Controls.NavigationViewItem navItem)
{
SettingsNavView.SelectedItem = navItem;
}
RestoreSettingsTabSelection(snapshot);
UpdateSettingsTabContent();
WallpaperPlacementComboBox.SelectedIndex = GetPlacementIndexFromSetting(snapshot.WallpaperPlacement);
@@ -1036,42 +1017,41 @@ public partial class MainWindow
private AppSettingsSnapshot BuildAppSettingsSnapshot()
{
return new AppSettingsSnapshot
{
GridShortSideCells = _targetShortSideCells,
GridSpacingPreset = _gridSpacingPreset,
DesktopEdgeInsetPercent = _desktopEdgeInsetPercent,
IsNightMode = _isNightMode,
ThemeColor = _selectedThemeColor.ToString(),
WallpaperPath = _wallpaperPath,
WallpaperPlacement = GetPlacementDisplayName(GetSelectedWallpaperPlacement()),
SettingsTabIndex = Math.Max(0, GetSettingsTabIndex()),
LanguageCode = _languageCode,
TimeZoneId = _timeZoneService.CurrentTimeZone.Id,
WeatherLocationMode = ToWeatherLocationModeTag(_weatherLocationMode),
WeatherLocationKey = _weatherLocationKey,
WeatherLocationName = _weatherLocationName,
WeatherLatitude = _weatherLatitude,
WeatherLongitude = _weatherLongitude,
WeatherAutoRefreshLocation = _weatherAutoRefreshLocation,
WeatherLocationQuery = BuildLegacyWeatherLocationQuery(),
WeatherExcludedAlerts = _weatherExcludedAlertsRaw,
WeatherIconPackId = _weatherIconPackId,
WeatherNoTlsRequests = _weatherNoTlsRequests,
AutoStartWithWindows = _autoStartWithWindows,
AutoCheckUpdates = _autoCheckUpdates,
IncludePrereleaseUpdates = IncludePrereleaseUpdates,
UpdateChannel = IncludePrereleaseUpdates ? "Preview" : "Stable",
TopStatusComponentIds = _topStatusComponentIds.ToList(),
PinnedTaskbarActions = _pinnedTaskbarActions.Select(action => action.ToString()).ToList(),
EnableDynamicTaskbarActions = _enableDynamicTaskbarActions,
TaskbarLayoutMode = _taskbarLayoutMode,
ClockDisplayFormat = _clockDisplayFormat == ClockDisplayFormat.HourMinute ? "HourMinute" : "HourMinuteSecond",
StatusBarSpacingMode = _statusBarSpacingMode,
StatusBarCustomSpacingPercent = _statusBarCustomSpacingPercent
};
var snapshot = _appSettingsService.Load();
snapshot.GridShortSideCells = _targetShortSideCells;
snapshot.GridSpacingPreset = _gridSpacingPreset;
snapshot.DesktopEdgeInsetPercent = _desktopEdgeInsetPercent;
snapshot.IsNightMode = _isNightMode;
snapshot.ThemeColor = _selectedThemeColor.ToString();
snapshot.WallpaperPath = _wallpaperPath;
snapshot.WallpaperPlacement = GetPlacementDisplayName(GetSelectedWallpaperPlacement());
snapshot.SettingsTabIndex = Math.Max(0, GetSettingsTabIndex());
snapshot.SettingsTabTag = GetSelectedSettingsTabTag();
snapshot.LanguageCode = _languageCode;
snapshot.TimeZoneId = _timeZoneService.CurrentTimeZone.Id;
snapshot.WeatherLocationMode = ToWeatherLocationModeTag(_weatherLocationMode);
snapshot.WeatherLocationKey = _weatherLocationKey;
snapshot.WeatherLocationName = _weatherLocationName;
snapshot.WeatherLatitude = _weatherLatitude;
snapshot.WeatherLongitude = _weatherLongitude;
snapshot.WeatherAutoRefreshLocation = _weatherAutoRefreshLocation;
snapshot.WeatherLocationQuery = BuildLegacyWeatherLocationQuery();
snapshot.WeatherExcludedAlerts = _weatherExcludedAlertsRaw;
snapshot.WeatherIconPackId = _weatherIconPackId;
snapshot.WeatherNoTlsRequests = _weatherNoTlsRequests;
snapshot.AutoStartWithWindows = _autoStartWithWindows;
snapshot.AutoCheckUpdates = _autoCheckUpdates;
snapshot.IncludePrereleaseUpdates = IncludePrereleaseUpdates;
snapshot.UpdateChannel = IncludePrereleaseUpdates ? UpdateChannelPreview : UpdateChannelStable;
snapshot.TopStatusComponentIds = _topStatusComponentIds.ToList();
snapshot.PinnedTaskbarActions = _pinnedTaskbarActions.Select(action => action.ToString()).ToList();
snapshot.EnableDynamicTaskbarActions = _enableDynamicTaskbarActions;
snapshot.TaskbarLayoutMode = _taskbarLayoutMode;
snapshot.ClockDisplayFormat = _clockDisplayFormat == ClockDisplayFormat.HourMinute ? "HourMinute" : "HourMinuteSecond";
snapshot.StatusBarSpacingMode = _statusBarSpacingMode;
snapshot.StatusBarCustomSpacingPercent = _statusBarCustomSpacingPercent;
return snapshot;
}
private DesktopLayoutSettingsSnapshot BuildDesktopLayoutSettingsSnapshot()
{
return new DesktopLayoutSettingsSnapshot
@@ -1081,7 +1061,6 @@ public partial class MainWindow
DesktopComponentPlacements = _desktopComponentPlacements.ToList()
};
}
private LauncherSettingsSnapshot BuildLauncherSettingsSnapshot()
{
return new LauncherSettingsSnapshot
@@ -2678,4 +2657,10 @@ public partial class MainWindow
internal FluentAvalonia.UI.Controls.SettingsExpander PluginSystemSettingsExpander => PluginSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("PluginSystemSettingsExpander")!;
internal TextBlock PluginSystemDescriptionTextBlock => PluginSettingsPanel.FindControl<TextBlock>("PluginSystemDescriptionTextBlock")!;
internal TextBlock PluginSystemStatusTextBlock => PluginSettingsPanel.FindControl<TextBlock>("PluginSystemStatusTextBlock")!;
internal FluentAvalonia.UI.Controls.SettingsExpander InstalledPluginsSettingsExpander => PluginSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("InstalledPluginsSettingsExpander")!;
internal TextBlock PluginRestartHintTextBlock => PluginSettingsPanel.FindControl<TextBlock>("PluginRestartHintTextBlock")!;
internal TextBlock PluginCatalogEmptyTextBlock => PluginSettingsPanel.FindControl<TextBlock>("PluginCatalogEmptyTextBlock")!;
}

View File

@@ -104,7 +104,7 @@
ClipToBounds="True"
BorderThickness="0"
PointerWheelChanged="OnDesktopPagesPointerWheelChanged">
<Grid>
<Grid x:Name="SettingsContentPagesHost">
<Grid x:Name="DesktopPagesHost"
HorizontalAlignment="Left"
VerticalAlignment="Top">

View File

@@ -19,7 +19,6 @@ using Avalonia.Styling;
using Avalonia.Threading;
using FluentAvalonia.Styling;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.ComponentSystem.Extensions;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using LanMountainDesktop.Theme;
@@ -98,11 +97,7 @@ public partial class MainWindow : Window
private readonly IWeatherDataService _weatherDataService = new XiaomiWeatherService();
private readonly IRecommendationInfoService _recommendationInfoService = new RecommendationDataService();
private readonly ICalculatorDataService _calculatorDataService = new CalculatorDataService();
private readonly ComponentRegistry _componentRegistry = ComponentRegistry
.CreateDefault()
.RegisterExtensions(
JsonComponentExtensionProvider.LoadProvidersFromDirectory(
Path.Combine(AppContext.BaseDirectory, "Extensions", "Components")));
private readonly ComponentRegistry _componentRegistry;
private readonly DesktopComponentRuntimeRegistry _componentRuntimeRegistry;
private readonly FluentAvaloniaTheme? _fluentAvaloniaTheme;
private readonly HashSet<string> _topStatusComponentIds = new(StringComparer.OrdinalIgnoreCase);
@@ -182,8 +177,14 @@ public partial class MainWindow : Window
public MainWindow()
{
var pluginRuntimeService = (Application.Current as App)?.PluginRuntimeService;
_componentRegistry = DesktopComponentRegistryFactory.Create(pluginRuntimeService);
InitializeComponent();
_componentRuntimeRegistry = DesktopComponentRuntimeRegistry.CreateDefault(_componentRegistry);
InitializePluginSettingsNavigation();
_componentRuntimeRegistry = DesktopComponentRegistryFactory.CreateRuntimeRegistry(
_componentRegistry,
pluginRuntimeService);
_fluentAvaloniaTheme = Application.Current?.Styles.OfType<FluentAvaloniaTheme>().FirstOrDefault();
AppSettingsService.SettingsSaved += OnExternalAppSettingsSaved;
LauncherSettingsService.SettingsSaved += OnExternalLauncherSettingsSaved;
@@ -300,10 +301,7 @@ public partial class MainWindow : Window
GridSizeSlider.ValueChanged += OnGridSizeSliderChanged;
GridSizeNumberBox.ValueChanged += OnGridSizeNumberBoxChanged;
if (SettingsNavView.MenuItems.ElementAtOrDefault(Math.Clamp(snapshot.SettingsTabIndex, 0, 9)) is FluentAvalonia.UI.Controls.NavigationViewItem navItem)
{
SettingsNavView.SelectedItem = navItem;
}
RestoreSettingsTabSelection(snapshot);
UpdateSettingsTabContent();
WallpaperPlacementComboBox.SelectedIndex = GetPlacementIndexFromSetting(snapshot.WallpaperPlacement);

View File

@@ -35,6 +35,31 @@
TextWrapping="Wrap"
Text="Plugin management UI is not connected yet. Next step is wiring the loader, permissions, and worker isolation state into this panel." />
</Border>
<StackPanel x:Name="PluginRuntimeSummaryPanel" Spacing="6" />
</StackPanel>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Border>
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="InstalledPluginsSettingsExpander"
Header="Installed Plugins"
Description="Enable plugins here. Detailed settings appear as separate settings pages."
IsExpanded="True">
<ui:SettingsExpander.IconSource>
<ui:FontIconSource Glyph="&#xe8fd;" FontFamily="{StaticResource SymbolThemeFontFamily}" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<StackPanel Spacing="10">
<TextBlock x:Name="PluginRestartHintTextBlock"
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
TextWrapping="Wrap"
Text="Plugin enable state changes take effect after restarting the app." />
<TextBlock x:Name="PluginCatalogEmptyTextBlock"
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
Text="No plugins found."
IsVisible="False" />
<StackPanel x:Name="PluginCatalogItemsHost" Spacing="12" />
</StackPanel>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>

View File

@@ -1,12 +1,222 @@
using System;
using System.Globalization;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Layout;
using Avalonia.Media;
using FluentAvalonia.UI.Controls;
using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views.SettingsPages;
public partial class PluginSettingsPage : UserControl
{
private readonly AppSettingsService _appSettingsService = new();
private readonly LocalizationService _localizationService = new();
public PluginSettingsPage()
{
InitializeComponent();
AttachedToVisualTree += (_, _) => RefreshFromRuntime();
}
public void RefreshFromRuntime()
{
var runtime = (Application.Current as App)?.PluginRuntimeService;
if (runtime is null)
{
PluginSystemStatusTextBlock.Text = L("settings.plugins.runtime_unavailable", "Plugin runtime is not available.");
PluginRuntimeSummaryPanel.Children.Clear();
PluginCatalogItemsHost.Children.Clear();
PluginRestartHintTextBlock.IsVisible = false;
return;
}
BuildRuntimeSummary(runtime);
BuildPluginCatalog(runtime);
}
private void BuildRuntimeSummary(PluginRuntimeService runtime)
{
var failures = runtime.LoadResults.Where(result => !result.IsSuccess).ToArray();
var enabledCount = runtime.Catalog.Count(entry => entry.IsEnabled);
PluginSystemStatusTextBlock.Text = F(
"settings.plugins.summary_format",
"Detected {0} plugin(s); enabled {1}; loaded {2}; settings pages {3}; widgets {4}; failures {5}.",
runtime.Catalog.Count,
enabledCount,
runtime.LoadedPlugins.Count,
runtime.SettingsPages.Count,
runtime.DesktopComponents.Count,
failures.Length);
PluginRuntimeSummaryPanel.Children.Clear();
foreach (var plugin in runtime.Catalog.OrderBy(entry => entry.Manifest.Name, StringComparer.OrdinalIgnoreCase))
{
var status = plugin.IsEnabled
? plugin.IsLoaded
? L("settings.plugins.state.enabled", "Enabled")
: L("settings.plugins.state.enabled_failed", "Enabled / failed to load")
: L("settings.plugins.state.disabled", "Disabled");
PluginRuntimeSummaryPanel.Children.Add(CreateSummaryLine(
F(
"settings.plugins.summary_item_format",
"{0} v{1} | {2}",
plugin.Manifest.Name,
plugin.Manifest.Version ?? "dev",
status)));
}
}
private void BuildPluginCatalog(PluginRuntimeService runtime)
{
PluginCatalogItemsHost.Children.Clear();
var plugins = runtime.Catalog
.OrderBy(entry => entry.Manifest.Name, StringComparer.OrdinalIgnoreCase)
.ToList();
PluginCatalogEmptyTextBlock.IsVisible = plugins.Count == 0;
PluginRestartHintTextBlock.IsVisible = plugins.Count > 0;
foreach (var plugin in plugins)
{
PluginCatalogItemsHost.Children.Add(CreatePluginCatalogItem(runtime, plugin));
}
}
private Control CreatePluginCatalogItem(PluginRuntimeService runtime, PluginCatalogEntry entry)
{
var title = new TextBlock
{
Text = entry.Manifest.Name,
FontSize = 16,
FontWeight = FontWeight.SemiBold,
TextWrapping = TextWrapping.Wrap
};
var subtitle = new TextBlock
{
Text = BuildPluginSubtitle(entry),
Foreground = PluginSystemDescriptionTextBlock.Foreground,
TextWrapping = TextWrapping.Wrap
};
var enabledToggle = new ToggleSwitch
{
IsChecked = entry.IsEnabled,
OnContent = L("settings.plugins.toggle_on", "Enabled"),
OffContent = L("settings.plugins.toggle_off", "Disabled"),
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Center
};
enabledToggle.Checked += (_, _) => OnPluginEnableChanged(runtime, entry, true);
enabledToggle.Unchecked += (_, _) => OnPluginEnableChanged(runtime, entry, false);
var header = new Grid
{
ColumnDefinitions = new ColumnDefinitions("*,Auto"),
ColumnSpacing = 12,
Children =
{
new StackPanel
{
Spacing = 4,
Children = { title, subtitle }
},
enabledToggle
}
};
Grid.SetColumn(enabledToggle, 1);
var details = new TextBlock
{
Text = BuildPluginDetails(entry),
Foreground = PluginSystemDescriptionTextBlock.Foreground,
TextWrapping = TextWrapping.Wrap
};
return new Border
{
Background = new SolidColorBrush(Color.Parse("#14000000")),
CornerRadius = new CornerRadius(16),
Padding = new Thickness(14),
Child = new StackPanel
{
Spacing = 10,
Children = { header, details }
}
};
}
private void OnPluginEnableChanged(PluginRuntimeService runtime, PluginCatalogEntry entry, bool isEnabled)
{
runtime.SetPluginEnabled(entry.Manifest.Id, isEnabled);
BuildRuntimeSummary(runtime);
BuildPluginCatalog(runtime);
PluginSystemStatusTextBlock.Text = F(
"settings.plugins.toggle_result_format",
"Plugin '{0}' was {1} for the next launch. Restart the app to apply page and widget changes.",
entry.Manifest.Name,
isEnabled
? L("settings.plugins.toggle_state_enabled", "enabled")
: L("settings.plugins.toggle_state_disabled", "disabled"));
}
private string BuildPluginSubtitle(PluginCatalogEntry entry)
{
var source = entry.IsPackage
? L("settings.plugins.source_package", ".laapp package")
: L("settings.plugins.source_manifest", "Loose manifest");
var state = entry.IsEnabled
? entry.IsLoaded
? L("settings.plugins.state.loaded", "Loaded")
: L("settings.plugins.state.load_failed", "Load failed")
: L("settings.plugins.state.disabled", "Disabled");
return F(
"settings.plugins.subtitle_format",
"{0} | {1} | {2}",
state,
source,
entry.Manifest.Id);
}
private string BuildPluginDetails(PluginCatalogEntry entry)
{
var detail = F(
"settings.plugins.detail_format",
"Settings pages: {0} | Widgets: {1}",
entry.SettingsPageCount,
entry.WidgetCount);
return string.IsNullOrWhiteSpace(entry.ErrorMessage)
? detail
: detail + Environment.NewLine + entry.ErrorMessage;
}
private TextBlock CreateSummaryLine(string text)
{
return new TextBlock
{
Text = text,
TextWrapping = TextWrapping.Wrap,
Foreground = PluginSystemDescriptionTextBlock.Foreground
};
}
private string L(string key, string fallback)
{
var snapshot = _appSettingsService.Load();
return _localizationService.GetString(snapshot.LanguageCode, key, fallback);
}
private string F(string key, string fallback, params object[] args)
{
return string.Format(CultureInfo.CurrentCulture, L(key, fallback), args);
}
}

View File

@@ -1,4 +1,4 @@
using Avalonia.Controls;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using LibVLCSharp.Avalonia;
@@ -206,4 +206,8 @@ public partial class SettingsWindow
internal FluentAvalonia.UI.Controls.SettingsExpander PluginSystemSettingsExpander => PluginSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("PluginSystemSettingsExpander")!;
internal TextBlock PluginSystemDescriptionTextBlock => PluginSettingsPanel.FindControl<TextBlock>("PluginSystemDescriptionTextBlock")!;
internal TextBlock PluginSystemStatusTextBlock => PluginSettingsPanel.FindControl<TextBlock>("PluginSystemStatusTextBlock")!;
internal FluentAvalonia.UI.Controls.SettingsExpander InstalledPluginsSettingsExpander => PluginSettingsPanel.FindControl<FluentAvalonia.UI.Controls.SettingsExpander>("InstalledPluginsSettingsExpander")!;
internal TextBlock PluginRestartHintTextBlock => PluginSettingsPanel.FindControl<TextBlock>("PluginRestartHintTextBlock")!;
internal TextBlock PluginCatalogEmptyTextBlock => PluginSettingsPanel.FindControl<TextBlock>("PluginCatalogEmptyTextBlock")!;
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -54,25 +54,7 @@ public partial class SettingsWindow
private int GetSettingsTabIndex()
{
if (SettingsNavView?.SelectedItem is FluentAvalonia.UI.Controls.NavigationViewItem item)
{
return item.Tag?.ToString() switch
{
"Wallpaper" => 0,
"Grid" => 1,
"Color" => 2,
"StatusBar" => 3,
"Weather" => 4,
"Region" => 5,
"Update" => 6,
"About" => 7,
"Launcher" => 8,
"Plugins" => 9,
_ => 0
};
}
return 0;
return ResolveSelectedSettingsTabIndex();
}
private void UpdateSettingsTabContent()
@@ -95,6 +77,7 @@ public partial class SettingsWindow
AboutSettingsPanel.IsVisible = tag == "About";
LauncherSettingsPanel.IsVisible = tag == "Launcher";
PluginSettingsPanel.IsVisible = tag == "Plugins";
UpdatePluginSettingsPageVisibility(tag);
if (tag == "Launcher")
{
@@ -122,40 +105,40 @@ public partial class SettingsWindow
private AppSettingsSnapshot BuildAppSettingsSnapshot()
{
return new AppSettingsSnapshot
{
GridShortSideCells = _targetShortSideCells,
GridSpacingPreset = _gridSpacingPreset,
DesktopEdgeInsetPercent = _desktopEdgeInsetPercent,
IsNightMode = _isNightMode,
ThemeColor = _selectedThemeColor.ToString(),
WallpaperPath = _wallpaperPath,
WallpaperPlacement = GetPlacementDisplayName(GetSelectedWallpaperPlacement()),
SettingsTabIndex = Math.Max(0, GetSettingsTabIndex()),
LanguageCode = _languageCode,
TimeZoneId = _timeZoneService.CurrentTimeZone.Id,
WeatherLocationMode = ToWeatherLocationModeTag(_weatherLocationMode),
WeatherLocationKey = _weatherLocationKey,
WeatherLocationName = _weatherLocationName,
WeatherLatitude = _weatherLatitude,
WeatherLongitude = _weatherLongitude,
WeatherAutoRefreshLocation = _weatherAutoRefreshLocation,
WeatherLocationQuery = BuildLegacyWeatherLocationQuery(),
WeatherExcludedAlerts = _weatherExcludedAlertsRaw,
WeatherIconPackId = _weatherIconPackId,
WeatherNoTlsRequests = _weatherNoTlsRequests,
AutoStartWithWindows = _autoStartWithWindows,
AutoCheckUpdates = _autoCheckUpdates,
IncludePrereleaseUpdates = IncludePrereleaseUpdates,
UpdateChannel = IncludePrereleaseUpdates ? UpdateChannelPreview : UpdateChannelStable,
TopStatusComponentIds = _topStatusComponentIds.ToList(),
PinnedTaskbarActions = _pinnedTaskbarActions.Select(action => action.ToString()).ToList(),
EnableDynamicTaskbarActions = _enableDynamicTaskbarActions,
TaskbarLayoutMode = _taskbarLayoutMode,
ClockDisplayFormat = _clockDisplayFormat == ClockDisplayFormat.HourMinute ? "HourMinute" : "HourMinuteSecond",
StatusBarSpacingMode = _statusBarSpacingMode,
StatusBarCustomSpacingPercent = _statusBarCustomSpacingPercent
};
var snapshot = _appSettingsService.Load();
snapshot.GridShortSideCells = _targetShortSideCells;
snapshot.GridSpacingPreset = _gridSpacingPreset;
snapshot.DesktopEdgeInsetPercent = _desktopEdgeInsetPercent;
snapshot.IsNightMode = _isNightMode;
snapshot.ThemeColor = _selectedThemeColor.ToString();
snapshot.WallpaperPath = _wallpaperPath;
snapshot.WallpaperPlacement = GetPlacementDisplayName(GetSelectedWallpaperPlacement());
snapshot.SettingsTabIndex = Math.Max(0, GetSettingsTabIndex());
snapshot.SettingsTabTag = GetSelectedSettingsTabTag();
snapshot.LanguageCode = _languageCode;
snapshot.TimeZoneId = _timeZoneService.CurrentTimeZone.Id;
snapshot.WeatherLocationMode = ToWeatherLocationModeTag(_weatherLocationMode);
snapshot.WeatherLocationKey = _weatherLocationKey;
snapshot.WeatherLocationName = _weatherLocationName;
snapshot.WeatherLatitude = _weatherLatitude;
snapshot.WeatherLongitude = _weatherLongitude;
snapshot.WeatherAutoRefreshLocation = _weatherAutoRefreshLocation;
snapshot.WeatherLocationQuery = BuildLegacyWeatherLocationQuery();
snapshot.WeatherExcludedAlerts = _weatherExcludedAlertsRaw;
snapshot.WeatherIconPackId = _weatherIconPackId;
snapshot.WeatherNoTlsRequests = _weatherNoTlsRequests;
snapshot.AutoStartWithWindows = _autoStartWithWindows;
snapshot.AutoCheckUpdates = _autoCheckUpdates;
snapshot.IncludePrereleaseUpdates = IncludePrereleaseUpdates;
snapshot.UpdateChannel = IncludePrereleaseUpdates ? UpdateChannelPreview : UpdateChannelStable;
snapshot.TopStatusComponentIds = _topStatusComponentIds.ToList();
snapshot.PinnedTaskbarActions = _pinnedTaskbarActions.Select(action => action.ToString()).ToList();
snapshot.EnableDynamicTaskbarActions = _enableDynamicTaskbarActions;
snapshot.TaskbarLayoutMode = _taskbarLayoutMode;
snapshot.ClockDisplayFormat = _clockDisplayFormat == ClockDisplayFormat.HourMinute ? "HourMinute" : "HourMinuteSecond";
snapshot.StatusBarSpacingMode = _statusBarSpacingMode;
snapshot.StatusBarCustomSpacingPercent = _statusBarCustomSpacingPercent;
return snapshot;
}
private LauncherSettingsSnapshot BuildLauncherSettingsSnapshot()
@@ -514,3 +497,4 @@ public partial class SettingsWindow
return Brushes.Transparent;
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using Avalonia.Controls;
@@ -111,9 +111,14 @@ public partial class SettingsWindow
PluginSettingsPanelTitleTextBlock.Text = L("settings.plugins.title", "Plugins");
PluginSystemSettingsExpander.Header = L("settings.plugins.runtime_header", "Plugin Runtime");
PluginSystemSettingsExpander.Description = L("settings.plugins.runtime_desc", "Manage plugin loading and backend isolation.");
PluginSystemDescriptionTextBlock.Text = L("settings.plugins.runtime_hint", "This page will host installed plugin management, permission review, and sandboxed backend runtime controls.");
PluginSystemStatusTextBlock.Text = L("settings.plugins.runtime_status", "Plugin management UI is not connected yet. Next step is wiring the loader, permissions, and worker isolation state into this panel.");
PluginSystemSettingsExpander.Description = L("settings.plugins.runtime_desc", "Review plugin runtime state and load results.");
PluginSystemDescriptionTextBlock.Text = L("settings.plugins.runtime_hint", "This page shows discovery status, load results, and runtime diagnostics for installed plugins.");
PluginSystemStatusTextBlock.Text = L("settings.plugins.runtime_status", "Plugin runtime status will appear here after plugin discovery completes.");
InstalledPluginsSettingsExpander.Header = L("settings.plugins.installed_header", "Installed Plugins");
InstalledPluginsSettingsExpander.Description = L("settings.plugins.installed_desc", "Enable or disable plugins here. Detailed plugin settings appear as separate settings pages.");
PluginRestartHintTextBlock.Text = L("settings.plugins.restart_hint", "Plugin enable state changes take effect after restarting the app.");
PluginCatalogEmptyTextBlock.Text = L("settings.plugins.empty", "No plugins found.");
PluginSettingsPanel.RefreshFromRuntime();
AboutPanelTitleTextBlock.Text = L("settings.about.title", "About");
VersionTextBlock.Text = Lf("settings.about.version_format", "Version: {0}", GetAppVersionText());
@@ -183,3 +188,4 @@ public partial class SettingsWindow
PersistSettings();
}
}

View File

@@ -0,0 +1,193 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using FluentAvalonia.UI.Controls;
using FluentIcons.Avalonia.Fluent;
using FluentIcons.Common;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views;
public partial class SettingsWindow
{
private readonly Dictionary<string, Control> _pluginSettingsPageHosts = new(StringComparer.OrdinalIgnoreCase);
private void InitializePluginSettingsNavigation()
{
if (_pluginSettingsPageHosts.Count > 0 || SettingsNavView?.MenuItems is null)
{
return;
}
var runtime = (Application.Current as App)?.PluginRuntimeService;
var contributions = runtime?.SettingsPages
.OrderBy(contribution => contribution.Registration.SortOrder)
.ThenBy(contribution => contribution.Plugin.Manifest.Name, StringComparer.OrdinalIgnoreCase)
.ThenBy(contribution => contribution.Registration.Title, StringComparer.OrdinalIgnoreCase)
.ToArray();
if (contributions is not { Length: > 0 })
{
return;
}
var pageCountsByPluginId = contributions
.GroupBy(contribution => contribution.Plugin.Manifest.Id, StringComparer.OrdinalIgnoreCase)
.ToDictionary(group => group.Key, group => group.Count(), StringComparer.OrdinalIgnoreCase);
var insertIndex = SettingsNavView.MenuItems.IndexOf(SettingsNavPluginsItem) + 1;
foreach (var contribution in contributions)
{
var tag = BuildPluginSettingsTag(contribution);
var navigationTitle = BuildPluginSettingsNavigationTitle(contribution, pageCountsByPluginId);
var navItem = new NavigationViewItem
{
Content = navigationTitle,
Tag = tag,
IconSource = new FluentIcons.Avalonia.Fluent.SymbolIconSource
{
Symbol = FluentIcons.Common.Symbol.PuzzlePiece,
IconVariant = FluentIcons.Common.IconVariant.Regular
}
};
ToolTip.SetTip(navItem, $"{contribution.Plugin.Manifest.Name} - {contribution.Registration.Title}");
SettingsNavView.MenuItems.Insert(insertIndex++, navItem);
var pageHost = CreatePluginSettingsPageHost(contribution);
pageHost.IsVisible = false;
SettingsContentPagesHost.Children.Add(pageHost);
_pluginSettingsPageHosts[tag] = pageHost;
}
}
private static string BuildPluginSettingsTag(PluginSettingsPageContribution contribution)
{
return $"PluginPage:{contribution.Plugin.Manifest.Id}:{contribution.Registration.Id}";
}
private static string BuildPluginSettingsNavigationTitle(
PluginSettingsPageContribution contribution,
IReadOnlyDictionary<string, int> pageCountsByPluginId)
{
return pageCountsByPluginId.TryGetValue(contribution.Plugin.Manifest.Id, out var pageCount) && pageCount > 1
? $"{contribution.Plugin.Manifest.Name} - {contribution.Registration.Title}"
: contribution.Plugin.Manifest.Name;
}
private Control CreatePluginSettingsPageHost(PluginSettingsPageContribution contribution)
{
Control content;
try
{
content = contribution.Registration.ContentFactory();
}
catch (Exception ex)
{
content = CreatePluginPageErrorContent(ex);
}
return new StackPanel
{
Spacing = 16,
Children =
{
new TextBlock
{
Text = contribution.Registration.Title,
FontSize = 24,
FontWeight = FontWeight.SemiBold,
Foreground = GetThemeBrush("AdaptiveTextPrimaryBrush")
},
new TextBlock
{
Text = contribution.Plugin.Manifest.Name,
Foreground = GetThemeBrush("AdaptiveTextSecondaryBrush")
},
content
}
};
}
private static Control CreatePluginPageErrorContent(Exception exception)
{
return new Border
{
Background = new SolidColorBrush(Color.Parse("#332B0F16")),
BorderBrush = new SolidColorBrush(Color.Parse("#66F97316")),
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(16),
Padding = new Thickness(16),
Child = new TextBlock
{
Text = exception.Message,
TextWrapping = TextWrapping.Wrap
}
};
}
private void UpdatePluginSettingsPageVisibility(string? selectedTag)
{
foreach (var pair in _pluginSettingsPageHosts)
{
pair.Value.IsVisible = string.Equals(pair.Key, selectedTag, StringComparison.OrdinalIgnoreCase);
}
}
private string? GetSelectedSettingsTabTag()
{
return (SettingsNavView?.SelectedItem as NavigationViewItem)?.Tag?.ToString();
}
private int ResolveSelectedSettingsTabIndex()
{
if (SettingsNavView?.SelectedItem is null || SettingsNavView.MenuItems is null)
{
return 0;
}
for (var i = 0; i < SettingsNavView.MenuItems.Count; i++)
{
if (ReferenceEquals(SettingsNavView.MenuItems[i], SettingsNavView.SelectedItem))
{
return i;
}
}
return 0;
}
private void RestoreSettingsTabSelection(AppSettingsSnapshot snapshot)
{
if (SettingsNavView?.MenuItems is null || SettingsNavView.MenuItems.Count == 0)
{
return;
}
if (!string.IsNullOrWhiteSpace(snapshot.SettingsTabTag))
{
var taggedItem = SettingsNavView.MenuItems
.OfType<NavigationViewItem>()
.FirstOrDefault(item => string.Equals(item.Tag?.ToString(), snapshot.SettingsTabTag, StringComparison.OrdinalIgnoreCase));
if (taggedItem is not null)
{
SettingsNavView.SelectedItem = taggedItem;
return;
}
}
var safeIndex = Math.Clamp(snapshot.SettingsTabIndex, 0, Math.Max(0, SettingsNavView.MenuItems.Count - 1));
if (SettingsNavView.MenuItems[safeIndex] is NavigationViewItem navItem)
{
SettingsNavView.SelectedItem = navItem;
}
}
}

View File

@@ -141,7 +141,7 @@
Padding="0,0,16,0"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Grid>
<Grid x:Name="SettingsContentPagesHost">
<pages:WallpaperSettingsPage x:Name="WallpaperSettingsPanel" IsVisible="True" />
<pages:GridSettingsPage x:Name="GridSettingsPanel" IsVisible="False" />
<pages:ColorSettingsPage x:Name="ColorSettingsPanel" IsVisible="False" />

View File

@@ -93,7 +93,7 @@ public partial class SettingsWindow : Window
private readonly WindowsStartupService _windowsStartupService = new();
private readonly GitHubReleaseUpdateService _releaseUpdateService = new("wwiinnddyy", "LanMountainDesktop");
private readonly IWeatherDataService _weatherDataService = new XiaomiWeatherService();
private readonly ComponentRegistry _componentRegistry = ComponentRegistry.CreateDefault();
private readonly ComponentRegistry _componentRegistry;
private readonly WindowsStartMenuService _windowsStartMenuService = new();
private readonly LinuxDesktopEntryService _linuxDesktopEntryService = new();
private readonly FluentAvaloniaTheme? _fluentAvaloniaTheme;
@@ -158,7 +158,9 @@ public partial class SettingsWindow : Window
public SettingsWindow()
{
_componentRegistry = DesktopComponentRegistryFactory.Create((Application.Current as App)?.PluginRuntimeService);
InitializeComponent();
InitializePluginSettingsNavigation();
_fluentAvaloniaTheme = Application.Current?.Styles.OfType<FluentAvaloniaTheme>().FirstOrDefault();
RequestedThemeVariant = Application.Current?.RequestedThemeVariant ?? ThemeVariant.Default;
HookEvents();
@@ -278,6 +280,7 @@ public partial class SettingsWindow : Window
WindowTitleTextBlock.Text = L("settings.title", "Settings");
WindowSubtitleTextBlock.Text = L("settings.footer", "LanMountainDesktop Settings");
_defaultDesktopBackground = DesktopWallpaperLayer.Background;
RestoreSettingsTabSelection(snapshot);
UpdateSettingsTabContent();
UpdateWallpaperDisplay();
UpdateWallpaperPreviewLayout();