Files
LanMountainDesktop/LanMountainDesktop/App.axaml.cs
lincube ac4617f5cf 0.6.4
2026-03-17 14:57:41 +08:00

829 lines
27 KiB
C#

using System;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.Threading;
using AvaloniaWebView;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.Theme;
using LanMountainDesktop.ViewModels;
using LanMountainDesktop.Views;
namespace LanMountainDesktop;
public partial class App : Application
{
private static readonly Color DefaultAccentColor = Color.Parse("#FF3B82F6");
private enum DesktopShellState
{
ForegroundDesktop = 0,
MinimizedToTaskbar = 1,
TrayOnly = 2
}
private enum ShutdownIntent
{
None = 0,
ExitRequested = 1,
RestartRequested = 2
}
private readonly ISettingsFacadeService _settingsFacade = HostSettingsFacadeProvider.GetOrCreate();
private readonly IAppearanceThemeService _appearanceThemeService = HostAppearanceThemeProvider.GetOrCreate();
private readonly IAppLogoService _appLogoService = HostAppLogoProvider.GetOrCreate();
private readonly LocalizationService _localizationService = new();
private readonly IHostApplicationLifecycle _hostApplicationLifecycle = new HostApplicationLifecycleService();
private readonly IDetachedComponentLibraryWindowService _detachedComponentLibraryWindowService = new DetachedComponentLibraryWindowService();
private readonly ILocationService _locationService = HostLocationServiceProvider.GetOrCreate();
private ISettingsPageRegistry? _settingsPageRegistry;
private ISettingsWindowService? _settingsWindowService;
private WeatherLocationRefreshService? _weatherLocationRefreshService;
private bool _exitCleanupCompleted;
private DesktopShellState _desktopShellState = DesktopShellState.ForegroundDesktop;
private ShutdownIntent _shutdownIntent;
private TrayIcons? _trayIcons;
private PluginRuntimeService? _pluginRuntimeService;
private MainWindow? _mainWindow;
private bool _mainWindowClosed;
private bool _uiUnhandledExceptionHooked;
internal static SingleInstanceService? CurrentSingleInstanceService { get; set; }
internal static (UserBehaviorAnalyticsService?, CrashReportService?) AnalyticsServices { get; set; }
internal static IHostApplicationLifecycle? CurrentHostApplicationLifecycle =>
(Current as App)?._hostApplicationLifecycle;
// 隐私政策查看事件
public static event Action? CurrentPrivacyPolicyViewRequested;
// 触发隐私政策查看事件的方法
public static void RaisePrivacyPolicyViewRequested()
{
CurrentPrivacyPolicyViewRequested?.Invoke();
}
public PluginRuntimeService? PluginRuntimeService => _pluginRuntimeService;
public ISettingsFacadeService SettingsFacade => _settingsFacade;
public IHostApplicationLifecycle HostApplicationLifecycle => _hostApplicationLifecycle;
internal ISettingsWindowService? SettingsWindowService => _settingsWindowService;
internal void OpenIndependentSettingsModule(string source, string? pageTag = null)
{
EnsureSettingsWindowService();
AppLogger.Info(
"SettingsFacade",
$"Opening settings window. Source='{source}'; PageTag='{pageTag ?? "<default>"}'.");
_settingsWindowService?.Open(new SettingsWindowOpenRequest(
Source: source,
Owner: _mainWindow is { IsVisible: true } ? _mainWindow : null,
PageId: pageTag));
}
public App()
{
_settingsFacade.Settings.Changed += OnSettingsChanged;
_appearanceThemeService.Changed += OnAppearanceThemeChanged;
}
public override void Initialize()
{
AppLogger.Info("App", "Initializing application resources.");
ConfigureWebViewUserDataFolder();
AvaloniaWebViewBuilder.Initialize(default);
AvaloniaXamlLoader.Load(this);
ApplyThemeFromSettings();
ApplyCurrentCultureFromSettings();
EnsureSettingsWindowService();
EnsureWeatherLocationRefreshService();
}
public override void OnFrameworkInitializationCompleted()
{
AppLogger.Info("App", "Framework initialization completed.");
RegisterUiUnhandledExceptionGuard();
LinuxDesktopEntryInstaller.EnsureInstalled();
InitializePluginRuntime();
InitializeTrayIcon();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
// Avoid duplicate validations from both Avalonia and the CommunityToolkit.
// More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins
DisableAvaloniaDataAnnotationValidation();
desktop.ShutdownMode = Avalonia.Controls.ShutdownMode.OnExplicitShutdown;
desktop.Exit += (_, _) =>
{
AppLogger.Info("App", "Desktop lifetime exit triggered.");
PerformExitCleanup();
};
CreateAndAssignMainWindow(desktop, "FrameworkInitialization");
CurrentSingleInstanceService?.StartActivationListener(ActivateMainWindow);
}
StartWeatherLocationRefreshIfNeeded();
base.OnFrameworkInitializationCompleted();
}
private void OnTrayExitClick(object? sender, EventArgs e)
{
_ = _hostApplicationLifecycle.TryExit(new HostApplicationLifecycleRequest(
Source: "TrayMenu",
Reason: "User selected Exit App from the tray menu."));
}
private void OnTrayShowDesktopClick(object? sender, EventArgs e)
{
RestoreOrCreateMainWindow(showSingleInstanceNotice: false, source: "TrayMenu");
}
private void OnTrayRestartClick(object? sender, EventArgs e)
{
_ = _hostApplicationLifecycle.TryRestart(new HostApplicationLifecycleRequest(
Source: "TrayMenu",
Reason: "User selected Restart App from the tray menu."));
}
private void OnTraySettingsClick(object? sender, EventArgs e)
{
_ = sender;
_ = e;
OpenIndependentSettingsModule("TrayMenu");
}
private void OnTrayComponentLibraryClick(object? sender, EventArgs e)
{
_ = sender;
_ = e;
if (_mainWindow is null)
{
return;
}
_detachedComponentLibraryWindowService.Open(_mainWindow);
}
private void DisableAvaloniaDataAnnotationValidation()
{
// Get an array of plugins to remove
var dataValidationPluginsToRemove =
BindingPlugins.DataValidators.OfType<DataAnnotationsValidationPlugin>().ToArray();
// remove each entry found
foreach (var plugin in dataValidationPluginsToRemove)
{
BindingPlugins.DataValidators.Remove(plugin);
}
}
private static void ConfigureWebViewUserDataFolder()
{
if (!OperatingSystem.IsWindows())
{
return;
}
const string userDataFolderEnvVar = "WEBVIEW2_USER_DATA_FOLDER";
try
{
if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(userDataFolderEnvVar)))
{
return;
}
var userDataFolder = WebView2RuntimeProbe.ResolveUserDataFolder();
Environment.SetEnvironmentVariable(
userDataFolderEnvVar,
userDataFolder,
EnvironmentVariableTarget.Process);
}
catch (Exception ex)
{
// Keep startup resilient if user profile folders are unavailable.
AppLogger.Warn("WebView2", "Failed to configure WebView2 user data folder.", ex);
}
}
private void InitializePluginRuntime()
{
try
{
_pluginRuntimeService?.Dispose();
_pluginRuntimeService = new PluginRuntimeService(_settingsFacade);
HostSettingsFacadeProvider.BindPluginRuntime(_pluginRuntimeService);
_pluginRuntimeService.LoadInstalledPlugins();
}
catch (Exception ex)
{
AppLogger.Warn("PluginRuntime", "Failed to initialize plugin runtime.", ex);
}
}
private void InitializeTrayIcon()
{
try
{
DisposeTrayIcon();
var trayIcon = new TrayIcon
{
Icon = _appLogoService.CreateTrayIcon(),
ToolTipText = L("tray.tooltip", "LanMountainDesktop"),
Menu = BuildTrayMenu(),
IsVisible = true
};
_trayIcons = [trayIcon];
TrayIcon.SetIcons(this, _trayIcons);
}
catch (Exception ex)
{
AppLogger.Warn("TrayIcon", "Failed to initialize tray icon.", ex);
}
}
private NativeMenu BuildTrayMenu()
{
var menu = new NativeMenu();
var showDesktopItem = new NativeMenuItem(L("tray.menu.show_desktop", "Open Desktop"));
showDesktopItem.Click += OnTrayShowDesktopClick;
menu.Items.Add(showDesktopItem);
var settingsItem = new NativeMenuItem(L("tray.menu.settings", "Settings"));
settingsItem.Click += OnTraySettingsClick;
menu.Items.Add(settingsItem);
var componentLibraryItem = new NativeMenuItem(L("tray.menu.component_library", "Component Library"));
componentLibraryItem.Click += OnTrayComponentLibraryClick;
menu.Items.Add(componentLibraryItem);
menu.Items.Add(new NativeMenuItemSeparator());
var restartItem = new NativeMenuItem(L("tray.menu.restart", "Restart App"));
restartItem.Click += OnTrayRestartClick;
menu.Items.Add(restartItem);
menu.Items.Add(new NativeMenuItemSeparator());
var exitItem = new NativeMenuItem(L("tray.menu.exit", "Exit App"));
exitItem.Click += OnTrayExitClick;
menu.Items.Add(exitItem);
return menu;
}
private void DisposeTrayIcon()
{
if (_trayIcons is null)
{
return;
}
TrayIcon.SetIcons(this, null);
foreach (var trayIcon in _trayIcons)
{
trayIcon.Dispose();
}
_trayIcons = null;
}
private void EnsureSettingsWindowService()
{
_settingsPageRegistry ??= new SettingsPageRegistry(
_settingsFacade,
_hostApplicationLifecycle,
_localizationService,
() => _pluginRuntimeService);
_settingsWindowService ??= new SettingsWindowService(
_settingsPageRegistry,
_hostApplicationLifecycle,
_settingsFacade);
}
private void EnsureWeatherLocationRefreshService()
{
_weatherLocationRefreshService ??= new WeatherLocationRefreshService(
_settingsFacade,
_locationService,
_localizationService);
}
private void StartWeatherLocationRefreshIfNeeded()
{
EnsureWeatherLocationRefreshService();
if (_weatherLocationRefreshService is null)
{
return;
}
_ = Task.Run(async () =>
{
try
{
await _weatherLocationRefreshService.TryRefreshOnStartupAsync();
}
catch (Exception ex)
{
AppLogger.Warn("Weather.Location", "Failed to refresh weather location during startup.", ex);
}
});
}
private void ApplyThemeFromSettings()
{
var snapshot = _appearanceThemeService.GetCurrent();
RequestedThemeVariant = snapshot.IsNightMode
? ThemeVariant.Dark
: ThemeVariant.Light;
ApplyAdaptiveThemeResources();
}
private void ApplyCurrentCultureFromSettings()
{
var snapshot = _settingsFacade.Settings.LoadSnapshot<AppSettingsSnapshot>(SettingsScope.App);
var languageCode = _localizationService.NormalizeLanguageCode(snapshot.LanguageCode);
CultureInfo culture;
try
{
culture = CultureInfo.GetCultureInfo(languageCode);
}
catch (CultureNotFoundException)
{
culture = CultureInfo.GetCultureInfo("zh-CN");
}
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
}
private void ActivateMainWindow()
{
RestoreOrCreateMainWindow(showSingleInstanceNotice: true, source: "SingleInstance");
}
private void RestoreOrCreateMainWindow(bool showSingleInstanceNotice, string source)
{
Dispatcher.UIThread.Post(() =>
{
if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
{
return;
}
try
{
var mainWindow = GetOrCreateMainWindow(desktop, source);
mainWindow.ShowInTaskbar = true;
if (!mainWindow.IsVisible)
{
mainWindow.Show();
}
if (mainWindow.WindowState == WindowState.Minimized)
{
mainWindow.WindowState = WindowState.Normal;
}
if (mainWindow.WindowState != WindowState.FullScreen)
{
mainWindow.WindowState = WindowState.FullScreen;
}
mainWindow.Activate();
mainWindow.Topmost = true;
mainWindow.Topmost = false;
SetDesktopShellState(DesktopShellState.ForegroundDesktop, $"Restore:{source}");
AppLogger.Info(
"DesktopShell",
$"Desktop restored. Source='{source}'; MainWindowClosed={_mainWindowClosed}; ShowSingleInstanceNotice={showSingleInstanceNotice}; WindowState='{mainWindow.WindowState}'.");
if (showSingleInstanceNotice)
{
mainWindow.ShowSingleInstanceNotice();
}
}
catch (Exception ex)
{
AppLogger.Warn("DesktopShell", $"Failed to restore desktop shell. Source='{source}'.", ex);
}
}, DispatcherPriority.Send);
}
internal void PrepareForShutdown(bool isRestart, string source)
{
void Mark()
{
_shutdownIntent = isRestart
? ShutdownIntent.RestartRequested
: ShutdownIntent.ExitRequested;
AppLogger.Info(
"DesktopShell",
$"Shutdown intent marked. Intent='{_shutdownIntent}'; Source='{source}'; CurrentShellState='{_desktopShellState}'.");
}
if (Dispatcher.UIThread.CheckAccess())
{
Mark();
return;
}
Dispatcher.UIThread.InvokeAsync(Mark, DispatcherPriority.Send).GetAwaiter().GetResult();
}
internal void ResetShutdownIntent(string source)
{
void Reset()
{
if (_shutdownIntent == ShutdownIntent.None)
{
return;
}
AppLogger.Warn(
"DesktopShell",
$"Shutdown intent cleared without process exit. PreviousIntent='{_shutdownIntent}'; Source='{source}'.");
_shutdownIntent = ShutdownIntent.None;
}
if (Dispatcher.UIThread.CheckAccess())
{
Reset();
return;
}
Dispatcher.UIThread.InvokeAsync(Reset, DispatcherPriority.Send).GetAwaiter().GetResult();
}
private void OnSettingsChanged(object? sender, SettingsChangedEvent e)
{
_ = sender;
if (e.Scope != SettingsScope.App)
{
return;
}
Dispatcher.UIThread.Post(() =>
{
var changedKeys = e.ChangedKeys?.ToArray();
var refreshAll = changedKeys is null || changedKeys.Length == 0;
var liveAppearance = _appearanceThemeService.GetCurrent();
var themeChanged =
refreshAll ||
changedKeys.Contains(nameof(AppSettingsSnapshot.IsNightMode), StringComparer.OrdinalIgnoreCase) ||
changedKeys.Contains(nameof(AppSettingsSnapshot.UseSystemChrome), StringComparer.OrdinalIgnoreCase) ||
(string.Equals(liveAppearance.ThemeColorMode, ThemeAppearanceValues.ColorModeSeedMonet, StringComparison.OrdinalIgnoreCase) &&
changedKeys.Contains(nameof(AppSettingsSnapshot.ThemeColor), StringComparer.OrdinalIgnoreCase)) ||
(string.Equals(liveAppearance.ThemeColorMode, ThemeAppearanceValues.ColorModeWallpaperMonet, StringComparison.OrdinalIgnoreCase) &&
(changedKeys.Contains(nameof(AppSettingsSnapshot.WallpaperPath), StringComparer.OrdinalIgnoreCase) ||
changedKeys.Contains(nameof(AppSettingsSnapshot.WallpaperType), StringComparer.OrdinalIgnoreCase) ||
changedKeys.Contains(nameof(AppSettingsSnapshot.WallpaperColor), StringComparer.OrdinalIgnoreCase)));
var languageChanged =
refreshAll ||
changedKeys.Contains(nameof(AppSettingsSnapshot.LanguageCode), StringComparer.OrdinalIgnoreCase);
if (themeChanged)
{
ApplyThemeFromSettings();
}
if (languageChanged)
{
// 清除本地化缓存,强制重新加载语言文件
_localizationService.ClearCache();
ApplyCurrentCultureFromSettings();
if (_trayIcons is not null)
{
InitializeTrayIcon();
}
}
}, DispatcherPriority.Background);
}
private void OnAppearanceThemeChanged(object? sender, AppearanceThemeSnapshot e)
{
_ = sender;
_ = e;
Dispatcher.UIThread.Post(ApplyThemeFromSettings, DispatcherPriority.Background);
}
private void ApplyAdaptiveThemeResources()
{
_appearanceThemeService.ApplyThemeResources(Resources);
}
private void RegisterUiUnhandledExceptionGuard()
{
if (_uiUnhandledExceptionHooked)
{
return;
}
Dispatcher.UIThread.UnhandledException += OnUiThreadUnhandledException;
_uiUnhandledExceptionHooked = true;
}
private void OnUiThreadUnhandledException(object? sender, DispatcherUnhandledExceptionEventArgs e)
{
if (!IsKnownWebViewStartupException(e.Exception))
{
return;
}
e.Handled = true;
AppLogger.Warn(
"WebView2",
"Suppressed a known WebView startup exception from AvaloniaWebView.Navigate to keep the host process alive.",
e.Exception);
}
private static bool IsKnownWebViewStartupException(Exception exception)
{
if (exception is not NullReferenceException)
{
return false;
}
var stackTrace = exception.StackTrace ?? string.Empty;
return stackTrace.Contains("AvaloniaWebView.WebView.Navigate", StringComparison.Ordinal) &&
stackTrace.Contains("AvaloniaWebView.WebView.OnAttachedToVisualTree", StringComparison.Ordinal);
}
private void PerformExitCleanup()
{
if (_exitCleanupCompleted)
{
return;
}
_exitCleanupCompleted = true;
_settingsFacade.Settings.Changed -= OnSettingsChanged;
_appearanceThemeService.Changed -= OnAppearanceThemeChanged;
try
{
var (analytics, crashReport) = App.AnalyticsServices;
analytics?.SendShutdownEvent();
crashReport?.SendShutdownEvent();
}
catch (Exception ex)
{
AppLogger.Warn("Analytics", "Failed to send shutdown events during exit cleanup.", ex);
}
try
{
HostUpdateWorkflowServiceProvider.GetOrCreate().TryApplyPendingUpdateOnExit();
}
catch (Exception ex)
{
AppLogger.Warn("UpdateWorkflow", "Failed to apply pending update during exit cleanup.", ex);
}
try
{
_pluginRuntimeService?.Dispose();
}
catch (Exception ex)
{
AppLogger.Warn("PluginRuntime", "Failed to dispose plugin runtime during shutdown.", ex);
}
finally
{
_pluginRuntimeService = null;
}
_settingsWindowService?.Close();
if (_settingsPageRegistry is IDisposable disposableRegistry)
{
disposableRegistry.Dispose();
}
AudioRecorderServiceFactory.DisposeSharedServices();
StudyAnalyticsServiceFactory.DisposeSharedService();
DisposeTrayIcon();
}
private MainWindow CreateAndAssignMainWindow(
IClassicDesktopStyleApplicationLifetime desktop,
string reason)
{
var mainWindow = new MainWindow
{
DataContext = new MainWindowViewModel(),
ShowInTaskbar = true
};
AttachMainWindow(mainWindow);
desktop.MainWindow = mainWindow;
AppLogger.Info("App", $"Main window created. Reason='{reason}'. LogFile={AppLogger.LogFilePath}");
LogBrowserStartupDiagnostics();
SetDesktopShellState(DesktopShellState.ForegroundDesktop, $"MainWindowCreated:{reason}");
return mainWindow;
}
private MainWindow GetOrCreateMainWindow(
IClassicDesktopStyleApplicationLifetime desktop,
string reason)
{
if (_mainWindow is not null && !_mainWindowClosed)
{
return _mainWindow;
}
if (desktop.MainWindow is MainWindow desktopMainWindow && !_mainWindowClosed)
{
AttachMainWindow(desktopMainWindow);
return desktopMainWindow;
}
return CreateAndAssignMainWindow(desktop, reason);
}
private void AttachMainWindow(MainWindow mainWindow)
{
if (ReferenceEquals(_mainWindow, mainWindow))
{
_mainWindowClosed = false;
return;
}
if (_mainWindow is not null)
{
_mainWindow.Closing -= OnMainWindowClosing;
_mainWindow.Closed -= OnMainWindowClosed;
_mainWindow.PropertyChanged -= OnMainWindowPropertyChanged;
}
_mainWindow = mainWindow;
_mainWindowClosed = false;
mainWindow.Closing += OnMainWindowClosing;
mainWindow.Closed += OnMainWindowClosed;
mainWindow.PropertyChanged += OnMainWindowPropertyChanged;
}
private void OnMainWindowClosing(object? sender, WindowClosingEventArgs e)
{
if (sender is not MainWindow mainWindow)
{
return;
}
AppLogger.Info(
"DesktopShell",
$"Main window closing requested. Intent='{_shutdownIntent}'; ShellState='{_desktopShellState}'; WindowState='{mainWindow.WindowState}'; IsVisible={mainWindow.IsVisible}.");
if (_shutdownIntent is ShutdownIntent.ExitRequested or ShutdownIntent.RestartRequested)
{
AppLogger.Info(
"DesktopShell",
$"Main window close allowed. Intent='{_shutdownIntent}'; ShellState='{_desktopShellState}'.");
return;
}
e.Cancel = true;
HideMainWindowToTray(mainWindow, "MainWindowClosing");
}
private void OnMainWindowClosed(object? sender, EventArgs e)
{
if (sender is not MainWindow mainWindow)
{
return;
}
mainWindow.Closing -= OnMainWindowClosing;
mainWindow.Closed -= OnMainWindowClosed;
mainWindow.PropertyChanged -= OnMainWindowPropertyChanged;
if (ReferenceEquals(_mainWindow, mainWindow))
{
_mainWindow = null;
}
_mainWindowClosed = true;
AppLogger.Info(
"DesktopShell",
$"Main window closed. Intent='{_shutdownIntent}'; ShellState='{_desktopShellState}'.");
if (_shutdownIntent == ShutdownIntent.None)
{
SetDesktopShellState(DesktopShellState.TrayOnly, "MainWindowClosedUnexpected");
}
}
private void OnMainWindowPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (sender is not MainWindow mainWindow)
{
return;
}
if (e.Property != Window.WindowStateProperty)
{
return;
}
if (_shutdownIntent != ShutdownIntent.None || !mainWindow.IsVisible)
{
return;
}
if (mainWindow.WindowState == WindowState.Minimized)
{
SetDesktopShellState(DesktopShellState.MinimizedToTaskbar, "MainWindowMinimized");
return;
}
SetDesktopShellState(DesktopShellState.ForegroundDesktop, "MainWindowRestored");
}
private void HideMainWindowToTray(MainWindow mainWindow, string source)
{
try
{
mainWindow.ShowInTaskbar = false;
mainWindow.Hide();
SetDesktopShellState(DesktopShellState.TrayOnly, source);
AppLogger.Info(
"DesktopShell",
$"Main window hidden to tray. Source='{source}'; WindowState='{mainWindow.WindowState}'.");
}
catch (Exception ex)
{
AppLogger.Warn("DesktopShell", $"Failed to hide main window to tray. Source='{source}'.", ex);
}
}
private void SetDesktopShellState(DesktopShellState state, string source)
{
if (_desktopShellState == state)
{
return;
}
var previous = _desktopShellState;
_desktopShellState = state;
AppLogger.Info(
"DesktopShell",
$"Shell state changed. Previous='{previous}'; Current='{state}'; Source='{source}'.");
}
private void LogBrowserStartupDiagnostics()
{
try
{
var snapshot = new DesktopLayoutSettingsService().Load();
var browserPlacements = snapshot.DesktopComponentPlacements
.Where(placement => string.Equals(
placement.ComponentId,
BuiltInComponentIds.DesktopBrowser,
StringComparison.OrdinalIgnoreCase))
.ToList();
var runtimeAvailability = WebView2RuntimeProbe.GetAvailability();
AppLogger.Info(
"StartupDiagnostics",
$"Browser component diagnostics. HasBrowserPlacement={browserPlacements.Count > 0}; " +
$"ActivePageHasBrowser={browserPlacements.Any(item => item.PageIndex == snapshot.CurrentDesktopSurfaceIndex)}; " +
$"CurrentDesktopSurfaceIndex={snapshot.CurrentDesktopSurfaceIndex}; " +
$"WebViewRuntimeAvailable={runtimeAvailability.IsAvailable}; " +
$"WebViewRuntimeVersion={runtimeAvailability.Version ?? string.Empty}; " +
$"WebViewRuntimeMessage={runtimeAvailability.Message}");
}
catch (Exception ex)
{
AppLogger.Warn("StartupDiagnostics", "Failed to log browser component diagnostics.", ex);
}
}
private string L(string key, string fallback)
{
var snapshot = _settingsFacade.Settings.LoadSnapshot<AppSettingsSnapshot>(SettingsScope.App);
var languageCode = _localizationService.NormalizeLanguageCode(snapshot.LanguageCode);
return _localizationService.GetString(languageCode, key, fallback);
}
}