diff --git a/Flow.Launcher.Test/Plugins/CalculatorTest.cs b/Flow.Launcher.Test/Plugins/CalculatorTest.cs
index 4e40d3645b0..3f0dc53d113 100644
--- a/Flow.Launcher.Test/Plugins/CalculatorTest.cs
+++ b/Flow.Launcher.Test/Plugins/CalculatorTest.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Reflection;
using Flow.Launcher.Plugin.Calculator;
+using Flow.Launcher.Plugin.Calculator.Storage;
using Mages.Core;
using NUnit.Framework;
using NUnit.Framework.Legacy;
@@ -17,7 +18,8 @@ public class CalculatorPluginTest
DecimalSeparator = DecimalSeparator.UseSystemLocale,
MaxDecimalPlaces = 10,
ShowErrorMessage = false, // Make sure we return the empty results when error occurs
- UseThousandsSeparator = true // Default value
+ UseThousandsSeparator = true, // Default value
+ EnableHistory = true,
};
private readonly Engine _engine = new(new Configuration
{
@@ -40,6 +42,14 @@ public CalculatorPluginTest()
if (engineField == null)
Assert.Fail("Could not find static field 'MagesEngine' on Flow.Launcher.Plugin.Calculator.Main");
engineField.SetValue(null, _engine);
+
+ SetHistory(new History());
+ }
+
+ [SetUp]
+ public void SetUp()
+ {
+ SetHistory(new History());
}
[Test]
@@ -80,6 +90,34 @@ public void ThousandsSeparatorTest_LargeNumber()
ClassicAssert.AreEqual("1234567", result);
}
+ [Test]
+ public void CalculationHistory_IsStoredWhenEnabled()
+ {
+ _settings.EnableHistory = true;
+
+ _plugin.Query(new Plugin.Query { Search = "1+1" });
+ WaitForHistoryDebounce();
+
+ ClassicAssert.AreEqual(1, GetHistory().Items.Count);
+ ClassicAssert.AreEqual("1+1", GetHistory().Items[0].Query);
+ ClassicAssert.AreEqual("2", GetHistory().Items[0].CopyText);
+ ClassicAssert.IsNotEmpty(GetHistory().Items[0].SubTitle);
+ }
+
+ [Test]
+ public void CalculationHistory_IsNotReturnedWhenDisabled()
+ {
+ _settings.EnableHistory = true;
+ _plugin.Query(new Plugin.Query { Search = "1+1" });
+ WaitForHistoryDebounce();
+
+ _settings.EnableHistory = false;
+ var results = _plugin.Query(new Plugin.Query { Search = "2+2" });
+
+ ClassicAssert.AreEqual(1, results.Count);
+ ClassicAssert.AreEqual("4", results[0].Title);
+ }
+
// Basic operations
[TestCase(@"1+1", "2")]
[TestCase(@"2-1", "1")]
@@ -130,5 +168,36 @@ private string GetCalculationResult(string expression)
});
return results.Count > 0 ? results[0].Title : string.Empty;
}
+
+ private void WaitForHistoryDebounce()
+ {
+ var flushPendingItemMethod = typeof(History).GetMethod("FlushPendingItem", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.That(flushPendingItemMethod, Is.Not.Null,
+ "Could not find method 'FlushPendingItem' on Flow.Launcher.Plugin.Calculator.Storage.History");
+
+ flushPendingItemMethod!.Invoke(GetHistory(), null);
+ }
+
+ private History GetHistory()
+ {
+ var historyProperty = typeof(Main).GetProperty("History", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.That(historyProperty, Is.Not.Null,
+ "Could not find property 'History' on Flow.Launcher.Plugin.Calculator.Main");
+
+ var history = historyProperty!.GetValue(_plugin) as History;
+ Assert.That(history, Is.Not.Null,
+ "Property 'History' on Flow.Launcher.Plugin.Calculator.Main' was not a calculator History instance");
+
+ return history!;
+ }
+
+ private void SetHistory(History history)
+ {
+ var historyProperty = typeof(Main).GetProperty("History", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.That(historyProperty, Is.Not.Null,
+ "Could not find property 'History' on Flow.Launcher.Plugin.Calculator.Main");
+
+ historyProperty!.SetValue(_plugin, history);
+ }
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/HistoryCreationMode.cs b/Plugins/Flow.Launcher.Plugin.Calculator/HistoryCreationMode.cs
new file mode 100644
index 00000000000..e14d7eaf089
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/HistoryCreationMode.cs
@@ -0,0 +1,23 @@
+using Flow.Launcher.Localization.Attributes;
+
+namespace Flow.Launcher.Plugin.Calculator
+{
+ ///
+ /// Represents the different modes for saving calculator calculations into the history.
+ ///
+ [EnumLocalize]
+ public enum HistoryCreationMode
+ {
+ ///
+ /// Saves calculations into the history automatically as the query is typed.
+ ///
+ [EnumLocalizeKey(nameof(Localize.flowlauncher_plugin_calculator_history_creation_mode_on_query))]
+ OnQuery,
+
+ ///
+ /// Saves calculations into the history only when the action is executed (e.g. Enter is pressed).
+ ///
+ [EnumLocalizeKey(nameof(Localize.flowlauncher_plugin_calculator_history_creation_mode_on_enter))]
+ OnEnter
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/HistoryHelper.cs b/Plugins/Flow.Launcher.Plugin.Calculator/HistoryHelper.cs
new file mode 100644
index 00000000000..0b367456bb1
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/HistoryHelper.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Globalization;
+using Flow.Launcher.Plugin;
+using Flow.Launcher.Plugin.Calculator.Storage;
+
+namespace Flow.Launcher.Plugin.Calculator
+{
+ ///
+ /// Helper class providing utility methods for formatting relative time strings and creating pending history items.
+ ///
+ internal static class HistoryHelper
+ {
+ ///
+ /// Formats a DateTime value into a localized relative time string (e.g. "just now", "5 minutes ago").
+ ///
+ /// The plugin init context used to retrieve localized strings.
+ /// The time when the calculation was recorded.
+ /// A localized relative time delta string.
+ public static string GetTimeDeltaString(PluginInitContext context, DateTime calculatedAt)
+ {
+ var now = DateTime.Now;
+ var timeSpan = now - calculatedAt;
+
+ if (timeSpan.TotalSeconds < 0)
+ {
+ timeSpan = TimeSpan.Zero;
+ }
+
+ if (timeSpan.TotalSeconds < 60)
+ {
+ return context == null ? "just now" : Localize.flowlauncher_plugin_calculator_time_just_now();
+ }
+ if (timeSpan.TotalMinutes < 60)
+ {
+ var minutes = (int)timeSpan.TotalMinutes;
+ if (minutes == 1)
+ {
+ return context == null ? "1 minute ago" : Localize.flowlauncher_plugin_calculator_time_minute_ago();
+ }
+ return context == null ? $"{minutes} minutes ago" : Localize.flowlauncher_plugin_calculator_time_minutes_ago(minutes);
+ }
+ if (timeSpan.TotalHours < 24)
+ {
+ var hours = (int)timeSpan.TotalHours;
+ if (hours == 1)
+ {
+ return context == null ? "1 hour ago" : Localize.flowlauncher_plugin_calculator_time_hour_ago();
+ }
+ return context == null ? $"{hours} hours ago" : Localize.flowlauncher_plugin_calculator_time_hours_ago(hours);
+ }
+ if (timeSpan.TotalDays < 30)
+ {
+ var days = (int)timeSpan.TotalDays;
+ if (days == 1)
+ {
+ return context == null ? "1 day ago" : Localize.flowlauncher_plugin_calculator_time_day_ago();
+ }
+ return context == null ? $"{days} days ago" : Localize.flowlauncher_plugin_calculator_time_days_ago(days);
+ }
+ if (timeSpan.TotalDays < 365)
+ {
+ var months = (int)(timeSpan.TotalDays / 30);
+ if (months == 1)
+ {
+ return context == null ? "1 month ago" : Localize.flowlauncher_plugin_calculator_time_month_ago();
+ }
+ return context == null ? $"{months} months ago" : Localize.flowlauncher_plugin_calculator_time_months_ago(months);
+ }
+ var years = (int)(timeSpan.TotalDays / 365);
+ if (years == 1)
+ {
+ return context == null ? "1 year ago" : Localize.flowlauncher_plugin_calculator_time_year_ago();
+ }
+ return context == null ? $"{years} years ago" : Localize.flowlauncher_plugin_calculator_time_years_ago(years);
+ }
+
+ ///
+ /// Creates a representing a calculation that is currently being typed by the user.
+ ///
+ /// The plugin init context.
+ /// The query result object.
+ /// The text representation of the calculation result.
+ /// The math expression string.
+ /// A new instance.
+ public static PendingHistoryItem CreatePendingHistoryItem(PluginInitContext context, Result result, string calcResult, string expression)
+ {
+ var calculatedAt = DateTime.Now;
+ var copyToClipboard = context == null
+ ? "Copy this number to the clipboard"
+ : Localize.flowlauncher_plugin_calculator_copy_number_to_clipboard();
+ var timeDeltaStr = GetTimeDeltaString(context, calculatedAt);
+ var historySubtitle = context == null
+ ? string.Format(CultureInfo.CurrentCulture, "Calculated {0}", timeDeltaStr)
+ : Localize.flowlauncher_plugin_calculator_history_subtitle(timeDeltaStr);
+ var subtitle =
+ $"{calcResult} - {copyToClipboard}" +
+ $"\n{historySubtitle}";
+ return new PendingHistoryItem(result, expression, result.Action, subtitle, calculatedAt);
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Images/history.png b/Plugins/Flow.Launcher.Plugin.Calculator/Images/history.png
new file mode 100644
index 00000000000..58ebafd56a9
Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.Calculator/Images/history.png differ
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml
index 5f9a3ca5a6c..e88b12df54a 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml
@@ -17,4 +17,22 @@
Show thousands separator in results
Copy failed, please try later
Show error message when calculation fails
-
\ No newline at end of file
+ Enable History
+ {0}
+ just now
+ 1 minute ago
+ {0} minutes ago
+ 1 hour ago
+ {0} hours ago
+ 1 day ago
+ {0} days ago
+ 1 month ago
+ {0} months ago
+ 1 year ago
+ {0} years ago
+ History save mode
+ Save on query
+ Save on Enter
+ Warning: Due to the plugin's architecture, saving on query may register incomplete calculations in the history while you type.
+ Copy the result of this expression to the clipboard
+
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/pt-br.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/pt-br.xaml
index 6f3ab9540eb..37218c19177 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/pt-br.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/pt-br.xaml
@@ -1,4 +1,4 @@
-
+
Calculadora
@@ -15,4 +15,10 @@
Show thousands separator in results
Copy failed, please try later
Show error message when calculation fails
+ Habilitar Histórico
+ Calculado em {0}
+ Modo de salvar no histórico
+ Salvar por query
+ Salvar por enter
+ Aviso: Devido à arquitetura do plugin, salvar por query pode registrar cálculos incompletos no histórico enquanto você digita.
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
index 1b9a38e1819..11d7c127394 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs
@@ -1,10 +1,11 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Windows.Controls;
+using Flow.Launcher.Plugin.Calculator.Storage;
using Flow.Launcher.Plugin.Calculator.ViewModels;
using Flow.Launcher.Plugin.Calculator.Views;
using Mages.Core;
@@ -24,8 +25,10 @@ public class Main : IPlugin, IPluginI18n, ISettingProvider
private const string Comma = ",";
private const string Dot = ".";
private const string IcoPath = "Images/calculator.png";
+
private static readonly List EmptyResults = [];
+ private History History { get; set; } = null!;
internal static PluginInitContext Context { get; private set; } = null!;
private Settings _settings;
@@ -35,6 +38,7 @@ public void Init(PluginInitContext context)
{
Context = context;
_settings = context.API.LoadSettingJsonStorage();
+ History = context.API.LoadSettingJsonStorage();
_viewModel = new SettingsViewModel(_settings);
MagesEngine = new Engine(new Configuration
@@ -57,7 +61,7 @@ public List Query(Query query)
{
var search = query.Search;
bool isFunctionPresent = FunctionRegex.IsMatch(search);
-
+ bool isValidResultToAddHistory = true;
// Mages is case sensitive, so we need to convert all function names to lower case.
search = FunctionRegex.Replace(search, m => m.Value.ToLowerInvariant());
@@ -119,43 +123,68 @@ public List Query(Query query)
if (result.ToString() == "NaN")
{
result = Localize.flowlauncher_plugin_calculator_not_a_number();
+ isValidResultToAddHistory = false;
}
if (result is Function)
{
result = Localize.flowlauncher_plugin_calculator_expression_not_complete();
+ isValidResultToAddHistory = false;
}
if (!string.IsNullOrEmpty(result.ToString()))
{
decimal roundedResult = Math.Round(Convert.ToDecimal(result), _settings.MaxDecimalPlaces, MidpointRounding.AwayFromZero);
string newResult = FormatResult(roundedResult);
-
- return
- [
- new Result
+
+ var results = new List();
+ var resultObject = new Result
+ {
+ Title = newResult,
+ IcoPath = IcoPath,
+ Score = 300,
+ // Check context nullability for unit testing
+ SubTitle = Context == null
+ ? string.Empty
+ : Localize.flowlauncher_plugin_calculator_copy_number_to_clipboard(),
+ CopyText = newResult
+ };
+
+ resultObject.Action = BuildResultAction(resultObject, newResult, expression);
+
+ if (_settings.EnableHistory && _settings.HistoryCreationMode == HistoryCreationMode.OnQuery)
+ {
+ var item = HistoryHelper.CreatePendingHistoryItem(Context, resultObject, newResult, expression);
+ History.AddOrUpdate(item);
+ }
+ results.Add(resultObject);
+ var historyItems = _settings.EnableHistory
+ ? History.GetItemsExcluding(expression)
+ : [];
+ var resultsToReturn = new List();
+ if (_settings.EnableHistory)
+ {
+ foreach (var item in historyItems)
{
- Title = newResult,
- IcoPath = IcoPath,
- Score = 300,
- // Check context nullability for unit testing
- SubTitle = Context == null ? string.Empty : Localize.flowlauncher_plugin_calculator_copy_number_to_clipboard(),
- CopyText = newResult,
- Action = c =>
+ var timeDeltaStr = HistoryHelper.GetTimeDeltaString(Context, item.CalculatedAt);
+ var historySubtitle = Context == null
+ ? string.Format(CultureInfo.CurrentCulture, "{0}", timeDeltaStr)
+ : Localize.flowlauncher_plugin_calculator_history_subtitle(timeDeltaStr);
+ resultsToReturn.Add(new Result
{
- try
- {
- Context.API.CopyToClipboard(newResult);
- return true;
- }
- catch (ExternalException)
- {
- Context.API.ShowMsgBox(Localize.flowlauncher_plugin_calculator_failed_to_copy());
- return false;
- }
- }
+ Title = item.Query,
+ SubTitle = $"{item.CopyText} - {historySubtitle}",
+ IcoPath = item.IcoPath,
+ BadgeIcoPath = item.BadgeIcoPath,
+ ShowBadge = item.ShowBadge,
+ Score = item.Score,
+ CopyText = item.CopyText,
+ Action = item.Action
+ });
}
- ];
+ }
+ return results.Concat(resultsToReturn).ToList();
+
}
}
catch (Exception)
@@ -175,6 +204,60 @@ public List Query(Query query)
return EmptyResults;
}
+
+
+ private Func BuildResultAction
+ (
+ Result result,
+ string newResult,
+ string expression
+ )
+ {
+
+ var action =
+ !_settings.EnableHistory || _settings.HistoryCreationMode == HistoryCreationMode.OnQuery
+ ? CreateClipboardAction(newResult)
+ : CreateClipboardActionWithHistory(result, newResult, expression);
+
+ return action;
+
+ }
+
+ private Func CreateClipboardAction(string newResult)
+ {
+ return (_) =>
+ {
+ try
+ {
+ Context.API.CopyToClipboard(newResult);
+
+ return true;
+ }
+ catch (ExternalException)
+ {
+ Context.API.ShowMsgBox(
+ Localize.flowlauncher_plugin_calculator_failed_to_copy()
+ );
+ return false;
+ }
+ };
+ }
+
+ private Func CreateClipboardActionWithHistory(Result resultObject, string newResult, string expression)
+ {
+ var baseAction = CreateClipboardAction(newResult);
+ return (actionContext) =>
+ {
+ if (baseAction(actionContext))
+ {
+ var item = new HistoryItem(resultObject, newResult, expression, DateTime.Now);
+ History.AddOrUpdate(item);
+ return true;
+ }
+ return false;
+ };
+ }
+
private static string PowMatchEvaluator(Match m)
{
// m.Groups[1].Value will be `(...)` with parens
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs
index 69cd17ed2da..e94b61581eb 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs
@@ -1,4 +1,4 @@
-namespace Flow.Launcher.Plugin.Calculator;
+namespace Flow.Launcher.Plugin.Calculator;
public class Settings
{
@@ -9,4 +9,8 @@ public class Settings
public bool ShowErrorMessage { get; set; } = false;
public bool UseThousandsSeparator { get; set; } = true;
+
+ public bool EnableHistory { get; set; } = false;
+
+ public HistoryCreationMode HistoryCreationMode { get; set; } = HistoryCreationMode.OnQuery;
}
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs
new file mode 100644
index 00000000000..8d7779c5bc4
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/History.cs
@@ -0,0 +1,142 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json.Serialization;
+using System.Threading;
+
+namespace Flow.Launcher.Plugin.Calculator.Storage;
+
+///
+/// Manages the collection of calculation history items, providing methods to add, update, and retrieve items.
+///
+/// Supports two creation modes:
+///
+/// -
+/// On Query Mode: Uses with a debounce timer to save calculations automatically as the user types without logging incomplete states.
+///
+/// -
+/// On Enter Mode: Adds or updates a standard immediately without any debounce when the calculation is executed.
+///
+///
+///
+///
+public class History
+{
+ private const int MaxItems = 5;
+ private const int DebounceDelayMs = 800;
+
+ ///
+ /// Gets or sets the list of saved history items.
+ ///
+ [JsonInclude]
+ public List Items { get; set; } = [];
+
+ [JsonIgnore]
+ private readonly Lock _syncRoot = new();
+
+ [JsonIgnore]
+ private Timer _debounceTimer;
+
+ [JsonIgnore]
+ private PendingHistoryItem _pendingItem;
+
+ ///
+ /// Adds a completed history item immediately without any debounce, or updates its timestamp if the query already exists.
+ /// Used when HistoryCreationMode is set to OnEnter.
+ ///
+ /// The history item to add or update.
+ public void AddOrUpdate(HistoryItem item)
+ => AddOrUpdateInternal(item);
+
+ ///
+ /// Unlocked internal helper to add a history item, or update its fields if the query already exists.
+ ///
+ /// The history item to add or update.
+ private void AddOrUpdateInternal(HistoryItem item)
+ {
+ var currentItem = Items.FirstOrDefault(x => x.Query == item.Query);
+ if (currentItem != null)
+ {
+ currentItem.CalculatedAt = item.CalculatedAt;
+ currentItem.Title = item.Title;
+ currentItem.SubTitle = item.SubTitle;
+ currentItem.CopyText = item.CopyText;
+ currentItem.Action = item.Action;
+ }
+ else
+ {
+ Add(item);
+ }
+ }
+
+ ///
+ /// Schedules a pending history item to be added or updated after a debounce delay.
+ /// Used when HistoryCreationMode is set to OnQuery to prevent spamming the history with incomplete queries as the user types.
+ ///
+ /// The pending history item to be debounced.
+ public void AddOrUpdate(PendingHistoryItem pendingItem)
+ {
+ lock (_syncRoot)
+ {
+ _pendingItem = pendingItem;
+
+ if (_debounceTimer == null)
+ {
+ _debounceTimer = new Timer(_ => FlushPendingItem(), null, DebounceDelayMs, Timeout.Infinite);
+ }
+ else
+ {
+ _debounceTimer.Change(DebounceDelayMs, Timeout.Infinite);
+ }
+ }
+ }
+
+ ///
+ /// Retrieves a list of history items, sorted by calculation date in descending order, excluding the specified expression.
+ ///
+ /// The expression to exclude from the results.
+ /// A list of history items matching the criteria.
+ public List GetItemsExcluding(string expression)
+ {
+ lock (_syncRoot)
+ {
+ return Items
+ .Where(x => x.Query != expression)
+ .OrderByDescending(x => x.CalculatedAt)
+ .ToList();
+ }
+ }
+
+ ///
+ /// Adds a history item to the list, removing the oldest calculation if the maximum count is exceeded.
+ ///
+ /// The history item to add.
+ private void Add(HistoryItem item)
+ {
+ if (Items.Count >= MaxItems)
+ {
+
+ Items = Items.OrderBy(x => x.CalculatedAt).ToList();
+ Items.RemoveAt(0);
+ }
+ Items.Add(item);
+ }
+
+ ///
+ /// Flushes the currently pending history item by adding it to the list.
+ ///
+ private void FlushPendingItem()
+ {
+ lock (_syncRoot)
+ {
+ if (_pendingItem == null)
+ {
+ return;
+ }
+
+ var item = new HistoryItem(_pendingItem);
+ AddOrUpdateInternal(item);
+
+ _pendingItem = null;
+ }
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs
new file mode 100644
index 00000000000..94e3d5b5ad0
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/HistoryItem.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Text.Json.Serialization;
+
+namespace Flow.Launcher.Plugin.Calculator.Storage;
+
+public class HistoryItem : Result
+{
+ public string Query { get; set; } = string.Empty;
+ public DateTime CalculatedAt { get; set; }
+
+ [JsonIgnore] private const string BadgeIconPath = "Images/history.png";
+
+ public HistoryItem()
+ {
+ }
+
+ public HistoryItem(Result item ,string result, string expression, DateTime calculatedAt)
+ {
+ CalculatedAt = calculatedAt;
+ Title = expression;
+ SubTitle = result;
+ Score = 300;
+ IcoPath = item.IcoPath;
+ Query = expression;
+ BadgeIcoPath = BadgeIconPath;
+ ShowBadge = true;
+ CopyText = item.Title;
+ Action = item.Action;
+
+ }
+
+
+ ///
+ /// Initializes a new instance of the class from a .
+ ///
+ /// The pending history item to copy properties from.
+ public HistoryItem(PendingHistoryItem item)
+ : this(item.Result, item.SubTitle, item.Expression, item.CalculatedAt)
+ {
+ }
+}
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Storage/PendingHistoryItem.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/PendingHistoryItem.cs
new file mode 100644
index 00000000000..972932199ae
--- /dev/null
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Storage/PendingHistoryItem.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Flow.Launcher.Plugin.Calculator.Storage;
+
+public sealed record PendingHistoryItem
+ (
+ Result Result,
+ string Expression,
+ Func Action,
+ string SubTitle,
+ DateTime CalculatedAt
+ );
+
+
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs
index 79236bdf8e5..8c78b697c6f 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs
@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Linq;
namespace Flow.Launcher.Plugin.Calculator.ViewModels;
@@ -23,4 +23,36 @@ public DecimalSeparator SelectedDecimalSeparator
}
}
}
+
+ public bool EnableHistory
+ {
+ get => Settings.EnableHistory;
+ set
+ {
+ if (Settings.EnableHistory != value)
+ {
+ Settings.EnableHistory = value;
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(ShowHistoryQueryWarning));
+ }
+ }
+ }
+
+ public List AllHistoryCreationMode { get; } = HistoryCreationModeLocalized.GetValues();
+
+ public HistoryCreationMode SelectedHistoryCreationMode
+ {
+ get => Settings.HistoryCreationMode;
+ set
+ {
+ if (Settings.HistoryCreationMode != value)
+ {
+ Settings.HistoryCreationMode = value;
+ OnPropertyChanged();
+ OnPropertyChanged(nameof(ShowHistoryQueryWarning));
+ }
+ }
+ }
+
+ public bool ShowHistoryQueryWarning => EnableHistory && SelectedHistoryCreationMode == HistoryCreationMode.OnQuery;
}
diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml
index 82f8eec60f3..ecb2f8fcc30 100644
--- a/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml
@@ -11,12 +11,19 @@
d:DesignWidth="800"
mc:Ignorable="d">
+
+
+
+
+
+
+
@@ -79,5 +86,49 @@
VerticalAlignment="Center"
Content="{DynamicResource flowlauncher_plugin_calculator_show_error_message}"
IsChecked="{Binding Settings.ShowErrorMessage, Mode=TwoWay}" />
+
+
+
+
+
+
+