From dda587493b7b999702a3f34c2168dc6048adc17e Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Mon, 19 Jan 2026 00:06:36 -0800 Subject: [PATCH] Migrate Program plugin settings to Avalonia and fix scroll issues - Update ISettingProvider to support CreateSettingPanelAvalonia - Create native Avalonia settings UI for Program Plugin (ProgramSetting.axaml) - Implement ProgramSettingViewModel using CommunityToolkit.Mvvm - Update PluginsSettingsViewModel to detect and load native Avalonia settings - Replace ListBox with ItemsControl in PluginsSettingsPage to fix auto-scroll issues when expanding settings - Align Avalonia package versions (11.2.3) across projects to fix type load errors --- .../SettingPages/PluginsSettingsViewModel.cs | 33 +- .../SettingPages/PluginsSettingsPage.axaml | 73 ++-- Flow.Launcher.Core/packages.lock.json | 28 +- .../packages.lock.json | 26 ++ .../Flow.Launcher.Plugin.csproj | 3 +- .../Interfaces/ISettingProvider.cs | 10 +- Flow.Launcher.Plugin/packages.lock.json | 26 ++ Flow.Launcher/packages.lock.json | 30 +- .../Flow.Launcher.Plugin.Program.csproj | 5 +- Plugins/Flow.Launcher.Plugin.Program/Main.cs | 12 +- .../ViewModels/ProgramSettingViewModel.cs | 337 ++++++++++++++++++ .../Views/Avalonia/ProgramSetting.axaml | 150 ++++++++ .../Views/Avalonia/ProgramSetting.axaml.cs | 24 ++ 13 files changed, 719 insertions(+), 38 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Program/ViewModels/ProgramSettingViewModel.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Program/Views/Avalonia/ProgramSetting.axaml create mode 100644 Plugins/Flow.Launcher.Plugin.Program/Views/Avalonia/ProgramSetting.axaml.cs diff --git a/Flow.Launcher.Avalonia/ViewModel/SettingPages/PluginsSettingsViewModel.cs b/Flow.Launcher.Avalonia/ViewModel/SettingPages/PluginsSettingsViewModel.cs index 08028a938..14a96cc83 100644 --- a/Flow.Launcher.Avalonia/ViewModel/SettingPages/PluginsSettingsViewModel.cs +++ b/Flow.Launcher.Avalonia/ViewModel/SettingPages/PluginsSettingsViewModel.cs @@ -7,7 +7,7 @@ using Flow.Launcher.Avalonia.Views.Controls; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Windows.Controls; +using AvaloniaControl = Avalonia.Controls.Control; namespace Flow.Launcher.Avalonia.ViewModel.SettingPages; @@ -68,16 +68,47 @@ public partial class PluginItemViewModel : ObservableObject HasSettings = true; } } + + if (HasSettings && _settingProvider != null) + { + try + { + System.Console.WriteLine($"Checking Avalonia settings for {Name}"); + AvaloniaSettingControl = _settingProvider.CreateSettingPanelAvalonia(); + HasNativeAvaloniaSettings = AvaloniaSettingControl != null; + System.Console.WriteLine($"Avalonia settings for {Name}: {HasNativeAvaloniaSettings}"); + } + catch (System.Exception ex) + { + System.Console.WriteLine($"Failed to create Avalonia settings for {Name}: {ex}"); + Flow.Launcher.Infrastructure.Logger.Log.Exception(nameof(PluginItemViewModel), $"Failed to create Avalonia settings for {Name}", ex); + } + } } [ObservableProperty] private bool _hasSettings; + [ObservableProperty] + private bool _hasNativeAvaloniaSettings; + + [ObservableProperty] + private AvaloniaControl? _avaloniaSettingControl; + + [ObservableProperty] + private bool _isExpanded; + [RelayCommand] private void OpenSettings() { if (_settingProvider == null) return; + if (HasNativeAvaloniaSettings) + { + IsExpanded = !IsExpanded; + return; + } + try { // Create the WPF settings panel on demand diff --git a/Flow.Launcher.Avalonia/Views/SettingPages/PluginsSettingsPage.axaml b/Flow.Launcher.Avalonia/Views/SettingPages/PluginsSettingsPage.axaml index 181c1d4e4..6f0e2beec 100644 --- a/Flow.Launcher.Avalonia/Views/SettingPages/PluginsSettingsPage.axaml +++ b/Flow.Launcher.Avalonia/Views/SettingPages/PluginsSettingsPage.axaml @@ -17,37 +17,50 @@ Watermark="{i18n:Localize search}" Margin="0,0,0,20" /> - - - - - - - - - - - - - - - - - + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + - - + + + diff --git a/Flow.Launcher.Core/packages.lock.json b/Flow.Launcher.Core/packages.lock.json index b499a5860..763ac07c2 100644 --- a/Flow.Launcher.Core/packages.lock.json +++ b/Flow.Launcher.Core/packages.lock.json @@ -61,6 +61,26 @@ "System.IO.Pipelines": "8.0.0" } }, + "Avalonia": { + "type": "Transitive", + "resolved": "11.2.3", + "contentHash": "pD6woFAUfGcyEvMmrpctntU4jv4fT8752pfx1J5iRORVX3Ob0oQi8PWo0TXVaAJZiSfH0cdKTeKx0w0DzD0/mg==", + "dependencies": { + "Avalonia.BuildServices": "0.0.29", + "Avalonia.Remote.Protocol": "11.2.3", + "MicroCom.Runtime": "0.11.0" + } + }, + "Avalonia.BuildServices": { + "type": "Transitive", + "resolved": "0.0.29", + "contentHash": "U4eJLQdoDNHXtEba7MZUCwrBErBTxFp6sUewXBOdAhU0Kwzwaa/EKFcYm8kpcysjzKtfB4S0S9n0uxKZFz/ikw==" + }, + "Avalonia.Remote.Protocol": { + "type": "Transitive", + "resolved": "11.2.3", + "contentHash": "6V0aNtld48WmO8tAlWwlRlUmXYcOWv+1eJUSl1ETF+1blUe5yhcSmuWarPprO0hDk8Ta6wGfdfcrnVl2gITYcA==" + }, "Ben.Demystifier": { "type": "Transitive", "resolved": "0.4.1", @@ -127,6 +147,11 @@ "resolved": "2.5.192", "contentHash": "jaJuwcgovWIZ8Zysdyf3b7b34/BrADw4v82GaEZymUhDd3ScMPrYd/cttekeDteJJPXseJxp04yTIcxiVUjTWg==" }, + "MicroCom.Runtime": { + "type": "Transitive", + "resolved": "0.11.0", + "contentHash": "MEnrZ3UIiH40hjzMDsxrTyi8dtqB5ziv3iBeeU4bXsL/7NLSal9F1lZKpK+tfBRnUoDSdtcW3KufE4yhATOMCA==" + }, "Microsoft.NET.StringTools": { "type": "Transitive", "resolved": "17.6.3", @@ -1161,7 +1186,7 @@ "Ben.Demystifier": "[0.4.1, )", "BitFaster.Caching": "[2.5.4, )", "CommunityToolkit.Mvvm": "[8.4.0, )", - "Flow.Launcher.Plugin": "[5.0.0, )", + "Flow.Launcher.Plugin": "[5.1.0, )", "InputSimulator": "[1.0.4, )", "MemoryPack": "[1.21.4, )", "Microsoft.VisualStudio.Threading": "[17.14.15, )", @@ -1176,6 +1201,7 @@ "flow.launcher.plugin": { "type": "Project", "dependencies": { + "Avalonia": "[11.2.3, )", "JetBrains.Annotations": "[2025.2.2, )" } } diff --git a/Flow.Launcher.Infrastructure/packages.lock.json b/Flow.Launcher.Infrastructure/packages.lock.json index 47c94d5f6..469d48ae3 100644 --- a/Flow.Launcher.Infrastructure/packages.lock.json +++ b/Flow.Launcher.Infrastructure/packages.lock.json @@ -121,6 +121,26 @@ "resolved": "3.1.0.3", "contentHash": "VKcf8sUq/+LyY99WgLhOu7Q32ROEyR30/2xCCj9ADRi45wVC7kpXrYCf9vH1qirkmrIfpL8inoxAbrqAlfXxsQ==" }, + "Avalonia": { + "type": "Transitive", + "resolved": "11.2.3", + "contentHash": "pD6woFAUfGcyEvMmrpctntU4jv4fT8752pfx1J5iRORVX3Ob0oQi8PWo0TXVaAJZiSfH0cdKTeKx0w0DzD0/mg==", + "dependencies": { + "Avalonia.BuildServices": "0.0.29", + "Avalonia.Remote.Protocol": "11.2.3", + "MicroCom.Runtime": "0.11.0" + } + }, + "Avalonia.BuildServices": { + "type": "Transitive", + "resolved": "0.0.29", + "contentHash": "U4eJLQdoDNHXtEba7MZUCwrBErBTxFp6sUewXBOdAhU0Kwzwaa/EKFcYm8kpcysjzKtfB4S0S9n0uxKZFz/ikw==" + }, + "Avalonia.Remote.Protocol": { + "type": "Transitive", + "resolved": "11.2.3", + "contentHash": "6V0aNtld48WmO8tAlWwlRlUmXYcOWv+1eJUSl1ETF+1blUe5yhcSmuWarPprO0hDk8Ta6wGfdfcrnVl2gITYcA==" + }, "JetBrains.Annotations": { "type": "Transitive", "resolved": "2025.2.2", @@ -136,6 +156,11 @@ "resolved": "1.21.4", "contentHash": "g14EsSS85yn0lHTi0J9ivqlZMf09A2iI51fmI+0KkzIzyCbWOBWPi5mdaY7YWmXprk12aYh9u/qfWHQUYthlwg==" }, + "MicroCom.Runtime": { + "type": "Transitive", + "resolved": "0.11.0", + "contentHash": "MEnrZ3UIiH40hjzMDsxrTyi8dtqB5ziv3iBeeU4bXsL/7NLSal9F1lZKpK+tfBRnUoDSdtcW3KufE4yhATOMCA==" + }, "Microsoft.VisualStudio.Threading.Analyzers": { "type": "Transitive", "resolved": "17.14.15", @@ -190,6 +215,7 @@ "flow.launcher.plugin": { "type": "Project", "dependencies": { + "Avalonia": "[11.2.3, )", "JetBrains.Annotations": "[2025.2.2, )" } } diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index 649d59ad7..25135e649 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -1,4 +1,4 @@ - + net9.0-windows @@ -68,6 +68,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Flow.Launcher.Plugin/Interfaces/ISettingProvider.cs b/Flow.Launcher.Plugin/Interfaces/ISettingProvider.cs index f034243c3..5cab51c71 100644 --- a/Flow.Launcher.Plugin/Interfaces/ISettingProvider.cs +++ b/Flow.Launcher.Plugin/Interfaces/ISettingProvider.cs @@ -1,4 +1,5 @@ -using System.Windows.Controls; +using System.Windows.Controls; +using AvaloniaControl = Avalonia.Controls.Control; namespace Flow.Launcher.Plugin { @@ -12,5 +13,12 @@ namespace Flow.Launcher.Plugin /// /// Control CreateSettingPanel(); + + /// + /// Create settings panel control for Avalonia version + /// + /// + virtual AvaloniaControl CreateSettingPanelAvalonia() => null; } } + diff --git a/Flow.Launcher.Plugin/packages.lock.json b/Flow.Launcher.Plugin/packages.lock.json index 70f71f20d..8e93f0fca 100644 --- a/Flow.Launcher.Plugin/packages.lock.json +++ b/Flow.Launcher.Plugin/packages.lock.json @@ -2,6 +2,17 @@ "version": 1, "dependencies": { "net9.0-windows7.0": { + "Avalonia": { + "type": "Direct", + "requested": "[11.2.3, )", + "resolved": "11.2.3", + "contentHash": "pD6woFAUfGcyEvMmrpctntU4jv4fT8752pfx1J5iRORVX3Ob0oQi8PWo0TXVaAJZiSfH0cdKTeKx0w0DzD0/mg==", + "dependencies": { + "Avalonia.BuildServices": "0.0.29", + "Avalonia.Remote.Protocol": "11.2.3", + "MicroCom.Runtime": "0.11.0" + } + }, "Fody": { "type": "Direct", "requested": "[6.9.3, )", @@ -44,6 +55,21 @@ "Fody": "6.6.4" } }, + "Avalonia.BuildServices": { + "type": "Transitive", + "resolved": "0.0.29", + "contentHash": "U4eJLQdoDNHXtEba7MZUCwrBErBTxFp6sUewXBOdAhU0Kwzwaa/EKFcYm8kpcysjzKtfB4S0S9n0uxKZFz/ikw==" + }, + "Avalonia.Remote.Protocol": { + "type": "Transitive", + "resolved": "11.2.3", + "contentHash": "6V0aNtld48WmO8tAlWwlRlUmXYcOWv+1eJUSl1ETF+1blUe5yhcSmuWarPprO0hDk8Ta6wGfdfcrnVl2gITYcA==" + }, + "MicroCom.Runtime": { + "type": "Transitive", + "resolved": "0.11.0", + "contentHash": "MEnrZ3UIiH40hjzMDsxrTyi8dtqB5ziv3iBeeU4bXsL/7NLSal9F1lZKpK+tfBRnUoDSdtcW3KufE4yhATOMCA==" + }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "8.0.0", diff --git a/Flow.Launcher/packages.lock.json b/Flow.Launcher/packages.lock.json index 2c685ea09..be2e9a195 100644 --- a/Flow.Launcher/packages.lock.json +++ b/Flow.Launcher/packages.lock.json @@ -163,6 +163,26 @@ "resolved": "6.3.0.90", "contentHash": "WVTb5MxwGqKdeasd3nG5udlV4t6OpvkFanziwI133K0/QJ5FvZmfzRQgpAjGTJhQfIA8GP7AzKQ3sTY9JOFk8Q==" }, + "Avalonia": { + "type": "Transitive", + "resolved": "11.2.3", + "contentHash": "pD6woFAUfGcyEvMmrpctntU4jv4fT8752pfx1J5iRORVX3Ob0oQi8PWo0TXVaAJZiSfH0cdKTeKx0w0DzD0/mg==", + "dependencies": { + "Avalonia.BuildServices": "0.0.29", + "Avalonia.Remote.Protocol": "11.2.3", + "MicroCom.Runtime": "0.11.0" + } + }, + "Avalonia.BuildServices": { + "type": "Transitive", + "resolved": "0.0.29", + "contentHash": "U4eJLQdoDNHXtEba7MZUCwrBErBTxFp6sUewXBOdAhU0Kwzwaa/EKFcYm8kpcysjzKtfB4S0S9n0uxKZFz/ikw==" + }, + "Avalonia.Remote.Protocol": { + "type": "Transitive", + "resolved": "11.2.3", + "contentHash": "6V0aNtld48WmO8tAlWwlRlUmXYcOWv+1eJUSl1ETF+1blUe5yhcSmuWarPprO0hDk8Ta6wGfdfcrnVl2gITYcA==" + }, "Ben.Demystifier": { "type": "Transitive", "resolved": "0.4.1", @@ -252,6 +272,11 @@ "resolved": "3.4.5", "contentHash": "R/d2VSdbRQHDWfPf5sjvMOWrWvxh/CswdMDKODppHTjsDLIkLQbQrnjmtAaVqu0qgUf8KFlVzEfxy3GIVoCK9g==" }, + "MicroCom.Runtime": { + "type": "Transitive", + "resolved": "0.11.0", + "contentHash": "MEnrZ3UIiH40hjzMDsxrTyi8dtqB5ziv3iBeeU4bXsL/7NLSal9F1lZKpK+tfBRnUoDSdtcW3KufE4yhATOMCA==" + }, "Microsoft.Extensions.Configuration": { "type": "Transitive", "resolved": "9.0.9", @@ -1599,7 +1624,7 @@ "Droplex": "[1.7.0, )", "FSharp.Core": "[9.0.303, )", "Flow.Launcher.Infrastructure": "[1.0.0, )", - "Flow.Launcher.Plugin": "[5.0.0, )", + "Flow.Launcher.Plugin": "[5.1.0, )", "Meziantou.Framework.Win32.Jobs": "[3.4.5, )", "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", "SemanticVersioning": "[3.0.0, )", @@ -1613,7 +1638,7 @@ "Ben.Demystifier": "[0.4.1, )", "BitFaster.Caching": "[2.5.4, )", "CommunityToolkit.Mvvm": "[8.4.0, )", - "Flow.Launcher.Plugin": "[5.0.0, )", + "Flow.Launcher.Plugin": "[5.1.0, )", "InputSimulator": "[1.0.4, )", "MemoryPack": "[1.21.4, )", "Microsoft.VisualStudio.Threading": "[17.14.15, )", @@ -1628,6 +1653,7 @@ "flow.launcher.plugin": { "type": "Project", "dependencies": { + "Avalonia": "[11.2.3, )", "JetBrains.Annotations": "[2025.2.2, )" } } diff --git a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj index e9515fab4..6fb36fa1a 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj +++ b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj @@ -1,4 +1,4 @@ - + Library @@ -62,6 +62,9 @@ + + + diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 456085fca..9fa969237 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -12,11 +12,13 @@ using Flow.Launcher.Plugin.Program.Views.Models; using Flow.Launcher.Plugin.SharedCommands; using Microsoft.Extensions.Caching.Memory; using Path = System.IO.Path; +using AvaloniaControl = Avalonia.Controls.Control; namespace Flow.Launcher.Plugin.Program { public class Main : ISettingProvider, IAsyncPlugin, IPluginI18n, IContextMenu, IAsyncReloadable, IDisposable { + private static readonly string ClassName = nameof(Main); private const string Win32CacheName = "Win32"; @@ -417,6 +419,14 @@ namespace Flow.Launcher.Plugin.Program return new ProgramSetting(Context, _settings); } + public AvaloniaControl CreateSettingPanelAvalonia() + { + System.Console.WriteLine("Program plugin: CreateSettingPanelAvalonia called!"); + Context.API.LogInfo(ClassName, "Creating Avalonia setting panel"); + return new Views.Avalonia.ProgramSetting(Context, _settings); + } + + public string GetTranslatedPluginTitle() { return Context.API.GetTranslation("flowlauncher_plugin_program_plugin_name"); diff --git a/Plugins/Flow.Launcher.Plugin.Program/ViewModels/ProgramSettingViewModel.cs b/Plugins/Flow.Launcher.Plugin.Program/ViewModels/ProgramSettingViewModel.cs new file mode 100644 index 000000000..78197447d --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Program/ViewModels/ProgramSettingViewModel.cs @@ -0,0 +1,337 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Flow.Launcher.Plugin.Program.Views.Models; +using Flow.Launcher.Plugin.Program.Views.Commands; +using Flow.Launcher.Plugin.Program.Programs; +using System.IO; +using System.Windows; +using Flow.Launcher.Plugin.Program.Views; +using Avalonia.Data.Converters; +using Avalonia.Media; +using System.Globalization; + +namespace Flow.Launcher.Plugin.Program.ViewModels; + +public partial class ProgramSettingViewModel : ObservableObject +{ + public static IValueConverter EnabledToColorConverter { get; } = new FuncValueConverter(enabled => + enabled ? Brushes.Green : Brushes.Red); + + public static IValueConverter IsNotNullConverter { get; } = new FuncValueConverter(obj => obj != null); + + private readonly PluginInitContext _context; + private readonly Settings _settings; + + [ObservableProperty] + private bool _enableUWP; + + [ObservableProperty] + private bool _enableStartMenuSource; + + [ObservableProperty] + private bool _enableRegistrySource; + + [ObservableProperty] + private bool _enablePATHSource; + + [ObservableProperty] + private bool _hideAppsPath; + + [ObservableProperty] + private bool _hideUninstallers; + + [ObservableProperty] + private bool _enableDescription; + + [ObservableProperty] + private bool _hideDuplicatedWindowsApp; + + public bool ShowUWPCheckbox => UWPPackage.SupportUWP(); + + [ObservableProperty] + private ObservableCollection _programSources = new(); + + [ObservableProperty] + private ProgramSource? _selectedProgramSource; + + [ObservableProperty] + private bool _isIndexing; + + [ObservableProperty] + private string _customSuffixes; + + [ObservableProperty] + private string _customProtocols; + + public ProgramSettingViewModel(PluginInitContext context, Settings settings) + { + _context = context; + _settings = settings; + + _enableUWP = _settings.EnableUWP; + _enableStartMenuSource = _settings.EnableStartMenuSource; + _enableRegistrySource = _settings.EnableRegistrySource; + _enablePATHSource = _settings.EnablePathSource; + _hideAppsPath = _settings.HideAppsPath; + _hideUninstallers = _settings.HideUninstallers; + _enableDescription = _settings.EnableDescription; + _hideDuplicatedWindowsApp = _settings.HideDuplicatedWindowsApp; + + _customSuffixes = string.Join(Settings.SuffixSeparator, _settings.CustomSuffixes); + _customProtocols = string.Join(Settings.SuffixSeparator, _settings.CustomProtocols); + + LoadProgramSources(); + } + + partial void OnCustomSuffixesChanged(string value) + { + _settings.CustomSuffixes = value.Split(Settings.SuffixSeparator, StringSplitOptions.RemoveEmptyEntries); + Main.ResetCache(); + } + + partial void OnCustomProtocolsChanged(string value) + { + _settings.CustomProtocols = value.Split(Settings.SuffixSeparator, StringSplitOptions.RemoveEmptyEntries); + Main.ResetCache(); + } + + public bool AppRefMS + { + get => _settings.BuiltinSuffixesStatus["appref-ms"]; + set + { + _settings.BuiltinSuffixesStatus["appref-ms"] = value; + OnPropertyChanged(); + Main.ResetCache(); + } + } + + public bool Exe + { + get => _settings.BuiltinSuffixesStatus["exe"]; + set + { + _settings.BuiltinSuffixesStatus["exe"] = value; + OnPropertyChanged(); + Main.ResetCache(); + } + } + + public bool Lnk + { + get => _settings.BuiltinSuffixesStatus["lnk"]; + set + { + _settings.BuiltinSuffixesStatus["lnk"] = value; + OnPropertyChanged(); + Main.ResetCache(); + } + } + + public bool Steam + { + get => _settings.BuiltinProtocolsStatus["steam"]; + set + { + _settings.BuiltinProtocolsStatus["steam"] = value; + OnPropertyChanged(); + Main.ResetCache(); + } + } + + public bool Epic + { + get => _settings.BuiltinProtocolsStatus["epic"]; + set + { + _settings.BuiltinProtocolsStatus["epic"] = value; + OnPropertyChanged(); + Main.ResetCache(); + } + } + + public bool Http + { + get => _settings.BuiltinProtocolsStatus["http"]; + set + { + _settings.BuiltinProtocolsStatus["http"] = value; + OnPropertyChanged(); + Main.ResetCache(); + } + } + + public bool UseCustomSuffixes + { + get => _settings.UseCustomSuffixes; + set + { + _settings.UseCustomSuffixes = value; + OnPropertyChanged(); + Main.ResetCache(); + } + } + + public bool UseCustomProtocols + { + get => _settings.UseCustomProtocols; + set + { + _settings.UseCustomProtocols = value; + OnPropertyChanged(); + Main.ResetCache(); + } + } + + + private void LoadProgramSources() + { + // For Avalonia, we want to maintain the same display list if possible, + // but for now let's just use a fresh one from settings + var sources = ProgramSettingDisplay.LoadProgramSources(); + ProgramSources = new ObservableCollection(sources); + + // We need to set the static list for ProgramSettingDisplay to work + ProgramSetting.ProgramSettingDisplayList = sources; + } + + [RelayCommand] + private async Task Reindex() + { + IsIndexing = true; + await Main.IndexProgramsAsync(); + IsIndexing = false; + } + + [RelayCommand] + private async Task LoadAllPrograms() + { + await ProgramSettingDisplay.DisplayAllProgramsAsync(); + // Refresh the observable collection + ProgramSources = new ObservableCollection(ProgramSetting.ProgramSettingDisplayList); + } + + [RelayCommand] + private async Task ToggleStatus() + { + if (SelectedProgramSource == null) return; + + var status = !SelectedProgramSource.Enabled; + await ProgramSettingDisplay.SetProgramSourcesStatusAsync(new List { SelectedProgramSource }, status); + + if (status) + ProgramSettingDisplay.RemoveDisabledFromSettings(); + else + ProgramSettingDisplay.StoreDisabledInSettings(); + + if (await new List { SelectedProgramSource }.IsReindexRequiredAsync()) + await Reindex(); + + // Trigger UI update for the item + OnPropertyChanged(nameof(ProgramSources)); + } + + [RelayCommand] + private async Task DeleteSource() + { + if (SelectedProgramSource == null) return; + + // Check if it's user added + if (!_settings.ProgramSources.Any(x => x.UniqueIdentifier == SelectedProgramSource.UniqueIdentifier)) + { + _context.API.ShowMsgBox(_context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source_select_user_added")); + return; + } + + if (_context.API.ShowMsgBox(_context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source"), + string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No) + { + return; + } + + var toDelete = SelectedProgramSource; + _settings.ProgramSources.RemoveAll(x => x.UniqueIdentifier == toDelete.UniqueIdentifier); + ProgramSetting.ProgramSettingDisplayList.Remove(toDelete); + ProgramSources.Remove(toDelete); + + if (await new List { toDelete }.IsReindexRequiredAsync()) + await Reindex(); + } + + [RelayCommand] + private async Task AddSource() + { + var dialog = new System.Windows.Forms.FolderBrowserDialog(); + if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) + { + var path = dialog.SelectedPath; + if (ProgramSources.Any(x => x.UniqueIdentifier.Equals(path, StringComparison.OrdinalIgnoreCase))) + { + _context.API.ShowMsgBox(_context.API.GetTranslation("flowlauncher_plugin_program_duplicate_program_source")); + return; + } + + var source = new ProgramSource(path, true); + _settings.ProgramSources.Add(source); + ProgramSetting.ProgramSettingDisplayList.Add(source); + ProgramSources.Add(source); + + await Reindex(); + } + } + + + + partial void OnEnableUWPChanged(bool value) + { + _settings.EnableUWP = value; + _ = Reindex(); + } + + partial void OnEnableStartMenuSourceChanged(bool value) + { + _settings.EnableStartMenuSource = value; + _ = Reindex(); + } + + partial void OnEnableRegistrySourceChanged(bool value) + { + _settings.EnableRegistrySource = value; + _ = Reindex(); + } + + partial void OnEnablePATHSourceChanged(bool value) + { + _settings.EnablePathSource = value; + _ = Reindex(); + } + + partial void OnHideAppsPathChanged(bool value) + { + _settings.HideAppsPath = value; + Main.ResetCache(); + } + + partial void OnHideUninstallersChanged(bool value) + { + _settings.HideUninstallers = value; + Main.ResetCache(); + } + + partial void OnEnableDescriptionChanged(bool value) + { + _settings.EnableDescription = value; + Main.ResetCache(); + } + + partial void OnHideDuplicatedWindowsAppChanged(bool value) + { + _settings.HideDuplicatedWindowsApp = value; + Main.ResetCache(); + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Program/Views/Avalonia/ProgramSetting.axaml b/Plugins/Flow.Launcher.Plugin.Program/Views/Avalonia/ProgramSetting.axaml new file mode 100644 index 000000000..4155715e9 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Program/Views/Avalonia/ProgramSetting.axaml @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +