Flow.Launcher/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs
Jack251970 f6d5a27e0b Refactor and enhance history management system
Refactored the `History` class to separate query and last opened history into distinct models, introducing `LastOpenedHistoryItem` and deprecating `HistoryItem`. Added a `HistoryStyle` enum and property to allow users to toggle between "Query History" and "Last Opened History." Simplified history display logic and updated the UI with a dropdown for history style selection.

Improved history item display with dynamic action population and relative timestamps. Enhanced performance by optimizing history storage with a maximum limit and streamlined logic for adding/retrieving items. Ensured backward compatibility by migrating legacy history data to the new format.

Updated localization strings, removed deprecated properties, and cleaned up redundant code. Fixed bugs related to inconsistent history actions and edge cases with legacy data.
2025-10-14 20:28:39 +08:00

408 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using CommunityToolkit.Mvvm.Input;
using Flow.Launcher.Core;
using Flow.Launcher.Core.Configuration;
using Flow.Launcher.Core.Resource;
using Flow.Launcher.Helper;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.DialogJump;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedModels;
namespace Flow.Launcher.SettingPages.ViewModels;
public partial class SettingsPaneGeneralViewModel : BaseModel
{
public Settings Settings { get; }
private readonly Updater _updater;
private readonly Portable _portable;
private readonly Internationalization _translater;
public SettingsPaneGeneralViewModel(Settings settings, Updater updater, Portable portable, Internationalization translater)
{
Settings = settings;
_updater = updater;
_portable = portable;
_translater = translater;
UpdateEnumDropdownLocalizations();
}
public class SearchWindowScreenData : DropdownDataGeneric<SearchWindowScreens> { }
public class SearchWindowAlignData : DropdownDataGeneric<SearchWindowAligns> { }
public class SearchPrecisionData : DropdownDataGeneric<SearchPrecisionScore> { }
public class LastQueryModeData : DropdownDataGeneric<LastQueryMode> { }
public class DoublePinyinSchemaData : DropdownDataGeneric<DoublePinyinSchemas> { }
public bool StartFlowLauncherOnSystemStartup
{
get => Settings.StartFlowLauncherOnSystemStartup;
set
{
Settings.StartFlowLauncherOnSystemStartup = value;
try
{
if (value)
{
if (UseLogonTaskForStartup)
{
AutoStartup.ChangeToViaLogonTask();
}
else
{
AutoStartup.ChangeToViaRegistry();
}
}
else
{
AutoStartup.DisableViaLogonTaskAndRegistry();
}
}
catch (Exception e)
{
App.API.ShowMsgError(Localize.setAutoStartFailed(), e.Message);
}
}
}
public bool UseLogonTaskForStartup
{
get => Settings.UseLogonTaskForStartup;
set
{
Settings.UseLogonTaskForStartup = value;
if (StartFlowLauncherOnSystemStartup)
{
try
{
if (value)
{
AutoStartup.ChangeToViaLogonTask();
}
else
{
AutoStartup.ChangeToViaRegistry();
}
}
catch (Exception e)
{
App.API.ShowMsgError(Localize.setAutoStartFailed(), e.Message);
}
}
}
}
public List<SearchWindowScreenData> SearchWindowScreens { get; } =
DropdownDataGeneric<SearchWindowScreens>.GetValues<SearchWindowScreenData>("SearchWindowScreen");
public List<SearchWindowAlignData> SearchWindowAligns { get; } =
DropdownDataGeneric<SearchWindowAligns>.GetValues<SearchWindowAlignData>("SearchWindowAlign");
public List<SearchPrecisionData> SearchPrecisionScores { get; } =
DropdownDataGeneric<SearchPrecisionScore>.GetValues<SearchPrecisionData>("SearchPrecision");
public List<int> ScreenNumbers
{
get
{
var screens = MonitorInfo.GetDisplayMonitors();
var screenNumbers = new List<int>();
for (int i = 1; i <= screens.Count; i++)
{
screenNumbers.Add(i);
}
return screenNumbers;
}
}
// This is only required to set at startup. When portable mode enabled/disabled a restart is always required
private static readonly bool _portableMode = DataLocation.PortableDataLocationInUse();
public bool PortableMode
{
get => _portableMode;
set
{
if (!_portable.CanUpdatePortability())
return;
if (DataLocation.PortableDataLocationInUse())
{
_portable.DisablePortableMode();
}
else
{
_portable.EnablePortableMode();
}
}
}
public List<LastQueryModeData> LastQueryModes { get; } =
DropdownDataGeneric<LastQueryMode>.GetValues<LastQueryModeData>("LastQuery");
public List<HistoryStyleLocalized> HistoryStyles { get; } = HistoryStyleLocalized.GetValues();
public bool EnableDialogJump
{
get => Settings.EnableDialogJump;
set
{
if (Settings.EnableDialogJump != value)
{
Settings.EnableDialogJump = value;
DialogJump.SetupDialogJump(value);
if (Settings.EnableDialogJump)
{
HotKeyMapper.SetHotkey(new(Settings.DialogJumpHotkey), DialogJump.OnToggleHotkey);
}
else
{
HotKeyMapper.RemoveHotkey(Settings.DialogJumpHotkey);
}
}
}
}
public class DialogJumpWindowPositionData : DropdownDataGeneric<DialogJumpWindowPositions> { }
public class DialogJumpResultBehaviourData : DropdownDataGeneric<DialogJumpResultBehaviours> { }
public class DialogJumpFileResultBehaviourData : DropdownDataGeneric<DialogJumpFileResultBehaviours> { }
public List<DialogJumpWindowPositionData> DialogJumpWindowPositions { get; } =
DropdownDataGeneric<DialogJumpWindowPositions>.GetValues<DialogJumpWindowPositionData>("DialogJumpWindowPosition");
public List<DialogJumpResultBehaviourData> DialogJumpResultBehaviours { get; } =
DropdownDataGeneric<DialogJumpResultBehaviours>.GetValues<DialogJumpResultBehaviourData>("DialogJumpResultBehaviour");
public List<DialogJumpFileResultBehaviourData> DialogJumpFileResultBehaviours { get; } =
DropdownDataGeneric<DialogJumpFileResultBehaviours>.GetValues<DialogJumpFileResultBehaviourData>("DialogJumpFileResultBehaviour");
public int SearchDelayTimeValue
{
get => Settings.SearchDelayTime;
set
{
if (Settings.SearchDelayTime != value)
{
Settings.SearchDelayTime = value;
OnPropertyChanged();
}
}
}
public int MaxHistoryResultsToShowValue
{
get => Settings.MaxHistoryResultsToShowForHomePage;
set
{
if (Settings.MaxHistoryResultsToShowForHomePage != value)
{
Settings.MaxHistoryResultsToShowForHomePage = value;
OnPropertyChanged();
}
}
}
private void UpdateEnumDropdownLocalizations()
{
DropdownDataGeneric<SearchWindowScreens>.UpdateLabels(SearchWindowScreens);
DropdownDataGeneric<SearchWindowAligns>.UpdateLabels(SearchWindowAligns);
DropdownDataGeneric<SearchPrecisionScore>.UpdateLabels(SearchPrecisionScores);
DropdownDataGeneric<LastQueryMode>.UpdateLabels(LastQueryModes);
HistoryStyleLocalized.UpdateLabels(HistoryStyles);
DropdownDataGeneric<DoublePinyinSchemas>.UpdateLabels(DoublePinyinSchemas);
DropdownDataGeneric<DialogJumpWindowPositions>.UpdateLabels(DialogJumpWindowPositions);
DropdownDataGeneric<DialogJumpResultBehaviours>.UpdateLabels(DialogJumpResultBehaviours);
DropdownDataGeneric<DialogJumpFileResultBehaviours>.UpdateLabels(DialogJumpFileResultBehaviours);
// Since we are using Binding instead of DynamicResource, we need to manually trigger the update
OnPropertyChanged(nameof(AlwaysPreviewToolTip));
Settings.CustomExplorer.OnDisplayNameChanged();
Settings.CustomBrowser.OnDisplayNameChanged();
}
public string Language
{
get => Settings.Language;
set
{
_translater.ChangeLanguage(value);
if (_translater.PromptShouldUsePinyin(value))
ShouldUsePinyin = true;
UpdateEnumDropdownLocalizations();
}
}
#region Korean IME
// The new Korean IME used in Windows 11 has compatibility issues with WPF. This issue is difficult to resolve within
// WPF itself, but it can be avoided by having the user switch to the legacy IME at the system level. Therefore,
// we provide guidance and a direct button for users to make this change themselves. If the relevant registry key does
// not exist (i.e., the Korean IME is not installed), this setting will not be shown at all.
public bool LegacyKoreanIMEEnabled
{
get => Win32Helper.IsLegacyKoreanIMEEnabled();
set
{
if (Win32Helper.SetLegacyKoreanIMEEnabled(value))
{
OnPropertyChanged();
OnPropertyChanged(nameof(KoreanIMERegistryValueIsZero));
}
else
{
// Since this is rarely seen text, language support is not provided.
App.API.ShowMsgError(Localize.KoreanImeSettingChangeFailTitle(), Localize.KoreanImeSettingChangeFailSubTitle());
}
}
}
public bool KoreanIMERegistryKeyExists
{
get
{
var registryKeyExists = Win32Helper.IsKoreanIMEExist();
var koreanLanguageInstalled = InputLanguage.InstalledInputLanguages.Cast<InputLanguage>().Any(lang => lang.Culture.Name.StartsWith("ko"));
var isWindows11 = Win32Helper.IsWindows11();
// Return true if Windows 11 with Korean IME installed, or if the registry key exists
return (isWindows11 && koreanLanguageInstalled) || registryKeyExists;
}
}
public bool KoreanIMERegistryValueIsZero
{
get
{
var value = Win32Helper.GetLegacyKoreanIMERegistryValue();
if (value is int intValue)
{
return intValue == 0;
}
else if (value != null && int.TryParse(value.ToString(), out var parsedValue))
{
return parsedValue == 0;
}
return false;
}
}
[RelayCommand]
private void OpenImeSettings()
{
Win32Helper.OpenImeSettings();
}
#endregion
public bool ShouldUsePinyin
{
get => Settings.ShouldUsePinyin;
set
{
if (value == false && UseDoublePinyin == true)
{
UseDoublePinyin = false;
}
Settings.ShouldUsePinyin = value;
}
}
public bool UseDoublePinyin
{
set => Settings.UseDoublePinyin = value;
get => Settings.UseDoublePinyin;
}
public List<DoublePinyinSchemaData> DoublePinyinSchemas { get; } =
DropdownDataGeneric<DoublePinyinSchemas>.GetValues<DoublePinyinSchemaData>("DoublePinyinSchemas");
public List<Language> Languages => _translater.LoadAvailableLanguages();
public string AlwaysPreviewToolTip => Localize.AlwaysPreviewToolTip(Settings.PreviewHotkey);
private static string GetFileFromDialog(string title, string filter = "")
{
var dlg = new OpenFileDialog
{
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
Multiselect = false,
CheckFileExists = true,
CheckPathExists = true,
Title = title,
Filter = filter
};
return dlg.ShowDialog() switch
{
DialogResult.OK => dlg.FileName,
_ => string.Empty
};
}
private void UpdateApp()
{
_ = _updater.UpdateAppAsync(false);
}
public bool AutoUpdates
{
get => Settings.AutoUpdates;
set
{
Settings.AutoUpdates = value;
if (value)
{
UpdateApp();
}
}
}
[RelayCommand]
private void SelectPython()
{
var selectedFile = GetFileFromDialog(
Localize.selectPythonExecutable(),
"Python|pythonw.exe"
);
if (!string.IsNullOrEmpty(selectedFile))
Settings.PluginSettings.PythonExecutablePath = selectedFile;
}
[RelayCommand]
private void SelectNode()
{
var selectedFile = GetFileFromDialog(
Localize.selectNodeExecutable(),
"node|*.exe"
);
if (!string.IsNullOrEmpty(selectedFile))
Settings.PluginSettings.NodeExecutablePath = selectedFile;
}
[RelayCommand]
private void SelectFileManager()
{
var fileManagerChangeWindow = new SelectFileManagerWindow();
fileManagerChangeWindow.ShowDialog();
}
[RelayCommand]
private void SelectBrowser()
{
var browserWindow = new SelectBrowserWindow();
browserWindow.ShowDialog();
}
}