mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-22 00:54:26 +08:00
0.5.1
插件系统试验
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
193
LanMountainDesktop/Views/MainWindow.PluginSettings.cs
Normal file
193
LanMountainDesktop/Views/MainWindow.PluginSettings.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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")!;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
ClipToBounds="True"
|
||||
BorderThickness="0"
|
||||
PointerWheelChanged="OnDesktopPagesPointerWheelChanged">
|
||||
<Grid>
|
||||
<Grid x:Name="SettingsContentPagesHost">
|
||||
<Grid x:Name="DesktopPagesHost"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top">
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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="" 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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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")!;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
193
LanMountainDesktop/Views/SettingsWindow.PluginSettings.cs
Normal file
193
LanMountainDesktop/Views/SettingsWindow.PluginSettings.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user