Merge branch 'dev' into settings_panel

This commit is contained in:
Jack Ye 2025-02-24 17:10:33 +08:00 committed by GitHub
commit 2ff1388619
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
64 changed files with 777 additions and 490 deletions

View file

@ -9,11 +9,15 @@ using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedCommands;
using System.Linq; using System.Linq;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Configuration namespace Flow.Launcher.Core.Configuration
{ {
public class Portable : IPortable public class Portable : IPortable
{ {
private readonly IPublicAPI API = Ioc.Default.GetRequiredService<IPublicAPI>();
/// <summary> /// <summary>
/// As at Squirrel.Windows version 1.5.2, UpdateManager needs to be disposed after finish /// As at Squirrel.Windows version 1.5.2, UpdateManager needs to be disposed after finish
/// </summary> /// </summary>
@ -40,7 +44,7 @@ namespace Flow.Launcher.Core.Configuration
#endif #endif
IndicateDeletion(DataLocation.PortableDataPath); IndicateDeletion(DataLocation.PortableDataPath);
MessageBoxEx.Show("Flow Launcher needs to restart to finish disabling portable mode, " + API.ShowMsgBox("Flow Launcher needs to restart to finish disabling portable mode, " +
"after the restart your portable data profile will be deleted and roaming data profile kept"); "after the restart your portable data profile will be deleted and roaming data profile kept");
UpdateManager.RestartApp(Constant.ApplicationFileName); UpdateManager.RestartApp(Constant.ApplicationFileName);
@ -64,7 +68,7 @@ namespace Flow.Launcher.Core.Configuration
#endif #endif
IndicateDeletion(DataLocation.RoamingDataPath); IndicateDeletion(DataLocation.RoamingDataPath);
MessageBoxEx.Show("Flow Launcher needs to restart to finish enabling portable mode, " + API.ShowMsgBox("Flow Launcher needs to restart to finish enabling portable mode, " +
"after the restart your roaming data profile will be deleted and portable data profile kept"); "after the restart your roaming data profile will be deleted and portable data profile kept");
UpdateManager.RestartApp(Constant.ApplicationFileName); UpdateManager.RestartApp(Constant.ApplicationFileName);
@ -95,13 +99,13 @@ namespace Flow.Launcher.Core.Configuration
public void MoveUserDataFolder(string fromLocation, string toLocation) public void MoveUserDataFolder(string fromLocation, string toLocation)
{ {
FilesFolders.CopyAll(fromLocation, toLocation, MessageBoxEx.Show); FilesFolders.CopyAll(fromLocation, toLocation, (s) => API.ShowMsgBox(s));
VerifyUserDataAfterMove(fromLocation, toLocation); VerifyUserDataAfterMove(fromLocation, toLocation);
} }
public void VerifyUserDataAfterMove(string fromLocation, string toLocation) public void VerifyUserDataAfterMove(string fromLocation, string toLocation)
{ {
FilesFolders.VerifyBothFolderFilesEqual(fromLocation, toLocation, MessageBoxEx.Show); FilesFolders.VerifyBothFolderFilesEqual(fromLocation, toLocation, (s) => API.ShowMsgBox(s));
} }
public void CreateShortcuts() public void CreateShortcuts()
@ -157,13 +161,13 @@ namespace Flow.Launcher.Core.Configuration
// delete it and prompt the user to pick the portable data location // delete it and prompt the user to pick the portable data location
if (File.Exists(roamingDataDeleteFilePath)) if (File.Exists(roamingDataDeleteFilePath))
{ {
FilesFolders.RemoveFolderIfExists(roamingDataDir, MessageBoxEx.Show); FilesFolders.RemoveFolderIfExists(roamingDataDir, (s) => API.ShowMsgBox(s));
if (MessageBoxEx.Show("Flow Launcher has detected you enabled portable mode, " + if (API.ShowMsgBox("Flow Launcher has detected you enabled portable mode, " +
"would you like to move it to a different location?", string.Empty, "would you like to move it to a different location?", string.Empty,
MessageBoxButton.YesNo) == MessageBoxResult.Yes) MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{ {
FilesFolders.OpenPath(Constant.RootDirectory, MessageBoxEx.Show); FilesFolders.OpenPath(Constant.RootDirectory, (s) => API.ShowMsgBox(s));
Environment.Exit(0); Environment.Exit(0);
} }
@ -172,9 +176,9 @@ namespace Flow.Launcher.Core.Configuration
// delete it and notify the user about it. // delete it and notify the user about it.
else if (File.Exists(portableDataDeleteFilePath)) else if (File.Exists(portableDataDeleteFilePath))
{ {
FilesFolders.RemoveFolderIfExists(portableDataDir, MessageBoxEx.Show); FilesFolders.RemoveFolderIfExists(portableDataDir, (s) => API.ShowMsgBox(s));
MessageBoxEx.Show("Flow Launcher has detected you disabled portable mode, " + API.ShowMsgBox("Flow Launcher has detected you disabled portable mode, " +
"the relevant shortcuts and uninstaller entry have been created"); "the relevant shortcuts and uninstaller entry have been created");
} }
} }
@ -186,7 +190,7 @@ namespace Flow.Launcher.Core.Configuration
if (roamingLocationExists && portableLocationExists) if (roamingLocationExists && portableLocationExists)
{ {
MessageBoxEx.Show(string.Format("Flow Launcher detected your user data exists both in {0} and " + API.ShowMsgBox(string.Format("Flow Launcher detected your user data exists both in {0} and " +
"{1}. {2}{2}Please delete {1} in order to proceed. No changes have occurred.", "{1}. {2}{2}Please delete {1} in order to proceed. No changes have occurred.",
DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine)); DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine));

View file

@ -8,11 +8,14 @@ using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Forms; using System.Windows.Forms;
using Flow.Launcher.Core.Resource; using Flow.Launcher.Core.Resource;
using CommunityToolkit.Mvvm.DependencyInjection;
namespace Flow.Launcher.Core.ExternalPlugins.Environments namespace Flow.Launcher.Core.ExternalPlugins.Environments
{ {
public abstract class AbstractPluginEnvironment public abstract class AbstractPluginEnvironment
{ {
protected readonly IPublicAPI API = Ioc.Default.GetRequiredService<IPublicAPI>();
internal abstract string Language { get; } internal abstract string Language { get; }
internal abstract string EnvName { get; } internal abstract string EnvName { get; }
@ -25,7 +28,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
internal virtual string FileDialogFilter => string.Empty; internal virtual string FileDialogFilter => string.Empty;
internal abstract string PluginsSettingsFilePath { get; set; } internal abstract string PluginsSettingsFilePath { get; set; }
internal List<PluginMetadata> PluginMetadataList; internal List<PluginMetadata> PluginMetadataList;
@ -57,7 +60,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
EnvName, EnvName,
Environment.NewLine Environment.NewLine
); );
if (MessageBoxEx.Show(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No) if (API.ShowMsgBox(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
{ {
var msg = string.Format(InternationalizationManager.Instance.GetTranslation("runtimePluginChooseRuntimeExecutable"), EnvName); var msg = string.Format(InternationalizationManager.Instance.GetTranslation("runtimePluginChooseRuntimeExecutable"), EnvName);
string selectedFile; string selectedFile;
@ -82,7 +85,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
} }
else else
{ {
MessageBoxEx.Show(string.Format(InternationalizationManager.Instance.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language)); API.ShowMsgBox(string.Format(InternationalizationManager.Instance.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language));
Log.Error("PluginsLoader", Log.Error("PluginsLoader",
$"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.", $"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.",
$"{Language}Environment"); $"{Language}Environment");
@ -98,7 +101,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
if (expectedPath == currentPath) if (expectedPath == currentPath)
return; return;
FilesFolders.RemoveFolderIfExists(installedDirPath, MessageBoxEx.Show); FilesFolders.RemoveFolderIfExists(installedDirPath, (s) => API.ShowMsgBox(s));
InstallEnvironment(); InstallEnvironment();

View file

@ -28,7 +28,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
internal override void InstallEnvironment() internal override void InstallEnvironment()
{ {
FilesFolders.RemoveFolderIfExists(InstallPath, MessageBoxEx.Show); FilesFolders.RemoveFolderIfExists(InstallPath, (s) => API.ShowMsgBox(s));
// Python 3.11.4 is no longer Windows 7 compatible. If user is on Win 7 and // Python 3.11.4 is no longer Windows 7 compatible. If user is on Win 7 and
// uses Python plugin they need to custom install and use v3.8.9 // uses Python plugin they need to custom install and use v3.8.9

View file

@ -25,7 +25,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
internal override void InstallEnvironment() internal override void InstallEnvironment()
{ {
FilesFolders.RemoveFolderIfExists(InstallPath, MessageBoxEx.Show); FilesFolders.RemoveFolderIfExists(InstallPath, (s) => API.ShowMsgBox(s));
DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait(); DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait();

View file

@ -25,7 +25,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
internal override void InstallEnvironment() internal override void InstallEnvironment()
{ {
FilesFolders.RemoveFolderIfExists(InstallPath, MessageBoxEx.Show); FilesFolders.RemoveFolderIfExists(InstallPath, (s) => API.ShowMsgBox(s));
DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait(); DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait();

View file

@ -44,8 +44,10 @@ namespace Flow.Launcher.Core.Plugin
private string SettingConfigurationPath => private string SettingConfigurationPath =>
Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml"); Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "SettingsTemplate.yaml");
private string SettingPath => Path.Combine(DataLocation.PluginSettingsDirectory, private string SettingDirectory => Path.Combine(DataLocation.PluginSettingsDirectory,
Context.CurrentPluginMetadata.Name, "Settings.json"); Context.CurrentPluginMetadata.Name);
private string SettingPath => Path.Combine(SettingDirectory, "Settings.json");
public abstract List<Result> LoadContextMenus(Result selectedResult); public abstract List<Result> LoadContextMenus(Result selectedResult);
@ -159,5 +161,13 @@ namespace Flow.Launcher.Core.Plugin
{ {
return Settings.CreateSettingPanel(); return Settings.CreateSettingPanel();
} }
public void DeletePluginSettingsDirectory()
{
if (Directory.Exists(SettingDirectory))
{
Directory.Delete(SettingDirectory, true);
}
}
} }
} }

View file

@ -112,10 +112,15 @@ namespace Flow.Launcher.Core.Plugin
RPC.StartListening(); RPC.StartListening();
} }
public virtual Task ReloadDataAsync() public virtual async Task ReloadDataAsync()
{ {
SetupJsonRPC(); try
return Task.CompletedTask; {
await RPC.InvokeAsync("reload_data", Context);
}
catch (RemoteMethodNotFoundException e)
{
}
} }
public virtual async ValueTask DisposeAsync() public virtual async ValueTask DisposeAsync()

View file

@ -14,6 +14,7 @@ using ISavable = Flow.Launcher.Plugin.ISavable;
using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedCommands;
using System.Text.Json; using System.Text.Json;
using Flow.Launcher.Core.Resource; using Flow.Launcher.Core.Resource;
using CommunityToolkit.Mvvm.DependencyInjection;
namespace Flow.Launcher.Core.Plugin namespace Flow.Launcher.Core.Plugin
{ {
@ -28,7 +29,7 @@ namespace Flow.Launcher.Core.Plugin
public static readonly HashSet<PluginPair> GlobalPlugins = new(); public static readonly HashSet<PluginPair> GlobalPlugins = new();
public static readonly Dictionary<string, PluginPair> NonGlobalPlugins = new(); public static readonly Dictionary<string, PluginPair> NonGlobalPlugins = new();
public static IPublicAPI API { private set; get; } public static IPublicAPI API { get; private set; } = Ioc.Default.GetRequiredService<IPublicAPI>();
private static PluginsSettings Settings; private static PluginsSettings Settings;
private static List<PluginMetadata> _metadatas; private static List<PluginMetadata> _metadatas;
@ -158,9 +159,8 @@ namespace Flow.Launcher.Core.Plugin
/// Call initialize for all plugins /// Call initialize for all plugins
/// </summary> /// </summary>
/// <returns>return the list of failed to init plugins or null for none</returns> /// <returns>return the list of failed to init plugins or null for none</returns>
public static async Task InitializePluginsAsync(IPublicAPI api) public static async Task InitializePluginsAsync()
{ {
API = api;
var failedPlugins = new ConcurrentQueue<PluginPair>(); var failedPlugins = new ConcurrentQueue<PluginPair>();
var InitTasks = AllPlugins.Select(pair => Task.Run(async delegate var InitTasks = AllPlugins.Select(pair => Task.Run(async delegate
@ -204,15 +204,15 @@ namespace Flow.Launcher.Core.Plugin
} }
InternationalizationManager.Instance.AddPluginLanguageDirectories(GetPluginsForInterface<IPluginI18n>()); InternationalizationManager.Instance.AddPluginLanguageDirectories(GetPluginsForInterface<IPluginI18n>());
InternationalizationManager.Instance.ChangeLanguage(InternationalizationManager.Instance.Settings.Language); InternationalizationManager.Instance.ChangeLanguage(Ioc.Default.GetRequiredService<Settings>().Language);
if (failedPlugins.Any()) if (failedPlugins.Any())
{ {
var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name)); var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name));
API.ShowMsg( API.ShowMsg(
InternationalizationManager.Instance.GetTranslation("failedToInitializePluginsTitle"), API.GetTranslation("failedToInitializePluginsTitle"),
string.Format( string.Format(
InternationalizationManager.Instance.GetTranslation("failedToInitializePluginsMessage"), API.GetTranslation("failedToInitializePluginsMessage"),
failed failed
), ),
"", "",
@ -281,7 +281,7 @@ namespace Flow.Launcher.Core.Plugin
return results; return results;
} }
public static void UpdatePluginMetadata(List<Result> results, PluginMetadata metadata, Query query) public static void UpdatePluginMetadata(IReadOnlyList<Result> results, PluginMetadata metadata, Query query)
{ {
foreach (var r in results) foreach (var r in results)
{ {
@ -439,7 +439,7 @@ namespace Flow.Launcher.Core.Plugin
public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath) public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath)
{ {
InstallPlugin(newVersion, zipFilePath, checkModified:false); InstallPlugin(newVersion, zipFilePath, checkModified:false);
UninstallPlugin(existingVersion, removeSettings:false, checkModified:false); UninstallPlugin(existingVersion, removePluginFromSettings:false, removePluginSettings:false, checkModified: false);
_modifiedPlugins.Add(existingVersion.ID); _modifiedPlugins.Add(existingVersion.ID);
} }
@ -454,9 +454,9 @@ namespace Flow.Launcher.Core.Plugin
/// <summary> /// <summary>
/// Uninstall a plugin. /// Uninstall a plugin.
/// </summary> /// </summary>
public static void UninstallPlugin(PluginMetadata plugin, bool removeSettings = true) public static void UninstallPlugin(PluginMetadata plugin, bool removePluginFromSettings = true, bool removePluginSettings = false)
{ {
UninstallPlugin(plugin, removeSettings, true); UninstallPlugin(plugin, removePluginFromSettings, removePluginSettings, true);
} }
#endregion #endregion
@ -519,9 +519,17 @@ namespace Flow.Launcher.Core.Plugin
var newPluginPath = Path.Combine(installDirectory, folderName); var newPluginPath = Path.Combine(installDirectory, folderName);
FilesFolders.CopyAll(pluginFolderPath, newPluginPath, MessageBoxEx.Show); FilesFolders.CopyAll(pluginFolderPath, newPluginPath, (s) => API.ShowMsgBox(s));
Directory.Delete(tempFolderPluginPath, true); try
{
if (Directory.Exists(tempFolderPluginPath))
Directory.Delete(tempFolderPluginPath, true);
}
catch (Exception e)
{
Log.Exception($"|PluginManager.InstallPlugin|Failed to delete temp folder {tempFolderPluginPath}", e);
}
if (checkModified) if (checkModified)
{ {
@ -529,14 +537,62 @@ namespace Flow.Launcher.Core.Plugin
} }
} }
internal static void UninstallPlugin(PluginMetadata plugin, bool removeSettings, bool checkModified) internal static void UninstallPlugin(PluginMetadata plugin, bool removePluginFromSettings, bool removePluginSettings, bool checkModified)
{ {
if (checkModified && PluginModified(plugin.ID)) if (checkModified && PluginModified(plugin.ID))
{ {
throw new ArgumentException($"Plugin {plugin.Name} has been modified"); throw new ArgumentException($"Plugin {plugin.Name} has been modified");
} }
if (removeSettings) if (removePluginSettings)
{
if (AllowedLanguage.IsDotNet(plugin.Language)) // for the plugin in .NET, we can use assembly loader
{
var assemblyLoader = new PluginAssemblyLoader(plugin.ExecuteFilePath);
var assembly = assemblyLoader.LoadAssemblyAndDependencies();
var assemblyName = assembly.GetName().Name;
// if user want to remove the plugin settings, we cannot call save method for the plugin json storage instance of this plugin
// so we need to remove it from the api instance
var method = API.GetType().GetMethod("RemovePluginSettings");
var pluginJsonStorage = method?.Invoke(API, new object[] { assemblyName });
// if there exists a json storage for current plugin, we need to delete the directory path
if (pluginJsonStorage != null)
{
var deleteMethod = pluginJsonStorage.GetType().GetMethod("DeleteDirectory");
try
{
deleteMethod?.Invoke(pluginJsonStorage, null);
}
catch (Exception e)
{
Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin json folder for {plugin.Name}", e);
API.ShowMsg(API.GetTranslation("failedToRemovePluginSettingsTitle"),
string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name));
}
}
}
else // the plugin with json prc interface
{
var pluginPair = AllPlugins.FirstOrDefault(p => p.Metadata.ID == plugin.ID);
if (pluginPair != null && pluginPair.Plugin is JsonRPCPlugin jsonRpcPlugin)
{
try
{
jsonRpcPlugin.DeletePluginSettingsDirectory();
}
catch (Exception e)
{
Log.Exception($"|PluginManager.UninstallPlugin|Failed to delete plugin json folder for {plugin.Name}", e);
API.ShowMsg(API.GetTranslation("failedToRemovePluginSettingsTitle"),
string.Format(API.GetTranslation("failedToRemovePluginSettingsMessage"), plugin.Name));
}
}
}
}
if (removePluginFromSettings)
{ {
Settings.Plugins.Remove(plugin.ID); Settings.Plugins.Remove(plugin.ID);
AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID);

View file

@ -4,6 +4,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.ExternalPlugins.Environments; using Flow.Launcher.Core.ExternalPlugins.Environments;
#pragma warning disable IDE0005 #pragma warning disable IDE0005
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
@ -119,7 +120,7 @@ namespace Flow.Launcher.Core.Plugin
_ = Task.Run(() => _ = Task.Run(() =>
{ {
MessageBoxEx.Show($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" + Ioc.Default.GetRequiredService<IPublicAPI>().ShowMsgBox($"{errorMessage}{Environment.NewLine}{Environment.NewLine}" +
$"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" + $"{errorPluginString}{Environment.NewLine}{Environment.NewLine}" +
$"Please refer to the logs for more information", "", $"Please refer to the logs for more information", "",
MessageBoxButton.OK, MessageBoxImage.Warning); MessageBoxButton.OK, MessageBoxImage.Warning);

View file

@ -11,22 +11,24 @@ using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using System.Globalization; using System.Globalization;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
namespace Flow.Launcher.Core.Resource namespace Flow.Launcher.Core.Resource
{ {
public class Internationalization public class Internationalization
{ {
public Settings Settings { get; set; }
private const string Folder = "Languages"; private const string Folder = "Languages";
private const string DefaultLanguageCode = "en"; private const string DefaultLanguageCode = "en";
private const string DefaultFile = "en.xaml"; private const string DefaultFile = "en.xaml";
private const string Extension = ".xaml"; private const string Extension = ".xaml";
private readonly Settings _settings;
private readonly List<string> _languageDirectories = new List<string>(); private readonly List<string> _languageDirectories = new List<string>();
private readonly List<ResourceDictionary> _oldResources = new List<ResourceDictionary>(); private readonly List<ResourceDictionary> _oldResources = new List<ResourceDictionary>();
private readonly string SystemLanguageCode; private readonly string SystemLanguageCode;
public Internationalization() public Internationalization(Settings settings)
{ {
_settings = settings;
AddFlowLauncherLanguageDirectory(); AddFlowLauncherLanguageDirectory();
SystemLanguageCode = GetSystemLanguageCodeAtStartup(); SystemLanguageCode = GetSystemLanguageCodeAtStartup();
} }
@ -141,7 +143,7 @@ namespace Flow.Launcher.Core.Resource
CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture; CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture;
// Raise event after culture is set // Raise event after culture is set
Settings.Language = isSystem ? Constant.SystemLanguageCode : language.LanguageCode; _settings.Language = isSystem ? Constant.SystemLanguageCode : language.LanguageCode;
_ = Task.Run(() => _ = Task.Run(() =>
{ {
UpdatePluginMetadataTranslations(); UpdatePluginMetadataTranslations();
@ -152,7 +154,7 @@ namespace Flow.Launcher.Core.Resource
{ {
var languageToSet = GetLanguageByLanguageCode(languageCodeToSet); var languageToSet = GetLanguageByLanguageCode(languageCodeToSet);
if (Settings.ShouldUsePinyin) if (_settings.ShouldUsePinyin)
return false; return false;
if (languageToSet != AvailableLanguages.Chinese && languageToSet != AvailableLanguages.Chinese_TW) if (languageToSet != AvailableLanguages.Chinese && languageToSet != AvailableLanguages.Chinese_TW)
@ -162,7 +164,7 @@ namespace Flow.Launcher.Core.Resource
// "Do you want to search with pinyin?" // "Do you want to search with pinyin?"
string text = languageToSet == AvailableLanguages.Chinese ? "是否启用拼音搜索?" : "是否啓用拼音搜索?" ; string text = languageToSet == AvailableLanguages.Chinese ? "是否启用拼音搜索?" : "是否啓用拼音搜索?" ;
if (MessageBoxEx.Show(text, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No) if (Ioc.Default.GetRequiredService<IPublicAPI>().ShowMsgBox(text, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
return false; return false;
return true; return true;

View file

@ -1,26 +1,12 @@
namespace Flow.Launcher.Core.Resource using System;
using CommunityToolkit.Mvvm.DependencyInjection;
namespace Flow.Launcher.Core.Resource
{ {
[Obsolete("InternationalizationManager.Instance is obsolete. Use Ioc.Default.GetRequiredService<Internationalization>() instead.")]
public static class InternationalizationManager public static class InternationalizationManager
{ {
private static Internationalization instance;
private static object syncObject = new object();
public static Internationalization Instance public static Internationalization Instance
{ => Ioc.Default.GetRequiredService<Internationalization>();
get
{
if (instance == null)
{
lock (syncObject)
{
if (instance == null)
{
instance = new Internationalization();
}
}
}
return instance;
}
}
} }
} }

View file

@ -8,10 +8,12 @@ using System.Windows.Controls;
using System.Windows.Markup; using System.Windows.Markup;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Effects; using System.Windows.Media.Effects;
using System.Windows.Shell;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using System.Windows.Shell; using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin;
namespace Flow.Launcher.Core.Resource namespace Flow.Launcher.Core.Resource
{ {
@ -23,10 +25,11 @@ namespace Flow.Launcher.Core.Resource
private const int ShadowExtraMargin = 32; private const int ShadowExtraMargin = 32;
private readonly List<string> _themeDirectories = new List<string>(); private readonly IPublicAPI _api;
private readonly Settings _settings;
private readonly List<string> _themeDirectories = new();
private ResourceDictionary _oldResource; private ResourceDictionary _oldResource;
private string _oldTheme; private string _oldTheme;
public Settings Settings { get; set; }
private const string Folder = Constant.Themes; private const string Folder = Constant.Themes;
private const string Extension = ".xaml"; private const string Extension = ".xaml";
private string DirectoryPath => Path.Combine(Constant.ProgramDirectory, Folder); private string DirectoryPath => Path.Combine(Constant.ProgramDirectory, Folder);
@ -36,8 +39,11 @@ namespace Flow.Launcher.Core.Resource
private double mainWindowWidth; private double mainWindowWidth;
public Theme() public Theme(IPublicAPI publicAPI, Settings settings)
{ {
_api = publicAPI;
_settings = settings;
_themeDirectories.Add(DirectoryPath); _themeDirectories.Add(DirectoryPath);
_themeDirectories.Add(UserDirectoryPath); _themeDirectories.Add(UserDirectoryPath);
MakeSureThemeDirectoriesExist(); MakeSureThemeDirectoriesExist();
@ -88,7 +94,7 @@ namespace Flow.Launcher.Core.Resource
// to things like fonts // to things like fonts
UpdateResourceDictionary(GetResourceDictionary(theme)); UpdateResourceDictionary(GetResourceDictionary(theme));
Settings.Theme = theme; _settings.Theme = theme;
//always allow re-loading default theme, in case of failure of switching to a new theme from default theme //always allow re-loading default theme, in case of failure of switching to a new theme from default theme
@ -99,7 +105,7 @@ namespace Flow.Launcher.Core.Resource
BlurEnabled = Win32Helper.IsBlurTheme(); BlurEnabled = Win32Helper.IsBlurTheme();
if (Settings.UseDropShadowEffect && !BlurEnabled) if (_settings.UseDropShadowEffect && !BlurEnabled)
AddDropShadowEffectToCurrentTheme(); AddDropShadowEffectToCurrentTheme();
Win32Helper.SetBlurForWindow(Application.Current.MainWindow, BlurEnabled); Win32Helper.SetBlurForWindow(Application.Current.MainWindow, BlurEnabled);
@ -109,7 +115,7 @@ namespace Flow.Launcher.Core.Resource
Log.Error($"|Theme.ChangeTheme|Theme <{theme}> path can't be found"); Log.Error($"|Theme.ChangeTheme|Theme <{theme}> path can't be found");
if (theme != defaultTheme) if (theme != defaultTheme)
{ {
MessageBoxEx.Show(string.Format(InternationalizationManager.Instance.GetTranslation("theme_load_failure_path_not_exists"), theme)); _api.ShowMsgBox(string.Format(InternationalizationManager.Instance.GetTranslation("theme_load_failure_path_not_exists"), theme));
ChangeTheme(defaultTheme); ChangeTheme(defaultTheme);
} }
return false; return false;
@ -119,7 +125,7 @@ namespace Flow.Launcher.Core.Resource
Log.Error($"|Theme.ChangeTheme|Theme <{theme}> fail to parse"); Log.Error($"|Theme.ChangeTheme|Theme <{theme}> fail to parse");
if (theme != defaultTheme) if (theme != defaultTheme)
{ {
MessageBoxEx.Show(string.Format(InternationalizationManager.Instance.GetTranslation("theme_load_failure_parse_error"), theme)); _api.ShowMsgBox(string.Format(InternationalizationManager.Instance.GetTranslation("theme_load_failure_parse_error"), theme));
ChangeTheme(defaultTheme); ChangeTheme(defaultTheme);
} }
return false; return false;
@ -147,7 +153,7 @@ namespace Flow.Launcher.Core.Resource
return dict; return dict;
} }
private ResourceDictionary CurrentThemeResourceDictionary() => GetThemeResourceDictionary(Settings.Theme); private ResourceDictionary CurrentThemeResourceDictionary() => GetThemeResourceDictionary(_settings.Theme);
public ResourceDictionary GetResourceDictionary(string theme) public ResourceDictionary GetResourceDictionary(string theme)
{ {
@ -156,10 +162,10 @@ namespace Flow.Launcher.Core.Resource
if (dict["QueryBoxStyle"] is Style queryBoxStyle && if (dict["QueryBoxStyle"] is Style queryBoxStyle &&
dict["QuerySuggestionBoxStyle"] is Style querySuggestionBoxStyle) dict["QuerySuggestionBoxStyle"] is Style querySuggestionBoxStyle)
{ {
var fontFamily = new FontFamily(Settings.QueryBoxFont); var fontFamily = new FontFamily(_settings.QueryBoxFont);
var fontStyle = FontHelper.GetFontStyleFromInvariantStringOrNormal(Settings.QueryBoxFontStyle); var fontStyle = FontHelper.GetFontStyleFromInvariantStringOrNormal(_settings.QueryBoxFontStyle);
var fontWeight = FontHelper.GetFontWeightFromInvariantStringOrNormal(Settings.QueryBoxFontWeight); var fontWeight = FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.QueryBoxFontWeight);
var fontStretch = FontHelper.GetFontStretchFromInvariantStringOrNormal(Settings.QueryBoxFontStretch); var fontStretch = FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.QueryBoxFontStretch);
queryBoxStyle.Setters.Add(new Setter(TextBox.FontFamilyProperty, fontFamily)); queryBoxStyle.Setters.Add(new Setter(TextBox.FontFamilyProperty, fontFamily));
queryBoxStyle.Setters.Add(new Setter(TextBox.FontStyleProperty, fontStyle)); queryBoxStyle.Setters.Add(new Setter(TextBox.FontStyleProperty, fontStyle));
@ -184,10 +190,10 @@ namespace Flow.Launcher.Core.Resource
dict["ItemHotkeyStyle"] is Style resultHotkeyItemStyle && dict["ItemHotkeyStyle"] is Style resultHotkeyItemStyle &&
dict["ItemHotkeySelectedStyle"] is Style resultHotkeyItemSelectedStyle) dict["ItemHotkeySelectedStyle"] is Style resultHotkeyItemSelectedStyle)
{ {
Setter fontFamily = new Setter(TextBlock.FontFamilyProperty, new FontFamily(Settings.ResultFont)); Setter fontFamily = new Setter(TextBlock.FontFamilyProperty, new FontFamily(_settings.ResultFont));
Setter fontStyle = new Setter(TextBlock.FontStyleProperty, FontHelper.GetFontStyleFromInvariantStringOrNormal(Settings.ResultFontStyle)); Setter fontStyle = new Setter(TextBlock.FontStyleProperty, FontHelper.GetFontStyleFromInvariantStringOrNormal(_settings.ResultFontStyle));
Setter fontWeight = new Setter(TextBlock.FontWeightProperty, FontHelper.GetFontWeightFromInvariantStringOrNormal(Settings.ResultFontWeight)); Setter fontWeight = new Setter(TextBlock.FontWeightProperty, FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.ResultFontWeight));
Setter fontStretch = new Setter(TextBlock.FontStretchProperty, FontHelper.GetFontStretchFromInvariantStringOrNormal(Settings.ResultFontStretch)); Setter fontStretch = new Setter(TextBlock.FontStretchProperty, FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.ResultFontStretch));
Setter[] setters = { fontFamily, fontStyle, fontWeight, fontStretch }; Setter[] setters = { fontFamily, fontStyle, fontWeight, fontStretch };
Array.ForEach( Array.ForEach(
@ -199,10 +205,10 @@ namespace Flow.Launcher.Core.Resource
dict["ItemSubTitleStyle"] is Style resultSubItemStyle && dict["ItemSubTitleStyle"] is Style resultSubItemStyle &&
dict["ItemSubTitleSelectedStyle"] is Style resultSubItemSelectedStyle) dict["ItemSubTitleSelectedStyle"] is Style resultSubItemSelectedStyle)
{ {
Setter fontFamily = new Setter(TextBlock.FontFamilyProperty, new FontFamily(Settings.ResultSubFont)); Setter fontFamily = new Setter(TextBlock.FontFamilyProperty, new FontFamily(_settings.ResultSubFont));
Setter fontStyle = new Setter(TextBlock.FontStyleProperty, FontHelper.GetFontStyleFromInvariantStringOrNormal(Settings.ResultSubFontStyle)); Setter fontStyle = new Setter(TextBlock.FontStyleProperty, FontHelper.GetFontStyleFromInvariantStringOrNormal(_settings.ResultSubFontStyle));
Setter fontWeight = new Setter(TextBlock.FontWeightProperty, FontHelper.GetFontWeightFromInvariantStringOrNormal(Settings.ResultSubFontWeight)); Setter fontWeight = new Setter(TextBlock.FontWeightProperty, FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.ResultSubFontWeight));
Setter fontStretch = new Setter(TextBlock.FontStretchProperty, FontHelper.GetFontStretchFromInvariantStringOrNormal(Settings.ResultSubFontStretch)); Setter fontStretch = new Setter(TextBlock.FontStretchProperty, FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.ResultSubFontStretch));
Setter[] setters = { fontFamily, fontStyle, fontWeight, fontStretch }; Setter[] setters = { fontFamily, fontStyle, fontWeight, fontStretch };
Array.ForEach( Array.ForEach(
@ -212,7 +218,7 @@ namespace Flow.Launcher.Core.Resource
/* Ignore Theme Window Width and use setting */ /* Ignore Theme Window Width and use setting */
var windowStyle = dict["WindowStyle"] as Style; var windowStyle = dict["WindowStyle"] as Style;
var width = Settings.WindowSize; var width = _settings.WindowSize;
windowStyle.Setters.Add(new Setter(Window.WidthProperty, width)); windowStyle.Setters.Add(new Setter(Window.WidthProperty, width));
mainWindowWidth = (double)width; mainWindowWidth = (double)width;
return dict; return dict;
@ -220,7 +226,7 @@ namespace Flow.Launcher.Core.Resource
private ResourceDictionary GetCurrentResourceDictionary( ) private ResourceDictionary GetCurrentResourceDictionary( )
{ {
return GetResourceDictionary(Settings.Theme); return GetResourceDictionary(_settings.Theme);
} }
public List<ThemeData> LoadAvailableThemes() public List<ThemeData> LoadAvailableThemes()

View file

@ -1,26 +1,12 @@
namespace Flow.Launcher.Core.Resource using System;
using CommunityToolkit.Mvvm.DependencyInjection;
namespace Flow.Launcher.Core.Resource
{ {
[Obsolete("ThemeManager.Instance is obsolete. Use Ioc.Default.GetRequiredService<Theme>() instead.")]
public class ThemeManager public class ThemeManager
{ {
private static Theme instance;
private static object syncObject = new object();
public static Theme Instance public static Theme Instance
{ => Ioc.Default.GetRequiredService<Theme>();
get
{
if (instance == null)
{
lock (syncObject)
{
if (instance == null)
{
instance = new Theme();
}
}
}
return instance;
}
}
} }
} }

View file

@ -22,23 +22,26 @@ namespace Flow.Launcher.Core
{ {
public class Updater public class Updater
{ {
public string GitHubRepository { get; } public string GitHubRepository { get; init; }
public Updater(string gitHubRepository) private readonly IPublicAPI _api;
public Updater(IPublicAPI publicAPI, string gitHubRepository)
{ {
_api = publicAPI;
GitHubRepository = gitHubRepository; GitHubRepository = gitHubRepository;
} }
private SemaphoreSlim UpdateLock { get; } = new SemaphoreSlim(1); private SemaphoreSlim UpdateLock { get; } = new SemaphoreSlim(1);
public async Task UpdateAppAsync(IPublicAPI api, bool silentUpdate = true) public async Task UpdateAppAsync(bool silentUpdate = true)
{ {
await UpdateLock.WaitAsync().ConfigureAwait(false); await UpdateLock.WaitAsync().ConfigureAwait(false);
try try
{ {
if (!silentUpdate) if (!silentUpdate)
api.ShowMsg(api.GetTranslation("pleaseWait"), _api.ShowMsg(_api.GetTranslation("pleaseWait"),
api.GetTranslation("update_flowlauncher_update_check")); _api.GetTranslation("update_flowlauncher_update_check"));
using var updateManager = await GitHubUpdateManagerAsync(GitHubRepository).ConfigureAwait(false); using var updateManager = await GitHubUpdateManagerAsync(GitHubRepository).ConfigureAwait(false);
@ -53,13 +56,13 @@ namespace Flow.Launcher.Core
if (newReleaseVersion <= currentVersion) if (newReleaseVersion <= currentVersion)
{ {
if (!silentUpdate) if (!silentUpdate)
MessageBoxEx.Show(api.GetTranslation("update_flowlauncher_already_on_latest")); _api.ShowMsgBox(_api.GetTranslation("update_flowlauncher_already_on_latest"));
return; return;
} }
if (!silentUpdate) if (!silentUpdate)
api.ShowMsg(api.GetTranslation("update_flowlauncher_update_found"), _api.ShowMsg(_api.GetTranslation("update_flowlauncher_update_found"),
api.GetTranslation("update_flowlauncher_updating")); _api.GetTranslation("update_flowlauncher_updating"));
await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply).ConfigureAwait(false); await updateManager.DownloadReleases(newUpdateInfo.ReleasesToApply).ConfigureAwait(false);
@ -68,9 +71,9 @@ namespace Flow.Launcher.Core
if (DataLocation.PortableDataLocationInUse()) if (DataLocation.PortableDataLocationInUse())
{ {
var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}"; var targetDestination = updateManager.RootAppDirectory + $"\\app-{newReleaseVersion.ToString()}\\{DataLocation.PortableFolderName}";
FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination, MessageBoxEx.Show); FilesFolders.CopyAll(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s));
if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination, MessageBoxEx.Show)) if (!FilesFolders.VerifyBothFolderFilesEqual(DataLocation.PortableDataPath, targetDestination, (s) => _api.ShowMsgBox(s)))
MessageBoxEx.Show(string.Format(api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"), _api.ShowMsgBox(string.Format(_api.GetTranslation("update_flowlauncher_fail_moving_portable_user_profile_data"),
DataLocation.PortableDataPath, DataLocation.PortableDataPath,
targetDestination)); targetDestination));
} }
@ -83,7 +86,7 @@ namespace Flow.Launcher.Core
Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}"); Log.Info($"|Updater.UpdateApp|Update success:{newVersionTips}");
if (MessageBoxEx.Show(newVersionTips, api.GetTranslation("update_flowlauncher_new_update"), MessageBoxButton.YesNo) == MessageBoxResult.Yes) if (_api.ShowMsgBox(newVersionTips, _api.GetTranslation("update_flowlauncher_new_update"), MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{ {
UpdateManager.RestartApp(Constant.ApplicationFileName); UpdateManager.RestartApp(Constant.ApplicationFileName);
} }
@ -96,8 +99,8 @@ namespace Flow.Launcher.Core
Log.Exception($"|Updater.UpdateApp|Error Occurred", e); Log.Exception($"|Updater.UpdateApp|Error Occurred", e);
if (!silentUpdate) if (!silentUpdate)
api.ShowMsg(api.GetTranslation("update_flowlauncher_fail"), _api.ShowMsg(_api.GetTranslation("update_flowlauncher_fail"),
api.GetTranslation("update_flowlauncher_check_connection")); _api.GetTranslation("update_flowlauncher_check_connection"));
} }
finally finally
{ {

View file

@ -54,6 +54,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Ben.Demystifier" Version="0.4.1" /> <PackageReference Include="Ben.Demystifier" Version="0.4.1" />
<PackageReference Include="BitFaster.Caching" Version="2.5.3" /> <PackageReference Include="BitFaster.Caching" Version="2.5.3" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Fody" Version="6.5.5"> <PackageReference Include="Fody" Version="6.5.5">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View file

@ -8,6 +8,7 @@ using Flow.Launcher.Infrastructure.UserSettings;
using System; using System;
using System.Threading; using System.Threading;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using CommunityToolkit.Mvvm.DependencyInjection;
namespace Flow.Launcher.Infrastructure.Http namespace Flow.Launcher.Infrastructure.Http
{ {
@ -17,8 +18,6 @@ namespace Flow.Launcher.Infrastructure.Http
private static HttpClient client = new HttpClient(); private static HttpClient client = new HttpClient();
public static IPublicAPI API { get; set; }
static Http() static Http()
{ {
// need to be added so it would work on a win10 machine // need to be added so it would work on a win10 machine
@ -78,7 +77,7 @@ namespace Flow.Launcher.Infrastructure.Http
} }
catch (UriFormatException e) catch (UriFormatException e)
{ {
API.ShowMsg("Please try again", "Unable to parse Http Proxy"); Ioc.Default.GetRequiredService<IPublicAPI>().ShowMsg("Please try again", "Unable to parse Http Proxy");
Log.Exception("Flow.Launcher.Infrastructure.Http", "Unable to parse Uri", e); Log.Exception("Flow.Launcher.Infrastructure.Http", "Unable to parse Uri", e);
} }
} }

View file

@ -6,6 +6,7 @@ using System.Text;
using JetBrains.Annotations; using JetBrains.Annotations;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using ToolGood.Words.Pinyin; using ToolGood.Words.Pinyin;
using CommunityToolkit.Mvvm.DependencyInjection;
namespace Flow.Launcher.Infrastructure namespace Flow.Launcher.Infrastructure
{ {
@ -129,7 +130,12 @@ namespace Flow.Launcher.Infrastructure
private Settings _settings; private Settings _settings;
public void Initialize([NotNull] Settings settings) public PinyinAlphabet()
{
Initialize(Ioc.Default.GetRequiredService<Settings>());
}
private void Initialize([NotNull] Settings settings)
{ {
_settings = settings ?? throw new ArgumentNullException(nameof(settings)); _settings = settings ?? throw new ArgumentNullException(nameof(settings));
} }

View file

@ -31,11 +31,12 @@ namespace Flow.Launcher.Infrastructure.Storage
protected JsonStorage() protected JsonStorage()
{ {
} }
public JsonStorage(string filePath) public JsonStorage(string filePath)
{ {
FilePath = filePath; FilePath = filePath;
DirectoryPath = Path.GetDirectoryName(filePath) ?? throw new ArgumentException("Invalid file path"); DirectoryPath = Path.GetDirectoryName(filePath) ?? throw new ArgumentException("Invalid file path");
Helper.ValidateDirectory(DirectoryPath); Helper.ValidateDirectory(DirectoryPath);
} }
@ -97,6 +98,7 @@ namespace Flow.Launcher.Infrastructure.Storage
return default; return default;
} }
} }
private void RestoreBackup() private void RestoreBackup()
{ {
Log.Info($"|JsonStorage.Load|Failed to load settings.json, {BackupFilePath} restored successfully"); Log.Info($"|JsonStorage.Load|Failed to load settings.json, {BackupFilePath} restored successfully");
@ -179,25 +181,21 @@ namespace Flow.Launcher.Infrastructure.Storage
public void Save() public void Save()
{ {
string serialized = JsonSerializer.Serialize(Data, string serialized = JsonSerializer.Serialize(Data,
new JsonSerializerOptions new JsonSerializerOptions { WriteIndented = true });
{
WriteIndented = true
});
File.WriteAllText(TempFilePath, serialized); File.WriteAllText(TempFilePath, serialized);
AtomicWriteSetting(); AtomicWriteSetting();
} }
public async Task SaveAsync() public async Task SaveAsync()
{ {
var tempOutput = File.OpenWrite(TempFilePath); await using var tempOutput = File.OpenWrite(TempFilePath);
await JsonSerializer.SerializeAsync(tempOutput, Data, await JsonSerializer.SerializeAsync(tempOutput, Data,
new JsonSerializerOptions new JsonSerializerOptions { WriteIndented = true });
{
WriteIndented = true
});
AtomicWriteSetting(); AtomicWriteSetting();
} }
private void AtomicWriteSetting() private void AtomicWriteSetting()
{ {
if (!File.Exists(FilePath)) if (!File.Exists(FilePath))
@ -206,9 +204,9 @@ namespace Flow.Launcher.Infrastructure.Storage
} }
else else
{ {
File.Replace(TempFilePath, FilePath, BackupFilePath); var finalFilePath = new FileInfo(FilePath).LinkTarget ?? FilePath;
File.Replace(TempFilePath, finalFilePath, BackupFilePath);
} }
} }
} }
} }

View file

@ -3,14 +3,17 @@ using Flow.Launcher.Infrastructure.UserSettings;
namespace Flow.Launcher.Infrastructure.Storage namespace Flow.Launcher.Infrastructure.Storage
{ {
public class PluginJsonStorage<T> :JsonStorage<T> where T : new() public class PluginJsonStorage<T> : JsonStorage<T> where T : new()
{ {
// Use assembly name to check which plugin is using this storage
public readonly string AssemblyName;
public PluginJsonStorage() public PluginJsonStorage()
{ {
// C# related, add python related below // C# related, add python related below
var dataType = typeof(T); var dataType = typeof(T);
var assemblyName = dataType.Assembly.GetName().Name; AssemblyName = dataType.Assembly.GetName().Name;
DirectoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName, Constant.Plugins, assemblyName); DirectoryPath = Path.Combine(DataLocation.DataDirectory(), DirectoryName, Constant.Plugins, AssemblyName);
Helper.ValidateDirectory(DirectoryPath); Helper.ValidateDirectory(DirectoryPath);
FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}"); FilePath = Path.Combine(DirectoryPath, $"{dataType.Name}{FileSuffix}");
@ -20,6 +23,13 @@ namespace Flow.Launcher.Infrastructure.Storage
{ {
Data = data; Data = data;
} }
public void DeleteDirectory()
{
if (Directory.Exists(DirectoryPath))
{
Directory.Delete(DirectoryPath, true);
}
}
} }
} }

View file

@ -1,28 +1,35 @@
using Flow.Launcher.Plugin.SharedModels; using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Plugin.SharedModels;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Flow.Launcher.Infrastructure.UserSettings;
namespace Flow.Launcher.Infrastructure namespace Flow.Launcher.Infrastructure
{ {
public class StringMatcher public class StringMatcher
{ {
private readonly MatchOption _defaultMatchOption = new MatchOption(); private readonly MatchOption _defaultMatchOption = new();
public SearchPrecisionScore UserSettingSearchPrecision { get; set; } public SearchPrecisionScore UserSettingSearchPrecision { get; set; }
private readonly IAlphabet _alphabet; private readonly IAlphabet _alphabet;
public StringMatcher(IAlphabet alphabet = null) public StringMatcher(IAlphabet alphabet, Settings settings)
{
_alphabet = alphabet;
UserSettingSearchPrecision = settings.QuerySearchPrecision;
}
// This is a workaround to allow unit tests to set the instance
public StringMatcher(IAlphabet alphabet)
{ {
_alphabet = alphabet; _alphabet = alphabet;
} }
public static StringMatcher Instance { get; internal set; }
public static MatchResult FuzzySearch(string query, string stringToCompare) public static MatchResult FuzzySearch(string query, string stringToCompare)
{ {
return Instance.FuzzyMatch(query, stringToCompare); return Ioc.Default.GetRequiredService<StringMatcher>().FuzzyMatch(query, stringToCompare);
} }
public MatchResult FuzzyMatch(string query, string stringToCompare) public MatchResult FuzzyMatch(string query, string stringToCompare)
@ -241,16 +248,16 @@ namespace Flow.Launcher.Infrastructure
return false; return false;
} }
private bool IsAcronymChar(string stringToCompare, int compareStringIndex) private static bool IsAcronymChar(string stringToCompare, int compareStringIndex)
=> char.IsUpper(stringToCompare[compareStringIndex]) || => char.IsUpper(stringToCompare[compareStringIndex]) ||
compareStringIndex == 0 || // 0 index means char is the start of the compare string, which is an acronym compareStringIndex == 0 || // 0 index means char is the start of the compare string, which is an acronym
char.IsWhiteSpace(stringToCompare[compareStringIndex - 1]); char.IsWhiteSpace(stringToCompare[compareStringIndex - 1]);
private bool IsAcronymNumber(string stringToCompare, int compareStringIndex) private static bool IsAcronymNumber(string stringToCompare, int compareStringIndex)
=> stringToCompare[compareStringIndex] >= 0 && stringToCompare[compareStringIndex] <= 9; => stringToCompare[compareStringIndex] >= 0 && stringToCompare[compareStringIndex] <= 9;
// To get the index of the closest space which preceeds the first matching index // To get the index of the closest space which preceeds the first matching index
private int CalculateClosestSpaceIndex(List<int> spaceIndices, int firstMatchIndex) private static int CalculateClosestSpaceIndex(List<int> spaceIndices, int firstMatchIndex)
{ {
var closestSpaceIndex = -1; var closestSpaceIndex = -1;

View file

@ -1,10 +1,11 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Drawing; using System.Drawing;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Windows; using System.Windows;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.Plugin.SharedModels;
using Flow.Launcher.ViewModel; using Flow.Launcher.ViewModel;
@ -13,6 +14,24 @@ namespace Flow.Launcher.Infrastructure.UserSettings
{ {
public class Settings : BaseModel, IHotkeySettings public class Settings : BaseModel, IHotkeySettings
{ {
private FlowLauncherJsonStorage<Settings> _storage;
private StringMatcher _stringMatcher = null;
public void SetStorage(FlowLauncherJsonStorage<Settings> storage)
{
_storage = storage;
}
public void Initialize()
{
_stringMatcher = Ioc.Default.GetRequiredService<StringMatcher>();
}
public void Save()
{
_storage.Save();
}
private string language = Constant.SystemLanguageCode; private string language = Constant.SystemLanguageCode;
private string _theme = Constant.DefaultTheme; private string _theme = Constant.DefaultTheme;
public string Hotkey { get; set; } = $"{KeyConstant.Alt} + {KeyConstant.Space}"; public string Hotkey { get; set; } = $"{KeyConstant.Alt} + {KeyConstant.Space}";
@ -180,7 +199,6 @@ namespace Flow.Launcher.Infrastructure.UserSettings
} }
}; };
/// <summary> /// <summary>
/// when false Alphabet static service will always return empty results /// when false Alphabet static service will always return empty results
/// </summary> /// </summary>
@ -198,8 +216,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings
set set
{ {
_querySearchPrecision = value; _querySearchPrecision = value;
if (StringMatcher.Instance != null) if (_stringMatcher != null)
StringMatcher.Instance.UserSettingSearchPrecision = value; _stringMatcher.UserSettingSearchPrecision = value;
} }
} }

View file

@ -17,7 +17,8 @@ namespace Flow.Launcher.Plugin
public interface IPublicAPI public interface IPublicAPI
{ {
/// <summary> /// <summary>
/// Change Flow.Launcher query /// Change Flow.Launcher query.
/// When current results are from context menu or history, it will go back to query results before changing query.
/// </summary> /// </summary>
/// <param name="query">query text</param> /// <param name="query">query text</param>
/// <param name="requery"> /// <param name="requery">
@ -299,7 +300,7 @@ namespace Flow.Launcher.Plugin
/// <summary> /// <summary>
/// Reloads the query. /// Reloads the query.
/// This method should run when selected item is from query results. /// When current results are from context menu or history, it will go back to query results before requerying.
/// </summary> /// </summary>
/// <param name="reselect">Choose the first result after reload if true; keep the last selected result if false. Default is true.</param> /// <param name="reselect">Choose the first result after reload if true; keep the last selected result if false. Default is true.</param>
public void ReQuery(bool reselect = true); public void ReQuery(bool reselect = true);

View file

@ -266,8 +266,9 @@ namespace Flow.Launcher.Plugin
/// The key to identify the record. This is used when FL checks whether the result is the topmost record. Or FL calculates the hashcode of the result for user selected records. /// The key to identify the record. This is used when FL checks whether the result is the topmost record. Or FL calculates the hashcode of the result for user selected records.
/// This can be useful when your plugin will change the Title or SubTitle of the result dynamically. /// This can be useful when your plugin will change the Title or SubTitle of the result dynamically.
/// If the plugin does not specific this, FL just uses Title and SubTitle to identify this result. /// If the plugin does not specific this, FL just uses Title and SubTitle to identify this result.
/// Note: Because old data does not have this key, we should use null as the default value for consistency.
/// </summary> /// </summary>
public string RecordKey { get; set; } = string.Empty; public string RecordKey { get; set; } = null;
/// <summary> /// <summary>
/// Info of the preview section of a <see cref="Result"/> /// Info of the preview section of a <see cref="Result"/>

View file

@ -1,5 +1,6 @@
using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedCommands;
using NUnit.Framework; using NUnit.Framework;
using NUnit.Framework.Legacy;
namespace Flow.Launcher.Test namespace Flow.Launcher.Test
{ {
@ -35,7 +36,7 @@ namespace Flow.Launcher.Test
[TestCase(@"c:\barr", @"c:\foo\..\bar\baz", false)] [TestCase(@"c:\barr", @"c:\foo\..\bar\baz", false)]
public void GivenTwoPaths_WhenCheckPathContains_ThenShouldBeExpectedResult(string parentPath, string path, bool expectedResult) public void GivenTwoPaths_WhenCheckPathContains_ThenShouldBeExpectedResult(string parentPath, string path, bool expectedResult)
{ {
Assert.AreEqual(expectedResult, FilesFolders.PathContains(parentPath, path)); ClassicAssert.AreEqual(expectedResult, FilesFolders.PathContains(parentPath, path));
} }
// Equality // Equality
@ -47,7 +48,7 @@ namespace Flow.Launcher.Test
[TestCase(@"c:\foo", @"c:\foo\", true)] [TestCase(@"c:\foo", @"c:\foo\", true)]
public void GivenTwoPathsAreTheSame_WhenCheckPathContains_ThenShouldBeExpectedResult(string parentPath, string path, bool expectedResult) public void GivenTwoPathsAreTheSame_WhenCheckPathContains_ThenShouldBeExpectedResult(string parentPath, string path, bool expectedResult)
{ {
Assert.AreEqual(expectedResult, FilesFolders.PathContains(parentPath, path, allowEqual: expectedResult)); ClassicAssert.AreEqual(expectedResult, FilesFolders.PathContains(parentPath, path, allowEqual: expectedResult));
} }
} }
} }

View file

@ -49,7 +49,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Moq" Version="4.18.4" /> <PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="nunit" Version="3.14.0" /> <PackageReference Include="nunit" Version="4.3.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0"> <PackageReference Include="NUnit3TestAdapter" Version="4.6.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using NUnit.Framework.Legacy;
using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.Plugin.SharedModels;
@ -21,6 +22,8 @@ namespace Flow.Launcher.Test
private const string MicrosoftSqlServerManagementStudio = "Microsoft SQL Server Management Studio"; private const string MicrosoftSqlServerManagementStudio = "Microsoft SQL Server Management Studio";
private const string VisualStudioCode = "Visual Studio Code"; private const string VisualStudioCode = "Visual Studio Code";
private readonly IAlphabet alphabet = null;
public List<string> GetSearchStrings() public List<string> GetSearchStrings()
=> new List<string> => new List<string>
{ {
@ -34,7 +37,7 @@ namespace Flow.Launcher.Test
OneOneOneOne OneOneOneOne
}; };
public List<int> GetPrecisionScores() public static List<int> GetPrecisionScores()
{ {
var listToReturn = new List<int>(); var listToReturn = new List<int>();
@ -59,7 +62,7 @@ namespace Flow.Launcher.Test
}; };
var results = new List<Result>(); var results = new List<Result>();
var matcher = new StringMatcher(); var matcher = new StringMatcher(alphabet);
foreach (var str in sources) foreach (var str in sources)
{ {
results.Add(new Result results.Add(new Result
@ -71,20 +74,20 @@ namespace Flow.Launcher.Test
results = results.Where(x => x.Score > 0).OrderByDescending(x => x.Score).ToList(); results = results.Where(x => x.Score > 0).OrderByDescending(x => x.Score).ToList();
Assert.IsTrue(results.Count == 3); ClassicAssert.IsTrue(results.Count == 3);
Assert.IsTrue(results[0].Title == "Inste"); ClassicAssert.IsTrue(results[0].Title == "Inste");
Assert.IsTrue(results[1].Title == "Install Package"); ClassicAssert.IsTrue(results[1].Title == "Install Package");
Assert.IsTrue(results[2].Title == "file open in browser-test"); ClassicAssert.IsTrue(results[2].Title == "file open in browser-test");
} }
[TestCase("Chrome")] [TestCase("Chrome")]
public void WhenNotAllCharactersFoundInSearchString_ThenShouldReturnZeroScore(string searchString) public void WhenNotAllCharactersFoundInSearchString_ThenShouldReturnZeroScore(string searchString)
{ {
var compareString = "Can have rum only in my glass"; var compareString = "Can have rum only in my glass";
var matcher = new StringMatcher(); var matcher = new StringMatcher(alphabet);
var scoreResult = matcher.FuzzyMatch(searchString, compareString).RawScore; var scoreResult = matcher.FuzzyMatch(searchString, compareString).RawScore;
Assert.True(scoreResult == 0); ClassicAssert.True(scoreResult == 0);
} }
[TestCase("chr")] [TestCase("chr")]
@ -97,7 +100,7 @@ namespace Flow.Launcher.Test
string searchTerm) string searchTerm)
{ {
var results = new List<Result>(); var results = new List<Result>();
var matcher = new StringMatcher(); var matcher = new StringMatcher(alphabet);
foreach (var str in GetSearchStrings()) foreach (var str in GetSearchStrings())
{ {
results.Add(new Result results.Add(new Result
@ -125,7 +128,7 @@ namespace Flow.Launcher.Test
Debug.WriteLine("###############################################"); Debug.WriteLine("###############################################");
Debug.WriteLine(""); Debug.WriteLine("");
Assert.IsFalse(filteredResult.Any(x => x.Score < precisionScore)); ClassicAssert.IsFalse(filteredResult.Any(x => x.Score < precisionScore));
} }
} }
@ -147,11 +150,11 @@ namespace Flow.Launcher.Test
string queryString, string compareString, int expectedScore) string queryString, string compareString, int expectedScore)
{ {
// When, Given // When, Given
var matcher = new StringMatcher {UserSettingSearchPrecision = SearchPrecisionScore.Regular}; var matcher = new StringMatcher(alphabet) {UserSettingSearchPrecision = SearchPrecisionScore.Regular};
var rawScore = matcher.FuzzyMatch(queryString, compareString).RawScore; var rawScore = matcher.FuzzyMatch(queryString, compareString).RawScore;
// Should // Should
Assert.AreEqual(expectedScore, rawScore, ClassicAssert.AreEqual(expectedScore, rawScore,
$"Expected score for compare string '{compareString}': {expectedScore}, Actual: {rawScore}"); $"Expected score for compare string '{compareString}': {expectedScore}, Actual: {rawScore}");
} }
@ -181,7 +184,7 @@ namespace Flow.Launcher.Test
bool expectedPrecisionResult) bool expectedPrecisionResult)
{ {
// When // When
var matcher = new StringMatcher {UserSettingSearchPrecision = expectedPrecisionScore}; var matcher = new StringMatcher(alphabet) {UserSettingSearchPrecision = expectedPrecisionScore};
// Given // Given
var matchResult = matcher.FuzzyMatch(queryString, compareString); var matchResult = matcher.FuzzyMatch(queryString, compareString);
@ -190,12 +193,12 @@ namespace Flow.Launcher.Test
Debug.WriteLine("###############################################"); Debug.WriteLine("###############################################");
Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}"); Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}");
Debug.WriteLine( Debug.WriteLine(
$"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int) expectedPrecisionScore})"); $"RAW SCORE: {matchResult.RawScore}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int) expectedPrecisionScore})");
Debug.WriteLine("###############################################"); Debug.WriteLine("###############################################");
Debug.WriteLine(""); Debug.WriteLine("");
// Should // Should
Assert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(), ClassicAssert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(),
$"Query: {queryString}{Environment.NewLine} " + $"Query: {queryString}{Environment.NewLine} " +
$"Compare: {compareString}{Environment.NewLine}" + $"Compare: {compareString}{Environment.NewLine}" +
$"Raw Score: {matchResult.RawScore}{Environment.NewLine}" + $"Raw Score: {matchResult.RawScore}{Environment.NewLine}" +
@ -232,7 +235,7 @@ namespace Flow.Launcher.Test
bool expectedPrecisionResult) bool expectedPrecisionResult)
{ {
// When // When
var matcher = new StringMatcher {UserSettingSearchPrecision = expectedPrecisionScore}; var matcher = new StringMatcher(alphabet) {UserSettingSearchPrecision = expectedPrecisionScore};
// Given // Given
var matchResult = matcher.FuzzyMatch(queryString, compareString); var matchResult = matcher.FuzzyMatch(queryString, compareString);
@ -241,12 +244,12 @@ namespace Flow.Launcher.Test
Debug.WriteLine("###############################################"); Debug.WriteLine("###############################################");
Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}"); Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}");
Debug.WriteLine( Debug.WriteLine(
$"RAW SCORE: {matchResult.RawScore.ToString()}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int) expectedPrecisionScore})"); $"RAW SCORE: {matchResult.RawScore}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int) expectedPrecisionScore})");
Debug.WriteLine("###############################################"); Debug.WriteLine("###############################################");
Debug.WriteLine(""); Debug.WriteLine("");
// Should // Should
Assert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(), ClassicAssert.AreEqual(expectedPrecisionResult, matchResult.IsSearchPrecisionScoreMet(),
$"Query:{queryString}{Environment.NewLine} " + $"Query:{queryString}{Environment.NewLine} " +
$"Compare:{compareString}{Environment.NewLine}" + $"Compare:{compareString}{Environment.NewLine}" +
$"Raw Score: {matchResult.RawScore}{Environment.NewLine}" + $"Raw Score: {matchResult.RawScore}{Environment.NewLine}" +
@ -260,7 +263,7 @@ namespace Flow.Launcher.Test
string queryString, string compareString1, string compareString2) string queryString, string compareString1, string compareString2)
{ {
// When // When
var matcher = new StringMatcher {UserSettingSearchPrecision = SearchPrecisionScore.Regular}; var matcher = new StringMatcher(alphabet) {UserSettingSearchPrecision = SearchPrecisionScore.Regular};
// Given // Given
var compareString1Result = matcher.FuzzyMatch(queryString, compareString1); var compareString1Result = matcher.FuzzyMatch(queryString, compareString1);
@ -277,7 +280,7 @@ namespace Flow.Launcher.Test
Debug.WriteLine(""); Debug.WriteLine("");
// Should // Should
Assert.True(compareString1Result.Score > compareString2Result.Score, ClassicAssert.True(compareString1Result.Score > compareString2Result.Score,
$"Query: \"{queryString}\"{Environment.NewLine} " + $"Query: \"{queryString}\"{Environment.NewLine} " +
$"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}" + $"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}" +
$"Should be greater than{Environment.NewLine}" + $"Should be greater than{Environment.NewLine}" +
@ -293,7 +296,7 @@ namespace Flow.Launcher.Test
string queryString, string compareString1, string compareString2) string queryString, string compareString1, string compareString2)
{ {
// When // When
var matcher = new StringMatcher { UserSettingSearchPrecision = SearchPrecisionScore.Regular }; var matcher = new StringMatcher(alphabet) { UserSettingSearchPrecision = SearchPrecisionScore.Regular };
// Given // Given
var compareString1Result = matcher.FuzzyMatch(queryString, compareString1); var compareString1Result = matcher.FuzzyMatch(queryString, compareString1);
@ -310,7 +313,7 @@ namespace Flow.Launcher.Test
Debug.WriteLine(""); Debug.WriteLine("");
// Should // Should
Assert.True(compareString1Result.Score > compareString2Result.Score, ClassicAssert.True(compareString1Result.Score > compareString2Result.Score,
$"Query: \"{queryString}\"{Environment.NewLine} " + $"Query: \"{queryString}\"{Environment.NewLine} " +
$"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}" + $"CompareString1: \"{compareString1}\", Score: {compareString1Result.Score}{Environment.NewLine}" +
$"Should be greater than{Environment.NewLine}" + $"Should be greater than{Environment.NewLine}" +
@ -323,7 +326,7 @@ namespace Flow.Launcher.Test
string secondName, string secondDescription, string secondExecutableName) string secondName, string secondDescription, string secondExecutableName)
{ {
// Act // Act
var matcher = new StringMatcher(); var matcher = new StringMatcher(alphabet);
var firstNameMatch = matcher.FuzzyMatch(queryString, firstName).RawScore; var firstNameMatch = matcher.FuzzyMatch(queryString, firstName).RawScore;
var firstDescriptionMatch = matcher.FuzzyMatch(queryString, firstDescription).RawScore; var firstDescriptionMatch = matcher.FuzzyMatch(queryString, firstDescription).RawScore;
var firstExecutableNameMatch = matcher.FuzzyMatch(queryString, firstExecutableName).RawScore; var firstExecutableNameMatch = matcher.FuzzyMatch(queryString, firstExecutableName).RawScore;
@ -336,7 +339,7 @@ namespace Flow.Launcher.Test
var secondScore = new[] {secondNameMatch, secondDescriptionMatch, secondExecutableNameMatch}.Max(); var secondScore = new[] {secondNameMatch, secondDescriptionMatch, secondExecutableNameMatch}.Max();
// Assert // Assert
Assert.IsTrue(firstScore > secondScore, ClassicAssert.IsTrue(firstScore > secondScore,
$"Query: \"{queryString}\"{Environment.NewLine} " + $"Query: \"{queryString}\"{Environment.NewLine} " +
$"Name of first: \"{firstName}\", Final Score: {firstScore}{Environment.NewLine}" + $"Name of first: \"{firstName}\", Final Score: {firstScore}{Environment.NewLine}" +
$"Should be greater than{Environment.NewLine}" + $"Should be greater than{Environment.NewLine}" +
@ -358,9 +361,9 @@ namespace Flow.Launcher.Test
public void WhenGivenAnAcronymQuery_ShouldReturnAcronymScore(string queryString, string compareString, public void WhenGivenAnAcronymQuery_ShouldReturnAcronymScore(string queryString, string compareString,
int desiredScore) int desiredScore)
{ {
var matcher = new StringMatcher(); var matcher = new StringMatcher(alphabet);
var score = matcher.FuzzyMatch(queryString, compareString).Score; var score = matcher.FuzzyMatch(queryString, compareString).Score;
Assert.IsTrue(score == desiredScore, ClassicAssert.IsTrue(score == desiredScore,
$@"Query: ""{queryString}"" $@"Query: ""{queryString}""
CompareString: ""{compareString}"" CompareString: ""{compareString}""
Score: {score} Score: {score}

View file

@ -1,4 +1,5 @@
using NUnit.Framework; using NUnit.Framework;
using NUnit.Framework.Legacy;
using System; using System;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Http;
@ -16,16 +17,16 @@ namespace Flow.Launcher.Test
proxy.Enabled = true; proxy.Enabled = true;
proxy.Server = "127.0.0.1"; proxy.Server = "127.0.0.1";
Assert.AreEqual(Http.WebProxy.Address, new Uri($"http://{proxy.Server}:{proxy.Port}")); ClassicAssert.AreEqual(Http.WebProxy.Address, new Uri($"http://{proxy.Server}:{proxy.Port}"));
Assert.IsNull(Http.WebProxy.Credentials); ClassicAssert.IsNull(Http.WebProxy.Credentials);
proxy.UserName = "test"; proxy.UserName = "test";
Assert.NotNull(Http.WebProxy.Credentials); ClassicAssert.NotNull(Http.WebProxy.Credentials);
Assert.AreEqual(Http.WebProxy.Credentials.GetCredential(Http.WebProxy.Address, "Basic").UserName, proxy.UserName); ClassicAssert.AreEqual(Http.WebProxy.Credentials.GetCredential(Http.WebProxy.Address, "Basic").UserName, proxy.UserName);
Assert.AreEqual(Http.WebProxy.Credentials.GetCredential(Http.WebProxy.Address, "Basic").Password, ""); ClassicAssert.AreEqual(Http.WebProxy.Credentials.GetCredential(Http.WebProxy.Address, "Basic").Password, "");
proxy.Password = "test password"; proxy.Password = "test password";
Assert.AreEqual(Http.WebProxy.Credentials.GetCredential(Http.WebProxy.Address, "Basic").Password, proxy.Password); ClassicAssert.AreEqual(Http.WebProxy.Credentials.GetCredential(Http.WebProxy.Address, "Basic").Password, proxy.Password);
} }
} }
} }

View file

@ -1,4 +1,5 @@
using NUnit.Framework; using NUnit.Framework;
using NUnit.Framework.Legacy;
using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using System.Collections.Generic; using System.Collections.Generic;
@ -15,37 +16,37 @@ namespace Flow.Launcher.Test
// Given // Given
var duplicateList = new List<PluginMetadata> var duplicateList = new List<PluginMetadata>
{ {
new PluginMetadata new()
{ {
ID = "CEA0TYUC6D3B4085823D60DC76F28855", ID = "CEA0TYUC6D3B4085823D60DC76F28855",
Version = "1.0.0" Version = "1.0.0"
}, },
new PluginMetadata new()
{ {
ID = "CEA0TYUC6D3B4085823D60DC76F28855", ID = "CEA0TYUC6D3B4085823D60DC76F28855",
Version = "1.0.1" Version = "1.0.1"
}, },
new PluginMetadata new()
{ {
ID = "CEA0TYUC6D3B4085823D60DC76F28855", ID = "CEA0TYUC6D3B4085823D60DC76F28855",
Version = "1.0.2" Version = "1.0.2"
}, },
new PluginMetadata new()
{ {
ID = "CEA0TYUC6D3B4085823D60DC76F28855", ID = "CEA0TYUC6D3B4085823D60DC76F28855",
Version = "1.0.0" Version = "1.0.0"
}, },
new PluginMetadata new()
{ {
ID = "CEA0TYUC6D3B4085823D60DC76F28855", ID = "CEA0TYUC6D3B4085823D60DC76F28855",
Version = "1.0.0" Version = "1.0.0"
}, },
new PluginMetadata new()
{ {
ID = "ABC0TYUC6D3B7855823D60DC76F28855", ID = "ABC0TYUC6D3B7855823D60DC76F28855",
Version = "1.0.0" Version = "1.0.0"
}, },
new PluginMetadata new()
{ {
ID = "ABC0TYUC6D3B7855823D60DC76F28855", ID = "ABC0TYUC6D3B7855823D60DC76F28855",
Version = "1.0.0" Version = "1.0.0"
@ -56,11 +57,11 @@ namespace Flow.Launcher.Test
(var unique, var duplicates) = PluginConfig.GetUniqueLatestPluginMetadata(duplicateList); (var unique, var duplicates) = PluginConfig.GetUniqueLatestPluginMetadata(duplicateList);
// Then // Then
Assert.True(unique.FirstOrDefault().ID == "CEA0TYUC6D3B4085823D60DC76F28855" && unique.FirstOrDefault().Version == "1.0.2"); ClassicAssert.True(unique.FirstOrDefault().ID == "CEA0TYUC6D3B4085823D60DC76F28855" && unique.FirstOrDefault().Version == "1.0.2");
Assert.True(unique.Count() == 1); ClassicAssert.True(unique.Count == 1);
Assert.False(duplicates.Any(x => x.Version == "1.0.2" && x.ID == "CEA0TYUC6D3B4085823D60DC76F28855")); ClassicAssert.False(duplicates.Any(x => x.Version == "1.0.2" && x.ID == "CEA0TYUC6D3B4085823D60DC76F28855"));
Assert.True(duplicates.Count() == 6); ClassicAssert.True(duplicates.Count == 6);
} }
[Test] [Test]
@ -69,12 +70,12 @@ namespace Flow.Launcher.Test
// Given // Given
var duplicateList = new List<PluginMetadata> var duplicateList = new List<PluginMetadata>
{ {
new PluginMetadata new()
{ {
ID = "CEA0TYUC6D3B7855823D60DC76F28855", ID = "CEA0TYUC6D3B7855823D60DC76F28855",
Version = "1.0.0" Version = "1.0.0"
}, },
new PluginMetadata new()
{ {
ID = "CEA0TYUC6D3B7855823D60DC76F28855", ID = "CEA0TYUC6D3B7855823D60DC76F28855",
Version = "1.0.0" Version = "1.0.0"
@ -85,8 +86,8 @@ namespace Flow.Launcher.Test
(var unique, var duplicates) = PluginConfig.GetUniqueLatestPluginMetadata(duplicateList); (var unique, var duplicates) = PluginConfig.GetUniqueLatestPluginMetadata(duplicateList);
// Then // Then
Assert.True(unique.Count() == 0); ClassicAssert.True(unique.Count == 0);
Assert.True(duplicates.Count() == 2); ClassicAssert.True(duplicates.Count == 2);
} }
} }
} }

View file

@ -5,12 +5,10 @@ using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo;
using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex;
using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedCommands;
using NUnit.Framework; using NUnit.Framework;
using NUnit.Framework.Legacy;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
using static Flow.Launcher.Plugin.Explorer.Search.SearchManager; using static Flow.Launcher.Plugin.Explorer.Search.SearchManager;
namespace Flow.Launcher.Test.Plugins namespace Flow.Launcher.Test.Plugins
@ -22,28 +20,6 @@ namespace Flow.Launcher.Test.Plugins
[TestFixture] [TestFixture]
public class ExplorerTest public class ExplorerTest
{ {
#pragma warning disable CS1998 // async method with no await (more readable to leave it async to match the tested signature)
private async Task<List<Result>> MethodWindowsIndexSearchReturnsZeroResultsAsync(Query dummyQuery, string dummyString, CancellationToken dummyToken)
{
return new List<Result>();
}
#pragma warning restore CS1998
private List<Result> MethodDirectoryInfoClassSearchReturnsTwoResults(Query dummyQuery, string dummyString, CancellationToken token)
{
return new List<Result>
{
new Result
{
Title = "Result 1"
},
new Result
{
Title = "Result 2"
}
};
}
private bool PreviousLocationExistsReturnsTrue(string dummyString) => true; private bool PreviousLocationExistsReturnsTrue(string dummyString) => true;
private bool PreviousLocationNotExistReturnsFalse(string dummyString) => false; private bool PreviousLocationNotExistReturnsFalse(string dummyString) => false;
@ -57,7 +33,7 @@ namespace Flow.Launcher.Test.Plugins
var result = QueryConstructor.TopLevelDirectoryConstraint(folderPath); var result = QueryConstructor.TopLevelDirectoryConstraint(folderPath);
// Then // Then
Assert.IsTrue(result == expectedString, ClassicAssert.IsTrue(result == expectedString,
$"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " + $"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " +
$"Actual: {result}{Environment.NewLine}"); $"Actual: {result}{Environment.NewLine}");
} }
@ -74,7 +50,7 @@ namespace Flow.Launcher.Test.Plugins
var queryString = queryConstructor.Directory(folderPath); var queryString = queryConstructor.Directory(folderPath);
// Then // Then
Assert.IsTrue(queryString.Replace(" ", " ") == expectedString.Replace(" ", " "), ClassicAssert.IsTrue(queryString.Replace(" ", " ") == expectedString.Replace(" ", " "),
$"Expected string: {expectedString}{Environment.NewLine} " + $"Expected string: {expectedString}{Environment.NewLine} " +
$"Actual string was: {queryString}{Environment.NewLine}"); $"Actual string was: {queryString}{Environment.NewLine}");
} }
@ -94,7 +70,7 @@ namespace Flow.Launcher.Test.Plugins
var queryString = queryConstructor.Directory(folderPath, userSearchString); var queryString = queryConstructor.Directory(folderPath, userSearchString);
// Then // Then
Assert.AreEqual(expectedString, queryString); ClassicAssert.AreEqual(expectedString, queryString);
} }
[SupportedOSPlatform("windows7.0")] [SupportedOSPlatform("windows7.0")]
@ -105,7 +81,7 @@ namespace Flow.Launcher.Test.Plugins
const string resultString = QueryConstructor.RestrictionsForAllFilesAndFoldersSearch; const string resultString = QueryConstructor.RestrictionsForAllFilesAndFoldersSearch;
// Then // Then
Assert.AreEqual(expectedString, resultString); ClassicAssert.AreEqual(expectedString, resultString);
} }
[SupportedOSPlatform("windows7.0")] [SupportedOSPlatform("windows7.0")]
@ -128,7 +104,7 @@ namespace Flow.Launcher.Test.Plugins
var resultString = queryConstructor.FilesAndFolders(userSearchString); var resultString = queryConstructor.FilesAndFolders(userSearchString);
// Then // Then
Assert.AreEqual(expectedString, resultString); ClassicAssert.AreEqual(expectedString, resultString);
} }
@ -138,13 +114,13 @@ namespace Flow.Launcher.Test.Plugins
string querySearchString, string expectedString) string querySearchString, string expectedString)
{ {
// Given // Given
var queryConstructor = new QueryConstructor(new Settings()); _ = new QueryConstructor(new Settings());
//When //When
var resultString = QueryConstructor.RestrictionsForFileContentSearch(querySearchString); var resultString = QueryConstructor.RestrictionsForFileContentSearch(querySearchString);
// Then // Then
Assert.IsTrue(resultString == expectedString, ClassicAssert.IsTrue(resultString == expectedString,
$"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " + $"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " +
$"Actual string was: {resultString}{Environment.NewLine}"); $"Actual string was: {resultString}{Environment.NewLine}");
} }
@ -162,12 +138,12 @@ namespace Flow.Launcher.Test.Plugins
var resultString = queryConstructor.FileContent(userSearchString); var resultString = queryConstructor.FileContent(userSearchString);
// Then // Then
Assert.IsTrue(resultString == expectedString, ClassicAssert.IsTrue(resultString == expectedString,
$"Expected query string: {expectedString}{Environment.NewLine} " + $"Expected query string: {expectedString}{Environment.NewLine} " +
$"Actual string was: {resultString}{Environment.NewLine}"); $"Actual string was: {resultString}{Environment.NewLine}");
} }
public void GivenQuery_WhenActionKeywordForFileContentSearchExists_ThenFileContentSearchRequiredShouldReturnTrue() public static void GivenQuery_WhenActionKeywordForFileContentSearchExists_ThenFileContentSearchRequiredShouldReturnTrue()
{ {
// Given // Given
var query = new Query var query = new Query
@ -181,7 +157,7 @@ namespace Flow.Launcher.Test.Plugins
var result = searchManager.IsFileContentSearch(query.ActionKeyword); var result = searchManager.IsFileContentSearch(query.ActionKeyword);
// Then // Then
Assert.IsTrue(result, ClassicAssert.IsTrue(result,
$"Expected True for file content search. {Environment.NewLine} " + $"Expected True for file content search. {Environment.NewLine} " +
$"Actual result was: {result}{Environment.NewLine}"); $"Actual result was: {result}{Environment.NewLine}");
} }
@ -206,7 +182,7 @@ namespace Flow.Launcher.Test.Plugins
var result = FilesFolders.IsLocationPathString(querySearchString); var result = FilesFolders.IsLocationPathString(querySearchString);
//Then //Then
Assert.IsTrue(result == expectedResult, ClassicAssert.IsTrue(result == expectedResult,
$"Expected query search string check result is: {expectedResult} {Environment.NewLine} " + $"Expected query search string check result is: {expectedResult} {Environment.NewLine} " +
$"Actual check result is {result} {Environment.NewLine}"); $"Actual check result is {result} {Environment.NewLine}");
@ -233,7 +209,7 @@ namespace Flow.Launcher.Test.Plugins
var previousDirectoryPath = FilesFolders.GetPreviousExistingDirectory(previousLocationExists, path); var previousDirectoryPath = FilesFolders.GetPreviousExistingDirectory(previousLocationExists, path);
//Then //Then
Assert.IsTrue(previousDirectoryPath == expectedString, ClassicAssert.IsTrue(previousDirectoryPath == expectedString,
$"Expected path string: {expectedString} {Environment.NewLine} " + $"Expected path string: {expectedString} {Environment.NewLine} " +
$"Actual path string is {previousDirectoryPath} {Environment.NewLine}"); $"Actual path string is {previousDirectoryPath} {Environment.NewLine}");
} }
@ -246,7 +222,7 @@ namespace Flow.Launcher.Test.Plugins
var returnedPath = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(path); var returnedPath = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(path);
//Then //Then
Assert.IsTrue(returnedPath == expectedString, ClassicAssert.IsTrue(returnedPath == expectedString,
$"Expected path string: {expectedString} {Environment.NewLine} " + $"Expected path string: {expectedString} {Environment.NewLine} " +
$"Actual path string is {returnedPath} {Environment.NewLine}"); $"Actual path string is {returnedPath} {Environment.NewLine}");
} }
@ -260,7 +236,7 @@ namespace Flow.Launcher.Test.Plugins
var resultString = QueryConstructor.RecursiveDirectoryConstraint(path); var resultString = QueryConstructor.RecursiveDirectoryConstraint(path);
// Then // Then
Assert.AreEqual(expectedString, resultString); ClassicAssert.AreEqual(expectedString, resultString);
} }
[SupportedOSPlatform("windows7.0")] [SupportedOSPlatform("windows7.0")]
@ -274,7 +250,7 @@ namespace Flow.Launcher.Test.Plugins
var resultString = DirectoryInfoSearch.ConstructSearchCriteria(path); var resultString = DirectoryInfoSearch.ConstructSearchCriteria(path);
// Then // Then
Assert.AreEqual(expectedString, resultString); ClassicAssert.AreEqual(expectedString, resultString);
} }
[TestCase("c:\\somefolder\\someotherfolder", ResultType.Folder, "irrelevant", false, true, "c:\\somefolder\\someotherfolder\\")] [TestCase("c:\\somefolder\\someotherfolder", ResultType.Folder, "irrelevant", false, true, "c:\\somefolder\\someotherfolder\\")]
@ -305,7 +281,7 @@ namespace Flow.Launcher.Test.Plugins
var result = ResultManager.GetPathWithActionKeyword(path, type, actionKeyword); var result = ResultManager.GetPathWithActionKeyword(path, type, actionKeyword);
// Then // Then
Assert.AreEqual(result, expectedResult); ClassicAssert.AreEqual(result, expectedResult);
} }
[TestCase("c:\\somefolder\\somefile", ResultType.File, "irrelevant", false, true, "e c:\\somefolder\\somefile")] [TestCase("c:\\somefolder\\somefile", ResultType.File, "irrelevant", false, true, "e c:\\somefolder\\somefile")]
@ -334,7 +310,7 @@ namespace Flow.Launcher.Test.Plugins
var result = ResultManager.GetPathWithActionKeyword(path, type, actionKeyword); var result = ResultManager.GetPathWithActionKeyword(path, type, actionKeyword);
// Then // Then
Assert.AreEqual(result, expectedResult); ClassicAssert.AreEqual(result, expectedResult);
} }
[TestCase("somefolder", "c:\\somefolder\\", ResultType.Folder, "q", false, false, "q somefolder")] [TestCase("somefolder", "c:\\somefolder\\", ResultType.Folder, "q", false, false, "q somefolder")]
@ -366,7 +342,7 @@ namespace Flow.Launcher.Test.Plugins
var result = ResultManager.GetAutoCompleteText(title, query, path, resultType); var result = ResultManager.GetAutoCompleteText(title, query, path, resultType);
// Then // Then
Assert.AreEqual(result, expectedResult); ClassicAssert.AreEqual(result, expectedResult);
} }
[TestCase("somefile", "c:\\somefolder\\somefile", ResultType.File, "q", false, false, "q somefile")] [TestCase("somefile", "c:\\somefolder\\somefile", ResultType.File, "q", false, false, "q somefile")]
@ -398,7 +374,7 @@ namespace Flow.Launcher.Test.Plugins
var result = ResultManager.GetAutoCompleteText(title, query, path, resultType); var result = ResultManager.GetAutoCompleteText(title, query, path, resultType);
// Then // Then
Assert.AreEqual(result, expectedResult); ClassicAssert.AreEqual(result, expectedResult);
} }
[TestCase(@"c:\foo", @"c:\foo", true)] [TestCase(@"c:\foo", @"c:\foo", true)]
@ -420,7 +396,7 @@ namespace Flow.Launcher.Test.Plugins
}; };
// When, Then // When, Then
Assert.AreEqual(expectedResult, comparator.Equals(result1, result2)); ClassicAssert.AreEqual(expectedResult, comparator.Equals(result1, result2));
} }
[TestCase(@"c:\foo\", @"c:\foo\")] [TestCase(@"c:\foo\", @"c:\foo\")]
@ -444,7 +420,7 @@ namespace Flow.Launcher.Test.Plugins
var hash2 = comparator.GetHashCode(result2); var hash2 = comparator.GetHashCode(result2);
// When, Then // When, Then
Assert.IsTrue(hash1 == hash2); ClassicAssert.IsTrue(hash1 == hash2);
} }
[TestCase(@"%appdata%", true)] [TestCase(@"%appdata%", true)]
@ -461,7 +437,7 @@ namespace Flow.Launcher.Test.Plugins
var result = EnvironmentVariables.HasEnvironmentVar(path); var result = EnvironmentVariables.HasEnvironmentVar(path);
// Then // Then
Assert.AreEqual(result, expectedResult); ClassicAssert.AreEqual(result, expectedResult);
} }
} }
} }

View file

@ -1,12 +1,11 @@
using NUnit.Framework; using NUnit.Framework;
using NUnit.Framework.Legacy;
using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Text; using System.Text;
using System.Text.Json;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
namespace Flow.Launcher.Test.Plugins namespace Flow.Launcher.Test.Plugins
@ -40,13 +39,13 @@ namespace Flow.Launcher.Test.Plugins
Search = resultText Search = resultText
}, default); }, default);
Assert.IsNotNull(results); ClassicAssert.IsNotNull(results);
foreach (var result in results) foreach (var result in results)
{ {
Assert.IsNotNull(result); ClassicAssert.IsNotNull(result);
Assert.IsNotNull(result.AsyncAction); ClassicAssert.IsNotNull(result.AsyncAction);
Assert.IsNotNull(result.Title); ClassicAssert.IsNotNull(result.Title);
} }
} }
@ -56,12 +55,11 @@ namespace Flow.Launcher.Test.Plugins
new JsonRPCQueryResponseModel(0, new List<JsonRPCResult>()), new JsonRPCQueryResponseModel(0, new List<JsonRPCResult>()),
new JsonRPCQueryResponseModel(0, new List<JsonRPCResult> new JsonRPCQueryResponseModel(0, new List<JsonRPCResult>
{ {
new JsonRPCResult new()
{ {
Title = "Test1", SubTitle = "Test2" Title = "Test1", SubTitle = "Test2"
} }
}) })
}; };
} }
} }

View file

@ -1,7 +1,8 @@
using NUnit.Framework; using NUnit.Framework;
using NUnit.Framework.Legacy;
using Flow.Launcher.Plugin.Url; using Flow.Launcher.Plugin.Url;
namespace Flow.Launcher.Test namespace Flow.Launcher.Test.Plugins
{ {
[TestFixture] [TestFixture]
public class UrlPluginTest public class UrlPluginTest
@ -10,23 +11,23 @@ namespace Flow.Launcher.Test
public void URLMatchTest() public void URLMatchTest()
{ {
var plugin = new Main(); var plugin = new Main();
Assert.IsTrue(plugin.IsURL("http://www.google.com")); ClassicAssert.IsTrue(plugin.IsURL("http://www.google.com"));
Assert.IsTrue(plugin.IsURL("https://www.google.com")); ClassicAssert.IsTrue(plugin.IsURL("https://www.google.com"));
Assert.IsTrue(plugin.IsURL("http://google.com")); ClassicAssert.IsTrue(plugin.IsURL("http://google.com"));
Assert.IsTrue(plugin.IsURL("www.google.com")); ClassicAssert.IsTrue(plugin.IsURL("www.google.com"));
Assert.IsTrue(plugin.IsURL("google.com")); ClassicAssert.IsTrue(plugin.IsURL("google.com"));
Assert.IsTrue(plugin.IsURL("http://localhost")); ClassicAssert.IsTrue(plugin.IsURL("http://localhost"));
Assert.IsTrue(plugin.IsURL("https://localhost")); ClassicAssert.IsTrue(plugin.IsURL("https://localhost"));
Assert.IsTrue(plugin.IsURL("http://localhost:80")); ClassicAssert.IsTrue(plugin.IsURL("http://localhost:80"));
Assert.IsTrue(plugin.IsURL("https://localhost:80")); ClassicAssert.IsTrue(plugin.IsURL("https://localhost:80"));
Assert.IsTrue(plugin.IsURL("http://110.10.10.10")); ClassicAssert.IsTrue(plugin.IsURL("http://110.10.10.10"));
Assert.IsTrue(plugin.IsURL("110.10.10.10")); ClassicAssert.IsTrue(plugin.IsURL("110.10.10.10"));
Assert.IsTrue(plugin.IsURL("ftp://110.10.10.10")); ClassicAssert.IsTrue(plugin.IsURL("ftp://110.10.10.10"));
Assert.IsFalse(plugin.IsURL("wwww")); ClassicAssert.IsFalse(plugin.IsURL("wwww"));
Assert.IsFalse(plugin.IsURL("wwww.c")); ClassicAssert.IsFalse(plugin.IsURL("wwww.c"));
Assert.IsFalse(plugin.IsURL("wwww.c")); ClassicAssert.IsFalse(plugin.IsURL("wwww.c"));
} }
} }
} }

View file

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using NUnit.Framework.Legacy;
using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
@ -17,17 +18,17 @@ namespace Flow.Launcher.Test
Query q = QueryBuilder.Build("> ping google.com -n 20 -6", nonGlobalPlugins); Query q = QueryBuilder.Build("> ping google.com -n 20 -6", nonGlobalPlugins);
Assert.AreEqual("> ping google.com -n 20 -6", q.RawQuery); ClassicAssert.AreEqual("> ping google.com -n 20 -6", q.RawQuery);
Assert.AreEqual("ping google.com -n 20 -6", q.Search, "Search should not start with the ActionKeyword."); ClassicAssert.AreEqual("ping google.com -n 20 -6", q.Search, "Search should not start with the ActionKeyword.");
Assert.AreEqual(">", q.ActionKeyword); ClassicAssert.AreEqual(">", q.ActionKeyword);
Assert.AreEqual(5, q.SearchTerms.Length, "The length of SearchTerms should match."); ClassicAssert.AreEqual(5, q.SearchTerms.Length, "The length of SearchTerms should match.");
Assert.AreEqual("ping", q.FirstSearch); ClassicAssert.AreEqual("ping", q.FirstSearch);
Assert.AreEqual("google.com", q.SecondSearch); ClassicAssert.AreEqual("google.com", q.SecondSearch);
Assert.AreEqual("-n", q.ThirdSearch); ClassicAssert.AreEqual("-n", q.ThirdSearch);
Assert.AreEqual("google.com -n 20 -6", q.SecondToEndSearch, "SecondToEndSearch should be trimmed of multiple whitespace characters"); ClassicAssert.AreEqual("google.com -n 20 -6", q.SecondToEndSearch, "SecondToEndSearch should be trimmed of multiple whitespace characters");
} }
[Test] [Test]
@ -40,11 +41,11 @@ namespace Flow.Launcher.Test
Query q = QueryBuilder.Build("> ping google.com -n 20 -6", nonGlobalPlugins); Query q = QueryBuilder.Build("> ping google.com -n 20 -6", nonGlobalPlugins);
Assert.AreEqual("> ping google.com -n 20 -6", q.Search); ClassicAssert.AreEqual("> ping google.com -n 20 -6", q.Search);
Assert.AreEqual(q.Search, q.RawQuery, "RawQuery should be equal to Search."); ClassicAssert.AreEqual(q.Search, q.RawQuery, "RawQuery should be equal to Search.");
Assert.AreEqual(6, q.SearchTerms.Length, "The length of SearchTerms should match."); ClassicAssert.AreEqual(6, q.SearchTerms.Length, "The length of SearchTerms should match.");
Assert.AreNotEqual(">", q.ActionKeyword, "ActionKeyword should not match that of a disabled plugin."); ClassicAssert.AreNotEqual(">", q.ActionKeyword, "ActionKeyword should not match that of a disabled plugin.");
Assert.AreEqual("ping google.com -n 20 -6", q.SecondToEndSearch, "SecondToEndSearch should be trimmed of multiple whitespace characters"); ClassicAssert.AreEqual("ping google.com -n 20 -6", q.SecondToEndSearch, "SecondToEndSearch should be trimmed of multiple whitespace characters");
} }
[Test] [Test]
@ -52,13 +53,13 @@ namespace Flow.Launcher.Test
{ {
Query q = QueryBuilder.Build("file.txt file2 file3", new Dictionary<string, PluginPair>()); Query q = QueryBuilder.Build("file.txt file2 file3", new Dictionary<string, PluginPair>());
Assert.AreEqual("file.txt file2 file3", q.Search); ClassicAssert.AreEqual("file.txt file2 file3", q.Search);
Assert.AreEqual("", q.ActionKeyword); ClassicAssert.AreEqual("", q.ActionKeyword);
Assert.AreEqual("file.txt", q.FirstSearch); ClassicAssert.AreEqual("file.txt", q.FirstSearch);
Assert.AreEqual("file2", q.SecondSearch); ClassicAssert.AreEqual("file2", q.SecondSearch);
Assert.AreEqual("file3", q.ThirdSearch); ClassicAssert.AreEqual("file3", q.ThirdSearch);
Assert.AreEqual("file2 file3", q.SecondToEndSearch); ClassicAssert.AreEqual("file2 file3", q.SecondToEndSearch);
} }
} }
} }

View file

@ -44,7 +44,7 @@ namespace Flow.Launcher
else else
{ {
string msg = translater.GetTranslation("newActionKeywordsHasBeenAssigned"); string msg = translater.GetTranslation("newActionKeywordsHasBeenAssigned");
MessageBoxEx.Show(msg); App.API.ShowMsgBox(msg);
} }
} }
} }

View file

@ -4,6 +4,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core; using Flow.Launcher.Core;
using Flow.Launcher.Core.Configuration; using Flow.Launcher.Core.Configuration;
using Flow.Launcher.Core.ExternalPlugins.Environments; using Flow.Launcher.Core.ExternalPlugins.Environments;
@ -14,24 +15,52 @@ using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Http;
using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.ViewModel; using Flow.Launcher.ViewModel;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch;
namespace Flow.Launcher namespace Flow.Launcher
{ {
public partial class App : IDisposable, ISingleInstanceApp public partial class App : IDisposable, ISingleInstanceApp
{ {
public static PublicAPIInstance API { get; private set; } public static IPublicAPI API { get; private set; }
private const string Unique = "Flow.Launcher_Unique_Application_Mutex"; private const string Unique = "Flow.Launcher_Unique_Application_Mutex";
private static bool _disposed; private static bool _disposed;
private Settings _settings; private readonly Settings _settings;
private MainViewModel _mainVM;
private SettingWindowViewModel _settingsVM; public App()
private readonly Updater _updater = new Updater(Flow.Launcher.Properties.Settings.Default.GithubRepo); {
private readonly Portable _portable = new Portable(); // Initialize settings
private readonly PinyinAlphabet _alphabet = new PinyinAlphabet(); var storage = new FlowLauncherJsonStorage<Settings>();
private StringMatcher _stringMatcher; _settings = storage.Load();
_settings.SetStorage(storage);
_settings.WMPInstalled = WindowsMediaPlayerHelper.IsWindowsMediaPlayerInstalled();
// Configure the dependency injection container
var host = Host.CreateDefaultBuilder()
.UseContentRoot(AppContext.BaseDirectory)
.ConfigureServices(services => services
.AddSingleton(_ => _settings)
.AddSingleton(sp => new Updater(sp.GetRequiredService<IPublicAPI>(), Launcher.Properties.Settings.Default.GithubRepo))
.AddSingleton<Portable>()
.AddSingleton<SettingWindowViewModel>()
.AddSingleton<IAlphabet, PinyinAlphabet>()
.AddSingleton<StringMatcher>()
.AddSingleton<Internationalization>()
.AddSingleton<IPublicAPI, PublicAPIInstance>()
.AddSingleton<MainViewModel>()
.AddSingleton<Theme>()
).Build();
Ioc.Default.ConfigureServices(host.Services);
// Initialize the public API and Settings first
API = Ioc.Default.GetRequiredService<IPublicAPI>();
_settings.Initialize();
}
[STAThread] [STAThread]
public static void Main() public static void Main()
@ -50,52 +79,40 @@ namespace Flow.Launcher
{ {
await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () => await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () =>
{ {
_portable.PreStartCleanUpAfterPortabilityUpdate(); Ioc.Default.GetRequiredService<Portable>().PreStartCleanUpAfterPortabilityUpdate();
Log.Info( Log.Info("|App.OnStartup|Begin Flow Launcher startup ----------------------------------------------------");
"|App.OnStartup|Begin Flow Launcher startup ----------------------------------------------------");
Log.Info($"|App.OnStartup|Runtime info:{ErrorReporting.RuntimeInfo()}"); Log.Info($"|App.OnStartup|Runtime info:{ErrorReporting.RuntimeInfo()}");
RegisterAppDomainExceptions(); RegisterAppDomainExceptions();
RegisterDispatcherUnhandledException(); RegisterDispatcherUnhandledException();
var imageLoadertask = ImageLoader.InitializeAsync(); var imageLoadertask = ImageLoader.InitializeAsync();
_settingsVM = new SettingWindowViewModel(_updater, _portable);
_settings = _settingsVM.Settings;
_settings.WMPInstalled = WindowsMediaPlayerHelper.IsWindowsMediaPlayerInstalled();
AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate(_settings); AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate(_settings);
_alphabet.Initialize(_settings); // TODO: Clean InternationalizationManager.Instance and InternationalizationManager.Instance.GetTranslation in future
_stringMatcher = new StringMatcher(_alphabet);
StringMatcher.Instance = _stringMatcher;
_stringMatcher.UserSettingSearchPrecision = _settings.QuerySearchPrecision;
InternationalizationManager.Instance.Settings = _settings;
InternationalizationManager.Instance.ChangeLanguage(_settings.Language); InternationalizationManager.Instance.ChangeLanguage(_settings.Language);
PluginManager.LoadPlugins(_settings.PluginSettings); PluginManager.LoadPlugins(_settings.PluginSettings);
_mainVM = new MainViewModel(_settings);
API = new PublicAPIInstance(_settingsVM, _mainVM, _alphabet);
Http.API = API;
Http.Proxy = _settings.Proxy; Http.Proxy = _settings.Proxy;
await PluginManager.InitializePluginsAsync(API); await PluginManager.InitializePluginsAsync();
await imageLoadertask; await imageLoadertask;
var window = new MainWindow(_settings, _mainVM); var mainVM = Ioc.Default.GetRequiredService<MainViewModel>();
var window = new MainWindow(_settings, mainVM);
Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}"); Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}");
Current.MainWindow = window; Current.MainWindow = window;
Current.MainWindow.Title = Constant.FlowLauncher; Current.MainWindow.Title = Constant.FlowLauncher;
HotKeyMapper.Initialize(_mainVM); HotKeyMapper.Initialize();
// main windows needs initialized before theme change because of blur settings // main windows needs initialized before theme change because of blur settings
ThemeManager.Instance.Settings = _settings; // TODO: Clean ThemeManager.Instance in future
ThemeManager.Instance.ChangeTheme(_settings.Theme); ThemeManager.Instance.ChangeTheme(_settings.Theme);
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
@ -141,11 +158,11 @@ namespace Flow.Launcher
{ {
// check update every 5 hours // check update every 5 hours
var timer = new PeriodicTimer(TimeSpan.FromHours(5)); var timer = new PeriodicTimer(TimeSpan.FromHours(5));
await _updater.UpdateAppAsync(API); await Ioc.Default.GetRequiredService<Updater>().UpdateAppAsync();
while (await timer.WaitForNextTickAsync()) while (await timer.WaitForNextTickAsync())
// check updates on startup // check updates on startup
await _updater.UpdateAppAsync(API); await Ioc.Default.GetRequiredService<Updater>().UpdateAppAsync();
} }
}); });
} }
@ -188,7 +205,7 @@ namespace Flow.Launcher
public void OnSecondAppStarted() public void OnSecondAppStarted()
{ {
_mainVM.Show(); Ioc.Default.GetRequiredService<MainViewModel>().Show();
} }
} }
} }

View file

@ -6,21 +6,18 @@ using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Controls; using System.Windows.Controls;
using Flow.Launcher.Core;
namespace Flow.Launcher namespace Flow.Launcher
{ {
public partial class CustomQueryHotkeySetting : Window public partial class CustomQueryHotkeySetting : Window
{ {
private SettingWindow _settingWidow; private readonly Settings _settings;
private bool update; private bool update;
private CustomPluginHotkey updateCustomHotkey; private CustomPluginHotkey updateCustomHotkey;
public Settings Settings { get; }
public CustomQueryHotkeySetting(SettingWindow settingWidow, Settings settings) public CustomQueryHotkeySetting(Settings settings)
{ {
_settingWidow = settingWidow; _settings = settings;
Settings = settings;
InitializeComponent(); InitializeComponent();
} }
@ -33,13 +30,13 @@ namespace Flow.Launcher
{ {
if (!update) if (!update)
{ {
Settings.CustomPluginHotkeys ??= new ObservableCollection<CustomPluginHotkey>(); _settings.CustomPluginHotkeys ??= new ObservableCollection<CustomPluginHotkey>();
var pluginHotkey = new CustomPluginHotkey var pluginHotkey = new CustomPluginHotkey
{ {
Hotkey = HotkeyControl.CurrentHotkey.ToString(), ActionKeyword = tbAction.Text Hotkey = HotkeyControl.CurrentHotkey.ToString(), ActionKeyword = tbAction.Text
}; };
Settings.CustomPluginHotkeys.Add(pluginHotkey); _settings.CustomPluginHotkeys.Add(pluginHotkey);
HotKeyMapper.SetCustomQueryHotkey(pluginHotkey); HotKeyMapper.SetCustomQueryHotkey(pluginHotkey);
} }
@ -59,11 +56,11 @@ namespace Flow.Launcher
public void UpdateItem(CustomPluginHotkey item) public void UpdateItem(CustomPluginHotkey item)
{ {
updateCustomHotkey = Settings.CustomPluginHotkeys.FirstOrDefault(o => updateCustomHotkey = _settings.CustomPluginHotkeys.FirstOrDefault(o =>
o.ActionKeyword == item.ActionKeyword && o.Hotkey == item.Hotkey); o.ActionKeyword == item.ActionKeyword && o.Hotkey == item.Hotkey);
if (updateCustomHotkey == null) if (updateCustomHotkey == null)
{ {
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("invalidPluginHotkey")); App.API.ShowMsgBox(InternationalizationManager.Instance.GetTranslation("invalidPluginHotkey"));
Close(); Close();
return; return;
} }
@ -77,8 +74,7 @@ namespace Flow.Launcher
private void BtnTestActionKeyword_OnClick(object sender, RoutedEventArgs e) private void BtnTestActionKeyword_OnClick(object sender, RoutedEventArgs e)
{ {
App.API.ChangeQuery(tbAction.Text); App.API.ChangeQuery(tbAction.Text);
Application.Current.MainWindow.Show(); App.API.ShowMainWindow();
Application.Current.MainWindow.Opacity = 1;
Application.Current.MainWindow.Focus(); Application.Current.MainWindow.Focus();
} }

View file

@ -43,13 +43,13 @@ namespace Flow.Launcher
{ {
if (String.IsNullOrEmpty(Key) || String.IsNullOrEmpty(Value)) if (String.IsNullOrEmpty(Key) || String.IsNullOrEmpty(Value))
{ {
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("emptyShortcut")); App.API.ShowMsgBox(InternationalizationManager.Instance.GetTranslation("emptyShortcut"));
return; return;
} }
// Check if key is modified or adding a new one // Check if key is modified or adding a new one
if (((update && originalKey != Key) || !update) && _hotkeyVm.DoesShortcutExist(Key)) if (((update && originalKey != Key) || !update) && _hotkeyVm.DoesShortcutExist(Key))
{ {
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("duplicateShortcut")); App.API.ShowMsgBox(InternationalizationManager.Instance.GetTranslation("duplicateShortcut"));
return; return;
} }
DialogResult = !update || originalKey != Key || originalValue != Value; DialogResult = !update || originalKey != Key || originalValue != Value;
@ -65,8 +65,7 @@ namespace Flow.Launcher
private void BtnTestShortcut_OnClick(object sender, RoutedEventArgs e) private void BtnTestShortcut_OnClick(object sender, RoutedEventArgs e)
{ {
App.API.ChangeQuery(tbExpand.Text); App.API.ChangeQuery(tbExpand.Text);
Application.Current.MainWindow.Show(); App.API.ShowMainWindow();
Application.Current.MainWindow.Opacity = 1;
Application.Current.MainWindow.Focus(); Application.Current.MainWindow.Focus();
} }
} }

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
@ -83,12 +83,15 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ChefKeys" Version="0.1.2" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="Fody" Version="6.5.4"> <PackageReference Include="Fody" Version="6.5.4">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="InputSimulator" Version="1.0.4" /> <PackageReference Include="InputSimulator" Version="1.0.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" /> <PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106"> <PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>

View file

@ -5,7 +5,9 @@ using NHotkey;
using NHotkey.Wpf; using NHotkey.Wpf;
using Flow.Launcher.Core.Resource; using Flow.Launcher.Core.Resource;
using Flow.Launcher.ViewModel; using Flow.Launcher.ViewModel;
using Flow.Launcher.Core; using ChefKeys;
using Flow.Launcher.Infrastructure.Logger;
using CommunityToolkit.Mvvm.DependencyInjection;
namespace Flow.Launcher.Helper; namespace Flow.Launcher.Helper;
@ -14,10 +16,10 @@ internal static class HotKeyMapper
private static Settings _settings; private static Settings _settings;
private static MainViewModel _mainViewModel; private static MainViewModel _mainViewModel;
internal static void Initialize(MainViewModel mainVM) internal static void Initialize()
{ {
_mainViewModel = mainVM; _mainViewModel = Ioc.Default.GetRequiredService<MainViewModel>();
_settings = _mainViewModel.Settings; _settings = Ioc.Default.GetService<Settings>();
SetHotkey(_settings.Hotkey, OnToggleHotkey); SetHotkey(_settings.Hotkey, OnToggleHotkey);
LoadCustomPluginHotkey(); LoadCustomPluginHotkey();
@ -29,33 +31,92 @@ internal static class HotKeyMapper
_mainViewModel.ToggleFlowLauncher(); _mainViewModel.ToggleFlowLauncher();
} }
internal static void OnToggleHotkeyWithChefKeys()
{
if (!_mainViewModel.ShouldIgnoreHotkeys())
_mainViewModel.ToggleFlowLauncher();
}
private static void SetHotkey(string hotkeyStr, EventHandler<HotkeyEventArgs> action) private static void SetHotkey(string hotkeyStr, EventHandler<HotkeyEventArgs> action)
{ {
var hotkey = new HotkeyModel(hotkeyStr); var hotkey = new HotkeyModel(hotkeyStr);
SetHotkey(hotkey, action); SetHotkey(hotkey, action);
} }
private static void SetWithChefKeys(string hotkeyStr)
{
try
{
ChefKeysManager.RegisterHotkey(hotkeyStr, hotkeyStr, OnToggleHotkeyWithChefKeys);
ChefKeysManager.Start();
}
catch (Exception e)
{
Log.Error(
string.Format("|HotkeyMapper.SetWithChefKeys|Error registering hotkey: {0} \nStackTrace:{1}",
e.Message,
e.StackTrace));
string errorMsg = string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr);
string errorMsgTitle = InternationalizationManager.Instance.GetTranslation("MessageBoxTitle");
MessageBoxEx.Show(errorMsg, errorMsgTitle);
}
}
internal static void SetHotkey(HotkeyModel hotkey, EventHandler<HotkeyEventArgs> action) internal static void SetHotkey(HotkeyModel hotkey, EventHandler<HotkeyEventArgs> action)
{ {
string hotkeyStr = hotkey.ToString(); string hotkeyStr = hotkey.ToString();
try try
{ {
if (hotkeyStr == "LWin" || hotkeyStr == "RWin")
{
SetWithChefKeys(hotkeyStr);
return;
}
HotkeyManager.Current.AddOrReplace(hotkeyStr, hotkey.CharKey, hotkey.ModifierKeys, action); HotkeyManager.Current.AddOrReplace(hotkeyStr, hotkey.CharKey, hotkey.ModifierKeys, action);
} }
catch (Exception) catch (Exception e)
{ {
Log.Error(
string.Format("|HotkeyMapper.SetHotkey|Error registering hotkey {2}: {0} \nStackTrace:{1}",
e.Message,
e.StackTrace,
hotkeyStr));
string errorMsg = string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr); string errorMsg = string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr);
string errorMsgTitle = InternationalizationManager.Instance.GetTranslation("MessageBoxTitle"); string errorMsgTitle = InternationalizationManager.Instance.GetTranslation("MessageBoxTitle");
MessageBoxEx.Show(errorMsg, errorMsgTitle); App.API.ShowMsgBox(errorMsg, errorMsgTitle);
} }
} }
internal static void RemoveHotkey(string hotkeyStr) internal static void RemoveHotkey(string hotkeyStr)
{ {
if (!string.IsNullOrEmpty(hotkeyStr)) try
{ {
HotkeyManager.Current.Remove(hotkeyStr); if (hotkeyStr == "LWin" || hotkeyStr == "RWin")
{
RemoveWithChefKeys(hotkeyStr);
return;
}
if (!string.IsNullOrEmpty(hotkeyStr))
HotkeyManager.Current.Remove(hotkeyStr);
} }
catch (Exception e)
{
Log.Error(
string.Format("|HotkeyMapper.RemoveHotkey|Error removing hotkey: {0} \nStackTrace:{1}",
e.Message,
e.StackTrace));
string errorMsg = string.Format(InternationalizationManager.Instance.GetTranslation("unregisterHotkeyFailed"), hotkeyStr);
string errorMsgTitle = InternationalizationManager.Instance.GetTranslation("MessageBoxTitle");
MessageBoxEx.Show(errorMsg, errorMsgTitle);
}
}
private static void RemoveWithChefKeys(string hotkeyStr)
{
ChefKeysManager.UnregisterHotkey(hotkeyStr);
ChefKeysManager.Stop();
} }
internal static void LoadCustomPluginHotkey() internal static void LoadCustomPluginHotkey()

View file

@ -154,7 +154,16 @@ namespace Flow.Launcher
{ {
if (triggerValidate) if (triggerValidate)
{ {
bool hotkeyAvailable = CheckHotkeyAvailability(keyModel, ValidateKeyGesture); bool hotkeyAvailable = false;
// TODO: This is a temporary way to enforce changing only the open flow hotkey to Win, and will be removed by PR #3157
if (keyModel.ToString() == "LWin" || keyModel.ToString() == "RWin")
{
hotkeyAvailable = true;
}
else
{
hotkeyAvailable = CheckHotkeyAvailability(keyModel, ValidateKeyGesture);
}
if (!hotkeyAvailable) if (!hotkeyAvailable)
{ {

View file

@ -3,6 +3,7 @@ using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using ChefKeys;
using Flow.Launcher.Core.Resource; using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper; using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.Hotkey;
@ -33,6 +34,8 @@ public partial class HotkeyControlDialog : ContentDialog
public string ResultValue { get; private set; } = string.Empty; public string ResultValue { get; private set; } = string.Empty;
public static string EmptyHotkey => InternationalizationManager.Instance.GetTranslation("none"); public static string EmptyHotkey => InternationalizationManager.Instance.GetTranslation("none");
private static bool isOpenFlowHotkey;
public HotkeyControlDialog(string hotkey, string defaultHotkey, IHotkeySettings hotkeySettings, string windowTitle = "") public HotkeyControlDialog(string hotkey, string defaultHotkey, IHotkeySettings hotkeySettings, string windowTitle = "")
{ {
WindowTitle = windowTitle switch WindowTitle = windowTitle switch
@ -46,6 +49,14 @@ public partial class HotkeyControlDialog : ContentDialog
SetKeysToDisplay(CurrentHotkey); SetKeysToDisplay(CurrentHotkey);
InitializeComponent(); InitializeComponent();
// TODO: This is a temporary way to enforce changing only the open flow hotkey to Win, and will be removed by PR #3157
isOpenFlowHotkey = _hotkeySettings.RegisteredHotkeys
.Any(x => x.DescriptionResourceKey == "flowlauncherHotkey"
&& x.Hotkey.ToString() == hotkey);
ChefKeysManager.StartMenuEnableBlocking = true;
ChefKeysManager.Start();
} }
private void Reset(object sender, RoutedEventArgs routedEventArgs) private void Reset(object sender, RoutedEventArgs routedEventArgs)
@ -61,12 +72,18 @@ public partial class HotkeyControlDialog : ContentDialog
private void Cancel(object sender, RoutedEventArgs routedEventArgs) private void Cancel(object sender, RoutedEventArgs routedEventArgs)
{ {
ChefKeysManager.StartMenuEnableBlocking = false;
ChefKeysManager.Stop();
ResultType = EResultType.Cancel; ResultType = EResultType.Cancel;
Hide(); Hide();
} }
private void Save(object sender, RoutedEventArgs routedEventArgs) private void Save(object sender, RoutedEventArgs routedEventArgs)
{ {
ChefKeysManager.StartMenuEnableBlocking = false;
ChefKeysManager.Stop();
if (KeysToDisplay.Count == 1 && KeysToDisplay[0] == EmptyHotkey) if (KeysToDisplay.Count == 1 && KeysToDisplay[0] == EmptyHotkey)
{ {
ResultType = EResultType.Delete; ResultType = EResultType.Delete;
@ -85,6 +102,9 @@ public partial class HotkeyControlDialog : ContentDialog
//when alt is pressed, the real key should be e.SystemKey //when alt is pressed, the real key should be e.SystemKey
Key key = e.Key == Key.System ? e.SystemKey : e.Key; Key key = e.Key == Key.System ? e.SystemKey : e.Key;
if (ChefKeysManager.StartMenuBlocked && key.ToString() == ChefKeysManager.StartMenuSimulatedKey)
return;
SpecialKeyState specialKeyState = GlobalHotkey.CheckModifiers(); SpecialKeyState specialKeyState = GlobalHotkey.CheckModifiers();
var hotkeyModel = new HotkeyModel( var hotkeyModel = new HotkeyModel(
@ -168,8 +188,13 @@ public partial class HotkeyControlDialog : ContentDialog
} }
} }
private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) => private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture)
hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey); {
if (isOpenFlowHotkey && (hotkey.ToString() == "LWin" || hotkey.ToString() == "RWin"))
return true;
return hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey);
}
private void Overwrite(object sender, RoutedEventArgs e) private void Overwrite(object sender, RoutedEventArgs e)
{ {

View file

@ -15,6 +15,7 @@
<!-- MainWindow --> <!-- MainWindow -->
<system:String x:Key="registerHotkeyFailed">Failed to register hotkey "{0}". The hotkey may be in use by another program. Change to a different hotkey, or exit another program.</system:String> <system:String x:Key="registerHotkeyFailed">Failed to register hotkey "{0}". The hotkey may be in use by another program. Change to a different hotkey, or exit another program.</system:String>
<system:String x:Key="unregisterHotkeyFailed">Failed to unregister hotkey "{0}". Please try again or see log for details</system:String>
<system:String x:Key="MessageBoxTitle">Flow Launcher</system:String> <system:String x:Key="MessageBoxTitle">Flow Launcher</system:String>
<system:String x:Key="couldnotStartCmd">Could not start {0}</system:String> <system:String x:Key="couldnotStartCmd">Could not start {0}</system:String>
<system:String x:Key="invalidFlowLauncherPluginFileFormat">Invalid Flow Launcher plugin file format</system:String> <system:String x:Key="invalidFlowLauncherPluginFileFormat">Invalid Flow Launcher plugin file format</system:String>
@ -128,7 +129,8 @@
<system:String x:Key="plugin_query_version">Version</system:String> <system:String x:Key="plugin_query_version">Version</system:String>
<system:String x:Key="plugin_query_web">Website</system:String> <system:String x:Key="plugin_query_web">Website</system:String>
<system:String x:Key="plugin_uninstall">Uninstall</system:String> <system:String x:Key="plugin_uninstall">Uninstall</system:String>
<system:String x:Key="failedToRemovePluginSettingsTitle">Fail to remove plugin settings</system:String>
<system:String x:Key="failedToRemovePluginSettingsMessage">Plugins: {0} - Fail to remove plugin settings files, please remove them manually</system:String>
<!-- Setting Plugin Store --> <!-- Setting Plugin Store -->
<system:String x:Key="pluginStore">Plugin Store</system:String> <system:String x:Key="pluginStore">Plugin Store</system:String>
@ -145,8 +147,6 @@
<system:String x:Key="LabelNewToolTip">This plugin has been updated within the last 7 days</system:String> <system:String x:Key="LabelNewToolTip">This plugin has been updated within the last 7 days</system:String>
<system:String x:Key="LabelUpdateToolTip">New Update is Available</system:String> <system:String x:Key="LabelUpdateToolTip">New Update is Available</system:String>
<!-- Setting Theme --> <!-- Setting Theme -->
<system:String x:Key="theme">Theme</system:String> <system:String x:Key="theme">Theme</system:String>
<system:String x:Key="appearance">Appearance</system:String> <system:String x:Key="appearance">Appearance</system:String>
@ -196,7 +196,6 @@
<system:String x:Key="TypeIsDarkToolTip">This theme supports two(light/dark) modes.</system:String> <system:String x:Key="TypeIsDarkToolTip">This theme supports two(light/dark) modes.</system:String>
<system:String x:Key="TypeHasBlurToolTip">This theme supports Blur Transparent Background.</system:String> <system:String x:Key="TypeHasBlurToolTip">This theme supports Blur Transparent Background.</system:String>
<!-- Setting Hotkey --> <!-- Setting Hotkey -->
<system:String x:Key="hotkey">Hotkey</system:String> <system:String x:Key="hotkey">Hotkey</system:String>
<system:String x:Key="hotkeys">Hotkeys</system:String> <system:String x:Key="hotkeys">Hotkeys</system:String>

View file

@ -1,9 +1,9 @@
<Window <Window
x:Class="Flow.Launcher.Core.MessageBoxEx" x:Class="Flow.Launcher.MessageBoxEx"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Flow.Launcher.Core" xmlns:local="clr-namespace:Flow.Launcher"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="MessageBoxWindow" x:Name="MessageBoxWindow"
Width="420" Width="420"
@ -12,6 +12,7 @@
Foreground="{DynamicResource PopupTextColor}" Foreground="{DynamicResource PopupTextColor}"
ResizeMode="NoResize" ResizeMode="NoResize"
SizeToContent="Height" SizeToContent="Height"
Topmost="True"
WindowStartupLocation="CenterScreen" WindowStartupLocation="CenterScreen"
mc:Ignorable="d"> mc:Ignorable="d">
<WindowChrome.WindowChrome> <WindowChrome.WindowChrome>

View file

@ -7,7 +7,7 @@ using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Infrastructure.Image;
using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Logger;
namespace Flow.Launcher.Core namespace Flow.Launcher
{ {
public partial class MessageBoxEx : Window public partial class MessageBoxEx : Window
{ {

View file

@ -24,7 +24,7 @@ namespace Flow.Launcher
this.pluginViewModel = pluginViewModel; this.pluginViewModel = pluginViewModel;
if (plugin == null) if (plugin == null)
{ {
MessageBoxEx.Show(translater.GetTranslation("cannotFindSpecifiedPlugin")); App.API.ShowMsgBox(translater.GetTranslation("cannotFindSpecifiedPlugin"));
Close(); Close();
} }
} }
@ -44,7 +44,7 @@ namespace Flow.Launcher
else else
{ {
string msg = translater.GetTranslation("invalidPriority"); string msg = translater.GetTranslation("invalidPriority");
MessageBoxEx.Show(msg); App.API.ShowMsgBox(msg);
} }
} }

View file

@ -25,23 +25,23 @@ using Flow.Launcher.Infrastructure.Storage;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics; using System.Diagnostics;
using System.Collections.Specialized; using System.Collections.Specialized;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core; using Flow.Launcher.Core;
using Flow.Launcher.Infrastructure.UserSettings;
namespace Flow.Launcher namespace Flow.Launcher
{ {
public class PublicAPIInstance : IPublicAPI public class PublicAPIInstance : IPublicAPI
{ {
private readonly SettingWindowViewModel _settingsVM; private readonly Settings _settings;
private readonly MainViewModel _mainVM; private readonly MainViewModel _mainVM;
private readonly PinyinAlphabet _alphabet;
#region Constructor #region Constructor
public PublicAPIInstance(SettingWindowViewModel settingsVM, MainViewModel mainVM, PinyinAlphabet alphabet) public PublicAPIInstance(Settings settings, MainViewModel mainVM)
{ {
_settingsVM = settingsVM; _settings = settings;
_mainVM = mainVM; _mainVM = mainVM;
_alphabet = alphabet;
GlobalHotkey.hookedKeyboardCallback = KListener_hookedKeyboardCallback; GlobalHotkey.hookedKeyboardCallback = KListener_hookedKeyboardCallback;
WebRequest.RegisterPrefix("data", new DataWebRequestFactory()); WebRequest.RegisterPrefix("data", new DataWebRequestFactory());
} }
@ -78,14 +78,15 @@ namespace Flow.Launcher
public event VisibilityChangedEventHandler VisibilityChanged { add => _mainVM.VisibilityChanged += value; remove => _mainVM.VisibilityChanged -= value; } public event VisibilityChangedEventHandler VisibilityChanged { add => _mainVM.VisibilityChanged += value; remove => _mainVM.VisibilityChanged -= value; }
public void CheckForNewUpdate() => _settingsVM.UpdateApp(); // Must use Ioc.Default.GetRequiredService<Updater>() to avoid circular dependency
public void CheckForNewUpdate() => _ = Ioc.Default.GetRequiredService<Updater>().UpdateAppAsync(false);
public void SaveAppAllSettings() public void SaveAppAllSettings()
{ {
PluginManager.Save(); PluginManager.Save();
_mainVM.Save(); _mainVM.Save();
_settingsVM.Save(); _settings.Save();
ImageLoader.Save(); _ = ImageLoader.Save();
} }
public Task ReloadAllPluginData() => PluginManager.ReloadDataAsync(); public Task ReloadAllPluginData() => PluginManager.ReloadDataAsync();
@ -105,7 +106,7 @@ namespace Flow.Launcher
{ {
Application.Current.Dispatcher.Invoke(() => Application.Current.Dispatcher.Invoke(() =>
{ {
SettingWindow sw = SingletonWindowOpener.Open<SettingWindow>(this, _settingsVM); SettingWindow sw = SingletonWindowOpener.Open<SettingWindow>();
}); });
} }
@ -189,6 +190,23 @@ namespace Flow.Launcher
private readonly ConcurrentDictionary<Type, object> _pluginJsonStorages = new(); private readonly ConcurrentDictionary<Type, object> _pluginJsonStorages = new();
public object RemovePluginSettings(string assemblyName)
{
foreach (var keyValuePair in _pluginJsonStorages)
{
var key = keyValuePair.Key;
var value = keyValuePair.Value;
var name = value.GetType().GetField("AssemblyName")?.GetValue(value)?.ToString();
if (name == assemblyName)
{
_pluginJsonStorages.Remove(key, out var pluginJsonStorage);
return pluginJsonStorage;
}
}
return null;
}
/// <summary> /// <summary>
/// Save plugin settings. /// Save plugin settings.
/// </summary> /// </summary>
@ -230,7 +248,7 @@ namespace Flow.Launcher
public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null) public void OpenDirectory(string DirectoryPath, string FileNameOrFilePath = null)
{ {
using var explorer = new Process(); using var explorer = new Process();
var explorerInfo = _settingsVM.Settings.CustomExplorer; var explorerInfo = _settings.CustomExplorer;
explorer.StartInfo = new ProcessStartInfo explorer.StartInfo = new ProcessStartInfo
{ {
@ -251,7 +269,7 @@ namespace Flow.Launcher
{ {
if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
{ {
var browserInfo = _settingsVM.Settings.CustomBrowser; var browserInfo = _settings.CustomBrowser;
var path = browserInfo.Path == "*" ? "" : browserInfo.Path; var path = browserInfo.Path == "*" ? "" : browserInfo.Path;

View file

@ -62,7 +62,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
[RelayCommand] [RelayCommand]
private void AskClearLogFolderConfirmation() private void AskClearLogFolderConfirmation()
{ {
var confirmResult = MessageBoxEx.Show( var confirmResult = App.API.ShowMsgBox(
InternationalizationManager.Instance.GetTranslation("clearlogfolderMessage"), InternationalizationManager.Instance.GetTranslation("clearlogfolderMessage"),
InternationalizationManager.Instance.GetTranslation("clearlogfolder"), InternationalizationManager.Instance.GetTranslation("clearlogfolder"),
MessageBoxButton.YesNo MessageBoxButton.YesNo
@ -96,7 +96,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel
} }
[RelayCommand] [RelayCommand]
private Task UpdateApp() => _updater.UpdateAppAsync(App.API, false); private Task UpdateApp() => _updater.UpdateAppAsync(false);
private void ClearLogFolder() private void ClearLogFolder()
{ {

View file

@ -160,7 +160,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
private void UpdateApp() private void UpdateApp()
{ {
_ = _updater.UpdateAppAsync(App.API, false); _ = _updater.UpdateAppAsync(false);
} }
public bool AutoUpdates public bool AutoUpdates

View file

@ -7,7 +7,6 @@ using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
using Flow.Launcher.Core;
namespace Flow.Launcher.SettingPages.ViewModels; namespace Flow.Launcher.SettingPages.ViewModels;
@ -42,11 +41,11 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
var item = SelectedCustomPluginHotkey; var item = SelectedCustomPluginHotkey;
if (item is null) if (item is null)
{ {
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem")); App.API.ShowMsgBox(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
return; return;
} }
var result = MessageBoxEx.Show( var result = App.API.ShowMsgBox(
string.Format( string.Format(
InternationalizationManager.Instance.GetTranslation("deleteCustomHotkeyWarning"), item.Hotkey InternationalizationManager.Instance.GetTranslation("deleteCustomHotkeyWarning"), item.Hotkey
), ),
@ -67,11 +66,11 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
var item = SelectedCustomPluginHotkey; var item = SelectedCustomPluginHotkey;
if (item is null) if (item is null)
{ {
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem")); App.API.ShowMsgBox(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
return; return;
} }
var window = new CustomQueryHotkeySetting(null, Settings); var window = new CustomQueryHotkeySetting(Settings);
window.UpdateItem(item); window.UpdateItem(item);
window.ShowDialog(); window.ShowDialog();
} }
@ -79,7 +78,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
[RelayCommand] [RelayCommand]
private void CustomHotkeyAdd() private void CustomHotkeyAdd()
{ {
new CustomQueryHotkeySetting(null, Settings).ShowDialog(); new CustomQueryHotkeySetting(Settings).ShowDialog();
} }
[RelayCommand] [RelayCommand]
@ -88,11 +87,11 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
var item = SelectedCustomShortcut; var item = SelectedCustomShortcut;
if (item is null) if (item is null)
{ {
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem")); App.API.ShowMsgBox(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
return; return;
} }
var result = MessageBoxEx.Show( var result = App.API.ShowMsgBox(
string.Format( string.Format(
InternationalizationManager.Instance.GetTranslation("deleteCustomShortcutWarning"), item.Key, item.Value InternationalizationManager.Instance.GetTranslation("deleteCustomShortcutWarning"), item.Key, item.Value
), ),
@ -112,7 +111,7 @@ public partial class SettingsPaneHotkeyViewModel : BaseModel
var item = SelectedCustomShortcut; var item = SelectedCustomShortcut;
if (item is null) if (item is null)
{ {
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem")); App.API.ShowMsgBox(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem"));
return; return;
} }

View file

@ -22,7 +22,7 @@ public partial class SettingsPaneProxyViewModel : BaseModel
private void OnTestProxyClicked() private void OnTestProxyClicked()
{ {
var message = TestProxy(); var message = TestProxy();
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation(message)); App.API.ShowMsgBox(InternationalizationManager.Instance.GetTranslation(message));
} }
private string TestProxy() private string TestProxy()

View file

@ -49,7 +49,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel
{ {
if (ThemeManager.Instance.BlurEnabled && value) if (ThemeManager.Instance.BlurEnabled && value)
{ {
MessageBoxEx.Show(InternationalizationManager.Instance.GetTranslation("shadowEffectNotAllowed")); App.API.ShowMsgBox(InternationalizationManager.Instance.GetTranslation("shadowEffectNotAllowed"));
return; return;
} }

View file

@ -3,6 +3,7 @@ using System.Windows;
using System.Windows.Forms; using System.Windows.Forms;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Interop; using System.Windows.Interop;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core; using Flow.Launcher.Core;
using Flow.Launcher.Core.Configuration; using Flow.Launcher.Core.Configuration;
using Flow.Launcher.Helper; using Flow.Launcher.Helper;
@ -17,16 +18,21 @@ namespace Flow.Launcher;
public partial class SettingWindow public partial class SettingWindow
{ {
private readonly Updater _updater;
private readonly IPortable _portable;
private readonly IPublicAPI _api; private readonly IPublicAPI _api;
private readonly Settings _settings; private readonly Settings _settings;
private readonly SettingWindowViewModel _viewModel; private readonly SettingWindowViewModel _viewModel;
public SettingWindow(IPublicAPI api, SettingWindowViewModel viewModel) public SettingWindow()
{ {
_settings = viewModel.Settings; var viewModel = Ioc.Default.GetRequiredService<SettingWindowViewModel>();
_settings = Ioc.Default.GetRequiredService<Settings>();
DataContext = viewModel; DataContext = viewModel;
_viewModel = viewModel; _viewModel = viewModel;
_api = api; _updater = Ioc.Default.GetRequiredService<Updater>();
_portable = Ioc.Default.GetRequiredService<Portable>();
_api = Ioc.Default.GetRequiredService<IPublicAPI>();
InitializePosition(); InitializePosition();
InitializeComponent(); InitializeComponent();
} }
@ -125,7 +131,7 @@ public partial class SettingWindow
WindowState = _settings.SettingWindowState; WindowState = _settings.SettingWindowState;
} }
private bool IsPositionValid(double top, double left) private static bool IsPositionValid(double top, double left)
{ {
foreach (var screen in Screen.AllScreens) foreach (var screen in Screen.AllScreens)
{ {
@ -145,7 +151,7 @@ public partial class SettingWindow
var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0);
var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0); var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0);
var left = (dip2.X - this.ActualWidth) / 2 + dip1.X; var left = (dip2.X - ActualWidth) / 2 + dip1.X;
return left; return left;
} }
@ -154,13 +160,13 @@ public partial class SettingWindow
var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position);
var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y); var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y);
var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height); var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height);
var top = (dip2.Y - this.ActualHeight) / 2 + dip1.Y - 20; var top = (dip2.Y - ActualHeight) / 2 + dip1.Y - 20;
return top; return top;
} }
private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
{ {
var paneData = new PaneData(_settings, _viewModel.Updater, _viewModel.Portable); var paneData = new PaneData(_settings, _updater, _portable);
if (args.IsSettingsSelected) if (args.IsSettingsSelected)
{ {
ContentFrame.Navigate(typeof(SettingsPaneGeneral), paneData); ContentFrame.Navigate(typeof(SettingsPaneGeneral), paneData);

View file

@ -25,6 +25,7 @@ using System.Windows.Input;
using System.ComponentModel; using System.ComponentModel;
using Flow.Launcher.Infrastructure.Image; using Flow.Launcher.Infrastructure.Image;
using System.Windows.Media; using System.Windows.Media;
using CommunityToolkit.Mvvm.DependencyInjection;
namespace Flow.Launcher.ViewModel namespace Flow.Launcher.ViewModel
{ {
@ -56,13 +57,13 @@ namespace Flow.Launcher.ViewModel
#region Constructor #region Constructor
public MainViewModel(Settings settings) public MainViewModel()
{ {
_queryTextBeforeLeaveResults = ""; _queryTextBeforeLeaveResults = "";
_queryText = ""; _queryText = "";
_lastQuery = new Query(); _lastQuery = new Query();
Settings = settings; Settings = Ioc.Default.GetRequiredService<Settings>();
Settings.PropertyChanged += (_, args) => Settings.PropertyChanged += (_, args) =>
{ {
switch (args.PropertyName) switch (args.PropertyName)
@ -231,8 +232,8 @@ namespace Flow.Launcher.ViewModel
var token = e.Token == default ? _updateToken : e.Token; var token = e.Token == default ? _updateToken : e.Token;
// make a copy of results to avoid plugin change the result when updating view model // make a clone to avoid possible issue that plugin will also change the list and items when updating view model
var resultsCopy = e.Results.ToList(); var resultsCopy = DeepCloneResults(e.Results, token);
PluginManager.UpdatePluginMetadata(resultsCopy, pair.Metadata, e.Query); PluginManager.UpdatePluginMetadata(resultsCopy, pair.Metadata, e.Query);
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, pair.Metadata, e.Query, if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, pair.Metadata, e.Query,
@ -279,10 +280,8 @@ namespace Flow.Launcher.ViewModel
public void ReQuery(bool reselect) public void ReQuery(bool reselect)
{ {
if (SelectedIsFromQueryResults()) BackToQueryResults();
{ QueryResults(isReQuery: true, reSelect: reselect);
QueryResults(isReQuery: true, reSelect: reselect);
}
} }
[RelayCommand] [RelayCommand]
@ -414,6 +413,22 @@ namespace Flow.Launcher.ViewModel
} }
} }
private static IReadOnlyList<Result> DeepCloneResults(IReadOnlyList<Result> results, CancellationToken token = default)
{
var resultsCopy = new List<Result>();
foreach (var result in results.ToList())
{
if (token.IsCancellationRequested)
{
break;
}
var resultCopy = result.Clone();
resultsCopy.Add(resultCopy);
}
return resultsCopy;
}
#endregion #endregion
#region BasicCommands #region BasicCommands
@ -610,6 +625,8 @@ namespace Flow.Launcher.ViewModel
{ {
Application.Current.Dispatcher.Invoke(() => Application.Current.Dispatcher.Invoke(() =>
{ {
BackToQueryResults();
if (QueryText != queryText) if (QueryText != queryText)
{ {
// re-query is done in QueryText's setter method // re-query is done in QueryText's setter method
@ -1180,9 +1197,18 @@ namespace Flow.Launcher.ViewModel
currentCancellationToken.ThrowIfCancellationRequested(); currentCancellationToken.ThrowIfCancellationRequested();
results ??= _emptyResult; IReadOnlyList<Result> resultsCopy;
if (results == null)
{
resultsCopy = _emptyResult;
}
else
{
// make a copy of results to avoid possible issue that FL changes some properties of the records, like score, etc.
resultsCopy = DeepCloneResults(results);
}
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(results, plugin.Metadata, query, if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, plugin.Metadata, query,
currentCancellationToken, reSelect))) currentCancellationToken, reSelect)))
{ {
Log.Error("MainViewModel", "Unable to add item to Result Update Queue"); Log.Error("MainViewModel", "Unable to add item to Result Update Queue");
@ -1266,7 +1292,6 @@ namespace Flow.Launcher.ViewModel
{ {
_topMostRecord.Remove(result); _topMostRecord.Remove(result);
App.API.ShowMsg(InternationalizationManager.Instance.GetTranslation("success")); App.API.ShowMsg(InternationalizationManager.Instance.GetTranslation("success"));
App.API.BackToQueryResults();
App.API.ReQuery(); App.API.ReQuery();
return false; return false;
} }
@ -1284,7 +1309,6 @@ namespace Flow.Launcher.ViewModel
{ {
_topMostRecord.AddOrUpdate(result); _topMostRecord.AddOrUpdate(result);
App.API.ShowMsg(InternationalizationManager.Instance.GetTranslation("success")); App.API.ShowMsg(InternationalizationManager.Instance.GetTranslation("success"));
App.API.BackToQueryResults();
App.API.ReQuery(); App.API.ReQuery();
return false; return false;
} }
@ -1471,12 +1495,31 @@ namespace Flow.Launcher.ViewModel
{ {
result.Score = Result.MaxScore; result.Score = Result.MaxScore;
} }
else if (result.Score != Result.MaxScore) else
{ {
var priorityScore = metaResults.Metadata.Priority * 150; var priorityScore = metaResults.Metadata.Priority * 150;
result.Score += result.AddSelectedCount ? if (result.AddSelectedCount)
_userSelectedRecord.GetSelectedCount(result) + priorityScore : {
priorityScore; if ((long)result.Score + _userSelectedRecord.GetSelectedCount(result) + priorityScore > Result.MaxScore)
{
result.Score = Result.MaxScore;
}
else
{
result.Score += _userSelectedRecord.GetSelectedCount(result) + priorityScore;
}
}
else
{
if ((long)result.Score + priorityScore > Result.MaxScore)
{
result.Score = Result.MaxScore;
}
else
{
result.Score += priorityScore;
}
}
} }
} }
} }

View file

@ -159,5 +159,4 @@ namespace Flow.Launcher.ViewModel
changeKeywordsWindow.ShowDialog(); changeKeywordsWindow.ShowDialog();
} }
} }
} }

View file

@ -1,66 +1,46 @@
using Flow.Launcher.Core; using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Core.Configuration;
using Flow.Launcher.Infrastructure.Storage;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin; using Flow.Launcher.Plugin;
namespace Flow.Launcher.ViewModel; namespace Flow.Launcher.ViewModel;
public class SettingWindowViewModel : BaseModel public partial class SettingWindowViewModel : BaseModel
{ {
private readonly FlowLauncherJsonStorage<Settings> _storage; private readonly Settings _settings;
public Updater Updater { get; } public SettingWindowViewModel(Settings settings)
public IPortable Portable { get; }
public Settings Settings { get; }
public SettingWindowViewModel(Updater updater, IPortable portable)
{ {
_storage = new FlowLauncherJsonStorage<Settings>(); _settings = settings;
Updater = updater;
Portable = portable;
Settings = _storage.Load();
} }
public async void UpdateApp()
{
await Updater.UpdateAppAsync(App.API, false);
}
/// <summary> /// <summary>
/// Save Flow settings. Plugins settings are not included. /// Save Flow settings. Plugins settings are not included.
/// </summary> /// </summary>
public void Save() public void Save()
{ {
_storage.Save(); _settings.Save();
} }
public double SettingWindowWidth public double SettingWindowWidth
{ {
get => Settings.SettingWindowWidth; get => _settings.SettingWindowWidth;
set => Settings.SettingWindowWidth = value; set => _settings.SettingWindowWidth = value;
} }
public double SettingWindowHeight public double SettingWindowHeight
{ {
get => Settings.SettingWindowHeight; get => _settings.SettingWindowHeight;
set => Settings.SettingWindowHeight = value; set => _settings.SettingWindowHeight = value;
} }
public double? SettingWindowTop public double? SettingWindowTop
{ {
get => Settings.SettingWindowTop; get => _settings.SettingWindowTop;
set => Settings.SettingWindowTop = value; set => _settings.SettingWindowTop = value;
} }
public double? SettingWindowLeft public double? SettingWindowLeft
{ {
get => Settings.SettingWindowLeft; get => _settings.SettingWindowLeft;
set => Settings.SettingWindowLeft = value; set => _settings.SettingWindowLeft = value;
} }
} }

View file

@ -31,13 +31,13 @@ namespace Flow.Launcher.Plugin.Explorer.Views
} }
private string actionKeyword; private string actionKeyword;
private readonly IPublicAPI api; private readonly IPublicAPI _api;
private bool _keywordEnabled; private bool _keywordEnabled;
public ActionKeywordSetting(ActionKeywordModel selectedActionKeyword, IPublicAPI api) public ActionKeywordSetting(ActionKeywordModel selectedActionKeyword, IPublicAPI api)
{ {
CurrentActionKeyword = selectedActionKeyword; CurrentActionKeyword = selectedActionKeyword;
this.api = api; _api = api;
ActionKeyword = selectedActionKeyword.Keyword; ActionKeyword = selectedActionKeyword.Keyword;
KeywordEnabled = selectedActionKeyword.Enabled; KeywordEnabled = selectedActionKeyword.Enabled;
@ -62,14 +62,14 @@ namespace Flow.Launcher.Plugin.Explorer.Views
switch (CurrentActionKeyword.KeywordProperty, KeywordEnabled) switch (CurrentActionKeyword.KeywordProperty, KeywordEnabled)
{ {
case (Settings.ActionKeyword.FileContentSearchActionKeyword, true): case (Settings.ActionKeyword.FileContentSearchActionKeyword, true):
api.ShowMsgBox(api.GetTranslation("plugin_explorer_globalActionKeywordInvalid")); _api.ShowMsgBox(_api.GetTranslation("plugin_explorer_globalActionKeywordInvalid"));
return; return;
case (Settings.ActionKeyword.QuickAccessActionKeyword, true): case (Settings.ActionKeyword.QuickAccessActionKeyword, true):
api.ShowMsgBox(api.GetTranslation("plugin_explorer_quickaccess_globalActionKeywordInvalid")); _api.ShowMsgBox(_api.GetTranslation("plugin_explorer_quickaccess_globalActionKeywordInvalid"));
return; return;
} }
if (!KeywordEnabled || !api.ActionKeywordAssigned(ActionKeyword)) if (!KeywordEnabled || !_api.ActionKeywordAssigned(ActionKeyword))
{ {
DialogResult = true; DialogResult = true;
Close(); Close();
@ -77,7 +77,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views
} }
// The keyword is not valid, so show message // The keyword is not valid, so show message
api.ShowMsgBox(api.GetTranslation("newActionKeywordsHasBeenAssigned")); _api.ShowMsgBox(_api.GetTranslation("newActionKeywordsHasBeenAssigned"));
} }
private void BtnCancel_OnClick(object sender, RoutedEventArgs e) private void BtnCancel_OnClick(object sender, RoutedEventArgs e)

View file

@ -59,7 +59,6 @@ namespace Flow.Launcher.Plugin.PluginsManager
var link = pluginManifestInfo.UrlSourceCode.StartsWith("https://github.com") var link = pluginManifestInfo.UrlSourceCode.StartsWith("https://github.com")
? Regex.Replace(pluginManifestInfo.UrlSourceCode, @"\/tree\/\w+$", "") + "/issues" ? Regex.Replace(pluginManifestInfo.UrlSourceCode, @"\/tree\/\w+$", "") + "/issues"
: pluginManifestInfo.UrlSourceCode; : pluginManifestInfo.UrlSourceCode;
Context.API.OpenUrl(link); Context.API.OpenUrl(link);
return true; return true;
} }

View file

@ -15,6 +15,8 @@
<system:String x:Key="plugin_pluginsmanager_installing_plugin">Installing Plugin</system:String> <system:String x:Key="plugin_pluginsmanager_installing_plugin">Installing Plugin</system:String>
<system:String x:Key="plugin_pluginsmanager_install_from_web">Download and install {0}</system:String> <system:String x:Key="plugin_pluginsmanager_install_from_web">Download and install {0}</system:String>
<system:String x:Key="plugin_pluginsmanager_uninstall_title">Plugin Uninstall</system:String> <system:String x:Key="plugin_pluginsmanager_uninstall_title">Plugin Uninstall</system:String>
<system:String x:Key="plugin_pluginsmanager_keep_plugin_settings_title">Keep plugin settings</system:String>
<system:String x:Key="plugin_pluginsmanager_keep_plugin_settings_subtitle">Do you want to keep the settings of the plugin for the next usage?</system:String>
<system:String x:Key="plugin_pluginsmanager_install_success_restart">Plugin {0} successfully installed. Restarting Flow, please wait...</system:String> <system:String x:Key="plugin_pluginsmanager_install_success_restart">Plugin {0} successfully installed. Restarting Flow, please wait...</system:String>
<system:String x:Key="plugin_pluginsmanager_install_errormetadatafile">Unable to find the plugin.json metadata file from the extracted zip file.</system:String> <system:String x:Key="plugin_pluginsmanager_install_errormetadatafile">Unable to find the plugin.json metadata file from the extracted zip file.</system:String>
<system:String x:Key="plugin_pluginsmanager_install_error_duplicate">Error: A plugin which has the same or greater version with {0} already exists.</system:String> <system:String x:Key="plugin_pluginsmanager_install_error_duplicate">Error: A plugin which has the same or greater version with {0} already exists.</system:String>
@ -37,13 +39,13 @@
<system:String x:Key="plugin_pluginsmanager_update_success_restart">Plugin {0} successfully updated. Restarting Flow, please wait...</system:String> <system:String x:Key="plugin_pluginsmanager_update_success_restart">Plugin {0} successfully updated. Restarting Flow, please wait...</system:String>
<system:String x:Key="plugin_pluginsmanager_install_unknown_source_warning_title">Installing from an unknown source</system:String> <system:String x:Key="plugin_pluginsmanager_install_unknown_source_warning_title">Installing from an unknown source</system:String>
<system:String x:Key="plugin_pluginsmanager_install_unknown_source_warning">You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings)</system:String> <system:String x:Key="plugin_pluginsmanager_install_unknown_source_warning">You are installing this plugin from an unknown source and it may contain potential risks!{0}{0}Please ensure you understand where this plugin is from and that it is safe.{0}{0}Would you like to continue still?{0}{0}(You can switch off this warning via settings)</system:String>
<system:String x:Key="plugin_pluginsmanager_install_success_no_restart">Plugin {0} successfully installed. Please restart Flow.</system:String> <system:String x:Key="plugin_pluginsmanager_install_success_no_restart">Plugin {0} successfully installed. Please restart Flow.</system:String>
<system:String x:Key="plugin_pluginsmanager_uninstall_success_no_restart">Plugin {0} successfully uninstalled. Please restart Flow.</system:String> <system:String x:Key="plugin_pluginsmanager_uninstall_success_no_restart">Plugin {0} successfully uninstalled. Please restart Flow.</system:String>
<system:String x:Key="plugin_pluginsmanager_update_success_no_restart">Plugin {0} successfully updated. Please restart Flow.</system:String> <system:String x:Key="plugin_pluginsmanager_update_success_no_restart">Plugin {0} successfully updated. Please restart Flow.</system:String>
<system:String x:Key="plugin_pluginsmanager_update_all_success_no_restart">{0} plugins successfully updated. Please restart Flow.</system:String> <system:String x:Key="plugin_pluginsmanager_update_all_success_no_restart">{0} plugins successfully updated. Please restart Flow.</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_modified_error">Plugin {0} has already been modified. Please restart Flow before making any further changes.</system:String> <system:String x:Key="plugin_pluginsmanager_plugin_modified_error">Plugin {0} has already been modified. Please restart Flow before making any further changes.</system:String>
<!-- Plugin Infos --> <!-- Plugin Infos -->
<system:String x:Key="plugin_pluginsmanager_plugin_name">Plugins Manager</system:String> <system:String x:Key="plugin_pluginsmanager_plugin_name">Plugins Manager</system:String>
<system:String x:Key="plugin_pluginsmanager_plugin_description">Management of installing, uninstalling or updating Flow Launcher plugins</system:String> <system:String x:Key="plugin_pluginsmanager_plugin_description">Management of installing, uninstalling or updating Flow Launcher plugins</system:String>

View file

@ -733,7 +733,11 @@ namespace Flow.Launcher.Plugin.PluginsManager
{ {
try try
{ {
PluginManager.UninstallPlugin(plugin, removeSettings: true); var removePluginSettings = Context.API.ShowMsgBox(
Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_subtitle"),
Context.API.GetTranslation("plugin_pluginsmanager_keep_plugin_settings_title"),
button: MessageBoxButton.YesNo) == MessageBoxResult.No;
PluginManager.UninstallPlugin(plugin, removePluginFromSettings: true, removePluginSettings: removePluginSettings);
} }
catch (ArgumentException e) catch (ArgumentException e)
{ {

View file

@ -36,6 +36,8 @@
<system:String x:Key="flowlauncher_plugin_program_enable_hideuninstallers_tooltip">Hides programs with common uninstaller names, such as unins000.exe</system:String> <system:String x:Key="flowlauncher_plugin_program_enable_hideuninstallers_tooltip">Hides programs with common uninstaller names, such as unins000.exe</system:String>
<system:String x:Key="flowlauncher_plugin_program_enable_description">Search in Program Description</system:String> <system:String x:Key="flowlauncher_plugin_program_enable_description">Search in Program Description</system:String>
<system:String x:Key="flowlauncher_plugin_program_enable_description_tooltip">Flow will search program's description</system:String> <system:String x:Key="flowlauncher_plugin_program_enable_description_tooltip">Flow will search program's description</system:String>
<system:String x:Key="flowlauncher_plugin_program_enable_hideduplicatedwindowsapp">Hide duplicated apps</system:String>
<system:String x:Key="flowlauncher_plugin_program_enable_hideduplicatedwindowsapp_tooltip">Hide duplicated Win32 programs that are already in the UWP list</system:String>
<system:String x:Key="flowlauncher_plugin_program_suffixes_header">Suffixes</system:String> <system:String x:Key="flowlauncher_plugin_program_suffixes_header">Suffixes</system:String>
<system:String x:Key="flowlauncher_plugin_program_max_depth_header">Max Depth</system:String> <system:String x:Key="flowlauncher_plugin_program_max_depth_header">Max Depth</system:String>

View file

@ -72,6 +72,8 @@ namespace Flow.Launcher.Plugin.Program
private const string ExeUninstallerSuffix = ".exe"; private const string ExeUninstallerSuffix = ".exe";
private const string InkUninstallerSuffix = ".lnk"; private const string InkUninstallerSuffix = ".lnk";
private static readonly string WindowsAppPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WindowsApps");
static Main() static Main()
{ {
} }
@ -90,11 +92,20 @@ namespace Flow.Launcher.Plugin.Program
{ {
try try
{ {
// Collect all UWP Windows app directories
var uwpsDirectories = _settings.HideDuplicatedWindowsApp ? _uwps
.Where(uwp => !string.IsNullOrEmpty(uwp.Location)) // Exclude invalid paths
.Where(uwp => uwp.Location.StartsWith(WindowsAppPath, StringComparison.OrdinalIgnoreCase)) // Keep system apps
.Select(uwp => uwp.Location.TrimEnd('\\')) // Remove trailing slash
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray() : null;
return _win32s.Cast<IProgram>() return _win32s.Cast<IProgram>()
.Concat(_uwps) .Concat(_uwps)
.AsParallel() .AsParallel()
.WithCancellation(token) .WithCancellation(token)
.Where(HideUninstallersFilter) .Where(HideUninstallersFilter)
.Where(p => HideDuplicatedWindowsAppFilter(p, uwpsDirectories))
.Where(p => p.Enabled) .Where(p => p.Enabled)
.Select(p => p.Result(query.Search, Context.API)) .Select(p => p.Result(query.Search, Context.API))
.Where(r => r?.Score > 0) .Where(r => r?.Score > 0)
@ -152,6 +163,23 @@ namespace Flow.Launcher.Plugin.Program
return true; return true;
} }
private static bool HideDuplicatedWindowsAppFilter(IProgram program, string[] uwpsDirectories)
{
if (uwpsDirectories == null || uwpsDirectories.Length == 0) return true;
if (program is UWPApp) return true;
var location = program.Location.TrimEnd('\\'); // Ensure trailing slash
if (string.IsNullOrEmpty(location))
return true; // Keep if location is invalid
if (!location.StartsWith(WindowsAppPath, StringComparison.OrdinalIgnoreCase))
return true; // Keep if not a Windows app
// Check if the any Win32 executable directory contains UWP Windows app location matches
return !uwpsDirectories.Any(uwpDirectory =>
location.StartsWith(uwpDirectory, StringComparison.OrdinalIgnoreCase));
}
public async Task InitAsync(PluginInitContext context) public async Task InitAsync(PluginInitContext context)
{ {
Context = context; Context = context;
@ -264,7 +292,6 @@ namespace Flow.Launcher.Plugin.Program
Context.API.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_success"), Context.API.GetTranslation("flowlauncher_plugin_program_disable_dlgtitle_success"),
Context.API.GetTranslation( Context.API.GetTranslation(
"flowlauncher_plugin_program_disable_dlgtitle_success_message")); "flowlauncher_plugin_program_disable_dlgtitle_success_message"));
Context.API.BackToQueryResults();
Context.API.ReQuery(); Context.API.ReQuery();
return false; return false;
}, },

View file

@ -121,6 +121,7 @@ namespace Flow.Launcher.Plugin.Program
public bool EnableRegistrySource { get; set; } = true; public bool EnableRegistrySource { get; set; } = true;
public bool EnablePathSource { get; set; } = false; public bool EnablePathSource { get; set; } = false;
public bool EnableUWP { get; set; } = true; public bool EnableUWP { get; set; } = true;
public bool HideDuplicatedWindowsApp { get; set; } = false;
internal const char SuffixSeparator = ';'; internal const char SuffixSeparator = ';';
} }

View file

@ -8,7 +8,7 @@
DataContext="{Binding RelativeSource={RelativeSource Self}}" DataContext="{Binding RelativeSource={RelativeSource Self}}"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Resources> <UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/> <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</UserControl.Resources> </UserControl.Resources>
<Grid Margin="0"> <Grid Margin="0">
<Grid.RowDefinitions> <Grid.RowDefinitions>
@ -18,40 +18,40 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<DockPanel <DockPanel
Margin="70,10,0,8" Margin="70 10 0 8"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
LastChildFill="True"> LastChildFill="True">
<TextBlock <TextBlock
MinWidth="120" MinWidth="120"
Margin="0,5,10,0" Margin="0 5 10 0"
Text="{DynamicResource flowlauncher_plugin_program_index_source}" /> Text="{DynamicResource flowlauncher_plugin_program_index_source}" />
<WrapPanel <WrapPanel
Width="Auto" Width="Auto"
Margin="0,0,14,0" Margin="0 0 14 0"
HorizontalAlignment="Right" HorizontalAlignment="Right"
DockPanel.Dock="Right"> DockPanel.Dock="Right">
<CheckBox <CheckBox
Name="UWPEnabled" Name="UWPEnabled"
Margin="12,0,12,0" Margin="12 0 12 0"
Visibility="{Binding ShowUWPCheckbox, Converter={StaticResource BooleanToVisibilityConverter}}"
Content="{DynamicResource flowlauncher_plugin_program_index_uwp}" Content="{DynamicResource flowlauncher_plugin_program_index_uwp}"
IsChecked="{Binding EnableUWP}" IsChecked="{Binding EnableUWP}"
ToolTip="{DynamicResource flowlauncher_plugin_program_index_uwp_tooltip}" /> ToolTip="{DynamicResource flowlauncher_plugin_program_index_uwp_tooltip}"
Visibility="{Binding ShowUWPCheckbox, Converter={StaticResource BooleanToVisibilityConverter}}" />
<CheckBox <CheckBox
Name="StartMenuEnabled" Name="StartMenuEnabled"
Margin="12,0,12,0" Margin="12 0 12 0"
Content="{DynamicResource flowlauncher_plugin_program_index_start}" Content="{DynamicResource flowlauncher_plugin_program_index_start}"
IsChecked="{Binding EnableStartMenuSource}" IsChecked="{Binding EnableStartMenuSource}"
ToolTip="{DynamicResource flowlauncher_plugin_program_index_start_tooltip}" /> ToolTip="{DynamicResource flowlauncher_plugin_program_index_start_tooltip}" />
<CheckBox <CheckBox
Name="RegistryEnabled" Name="RegistryEnabled"
Margin="12,0,12,0" Margin="12 0 12 0"
Content="{DynamicResource flowlauncher_plugin_program_index_registry}" Content="{DynamicResource flowlauncher_plugin_program_index_registry}"
IsChecked="{Binding EnableRegistrySource}" IsChecked="{Binding EnableRegistrySource}"
ToolTip="{DynamicResource flowlauncher_plugin_program_index_registry_tooltip}" /> ToolTip="{DynamicResource flowlauncher_plugin_program_index_registry_tooltip}" />
<CheckBox <CheckBox
Name="PATHEnabled" Name="PATHEnabled"
Margin="12,0,12,0" Margin="12 0 12 0"
Content="{DynamicResource flowlauncher_plugin_program_index_PATH}" Content="{DynamicResource flowlauncher_plugin_program_index_PATH}"
IsChecked="{Binding EnablePATHSource}" IsChecked="{Binding EnablePATHSource}"
ToolTip="{DynamicResource flowlauncher_plugin_program_index_PATH_tooltip}" /> ToolTip="{DynamicResource flowlauncher_plugin_program_index_PATH_tooltip}" />
@ -67,21 +67,20 @@
BorderBrush="{DynamicResource Color03B}" BorderBrush="{DynamicResource Color03B}"
BorderThickness="1" /> BorderThickness="1" />
<DockPanel <DockPanel
Margin="70,10,0,8" Margin="70 10 0 8"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
LastChildFill="True"> LastChildFill="True">
<TextBlock <TextBlock
MinWidth="120" MinWidth="120"
Margin="0,5,10,0" Margin="0 5 10 0"
Text="{DynamicResource flowlauncher_plugin_program_index_option}" /> Text="{DynamicResource flowlauncher_plugin_program_index_option}" />
<WrapPanel <WrapPanel
Width="Auto" Width="Auto"
Margin="0,0,14,0" Margin="0 0 14 0"
HorizontalAlignment="Right" HorizontalAlignment="Right"
DockPanel.Dock="Right"> DockPanel.Dock="Right">
<CheckBox <CheckBox
Name="HideLnkEnabled" Margin="12 0 12 0"
Margin="12,0,12,0"
Content="{DynamicResource flowlauncher_plugin_program_enable_hidelnkpath}" Content="{DynamicResource flowlauncher_plugin_program_enable_hidelnkpath}"
IsChecked="{Binding HideAppsPath}" IsChecked="{Binding HideAppsPath}"
ToolTip="{DynamicResource flowlauncher_plugin_program_enable_hidelnkpath_tooltip}" /> ToolTip="{DynamicResource flowlauncher_plugin_program_enable_hidelnkpath_tooltip}" />
@ -91,11 +90,15 @@
IsChecked="{Binding HideUninstallers}" IsChecked="{Binding HideUninstallers}"
ToolTip="{DynamicResource flowlauncher_plugin_program_enable_hideuninstallers_tooltip}" /> ToolTip="{DynamicResource flowlauncher_plugin_program_enable_hideuninstallers_tooltip}" />
<CheckBox <CheckBox
Name="DescriptionEnabled" Margin="12 0 12 0"
Margin="12,0,12,0"
Content="{DynamicResource flowlauncher_plugin_program_enable_description}" Content="{DynamicResource flowlauncher_plugin_program_enable_description}"
IsChecked="{Binding EnableDescription}" IsChecked="{Binding EnableDescription}"
ToolTip="{DynamicResource flowlauncher_plugin_program_enable_description_tooltip}" /> ToolTip="{DynamicResource flowlauncher_plugin_program_enable_description_tooltip}" />
<CheckBox
Margin="12 0 12 0"
Content="{DynamicResource flowlauncher_plugin_program_enable_hideduplicatedwindowsapp}"
IsChecked="{Binding HideDuplicatedWindowsApp}"
ToolTip="{DynamicResource flowlauncher_plugin_program_enable_hideduplicatedwindowsapp_tooltip}" />
</WrapPanel> </WrapPanel>
</DockPanel> </DockPanel>
<Separator <Separator
@ -103,28 +106,28 @@
BorderBrush="{DynamicResource Color03B}" BorderBrush="{DynamicResource Color03B}"
BorderThickness="1" /> BorderThickness="1" />
<StackPanel <StackPanel
Margin="60,0,0,2" Margin="60 0 0 2"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
Orientation="Horizontal"> Orientation="Horizontal">
<Button <Button
x:Name="btnLoadAllProgramSource" x:Name="btnLoadAllProgramSource"
MinWidth="120" MinWidth="120"
Margin="10,10,5,10" Margin="10 10 5 10"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Click="btnLoadAllProgramSource_OnClick" Click="btnLoadAllProgramSource_OnClick"
Content="{DynamicResource flowlauncher_plugin_program_all_programs}" /> Content="{DynamicResource flowlauncher_plugin_program_all_programs}" />
<Button <Button
x:Name="btnProgramSuffixes" x:Name="btnProgramSuffixes"
MinWidth="120" MinWidth="120"
Margin="5,10,5,10" Margin="5 10 5 10"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Click="BtnProgramSuffixes_OnClick" Click="BtnProgramSuffixes_OnClick"
Content="{DynamicResource flowlauncher_plugin_program_suffixes}" /> Content="{DynamicResource flowlauncher_plugin_program_suffixes}" />
<Button <Button
x:Name="btnReindex" x:Name="btnReindex"
MinWidth="120" MinWidth="120"
Margin="5,10,5,10" Margin="5 10 5 10"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Click="btnReindex_Click" Click="btnReindex_Click"
Content="{DynamicResource flowlauncher_plugin_program_reindex}" /> Content="{DynamicResource flowlauncher_plugin_program_reindex}" />
@ -142,7 +145,7 @@
Minimum="0" /> Minimum="0" />
<TextBlock <TextBlock
Height="20" Height="20"
Margin="10,0,0,0" Margin="10 0 0 0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{DynamicResource flowlauncher_plugin_program_indexing}" /> Text="{DynamicResource flowlauncher_plugin_program_indexing}" />
</StackPanel> </StackPanel>
@ -151,7 +154,7 @@
<ListView <ListView
x:Name="programSourceView" x:Name="programSourceView"
Grid.Row="2" Grid.Row="2"
Margin="70,0,20,0" Margin="70 0 20 0"
AllowDrop="True" AllowDrop="True"
BorderBrush="DarkGray" BorderBrush="DarkGray"
BorderThickness="1" BorderThickness="1"
@ -203,7 +206,7 @@
<DockPanel <DockPanel
Grid.Row="3" Grid.Row="3"
Grid.RowSpan="1" Grid.RowSpan="1"
Margin="0,0,20,0"> Margin="0 0 20 0">
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal"> <StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<Button <Button
x:Name="btnProgramSourceStatus" x:Name="btnProgramSourceStatus"
@ -220,7 +223,7 @@
<Button <Button
x:Name="btnAddProgramSource" x:Name="btnAddProgramSource"
MinWidth="100" MinWidth="100"
Margin="10,10,0,10" Margin="10 10 0 10"
Click="btnAddProgramSource_OnClick" Click="btnAddProgramSource_OnClick"
Content="{DynamicResource flowlauncher_plugin_program_add}" /> Content="{DynamicResource flowlauncher_plugin_program_add}" />
</StackPanel> </StackPanel>

View file

@ -57,6 +57,16 @@ namespace Flow.Launcher.Plugin.Program.Views
} }
} }
public bool HideDuplicatedWindowsApp
{
get => _settings.HideDuplicatedWindowsApp;
set
{
Main.ResetCache();
_settings.HideDuplicatedWindowsApp = value;
}
}
public bool EnableRegistrySource public bool EnableRegistrySource
{ {
get => _settings.EnableRegistrySource; get => _settings.EnableRegistrySource;

View file

@ -5,7 +5,6 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows;
using WindowsInput; using WindowsInput;
using WindowsInput.Native; using WindowsInput.Native;
using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.Hotkey;
@ -379,7 +378,7 @@ namespace Flow.Launcher.Plugin.Shell
private void OnWinRPressed() private void OnWinRPressed()
{ {
// show the main window and set focus to the query box // show the main window and set focus to the query box
Task.Run(() => _ = Task.Run(() =>
{ {
context.API.ShowMainWindow(); context.API.ShowMainWindow();
context.API.ChangeQuery($"{context.CurrentPluginMetadata.ActionKeywords[0]}{Plugin.Query.TermSeparator}"); context.API.ChangeQuery($"{context.CurrentPluginMetadata.ActionKeywords[0]}{Plugin.Query.TermSeparator}");