diff --git a/Flow.Launcher/App.xaml b/Flow.Launcher/App.xaml
index b8e2a1cfe48..76a613c3f96 100644
--- a/Flow.Launcher/App.xaml
+++ b/Flow.Launcher/App.xaml
@@ -24,6 +24,7 @@
+
diff --git a/Flow.Launcher/CustomShortcutSetting.xaml.cs b/Flow.Launcher/CustomShortcutSetting.xaml.cs
index 097d6a53b8a..82efb48693c 100644
--- a/Flow.Launcher/CustomShortcutSetting.xaml.cs
+++ b/Flow.Launcher/CustomShortcutSetting.xaml.cs
@@ -8,7 +8,6 @@ namespace Flow.Launcher
{
public partial class CustomShortcutSetting : Window
{
- private SettingWindowViewModel viewModel;
public string Key { get; set; } = String.Empty;
public string Value { get; set; } = String.Empty;
private string originalKey { get; init; } = null;
@@ -17,13 +16,11 @@ public partial class CustomShortcutSetting : Window
public CustomShortcutSetting(SettingWindowViewModel vm)
{
- viewModel = vm;
InitializeComponent();
}
- public CustomShortcutSetting(string key, string value, SettingWindowViewModel vm)
+ public CustomShortcutSetting(string key, string value)
{
- viewModel = vm;
Key = key;
Value = value;
originalKey = key;
@@ -46,8 +43,7 @@ private void BtnAdd_OnClick(object sender, RoutedEventArgs e)
return;
}
// Check if key is modified or adding a new one
- if (((update && originalKey != Key) || !update)
- && viewModel.ShortcutExists(Key))
+ if ((update && originalKey != Key) || !update)
{
MessageBox.Show(InternationalizationManager.Instance.GetTranslation("duplicateShortcut"));
return;
diff --git a/Flow.Launcher/Images/info.png b/Flow.Launcher/Images/info.png
new file mode 100644
index 00000000000..75ffe11e0e8
Binary files /dev/null and b/Flow.Launcher/Images/info.png differ
diff --git a/Flow.Launcher/Images/keyboard.png b/Flow.Launcher/Images/keyboard.png
new file mode 100644
index 00000000000..5ac670419ab
Binary files /dev/null and b/Flow.Launcher/Images/keyboard.png differ
diff --git a/Flow.Launcher/Images/plugins.png b/Flow.Launcher/Images/plugins.png
new file mode 100644
index 00000000000..cb1e5906c3e
Binary files /dev/null and b/Flow.Launcher/Images/plugins.png differ
diff --git a/Flow.Launcher/Images/proxy.png b/Flow.Launcher/Images/proxy.png
new file mode 100644
index 00000000000..ded7126aa08
Binary files /dev/null and b/Flow.Launcher/Images/proxy.png differ
diff --git a/Flow.Launcher/Images/store.png b/Flow.Launcher/Images/store.png
new file mode 100644
index 00000000000..a4b19c8f933
Binary files /dev/null and b/Flow.Launcher/Images/store.png differ
diff --git a/Flow.Launcher/Images/theme.png b/Flow.Launcher/Images/theme.png
new file mode 100644
index 00000000000..9518343b7fe
Binary files /dev/null and b/Flow.Launcher/Images/theme.png differ
diff --git a/Flow.Launcher/Resources/Controls/Card.xaml b/Flow.Launcher/Resources/Controls/Card.xaml
index 24a89548513..c29a5f6025b 100644
--- a/Flow.Launcher/Resources/Controls/Card.xaml
+++ b/Flow.Launcher/Resources/Controls/Card.xaml
@@ -20,6 +20,7 @@
+
@@ -36,6 +37,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Flow.Launcher/Resources/Controls/Card.xaml.cs b/Flow.Launcher/Resources/Controls/Card.xaml.cs
index 06266c77507..c8f788acab0 100644
--- a/Flow.Launcher/Resources/Controls/Card.xaml.cs
+++ b/Flow.Launcher/Resources/Controls/Card.xaml.cs
@@ -16,6 +16,7 @@ public Card()
{
InitializeComponent();
}
+
public string Title
{
get { return (string)GetValue(TitleProperty); }
diff --git a/Flow.Launcher/Resources/Controls/CardGroup.xaml b/Flow.Launcher/Resources/Controls/CardGroup.xaml
new file mode 100644
index 00000000000..f48bf4b6c9f
--- /dev/null
+++ b/Flow.Launcher/Resources/Controls/CardGroup.xaml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Flow.Launcher/Resources/Controls/CardGroup.xaml.cs b/Flow.Launcher/Resources/Controls/CardGroup.xaml.cs
new file mode 100644
index 00000000000..b9588275c7f
--- /dev/null
+++ b/Flow.Launcher/Resources/Controls/CardGroup.xaml.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Flow.Launcher.Resources.Controls;
+
+public partial class CardGroup : UserControl
+{
+ public enum CardGroupPosition
+ {
+ NotInGroup,
+ First,
+ Middle,
+ Last
+ }
+
+ public new ObservableCollection Content
+ {
+ get { return (ObservableCollection)GetValue(ContentProperty); }
+ set { SetValue(ContentProperty, value); }
+ }
+
+ public static new readonly DependencyProperty ContentProperty =
+ DependencyProperty.Register(nameof(Content), typeof(ObservableCollection), typeof(CardGroup));
+
+ public static readonly DependencyProperty PositionProperty = DependencyProperty.RegisterAttached(
+ "Position", typeof(CardGroupPosition), typeof(CardGroup),
+ new FrameworkPropertyMetadata(CardGroupPosition.NotInGroup, FrameworkPropertyMetadataOptions.AffectsRender)
+ );
+
+ public static void SetPosition(UIElement element, CardGroupPosition value)
+ {
+ element.SetValue(PositionProperty, value);
+ }
+
+ public static CardGroupPosition GetPosition(UIElement element)
+ {
+ return (CardGroupPosition)element.GetValue(PositionProperty);
+ }
+
+ public CardGroup()
+ {
+ InitializeComponent();
+ Content = new ObservableCollection();
+ }
+}
diff --git a/Flow.Launcher/Resources/Controls/CardGroupCardStyleSelector.cs b/Flow.Launcher/Resources/Controls/CardGroupCardStyleSelector.cs
new file mode 100644
index 00000000000..605934e80c1
--- /dev/null
+++ b/Flow.Launcher/Resources/Controls/CardGroupCardStyleSelector.cs
@@ -0,0 +1,21 @@
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Flow.Launcher.Resources.Controls;
+
+public class CardGroupCardStyleSelector : StyleSelector
+{
+ public Style FirstStyle { get; set; }
+ public Style MiddleStyle { get; set; }
+ public Style LastStyle { get; set; }
+
+ public override Style SelectStyle(object item, DependencyObject container)
+ {
+ var itemsControl = ItemsControl.ItemsControlFromItemContainer(container);
+ var index = itemsControl.ItemContainerGenerator.IndexFromContainer(container);
+
+ if (index == 0) return FirstStyle;
+ if (index == itemsControl.Items.Count - 1) return LastStyle;
+ return MiddleStyle;
+ }
+}
diff --git a/Flow.Launcher/Resources/Controls/HyperLink.xaml b/Flow.Launcher/Resources/Controls/HyperLink.xaml
new file mode 100644
index 00000000000..9ea550afd51
--- /dev/null
+++ b/Flow.Launcher/Resources/Controls/HyperLink.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/Flow.Launcher/Resources/Controls/HyperLink.xaml.cs b/Flow.Launcher/Resources/Controls/HyperLink.xaml.cs
new file mode 100644
index 00000000000..855cccdbd60
--- /dev/null
+++ b/Flow.Launcher/Resources/Controls/HyperLink.xaml.cs
@@ -0,0 +1,39 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Navigation;
+
+namespace Flow.Launcher.Resources.Controls;
+
+public partial class HyperLink : UserControl
+{
+ public static readonly DependencyProperty UriProperty = DependencyProperty.Register(
+ nameof(Uri), typeof(string), typeof(HyperLink), new PropertyMetadata(default(string))
+ );
+
+ public string Uri
+ {
+ get => (string)GetValue(UriProperty);
+ set => SetValue(UriProperty, value);
+ }
+
+ public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
+ nameof(Text), typeof(string), typeof(HyperLink), new PropertyMetadata(default(string))
+ );
+
+ public string Text
+ {
+ get => (string)GetValue(TextProperty);
+ set => SetValue(TextProperty, value);
+ }
+
+ public HyperLink()
+ {
+ InitializeComponent();
+ }
+
+ private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e)
+ {
+ App.API.OpenUrl(e.Uri);
+ e.Handled = true;
+ }
+}
diff --git a/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml b/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml
new file mode 100644
index 00000000000..c2ed4975bf8
--- /dev/null
+++ b/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml
@@ -0,0 +1,224 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml.cs b/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml.cs
new file mode 100644
index 00000000000..dfa03a2043f
--- /dev/null
+++ b/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml.cs
@@ -0,0 +1,9 @@
+namespace Flow.Launcher.Resources.Controls;
+
+public partial class InstalledPluginDisplay
+{
+ public InstalledPluginDisplay()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/Flow.Launcher/Resources/CustomControlTemplate.xaml b/Flow.Launcher/Resources/CustomControlTemplate.xaml
index 2ce53bfdbc0..cbb8285829b 100644
--- a/Flow.Launcher/Resources/CustomControlTemplate.xaml
+++ b/Flow.Launcher/Resources/CustomControlTemplate.xaml
@@ -1487,10 +1487,11 @@
0.1,0.9 0.2,1.0
+
+
+
+ 48
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Flow.Launcher/Resources/MarkupExtensions/CollapsedWhenExtension.cs b/Flow.Launcher/Resources/MarkupExtensions/CollapsedWhenExtension.cs
new file mode 100644
index 00000000000..cdcd49339d8
--- /dev/null
+++ b/Flow.Launcher/Resources/MarkupExtensions/CollapsedWhenExtension.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Windows;
+using System.Windows.Data;
+using System.Windows.Markup;
+
+namespace Flow.Launcher.Resources.MarkupExtensions;
+
+#nullable enable
+
+public class CollapsedWhenExtension : MarkupExtension {
+ private Binding? When { get; set; }
+ public object? IsEqualTo { get; set; }
+
+ public bool? IsEqualToBool
+ {
+ get => IsEqualTo switch
+ {
+ bool b => b,
+ _ => null
+ };
+ set => IsEqualTo = value;
+ }
+
+ public int? IsEqualToInt
+ {
+ get => IsEqualTo switch
+ {
+ int i => i,
+ _ => null
+ };
+ set => IsEqualTo = value;
+ }
+
+ protected virtual Visibility DefaultVisibility => Visibility.Visible;
+ protected virtual Visibility InvertedVisibility => Visibility.Collapsed;
+
+ public CollapsedWhenExtension(Binding when)
+ {
+ When = when;
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider) {
+ if (serviceProvider.GetService(typeof(IProvideValueTarget)) is not IProvideValueTarget provideValueTarget)
+ return DependencyProperty.UnsetValue;
+
+ if (provideValueTarget is not { TargetObject: not null, TargetProperty: not null })
+ return DependencyProperty.UnsetValue;
+
+
+ if (When is null)
+ return DependencyProperty.UnsetValue;
+
+ if (IsEqualTo is Binding isEqualToBinding)
+ {
+ var multiBinding = new MultiBinding
+ {
+ Converter = new HideableVisibilityConverter
+ {
+ DefaultVisibility = DefaultVisibility,
+ InvertedVisibility = InvertedVisibility
+ },
+ Bindings = { When, isEqualToBinding }
+ };
+
+ return multiBinding.ProvideValue(serviceProvider);
+ }
+
+ When.Converter = new HideableVisibilityConverter
+ {
+ DefaultVisibility = DefaultVisibility,
+ InvertedVisibility = InvertedVisibility,
+ IsEqualTo = IsEqualTo
+ };
+ return When.ProvideValue(serviceProvider);
+ }
+}
diff --git a/Flow.Launcher/Resources/MarkupExtensions/HideableVisibilityConverter.cs b/Flow.Launcher/Resources/MarkupExtensions/HideableVisibilityConverter.cs
new file mode 100644
index 00000000000..e7d1fc6ead1
--- /dev/null
+++ b/Flow.Launcher/Resources/MarkupExtensions/HideableVisibilityConverter.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace Flow.Launcher.Resources.MarkupExtensions;
+
+#nullable enable
+
+public class HideableVisibilityConverter : IMultiValueConverter, IValueConverter {
+ public Visibility DefaultVisibility { get; init; } = Visibility.Visible;
+ public Visibility InvertedVisibility { get; init; } = Visibility.Collapsed;
+
+ public object? IsEqualTo { get; set; }
+
+ public object Convert(object?[] values, Type targetType, object? parameter, CultureInfo culture) {
+ if (values is not { Length: 2 })
+ return DependencyProperty.UnsetValue;
+
+ var value1 = values[0];
+ var value2 = values[1];
+ if (value1 is Enum enum1 && value2 is Enum enum2)
+ {
+ value1 = System.Convert.ToInt32(enum1);
+ value2 = System.Convert.ToInt32(enum2);
+ }
+
+ return Equals(value1, value2) ? InvertedVisibility : DefaultVisibility;
+ }
+
+ public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ return Equals(value, IsEqualTo) ? InvertedVisibility : DefaultVisibility;
+ }
+
+ public object[] ConvertBack(object? value, Type[] targetTypes, object? parameter, CultureInfo culture) {
+ throw new NotSupportedException();
+ }
+
+ public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ throw new NotSupportedException();
+ }
+}
diff --git a/Flow.Launcher/Resources/MarkupExtensions/VisibleWhenExtension.cs b/Flow.Launcher/Resources/MarkupExtensions/VisibleWhenExtension.cs
new file mode 100644
index 00000000000..b5cc26ae537
--- /dev/null
+++ b/Flow.Launcher/Resources/MarkupExtensions/VisibleWhenExtension.cs
@@ -0,0 +1,14 @@
+using System.Windows;
+using System.Windows.Data;
+
+namespace Flow.Launcher.Resources.MarkupExtensions;
+
+#nullable enable
+
+public class VisibleWhenExtension : CollapsedWhenExtension
+{
+ protected override Visibility DefaultVisibility => Visibility.Collapsed;
+ protected override Visibility InvertedVisibility => Visibility.Visible;
+
+ public VisibleWhenExtension(Binding when) : base(when) { }
+}
diff --git a/Flow.Launcher/Resources/SettingWindowStyle.xaml b/Flow.Launcher/Resources/SettingWindowStyle.xaml
new file mode 100644
index 00000000000..2c73094c744
--- /dev/null
+++ b/Flow.Launcher/Resources/SettingWindowStyle.xaml
@@ -0,0 +1,413 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Flow.Launcher/SettingPages/ViewModels/DropdownDataGeneric.cs b/Flow.Launcher/SettingPages/ViewModels/DropdownDataGeneric.cs
new file mode 100644
index 00000000000..025c73f85df
--- /dev/null
+++ b/Flow.Launcher/SettingPages/ViewModels/DropdownDataGeneric.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using Flow.Launcher.Core.Resource;
+using Flow.Launcher.Plugin;
+
+namespace Flow.Launcher.SettingPages.ViewModels;
+
+public class DropdownDataGeneric : BaseModel where TValue : Enum
+{
+ public string Display { get; set; }
+ public TValue Value { get; set; }
+
+ public static List GetValues
(string keyPrefix) where TR : DropdownDataGeneric, new()
+ {
+ var data = new List();
+ var enumValues = (TValue[])Enum.GetValues(typeof(TValue));
+
+ foreach (var value in enumValues)
+ {
+ var key = keyPrefix + value;
+ var display = InternationalizationManager.Instance.GetTranslation(key);
+ data.Add(new TR { Display = display, Value = value });
+ }
+
+ return data;
+ }
+}
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs
new file mode 100644
index 00000000000..b6563d3e502
--- /dev/null
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+using CommunityToolkit.Mvvm.Input;
+using Flow.Launcher.Core;
+using Flow.Launcher.Core.Plugin;
+using Flow.Launcher.Core.Resource;
+using Flow.Launcher.Infrastructure;
+using Flow.Launcher.Infrastructure.UserSettings;
+using Flow.Launcher.Plugin;
+
+namespace Flow.Launcher.SettingPages.ViewModels;
+
+public partial class SettingsPaneAboutViewModel : BaseModel
+{
+ private readonly Settings _settings;
+ private readonly Updater _updater;
+
+ public string LogFolderSize
+ {
+ get
+ {
+ var size = GetLogFiles().Sum(file => file.Length);
+ return $"{InternationalizationManager.Instance.GetTranslation("clearlogfolder")} ({BytesToReadableString(size)})";
+ }
+ }
+
+ public string Website => Constant.Website;
+ public string SponsorPage => Constant.SponsorPage;
+ public string ReleaseNotes => _updater.GitHubRepository + "/releases/latest";
+ public string Documentation => Constant.Documentation;
+ public string Docs => Constant.Docs;
+ public string Github => Constant.GitHub;
+
+ public string Version => Constant.Version switch
+ {
+ "1.0.0" => Constant.Dev,
+ _ => Constant.Version
+ };
+
+ public string ActivatedTimes => string.Format(
+ InternationalizationManager.Instance.GetTranslation("about_activate_times"),
+ _settings.ActivateTimes
+ );
+
+ public SettingsPaneAboutViewModel(Settings settings, Updater updater)
+ {
+ _settings = settings;
+ _updater = updater;
+ }
+
+ [RelayCommand]
+ private void OpenWelcomeWindow()
+ {
+ var window = new WelcomeWindow(_settings);
+ window.ShowDialog();
+ }
+
+ [RelayCommand]
+ private void AskClearLogFolderConfirmation()
+ {
+ var confirmResult = MessageBox.Show(
+ InternationalizationManager.Instance.GetTranslation("clearlogfolderMessage"),
+ InternationalizationManager.Instance.GetTranslation("clearlogfolder"),
+ MessageBoxButton.YesNo
+ );
+
+ if (confirmResult == MessageBoxResult.Yes)
+ {
+ ClearLogFolder();
+ }
+ }
+
+ [RelayCommand]
+ private void OpenSettingsFolder()
+ {
+ PluginManager.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Settings));
+ }
+
+ [RelayCommand]
+ private void OpenLogsFolder()
+ {
+ App.API.OpenDirectory(GetLogDir(Constant.Version).FullName);
+ }
+
+ [RelayCommand]
+ private Task UpdateApp() => _updater.UpdateAppAsync(App.API, false);
+
+ private void ClearLogFolder()
+ {
+ var logDirectory = GetLogDir();
+ var logFiles = GetLogFiles();
+
+ logFiles.ForEach(f => f.Delete());
+
+ logDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
+ .Where(dir => !Constant.Version.Equals(dir.Name))
+ .ToList()
+ .ForEach(dir => dir.Delete());
+
+ OnPropertyChanged(nameof(LogFolderSize));
+ }
+
+ private static DirectoryInfo GetLogDir(string version = "")
+ {
+ return new DirectoryInfo(Path.Combine(DataLocation.DataDirectory(), Constant.Logs, version));
+ }
+
+ private static List GetLogFiles(string version = "")
+ {
+ return GetLogDir(version).EnumerateFiles("*", SearchOption.AllDirectories).ToList();
+ }
+
+ private static string BytesToReadableString(long bytes)
+ {
+ const int scale = 1024;
+ string[] orders = { "GB", "MB", "KB", "B" };
+ long max = (long)Math.Pow(scale, orders.Length - 1);
+
+ foreach (string order in orders)
+ {
+ if (bytes > max)
+ return $"{decimal.Divide(bytes, max):##.##} {order}";
+
+ max /= scale;
+ }
+
+ return "0 B";
+ }
+
+}
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
new file mode 100644
index 00000000000..a9718a0ac19
--- /dev/null
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
@@ -0,0 +1,235 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Forms;
+using CommunityToolkit.Mvvm.Input;
+using Flow.Launcher.Core;
+using Flow.Launcher.Core.Configuration;
+using Flow.Launcher.Core.Resource;
+using Flow.Launcher.Helper;
+using Flow.Launcher.Infrastructure.UserSettings;
+using Flow.Launcher.Plugin;
+using Flow.Launcher.Plugin.SharedModels;
+
+namespace Flow.Launcher.SettingPages.ViewModels;
+
+public partial class SettingsPaneGeneralViewModel : BaseModel
+{
+ public Settings Settings { get; }
+ private readonly Updater _updater;
+ private readonly IPortable _portable;
+
+ public SettingsPaneGeneralViewModel(Settings settings, Updater updater, IPortable portable)
+ {
+ Settings = settings;
+ _updater = updater;
+ _portable = portable;
+ UpdateLastQueryModeDisplay();
+ }
+
+ public class SearchWindowScreen : DropdownDataGeneric { }
+ public class SearchWindowAlign : DropdownDataGeneric { }
+ // todo a better name?
+ public class LastQueryMode : DropdownDataGeneric { }
+
+ public bool StartFlowLauncherOnSystemStartup
+ {
+ get => Settings.StartFlowLauncherOnSystemStartup;
+ set
+ {
+ Settings.StartFlowLauncherOnSystemStartup = value;
+
+ try
+ {
+ if (value)
+ AutoStartup.Enable();
+ else
+ AutoStartup.Disable();
+ }
+ catch (Exception e)
+ {
+ Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"),
+ e.Message);
+ }
+ }
+ }
+
+
+ public List SearchWindowScreens =>
+ DropdownDataGeneric.GetValues("SearchWindowScreen");
+
+ public List SearchWindowAligns =>
+ DropdownDataGeneric.GetValues("SearchWindowAlign");
+
+ public List ScreenNumbers
+ {
+ get
+ {
+ var screens = Screen.AllScreens;
+ var screenNumbers = new List();
+ for (int i = 1; i <= screens.Length; i++)
+ {
+ screenNumbers.Add(i);
+ }
+
+ return screenNumbers;
+ }
+ }
+
+ // This is only required to set at startup. When portable mode enabled/disabled a restart is always required
+ private bool _portableMode = DataLocation.PortableDataLocationInUse();
+
+ public bool PortableMode
+ {
+ get => _portableMode;
+ set
+ {
+ if (!_portable.CanUpdatePortability())
+ return;
+
+ if (DataLocation.PortableDataLocationInUse())
+ {
+ _portable.DisablePortableMode();
+ }
+ else
+ {
+ _portable.EnablePortableMode();
+ }
+ }
+ }
+
+ private List _lastQueryModes = new();
+
+ public List LastQueryModes
+ {
+ get
+ {
+ if (_lastQueryModes.Count == 0)
+ {
+ _lastQueryModes =
+ DropdownDataGeneric
+ .GetValues("LastQuery");
+ }
+
+ return _lastQueryModes;
+ }
+ }
+
+ private void UpdateLastQueryModeDisplay()
+ {
+ foreach (var item in LastQueryModes)
+ {
+ item.Display = InternationalizationManager.Instance.GetTranslation($"LastQuery{item.Value}");
+ }
+ }
+
+ public string Language
+ {
+ get => Settings.Language;
+ set
+ {
+ InternationalizationManager.Instance.ChangeLanguage(value);
+
+ if (InternationalizationManager.Instance.PromptShouldUsePinyin(value))
+ ShouldUsePinyin = true;
+
+ UpdateLastQueryModeDisplay();
+ }
+ }
+
+ public bool ShouldUsePinyin
+ {
+ get => Settings.ShouldUsePinyin;
+ set => Settings.ShouldUsePinyin = value;
+ }
+
+ public List QuerySearchPrecisionStrings => Enum
+ .GetValues(typeof(SearchPrecisionScore))
+ .Cast()
+ .Select(v => v.ToString())
+ .ToList();
+
+ public List Languages => InternationalizationManager.Instance.LoadAvailableLanguages();
+ public IEnumerable MaxResultsRange => Enumerable.Range(2, 16);
+
+ public string AlwaysPreviewToolTip => string.Format(
+ InternationalizationManager.Instance.GetTranslation("AlwaysPreviewToolTip"),
+ Settings.PreviewHotkey
+ );
+
+ private string GetFileFromDialog(string title, string filter = "")
+ {
+ var dlg = new OpenFileDialog
+ {
+ InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
+ Multiselect = false,
+ CheckFileExists = true,
+ CheckPathExists = true,
+ Title = title,
+ Filter = filter
+ };
+
+ return dlg.ShowDialog() switch
+ {
+ DialogResult.OK => dlg.FileName,
+ _ => string.Empty
+ };
+ }
+
+ private void UpdateApp()
+ {
+ _ = _updater.UpdateAppAsync(App.API, false);
+ }
+
+ public bool AutoUpdates
+ {
+ get => Settings.AutoUpdates;
+ set
+ {
+ Settings.AutoUpdates = value;
+
+ if (value)
+ {
+ UpdateApp();
+ }
+ }
+ }
+
+ [RelayCommand]
+ private void SelectPython()
+ {
+ var selectedFile = GetFileFromDialog(
+ InternationalizationManager.Instance.GetTranslation("selectPythonExecutable"),
+ "Python|pythonw.exe"
+ );
+
+ if (!string.IsNullOrEmpty(selectedFile))
+ Settings.PluginSettings.PythonExecutablePath = selectedFile;
+ }
+
+ [RelayCommand]
+ private void SelectNode()
+ {
+ var selectedFile = GetFileFromDialog(
+ InternationalizationManager.Instance.GetTranslation("selectNodeExecutable"),
+ "*.exe"
+ );
+
+ if (!string.IsNullOrEmpty(selectedFile))
+ Settings.PluginSettings.NodeExecutablePath = selectedFile;
+ }
+
+ [RelayCommand]
+ private void SelectFileManager()
+ {
+ var fileManagerChangeWindow = new SelectFileManagerWindow(Settings);
+ fileManagerChangeWindow.ShowDialog();
+ }
+
+ [RelayCommand]
+ private void SelectBrowser()
+ {
+ var browserWindow = new SelectBrowserWindow(Settings);
+ browserWindow.ShowDialog();
+ }
+}
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs
new file mode 100644
index 00000000000..7eb05d9450e
--- /dev/null
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneHotkeyViewModel.cs
@@ -0,0 +1,134 @@
+using System.Windows;
+using CommunityToolkit.Mvvm.Input;
+using Flow.Launcher.Core.Resource;
+using Flow.Launcher.Helper;
+using Flow.Launcher.Infrastructure;
+using Flow.Launcher.Infrastructure.Hotkey;
+using Flow.Launcher.Infrastructure.UserSettings;
+using Flow.Launcher.Plugin;
+
+namespace Flow.Launcher.SettingPages.ViewModels;
+
+public partial class SettingsPaneHotkeyViewModel : BaseModel
+{
+ public Settings Settings { get; }
+
+ public CustomPluginHotkey SelectedCustomPluginHotkey { get; set; }
+ public CustomShortcutModel SelectedCustomShortcut { get; set; }
+
+ public string[] OpenResultModifiersList => new[]
+ {
+ KeyConstant.Alt,
+ KeyConstant.Ctrl,
+ $"{KeyConstant.Ctrl}+{KeyConstant.Alt}"
+ };
+
+ public SettingsPaneHotkeyViewModel(Settings settings)
+ {
+ Settings = settings;
+ }
+
+ [RelayCommand]
+ private void SetTogglingHotkey(HotkeyModel hotkey)
+ {
+ HotKeyMapper.SetHotkey(hotkey, HotKeyMapper.OnToggleHotkey);
+ }
+
+ [RelayCommand]
+ private void CustomHotkeyDelete()
+ {
+ var item = SelectedCustomPluginHotkey;
+ if (item is null)
+ {
+ MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
+ return;
+ }
+
+ var result = MessageBox.Show(
+ string.Format(
+ InternationalizationManager.Instance.GetTranslation("deleteCustomHotkeyWarning"), item.Hotkey
+ ),
+ InternationalizationManager.Instance.GetTranslation("delete"),
+ MessageBoxButton.YesNo
+ );
+
+ if (result is MessageBoxResult.Yes)
+ {
+ Settings.CustomPluginHotkeys.Remove(item);
+ HotKeyMapper.RemoveHotkey(item.Hotkey);
+ }
+ }
+
+ [RelayCommand]
+ private void CustomHotkeyEdit()
+ {
+ var item = SelectedCustomPluginHotkey;
+ if (item is null)
+ {
+ MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
+ return;
+ }
+
+ var window = new CustomQueryHotkeySetting(null, Settings);
+ window.UpdateItem(item);
+ window.ShowDialog();
+ }
+
+ [RelayCommand]
+ private void CustomHotkeyAdd()
+ {
+ new CustomQueryHotkeySetting(null, Settings).ShowDialog();
+ }
+
+ [RelayCommand]
+ private void CustomShortcutDelete()
+ {
+ var item = SelectedCustomShortcut;
+ if (item is null)
+ {
+ MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
+ return;
+ }
+
+ var result = MessageBox.Show(
+ string.Format(
+ InternationalizationManager.Instance.GetTranslation("deleteCustomShortcutWarning"), item.Key, item.Value
+ ),
+ InternationalizationManager.Instance.GetTranslation("delete"),
+ MessageBoxButton.YesNo
+ );
+
+ if (result is MessageBoxResult.Yes)
+ {
+ Settings.CustomShortcuts.Remove(item);
+ }
+ }
+
+ [RelayCommand]
+ private void CustomShortcutEdit()
+ {
+ var item = SelectedCustomShortcut;
+ if (item is null)
+ {
+ MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
+ return;
+ }
+
+ var window = new CustomShortcutSetting(item.Key, item.Value);
+ if (window.ShowDialog() is not true) return;
+
+ var index = Settings.CustomShortcuts.IndexOf(item);
+ Settings.CustomShortcuts[index] = new CustomShortcutModel(window.Key, window.Value);
+ }
+
+ [RelayCommand]
+ private void CustomShortcutAdd()
+ {
+ var window = new CustomShortcutSetting(null);
+ if (window.ShowDialog() is true)
+ {
+ var shortcut = new CustomShortcutModel(window.Key, window.Value);
+ Settings.CustomShortcuts.Add(shortcut);
+ }
+ }
+}
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
new file mode 100644
index 00000000000..e069310119b
--- /dev/null
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using CommunityToolkit.Mvvm.Input;
+using Flow.Launcher.Core.ExternalPlugins;
+using Flow.Launcher.Infrastructure;
+using Flow.Launcher.Plugin;
+using Flow.Launcher.ViewModel;
+
+namespace Flow.Launcher.SettingPages.ViewModels;
+
+public partial class SettingsPanePluginStoreViewModel : BaseModel
+{
+ public string FilterText { get; set; } = string.Empty;
+
+ public IList ExternalPlugins => PluginsManifest.UserPlugins
+ .Select(p => new PluginStoreItemViewModel(p))
+ .OrderByDescending(p => p.Category == PluginStoreItemViewModel.NewRelease)
+ .ThenByDescending(p => p.Category == PluginStoreItemViewModel.RecentlyUpdated)
+ .ThenByDescending(p => p.Category == PluginStoreItemViewModel.None)
+ .ThenByDescending(p => p.Category == PluginStoreItemViewModel.Installed)
+ .ToList();
+
+ [RelayCommand]
+ private async Task RefreshExternalPluginsAsync()
+ {
+ await PluginsManifest.UpdateManifestAsync();
+ OnPropertyChanged(nameof(ExternalPlugins));
+ }
+
+ public bool SatisfiesFilter(PluginStoreItemViewModel plugin)
+ {
+ return string.IsNullOrEmpty(FilterText) ||
+ StringMatcher.FuzzySearch(FilterText, plugin.Name).IsSearchPrecisionScoreMet() ||
+ StringMatcher.FuzzySearch(FilterText, plugin.Description).IsSearchPrecisionScoreMet();
+ }
+}
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs
new file mode 100644
index 00000000000..dd9e5786de1
--- /dev/null
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs
@@ -0,0 +1,44 @@
+using System.Collections.Generic;
+using System.Linq;
+using Flow.Launcher.Core.Plugin;
+using Flow.Launcher.Infrastructure;
+using Flow.Launcher.Infrastructure.UserSettings;
+using Flow.Launcher.Plugin;
+using Flow.Launcher.ViewModel;
+
+#nullable enable
+
+namespace Flow.Launcher.SettingPages.ViewModels;
+
+public class SettingsPanePluginsViewModel : BaseModel
+{
+ private readonly Settings _settings;
+
+ public SettingsPanePluginsViewModel(Settings settings)
+ {
+ _settings = settings;
+ }
+
+ public string FilterText { get; set; } = string.Empty;
+
+ public PluginViewModel? SelectedPlugin { get; set; }
+
+ private IEnumerable? _pluginViewModels;
+ private IEnumerable PluginViewModels => _pluginViewModels ??= PluginManager.AllPlugins
+ .OrderBy(plugin => plugin.Metadata.Disabled)
+ .ThenBy(plugin => plugin.Metadata.Name)
+ .Select(plugin => new PluginViewModel
+ {
+ PluginPair = plugin,
+ PluginSettingsObject = _settings.PluginSettings.Plugins[plugin.Metadata.ID]
+ })
+ .ToList();
+
+ public List FilteredPluginViewModels => PluginViewModels
+ .Where(v =>
+ string.IsNullOrEmpty(FilterText) ||
+ StringMatcher.FuzzySearch(FilterText, v.PluginPair.Metadata.Name).IsSearchPrecisionScoreMet() ||
+ StringMatcher.FuzzySearch(FilterText, v.PluginPair.Metadata.Description).IsSearchPrecisionScoreMet()
+ )
+ .ToList();
+}
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneProxyViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneProxyViewModel.cs
new file mode 100644
index 00000000000..2dd57809db2
--- /dev/null
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneProxyViewModel.cs
@@ -0,0 +1,62 @@
+using System.Net;
+using System.Windows;
+using CommunityToolkit.Mvvm.Input;
+using Flow.Launcher.Core;
+using Flow.Launcher.Core.Resource;
+using Flow.Launcher.Infrastructure.UserSettings;
+using Flow.Launcher.Plugin;
+
+namespace Flow.Launcher.SettingPages.ViewModels;
+
+public partial class SettingsPaneProxyViewModel : BaseModel
+{
+ private readonly Updater _updater;
+ public Settings Settings { get; }
+
+ public SettingsPaneProxyViewModel(Settings settings, Updater updater)
+ {
+ _updater = updater;
+ Settings = settings;
+ }
+
+ [RelayCommand]
+ private void OnTestProxyClicked()
+ {
+ var message = TestProxy();
+ MessageBox.Show(InternationalizationManager.Instance.GetTranslation(message));
+ }
+
+ private string TestProxy()
+ {
+ if (string.IsNullOrEmpty(Settings.Proxy.Server)) return "serverCantBeEmpty";
+ if (Settings.Proxy.Port <= 0) return "portCantBeEmpty";
+
+ HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_updater.GitHubRepository);
+
+ if (string.IsNullOrEmpty(Settings.Proxy.UserName) || string.IsNullOrEmpty(Settings.Proxy.Password))
+ {
+ request.Proxy = new WebProxy(Settings.Proxy.Server, Settings.Proxy.Port);
+ }
+ else
+ {
+ request.Proxy = new WebProxy(Settings.Proxy.Server, Settings.Proxy.Port)
+ {
+ Credentials = new NetworkCredential(Settings.Proxy.UserName, Settings.Proxy.Password)
+ };
+ }
+
+ try
+ {
+ var response = (HttpWebResponse)request.GetResponse();
+ return response.StatusCode switch
+ {
+ HttpStatusCode.OK => "proxyIsCorrect",
+ _ => "proxyConnectFailed"
+ };
+ }
+ catch
+ {
+ return "proxyConnectFailed";
+ }
+ }
+}
diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs
new file mode 100644
index 00000000000..89f4fe15c5d
--- /dev/null
+++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs
@@ -0,0 +1,392 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using CommunityToolkit.Mvvm.Input;
+using Flow.Launcher.Core.Resource;
+using Flow.Launcher.Helper;
+using Flow.Launcher.Infrastructure;
+using Flow.Launcher.Infrastructure.UserSettings;
+using Flow.Launcher.Plugin;
+using Flow.Launcher.ViewModel;
+using ModernWpf;
+using ThemeManager = Flow.Launcher.Core.Resource.ThemeManager;
+using ThemeManagerForColorSchemeSwitch = ModernWpf.ThemeManager;
+
+namespace Flow.Launcher.SettingPages.ViewModels;
+
+public partial class SettingsPaneThemeViewModel : BaseModel
+{
+ private CultureInfo Culture => CultureInfo.DefaultThreadCurrentCulture;
+
+ public Settings Settings { get; }
+
+ public static string LinkHowToCreateTheme => @"https://flowlauncher.com/docs/#/how-to-create-a-theme";
+ public static string LinkThemeGallery => "https://github.com/Flow-Launcher/Flow.Launcher/discussions/1438";
+
+ public string SelectedTheme
+ {
+ get => Settings.Theme;
+ set
+ {
+ ThemeManager.Instance.ChangeTheme(value);
+
+ if (ThemeManager.Instance.BlurEnabled && Settings.UseDropShadowEffect)
+ DropShadowEffect = false;
+ }
+ }
+
+ public bool DropShadowEffect
+ {
+ get => Settings.UseDropShadowEffect;
+ set
+ {
+ if (ThemeManager.Instance.BlurEnabled && value)
+ {
+ MessageBox.Show(InternationalizationManager.Instance.GetTranslation("shadowEffectNotAllowed"));
+ return;
+ }
+
+ if (value)
+ {
+ ThemeManager.Instance.AddDropShadowEffectToCurrentTheme();
+ }
+ else
+ {
+ ThemeManager.Instance.RemoveDropShadowEffectFromCurrentTheme();
+ }
+
+ Settings.UseDropShadowEffect = value;
+ }
+ }
+
+ public List Themes =>
+ ThemeManager.Instance.LoadAvailableThemes().Select(Path.GetFileNameWithoutExtension).ToList();
+
+
+ public class ColorScheme
+ {
+ public string Display { get; set; }
+ public ColorSchemes Value { get; set; }
+ }
+
+ public List ColorSchemes
+ {
+ get
+ {
+ List modes = new List();
+ var enums = (ColorSchemes[])Enum.GetValues(typeof(ColorSchemes));
+ foreach (var e in enums)
+ {
+ var key = $"ColorScheme{e}";
+ var display = InternationalizationManager.Instance.GetTranslation(key);
+ var m = new ColorScheme { Display = display, Value = e, };
+ modes.Add(m);
+ }
+
+ return modes;
+ }
+ }
+
+ public List TimeFormatList { get; } = new()
+ {
+ "h:mm",
+ "hh:mm",
+ "H:mm",
+ "HH:mm",
+ "tt h:mm",
+ "tt hh:mm",
+ "h:mm tt",
+ "hh:mm tt",
+ "hh:mm:ss tt",
+ "HH:mm:ss"
+ };
+
+ public List DateFormatList { get; } = new()
+ {
+ "MM'/'dd dddd",
+ "MM'/'dd ddd",
+ "MM'/'dd",
+ "MM'-'dd",
+ "MMMM', 'dd",
+ "dd'/'MM",
+ "dd'-'MM",
+ "ddd MM'/'dd",
+ "dddd MM'/'dd",
+ "dddd",
+ "ddd dd'/'MM",
+ "dddd dd'/'MM",
+ "dddd dd', 'MMMM",
+ "dd', 'MMMM"
+ };
+
+ public string TimeFormat
+ {
+ get => Settings.TimeFormat;
+ set => Settings.TimeFormat = value;
+ }
+
+ public string DateFormat
+ {
+ get => Settings.DateFormat;
+ set => Settings.DateFormat = value;
+ }
+
+ public string ClockText => DateTime.Now.ToString(TimeFormat, Culture);
+
+ public string DateText => DateTime.Now.ToString(DateFormat, Culture);
+
+ public double WindowWidthSize
+ {
+ get => Settings.WindowSize;
+ set => Settings.WindowSize = value;
+ }
+
+ public bool UseGlyphIcons
+ {
+ get => Settings.UseGlyphIcons;
+ set => Settings.UseGlyphIcons = value;
+ }
+
+ public bool UseAnimation
+ {
+ get => Settings.UseAnimation;
+ set => Settings.UseAnimation = value;
+ }
+
+ public class AnimationSpeed
+ {
+ public string Display { get; set; }
+ public AnimationSpeeds Value { get; set; }
+ }
+
+ public List AnimationSpeeds
+ {
+ get
+ {
+ List speeds = new List();
+ var enums = (AnimationSpeeds[])Enum.GetValues(typeof(AnimationSpeeds));
+ foreach (var e in enums)
+ {
+ var key = $"AnimationSpeed{e}";
+ var display = InternationalizationManager.Instance.GetTranslation(key);
+ var m = new AnimationSpeed { Display = display, Value = e, };
+ speeds.Add(m);
+ }
+
+ return speeds;
+ }
+ }
+
+ public bool UseSound
+ {
+ get => Settings.UseSound;
+ set => Settings.UseSound = value;
+ }
+
+ public double SoundEffectVolume
+ {
+ get => Settings.SoundVolume;
+ set => Settings.SoundVolume = value;
+ }
+
+ public bool UseClock
+ {
+ get => Settings.UseClock;
+ set => Settings.UseClock = value;
+ }
+
+ public bool UseDate
+ {
+ get => Settings.UseDate;
+ set => Settings.UseDate = value;
+ }
+
+ public Brush PreviewBackground
+ {
+ get
+ {
+ var wallpaper = WallpaperPathRetrieval.GetWallpaperPath();
+ if (wallpaper is not null && File.Exists(wallpaper))
+ {
+ var memStream = new MemoryStream(File.ReadAllBytes(wallpaper));
+ var bitmap = new BitmapImage();
+ bitmap.BeginInit();
+ bitmap.StreamSource = memStream;
+ bitmap.DecodePixelWidth = 800;
+ bitmap.DecodePixelHeight = 600;
+ bitmap.EndInit();
+ return new ImageBrush(bitmap) { Stretch = Stretch.UniformToFill };
+ }
+
+ var wallpaperColor = WallpaperPathRetrieval.GetWallpaperColor();
+ return new SolidColorBrush(wallpaperColor);
+ }
+ }
+
+ public ResultsViewModel PreviewResults
+ {
+ get
+ {
+ var results = new List
+ {
+ new Result
+ {
+ Title = InternationalizationManager.Instance.GetTranslation("SampleTitleExplorer"),
+ SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleExplorer"),
+ IcoPath = Path.Combine(
+ Constant.ProgramDirectory,
+ @"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png"
+ )
+ },
+ new Result
+ {
+ Title = InternationalizationManager.Instance.GetTranslation("SampleTitleWebSearch"),
+ SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleWebSearch"),
+ IcoPath = Path.Combine(
+ Constant.ProgramDirectory,
+ @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png"
+ )
+ },
+ new Result
+ {
+ Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProgram"),
+ SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProgram"),
+ IcoPath = Path.Combine(
+ Constant.ProgramDirectory,
+ @"Plugins\Flow.Launcher.Plugin.Program\Images\program.png"
+ )
+ },
+ new Result
+ {
+ Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProcessKiller"),
+ SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProcessKiller"),
+ IcoPath = Path.Combine(
+ Constant.ProgramDirectory,
+ @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png"
+ )
+ }
+ };
+ var vm = new ResultsViewModel(Settings);
+ vm.AddResults(results, "PREVIEW");
+ return vm;
+ }
+ }
+
+ public FontFamily SelectedQueryBoxFont
+ {
+ get
+ {
+ var fontExists = Fonts.SystemFontFamilies.Any(
+ fontFamily =>
+ fontFamily.FamilyNames.Values != null &&
+ fontFamily.FamilyNames.Values.Contains(Settings.QueryBoxFont)
+ );
+
+ return fontExists switch
+ {
+ true => new FontFamily(Settings.QueryBoxFont),
+ _ => new FontFamily("Segoe UI")
+ };
+ }
+ set
+ {
+ Settings.QueryBoxFont = value.ToString();
+ ThemeManager.Instance.ChangeTheme(Settings.Theme);
+ }
+ }
+
+ public FamilyTypeface SelectedQueryBoxFontFaces
+ {
+ get
+ {
+ var typeface = SyntaxSugars.CallOrRescueDefault(
+ () => SelectedQueryBoxFont.ConvertFromInvariantStringsOrNormal(
+ Settings.QueryBoxFontStyle,
+ Settings.QueryBoxFontWeight,
+ Settings.QueryBoxFontStretch
+ )
+ );
+ return typeface;
+ }
+ set
+ {
+ Settings.QueryBoxFontStretch = value.Stretch.ToString();
+ Settings.QueryBoxFontWeight = value.Weight.ToString();
+ Settings.QueryBoxFontStyle = value.Style.ToString();
+ ThemeManager.Instance.ChangeTheme(Settings.Theme);
+ }
+ }
+
+ public FontFamily SelectedResultFont
+ {
+ get
+ {
+ var fontExists = Fonts.SystemFontFamilies.Any(
+ fontFamily =>
+ fontFamily.FamilyNames.Values != null &&
+ fontFamily.FamilyNames.Values.Contains(Settings.ResultFont)
+ );
+ return fontExists switch
+ {
+ true => new FontFamily(Settings.ResultFont),
+ _ => new FontFamily("Segoe UI")
+ };
+ }
+ set
+ {
+ Settings.ResultFont = value.ToString();
+ ThemeManager.Instance.ChangeTheme(Settings.Theme);
+ }
+ }
+
+ public FamilyTypeface SelectedResultFontFaces
+ {
+ get
+ {
+ var typeface = SyntaxSugars.CallOrRescueDefault(
+ () => SelectedResultFont.ConvertFromInvariantStringsOrNormal(
+ Settings.ResultFontStyle,
+ Settings.ResultFontWeight,
+ Settings.ResultFontStretch
+ )
+ );
+ return typeface;
+ }
+ set
+ {
+ Settings.ResultFontStretch = value.Stretch.ToString();
+ Settings.ResultFontWeight = value.Weight.ToString();
+ Settings.ResultFontStyle = value.Style.ToString();
+ ThemeManager.Instance.ChangeTheme(Settings.Theme);
+ }
+ }
+
+ public string ThemeImage => Constant.QueryTextBoxIconImagePath;
+
+ [RelayCommand]
+ private void OpenThemesFolder()
+ {
+ App.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Themes));
+ }
+
+ public void UpdateColorScheme()
+ {
+ ThemeManagerForColorSchemeSwitch.Current.ApplicationTheme = Settings.ColorScheme switch
+ {
+ Constant.Light => ApplicationTheme.Light,
+ Constant.Dark => ApplicationTheme.Dark,
+ Constant.System => null,
+ _ => ThemeManagerForColorSchemeSwitch.Current.ApplicationTheme
+ };
+ }
+
+ public SettingsPaneThemeViewModel(Settings settings)
+ {
+ Settings = settings;
+ }
+}
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneAbout.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneAbout.xaml
new file mode 100644
index 00000000000..15afd7741e5
--- /dev/null
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneAbout.xaml
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneAbout.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneAbout.xaml.cs
new file mode 100644
index 00000000000..de6a4df5ee8
--- /dev/null
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneAbout.xaml.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Windows.Navigation;
+using Flow.Launcher.SettingPages.ViewModels;
+
+namespace Flow.Launcher.SettingPages.Views;
+
+public partial class SettingsPaneAbout
+{
+ private SettingsPaneAboutViewModel _viewModel = null!;
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ if (!IsInitialized)
+ {
+ if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings, Updater: { } updater })
+ throw new ArgumentException("Settings are required for SettingsPaneAbout.");
+ _viewModel = new SettingsPaneAboutViewModel(settings, updater);
+ DataContext = _viewModel;
+ InitializeComponent();
+ }
+ base.OnNavigatedTo(e);
+ }
+
+ private void OnRequestNavigate(object sender, RequestNavigateEventArgs e)
+ {
+ App.API.OpenUrl(e.Uri.AbsoluteUri);
+ e.Handled = true;
+ }
+}
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
new file mode 100644
index 00000000000..95505165d33
--- /dev/null
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml.cs
new file mode 100644
index 00000000000..f95015b2eef
--- /dev/null
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Windows.Navigation;
+using Flow.Launcher.Infrastructure.UserSettings;
+using Flow.Launcher.SettingPages.ViewModels;
+
+namespace Flow.Launcher.SettingPages.Views;
+
+public partial class SettingsPaneGeneral
+{
+ private SettingsPaneGeneralViewModel _viewModel = null!;
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ if (!IsInitialized)
+ {
+ if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings, Updater: {} updater, Portable: {} portable })
+ throw new ArgumentException("Settings, Updater and Portable are required for SettingsPaneGeneral.");
+ _viewModel = new SettingsPaneGeneralViewModel(settings, updater, portable);
+ DataContext = _viewModel;
+ InitializeComponent();
+ }
+ base.OnNavigatedTo(e);
+ }
+}
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml
new file mode 100644
index 00000000000..2698e860950
--- /dev/null
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml
@@ -0,0 +1,452 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs
new file mode 100644
index 00000000000..061eabf515d
--- /dev/null
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Windows.Navigation;
+using Flow.Launcher.SettingPages.ViewModels;
+
+namespace Flow.Launcher.SettingPages.Views;
+
+public partial class SettingsPaneHotkey
+{
+ private SettingsPaneHotkeyViewModel _viewModel = null!;
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ if (!IsInitialized)
+ {
+ if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings })
+ throw new ArgumentException("Settings are required for SettingsPaneHotkey.");
+ _viewModel = new SettingsPaneHotkeyViewModel(settings);
+ DataContext = _viewModel;
+ InitializeComponent();
+ }
+ base.OnNavigatedTo(e);
+ }
+}
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml
new file mode 100644
index 00000000000..675cef7de1b
--- /dev/null
+++ b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml
@@ -0,0 +1,352 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs
new file mode 100644
index 00000000000..dfb4a7eaf6c
--- /dev/null
+++ b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs
@@ -0,0 +1,66 @@
+using System;
+using System.ComponentModel;
+using System.Windows.Data;
+using System.Windows.Input;
+using System.Windows.Navigation;
+using Flow.Launcher.Core.Plugin;
+using Flow.Launcher.SettingPages.ViewModels;
+using Flow.Launcher.ViewModel;
+
+namespace Flow.Launcher.SettingPages.Views;
+
+public partial class SettingsPanePluginStore
+{
+ private SettingsPanePluginStoreViewModel _viewModel = null!;
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ if (!IsInitialized)
+ {
+ if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings })
+ throw new ArgumentException($"Settings are required for {nameof(SettingsPanePluginStore)}.");
+ _viewModel = new SettingsPanePluginStoreViewModel();
+ DataContext = _viewModel;
+ InitializeComponent();
+ }
+ _viewModel.PropertyChanged += ViewModel_PropertyChanged;
+ base.OnNavigatedTo(e);
+ }
+
+ private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(SettingsPanePluginStoreViewModel.FilterText))
+ {
+ ((CollectionViewSource)FindResource("PluginStoreCollectionView")).View.Refresh();
+ }
+ }
+
+ protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
+ {
+ _viewModel.PropertyChanged -= ViewModel_PropertyChanged;
+ base.OnNavigatingFrom(e);
+ }
+
+ private void SettingsPanePlugins_OnKeyDown(object sender, KeyEventArgs e)
+ {
+ if (e.Key is not Key.F || Keyboard.Modifiers is not ModifierKeys.Control) return;
+ PluginStoreFilterTextbox.Focus();
+ }
+
+ private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e)
+ {
+ PluginManager.API.OpenUrl(e.Uri.AbsoluteUri);
+ e.Handled = true;
+ }
+
+ private void PluginStoreCollectionView_OnFilter(object sender, FilterEventArgs e)
+ {
+ if (e.Item is not PluginStoreItemViewModel plugin)
+ {
+ e.Accepted = false;
+ return;
+ }
+
+ e.Accepted = _viewModel.SatisfiesFilter(plugin);
+ }
+}
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml b/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml
new file mode 100644
index 00000000000..37079a46fef
--- /dev/null
+++ b/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml.cs
new file mode 100644
index 00000000000..d48505c3dbe
--- /dev/null
+++ b/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Windows.Input;
+using System.Windows.Navigation;
+using Flow.Launcher.SettingPages.ViewModels;
+
+namespace Flow.Launcher.SettingPages.Views;
+
+public partial class SettingsPanePlugins
+{
+ private SettingsPanePluginsViewModel _viewModel = null!;
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ if (!IsInitialized)
+ {
+ if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings })
+ throw new ArgumentException("Settings are required for SettingsPaneHotkey.");
+ _viewModel = new SettingsPanePluginsViewModel(settings);
+ DataContext = _viewModel;
+ InitializeComponent();
+ }
+ base.OnNavigatedTo(e);
+ }
+
+ private void SettingsPanePlugins_OnKeyDown(object sender, KeyEventArgs e)
+ {
+ if (e.Key is not Key.F || Keyboard.Modifiers is not ModifierKeys.Control) return;
+ PluginFilterTextbox.Focus();
+ }
+}
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml
new file mode 100644
index 00000000000..63a43a8fb3b
--- /dev/null
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml.cs
new file mode 100644
index 00000000000..95c88d6277b
--- /dev/null
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Windows.Navigation;
+using Flow.Launcher.SettingPages.ViewModels;
+
+namespace Flow.Launcher.SettingPages.Views;
+
+public partial class SettingsPaneProxy
+{
+ private SettingsPaneProxyViewModel _viewModel = null!;
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ if (!IsInitialized)
+ {
+ if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings, Updater: { } updater })
+ throw new ArgumentException($"Settings are required for {nameof(SettingsPaneProxy)}.");
+ _viewModel = new SettingsPaneProxyViewModel(settings, updater);
+ DataContext = _viewModel;
+ InitializeComponent();
+ }
+
+ base.OnNavigatedTo(e);
+ }
+}
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml
new file mode 100644
index 00000000000..3b581767c10
--- /dev/null
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml
@@ -0,0 +1,492 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml.cs
new file mode 100644
index 00000000000..93cf7ad18bf
--- /dev/null
+++ b/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Windows.Controls;
+using System.Windows.Navigation;
+using Flow.Launcher.SettingPages.ViewModels;
+using Page = ModernWpf.Controls.Page;
+
+namespace Flow.Launcher.SettingPages.Views;
+
+public partial class SettingsPaneTheme : Page
+{
+ private SettingsPaneThemeViewModel _viewModel = null!;
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ if (!IsInitialized)
+ {
+ if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings })
+ throw new ArgumentException($"Settings are required for {nameof(SettingsPaneTheme)}.");
+ _viewModel = new SettingsPaneThemeViewModel(settings);
+ DataContext = _viewModel;
+ InitializeComponent();
+ }
+
+ base.OnNavigatedTo(e);
+ }
+
+ private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ _viewModel.UpdateColorScheme();
+ }
+}
diff --git a/Flow.Launcher/SettingWindow.xaml b/Flow.Launcher/SettingWindow.xaml
index a8de26af7c9..6194db201e3 100644
--- a/Flow.Launcher/SettingWindow.xaml
+++ b/Flow.Launcher/SettingWindow.xaml
@@ -2,18 +2,10 @@
x:Class="Flow.Launcher.SettingWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:cc="clr-namespace:Flow.Launcher.Resources.Controls"
- xmlns:converters="clr-namespace:Flow.Launcher.Converters"
- xmlns:core="clr-namespace:Flow.Launcher.Core.Resource;assembly=Flow.Launcher.Core"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:flowlauncher="clr-namespace:Flow.Launcher"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
- xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:ui="http://schemas.modernwpf.com/2019"
- xmlns:userSettings="clr-namespace:Flow.Launcher.Infrastructure.UserSettings;assembly=Flow.Launcher.Infrastructure"
xmlns:vm="clr-namespace:Flow.Launcher.ViewModel"
- xmlns:wpftk="clr-namespace:WpfToolkit.Controls;assembly=VirtualizingWrapPanel"
Title="{DynamicResource flowlauncher_settings}"
Width="{Binding SettingWindowWidth, Mode=TwoWay}"
Height="{Binding SettingWindowHeight, Mode=TwoWay}"
@@ -40,566 +32,162 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -609,2940 +197,72 @@
Grid.Row="0"
Width="50"
Height="50"
+ RenderOptions.BitmapScalingMode="HighQuality"
Source="images/app.png" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ TextAlignment="Center" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/Flow.Launcher/SettingWindow.xaml.cs b/Flow.Launcher/SettingWindow.xaml.cs
index 1a75a853bef..957379ce42d 100644
--- a/Flow.Launcher/SettingWindow.xaml.cs
+++ b/Flow.Launcher/SettingWindow.xaml.cs
@@ -1,507 +1,172 @@
-using Flow.Launcher.Core.Plugin;
-using Flow.Launcher.Core.Resource;
+using System;
+using System.Windows;
+using System.Windows.Forms;
+using System.Windows.Input;
+using System.Windows.Interop;
+using Flow.Launcher.Core;
+using Flow.Launcher.Core.Configuration;
using Flow.Launcher.Helper;
-using Flow.Launcher.Infrastructure;
-using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
+using Flow.Launcher.SettingPages.Views;
using Flow.Launcher.ViewModel;
-using ModernWpf;
using ModernWpf.Controls;
-using System;
-using System.ComponentModel;
-using System.IO;
-using System.Windows;
-using System.Windows.Data;
-using System.Windows.Forms;
-using System.Windows.Input;
-using System.Windows.Interop;
-using System.Windows.Navigation;
-using NHotkey;
-using Button = System.Windows.Controls.Button;
-using Control = System.Windows.Controls.Control;
-using KeyEventArgs = System.Windows.Input.KeyEventArgs;
-using MessageBox = System.Windows.MessageBox;
using TextBox = System.Windows.Controls.TextBox;
-using ThemeManager = ModernWpf.ThemeManager;
-
-namespace Flow.Launcher
-{
- public partial class SettingWindow
- {
- public readonly IPublicAPI API;
- private Settings settings;
- private SettingWindowViewModel viewModel;
-
- public SettingWindow(IPublicAPI api, SettingWindowViewModel viewModel)
- {
- settings = viewModel.Settings;
- DataContext = viewModel;
- this.viewModel = viewModel;
- API = api;
- InitializePosition();
- InitializeComponent();
-
- }
-
- #region General
-
- private void OnLoaded(object sender, RoutedEventArgs e)
- {
- RefreshMaximizeRestoreButton();
- // Fix (workaround) for the window freezes after lock screen (Win+L)
- // https://stackoverflow.com/questions/4951058/software-rendering-mode-wpf
- HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource;
- HwndTarget hwndTarget = hwndSource.CompositionTarget;
- hwndTarget.RenderMode = RenderMode.SoftwareOnly;
-
- pluginListView = (CollectionView)CollectionViewSource.GetDefaultView(Plugins.ItemsSource);
- pluginListView.Filter = PluginListFilter;
-
- pluginStoreView = (CollectionView)CollectionViewSource.GetDefaultView(StoreListBox.ItemsSource);
- pluginStoreView.Filter = PluginStoreFilter;
-
- viewModel.PropertyChanged += new PropertyChangedEventHandler(SettingsWindowViewModelChanged);
-
- InitializePosition();
- }
-
- private void SettingsWindowViewModelChanged(object sender, PropertyChangedEventArgs e)
- {
- if (e.PropertyName == nameof(viewModel.ExternalPlugins))
- {
- pluginStoreView = (CollectionView)CollectionViewSource.GetDefaultView(StoreListBox.ItemsSource);
- pluginStoreView.Filter = PluginStoreFilter;
- pluginStoreView.Refresh();
- }
- }
-
- private void OnSelectPythonPathClick(object sender, RoutedEventArgs e)
- {
- var selectedFile = viewModel.GetFileFromDialog(
- InternationalizationManager.Instance.GetTranslation("selectPythonExecutable"),
- "Python|pythonw.exe");
-
- if (!string.IsNullOrEmpty(selectedFile))
- settings.PluginSettings.PythonExecutablePath = selectedFile;
- }
-
- private void OnSelectNodePathClick(object sender, RoutedEventArgs e)
- {
- var selectedFile = viewModel.GetFileFromDialog(
- InternationalizationManager.Instance.GetTranslation("selectNodeExecutable"));
-
- if (!string.IsNullOrEmpty(selectedFile))
- settings.PluginSettings.NodeExecutablePath = selectedFile;
- }
-
- private void OnSelectFileManagerClick(object sender, RoutedEventArgs e)
- {
- SelectFileManagerWindow fileManagerChangeWindow = new SelectFileManagerWindow(settings);
- fileManagerChangeWindow.ShowDialog();
- }
-
- private void OnSelectDefaultBrowserClick(object sender, RoutedEventArgs e)
- {
- var browserWindow = new SelectBrowserWindow(settings);
- browserWindow.ShowDialog();
- }
-
- #endregion
-
- #region Hotkey
-
- private void OnToggleHotkey(object sender, HotkeyEventArgs e)
- {
- HotKeyMapper.OnToggleHotkey(sender, e);
- }
-
- private void OnDeleteCustomHotkeyClick(object sender, RoutedEventArgs e)
- {
- var item = viewModel.SelectedCustomPluginHotkey;
- if (item == null)
- {
- MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
- return;
- }
-
- string deleteWarning =
- string.Format(InternationalizationManager.Instance.GetTranslation("deleteCustomHotkeyWarning"),
- item.Hotkey);
- if (
- MessageBox.Show(deleteWarning, InternationalizationManager.Instance.GetTranslation("delete"),
- MessageBoxButton.YesNo) == MessageBoxResult.Yes)
- {
- settings.CustomPluginHotkeys.Remove(item);
- HotKeyMapper.RemoveHotkey(item.Hotkey);
- }
- }
-
- private void OnEditCustomHotkeyClick(object sender, RoutedEventArgs e)
- {
- var item = viewModel.SelectedCustomPluginHotkey;
- if (item != null)
- {
- CustomQueryHotkeySetting window = new CustomQueryHotkeySetting(this, settings);
- window.UpdateItem(item);
- window.ShowDialog();
- }
- else
- {
- MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
- }
- }
-
- private void OnAddCustomHotkeyClick(object sender, RoutedEventArgs e)
- {
- new CustomQueryHotkeySetting(this, settings).ShowDialog();
- }
-
- #endregion
-
- #region Plugin
-
- private void OnPluginToggled(object sender, RoutedEventArgs e)
- {
- var id = viewModel.SelectedPlugin.PluginPair.Metadata.ID;
- // used to sync the current status from the plugin manager into the setting to keep consistency after save
- settings.PluginSettings.Plugins[id].Disabled = viewModel.SelectedPlugin.PluginPair.Metadata.Disabled;
- }
-
- private void OnPluginPriorityClick(object sender, RoutedEventArgs e)
- {
- if (sender is Control { DataContext: PluginViewModel pluginViewModel })
- {
- PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(pluginViewModel.PluginPair.Metadata.ID, pluginViewModel);
- priorityChangeWindow.ShowDialog();
- }
- }
-
- #endregion
-
- #region Proxy
-
- private void OnTestProxyClick(object sender, RoutedEventArgs e)
- { // TODO: change to command
- var msg = viewModel.TestProxy();
- MessageBox.Show(msg); // TODO: add message box service
- }
-
- #endregion
-
- private void OnCheckUpdates(object sender, RoutedEventArgs e)
- {
- viewModel.UpdateApp(); // TODO: change to command
- }
-
- private void OnRequestNavigate(object sender, RequestNavigateEventArgs e)
- {
- API.OpenUrl(e.Uri.AbsoluteUri);
- e.Handled = true;
- }
-
- private void OnClosed(object sender, EventArgs e)
- {
- settings.SettingWindowState = WindowState;
- settings.SettingWindowTop = Top;
- settings.SettingWindowLeft = Left;
- viewModel.Save();
- API.SavePluginSettings();
- }
-
- private void OnCloseExecuted(object sender, ExecutedRoutedEventArgs e)
- {
- Close();
- }
-
- private void OpenThemeFolder(object sender, RoutedEventArgs e)
- {
- PluginManager.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Themes));
- }
-
- private void OpenSettingFolder(object sender, RoutedEventArgs e)
- {
- PluginManager.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Settings));
- }
-
- private void OpenWelcomeWindow(object sender, RoutedEventArgs e)
- {
- var WelcomeWindow = new WelcomeWindow(settings);
- WelcomeWindow.ShowDialog();
- }
- private void OpenLogFolder(object sender, RoutedEventArgs e)
- {
- viewModel.OpenLogFolder();
- }
- private void ClearLogFolder(object sender, RoutedEventArgs e)
- {
- var confirmResult = MessageBox.Show(
- InternationalizationManager.Instance.GetTranslation("clearlogfolderMessage"),
- InternationalizationManager.Instance.GetTranslation("clearlogfolder"),
- MessageBoxButton.YesNo);
-
- if (confirmResult == MessageBoxResult.Yes)
- {
- viewModel.ClearLogFolder();
- }
- }
-
- private void OnExternalPluginInstallClick(object sender, RoutedEventArgs e)
- {
- if (sender is not Button { DataContext: PluginStoreItemViewModel plugin } button)
- {
- return;
- }
-
- if (storeClickedButton != null)
- {
- FlyoutService.GetFlyout(storeClickedButton).Hide();
- }
- viewModel.DisplayPluginQuery($"install {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7"));
- }
-
- private void OnExternalPluginUninstallClick(object sender, MouseButtonEventArgs e)
- {
- if (e.ChangedButton == MouseButton.Left)
- {
- var name = viewModel.SelectedPlugin.PluginPair.Metadata.Name;
- viewModel.DisplayPluginQuery($"uninstall {name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7"));
- }
- }
-
- private void OnExternalPluginUninstallClick(object sender, RoutedEventArgs e)
- {
- if (storeClickedButton != null)
- {
- FlyoutService.GetFlyout(storeClickedButton).Hide();
- }
-
- if (sender is Button { DataContext: PluginStoreItemViewModel plugin })
- viewModel.DisplayPluginQuery($"uninstall {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7"));
-
- }
-
- private void OnExternalPluginUpdateClick(object sender, RoutedEventArgs e)
- {
- if (storeClickedButton != null)
- {
- FlyoutService.GetFlyout(storeClickedButton).Hide();
- }
- if (sender is Button { DataContext: PluginStoreItemViewModel plugin })
- viewModel.DisplayPluginQuery($"update {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7"));
-
- }
-
- private void window_MouseDown(object sender, MouseButtonEventArgs e) /* for close hotkey popup */
- {
- if (Keyboard.FocusedElement is not TextBox textBox)
- {
- return;
- }
- var tRequest = new TraversalRequest(FocusNavigationDirection.Next);
- textBox.MoveFocus(tRequest);
- }
-
- private void ColorSchemeSelectedIndexChanged(object sender, EventArgs e)
- => ThemeManager.Current.ApplicationTheme = settings.ColorScheme switch
- {
- Constant.Light => ApplicationTheme.Light,
- Constant.Dark => ApplicationTheme.Dark,
- Constant.System => null,
- _ => ThemeManager.Current.ApplicationTheme
- };
-
- /* Custom TitleBar */
-
- private void OnMinimizeButtonClick(object sender, RoutedEventArgs e)
- {
- WindowState = WindowState.Minimized;
- }
+namespace Flow.Launcher;
- private void OnMaximizeRestoreButtonClick(object sender, RoutedEventArgs e)
- {
- WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
- }
-
- private void OnCloseButtonClick(object sender, RoutedEventArgs e)
- {
-
- Close();
- }
-
- private void RefreshMaximizeRestoreButton()
- {
- if (WindowState == WindowState.Maximized)
- {
- maximizeButton.Visibility = Visibility.Collapsed;
- restoreButton.Visibility = Visibility.Visible;
- }
- else
- {
- maximizeButton.Visibility = Visibility.Visible;
- restoreButton.Visibility = Visibility.Collapsed;
- }
- }
+public partial class SettingWindow
+{
+ private readonly IPublicAPI _api;
+ private readonly Settings _settings;
+ private readonly SettingWindowViewModel _viewModel;
- private void Window_StateChanged(object sender, EventArgs e)
- {
- RefreshMaximizeRestoreButton();
- }
+ public SettingWindow(IPublicAPI api, SettingWindowViewModel viewModel)
+ {
+ _settings = viewModel.Settings;
+ DataContext = viewModel;
+ _viewModel = viewModel;
+ _api = api;
+ InitializePosition();
+ InitializeComponent();
+ NavView.SelectedItem = NavView.MenuItems[0]; /* Set First Page */
+ }
- #region Shortcut
+ private void OnLoaded(object sender, RoutedEventArgs e)
+ {
+ RefreshMaximizeRestoreButton();
+ // Fix (workaround) for the window freezes after lock screen (Win+L)
+ // https://stackoverflow.com/questions/4951058/software-rendering-mode-wpf
+ HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource;
+ HwndTarget hwndTarget = hwndSource.CompositionTarget;
+ hwndTarget.RenderMode = RenderMode.Default;
+
+ InitializePosition();
+ }
- private void OnDeleteCustomShortCutClick(object sender, RoutedEventArgs e)
- {
- viewModel.DeleteSelectedCustomShortcut();
- }
+ private void OnClosed(object sender, EventArgs e)
+ {
+ _settings.SettingWindowState = WindowState;
+ _settings.SettingWindowTop = Top;
+ _settings.SettingWindowLeft = Left;
+ _viewModel.Save();
+ _api.SavePluginSettings();
+ }
- private void OnEditCustomShortCutClick(object sender, RoutedEventArgs e)
- {
- if (viewModel.EditSelectedCustomShortcut())
- {
- //customShortcutView.Items.Refresh(); Should Fix
- }
- }
+ private void OnCloseExecuted(object sender, ExecutedRoutedEventArgs e)
+ {
+ Close();
+ }
- private void OnAddCustomShortCutClick(object sender, RoutedEventArgs e)
+ private void window_MouseDown(object sender, MouseButtonEventArgs e) /* for close hotkey popup */
+ {
+ if (Keyboard.FocusedElement is not TextBox textBox)
{
- viewModel.AddCustomShortcut();
+ return;
}
+ var tRequest = new TraversalRequest(FocusNavigationDirection.Next);
+ textBox.MoveFocus(tRequest);
+ }
- #endregion
-
- private CollectionView pluginListView;
- private CollectionView pluginStoreView;
+ /* Custom TitleBar */
- private bool PluginListFilter(object item)
- {
- if (string.IsNullOrEmpty(pluginFilterTxb.Text))
- return true;
- if (item is PluginViewModel model)
- {
- return StringMatcher.FuzzySearch(pluginFilterTxb.Text, model.PluginPair.Metadata.Name).IsSearchPrecisionScoreMet();
- }
- return false;
- }
+ private void OnMinimizeButtonClick(object sender, RoutedEventArgs e)
+ {
+ WindowState = WindowState.Minimized;
+ }
- private bool PluginStoreFilter(object item)
+ private void OnMaximizeRestoreButtonClick(object sender, RoutedEventArgs e)
+ {
+ WindowState = WindowState switch
{
- if (string.IsNullOrEmpty(pluginStoreFilterTxb.Text))
- return true;
- if (item is PluginStoreItemViewModel model)
- {
- return StringMatcher.FuzzySearch(pluginStoreFilterTxb.Text, model.Name).IsSearchPrecisionScoreMet()
- || StringMatcher.FuzzySearch(pluginStoreFilterTxb.Text, model.Description).IsSearchPrecisionScoreMet();
- }
- return false;
- }
-
- private string lastPluginListSearch = "";
- private string lastPluginStoreSearch = "";
+ WindowState.Maximized => WindowState.Normal,
+ _ => WindowState.Maximized
+ };
+ }
- private void RefreshPluginListEventHandler(object sender, RoutedEventArgs e)
- {
- if (pluginFilterTxb.Text != lastPluginListSearch)
- {
- lastPluginListSearch = pluginFilterTxb.Text;
- pluginListView.Refresh();
- }
- }
+ private void OnCloseButtonClick(object sender, RoutedEventArgs e)
+ {
+ Close();
+ }
- private void RefreshPluginStoreEventHandler(object sender, RoutedEventArgs e)
+ private void RefreshMaximizeRestoreButton()
+ {
+ if (WindowState == WindowState.Maximized)
{
- if (pluginStoreFilterTxb.Text != lastPluginStoreSearch)
- {
- lastPluginStoreSearch = pluginStoreFilterTxb.Text;
- pluginStoreView.Refresh();
- }
+ MaximizeButton.Visibility = Visibility.Collapsed;
+ RestoreButton.Visibility = Visibility.Visible;
}
-
- private void PluginFilterTxb_OnKeyDown(object sender, KeyEventArgs e)
+ else
{
- if (e.Key == Key.Enter)
- RefreshPluginListEventHandler(sender, e);
+ MaximizeButton.Visibility = Visibility.Visible;
+ RestoreButton.Visibility = Visibility.Collapsed;
}
+ }
- private void PluginStoreFilterTxb_OnKeyDown(object sender, KeyEventArgs e)
- {
- if (e.Key == Key.Enter)
- RefreshPluginStoreEventHandler(sender, e);
- }
+ private void Window_StateChanged(object sender, EventArgs e)
+ {
+ RefreshMaximizeRestoreButton();
+ }
- private void OnPluginSettingKeydown(object sender, KeyEventArgs e)
+ public void InitializePosition()
+ {
+ if (_settings.SettingWindowTop == null)
{
- if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control && e.Key == Key.F)
- pluginFilterTxb.Focus();
+ Top = WindowTop();
+ Left = WindowLeft();
}
-
- private void PluginStore_OnKeyDown(object sender, KeyEventArgs e)
+ else
{
- if (e.Key == Key.F && (Keyboard.Modifiers & ModifierKeys.Control) != 0)
- {
- pluginStoreFilterTxb.Focus();
- }
+ Top = _settings.SettingWindowTop;
+ Left = _settings.SettingWindowLeft;
}
+ WindowState = _settings.SettingWindowState;
+ }
- public void InitializePosition()
- {
- if (settings.SettingWindowTop == null)
- {
- Top = WindowTop();
- Left = WindowLeft();
- }
- else
- {
- Top = settings.SettingWindowTop;
- Left = settings.SettingWindowLeft;
- }
- WindowState = settings.SettingWindowState;
- }
+ private double WindowLeft()
+ {
+ var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
+ var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0);
+ var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0);
+ var left = (dip2.X - this.ActualWidth) / 2 + dip1.X;
+ return left;
+ }
- public double WindowLeft()
- {
- var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
- var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0);
- var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0);
- var left = (dip2.X - this.ActualWidth) / 2 + dip1.X;
- return left;
- }
+ private double WindowTop()
+ {
+ var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
+ var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y);
+ var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height);
+ var top = (dip2.Y - this.ActualHeight) / 2 + dip1.Y - 20;
+ return top;
+ }
- public double WindowTop()
+ private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
+ {
+ var paneData = new PaneData(_settings, _viewModel.Updater, _viewModel.Portable);
+ if (args.IsSettingsSelected)
{
- var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
- var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y);
- var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height);
- var top = (dip2.Y - this.ActualHeight) / 2 + dip1.Y - 20;
- return top;
+ ContentFrame.Navigate(typeof(SettingsPaneGeneral), paneData);
}
-
- private Button storeClickedButton;
-
- private void StoreListItem_Click(object sender, RoutedEventArgs e)
+ else
{
- if (sender is not Button button)
- return;
+ var selectedItem = (NavigationViewItem)args.SelectedItem;
+ if (selectedItem == null) return;
- storeClickedButton = button;
-
- var flyout = FlyoutService.GetFlyout(button);
- flyout.Closed += (_, _) =>
+ var pageType = selectedItem.Name switch
{
- storeClickedButton = null;
+ nameof(General) => typeof(SettingsPaneGeneral),
+ nameof(Plugins) => typeof(SettingsPanePlugins),
+ nameof(PluginStore) => typeof(SettingsPanePluginStore),
+ nameof(Theme) => typeof(SettingsPaneTheme),
+ nameof(Hotkey) => typeof(SettingsPaneHotkey),
+ nameof(Proxy) => typeof(SettingsPaneProxy),
+ nameof(About) => typeof(SettingsPaneAbout),
+ _ => typeof(SettingsPaneGeneral)
};
-
- }
-
- private void PluginStore_GotFocus(object sender, RoutedEventArgs e)
- {
- Keyboard.Focus(pluginStoreFilterTxb);
- }
-
- private void Plugin_GotFocus(object sender, RoutedEventArgs e)
- {
- Keyboard.Focus(pluginFilterTxb);
+ ContentFrame.Navigate(pageType, paneData);
}
}
+
+ public record PaneData(Settings Settings, Updater Updater, IPortable Portable);
}
diff --git a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
index bc98efabc86..513d0443fe4 100644
--- a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs
@@ -1,12 +1,15 @@
using System;
+using System.Linq;
+using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.ViewModel
{
- public class PluginStoreItemViewModel : BaseModel
+ public partial class PluginStoreItemViewModel : BaseModel
{
+ private PluginPair PluginManagerData => PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7");
public PluginStoreItemViewModel(UserPlugin plugin)
{
_plugin = plugin;
@@ -26,19 +29,13 @@ public PluginStoreItemViewModel(UserPlugin plugin)
public string IcoPath => _plugin.IcoPath;
public bool LabelInstalled => PluginManager.GetPluginForId(_plugin.ID) != null;
- public bool LabelUpdate => LabelInstalled && VersionConvertor(_plugin.Version) > VersionConvertor(PluginManager.GetPluginForId(_plugin.ID).Metadata.Version);
+ public bool LabelUpdate => LabelInstalled && new Version(_plugin.Version) > new Version(PluginManager.GetPluginForId(_plugin.ID).Metadata.Version);
internal const string None = "None";
internal const string RecentlyUpdated = "RecentlyUpdated";
internal const string NewRelease = "NewRelease";
internal const string Installed = "Installed";
- public Version VersionConvertor(string version)
- {
- Version ResultVersion = new Version(version);
- return ResultVersion;
- }
-
public string Category
{
get
@@ -60,5 +57,13 @@ public string Category
return category;
}
}
+
+ [RelayCommand]
+ private void ShowCommandQuery(string action)
+ {
+ var actionKeyword = PluginManagerData.Metadata.ActionKeywords.Any() ? PluginManagerData.Metadata.ActionKeywords[0] + " " : String.Empty;
+ App.API.ChangeQuery($"{actionKeyword}{action} {_plugin.Name}");
+ App.API.ShowMainWindow();
+ }
}
}
diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs
index d2919507db0..b4daa8c7af4 100644
--- a/Flow.Launcher/ViewModel/PluginViewModel.cs
+++ b/Flow.Launcher/ViewModel/PluginViewModel.cs
@@ -1,4 +1,5 @@
-using System.Windows;
+using System.Linq;
+using System.Windows;
using System.Windows.Media;
using Flow.Launcher.Plugin;
using Flow.Launcher.Infrastructure.Image;
@@ -26,6 +27,21 @@ public PluginPair PluginPair
}
}
+ private string PluginManagerActionKeyword
+ {
+ get
+ {
+ var keyword = PluginManager
+ .GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7")
+ .Metadata.ActionKeywords.FirstOrDefault();
+ return keyword switch
+ {
+ null or "*" => string.Empty,
+ _ => keyword
+ };
+ }
+ }
+
private async void LoadIconAsync()
{
@@ -46,7 +62,11 @@ public ImageSource Image
public bool PluginState
{
get => !PluginPair.Metadata.Disabled;
- set => PluginPair.Metadata.Disabled = !value;
+ set
+ {
+ PluginPair.Metadata.Disabled = !value;
+ PluginSettingsObject.Disabled = !value;
+ }
}
public bool IsExpanded
{
@@ -62,11 +82,13 @@ public bool IsExpanded
private Control _settingControl;
private bool _isExpanded;
+
+ public bool HasSettingControl => PluginPair.Plugin is ISettingProvider;
public Control SettingControl
=> IsExpanded
? _settingControl
??= PluginPair.Plugin is not ISettingProvider settingProvider
- ? new Control()
+ ? null
: settingProvider.CreateSettingPanel()
: null;
private ImageSource _image = ImageLoader.MissingImage;
@@ -78,6 +100,7 @@ public Control SettingControl
public string InitAndQueryTime => InternationalizationManager.Instance.GetTranslation("plugin_init_time") + " " + PluginPair.Metadata.InitTime + "ms, " + InternationalizationManager.Instance.GetTranslation("plugin_query_time") + " " + PluginPair.Metadata.AvgQueryTime + "ms";
public string ActionKeywordsText => string.Join(Query.ActionKeywordSeparator, PluginPair.Metadata.ActionKeywords);
public int Priority => PluginPair.Metadata.Priority;
+ public Infrastructure.UserSettings.Plugin PluginSettingsObject { get; set; }
public void ChangeActionKeyword(string newActionKeyword, string oldActionKeyword)
{
@@ -88,13 +111,14 @@ public void ChangeActionKeyword(string newActionKeyword, string oldActionKeyword
public void ChangePriority(int newPriority)
{
PluginPair.Metadata.Priority = newPriority;
+ PluginSettingsObject.Priority = newPriority;
OnPropertyChanged(nameof(Priority));
}
[RelayCommand]
private void EditPluginPriority()
{
- PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(PluginPair.Metadata.ID, this);
+ PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(PluginPair. Metadata.ID, this);
priorityChangeWindow.ShowDialog();
}
@@ -106,6 +130,19 @@ private void OpenPluginDirectory()
PluginManager.API.OpenDirectory(directory);
}
+ [RelayCommand]
+ private void OpenSourceCodeLink()
+ {
+ PluginManager.API.OpenUrl(PluginPair.Metadata.Website);
+ }
+
+ [RelayCommand]
+ private void OpenDeletePluginWindow()
+ {
+ PluginManager.API.ChangeQuery($"{PluginManagerActionKeyword} uninstall {PluginPair.Metadata.Name}".Trim(), true);
+ PluginManager.API.ShowMainWindow();
+ }
+
public static bool IsActionKeywordRegistered(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword);
[RelayCommand]
diff --git a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs
index fe1ea4e7b0b..48180204550 100644
--- a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs
+++ b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs
@@ -1,1006 +1,66 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using Flow.Launcher.Core;
+using Flow.Launcher.Core;
using Flow.Launcher.Core.Configuration;
-using Flow.Launcher.Core.ExternalPlugins;
-using Flow.Launcher.Core.Plugin;
-using Flow.Launcher.Core.Resource;
-using Flow.Launcher.Helper;
-using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
-using Flow.Launcher.Plugin.SharedModels;
-using System.Collections.ObjectModel;
-using CommunityToolkit.Mvvm.Input;
-using System.Globalization;
-using System.Runtime.CompilerServices;
-using Flow.Launcher.Infrastructure.Hotkey;
-namespace Flow.Launcher.ViewModel
-{
- public partial class SettingWindowViewModel : BaseModel
- {
- private readonly Updater _updater;
- private readonly IPortable _portable;
- private readonly FlowLauncherJsonStorage _storage;
-
- public SettingWindowViewModel(Updater updater, IPortable portable)
- {
- _updater = updater;
- _portable = portable;
- _storage = new FlowLauncherJsonStorage();
- Settings = _storage.Load();
- Settings.PropertyChanged += (s, e) =>
- {
- switch (e.PropertyName)
- {
- case nameof(Settings.ActivateTimes):
- OnPropertyChanged(nameof(ActivatedTimes));
- break;
- case nameof(Settings.WindowSize):
- OnPropertyChanged(nameof(WindowWidthSize));
- break;
- case nameof(Settings.UseDate):
- case nameof(Settings.DateFormat):
- OnPropertyChanged(nameof(DateText));
- break;
- case nameof(Settings.UseClock):
- case nameof(Settings.TimeFormat):
- OnPropertyChanged(nameof(ClockText));
- break;
- case nameof(Settings.Language):
- OnPropertyChanged(nameof(ClockText));
- OnPropertyChanged(nameof(DateText));
- OnPropertyChanged(nameof(AlwaysPreviewToolTip));
- break;
- case nameof(Settings.PreviewHotkey):
- OnPropertyChanged(nameof(AlwaysPreviewToolTip));
- break;
- case nameof(Settings.SoundVolume):
- OnPropertyChanged(nameof(SoundEffectVolume));
- break;
- }
- };
- }
-
- [RelayCommand]
- public void SetTogglingHotkey(HotkeyModel hotkey)
- {
- HotKeyMapper.SetHotkey(hotkey, HotKeyMapper.OnToggleHotkey);
- }
-
- public Settings Settings { get; set; }
-
- public async void UpdateApp()
- {
- await _updater.UpdateAppAsync(App.API, false);
- }
-
- public bool AutoUpdates
- {
- get => Settings.AutoUpdates;
- set
- {
- Settings.AutoUpdates = value;
-
- if (value)
- {
- UpdateApp();
- }
- }
- }
-
- public CultureInfo Culture => CultureInfo.DefaultThreadCurrentCulture;
-
- public bool StartFlowLauncherOnSystemStartup
- {
- get => Settings.StartFlowLauncherOnSystemStartup;
- set
- {
- Settings.StartFlowLauncherOnSystemStartup = value;
-
- try
- {
- if (value)
- AutoStartup.Enable();
- else
- AutoStartup.Disable();
- }
- catch (Exception e)
- {
- Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"),
- e.Message);
- }
- }
- }
-
- // This is only required to set at startup. When portable mode enabled/disabled a restart is always required
- private bool _portableMode = DataLocation.PortableDataLocationInUse();
-
- public bool PortableMode
- {
- get => _portableMode;
- set
- {
- if (!_portable.CanUpdatePortability())
- return;
-
- if (DataLocation.PortableDataLocationInUse())
- {
- _portable.DisablePortableMode();
- }
- else
- {
- _portable.EnablePortableMode();
- }
- }
- }
-
- ///
- /// Save Flow settings. Plugins settings are not included.
- ///
- public void Save()
- {
- foreach (var vm in PluginViewModels)
- {
- var id = vm.PluginPair.Metadata.ID;
-
- Settings.PluginSettings.Plugins[id].Disabled = vm.PluginPair.Metadata.Disabled;
- Settings.PluginSettings.Plugins[id].Priority = vm.Priority;
- }
-
- _storage.Save();
- }
-
- public string GetFileFromDialog(string title, string filter = "")
- {
- var dlg = new System.Windows.Forms.OpenFileDialog
- {
- InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
- Multiselect = false,
- CheckFileExists = true,
- CheckPathExists = true,
- Title = title,
- Filter = filter
- };
-
- var result = dlg.ShowDialog();
- if (result == System.Windows.Forms.DialogResult.OK)
- {
- return dlg.FileName;
- }
- else
- {
- return string.Empty;
- }
- }
-
- #region general
-
- // todo a better name?
- public class LastQueryMode : BaseModel
- {
- public string Display { get; set; }
- public Infrastructure.UserSettings.LastQueryMode Value { get; set; }
- }
-
- private List _lastQueryModes = new List();
-
- public List LastQueryModes
- {
- get
- {
- if (_lastQueryModes.Count == 0)
- {
- _lastQueryModes = InitLastQueryModes();
- }
-
- return _lastQueryModes;
- }
- }
-
- private List InitLastQueryModes()
- {
- var modes = new List();
- var enums = (Infrastructure.UserSettings.LastQueryMode[])Enum.GetValues(
- typeof(Infrastructure.UserSettings.LastQueryMode));
- foreach (var e in enums)
- {
- var key = $"LastQuery{e}";
- var display = _translater.GetTranslation(key);
- var m = new LastQueryMode { Display = display, Value = e, };
- modes.Add(m);
- }
-
- return modes;
- }
-
- private void UpdateLastQueryModeDisplay()
- {
- foreach (var item in LastQueryModes)
- {
- item.Display = _translater.GetTranslation($"LastQuery{item.Value}");
- }
- }
-
- public string Language
- {
- get
- {
- return Settings.Language;
- }
- set
- {
- InternationalizationManager.Instance.ChangeLanguage(value);
-
- if (InternationalizationManager.Instance.PromptShouldUsePinyin(value))
- ShouldUsePinyin = true;
-
- UpdateLastQueryModeDisplay();
- }
- }
-
- public bool ShouldUsePinyin
- {
- get
- {
- return Settings.ShouldUsePinyin;
- }
- set
- {
- Settings.ShouldUsePinyin = value;
- }
- }
-
- public List QuerySearchPrecisionStrings
- {
- get
- {
- var precisionStrings = new List();
-
- var enumList = Enum.GetValues(typeof(SearchPrecisionScore)).Cast().ToList();
-
- enumList.ForEach(x => precisionStrings.Add(x.ToString()));
-
- return precisionStrings;
- }
- }
-
- public List OpenResultModifiersList => new List
- {
- KeyConstant.Alt, KeyConstant.Ctrl, $"{KeyConstant.Ctrl}+{KeyConstant.Alt}"
- };
-
- private Internationalization _translater => InternationalizationManager.Instance;
- public List Languages => _translater.LoadAvailableLanguages();
- public IEnumerable MaxResultsRange => Enumerable.Range(2, 16);
-
- public string AlwaysPreviewToolTip =>
- string.Format(_translater.GetTranslation("AlwaysPreviewToolTip"), Settings.PreviewHotkey);
-
- public string TestProxy()
- {
- var proxyServer = Settings.Proxy.Server;
- var proxyUserName = Settings.Proxy.UserName;
- if (string.IsNullOrEmpty(proxyServer))
- {
- return InternationalizationManager.Instance.GetTranslation("serverCantBeEmpty");
- }
-
- if (Settings.Proxy.Port <= 0)
- {
- return InternationalizationManager.Instance.GetTranslation("portCantBeEmpty");
- }
-
- HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_updater.GitHubRepository);
-
- if (string.IsNullOrEmpty(proxyUserName) || string.IsNullOrEmpty(Settings.Proxy.Password))
- {
- request.Proxy = new WebProxy(proxyServer, Settings.Proxy.Port);
- }
- else
- {
- request.Proxy = new WebProxy(proxyServer, Settings.Proxy.Port)
- {
- Credentials = new NetworkCredential(proxyUserName, Settings.Proxy.Password)
- };
- }
-
- try
- {
- var response = (HttpWebResponse)request.GetResponse();
- if (response.StatusCode == HttpStatusCode.OK)
- {
- return InternationalizationManager.Instance.GetTranslation("proxyIsCorrect");
- }
- else
- {
- return InternationalizationManager.Instance.GetTranslation("proxyConnectFailed");
- }
- }
- catch
- {
- return InternationalizationManager.Instance.GetTranslation("proxyConnectFailed");
- }
- }
-
- #endregion
-
- #region plugin
-
- public static string Plugin => @"https://github.com/Flow-Launcher/Flow.Launcher.PluginsManifest";
- public PluginViewModel SelectedPlugin { get; set; }
-
- public IList PluginViewModels
- {
- get => PluginManager.AllPlugins
- .OrderBy(x => x.Metadata.Disabled)
- .ThenBy(y => y.Metadata.Name)
- .Select(p => new PluginViewModel { PluginPair = p })
- .ToList();
- }
-
- public IList ExternalPlugins
- {
- get
- {
- return LabelMaker(PluginsManifest.UserPlugins);
- }
- }
-
- private IList LabelMaker(IList list)
- {
- return list.Select(p => new PluginStoreItemViewModel(p))
- .OrderByDescending(p => p.Category == PluginStoreItemViewModel.NewRelease)
- .ThenByDescending(p => p.Category == PluginStoreItemViewModel.RecentlyUpdated)
- .ThenByDescending(p => p.Category == PluginStoreItemViewModel.None)
- .ThenByDescending(p => p.Category == PluginStoreItemViewModel.Installed)
- .ToList();
- }
-
- public Control SettingProvider
- {
- get
- {
- var settingProvider = SelectedPlugin.PluginPair.Plugin as ISettingProvider;
- if (settingProvider != null)
- {
- var control = settingProvider.CreateSettingPanel();
- control.HorizontalAlignment = HorizontalAlignment.Stretch;
- control.VerticalAlignment = VerticalAlignment.Stretch;
- return control;
- }
- else
- {
- return new Control();
- }
- }
- }
-
- [RelayCommand]
- private async Task RefreshExternalPluginsAsync()
- {
- await PluginsManifest.UpdateManifestAsync();
- OnPropertyChanged(nameof(ExternalPlugins));
- }
-
-
- internal void DisplayPluginQuery(string queryToDisplay, PluginPair plugin, int actionKeywordPosition = 0)
- {
- var actionKeyword = plugin.Metadata.ActionKeywords.Count == 0
- ? string.Empty
- : plugin.Metadata.ActionKeywords[actionKeywordPosition];
-
- App.API.ChangeQuery($"{actionKeyword} {queryToDisplay}");
- App.API.ShowMainWindow();
- }
-
- #endregion
-
- #region theme
-
- public static string Theme => @"https://flowlauncher.com/docs/#/how-to-create-a-theme";
- public static string ThemeGallery => @"https://github.com/Flow-Launcher/Flow.Launcher/discussions/1438";
-
- public string SelectedTheme
- {
- get { return Settings.Theme; }
- set
- {
- ThemeManager.Instance.ChangeTheme(value);
-
- if (ThemeManager.Instance.BlurEnabled && Settings.UseDropShadowEffect)
- DropShadowEffect = false;
- }
- }
-
- public List Themes
- => ThemeManager.Instance.LoadAvailableThemes().Select(Path.GetFileNameWithoutExtension).ToList();
-
- public bool DropShadowEffect
- {
- get { return Settings.UseDropShadowEffect; }
- set
- {
- if (ThemeManager.Instance.BlurEnabled && value)
- {
- MessageBox.Show(InternationalizationManager.Instance.GetTranslation("shadowEffectNotAllowed"));
- return;
- }
-
- if (value)
- {
- ThemeManager.Instance.AddDropShadowEffectToCurrentTheme();
- }
- else
- {
- ThemeManager.Instance.RemoveDropShadowEffectFromCurrentTheme();
- }
-
- Settings.UseDropShadowEffect = value;
- }
- }
-
- public class ColorScheme
- {
- public string Display { get; set; }
- public ColorSchemes Value { get; set; }
- }
-
- public List ColorSchemes
- {
- get
- {
- List modes = new List();
- var enums = (ColorSchemes[])Enum.GetValues(typeof(ColorSchemes));
- foreach (var e in enums)
- {
- var key = $"ColorScheme{e}";
- var display = _translater.GetTranslation(key);
- var m = new ColorScheme { Display = display, Value = e, };
- modes.Add(m);
- }
-
- return modes;
- }
- }
-
- public class SearchWindowScreen
- {
- public string Display { get; set; }
- public SearchWindowScreens Value { get; set; }
- }
+namespace Flow.Launcher.ViewModel;
- public List SearchWindowScreens
- {
- get
- {
- List modes = new List();
- var enums = (SearchWindowScreens[])Enum.GetValues(typeof(SearchWindowScreens));
- foreach (var e in enums)
- {
- var key = $"SearchWindowScreen{e}";
- var display = _translater.GetTranslation(key);
- var m = new SearchWindowScreen { Display = display, Value = e, };
- modes.Add(m);
- }
-
- return modes;
- }
- }
-
- public class SearchWindowAlign
- {
- public string Display { get; set; }
- public SearchWindowAligns Value { get; set; }
- }
-
- public List SearchWindowAligns
- {
- get
- {
- List modes = new List();
- var enums = (SearchWindowAligns[])Enum.GetValues(typeof(SearchWindowAligns));
- foreach (var e in enums)
- {
- var key = $"SearchWindowAlign{e}";
- var display = _translater.GetTranslation(key);
- var m = new SearchWindowAlign { Display = display, Value = e, };
- modes.Add(m);
- }
-
- return modes;
- }
- }
-
- public List ScreenNumbers
- {
- get
- {
- var screens = System.Windows.Forms.Screen.AllScreens;
- var screenNumbers = new List();
- for (int i = 1; i <= screens.Length; i++)
- {
- screenNumbers.Add(i);
- }
-
- return screenNumbers;
- }
- }
-
- public List TimeFormatList { get; } = new()
- {
- "h:mm",
- "hh:mm",
- "H:mm",
- "HH:mm",
- "tt h:mm",
- "tt hh:mm",
- "h:mm tt",
- "hh:mm tt",
- "hh:mm:ss tt",
- "HH:mm:ss"
- };
-
- public List DateFormatList { get; } = new()
- {
- "MM'/'dd dddd",
- "MM'/'dd ddd",
- "MM'/'dd",
- "MM'-'dd",
- "MMMM', 'dd",
- "dd'/'MM",
- "dd'-'MM",
- "ddd MM'/'dd",
- "dddd MM'/'dd",
- "dddd",
- "ddd dd'/'MM",
- "dddd dd'/'MM",
- "dddd dd', 'MMMM",
- "dd', 'MMMM"
- };
-
- public string TimeFormat
- {
- get => Settings.TimeFormat;
- set => Settings.TimeFormat = value;
- }
-
- public string DateFormat
- {
- get => Settings.DateFormat;
- set => Settings.DateFormat = value;
- }
-
- public string ClockText => DateTime.Now.ToString(TimeFormat, Culture);
-
- public string DateText => DateTime.Now.ToString(DateFormat, Culture);
-
- public double WindowWidthSize
- {
- get => Settings.WindowSize;
- set => Settings.WindowSize = value;
- }
-
- public bool UseGlyphIcons
- {
- get => Settings.UseGlyphIcons;
- set => Settings.UseGlyphIcons = value;
- }
-
- public bool UseAnimation
- {
- get => Settings.UseAnimation;
- set => Settings.UseAnimation = value;
- }
-
- public class AnimationSpeed
- {
- public string Display { get; set; }
- public AnimationSpeeds Value { get; set; }
- }
-
- public List AnimationSpeeds
- {
- get
- {
- List speeds = new List();
- var enums = (AnimationSpeeds[])Enum.GetValues(typeof(AnimationSpeeds));
- foreach (var e in enums)
- {
- var key = $"AnimationSpeed{e}";
- var display = _translater.GetTranslation(key);
- var m = new AnimationSpeed { Display = display, Value = e, };
- speeds.Add(m);
- }
-
- return speeds;
- }
- }
-
- public bool UseSound
- {
- get => Settings.UseSound;
- set => Settings.UseSound = value;
- }
-
- public double SoundEffectVolume
- {
- get => Settings.SoundVolume;
- set => Settings.SoundVolume = value;
- }
-
- public bool UseClock
- {
- get => Settings.UseClock;
- set => Settings.UseClock = value;
- }
-
- public bool UseDate
- {
- get => Settings.UseDate;
- set => Settings.UseDate = value;
- }
-
- public double SettingWindowWidth
- {
- get => Settings.SettingWindowWidth;
- set => Settings.SettingWindowWidth = value;
- }
-
- public double SettingWindowHeight
- {
- get => Settings.SettingWindowHeight;
- set => Settings.SettingWindowHeight = value;
- }
-
- public double SettingWindowTop
- {
- get => Settings.SettingWindowTop;
- set => Settings.SettingWindowTop = value;
- }
-
- public double SettingWindowLeft
- {
- get => Settings.SettingWindowLeft;
- set => Settings.SettingWindowLeft = value;
- }
-
- public Brush PreviewBackground
- {
- get
- {
- var wallpaper = WallpaperPathRetrieval.GetWallpaperPath();
- if (wallpaper != null && File.Exists(wallpaper))
- {
- var memStream = new MemoryStream(File.ReadAllBytes(wallpaper));
- var bitmap = new BitmapImage();
- bitmap.BeginInit();
- bitmap.StreamSource = memStream;
- bitmap.DecodePixelWidth = 800;
- bitmap.DecodePixelHeight = 600;
- bitmap.EndInit();
- var brush = new ImageBrush(bitmap) { Stretch = Stretch.UniformToFill };
- return brush;
- }
- else
- {
- var wallpaperColor = WallpaperPathRetrieval.GetWallpaperColor();
- var brush = new SolidColorBrush(wallpaperColor);
- return brush;
- }
- }
- }
-
- public ResultsViewModel PreviewResults
- {
- get
- {
- var results = new List
- {
- new Result
- {
- Title = InternationalizationManager.Instance.GetTranslation("SampleTitleExplorer"),
- SubTitle =
- InternationalizationManager.Instance.GetTranslation("SampleSubTitleExplorer"),
- IcoPath =
- Path.Combine(Constant.ProgramDirectory,
- @"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png")
- },
- new Result
- {
- Title = InternationalizationManager.Instance.GetTranslation("SampleTitleWebSearch"),
- SubTitle =
- InternationalizationManager.Instance.GetTranslation("SampleSubTitleWebSearch"),
- IcoPath =
- Path.Combine(Constant.ProgramDirectory,
- @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png")
- },
- new Result
- {
- Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProgram"),
- SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProgram"),
- IcoPath =
- Path.Combine(Constant.ProgramDirectory,
- @"Plugins\Flow.Launcher.Plugin.Program\Images\program.png")
- },
- new Result
- {
- Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProcessKiller"),
- SubTitle =
- InternationalizationManager.Instance.GetTranslation("SampleSubTitleProcessKiller"),
- IcoPath = Path.Combine(Constant.ProgramDirectory,
- @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png")
- }
- };
- var vm = new ResultsViewModel(Settings);
- vm.AddResults(results, "PREVIEW");
- return vm;
- }
- }
-
- public FontFamily SelectedQueryBoxFont
- {
- get
- {
- if (Fonts.SystemFontFamilies.Count(o =>
- o.FamilyNames.Values != null &&
- o.FamilyNames.Values.Contains(Settings.QueryBoxFont)) > 0)
- {
- var font = new FontFamily(Settings.QueryBoxFont);
- return font;
- }
- else
- {
- var font = new FontFamily("Segoe UI");
- return font;
- }
- }
- set
- {
- Settings.QueryBoxFont = value.ToString();
- ThemeManager.Instance.ChangeTheme(Settings.Theme);
- }
- }
-
- public FamilyTypeface SelectedQueryBoxFontFaces
- {
- get
- {
- var typeface = SyntaxSugars.CallOrRescueDefault(
- () => SelectedQueryBoxFont.ConvertFromInvariantStringsOrNormal(
- Settings.QueryBoxFontStyle,
- Settings.QueryBoxFontWeight,
- Settings.QueryBoxFontStretch
- ));
- return typeface;
- }
- set
- {
- Settings.QueryBoxFontStretch = value.Stretch.ToString();
- Settings.QueryBoxFontWeight = value.Weight.ToString();
- Settings.QueryBoxFontStyle = value.Style.ToString();
- ThemeManager.Instance.ChangeTheme(Settings.Theme);
- }
- }
-
- public FontFamily SelectedResultFont
- {
- get
- {
- if (Fonts.SystemFontFamilies.Count(o =>
- o.FamilyNames.Values != null &&
- o.FamilyNames.Values.Contains(Settings.ResultFont)) > 0)
- {
- var font = new FontFamily(Settings.ResultFont);
- return font;
- }
- else
- {
- var font = new FontFamily("Segoe UI");
- return font;
- }
- }
- set
- {
- Settings.ResultFont = value.ToString();
- ThemeManager.Instance.ChangeTheme(Settings.Theme);
- }
- }
-
- public FamilyTypeface SelectedResultFontFaces
- {
- get
- {
- var typeface = SyntaxSugars.CallOrRescueDefault(
- () => SelectedResultFont.ConvertFromInvariantStringsOrNormal(
- Settings.ResultFontStyle,
- Settings.ResultFontWeight,
- Settings.ResultFontStretch
- ));
- return typeface;
- }
- set
- {
- Settings.ResultFontStretch = value.Stretch.ToString();
- Settings.ResultFontWeight = value.Weight.ToString();
- Settings.ResultFontStyle = value.Style.ToString();
- ThemeManager.Instance.ChangeTheme(Settings.Theme);
- }
- }
-
- public string ThemeImage => Constant.QueryTextBoxIconImagePath;
-
- #endregion
-
- #region hotkey
-
- public CustomPluginHotkey SelectedCustomPluginHotkey { get; set; }
-
- #endregion
-
- #region shortcut
-
- public ObservableCollection CustomShortcuts => Settings.CustomShortcuts;
-
- public ObservableCollection BuiltinShortcuts => Settings.BuiltinShortcuts;
-
- public CustomShortcutModel? SelectedCustomShortcut { get; set; }
-
- public void DeleteSelectedCustomShortcut()
- {
- var item = SelectedCustomShortcut;
- if (item == null)
- {
- MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
- return;
- }
-
- string deleteWarning = string.Format(
- InternationalizationManager.Instance.GetTranslation("deleteCustomShortcutWarning"),
- item.Key, item.Value);
- if (MessageBox.Show(deleteWarning, InternationalizationManager.Instance.GetTranslation("delete"),
- MessageBoxButton.YesNo) == MessageBoxResult.Yes)
- {
- Settings.CustomShortcuts.Remove(item);
- }
- }
-
- public bool EditSelectedCustomShortcut()
- {
- var item = SelectedCustomShortcut;
- if (item == null)
- {
- MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
- return false;
- }
-
- var shortcutSettingWindow = new CustomShortcutSetting(item.Key, item.Value, this);
- if (shortcutSettingWindow.ShowDialog() == true)
- {
- // Fix un-selectable shortcut item after the first selection
- // https://stackoverflow.com/questions/16789360/wpf-listbox-items-with-changing-hashcode
- SelectedCustomShortcut = null;
- item.Key = shortcutSettingWindow.Key;
- item.Value = shortcutSettingWindow.Value;
- SelectedCustomShortcut = item;
- return true;
- }
-
- return false;
- }
-
- public void AddCustomShortcut()
- {
- var shortcutSettingWindow = new CustomShortcutSetting(this);
- if (shortcutSettingWindow.ShowDialog() == true)
- {
- var shortcut = new CustomShortcutModel(shortcutSettingWindow.Key, shortcutSettingWindow.Value);
- Settings.CustomShortcuts.Add(shortcut);
- }
- }
-
- public bool ShortcutExists(string key)
- {
- return Settings.CustomShortcuts.Any(x => x.Key == key) || Settings.BuiltinShortcuts.Any(x => x.Key == key);
- }
-
- #endregion
-
- #region about
-
- public string Website => Constant.Website;
- public string SponsorPage => Constant.SponsorPage;
- public string ReleaseNotes => _updater.GitHubRepository + @"/releases/latest";
- public string Documentation => Constant.Documentation;
- public string Docs => Constant.Docs;
- public string Github => Constant.GitHub;
-
- public string Version
- {
- get
- {
- if (Constant.Version == "1.0.0")
- {
- return Constant.Dev;
- }
- else
- {
- return Constant.Version;
- }
- }
- }
-
- public string ActivatedTimes =>
- string.Format(_translater.GetTranslation("about_activate_times"), Settings.ActivateTimes);
+public class SettingWindowViewModel : BaseModel
+{
+ private readonly FlowLauncherJsonStorage _storage;
- public string CheckLogFolder
- {
- get
- {
- var logFiles = GetLogFiles();
- long size = logFiles.Sum(file => file.Length);
- return string.Format("{0} ({1})", _translater.GetTranslation("clearlogfolder"),
- BytesToReadableString(size));
- }
- }
+ public Updater Updater { get; }
- private static DirectoryInfo GetLogDir(string version = "")
- {
- return new DirectoryInfo(Path.Combine(DataLocation.DataDirectory(), Constant.Logs, version));
- }
+ public IPortable Portable { get; }
- private static List GetLogFiles(string version = "")
- {
- return GetLogDir(version).EnumerateFiles("*", SearchOption.AllDirectories).ToList();
- }
+ public Settings Settings { get; }
- internal void ClearLogFolder()
- {
- var logDirectory = GetLogDir();
- var logFiles = GetLogFiles();
+ public SettingWindowViewModel(Updater updater, IPortable portable)
+ {
+ _storage = new FlowLauncherJsonStorage();
- logFiles.ForEach(f => f.Delete());
+ Updater = updater;
+ Portable = portable;
+ Settings = _storage.Load();
+ }
- logDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
- .Where(dir => !Constant.Version.Equals(dir.Name))
- .ToList()
- .ForEach(dir => dir.Delete());
+ public async void UpdateApp()
+ {
+ await Updater.UpdateAppAsync(App.API, false);
+ }
- OnPropertyChanged(nameof(CheckLogFolder));
- }
- internal void OpenLogFolder()
- {
- App.API.OpenDirectory(GetLogDir(Constant.Version).FullName);
- }
- internal static string BytesToReadableString(long bytes)
- {
- const int scale = 1024;
- string[] orders = new string[] { "GB", "MB", "KB", "B" };
- long max = (long)Math.Pow(scale, orders.Length - 1);
+ ///
+ /// Save Flow settings. Plugins settings are not included.
+ ///
+ public void Save()
+ {
+ _storage.Save();
+ }
- foreach (string order in orders)
- {
- if (bytes > max)
- return string.Format("{0:##.##} {1}", decimal.Divide(bytes, max), order);
+ public double SettingWindowWidth
+ {
+ get => Settings.SettingWindowWidth;
+ set => Settings.SettingWindowWidth = value;
+ }
- max /= scale;
- }
+ public double SettingWindowHeight
+ {
+ get => Settings.SettingWindowHeight;
+ set => Settings.SettingWindowHeight = value;
+ }
- return "0 B";
- }
+ public double SettingWindowTop
+ {
+ get => Settings.SettingWindowTop;
+ set => Settings.SettingWindowTop = value;
+ }
- #endregion
+ public double SettingWindowLeft
+ {
+ get => Settings.SettingWindowLeft;
+ set => Settings.SettingWindowLeft = value;
}
}