mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-07-02 08:28:55 +02:00
Compare commits
7 Commits
powerscrip
...
dev/mjolle
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
000f0af4fb | ||
|
|
cfca046f9f | ||
|
|
9fd573b71e | ||
|
|
42c5ce436f | ||
|
|
89e0e73a0c | ||
|
|
c18a4dc367 | ||
|
|
776a0f6b0d |
@@ -48,6 +48,7 @@
|
||||
|
||||
<Style x:Key="DefaultDockItemControlStyle" TargetType="local:DockItemControl">
|
||||
<Style.Setters>
|
||||
<Setter Property="AutomationProperties.LiveSetting" Value="Off" />
|
||||
<Setter Property="Background" Value="{ThemeResource DockItemBackground}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource DockItemBorderBrush}" />
|
||||
<Setter Property="Padding" Value="{StaticResource DockItemPadding}" />
|
||||
|
||||
@@ -13,8 +13,8 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
[TestClass]
|
||||
public class AvailableResultsListTests
|
||||
{
|
||||
private CultureInfo originalCulture;
|
||||
private CultureInfo originalUiCulture;
|
||||
private CultureInfo originalCulture = null!;
|
||||
private CultureInfo originalUiCulture = null!;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
|
||||
@@ -12,8 +12,8 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
[TestClass]
|
||||
public class FallbackTimeDateItemTests
|
||||
{
|
||||
private CultureInfo originalCulture;
|
||||
private CultureInfo originalUiCulture;
|
||||
private CultureInfo originalCulture = null!;
|
||||
private CultureInfo originalUiCulture = null!;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Microsoft.CmdPal.Ext.TimeDate.UnitTests</RootNamespace>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\tests\</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Microsoft.CmdPal.Ext.TimeDate;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("IDisposable", "CA1001:Types that own disposable fields should be disposable", Justification = "Disposed in TestCleanup")]
|
||||
public class NowDockBandTests
|
||||
{
|
||||
private static readonly DateTime FixedTime = new DateTime(2025, 7, 1, 14, 5, 32);
|
||||
|
||||
private CultureInfo _originalCulture = null!;
|
||||
private CultureInfo _originalUiCulture = null!;
|
||||
private NowDockBand? _band;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
{
|
||||
_originalCulture = CultureInfo.CurrentCulture;
|
||||
_originalUiCulture = CultureInfo.CurrentUICulture;
|
||||
CultureInfo.CurrentCulture = new CultureInfo("en-US", false);
|
||||
CultureInfo.CurrentUICulture = new CultureInfo("en-US", false);
|
||||
}
|
||||
|
||||
[TestCleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
_band?.Dispose();
|
||||
_band = null;
|
||||
CultureInfo.CurrentCulture = _originalCulture;
|
||||
CultureInfo.CurrentUICulture = _originalUiCulture;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Constructor_TitleIsSetImmediately()
|
||||
{
|
||||
_band = new NowDockBand(clock: () => FixedTime);
|
||||
|
||||
Assert.AreEqual("2:05:32 PM", _band.Title);
|
||||
Assert.IsFalse(string.IsNullOrEmpty(_band.Subtitle));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateText_LongTimeFormat_TitleContainsSeconds()
|
||||
{
|
||||
_band = new NowDockBand(clock: () => FixedTime);
|
||||
|
||||
_band.UpdateText();
|
||||
|
||||
Assert.AreEqual("2:05:32 PM", _band.Title);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateText_ShortDateFormat_SubtitleIsShortDate()
|
||||
{
|
||||
_band = new NowDockBand(clock: () => FixedTime);
|
||||
|
||||
_band.UpdateText();
|
||||
|
||||
Assert.AreEqual("7/1/2025", _band.Subtitle);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateText_FiresOnUpdatedCallback()
|
||||
{
|
||||
var callbackFired = false;
|
||||
_band = new NowDockBand(onUpdated: () => callbackFired = true, clock: () => FixedTime);
|
||||
|
||||
callbackFired = false; // reset — constructor already fired it once during synchronous UpdateText()
|
||||
|
||||
_band.UpdateText();
|
||||
|
||||
Assert.IsTrue(callbackFired);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateText_CallbackFiredAfterAssignments()
|
||||
{
|
||||
var titleAtCallback = string.Empty;
|
||||
_band = new NowDockBand(
|
||||
onUpdated: () => titleAtCallback = _band?.Title ?? string.Empty,
|
||||
clock: () => FixedTime);
|
||||
|
||||
titleAtCallback = string.Empty; // reset after construction callback
|
||||
|
||||
_band.UpdateText();
|
||||
|
||||
Assert.IsFalse(string.IsNullOrEmpty(titleAtCallback), "Title should be assigned before callback fires");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateText_CopyCommandsUpdated()
|
||||
{
|
||||
_band = new NowDockBand(clock: () => FixedTime);
|
||||
|
||||
_band.UpdateText();
|
||||
|
||||
Assert.AreEqual(_band.Title, _band.CopyTimeCommand.Text);
|
||||
Assert.AreEqual(_band.Subtitle, _band.CopyDateCommand.Text);
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow("de-DE")]
|
||||
[DataRow("fr-FR")]
|
||||
[DataRow("ar-SA")]
|
||||
public void UpdateText_CultureSmoke_TitleNonEmpty(string cultureName)
|
||||
{
|
||||
// Culture MUST be set before construction — constructor calls UpdateText() synchronously
|
||||
CultureInfo.CurrentCulture = new CultureInfo(cultureName, false);
|
||||
CultureInfo.CurrentUICulture = new CultureInfo(cultureName, false);
|
||||
|
||||
_band = new NowDockBand(clock: () => FixedTime);
|
||||
|
||||
Assert.IsFalse(string.IsNullOrEmpty(_band.Title), $"Title should be non-empty for culture '{cultureName}'");
|
||||
Assert.IsFalse(string.IsNullOrEmpty(_band.Subtitle), $"Subtitle should be non-empty for culture '{cultureName}'");
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,8 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
[TestClass]
|
||||
public class QueryTests : CommandPaletteUnitTestBase
|
||||
{
|
||||
private CultureInfo originalCulture;
|
||||
private CultureInfo originalUiCulture;
|
||||
private CultureInfo originalCulture = null!;
|
||||
private CultureInfo originalUiCulture = null!;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
@@ -166,7 +166,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
Assert.IsTrue(results.Length > 0, $"Query '{query}' should return at least one result");
|
||||
|
||||
var firstItem = results.FirstOrDefault();
|
||||
Assert.IsTrue(firstItem.Title.StartsWith("Error: Invalid input", StringComparison.CurrentCulture), $"Query '{query}' should return an error result for invalid input");
|
||||
Assert.IsTrue(firstItem!.Title.StartsWith("Error: Invalid input", StringComparison.CurrentCulture), $"Query '{query}' should return an error result for invalid input");
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
@@ -201,7 +201,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
// Assert
|
||||
Assert.IsNotNull(results);
|
||||
var firstResult = results.FirstOrDefault();
|
||||
Assert.IsTrue(firstResult.Subtitle.StartsWith(expectedSubtitle, StringComparison.CurrentCulture), $"Could not find result with subtitle starting with '{expectedSubtitle}' for query '{query}'");
|
||||
Assert.IsTrue(firstResult!.Subtitle.StartsWith(expectedSubtitle, StringComparison.CurrentCulture), $"Could not find result with subtitle starting with '{expectedSubtitle}' for query '{query}'");
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
@@ -218,7 +218,7 @@ public class QueryTests : CommandPaletteUnitTestBase
|
||||
// Assert
|
||||
Assert.IsNotNull(resultsList);
|
||||
var firstResult = resultsList.FirstOrDefault();
|
||||
Assert.IsTrue(firstResult.Title.Contains(expectedResult, StringComparison.CurrentCulture), $"Delimiter query '{query}' result not match {expectedResult} current result {firstResult.Title}");
|
||||
Assert.IsTrue(firstResult!.Title.Contains(expectedResult, StringComparison.CurrentCulture), $"Delimiter query '{query}' result not match {expectedResult} current result {firstResult.Title}");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
||||
@@ -12,8 +12,8 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
[TestClass]
|
||||
public class ResultHelperTests
|
||||
{
|
||||
private CultureInfo originalCulture;
|
||||
private CultureInfo originalUiCulture;
|
||||
private CultureInfo originalCulture = null!;
|
||||
private CultureInfo originalUiCulture = null!;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
@@ -41,6 +41,8 @@ public class ResultHelperTests
|
||||
{
|
||||
Label = "Test Label",
|
||||
Value = "Test Value",
|
||||
AlternativeSearchTag = string.Empty,
|
||||
IconType = ResultIconType.Time,
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -55,10 +57,10 @@ public class ResultHelperTests
|
||||
[TestMethod]
|
||||
public void ResultHelper_CreateListItem_HandlesNullInput()
|
||||
{
|
||||
AvailableResult availableResult = null;
|
||||
AvailableResult? availableResult = null;
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsException<System.NullReferenceException>(() => availableResult.ToListItem());
|
||||
Assert.ThrowsException<System.NullReferenceException>(() => availableResult!.ToListItem());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -69,6 +71,8 @@ public class ResultHelperTests
|
||||
{
|
||||
Label = string.Empty,
|
||||
Value = string.Empty,
|
||||
AlternativeSearchTag = string.Empty,
|
||||
IconType = ResultIconType.Time,
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -88,6 +92,7 @@ public class ResultHelperTests
|
||||
{
|
||||
Label = "Test Label",
|
||||
Value = "Test Value",
|
||||
AlternativeSearchTag = string.Empty,
|
||||
IconType = ResultIconType.Date,
|
||||
};
|
||||
|
||||
@@ -110,6 +115,8 @@ public class ResultHelperTests
|
||||
{
|
||||
Label = longText,
|
||||
Value = longText,
|
||||
AlternativeSearchTag = string.Empty,
|
||||
IconType = ResultIconType.Time,
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -130,6 +137,8 @@ public class ResultHelperTests
|
||||
{
|
||||
Label = specialText,
|
||||
Value = specialText,
|
||||
AlternativeSearchTag = string.Empty,
|
||||
IconType = ResultIconType.Time,
|
||||
};
|
||||
|
||||
// Act
|
||||
|
||||
@@ -22,7 +22,7 @@ public class Settings : ISettingsInterface
|
||||
bool enableFallbackItems = true,
|
||||
bool timeWithSecond = false,
|
||||
bool dateWithWeekday = false,
|
||||
List<string> customFormats = null)
|
||||
List<string>? customFormats = null)
|
||||
{
|
||||
this.firstWeekOfYear = firstWeekOfYear;
|
||||
this.firstDayOfWeek = firstDayOfWeek;
|
||||
|
||||
@@ -12,8 +12,8 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
[TestClass]
|
||||
public class StringParserTests
|
||||
{
|
||||
private CultureInfo originalCulture;
|
||||
private CultureInfo originalUiCulture;
|
||||
private CultureInfo originalCulture = null!;
|
||||
private CultureInfo originalUiCulture = null!;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
|
||||
@@ -12,8 +12,8 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
[TestClass]
|
||||
public class TimeAndDateHelperTests
|
||||
{
|
||||
private CultureInfo originalCulture;
|
||||
private CultureInfo originalUiCulture;
|
||||
private CultureInfo originalCulture = null!;
|
||||
private CultureInfo originalUiCulture = null!;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
|
||||
@@ -15,8 +15,8 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests;
|
||||
[TestClass]
|
||||
public class TimeDateCalculatorTests
|
||||
{
|
||||
private CultureInfo originalCulture;
|
||||
private CultureInfo originalUiCulture;
|
||||
private CultureInfo originalCulture = null!;
|
||||
private CultureInfo originalUiCulture = null!;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
@@ -69,7 +69,7 @@ public class TimeDateCalculatorTests
|
||||
var settings = new SettingsManager();
|
||||
|
||||
// Act
|
||||
var results = TimeDateCalculator.ExecuteSearch(settings, null);
|
||||
var results = TimeDateCalculator.ExecuteSearch(settings, null!);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(results);
|
||||
|
||||
@@ -11,8 +11,8 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests
|
||||
[TestClass]
|
||||
public class TimeDateCommandsProviderTests
|
||||
{
|
||||
private CultureInfo originalCulture;
|
||||
private CultureInfo originalUiCulture;
|
||||
private CultureInfo originalCulture = null!;
|
||||
private CultureInfo originalUiCulture = null!;
|
||||
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
@@ -90,5 +90,16 @@ namespace Microsoft.CmdPal.Ext.TimeDate.UnitTests
|
||||
// Assert
|
||||
Assert.IsFalse(string.IsNullOrEmpty(displayName));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetDockBands_ReturnsNonEmptyArray()
|
||||
{
|
||||
var provider = new TimeDateCommandsProvider();
|
||||
|
||||
var bands = provider.GetDockBands();
|
||||
|
||||
Assert.IsTrue(bands.Length > 0, "GetDockBands should return at least one item");
|
||||
Assert.IsNotNull(bands[0], "First dock band should not be null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,16 +29,16 @@ internal sealed partial class FallbackTimeDateItem : FallbackCommandItem
|
||||
|
||||
_validOptions = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagDate", CultureInfo.CurrentCulture),
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagDateNow", CultureInfo.CurrentCulture),
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagDate", CultureInfo.CurrentCulture)!,
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagDateNow", CultureInfo.CurrentCulture)!,
|
||||
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagTime", CultureInfo.CurrentCulture),
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagTimeNow", CultureInfo.CurrentCulture),
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagTime", CultureInfo.CurrentCulture)!,
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagTimeNow", CultureInfo.CurrentCulture)!,
|
||||
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagFormat", CultureInfo.CurrentCulture),
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagFormatNow", CultureInfo.CurrentCulture),
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagFormat", CultureInfo.CurrentCulture)!,
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagFormatNow", CultureInfo.CurrentCulture)!,
|
||||
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagWeek", CultureInfo.CurrentCulture),
|
||||
Resources.ResourceManager.GetString("Microsoft_plugin_timedate_SearchTagWeek", CultureInfo.CurrentCulture)!,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ internal sealed partial class FallbackTimeDateItem : FallbackCommandItem
|
||||
}
|
||||
|
||||
var availableResults = AvailableResultsList.GetList(false, _settingsManager, timestamp: _timestamp);
|
||||
ListItem result = null;
|
||||
ListItem? result = null;
|
||||
var maxScore = 0;
|
||||
|
||||
foreach (var f in availableResults)
|
||||
|
||||
@@ -10,22 +10,22 @@ internal sealed class AvailableResult
|
||||
/// <summary>
|
||||
/// Gets or sets the time/date value
|
||||
/// </summary>
|
||||
internal string Value { get; set; }
|
||||
internal required string Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text used for the subtitle and as search term
|
||||
/// </summary>
|
||||
internal string Label { get; set; }
|
||||
internal required string Label { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an alternative search tag that will be evaluated if label doesn't match. For example we like to show the era on searches for 'year' too.
|
||||
/// </summary>
|
||||
internal string AlternativeSearchTag { get; set; }
|
||||
internal required string AlternativeSearchTag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the type of result
|
||||
/// </summary>
|
||||
internal ResultIconType IconType { get; set; }
|
||||
internal required ResultIconType IconType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value to show additional error details
|
||||
@@ -37,7 +37,7 @@ internal sealed class AvailableResult
|
||||
/// </summary>
|
||||
/// <param name="theme">Theme</param>
|
||||
/// <returns>Path</returns>
|
||||
public IconInfo GetIconInfo()
|
||||
public IconInfo? GetIconInfo()
|
||||
{
|
||||
return IconType switch
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ internal static class ResultHelper
|
||||
/// <param name="stringId">Id of the string. (Example: `MyString` for `MyString` and `MyStringNow`)</param>
|
||||
/// <param name="stringIdNow">Optional string id for now case</param>
|
||||
/// <returns>The string from the resource file, or <see cref="string.Empty"/> otherwise.</returns>
|
||||
internal static string SelectStringFromResources(bool isSystemTimeDate, string stringId, string stringIdNow = default)
|
||||
internal static string SelectStringFromResources(bool isSystemTimeDate, string stringId, string? stringIdNow = default)
|
||||
{
|
||||
return !isSystemTimeDate
|
||||
? Resources.ResourceManager.GetString(stringId, CultureInfo.CurrentUICulture) ?? string.Empty
|
||||
|
||||
@@ -145,7 +145,7 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
|
||||
|
||||
public bool DateWithWeekday => _dateWithWeekday.Value;
|
||||
|
||||
public List<string> CustomFormats => _customFormats.Value.Split(TEXTBOXNEWLINE).ToList();
|
||||
public List<string> CustomFormats => (_customFormats.Value ?? string.Empty).Split(TEXTBOXNEWLINE).ToList();
|
||||
|
||||
internal static string SettingsJsonPath()
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Microsoft.CmdPal.Ext.TimeDate</RootNamespace>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Microsoft.CmdPal.Ext.TimeDate.Helpers;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.TimeDate;
|
||||
|
||||
internal sealed partial class NowDockBand : ListItem, IDisposable
|
||||
{
|
||||
private readonly System.Timers.Timer _timer;
|
||||
private readonly Action? _onUpdated;
|
||||
private readonly Func<DateTime> _clock;
|
||||
|
||||
private CopyTextCommand _copyTimeCommand;
|
||||
private CopyTextCommand _copyDateCommand;
|
||||
|
||||
internal CopyTextCommand CopyTimeCommand => _copyTimeCommand;
|
||||
|
||||
internal CopyTextCommand CopyDateCommand => _copyDateCommand;
|
||||
|
||||
internal NowDockBand(Action? onUpdated = null, Func<DateTime>? clock = null)
|
||||
{
|
||||
_onUpdated = onUpdated;
|
||||
_clock = clock ?? (() => DateTime.Now);
|
||||
|
||||
Command = new OpenUrlCommand("ms-actioncenter:")
|
||||
{
|
||||
Id = "com.microsoft.cmdpal.timedate.dockBand",
|
||||
Name = Resources.timedate_show_notification_center_command_name,
|
||||
Result = CommandResult.Dismiss(),
|
||||
};
|
||||
_copyTimeCommand = new CopyTextCommand(string.Empty) { Name = Resources.timedate_copy_time_command_name };
|
||||
_copyDateCommand = new CopyTextCommand(string.Empty) { Name = Resources.timedate_copy_date_command_name };
|
||||
MoreCommands =
|
||||
[
|
||||
new CommandContextItem(_copyTimeCommand),
|
||||
new CommandContextItem(_copyDateCommand),
|
||||
];
|
||||
|
||||
UpdateText();
|
||||
|
||||
_timer = new System.Timers.Timer(1000) { AutoReset = true };
|
||||
_timer.Elapsed += (_, _) => UpdateText();
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
internal void UpdateText()
|
||||
{
|
||||
var now = _clock();
|
||||
var timeString = now.ToString(
|
||||
TimeAndDateHelper.GetStringFormat(FormatStringType.Time, true, false),
|
||||
CultureInfo.CurrentCulture);
|
||||
var dateString = now.ToString(
|
||||
TimeAndDateHelper.GetStringFormat(FormatStringType.Date, false, false),
|
||||
CultureInfo.CurrentCulture);
|
||||
|
||||
Title = timeString;
|
||||
Subtitle = dateString;
|
||||
_copyTimeCommand.Text = timeString;
|
||||
_copyDateCommand.Text = dateString;
|
||||
|
||||
_onUpdated?.Invoke(); // Must remain last — ViewModel reads Title/Subtitle via GetItems() on callback
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_timer.Stop();
|
||||
_timer.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,11 @@ public sealed partial class TimeDateCommandsProvider : CommandProvider
|
||||
private static readonly TimeDateExtensionPage _timeDateExtensionPage = new(_settingsManager);
|
||||
private readonly FallbackTimeDateItem _fallbackTimeDateItem = new(_settingsManager);
|
||||
|
||||
private readonly ListItem _bandItem;
|
||||
private readonly ListItem _notificationCenterBandItem;
|
||||
private readonly WrappedDockItem _bandItem;
|
||||
private readonly WrappedDockItem _notificationCenterBandItem;
|
||||
|
||||
// Keep a reference to the band so we can dispose it when the provider is disposed.
|
||||
private NowDockBand? _nowDockBand;
|
||||
|
||||
public TimeDateCommandsProvider()
|
||||
{
|
||||
@@ -37,8 +40,39 @@ public sealed partial class TimeDateCommandsProvider : CommandProvider
|
||||
Icon = _timeDateExtensionPage.Icon;
|
||||
Settings = _settingsManager.Settings;
|
||||
|
||||
_bandItem = new NowDockBand();
|
||||
_notificationCenterBandItem = new NotificationCenterDockBand();
|
||||
WrappedDockItem? wrappedBand = null;
|
||||
|
||||
// During NowDockBand construction, UpdateText() runs synchronously.
|
||||
// At that point wrappedBand is still null so the callback is a no-op.
|
||||
// On subsequent timer ticks, wrappedBand is non-null and SetItems fires
|
||||
// RaiseItemsChanged — the framework marshals to the UI thread in
|
||||
// DockBandViewModel.InitializeFromList via DoOnUiThread.
|
||||
_nowDockBand = new NowDockBand(onUpdated: () =>
|
||||
{
|
||||
if (wrappedBand is not null)
|
||||
{
|
||||
wrappedBand.Items = [_nowDockBand!];
|
||||
}
|
||||
});
|
||||
|
||||
wrappedBand = new WrappedDockItem(
|
||||
[_nowDockBand],
|
||||
"com.microsoft.cmdpal.timedate.dockBand",
|
||||
Resources.Microsoft_plugin_timedate_dock_band_title)
|
||||
{
|
||||
Icon = Icons.TimeDateExtIcon,
|
||||
};
|
||||
|
||||
_bandItem = wrappedBand;
|
||||
|
||||
var notificationCenterBand = new NotificationCenterDockBand();
|
||||
_notificationCenterBandItem = new WrappedDockItem(
|
||||
[notificationCenterBand],
|
||||
"com.microsoft.cmdpal.timedate.notificationCenterBand",
|
||||
Resources.timedate_notification_center_band_title)
|
||||
{
|
||||
Icon = Icons.NotificationCenterIcon,
|
||||
};
|
||||
}
|
||||
|
||||
private string GetTranslatedPluginDescription()
|
||||
@@ -56,97 +90,20 @@ public sealed partial class TimeDateCommandsProvider : CommandProvider
|
||||
|
||||
public override ICommandItem[] GetDockBands()
|
||||
{
|
||||
var clockBand = new WrappedDockItem(
|
||||
[_bandItem],
|
||||
"com.microsoft.cmdpal.timedate.dockBand",
|
||||
Resources.Microsoft_plugin_timedate_dock_band_title)
|
||||
{
|
||||
Icon = Icons.TimeDateExtIcon,
|
||||
};
|
||||
return [_bandItem, _notificationCenterBandItem];
|
||||
}
|
||||
|
||||
var notificationBand = new WrappedDockItem(
|
||||
[_notificationCenterBandItem],
|
||||
"com.microsoft.cmdpal.timedate.notificationCenterBand",
|
||||
Resources.timedate_notification_center_band_title)
|
||||
{
|
||||
Icon = Icons.NotificationCenterIcon,
|
||||
};
|
||||
|
||||
return new ICommandItem[] { clockBand, notificationBand };
|
||||
public override void Dispose()
|
||||
{
|
||||
_nowDockBand?.Dispose();
|
||||
_nowDockBand = null;
|
||||
GC.SuppressFinalize(this);
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable SA1402 // File may only contain a single type
|
||||
|
||||
internal sealed partial class NowDockBand : ListItem
|
||||
{
|
||||
private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(60);
|
||||
private CopyTextCommand _copyTimeCommand;
|
||||
private CopyTextCommand _copyDateCommand;
|
||||
|
||||
public NowDockBand()
|
||||
{
|
||||
// Open Notification Center on click
|
||||
Command = new OpenUrlCommand("ms-actioncenter:") { Id = "com.microsoft.cmdpal.timedate.dockBand", Name = Resources.timedate_show_notification_center_command_name, Result = CommandResult.Dismiss(), Icon = null };
|
||||
_copyTimeCommand = new CopyTextCommand(string.Empty) { Name = Resources.timedate_copy_time_command_name };
|
||||
_copyDateCommand = new CopyTextCommand(string.Empty) { Name = Resources.timedate_copy_date_command_name };
|
||||
MoreCommands = [
|
||||
new CommandContextItem(_copyTimeCommand),
|
||||
new CommandContextItem(_copyDateCommand)
|
||||
];
|
||||
UpdateText();
|
||||
|
||||
// Create a timer to update the time every minute
|
||||
System.Timers.Timer timer = new(UpdateInterval.TotalMilliseconds);
|
||||
|
||||
// but we want it to tick on the minute, so calculate the initial delay
|
||||
var now = DateTime.Now;
|
||||
timer.Interval = UpdateInterval.TotalMilliseconds - ((now.Second * 1000) + now.Millisecond);
|
||||
|
||||
// then after the first tick, set it to 60 seconds
|
||||
timer.Elapsed += Timer_ElapsedFirst;
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
private void Timer_ElapsedFirst(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
// After the first tick, set the interval to 60 seconds
|
||||
if (sender is System.Timers.Timer timer)
|
||||
{
|
||||
timer.Interval = UpdateInterval.TotalMilliseconds;
|
||||
timer.Elapsed -= Timer_ElapsedFirst;
|
||||
timer.Elapsed += Timer_Elapsed;
|
||||
|
||||
// Still call the callback, so that we update the clock
|
||||
Timer_Elapsed(sender, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
private void UpdateText()
|
||||
{
|
||||
var timeExtended = false; // timeLongFormat ?? settings.TimeWithSecond;
|
||||
var dateExtended = false; // dateLongFormat ?? settings.DateWithWeekday;
|
||||
var dateTimeNow = DateTime.Now;
|
||||
|
||||
var timeString = dateTimeNow.ToString(
|
||||
TimeAndDateHelper.GetStringFormat(FormatStringType.Time, timeExtended, dateExtended),
|
||||
CultureInfo.CurrentCulture);
|
||||
var dateString = dateTimeNow.ToString(
|
||||
TimeAndDateHelper.GetStringFormat(FormatStringType.Date, timeExtended, dateExtended),
|
||||
CultureInfo.CurrentCulture);
|
||||
|
||||
Title = timeString;
|
||||
Subtitle = dateString;
|
||||
|
||||
_copyDateCommand.Text = dateString;
|
||||
_copyTimeCommand.Text = timeString;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed partial class NotificationCenterDockBand : ListItem
|
||||
{
|
||||
public NotificationCenterDockBand()
|
||||
|
||||
Reference in New Issue
Block a user