mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
0.7.3.1
This commit is contained in:
@@ -101,6 +101,11 @@ public partial class App : Application
|
||||
|
||||
public App()
|
||||
{
|
||||
if (Design.IsDesignMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_settingsFacade.Settings.Changed += OnSettingsChanged;
|
||||
_appearanceThemeService.Changed += OnAppearanceThemeChanged;
|
||||
}
|
||||
@@ -108,9 +113,16 @@ public partial class App : Application
|
||||
public override void Initialize()
|
||||
{
|
||||
AppLogger.Info("App", "Initializing application resources.");
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
|
||||
if (Design.IsDesignMode)
|
||||
{
|
||||
ApplyDesignTimeTheme();
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigureWebViewUserDataFolder();
|
||||
AvaloniaWebViewBuilder.Initialize(default);
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
ApplyThemeFromSettings();
|
||||
ApplyCurrentCultureFromSettings();
|
||||
EnsureSettingsWindowService();
|
||||
@@ -119,6 +131,12 @@ public partial class App : Application
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (Design.IsDesignMode)
|
||||
{
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
return;
|
||||
}
|
||||
|
||||
AppLogger.Info("App", "Framework initialization completed.");
|
||||
RegisterUiUnhandledExceptionGuard();
|
||||
LinuxDesktopEntryInstaller.EnsureInstalled();
|
||||
@@ -127,6 +145,20 @@ public partial class App : Application
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
|
||||
private void ApplyDesignTimeTheme()
|
||||
{
|
||||
RequestedThemeVariant = ThemeVariant.Light;
|
||||
|
||||
try
|
||||
{
|
||||
ApplyAdaptiveThemeResources();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppLogger.Warn("Previewer", "Failed to apply adaptive theme resources in design mode.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeDesktopShell()
|
||||
{
|
||||
_desktopShellHost ??= new DesktopShellHost(
|
||||
|
||||
@@ -11,7 +11,7 @@ using LanMountainDesktop.Services.Settings;
|
||||
|
||||
namespace LanMountainDesktop;
|
||||
|
||||
sealed class Program
|
||||
public sealed class Program
|
||||
{
|
||||
internal static string StartupRenderMode { get; private set; } = AppRenderingModeHelper.Default;
|
||||
|
||||
@@ -67,7 +67,12 @@ sealed class Program
|
||||
}
|
||||
}
|
||||
|
||||
public static AppBuilder BuildAvaloniaApp(string renderMode = AppRenderingModeHelper.Default)
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
{
|
||||
return BuildAvaloniaApp(AppRenderingModeHelper.Default);
|
||||
}
|
||||
|
||||
public static AppBuilder BuildAvaloniaApp(string renderMode)
|
||||
{
|
||||
var builder = AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
|
||||
@@ -27,7 +27,8 @@ public sealed partial class WeatherSettingsPageViewModel : ViewModelBase
|
||||
ISettingsFacadeService settingsFacade,
|
||||
LocalizationService localizationService,
|
||||
ILocationService locationService,
|
||||
WeatherLocationRefreshService weatherLocationRefreshService)
|
||||
WeatherLocationRefreshService weatherLocationRefreshService,
|
||||
bool enableStartupPreviewRefresh = true)
|
||||
{
|
||||
_settingsFacade = settingsFacade ?? throw new ArgumentNullException(nameof(settingsFacade));
|
||||
_localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService));
|
||||
@@ -52,7 +53,10 @@ public sealed partial class WeatherSettingsPageViewModel : ViewModelBase
|
||||
? LocationReadyText
|
||||
: LocationUnsupportedText;
|
||||
|
||||
_ = RefreshPreviewAsync();
|
||||
if (enableStartupPreviewRefresh)
|
||||
{
|
||||
_ = RefreshPreviewAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<SelectionOption> LocationModes { get; }
|
||||
@@ -476,6 +480,65 @@ public sealed partial class WeatherSettingsPageViewModel : ViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
internal void ApplyDesignTimePreview()
|
||||
{
|
||||
_isInitializing = true;
|
||||
|
||||
var previewLocation = new WeatherLocation(
|
||||
"Shenzhen Nanshan",
|
||||
"101280601",
|
||||
22.5431,
|
||||
114.0579,
|
||||
"Guangdong, China");
|
||||
var alternateLocation = new WeatherLocation(
|
||||
"Shanghai Pudong",
|
||||
"101020600",
|
||||
31.2304,
|
||||
121.4737,
|
||||
"Shanghai, China");
|
||||
|
||||
SelectedLocationMode = LocationModes.FirstOrDefault(option =>
|
||||
string.Equals(option.Value, "CitySearch", StringComparison.OrdinalIgnoreCase))
|
||||
?? LocationModes[0];
|
||||
SearchKeyword = "shenzhen";
|
||||
SelectedSearchResult = previewLocation;
|
||||
|
||||
SearchResults.Clear();
|
||||
SearchResults.Add(previewLocation);
|
||||
SearchResults.Add(alternateLocation);
|
||||
|
||||
Latitude = previewLocation.Latitude;
|
||||
Longitude = previewLocation.Longitude;
|
||||
LocationKey = previewLocation.LocationKey;
|
||||
LocationName = previewLocation.Name;
|
||||
AutoRefreshLocation = true;
|
||||
ExcludedAlerts = "Heat\nThunderstorm";
|
||||
NoTlsRequests = false;
|
||||
IsLocationSupported = true;
|
||||
IsRefreshingLocation = false;
|
||||
IsRefreshingPreview = false;
|
||||
|
||||
_isInitializing = false;
|
||||
|
||||
UpdateModeVisibility();
|
||||
UpdateCurrentLocationSummary();
|
||||
|
||||
var preview = XiaomiWeatherVisualResolver.Resolve(
|
||||
"Partly cloudy",
|
||||
4,
|
||||
isNight: false,
|
||||
_languageCode);
|
||||
|
||||
SearchStatus = "2 sample locations are shown for design preview.";
|
||||
LocationActionStatus = "Using mocked Windows location support in design mode.";
|
||||
PreviewIcon = HyperOS3WeatherAssetLoader.LoadImage(preview.PrimaryIconAsset);
|
||||
PreviewLocation = previewLocation.Name;
|
||||
PreviewTemperature = "24 deg C";
|
||||
PreviewCondition = preview.DisplayText;
|
||||
PreviewUpdated = "Updated 09:42";
|
||||
PreviewStatus = "Preview data is mocked for Avalonia design mode.";
|
||||
}
|
||||
|
||||
private void RefreshLocalizedText()
|
||||
{
|
||||
PageTitle = L("settings.weather.title", "Weather");
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:fi="using:FluentIcons.Avalonia"
|
||||
xmlns:webview="clr-namespace:AvaloniaWebView;assembly=Avalonia.WebView"
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="480"
|
||||
d:DesignHeight="480"
|
||||
@@ -24,20 +23,27 @@
|
||||
BorderBrush="#22000000"
|
||||
BorderThickness="1">
|
||||
<Grid>
|
||||
<webview:WebView x:Name="BrowserWebView" />
|
||||
<Grid x:Name="WebViewPresenter" />
|
||||
<Border x:Name="UnavailableOverlay"
|
||||
IsVisible="False"
|
||||
Background="#CC0F172A"
|
||||
Padding="16">
|
||||
<TextBlock x:Name="UnavailableMessageTextBlock"
|
||||
Foreground="#F8FAFC"
|
||||
TextWrapping="Wrap"
|
||||
TextAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
MaxWidth="360"
|
||||
FontSize="13"
|
||||
Text="Browser runtime unavailable." />
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="10"
|
||||
MaxWidth="360">
|
||||
<fi:SymbolIcon Symbol="Desktop"
|
||||
FontSize="28"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="#F8FAFC" />
|
||||
<TextBlock x:Name="UnavailableMessageTextBlock"
|
||||
Foreground="#F8FAFC"
|
||||
TextWrapping="Wrap"
|
||||
TextAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
FontSize="13"
|
||||
Text="Browser runtime unavailable." />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@@ -17,6 +17,7 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
||||
{
|
||||
private static readonly Uri DefaultHomeUri = new("https://www.bing.com");
|
||||
|
||||
private readonly bool _isDesignModePreview = Design.IsDesignMode;
|
||||
private double _currentCellSize = 48;
|
||||
private string _componentId = BuiltInComponentIds.DesktopBrowser;
|
||||
private string _placementId = string.Empty;
|
||||
@@ -27,6 +28,7 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
||||
private bool _isEditMode;
|
||||
private bool _isWebViewActive = true;
|
||||
private bool _isWebViewFaulted;
|
||||
private WebView? _browserWebView;
|
||||
private readonly WebView2RuntimeAvailability _runtimeAvailability;
|
||||
private bool _isDisposed;
|
||||
|
||||
@@ -41,10 +43,15 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
||||
ApplyCellSize(_currentCellSize);
|
||||
ApplyTheme(force: true);
|
||||
|
||||
_runtimeAvailability = WebView2RuntimeProbe.GetAvailability();
|
||||
_runtimeAvailability = _isDesignModePreview
|
||||
? new WebView2RuntimeAvailability(
|
||||
IsAvailable: false,
|
||||
Version: null,
|
||||
Message: "WebView preview is disabled in Avalonia design mode.")
|
||||
: WebView2RuntimeProbe.GetAvailability();
|
||||
if (_runtimeAvailability.IsAvailable)
|
||||
{
|
||||
BrowserWebView.NavigationStarting += OnBrowserWebViewNavigationStarting;
|
||||
EnsureWebViewCreated();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -69,9 +76,9 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
||||
AttachedToVisualTree -= OnAttachedToVisualTree;
|
||||
DetachedFromVisualTree -= OnDetachedFromVisualTree;
|
||||
|
||||
if (_runtimeAvailability.IsAvailable)
|
||||
if (_browserWebView is not null)
|
||||
{
|
||||
BrowserWebView.NavigationStarting -= OnBrowserWebViewNavigationStarting;
|
||||
_browserWebView.NavigationStarting -= OnBrowserWebViewNavigationStarting;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,6 +307,13 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
||||
|
||||
private void UpdateWebViewActiveState()
|
||||
{
|
||||
if (_isDesignModePreview)
|
||||
{
|
||||
_isWebViewActive = false;
|
||||
ApplyRuntimeUnavailableState();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_runtimeAvailability.IsAvailable || _isWebViewFaulted)
|
||||
{
|
||||
_isWebViewActive = false;
|
||||
@@ -325,14 +339,21 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
||||
|
||||
private void ActivateWebView()
|
||||
{
|
||||
EnsureWebViewCreated();
|
||||
if (_isWebViewFaulted || !_runtimeAvailability.IsAvailable)
|
||||
{
|
||||
ApplyRuntimeUnavailableState();
|
||||
return;
|
||||
}
|
||||
|
||||
BrowserWebView.IsVisible = true;
|
||||
BrowserWebView.IsHitTestVisible = true;
|
||||
if (_browserWebView is null)
|
||||
{
|
||||
ApplyRuntimeUnavailableState();
|
||||
return;
|
||||
}
|
||||
|
||||
_browserWebView.IsVisible = true;
|
||||
_browserWebView.IsHitTestVisible = true;
|
||||
RefreshButton.IsEnabled = true;
|
||||
GoButton.IsEnabled = true;
|
||||
AddressTextBox.IsEnabled = true;
|
||||
@@ -341,8 +362,11 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
||||
|
||||
private void DeactivateWebView(bool clearUrl)
|
||||
{
|
||||
BrowserWebView.IsHitTestVisible = false;
|
||||
BrowserWebView.IsVisible = false;
|
||||
if (_browserWebView is not null)
|
||||
{
|
||||
_browserWebView.IsHitTestVisible = false;
|
||||
_browserWebView.IsVisible = false;
|
||||
}
|
||||
|
||||
if (clearUrl)
|
||||
{
|
||||
@@ -352,9 +376,14 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
||||
|
||||
private bool TryReloadWebView(string action)
|
||||
{
|
||||
if (_browserWebView is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
BrowserWebView.Reload();
|
||||
_browserWebView.Reload();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) when (!UiExceptionGuard.IsFatalException(ex))
|
||||
@@ -366,9 +395,14 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
||||
|
||||
private bool TryNavigate(Uri uri, string action)
|
||||
{
|
||||
if (_browserWebView is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
BrowserWebView.Url = uri;
|
||||
_browserWebView.Url = uri;
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) when (!UiExceptionGuard.IsFatalException(ex))
|
||||
@@ -380,9 +414,14 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
||||
|
||||
private void TryClearWebViewUrl()
|
||||
{
|
||||
if (_browserWebView is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
BrowserWebView.Url = null;
|
||||
_browserWebView.Url = null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -392,14 +431,20 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
||||
|
||||
private bool CanUseWebView()
|
||||
{
|
||||
return _runtimeAvailability.IsAvailable && !_isWebViewFaulted && _isWebViewActive;
|
||||
return _runtimeAvailability.IsAvailable &&
|
||||
!_isWebViewFaulted &&
|
||||
_isWebViewActive &&
|
||||
_browserWebView is not null;
|
||||
}
|
||||
|
||||
private void ApplyRuntimeUnavailableState()
|
||||
{
|
||||
_isWebViewActive = false;
|
||||
BrowserWebView.IsVisible = false;
|
||||
BrowserWebView.IsHitTestVisible = false;
|
||||
if (_browserWebView is not null)
|
||||
{
|
||||
_browserWebView.IsVisible = false;
|
||||
_browserWebView.IsHitTestVisible = false;
|
||||
}
|
||||
|
||||
RefreshButton.IsEnabled = false;
|
||||
GoButton.IsEnabled = false;
|
||||
@@ -414,6 +459,22 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
|
||||
UnavailableOverlay.IsVisible = true;
|
||||
}
|
||||
|
||||
private void EnsureWebViewCreated()
|
||||
{
|
||||
if (_browserWebView is not null || _isDesignModePreview || !_runtimeAvailability.IsAvailable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_browserWebView = new WebView
|
||||
{
|
||||
IsVisible = false,
|
||||
IsHitTestVisible = false
|
||||
};
|
||||
_browserWebView.NavigationStarting += OnBrowserWebViewNavigationStarting;
|
||||
WebViewPresenter.Children.Insert(0, _browserWebView);
|
||||
}
|
||||
|
||||
private void EnterFaultedState(string action, Exception ex)
|
||||
{
|
||||
_isWebViewFaulted = true;
|
||||
|
||||
@@ -44,6 +44,7 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
|
||||
Interval = TimeSpan.FromMinutes(30)
|
||||
};
|
||||
|
||||
private readonly bool _isDesignModePreview = Design.IsDesignMode;
|
||||
private LanMountainDesktop.PluginSdk.ISettingsService _appSettingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
|
||||
private IComponentInstanceSettingsStore _componentSettingsService = HostComponentSettingsStoreProvider.GetOrCreate();
|
||||
private readonly LocalizationService _localizationService = new();
|
||||
@@ -102,12 +103,19 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
|
||||
News2TitleTextBlock.FontFamily = MiSansFontFamily;
|
||||
StatusTextBlock.FontFamily = MiSansFontFamily;
|
||||
|
||||
SizeChanged += OnSizeChanged;
|
||||
ActualThemeVariantChanged += OnActualThemeVariantChanged;
|
||||
if (_isDesignModePreview)
|
||||
{
|
||||
ApplyCellSize(_currentCellSize);
|
||||
ApplyDesignTimePreview();
|
||||
return;
|
||||
}
|
||||
|
||||
_refreshTimer.Tick += OnRefreshTimerTick;
|
||||
RefreshButton.Click += OnRefreshButtonClick;
|
||||
AttachedToVisualTree += OnAttachedToVisualTree;
|
||||
DetachedFromVisualTree += OnDetachedFromVisualTree;
|
||||
SizeChanged += OnSizeChanged;
|
||||
ActualThemeVariantChanged += OnActualThemeVariantChanged;
|
||||
|
||||
ApplyCellSize(_currentCellSize);
|
||||
UpdateLanguageCode();
|
||||
@@ -226,6 +234,12 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
|
||||
|
||||
private async void OnRefreshButtonClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isDesignModePreview)
|
||||
{
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isRefreshing)
|
||||
{
|
||||
return;
|
||||
@@ -242,6 +256,12 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
|
||||
|
||||
private void OnNewsItem1PointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (_isDesignModePreview)
|
||||
{
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||
{
|
||||
return;
|
||||
@@ -253,6 +273,12 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
|
||||
|
||||
private void OnNewsItem2PointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (_isDesignModePreview)
|
||||
{
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||
{
|
||||
return;
|
||||
@@ -264,6 +290,12 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
|
||||
|
||||
private void OnExtraNewsItemPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (_isDesignModePreview)
|
||||
{
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed ||
|
||||
sender is not Control control ||
|
||||
control.Tag is not int index)
|
||||
@@ -408,6 +440,55 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
|
||||
UpdateAdaptiveLayout();
|
||||
}
|
||||
|
||||
private void ApplyDesignTimePreview()
|
||||
{
|
||||
_isNightVisual = ResolveNightMode();
|
||||
_activeNewsItems =
|
||||
[
|
||||
new DailyNewsItemSnapshot(
|
||||
"LanMountain preview mode now shows mocked widget content in Rider.",
|
||||
null,
|
||||
"https://example.com/news/preview-1",
|
||||
null,
|
||||
"09:30"),
|
||||
new DailyNewsItemSnapshot(
|
||||
"Weather, artwork, and plugin market cards render without live network calls.",
|
||||
null,
|
||||
"https://example.com/news/preview-2",
|
||||
null,
|
||||
"09:10"),
|
||||
new DailyNewsItemSnapshot(
|
||||
"Design-time mocks make isolated widget layout tuning much faster.",
|
||||
null,
|
||||
"https://example.com/news/preview-3",
|
||||
null,
|
||||
"08:55")
|
||||
];
|
||||
|
||||
_newsUrls.Clear();
|
||||
foreach (var item in _activeNewsItems)
|
||||
{
|
||||
_newsUrls.Add(item.Url);
|
||||
}
|
||||
|
||||
UpdateHotHeadlineText(_activeNewsItems[0].Title);
|
||||
News2TitleTextBlock.Text = NormalizeCompactText(_activeNewsItems[1].Title);
|
||||
StatusTextBlock.Text = string.Empty;
|
||||
StatusTextBlock.IsVisible = false;
|
||||
|
||||
SetNewsBitmap(0, null);
|
||||
SetNewsBitmap(1, null);
|
||||
RenderExtraNewsRows(_activeNewsItems.Skip(2).ToArray());
|
||||
UpdateNewsInteractionState();
|
||||
|
||||
RefreshButton.IsEnabled = false;
|
||||
RefreshButton.Opacity = 1.0;
|
||||
RefreshGlyphIcon.Opacity = 0.82;
|
||||
RefreshLabelTextBlock.Opacity = 0.82;
|
||||
|
||||
UpdateAdaptiveLayout();
|
||||
}
|
||||
|
||||
private int ResolveDesiredNewsItemCount()
|
||||
{
|
||||
return 2;
|
||||
|
||||
@@ -60,6 +60,7 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
|
||||
Interval = TimeSpan.FromHours(6)
|
||||
};
|
||||
|
||||
private readonly bool _isDesignModePreview = Design.IsDesignMode;
|
||||
private ISettingsService _settingsService = HostSettingsFacadeProvider.GetOrCreate().Settings;
|
||||
private readonly LocalizationService _localizationService = new();
|
||||
|
||||
@@ -85,10 +86,17 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
|
||||
ArtistTextBlock.FontFamily = MiSansFontFamily;
|
||||
YearTextBlock.FontFamily = MiSansFontFamily;
|
||||
|
||||
SizeChanged += OnSizeChanged;
|
||||
if (_isDesignModePreview)
|
||||
{
|
||||
ApplyCellSize(_currentCellSize);
|
||||
ApplyDesignTimePreview();
|
||||
return;
|
||||
}
|
||||
|
||||
_refreshTimer.Tick += OnRefreshTimerTick;
|
||||
AttachedToVisualTree += OnAttachedToVisualTree;
|
||||
DetachedFromVisualTree += OnDetachedFromVisualTree;
|
||||
SizeChanged += OnSizeChanged;
|
||||
|
||||
ApplyCellSize(_currentCellSize);
|
||||
UpdateLanguageCode();
|
||||
@@ -177,6 +185,11 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
|
||||
|
||||
private void OnArtworkPanelPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (_isDesignModePreview)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||
{
|
||||
return;
|
||||
@@ -188,6 +201,11 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
|
||||
|
||||
private void OnInfoPanelPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (_isDesignModePreview)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||
{
|
||||
return;
|
||||
@@ -420,6 +438,36 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
|
||||
UpdateAdaptiveLayout();
|
||||
}
|
||||
|
||||
private void ApplyDesignTimePreview()
|
||||
{
|
||||
DisposeArtworkBitmap();
|
||||
_currentArtworkSourceUrl = null;
|
||||
_currentArtworkImageUrl = null;
|
||||
|
||||
RootBorder.Background = new SolidColorBrush(Color.Parse("#C6B08B"));
|
||||
ArtworkPanel.Background = new LinearGradientBrush
|
||||
{
|
||||
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
|
||||
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
|
||||
GradientStops = new GradientStops
|
||||
{
|
||||
new GradientStop(Color.Parse("#AA8B69"), 0),
|
||||
new GradientStop(Color.Parse("#5F4B3B"), 1)
|
||||
}
|
||||
};
|
||||
InfoPanel.Background = new SolidColorBrush(Color.Parse("#15181D"));
|
||||
|
||||
DateTextBlock.Text = "03/22";
|
||||
WeekdayTextBlock.Text = "Sunday";
|
||||
PaintingTitleTextBlock.Text = BuildQuotedTitle("The Starry Night");
|
||||
ArtistTextBlock.Text = NormalizeCompactText("Vincent van Gogh");
|
||||
YearTextBlock.Text = "1889 | MoMA";
|
||||
StatusTextBlock.IsVisible = false;
|
||||
StatusTextBlock.Text = string.Empty;
|
||||
|
||||
UpdateAdaptiveLayout();
|
||||
}
|
||||
|
||||
private void UpdateAdaptiveLayout()
|
||||
{
|
||||
var scale = ResolveScale();
|
||||
|
||||
@@ -31,6 +31,7 @@ public partial class DailyWordWidget : UserControl, IDesktopComponentWidget, IRe
|
||||
Interval = TimeSpan.FromHours(6)
|
||||
};
|
||||
|
||||
private readonly bool _isDesignModePreview = Design.IsDesignMode;
|
||||
private LanMountainDesktop.PluginSdk.ISettingsService _appSettingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
|
||||
private IComponentInstanceSettingsStore _componentSettingsService = HostComponentSettingsStoreProvider.GetOrCreate();
|
||||
private readonly LocalizationService _localizationService = new();
|
||||
@@ -55,12 +56,19 @@ public partial class DailyWordWidget : UserControl, IDesktopComponentWidget, IRe
|
||||
ExampleTranslationTextBlock.FontFamily = MiSansFontFamily;
|
||||
StatusTextBlock.FontFamily = MiSansFontFamily;
|
||||
|
||||
SizeChanged += OnSizeChanged;
|
||||
ActualThemeVariantChanged += OnActualThemeVariantChanged;
|
||||
if (_isDesignModePreview)
|
||||
{
|
||||
ApplyCellSize(_currentCellSize);
|
||||
ApplyDesignTimePreview();
|
||||
return;
|
||||
}
|
||||
|
||||
_refreshTimer.Tick += OnRefreshTimerTick;
|
||||
RefreshButton.Click += OnRefreshButtonClick;
|
||||
AttachedToVisualTree += OnAttachedToVisualTree;
|
||||
DetachedFromVisualTree += OnDetachedFromVisualTree;
|
||||
SizeChanged += OnSizeChanged;
|
||||
ActualThemeVariantChanged += OnActualThemeVariantChanged;
|
||||
|
||||
ApplyCellSize(_currentCellSize);
|
||||
UpdateLanguageCode();
|
||||
@@ -175,6 +183,12 @@ public partial class DailyWordWidget : UserControl, IDesktopComponentWidget, IRe
|
||||
|
||||
private async void OnRefreshButtonClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isDesignModePreview)
|
||||
{
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isRefreshing)
|
||||
{
|
||||
return;
|
||||
@@ -284,6 +298,26 @@ public partial class DailyWordWidget : UserControl, IDesktopComponentWidget, IRe
|
||||
UpdateAdaptiveLayout();
|
||||
}
|
||||
|
||||
private void ApplyDesignTimePreview()
|
||||
{
|
||||
_isNightVisual = ResolveNightMode();
|
||||
ApplyNightModeVisual();
|
||||
|
||||
WordTextBlock.Text = "serendipity";
|
||||
PronunciationTextBlock.Text = "UK /,seren'dipiti/ | US /,seren'dipiti/";
|
||||
MeaningTextBlock.Text = "n. finding something valuable by accident; a pleasant surprise.";
|
||||
ExampleTextBlock.Text = "The widget preview became useful by pure serendipity.";
|
||||
ExampleTranslationTextBlock.Text = "A mocked sample sentence shown only in design mode.";
|
||||
StatusTextBlock.Text = string.Empty;
|
||||
StatusTextBlock.IsVisible = false;
|
||||
|
||||
RefreshButton.IsEnabled = false;
|
||||
RefreshButton.Opacity = 1.0;
|
||||
RefreshIcon.Opacity = 0.82;
|
||||
|
||||
UpdateAdaptiveLayout();
|
||||
}
|
||||
|
||||
private void UpdateAdaptiveLayout()
|
||||
{
|
||||
var scale = ResolveScale();
|
||||
|
||||
@@ -95,6 +95,7 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesk
|
||||
Interval = FluttermotionToken.WeatherAnimationFrameInterval
|
||||
};
|
||||
|
||||
private readonly bool _isDesignModePreview = Design.IsDesignMode;
|
||||
private LanMountainDesktop.PluginSdk.ISettingsService _settingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
|
||||
private IComponentInstanceSettingsStore _componentSettingsStore = HostComponentSettingsStoreProvider.GetOrCreate();
|
||||
private readonly LocalizationService _localizationService = new();
|
||||
@@ -128,11 +129,19 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesk
|
||||
InitializeComponent();
|
||||
InitializeMotionTransform();
|
||||
|
||||
SizeChanged += OnSizeChanged;
|
||||
if (_isDesignModePreview)
|
||||
{
|
||||
InitializeParticleVisuals();
|
||||
ApplyCellSize(_currentCellSize);
|
||||
ApplyDesignTimePreview();
|
||||
return;
|
||||
}
|
||||
|
||||
_refreshTimer.Tick += OnRefreshTimerTick;
|
||||
_backgroundAnimationTimer.Tick += OnBackgroundAnimationTick;
|
||||
AttachedToVisualTree += OnAttachedToVisualTree;
|
||||
DetachedFromVisualTree += OnDetachedFromVisualTree;
|
||||
SizeChanged += OnSizeChanged;
|
||||
|
||||
InitializeParticleVisuals();
|
||||
ApplyVisualTheme(WeatherVisualKind.ClearDay);
|
||||
@@ -512,6 +521,29 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesk
|
||||
_latestSnapshot = null;
|
||||
}
|
||||
|
||||
private void ApplyDesignTimePreview()
|
||||
{
|
||||
const WeatherVisualKind previewVisualKind = WeatherVisualKind.PartlyCloudyDay;
|
||||
|
||||
_languageCode = "en-US";
|
||||
_latestSnapshot = null;
|
||||
|
||||
ApplyVisualTheme(previewVisualKind);
|
||||
SetWeatherIcon(
|
||||
HyperOS3WeatherTheme.ResolveHeroIconAsset(HyperOS3WeatherVisualKind.PartlyCloudyDay),
|
||||
previewVisualKind);
|
||||
SetLoadingSkeleton(false);
|
||||
|
||||
CityTextBlock.Text = "Shenzhen Bay";
|
||||
ConditionTextBlock.Text = "Partly cloudy";
|
||||
TemperatureTextBlock.Text = "24°";
|
||||
RangeTextBlock.Text = "28°/20°";
|
||||
|
||||
ResetAnimationState();
|
||||
ResetParticles();
|
||||
ApplyAdaptiveTypography();
|
||||
}
|
||||
|
||||
private void ApplyVisualTheme(WeatherVisualKind kind)
|
||||
{
|
||||
_activeVisualKind = kind;
|
||||
|
||||
@@ -160,7 +160,9 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
var pluginRuntimeService = (Application.Current as App)?.PluginRuntimeService;
|
||||
var pluginRuntimeService = Design.IsDesignMode
|
||||
? null
|
||||
: (Application.Current as App)?.PluginRuntimeService;
|
||||
_componentRegistry = DesktopComponentRegistryFactory.Create(pluginRuntimeService);
|
||||
_settingsService = _settingsFacade.Settings;
|
||||
_gridSettingsService = _settingsFacade.Grid;
|
||||
@@ -173,7 +175,6 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
||||
|
||||
InitializeComponent();
|
||||
Icon = _appLogoService.CreateWindowIcon();
|
||||
InitializeTaskbarProfileFlyout();
|
||||
_componentRuntimeRegistry = DesktopComponentRegistryFactory.CreateRuntimeRegistry(
|
||||
_componentRegistry,
|
||||
pluginRuntimeService,
|
||||
@@ -183,6 +184,14 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
||||
pluginRuntimeService);
|
||||
_componentLibraryService = new ComponentLibraryService(_componentRegistry, _componentRuntimeRegistry);
|
||||
_componentEditorWindowService = new ComponentEditorWindowService(_settingsFacade);
|
||||
|
||||
if (Design.IsDesignMode)
|
||||
{
|
||||
ApplyDesignTimePreview();
|
||||
return;
|
||||
}
|
||||
|
||||
InitializeTaskbarProfileFlyout();
|
||||
_fluentAvaloniaTheme = Application.Current?.Styles.OfType<FluentAvaloniaTheme>().FirstOrDefault();
|
||||
_settingsService.Changed += OnSettingsChanged;
|
||||
_appearanceThemeService.Changed += OnAppearanceThemeChanged;
|
||||
@@ -196,6 +205,170 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyDesignTimePreview()
|
||||
{
|
||||
Title = "LanMountainDesktop Preview";
|
||||
ShowInTaskbar = false;
|
||||
DesktopWallpaperLayer.Background = new LinearGradientBrush
|
||||
{
|
||||
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
|
||||
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
|
||||
GradientStops = new GradientStops
|
||||
{
|
||||
new GradientStop(Color.Parse("#FFF6F8FB"), 0d),
|
||||
new GradientStop(Color.Parse("#FFE9EEF7"), 0.55d),
|
||||
new GradientStop(Color.Parse("#FFDCE5F3"), 1d)
|
||||
}
|
||||
};
|
||||
DesktopWallpaperImageLayer.IsVisible = false;
|
||||
LauncherPagePanel.IsVisible = false;
|
||||
ComponentLibraryWindow.IsVisible = false;
|
||||
|
||||
BackToWindowsTextBlock.Text = "Back to Windows";
|
||||
ComponentLibraryTitleTextBlock.Text = "Widgets";
|
||||
ComponentLibraryBackTextBlock.Text = "Back";
|
||||
TaskbarProfileDisplayNameTextBlock.Text = "Preview User";
|
||||
TaskbarProfileSettingsActionTextBlock.Text = "Settings";
|
||||
TaskbarProfileDesktopEditActionTextBlock.Text = "Edit Desktop";
|
||||
TaskbarProfileAvatarFallbackText.Text = "P";
|
||||
TaskbarProfileHeaderAvatarFallbackText.Text = "P";
|
||||
TaskbarProfileButton.IsEnabled = false;
|
||||
TaskbarProfilePopup.IsOpen = false;
|
||||
|
||||
ClockWidget.IsVisible = true;
|
||||
ClockWidget.SetDisplayFormat(ClockDisplayFormat.HourMinute);
|
||||
ClockWidget.SetTransparentBackground(false);
|
||||
|
||||
ConfigureDesignTimeDesktopGrid();
|
||||
PopulateDesignTimeDesktopSurface();
|
||||
}
|
||||
|
||||
private void ConfigureDesignTimeDesktopGrid()
|
||||
{
|
||||
const int previewRows = 7;
|
||||
const int previewColumns = 12;
|
||||
|
||||
DesktopGrid.RowDefinitions.Clear();
|
||||
DesktopGrid.ColumnDefinitions.Clear();
|
||||
|
||||
for (var row = 0; row < previewRows; row++)
|
||||
{
|
||||
DesktopGrid.RowDefinitions.Add(new RowDefinition(new GridLength(1, GridUnitType.Star)));
|
||||
}
|
||||
|
||||
for (var column = 0; column < previewColumns; column++)
|
||||
{
|
||||
DesktopGrid.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(1, GridUnitType.Star)));
|
||||
}
|
||||
|
||||
DesktopGrid.Margin = new Thickness(28);
|
||||
DesktopGrid.RowSpacing = 14;
|
||||
DesktopGrid.ColumnSpacing = 14;
|
||||
DesktopGrid.Width = double.NaN;
|
||||
DesktopGrid.Height = double.NaN;
|
||||
|
||||
Grid.SetRow(TopStatusBarHost, 0);
|
||||
Grid.SetColumn(TopStatusBarHost, 0);
|
||||
Grid.SetRowSpan(TopStatusBarHost, 1);
|
||||
Grid.SetColumnSpan(TopStatusBarHost, previewColumns);
|
||||
|
||||
Grid.SetRow(DesktopPagesViewport, 1);
|
||||
Grid.SetColumn(DesktopPagesViewport, 0);
|
||||
Grid.SetRowSpan(DesktopPagesViewport, previewRows - 2);
|
||||
Grid.SetColumnSpan(DesktopPagesViewport, previewColumns);
|
||||
|
||||
Grid.SetRow(BottomTaskbarContainer, previewRows - 1);
|
||||
Grid.SetColumn(BottomTaskbarContainer, 0);
|
||||
Grid.SetRowSpan(BottomTaskbarContainer, 1);
|
||||
Grid.SetColumnSpan(BottomTaskbarContainer, previewColumns);
|
||||
|
||||
DesktopPagesHost.ColumnDefinitions.Clear();
|
||||
DesktopPagesHost.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(1, GridUnitType.Star)));
|
||||
|
||||
ClockWidget.ApplyCellSize(72);
|
||||
}
|
||||
|
||||
private void PopulateDesignTimeDesktopSurface()
|
||||
{
|
||||
DesktopPagesContainer.Children.Clear();
|
||||
DesktopPagesContainer.Width = double.NaN;
|
||||
DesktopPagesContainer.Height = double.NaN;
|
||||
|
||||
DesktopPagesContainer.Children.Add(CreateDesignTimePreviewCard(
|
||||
"Focus Clock",
|
||||
"Compact widget preview",
|
||||
32,
|
||||
32,
|
||||
300,
|
||||
170,
|
||||
"#FFFFFFFF",
|
||||
"#FFE8EEF8"));
|
||||
DesktopPagesContainer.Children.Add(CreateDesignTimePreviewCard(
|
||||
"Weather",
|
||||
"26°C Qingdao",
|
||||
360,
|
||||
86,
|
||||
260,
|
||||
132,
|
||||
"#FFF8FBFF",
|
||||
"#FFDDE8F6"));
|
||||
DesktopPagesContainer.Children.Add(CreateDesignTimePreviewCard(
|
||||
"Study Session",
|
||||
"Deep work · 48 min",
|
||||
210,
|
||||
248,
|
||||
340,
|
||||
144,
|
||||
"#FFFDFEFF",
|
||||
"#FFE7EEF7"));
|
||||
}
|
||||
|
||||
private static Border CreateDesignTimePreviewCard(
|
||||
string title,
|
||||
string subtitle,
|
||||
double left,
|
||||
double top,
|
||||
double width,
|
||||
double height,
|
||||
string backgroundColor,
|
||||
string borderColor)
|
||||
{
|
||||
return new Border
|
||||
{
|
||||
Width = width,
|
||||
Height = height,
|
||||
Margin = new Thickness(left, top, 0, 0),
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
VerticalAlignment = VerticalAlignment.Top,
|
||||
Background = new SolidColorBrush(Color.Parse(backgroundColor)),
|
||||
BorderBrush = new SolidColorBrush(Color.Parse(borderColor)),
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(28),
|
||||
Child = new StackPanel
|
||||
{
|
||||
Margin = new Thickness(20),
|
||||
Spacing = 8,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Children =
|
||||
{
|
||||
new TextBlock
|
||||
{
|
||||
Text = title,
|
||||
FontSize = 20,
|
||||
FontWeight = FontWeight.SemiBold,
|
||||
Foreground = new SolidColorBrush(Color.Parse("#FF1E293B"))
|
||||
},
|
||||
new TextBlock
|
||||
{
|
||||
Text = subtitle,
|
||||
FontSize = 13,
|
||||
Foreground = new SolidColorBrush(Color.Parse("#FF64748B"))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void OnNightModeIsCheckedChanged(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not ToggleButton toggleButton)
|
||||
@@ -231,6 +404,14 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
||||
protected override void OnOpened(EventArgs e)
|
||||
{
|
||||
base.OnOpened(e);
|
||||
|
||||
if (Design.IsDesignMode)
|
||||
{
|
||||
ConfigureDesignTimeDesktopGrid();
|
||||
PopulateDesignTimeDesktopSurface();
|
||||
return;
|
||||
}
|
||||
|
||||
SyncSettingsWindowState();
|
||||
|
||||
_suppressSettingsPersistence = true;
|
||||
@@ -307,6 +488,12 @@ public partial class MainWindow : Window, ISettingsWindowAnchorProvider
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
if (Design.IsDesignMode)
|
||||
{
|
||||
base.OnClosed(e);
|
||||
return;
|
||||
}
|
||||
|
||||
var wasVisible = IsVisible;
|
||||
var windowState = WindowState.ToString();
|
||||
|
||||
|
||||
@@ -12,12 +12,7 @@ namespace LanMountainDesktop.Views.SettingsPages;
|
||||
public partial class GeneratedPluginSettingsPage : SettingsPageBase
|
||||
{
|
||||
public GeneratedPluginSettingsPage()
|
||||
: this(
|
||||
new PluginGeneratedSettingsPageViewModel(
|
||||
HostSettingsFacadeProvider.GetOrCreate().Settings,
|
||||
string.Empty,
|
||||
new PluginSettingsSectionRegistration("_preview", "preview", []),
|
||||
new PluginLocalizer(AppContext.BaseDirectory, "en-US")))
|
||||
: this(Design.IsDesignMode ? CreateDesignTimeViewModel() : CreateDefaultViewModel())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -223,4 +218,272 @@ public partial class GeneratedPluginSettingsPage : SettingsPageBase
|
||||
|
||||
return textBox;
|
||||
}
|
||||
|
||||
private static PluginGeneratedSettingsPageViewModel CreateDefaultViewModel()
|
||||
{
|
||||
return new PluginGeneratedSettingsPageViewModel(
|
||||
HostSettingsFacadeProvider.GetOrCreate().Settings,
|
||||
string.Empty,
|
||||
new PluginSettingsSectionRegistration("_preview", "preview", []),
|
||||
new PluginLocalizer(AppContext.BaseDirectory, "en-US"));
|
||||
}
|
||||
|
||||
private static PluginGeneratedSettingsPageViewModel CreateDesignTimeViewModel()
|
||||
{
|
||||
const string pluginId = "preview.plugin";
|
||||
var settingsService = new DesignTimeSettingsService();
|
||||
var section = new PluginSettingsSectionRegistration(
|
||||
"desktop_preview",
|
||||
"Preview Widget Settings",
|
||||
[
|
||||
new SettingsOptionDefinition(
|
||||
"enable_glow",
|
||||
SettingsOptionType.Toggle,
|
||||
"Enable glow",
|
||||
"Adds a soft highlight around the preview widget.",
|
||||
true),
|
||||
new SettingsOptionDefinition(
|
||||
"refresh_minutes",
|
||||
SettingsOptionType.Number,
|
||||
"Refresh interval",
|
||||
"How often the plugin refreshes its cached content.",
|
||||
30d,
|
||||
minimum: 5d,
|
||||
maximum: 120d),
|
||||
new SettingsOptionDefinition(
|
||||
"layout_density",
|
||||
SettingsOptionType.Select,
|
||||
"Layout density",
|
||||
"Choose how compact the widget layout should feel.",
|
||||
"balanced",
|
||||
[
|
||||
new SettingsOptionChoice("compact", "Compact"),
|
||||
new SettingsOptionChoice("balanced", "Balanced"),
|
||||
new SettingsOptionChoice("comfortable", "Comfortable")
|
||||
]),
|
||||
new SettingsOptionDefinition(
|
||||
"content_path",
|
||||
SettingsOptionType.Path,
|
||||
"Content folder",
|
||||
"Local folder used by the plugin for mock assets.",
|
||||
@"C:\Preview\PluginAssets"),
|
||||
new SettingsOptionDefinition(
|
||||
"keywords",
|
||||
SettingsOptionType.List,
|
||||
"Pinned keywords",
|
||||
"Comma-separated topics that will be emphasized in the widget.",
|
||||
new[] { "avalonia", "preview", "design-time" })
|
||||
],
|
||||
"Mock plugin settings shown only in Avalonia design mode.");
|
||||
|
||||
settingsService.SetValue(
|
||||
SettingsScope.Plugin,
|
||||
"enable_glow",
|
||||
true,
|
||||
pluginId,
|
||||
sectionId: section.Id);
|
||||
settingsService.SetValue(
|
||||
SettingsScope.Plugin,
|
||||
"refresh_minutes",
|
||||
30d,
|
||||
pluginId,
|
||||
sectionId: section.Id);
|
||||
settingsService.SetValue(
|
||||
SettingsScope.Plugin,
|
||||
"layout_density",
|
||||
"balanced",
|
||||
pluginId,
|
||||
sectionId: section.Id);
|
||||
settingsService.SetValue(
|
||||
SettingsScope.Plugin,
|
||||
"content_path",
|
||||
@"C:\Preview\PluginAssets",
|
||||
pluginId,
|
||||
sectionId: section.Id);
|
||||
settingsService.SetValue(
|
||||
SettingsScope.Plugin,
|
||||
"keywords",
|
||||
new[] { "avalonia", "preview", "design-time" },
|
||||
pluginId,
|
||||
sectionId: section.Id);
|
||||
|
||||
return new PluginGeneratedSettingsPageViewModel(
|
||||
settingsService,
|
||||
pluginId,
|
||||
section,
|
||||
new PluginLocalizer(AppContext.BaseDirectory, "en-US"));
|
||||
}
|
||||
|
||||
private sealed class DesignTimeSettingsService : ISettingsService
|
||||
{
|
||||
private readonly Dictionary<string, object?> _values = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public event EventHandler<SettingsChangedEvent>? Changed;
|
||||
|
||||
public T LoadSnapshot<T>(SettingsScope scope, string? subjectId = null, string? placementId = null) where T : new()
|
||||
=> new();
|
||||
|
||||
public void SaveSnapshot<T>(
|
||||
SettingsScope scope,
|
||||
T snapshot,
|
||||
string? subjectId = null,
|
||||
string? placementId = null,
|
||||
string? sectionId = null,
|
||||
IReadOnlyCollection<string>? changedKeys = null)
|
||||
{
|
||||
RaiseChanged(scope, subjectId, placementId, sectionId, changedKeys);
|
||||
}
|
||||
|
||||
public T LoadSection<T>(
|
||||
SettingsScope scope,
|
||||
string subjectId,
|
||||
string sectionId,
|
||||
string? placementId = null) where T : new()
|
||||
=> new();
|
||||
|
||||
public void SaveSection<T>(
|
||||
SettingsScope scope,
|
||||
string subjectId,
|
||||
string sectionId,
|
||||
T section,
|
||||
string? placementId = null,
|
||||
IReadOnlyCollection<string>? changedKeys = null)
|
||||
{
|
||||
RaiseChanged(scope, subjectId, placementId, sectionId, changedKeys);
|
||||
}
|
||||
|
||||
public void DeleteSection(
|
||||
SettingsScope scope,
|
||||
string subjectId,
|
||||
string sectionId,
|
||||
string? placementId = null)
|
||||
{
|
||||
var prefix = BuildStorageKey(scope, subjectId, placementId, sectionId, key: null);
|
||||
foreach (var existingKey in _values.Keys.Where(key => key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToArray())
|
||||
{
|
||||
_values.Remove(existingKey);
|
||||
}
|
||||
|
||||
RaiseChanged(scope, subjectId, placementId, sectionId, changedKeys: null);
|
||||
}
|
||||
|
||||
public T? GetValue<T>(
|
||||
SettingsScope scope,
|
||||
string key,
|
||||
string? subjectId = null,
|
||||
string? placementId = null,
|
||||
string? sectionId = null)
|
||||
{
|
||||
return _values.TryGetValue(BuildStorageKey(scope, subjectId, placementId, sectionId, key), out var value)
|
||||
? ConvertValue<T>(value)
|
||||
: default;
|
||||
}
|
||||
|
||||
public void SetValue<T>(
|
||||
SettingsScope scope,
|
||||
string key,
|
||||
T value,
|
||||
string? subjectId = null,
|
||||
string? placementId = null,
|
||||
string? sectionId = null,
|
||||
IReadOnlyCollection<string>? changedKeys = null)
|
||||
{
|
||||
_values[BuildStorageKey(scope, subjectId, placementId, sectionId, key)] = value;
|
||||
RaiseChanged(scope, subjectId, placementId, sectionId, changedKeys ?? [key]);
|
||||
}
|
||||
|
||||
public IComponentSettingsAccessor GetComponentAccessor(string componentId, string? placementId)
|
||||
{
|
||||
return new DesignTimeComponentSettingsAccessor(this, componentId, placementId);
|
||||
}
|
||||
|
||||
private static T? ConvertValue<T>(object? value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
if (value is T typedValue)
|
||||
{
|
||||
return typedValue;
|
||||
}
|
||||
|
||||
var targetType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
|
||||
try
|
||||
{
|
||||
return (T?)Convert.ChangeType(value, targetType);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildStorageKey(
|
||||
SettingsScope scope,
|
||||
string? subjectId,
|
||||
string? placementId,
|
||||
string? sectionId,
|
||||
string? key)
|
||||
{
|
||||
return string.Join(
|
||||
"|",
|
||||
scope,
|
||||
subjectId ?? string.Empty,
|
||||
placementId ?? string.Empty,
|
||||
sectionId ?? string.Empty,
|
||||
key ?? string.Empty);
|
||||
}
|
||||
|
||||
private void RaiseChanged(
|
||||
SettingsScope scope,
|
||||
string? subjectId,
|
||||
string? placementId,
|
||||
string? sectionId,
|
||||
IReadOnlyCollection<string>? changedKeys)
|
||||
{
|
||||
Changed?.Invoke(this, new SettingsChangedEvent(scope, subjectId, placementId, sectionId, changedKeys));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DesignTimeComponentSettingsAccessor : IComponentSettingsAccessor
|
||||
{
|
||||
private readonly DesignTimeSettingsService _settingsService;
|
||||
|
||||
public DesignTimeComponentSettingsAccessor(
|
||||
DesignTimeSettingsService settingsService,
|
||||
string componentId,
|
||||
string? placementId)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
ComponentId = componentId;
|
||||
PlacementId = placementId;
|
||||
}
|
||||
|
||||
public string ComponentId { get; }
|
||||
|
||||
public string? PlacementId { get; }
|
||||
|
||||
public T LoadSnapshot<T>() where T : new()
|
||||
=> _settingsService.LoadSnapshot<T>(SettingsScope.ComponentInstance, ComponentId, PlacementId);
|
||||
|
||||
public void SaveSnapshot<T>(T snapshot, IReadOnlyCollection<string>? changedKeys = null)
|
||||
=> _settingsService.SaveSnapshot(SettingsScope.ComponentInstance, snapshot, ComponentId, PlacementId, changedKeys: changedKeys);
|
||||
|
||||
public T LoadSection<T>(string sectionId) where T : new()
|
||||
=> _settingsService.LoadSection<T>(SettingsScope.ComponentInstance, ComponentId, sectionId, PlacementId);
|
||||
|
||||
public void SaveSection<T>(string sectionId, T section, IReadOnlyCollection<string>? changedKeys = null)
|
||||
=> _settingsService.SaveSection(SettingsScope.ComponentInstance, ComponentId, sectionId, section, PlacementId, changedKeys);
|
||||
|
||||
public void DeleteSection(string sectionId)
|
||||
=> _settingsService.DeleteSection(SettingsScope.ComponentInstance, ComponentId, sectionId, PlacementId);
|
||||
|
||||
public T? GetValue<T>(string key)
|
||||
=> _settingsService.GetValue<T>(SettingsScope.ComponentInstance, key, ComponentId, PlacementId);
|
||||
|
||||
public void SetValue<T>(string key, T value, IReadOnlyCollection<string>? changedKeys = null)
|
||||
=> _settingsService.SetValue(SettingsScope.ComponentInstance, key, value, ComponentId, PlacementId, changedKeys: changedKeys);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
using Avalonia.Controls;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
using LanMountainDesktop.Services;
|
||||
using LanMountainDesktop.Services.PluginMarket;
|
||||
@@ -17,7 +19,7 @@ namespace LanMountainDesktop.Views.SettingsPages;
|
||||
public partial class PluginMarketSettingsPage : SettingsPageBase
|
||||
{
|
||||
public PluginMarketSettingsPage()
|
||||
: this(CreateDefaultViewModel())
|
||||
: this(Design.IsDesignMode ? CreateDesignTimeViewModel() : CreateDefaultViewModel())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -34,6 +36,11 @@ public partial class PluginMarketSettingsPage : SettingsPageBase
|
||||
|
||||
public override async void OnNavigatedTo(object? parameter)
|
||||
{
|
||||
if (Design.IsDesignMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ViewModel.InitializeAsync();
|
||||
}
|
||||
|
||||
@@ -48,6 +55,113 @@ public partial class PluginMarketSettingsPage : SettingsPageBase
|
||||
new AirAppMarketReadmeService());
|
||||
}
|
||||
|
||||
private static PluginMarketSettingsPageViewModel CreateDesignTimeViewModel()
|
||||
{
|
||||
var settingsFacade = HostSettingsFacadeProvider.GetOrCreate();
|
||||
var localizationService = new LocalizationService();
|
||||
var viewModel = new PluginMarketSettingsPageViewModel(
|
||||
settingsFacade,
|
||||
localizationService,
|
||||
new AirAppMarketIconService(),
|
||||
new AirAppMarketReadmeService());
|
||||
|
||||
var previewHostVersion = new Version(1, 2, 0);
|
||||
var items = new[]
|
||||
{
|
||||
CreateMarketItem(
|
||||
new PluginMarketPluginInfo(
|
||||
"news-tiles",
|
||||
"News Tiles",
|
||||
"Brings editorial news cards and ticker rows to the desktop.",
|
||||
"LanMountain Labs",
|
||||
"1.2.0",
|
||||
"1.0.0",
|
||||
"1.0.0",
|
||||
"https://example.com/news-tiles.zip",
|
||||
"v1.2.0",
|
||||
"news-tiles.zip",
|
||||
string.Empty,
|
||||
"https://example.com/news-tiles/readme",
|
||||
"https://example.com/news-tiles",
|
||||
"https://example.com/news-tiles/repo",
|
||||
["news", "widgets"],
|
||||
[],
|
||||
DateTimeOffset.Now.AddDays(-8),
|
||||
DateTimeOffset.Now.AddDays(-2)),
|
||||
localizationService,
|
||||
installedPlugin: null,
|
||||
previewHostVersion),
|
||||
CreateMarketItem(
|
||||
new PluginMarketPluginInfo(
|
||||
"workspace-pulse",
|
||||
"Workspace Pulse",
|
||||
"Tracks active projects and shows a compact productivity summary.",
|
||||
"Studio North",
|
||||
"2.4.0",
|
||||
"1.0.0",
|
||||
"1.0.0",
|
||||
"https://example.com/workspace-pulse.zip",
|
||||
"v2.4.0",
|
||||
"workspace-pulse.zip",
|
||||
string.Empty,
|
||||
"https://example.com/workspace-pulse/readme",
|
||||
"https://example.com/workspace-pulse",
|
||||
"https://example.com/workspace-pulse/repo",
|
||||
["dashboard", "productivity"],
|
||||
[],
|
||||
DateTimeOffset.Now.AddDays(-30),
|
||||
DateTimeOffset.Now.AddDays(-1)),
|
||||
localizationService,
|
||||
new InstalledPluginInfo(
|
||||
new PluginManifest(
|
||||
"workspace-pulse",
|
||||
"Workspace Pulse",
|
||||
"WorkspacePulse.dll",
|
||||
"Tracks active projects and shows a compact productivity summary.",
|
||||
"Studio North",
|
||||
"2.1.0"),
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
null),
|
||||
previewHostVersion),
|
||||
CreateMarketItem(
|
||||
new PluginMarketPluginInfo(
|
||||
"glass-panels",
|
||||
"Glass Panels",
|
||||
"Adds experimental acrylic surfaces for plugin-powered widgets.",
|
||||
"Aster Team",
|
||||
"0.8.0",
|
||||
"1.0.0",
|
||||
"9.0.0",
|
||||
"https://example.com/glass-panels.zip",
|
||||
"v0.8.0",
|
||||
"glass-panels.zip",
|
||||
string.Empty,
|
||||
"https://example.com/glass-panels/readme",
|
||||
"https://example.com/glass-panels",
|
||||
"https://example.com/glass-panels/repo",
|
||||
["theme", "experimental"],
|
||||
[],
|
||||
DateTimeOffset.Now.AddDays(-12),
|
||||
DateTimeOffset.Now.AddDays(-3)),
|
||||
localizationService,
|
||||
installedPlugin: null,
|
||||
previewHostVersion)
|
||||
};
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
viewModel.MarketPlugins.Add(item);
|
||||
viewModel.FilteredPlugins.Add(item);
|
||||
}
|
||||
|
||||
viewModel.ShowEmptyState = false;
|
||||
viewModel.EmptyStateText = string.Empty;
|
||||
viewModel.StatusMessage = "Showing 3 mocked marketplace plugins for Avalonia design mode.";
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
private void OnRestartRequested(string? reason)
|
||||
{
|
||||
RequestRestart(reason ?? ViewModel.RestartRequiredMessage);
|
||||
@@ -60,4 +174,17 @@ public partial class PluginMarketSettingsPage : SettingsPageBase
|
||||
OpenDrawer(drawer, detailViewModel.DrawerTitle);
|
||||
await detailViewModel.InitializeAsync();
|
||||
}
|
||||
|
||||
private static PluginMarketItemViewModel CreateMarketItem(
|
||||
PluginMarketPluginInfo plugin,
|
||||
LocalizationService localizationService,
|
||||
InstalledPluginInfo? installedPlugin,
|
||||
Version hostVersion)
|
||||
{
|
||||
var languageCode = localizationService.NormalizeLanguageCode(
|
||||
HostSettingsFacadeProvider.GetOrCreate().Region.Get().LanguageCode);
|
||||
var item = new PluginMarketItemViewModel(plugin, localizationService, languageCode);
|
||||
item.ApplyInstallState(installedPlugin, hostVersion);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Avalonia.Controls;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
using LanMountainDesktop.Services.Settings;
|
||||
using LanMountainDesktop.ViewModels;
|
||||
@@ -15,7 +16,7 @@ namespace LanMountainDesktop.Views.SettingsPages;
|
||||
public partial class PluginsSettingsPage : SettingsPageBase
|
||||
{
|
||||
public PluginsSettingsPage()
|
||||
: this(new PluginsSettingsPageViewModel(HostSettingsFacadeProvider.GetOrCreate()))
|
||||
: this(Design.IsDesignMode ? CreateDesignTimeViewModel() : new PluginsSettingsPageViewModel(HostSettingsFacadeProvider.GetOrCreate()))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -31,6 +32,11 @@ public partial class PluginsSettingsPage : SettingsPageBase
|
||||
|
||||
public override async void OnNavigatedTo(object? parameter)
|
||||
{
|
||||
if (Design.IsDesignMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await ViewModel.InitializeAsync();
|
||||
}
|
||||
|
||||
@@ -38,4 +44,47 @@ public partial class PluginsSettingsPage : SettingsPageBase
|
||||
{
|
||||
RequestRestart(ViewModel.RestartRequiredMessage);
|
||||
}
|
||||
|
||||
private static PluginsSettingsPageViewModel CreateDesignTimeViewModel()
|
||||
{
|
||||
var viewModel = new PluginsSettingsPageViewModel(HostSettingsFacadeProvider.GetOrCreate());
|
||||
viewModel.InstalledPlugins.Add(new InstalledPluginItemViewModel(new InstalledPluginInfo(
|
||||
new PluginManifest(
|
||||
"calendar-plus",
|
||||
"Calendar Plus",
|
||||
"CalendarPlus.dll",
|
||||
"Adds a compact agenda widget and richer date cards.",
|
||||
"LanMountain Labs",
|
||||
"1.4.0"),
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
null)));
|
||||
viewModel.InstalledPlugins.Add(new InstalledPluginItemViewModel(new InstalledPluginInfo(
|
||||
new PluginManifest(
|
||||
"focus-mode",
|
||||
"Focus Mode",
|
||||
"FocusMode.dll",
|
||||
"Provides a distraction-free overlay and quick toggles.",
|
||||
"Studio North",
|
||||
"0.9.2"),
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
null)));
|
||||
viewModel.InstalledPlugins.Add(new InstalledPluginItemViewModel(new InstalledPluginInfo(
|
||||
new PluginManifest(
|
||||
"notes-dock",
|
||||
"Notes Dock",
|
||||
"NotesDock.dll",
|
||||
"Pins short markdown notes directly on the desktop.",
|
||||
"Aster Team",
|
||||
"2.1.0"),
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
null)));
|
||||
viewModel.StatusMessage = "Loaded 3 mocked plugins for Avalonia design mode.";
|
||||
return viewModel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Avalonia.Controls;
|
||||
using LanMountainDesktop.PluginSdk;
|
||||
using LanMountainDesktop.Services;
|
||||
using LanMountainDesktop.Services.Settings;
|
||||
@@ -16,7 +17,7 @@ namespace LanMountainDesktop.Views.SettingsPages;
|
||||
public partial class WeatherSettingsPage : SettingsPageBase
|
||||
{
|
||||
public WeatherSettingsPage()
|
||||
: this(CreateDefaultViewModel())
|
||||
: this(Design.IsDesignMode ? CreateDesignTimeViewModel() : CreateDefaultViewModel())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -29,7 +30,7 @@ public partial class WeatherSettingsPage : SettingsPageBase
|
||||
|
||||
public WeatherSettingsPageViewModel ViewModel { get; }
|
||||
|
||||
private static WeatherSettingsPageViewModel CreateDefaultViewModel()
|
||||
private static WeatherSettingsPageViewModel CreateDefaultViewModel(bool enableStartupPreviewRefresh = true)
|
||||
{
|
||||
var settingsFacade = HostSettingsFacadeProvider.GetOrCreate();
|
||||
var localizationService = new LocalizationService();
|
||||
@@ -42,6 +43,14 @@ public partial class WeatherSettingsPage : SettingsPageBase
|
||||
settingsFacade,
|
||||
localizationService,
|
||||
locationService,
|
||||
weatherLocationRefreshService);
|
||||
weatherLocationRefreshService,
|
||||
enableStartupPreviewRefresh);
|
||||
}
|
||||
|
||||
private static WeatherSettingsPageViewModel CreateDesignTimeViewModel()
|
||||
{
|
||||
var viewModel = CreateDefaultViewModel(enableStartupPreviewRefresh: false);
|
||||
viewModel.ApplyDesignTimePreview();
|
||||
return viewModel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
## 2. 使用场景
|
||||
|
||||
| 场景 | 说明 |
|
||||
|-----|------|
|
||||
| 学习辅助 | 查看课程表、记录自习时长、获取每日诗词单词 |
|
||||
| 场景 | 说明 |
|
||||
| ---- | ---------------------- |
|
||||
| 学习辅助 | 查看课程表、记录自习时长、获取每日诗词单词 |
|
||||
| 办公效率 | 查看日历日程、快速访问最近文档、获取新闻资讯 |
|
||||
| 信息聚合 | 桌面一站式查看天气、日历、热搜、新闻 |
|
||||
| 个性美化 | 自由定制桌面组件布局、主题、壁纸 |
|
||||
| 信息聚合 | 桌面一站式查看天气、日历、热搜、新闻 |
|
||||
| 个性美化 | 自由定制桌面组件布局、主题、壁纸 |
|
||||
|
||||
## 3. 解决方案
|
||||
|
||||
@@ -27,20 +27,17 @@
|
||||
|
||||
## 4. 解决的问题
|
||||
|
||||
| 痛点 | 解决方案 |
|
||||
|-----|---------|
|
||||
| 信息分散,需打开多个应用 | 桌面聚合展示天气、日历、新闻等信息 |
|
||||
| 桌面单调,缺乏个性化 | 丰富的组件和主题自由定制 |
|
||||
| 学习管理不便 | 课程表、自习监测专为学生设计 |
|
||||
| 痛点 | 解决方案 |
|
||||
| -------------- | -------------------- |
|
||||
| 信息分散,需打开多个应用 | 桌面聚合展示天气、日历、新闻等信息 |
|
||||
| 桌面单调,缺乏个性化 | 丰富的组件和主题自由定制 |
|
||||
| 学习管理不便 | 课程表、自习监测专为学生设计 |
|
||||
| 功能单一,需安装多个独立应用 | 一个应用整合考试看板、噪音监测等多种功能 |
|
||||
| 功能无法满足个性需求 | 插件系统支持无限扩展 |
|
||||
| 功能无法满足个性需求 | 插件系统支持无限扩展 |
|
||||
|
||||
## 5. 产品进度
|
||||
|
||||
- **当前版本**:v0.7.0(插件 API 3.0.0)
|
||||
- **开发状态**:核心功能开发中,预计 v1.0 正式发布
|
||||
- **开发状态**:功能开发中,预计 1\~2 个月内发布 v1.0 正式版
|
||||
- **用户统计**:通过 PostHog 收集匿名数据(具体数据需后台查看)
|
||||
|
||||
---
|
||||
|
||||
**一句话总结**:阑山桌面是一款面向个人用户的可定制桌面工具,专注个人学习办公场景,通过组件化设计和插件生态提供轻量、开放、跨平台的桌面信息聚合方案。
|
||||
|
||||
Reference in New Issue
Block a user