|
| 1 | +using Avalonia; |
| 2 | +using Avalonia.Controls; |
| 3 | +using Avalonia.Controls.ApplicationLifetimes; |
| 4 | +using Avalonia.Markup.Xaml; |
| 5 | +using Avalonia.Threading; |
| 6 | +using CommunityToolkit.Mvvm.DependencyInjection; |
| 7 | +using Flow.Launcher.Avalonia.Helper; |
| 8 | +using Flow.Launcher.Avalonia.Resource; |
| 9 | +using Flow.Launcher.Avalonia.Views.Dialogs; |
| 10 | +using Flow.Launcher.Avalonia.ViewModel; |
| 11 | +using Flow.Launcher.Avalonia.Views.SettingPages; |
| 12 | +using Flow.Launcher.Core; |
| 13 | +using Flow.Launcher.Core.Configuration; |
| 14 | +using Flow.Launcher.Core.Plugin; |
| 15 | +using Flow.Launcher.Infrastructure; |
| 16 | +using Flow.Launcher.Infrastructure.Logger; |
| 17 | +using Flow.Launcher.Infrastructure.Storage; |
| 18 | +using Flow.Launcher.Infrastructure.UserSettings; |
| 19 | +using Flow.Launcher.Plugin; |
| 20 | +using Microsoft.Extensions.DependencyInjection; |
| 21 | +using System; |
| 22 | +using System.Diagnostics; |
| 23 | +using System.Threading.Tasks; |
| 24 | + |
| 25 | +namespace Flow.Launcher.Avalonia; |
| 26 | + |
| 27 | +public partial class App : Application |
| 28 | +{ |
| 29 | + private static readonly string ClassName = nameof(App); |
| 30 | + private Settings? _settings; |
| 31 | + private MainViewModel? _mainVM; |
| 32 | + private MainWindow? _mainWindow; |
| 33 | + |
| 34 | + public static IPublicAPI? API { get; private set; } |
| 35 | + |
| 36 | + public override void Initialize() |
| 37 | + { |
| 38 | + // Configure DI before loading XAML so markup extensions can access services |
| 39 | + LoadSettings(); |
| 40 | + ConfigureDI(); |
| 41 | + |
| 42 | + AvaloniaXamlLoader.Load(this); |
| 43 | + |
| 44 | + // Inject translations into Application.Resources for DynamicResource bindings in plugins |
| 45 | + var i18n = Ioc.Default.GetRequiredService<Internationalization>(); |
| 46 | + i18n.InjectIntoApplicationResources(); |
| 47 | + |
| 48 | + #if DEBUG |
| 49 | + this.AttachDeveloperTools(); |
| 50 | + #endif |
| 51 | + } |
| 52 | + |
| 53 | + public override void OnFrameworkInitializationCompleted() |
| 54 | + { |
| 55 | + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) |
| 56 | + { |
| 57 | + API = Ioc.Default.GetRequiredService<IPublicAPI>(); |
| 58 | + _mainVM = Ioc.Default.GetRequiredService<MainViewModel>(); |
| 59 | + |
| 60 | + _mainWindow = new MainWindow(); |
| 61 | + // desktop.MainWindow = _mainWindow; // Prevent auto-show on startup |
| 62 | + desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown; |
| 63 | + |
| 64 | + // Initialize hotkeys after window is created |
| 65 | + HotKeyMapper.Initialize(); |
| 66 | + |
| 67 | + AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; |
| 68 | + TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; |
| 69 | + Dispatcher.UIThread.UnhandledException += OnUiUnhandledException; |
| 70 | + |
| 71 | + AutoStartup(); |
| 72 | + |
| 73 | + Dispatcher.UIThread.Post(async () => await InitializePluginsAsync(), DispatcherPriority.Background); |
| 74 | + |
| 75 | + // Cleanup on exit |
| 76 | + desktop.Exit += (_, _) => |
| 77 | + { |
| 78 | + HotKeyMapper.Shutdown(); |
| 79 | + AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException; |
| 80 | + TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException; |
| 81 | + Dispatcher.UIThread.UnhandledException -= OnUiUnhandledException; |
| 82 | + }; |
| 83 | + } |
| 84 | + base.OnFrameworkInitializationCompleted(); |
| 85 | + } |
| 86 | + |
| 87 | + /// <summary> |
| 88 | + /// Check startup only for Release. |
| 89 | + /// </summary> |
| 90 | + [Conditional("RELEASE")] |
| 91 | + private void AutoStartup() |
| 92 | + { |
| 93 | + if (_settings?.StartFlowLauncherOnSystemStartup != true) |
| 94 | + { |
| 95 | + return; |
| 96 | + } |
| 97 | + |
| 98 | + try |
| 99 | + { |
| 100 | + Helper.AutoStartup.CheckIsEnabled(_settings.UseLogonTaskForStartup); |
| 101 | + } |
| 102 | + catch (Exception e) |
| 103 | + { |
| 104 | + _settings.StartFlowLauncherOnSystemStartup = false; |
| 105 | + _settings.Save(); |
| 106 | + API?.ShowMsgError(Translator.GetString("setAutoStartFailed"), e.Message); |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + private void TrayIcon_OnClicked(object? sender, EventArgs e) |
| 111 | + { |
| 112 | + _mainVM?.ToggleFlowLauncher(); |
| 113 | + } |
| 114 | + |
| 115 | + private void MenuShow_OnClick(object? sender, EventArgs e) |
| 116 | + { |
| 117 | + _mainVM?.Show(); |
| 118 | + } |
| 119 | + |
| 120 | + private void MenuSettings_OnClick(object? sender, EventArgs e) |
| 121 | + { |
| 122 | + var settingsWindow = new SettingsWindow(); |
| 123 | + settingsWindow.Show(); |
| 124 | + } |
| 125 | + |
| 126 | + private void MenuExit_OnClick(object? sender, EventArgs e) |
| 127 | + { |
| 128 | + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) |
| 129 | + { |
| 130 | + desktop.Shutdown(); |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + private void LoadSettings() |
| 135 | + { |
| 136 | + try |
| 137 | + { |
| 138 | + var storage = new FlowLauncherJsonStorage<Settings>(); |
| 139 | + _settings = storage.Load(); |
| 140 | + _settings.SetStorage(storage); |
| 141 | + } |
| 142 | + catch (Exception e) |
| 143 | + { |
| 144 | + Log.Exception(ClassName, "Settings load failed", e); |
| 145 | + var storage = new FlowLauncherJsonStorage<Settings>(); |
| 146 | + _settings = new Settings |
| 147 | + { |
| 148 | + WindowSize = 580, WindowHeightSize = 42, QueryBoxFontSize = 24, |
| 149 | + ItemHeightSize = 50, ResultItemFontSize = 14, ResultSubItemFontSize = 12, MaxResultsToShow = 6 |
| 150 | + }; |
| 151 | + _settings.SetStorage(storage); |
| 152 | + } |
| 153 | + } |
| 154 | + |
| 155 | + private void ConfigureDI() |
| 156 | + { |
| 157 | + var services = new ServiceCollection(); |
| 158 | + services.AddSingleton(_settings!); |
| 159 | + services.AddSingleton(sp => new Updater(sp.GetRequiredService<IPublicAPI>(), Constant.GitHub)); |
| 160 | + services.AddSingleton<Portable>(); |
| 161 | + services.AddSingleton<IAlphabet, PinyinAlphabet>(); |
| 162 | + services.AddSingleton<StringMatcher>(); |
| 163 | + services.AddSingleton<Internationalization>(); |
| 164 | + services.AddSingleton<MainViewModel>(); |
| 165 | + services.AddSingleton<IPublicAPI>(sp => new AvaloniaPublicAPI( |
| 166 | + sp.GetRequiredService<Settings>(), |
| 167 | + () => sp.GetRequiredService<MainViewModel>(), |
| 168 | + sp.GetRequiredService<Internationalization>())); |
| 169 | + Ioc.Default.ConfigureServices(services.BuildServiceProvider()); |
| 170 | + } |
| 171 | + |
| 172 | + private async Task InitializePluginsAsync() |
| 173 | + { |
| 174 | + try |
| 175 | + { |
| 176 | + Log.Info(ClassName, "Loading plugins..."); |
| 177 | + PluginManager.LoadPlugins(_settings!.PluginSettings); |
| 178 | + Log.Info(ClassName, $"Loaded {PluginManager.GetAllLoadedPlugins().Count} plugins"); |
| 179 | + |
| 180 | + await PluginManager.InitializePluginsAsync(_mainVM!); |
| 181 | + Log.Info(ClassName, "Plugins initialized"); |
| 182 | + |
| 183 | + // Update plugin translations after they are initialized |
| 184 | + var i18n = Ioc.Default.GetRequiredService<Internationalization>(); |
| 185 | + i18n.UpdatePluginMetadataTranslations(); |
| 186 | + |
| 187 | + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) |
| 188 | + { |
| 189 | + desktop.MainWindow = _mainWindow; |
| 190 | + } |
| 191 | + |
| 192 | + _mainVM?.OnPluginsReady(); |
| 193 | + } |
| 194 | + catch (Exception e) { Log.Exception(ClassName, "Plugin init failed", e); } |
| 195 | + } |
| 196 | + |
| 197 | + private void OnUiUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) |
| 198 | + { |
| 199 | + Log.Exception(ClassName, "Unhandled UI exception", e.Exception); |
| 200 | + ShowReportWindow(e.Exception); |
| 201 | + e.Handled = true; |
| 202 | + } |
| 203 | + |
| 204 | + private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) |
| 205 | + { |
| 206 | + if (e.ExceptionObject is Exception exception) |
| 207 | + { |
| 208 | + Log.Exception(ClassName, "Unhandled exception", exception); |
| 209 | + ShowReportWindow(exception); |
| 210 | + } |
| 211 | + } |
| 212 | + |
| 213 | + private void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e) |
| 214 | + { |
| 215 | + Log.Exception(ClassName, "Unobserved task exception occurred.", e.Exception); |
| 216 | + e.SetObserved(); |
| 217 | + } |
| 218 | + |
| 219 | + private static void ShowReportWindow(Exception exception) |
| 220 | + { |
| 221 | + Dispatcher.UIThread.Post(() => |
| 222 | + { |
| 223 | + var window = new ReportWindow(exception); |
| 224 | + window.Show(); |
| 225 | + window.Activate(); |
| 226 | + }); |
| 227 | + } |
| 228 | +} |
0 commit comments