From ecb0b194d0b9554a4290eac3bbad66e8f86c2fb5 Mon Sep 17 00:00:00 2001 From: adnan-bhaldar Date: Sun, 1 Nov 2026 20:39:18 +0530 Subject: [PATCH 1/4] feat(ui): spin the arrow on Check now while checking for updates The Check now button on the About page only disabled itself while an update check ran, so a quick click looked like nothing happened. It now has a refresh arrow in front of the label that turns for as long as the check runs. It looks more professional and shows the app is working. The arrow is the drawn Icon.Refresh, not a text character, so it turns around its own center. A check can finish in a blink, so the result is held back for 900 ms to let the arrow make about one full turn. The turning is done in code by a new attached property, Spin.IsActive (Controls/Spin.cs), not by a XAML animation in Soft.axaml. A Style.Animations rule for the arrow in Soft.axaml stopped the ComboBox dropdown chevron from flipping, even though the rule only matched the check arrow. Spin.cs is in Controls because that folder holds Filee's own controls and helpers, while Soft.axaml is for Avalonia's own controls and is shared with the installer. With Reduce animations on (or the system asking for reduced motion) the arrow does not spin and the result is not held back. --- src/Filee.App/Controls/Spin.cs | 71 +++++++++++++++++++ .../ViewModels/Pages/AboutPageViewModel.cs | 12 +++- src/Filee.App/Views/Pages/AboutPage.axaml | 9 ++- 3 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 src/Filee.App/Controls/Spin.cs diff --git a/src/Filee.App/Controls/Spin.cs b/src/Filee.App/Controls/Spin.cs new file mode 100644 index 000000000..8742db833 --- /dev/null +++ b/src/Filee.App/Controls/Spin.cs @@ -0,0 +1,71 @@ +// Turns an icon while Spin.IsActive is true (About: Check now, Engines: Check again). It is done in code, not with a +// XAML animation: a Style.Animations on RotateTransform.Angle in Soft.axaml stopped the ComboBox chevron from flipping. +// With "Reduce animations" on the icon does not turn. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Threading; +using Filee.App.Services; + +namespace Filee.App.Controls; + +public static class Spin +{ + private const double TurnMilliseconds = 900; + + public static readonly AttachedProperty IsActiveProperty = + AvaloniaProperty.RegisterAttached("IsActive", typeof(Spin)); + + public static bool GetIsActive(Control control) => control.GetValue(IsActiveProperty); + public static void SetIsActive(Control control, bool value) => control.SetValue(IsActiveProperty, value); + + private sealed class State + { + public DispatcherTimer? Timer; + } + + private static readonly ConditionalWeakTable States = new(); + + static Spin() + { + IsActiveProperty.Changed.AddClassHandler((control, e) => + { + if (e.NewValue is true) + Start(control); + else + Stop(control); + }); + } + + private static void Start(Control control) + { + if (!Motion.Enabled) + return; + var state = States.GetOrCreateValue(control); + if (state.Timer is not null) + return; + + var clock = Stopwatch.StartNew(); + var timer = new DispatcherTimer(DispatcherPriority.Render) { Interval = TimeSpan.FromMilliseconds(16) }; + timer.Tick += (_, _) => + { + var angle = clock.Elapsed.TotalMilliseconds % TurnMilliseconds / TurnMilliseconds * 360; + control.RenderTransform = new RotateTransform(angle); // the origin is the middle of the icon + }; + state.Timer = timer; + control.DetachedFromVisualTree += (_, _) => Stop(control); + timer.Start(); + } + + private static void Stop(Control control) + { + if (!States.TryGetValue(control, out var state) || state.Timer is null) + return; + state.Timer.Stop(); + state.Timer = null; + control.RenderTransform = null; + } +} diff --git a/src/Filee.App/ViewModels/Pages/AboutPageViewModel.cs b/src/Filee.App/ViewModels/Pages/AboutPageViewModel.cs index ea9f3172f..ebf1b1037 100644 --- a/src/Filee.App/ViewModels/Pages/AboutPageViewModel.cs +++ b/src/Filee.App/ViewModels/Pages/AboutPageViewModel.cs @@ -65,8 +65,18 @@ private void OnUpdatesChanged(object? sender, System.ComponentModel.PropertyChan ShowUpdateState(_updates.LatestVersion); } + /// A check can finish in a blink; the arrow on "Check now" keeps turning for at least this long so it reads as checking. + private static readonly TimeSpan MinimumCheckTime = TimeSpan.FromMilliseconds(900); + [RelayCommand] - private async Task CheckNow() => ShowUpdateState(await _updates.CheckAsync(), afterCheck: true); + private async Task CheckNow() + { + // With "Reduce animations" on there is no arrow turning, so there is nothing to hold the result back for. + var minimumSpin = Task.Delay(Motion.Enabled ? MinimumCheckTime : TimeSpan.Zero); + var newer = await _updates.CheckAsync(); + await minimumSpin; + ShowUpdateState(newer, afterCheck: true); + } /// Downloads and runs the new installer, which updates Filee in place (a portable copy opens the download page). [RelayCommand] diff --git a/src/Filee.App/Views/Pages/AboutPage.axaml b/src/Filee.App/Views/Pages/AboutPage.axaml index c3c20eb6a..7b9e81468 100644 --- a/src/Filee.App/Views/Pages/AboutPage.axaml +++ b/src/Filee.App/Views/Pages/AboutPage.axaml @@ -62,7 +62,14 @@ - From 7f2a9c58d59bdc65038f0e025865456a462b5733 Mon Sep 17 00:00:00 2001 From: adnan-bhaldar Date: Sun, 1 Nov 2026 21:04:41 +0530 Subject: [PATCH 3/4] fix(ui): stop the toggle switch label growing on hover in Material You In the Material You theme the whole ToggleSwitch scaled up by 2% on hover. The switch's text label is part of the same control, so the label grew along with the track and knob, which looked like the text jumping. The hover scale (ToggleSwitch:pointerover with scale(1.02)) is removed. The track and knob keep their colour change on hover and the springy transition on the switch is unchanged. Only Soft.axaml changes, and only the Material You ToggleSwitch block. --- src/Filee.App/Styles/Soft.axaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Filee.App/Styles/Soft.axaml b/src/Filee.App/Styles/Soft.axaml index 93ee63d3d..59a5c0c80 100644 --- a/src/Filee.App/Styles/Soft.axaml +++ b/src/Filee.App/Styles/Soft.axaml @@ -427,7 +427,7 @@ - + -