diff --git a/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs b/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs index 64c4cd627d2..79d6d7605e3 100644 --- a/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs +++ b/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Flow.Launcher.Core.ExternalPlugins { @@ -13,9 +13,11 @@ public record UserPlugin public string Website { get; set; } public string UrlDownload { get; set; } public string UrlSourceCode { get; set; } + public string LocalInstallPath { get; set; } public string IcoPath { get; set; } public DateTime? LatestReleaseDate { get; set; } public DateTime? DateAdded { get; set; } + public bool IsFromLocalInstallPath => !string.IsNullOrEmpty(LocalInstallPath); } } diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 7f4d4ea35cf..e7dfb31c07a 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -380,7 +380,8 @@ public static bool PluginModified(string uuid) /// - /// Update a plugin to new version, from a zip file. Will Delete zip after updating. + /// Update a plugin to new version, from a zip file. By default will remove the zip file if update is via url, + /// unless it's a local path installation /// public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath) { @@ -390,11 +391,11 @@ public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVe } /// - /// Install a plugin. Will Delete zip after updating. + /// Install a plugin. By default will remove the zip file if installation is from url, unless it's a local path installation /// public static void InstallPlugin(UserPlugin plugin, string zipFilePath) { - InstallPlugin(plugin, zipFilePath, true); + InstallPlugin(plugin, zipFilePath, checkModified: true); } /// @@ -420,7 +421,9 @@ internal static void InstallPlugin(UserPlugin plugin, string zipFilePath, bool c // Unzip plugin files to temp folder var tempFolderPluginPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, tempFolderPluginPath); - File.Delete(zipFilePath); + + if(!plugin.IsFromLocalInstallPath) + File.Delete(zipFilePath); var pluginFolderPath = GetContainingFolderPathAfterUnzip(tempFolderPluginPath); diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 7bb8fe2009c..61a9da40050 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -31,6 +31,8 @@ public class Settings : BaseModel, IHotkeySettings public string SelectPrevPageHotkey { get; set; } = $"PageDown"; public string OpenContextMenuHotkey { get; set; } = $"Ctrl+O"; public string SettingWindowHotkey { get; set; } = $"Ctrl+I"; + public string CycleHistoryUpHotkey { get; set; } = $"{KeyConstant.Alt} + Up"; + public string CycleHistoryDownHotkey { get; set; } = $"{KeyConstant.Alt} + Down"; public string Language { @@ -280,42 +282,9 @@ public List RegisteredHotkeys { get { - var list = new List - { - new("Up", "HotkeyLeftRightDesc"), - new("Down", "HotkeyLeftRightDesc"), - new("Left", "HotkeyUpDownDesc"), - new("Right", "HotkeyUpDownDesc"), - new("Escape", "HotkeyESCDesc"), - new("F5", "ReloadPluginHotkey"), - new("Alt+Home", "HotkeySelectFirstResult"), - new("Alt+End", "HotkeySelectLastResult"), - new("Ctrl+R", "HotkeyRequery"), - new("Ctrl+H", "ToggleHistoryHotkey"), - new("Ctrl+OemCloseBrackets", "QuickWidthHotkey"), - new("Ctrl+OemOpenBrackets", "QuickWidthHotkey"), - new("Ctrl+OemPlus", "QuickHeightHotkey"), - new("Ctrl+OemMinus", "QuickHeightHotkey"), - new("Ctrl+Shift+Enter", "HotkeyCtrlShiftEnterDesc"), - new("Shift+Enter", "OpenContextMenuHotkey"), - new("Enter", "HotkeyRunDesc"), - new("Ctrl+Enter", "OpenContainFolderHotkey"), - new("Alt+Enter", "HotkeyOpenResult"), - new("Ctrl+F12", "ToggleGameModeHotkey"), - new("Ctrl+Shift+C", "CopyFilePathHotkey"), - - new($"{OpenResultModifiers}+D1", "HotkeyOpenResultN", 1), - new($"{OpenResultModifiers}+D2", "HotkeyOpenResultN", 2), - new($"{OpenResultModifiers}+D3", "HotkeyOpenResultN", 3), - new($"{OpenResultModifiers}+D4", "HotkeyOpenResultN", 4), - new($"{OpenResultModifiers}+D5", "HotkeyOpenResultN", 5), - new($"{OpenResultModifiers}+D6", "HotkeyOpenResultN", 6), - new($"{OpenResultModifiers}+D7", "HotkeyOpenResultN", 7), - new($"{OpenResultModifiers}+D8", "HotkeyOpenResultN", 8), - new($"{OpenResultModifiers}+D9", "HotkeyOpenResultN", 9), - new($"{OpenResultModifiers}+D0", "HotkeyOpenResultN", 10) - }; + var list = FixedHotkeys(); + // Customizeable hotkeys if(!string.IsNullOrEmpty(Hotkey)) list.Add(new(Hotkey, "flowlauncherHotkey", () => Hotkey = "")); if(!string.IsNullOrEmpty(PreviewHotkey)) @@ -340,7 +309,12 @@ public List RegisteredHotkeys list.Add(new(SelectNextPageHotkey, "SelectNextPageHotkey", () => SelectNextPageHotkey = "")); if(!string.IsNullOrEmpty(SelectPrevPageHotkey)) list.Add(new(SelectPrevPageHotkey, "SelectPrevPageHotkey", () => SelectPrevPageHotkey = "")); + if (!string.IsNullOrEmpty(CycleHistoryUpHotkey)) + list.Add(new(CycleHistoryUpHotkey, "CycleHistoryUpHotkey", () => CycleHistoryUpHotkey = "")); + if (!string.IsNullOrEmpty(CycleHistoryDownHotkey)) + list.Add(new(CycleHistoryDownHotkey, "CycleHistoryDownHotkey", () => CycleHistoryDownHotkey = "")); + // Custom Query Hotkeys foreach (var customPluginHotkey in CustomPluginHotkeys) { if (!string.IsNullOrEmpty(customPluginHotkey.Hotkey)) @@ -350,6 +324,45 @@ public List RegisteredHotkeys return list; } } + + private List FixedHotkeys() + { + return new List + { + new("Up", "HotkeyLeftRightDesc"), + new("Down", "HotkeyLeftRightDesc"), + new("Left", "HotkeyUpDownDesc"), + new("Right", "HotkeyUpDownDesc"), + new("Escape", "HotkeyESCDesc"), + new("F5", "ReloadPluginHotkey"), + new("Alt+Home", "HotkeySelectFirstResult"), + new("Alt+End", "HotkeySelectLastResult"), + new("Ctrl+R", "HotkeyRequery"), + new("Ctrl+H", "ToggleHistoryHotkey"), + new("Ctrl+OemCloseBrackets", "QuickWidthHotkey"), + new("Ctrl+OemOpenBrackets", "QuickWidthHotkey"), + new("Ctrl+OemPlus", "QuickHeightHotkey"), + new("Ctrl+OemMinus", "QuickHeightHotkey"), + new("Ctrl+Shift+Enter", "HotkeyCtrlShiftEnterDesc"), + new("Shift+Enter", "OpenContextMenuHotkey"), + new("Enter", "HotkeyRunDesc"), + new("Ctrl+Enter", "OpenContainFolderHotkey"), + new("Alt+Enter", "HotkeyOpenResult"), + new("Ctrl+F12", "ToggleGameModeHotkey"), + new("Ctrl+Shift+C", "CopyFilePathHotkey"), + + new($"{OpenResultModifiers}+D1", "HotkeyOpenResultN", 1), + new($"{OpenResultModifiers}+D2", "HotkeyOpenResultN", 2), + new($"{OpenResultModifiers}+D3", "HotkeyOpenResultN", 3), + new($"{OpenResultModifiers}+D4", "HotkeyOpenResultN", 4), + new($"{OpenResultModifiers}+D5", "HotkeyOpenResultN", 5), + new($"{OpenResultModifiers}+D6", "HotkeyOpenResultN", 6), + new($"{OpenResultModifiers}+D7", "HotkeyOpenResultN", 7), + new($"{OpenResultModifiers}+D8", "HotkeyOpenResultN", 8), + new($"{OpenResultModifiers}+D9", "HotkeyOpenResultN", 9), + new($"{OpenResultModifiers}+D0", "HotkeyOpenResultN", 10) + }; + } } public enum LastQueryMode diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index a835c5b964e..1f34cd23e5d 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Linq; #pragma warning disable IDE0005 using System.Windows; @@ -199,6 +200,24 @@ public static void OpenFile(string filePath, string workingDir = "", bool asAdmi } } + /// + /// This checks whether a given string is a zip file path. + /// By default does not check if the zip file actually exist on disk, can do so by + /// setting checkFileExists = true. + /// + public static bool IsZipFilePath(string querySearchString, bool checkFileExists = false) + { + if (IsLocationPathString(querySearchString) && querySearchString.Split('.').Last() == "zip") + { + if (checkFileExists) + return FileExists(querySearchString); + + return true; + } + + return false; + } + /// /// This checks whether a given string is a directory path or network location string. /// It does not check if location actually exists. diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index ead4a25cf68..7fd6512eb65 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -192,11 +192,15 @@ public void GivenQuery_WhenActionKeywordForFileContentSearchExists_ThenFileConte [TestCase(@"\c:\", false)] [TestCase(@"cc:\", false)] [TestCase(@"\\\SomeNetworkLocation\", false)] + [TestCase(@"\\SomeNetworkLocation\", true)] [TestCase("RandomFile", false)] [TestCase(@"c:\>*", true)] [TestCase(@"c:\>", true)] [TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)] [TestCase(@"c:\SomeLocation\SomeOtherLocation", true)] + [TestCase(@"c:\SomeLocation\SomeOtherLocation\SomeFile.exe", true)] + [TestCase(@"\\SomeNetworkLocation\SomeFile.exe", true)] + public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult) { // When, Given 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/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index fd24f825ecc..9ab05555c1e 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -188,6 +188,8 @@ Select Previous Item Next Page Previous Page + Cycle Previous Query + Cycle Next Query Open Context Menu Open Setting Window Copy File Path diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 2e6e973a720..2315d99ec31 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -25,6 +25,7 @@ LocationChanged="OnLocationChanged" Opacity="{Binding MainWindowOpacity, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" PreviewKeyDown="OnKeyDown" + PreviewKeyUp="OnKeyUp" ResizeMode="NoResize" ShowInTaskbar="False" SizeToContent="Height" @@ -197,6 +198,14 @@ Key="{Binding SelectPrevPageHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}" Command="{Binding SelectPrevPageCommand}" Modifiers="{Binding SelectPrevPageHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" /> + + diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 76e587cdca5..9c5ed46c0cc 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -47,6 +47,7 @@ public partial class MainWindow private MainViewModel _viewModel; private bool _animating; MediaPlayer animationSound = new MediaPlayer(); + private bool isArrowKeyPressed = false; #endregion @@ -109,9 +110,11 @@ private async void OnClosing(object sender, CancelEventArgs e) private void OnInitialized(object sender, EventArgs e) { } - private void OnLoaded(object sender, RoutedEventArgs _) { + // MouseEventHandler + PreviewMouseMove += MainPreviewMouseMove; + CheckFirstLaunch(); HideStartup(); // show notify icon when flowlauncher is hidden @@ -406,6 +409,7 @@ public void WindowAnimator() if (_animating) return; + isArrowKeyPressed = true; _animating = true; UpdatePosition(); @@ -494,6 +498,7 @@ public void WindowAnimator() windowsb.Completed += (_, _) => _animating = false; _settings.WindowLeft = Left; _settings.WindowTop = Top; + isArrowKeyPressed = false; if (QueryTextBox.Text.Length == 0) { @@ -644,10 +649,12 @@ private void OnKeyDown(object sender, KeyEventArgs e) switch (e.Key) { case Key.Down: + isArrowKeyPressed = true; _viewModel.SelectNextItemCommand.Execute(null); e.Handled = true; break; case Key.Up: + isArrowKeyPressed = true; _viewModel.SelectPrevItemCommand.Execute(null); e.Handled = true; break; @@ -698,7 +705,21 @@ private void OnKeyDown(object sender, KeyEventArgs e) } } + private void OnKeyUp(object sender, KeyEventArgs e) + { + if (e.Key == Key.Up || e.Key == Key.Down) + { + isArrowKeyPressed = false; + } + } + private void MainPreviewMouseMove(object sender, System.Windows.Input.MouseEventArgs e) + { + if (isArrowKeyPressed) + { + e.Handled = true; // Ignore Mouse Hover when press Arrowkeys + } + } public void PreviewReset() { _viewModel.ResetPreview(); 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/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/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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -609,2920 +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 8144c8ff8a3..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 >= 0 && settings.SettingWindowLeft >= 0) - { - Top = settings.SettingWindowTop; - Left = settings.SettingWindowLeft; - } - else - { - Top = WindowTop(); - Left = WindowLeft(); - } - 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/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 67efc6a8657..01dc67b9844 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -42,6 +42,7 @@ public partial class MainViewModel : BaseModel, ISavable private readonly FlowLauncherJsonStorage _userSelectedRecordStorage; private readonly FlowLauncherJsonStorage _topMostRecordStorage; private readonly History _history; + private int lastHistoryIndex = 1; private readonly UserSelectedRecord _userSelectedRecord; private readonly TopMostRecord _topMostRecord; @@ -83,6 +84,12 @@ public MainViewModel(Settings settings) case nameof(Settings.AutoCompleteHotkey): OnPropertyChanged(nameof(AutoCompleteHotkey)); break; + case nameof(Settings.CycleHistoryUpHotkey): + OnPropertyChanged(nameof(CycleHistoryUpHotkey)); + break; + case nameof(Settings.CycleHistoryDownHotkey): + OnPropertyChanged(nameof(CycleHistoryDownHotkey)); + break; case nameof(Settings.AutoCompleteHotkey2): OnPropertyChanged(nameof(AutoCompleteHotkey2)); break; @@ -256,6 +263,32 @@ public void ReQuery(bool reselect) } } + [RelayCommand] + public void ReverseHistory() + { + if (_history.Items.Count > 0) + { + ChangeQueryText(_history.Items[_history.Items.Count - lastHistoryIndex].Query.ToString()); + if (lastHistoryIndex < _history.Items.Count) + { + lastHistoryIndex++; + } + } + } + + [RelayCommand] + public void ForwardHistory() + { + if (_history.Items.Count > 0) + { + ChangeQueryText(_history.Items[_history.Items.Count - lastHistoryIndex].Query.ToString()); + if (lastHistoryIndex > 1) + { + lastHistoryIndex--; + } + } + } + [RelayCommand] private void LoadContextMenu() { @@ -346,6 +379,7 @@ private async Task OpenResultAsync(string index) { _userSelectedRecord.Add(result); _history.Add(result.OriginQuery.RawQuery); + lastHistoryIndex = 1; } if (hideWindow) @@ -394,7 +428,18 @@ private void SelectNextPage() [RelayCommand] private void SelectPrevItem() { - SelectedResults.SelectPrevResult(); + if (_history.Items.Count > 0 + && QueryText == string.Empty + && SelectedIsFromQueryResults()) + { + lastHistoryIndex = 1; + ReverseHistory(); + } + else + { + SelectedResults.SelectPrevResult(); + } + } [RelayCommand] @@ -690,6 +735,8 @@ public string VerifyOrSetDefaultHotkey(string hotkey, string defaultHotkey) public string SelectPrevPageHotkey => VerifyOrSetDefaultHotkey(Settings.SelectPrevPageHotkey, ""); public string OpenContextMenuHotkey => VerifyOrSetDefaultHotkey(Settings.OpenContextMenuHotkey, "Ctrl+O"); public string SettingWindowHotkey => VerifyOrSetDefaultHotkey(Settings.SettingWindowHotkey, "Ctrl+I"); + public string CycleHistoryUpHotkey => VerifyOrSetDefaultHotkey(Settings.CycleHistoryUpHotkey, "Alt+Up"); + public string CycleHistoryDownHotkey => VerifyOrSetDefaultHotkey(Settings.CycleHistoryDownHotkey, "Alt+Down"); public string Image => Constant.QueryTextBoxIconImagePath; @@ -1116,6 +1163,7 @@ public void Show() public async void Hide() { + lastHistoryIndex = 1; // Trick for no delay MainWindowOpacity = 0; lastContextMenuResult = new Result(); 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; } } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/zipfolder.png b/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/zipfolder.png new file mode 100644 index 00000000000..5d3ed0ace3f Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/zipfolder.png differ diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml index a89d9df213d..de6a3a2fb4c 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml @@ -26,7 +26,6 @@ {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 8cd58ac5212..15cbda7f211 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -3,11 +3,9 @@ using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Logger; -using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin.SharedCommands; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; @@ -99,13 +97,12 @@ internal async Task InstallOrUpdateAsync(UserPlugin plugin) if (Context.API.GetAllPlugins() .Any(x => x.Metadata.ID == plugin.ID && x.Metadata.Version.CompareTo(plugin.Version) < 0)) { - if (MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_update_exists"), - Context.API.GetTranslation("plugin_pluginsmanager_update_title"), - MessageBoxButton.YesNo) == MessageBoxResult.Yes) - Context - .API - .ChangeQuery( - $"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.UpdateCommand} {plugin.Name}"); + var updateDetail = !plugin.IsFromLocalInstallPath ? plugin.Name : plugin.LocalInstallPath; + + Context + .API + .ChangeQuery( + $"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.UpdateCommand} {updateDetail}"); var mainWindow = Application.Current.MainWindow; mainWindow.Show(); @@ -147,12 +144,17 @@ internal async Task InstallOrUpdateAsync(UserPlugin plugin) try { - if (File.Exists(filePath)) + if (!plugin.IsFromLocalInstallPath) { - File.Delete(filePath); - } + if (File.Exists(filePath)) + File.Delete(filePath); - await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false); + await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false); + } + else + { + filePath = plugin.LocalInstallPath; + } Install(plugin, filePath); } @@ -193,24 +195,38 @@ internal async ValueTask> RequestUpdateAsync(string search, Cancell { await PluginsManifest.UpdateManifestAsync(token, usePrimaryUrlOnly); + var pluginFromLocalPath = null as UserPlugin; + var updateFromLocalPath = false; + + if (FilesFolders.IsZipFilePath(search, checkFileExists: true)) + { + pluginFromLocalPath = Utilities.GetPluginInfoFromZip(search); + pluginFromLocalPath.LocalInstallPath = search; + updateFromLocalPath = true; + } + + var updateSource = !updateFromLocalPath + ? PluginsManifest.UserPlugins + : new List { pluginFromLocalPath }; + var resultsForUpdate = ( from existingPlugin in Context.API.GetAllPlugins() - join pluginFromManifest in PluginsManifest.UserPlugins - on existingPlugin.Metadata.ID equals pluginFromManifest.ID - where String.Compare(existingPlugin.Metadata.Version, pluginFromManifest.Version, + join pluginUpdateSource in updateSource + on existingPlugin.Metadata.ID equals pluginUpdateSource.ID + where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version, StringComparison.InvariantCulture) < - 0 // if current version precedes manifest version + 0 // if current version precedes version of the plugin from update source (e.g. PluginsManifest) && !PluginManager.PluginModified(existingPlugin.Metadata.ID) select new { - pluginFromManifest.Name, - pluginFromManifest.Author, + pluginUpdateSource.Name, + pluginUpdateSource.Author, CurrentVersion = existingPlugin.Metadata.Version, - NewVersion = pluginFromManifest.Version, + NewVersion = pluginUpdateSource.Version, existingPlugin.Metadata.IcoPath, PluginExistingMetadata = existingPlugin.Metadata, - PluginNewUserPlugin = pluginFromManifest + PluginNewUserPlugin = pluginUpdateSource }).ToList(); if (!resultsForUpdate.Any()) @@ -261,13 +277,21 @@ where String.Compare(existingPlugin.Metadata.Version, pluginFromManifest.Version _ = Task.Run(async delegate { - if (File.Exists(downloadToFilePath)) + if (!x.PluginNewUserPlugin.IsFromLocalInstallPath) { - File.Delete(downloadToFilePath); - } + if (File.Exists(downloadToFilePath)) + { + File.Delete(downloadToFilePath); + } - await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath) - .ConfigureAwait(false); + await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath) + .ConfigureAwait(false); + } + else + { + downloadToFilePath = x.PluginNewUserPlugin.LocalInstallPath; + } + PluginManager.UpdatePlugin(x.PluginExistingMetadata, x.PluginNewUserPlugin, downloadToFilePath); @@ -396,7 +420,7 @@ await Http.DownloadAsync(plugin.PluginNewUserPlugin.UrlDownload, downloadToFileP results = results.Prepend(updateAllResult); } - return Search(results, search); + return !updateFromLocalPath ? Search(results, search) : results.ToList(); } internal bool PluginExists(string id) @@ -470,6 +494,42 @@ internal List InstallFromWeb(string url) return new List { result }; } + internal List InstallFromLocalPath(string localPath) + { + var plugin = Utilities.GetPluginInfoFromZip(localPath); + + plugin.LocalInstallPath = localPath; + + return new List + { + new Result + { + Title = $"{plugin.Name} by {plugin.Author}", + SubTitle = plugin.Description, + IcoPath = plugin.IcoPath, + Action = e => + { + if (Settings.WarnFromUnknownSource) + { + if (!InstallSourceKnown(plugin.Website) + && MessageBox.Show(string.Format( + Context.API.GetTranslation("plugin_pluginsmanager_install_unknown_source_warning"), + Environment.NewLine), + Context.API.GetTranslation( + "plugin_pluginsmanager_install_unknown_source_warning_title"), + MessageBoxButton.YesNo) == MessageBoxResult.No) + return false; + } + + Application.Current.MainWindow.Hide(); + _ = InstallOrUpdateAsync(plugin); + + return ShouldHideWindow; + } + } + }; + } + private bool InstallSourceKnown(string url) { var author = url.Split('/')[3]; @@ -489,6 +549,9 @@ internal async ValueTask> RequestInstallOrUpdate(string search, Can && search.Split('.').Last() == zip) return InstallFromWeb(search); + if (FilesFolders.IsZipFilePath(search, checkFileExists: true)) + return InstallFromLocalPath(search); + var results = PluginsManifest .UserPlugins @@ -522,10 +585,13 @@ private void Install(UserPlugin plugin, string downloadedFilePath) if (!File.Exists(downloadedFilePath)) throw new FileNotFoundException($"Plugin {plugin.ID} zip file not found at {downloadedFilePath}", downloadedFilePath); + try { PluginManager.InstallPlugin(plugin, downloadedFilePath); - File.Delete(downloadedFilePath); + + if (!plugin.IsFromLocalInstallPath) + File.Delete(downloadedFilePath); } catch (FileNotFoundException e) { diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs index 7a7496ece1a..691f9c26cc8 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs @@ -1,5 +1,10 @@ -using System.IO; +using Flow.Launcher.Core.ExternalPlugins; +using Flow.Launcher.Infrastructure.UserSettings; +using ICSharpCode.SharpZipLib.Zip; +using Newtonsoft.Json; +using System.IO; using System.IO.Compression; +using System.Linq; namespace Flow.Launcher.Plugin.PluginsManager { @@ -33,5 +38,28 @@ internal static string GetContainingFolderPathAfterUnzip(string unzippedParentFo return string.Empty; } + + internal static UserPlugin GetPluginInfoFromZip(string filePath) + { + var plugin = null as UserPlugin; + + using (ZipArchive archive = System.IO.Compression.ZipFile.OpenRead(filePath)) + { + var pluginJsonPath = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json").ToString(); + ZipArchiveEntry pluginJsonEntry = archive.GetEntry(pluginJsonPath); + + if (pluginJsonEntry != null) + { + using (StreamReader reader = new StreamReader(pluginJsonEntry.Open())) + { + string pluginJsonContent = reader.ReadToEnd(); + plugin = JsonConvert.DeserializeObject(pluginJsonContent); + plugin.IcoPath = "Images\\zipfolder.png"; + } + } + } + + return plugin; + } } }