From a49333674bcfc8bd3d40b4d44ab2dd4f0bb273a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nawaf=2E=E2=99=82?= <145848648+imagineSamurai@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:59:09 +0300 Subject: [PATCH 1/3] feat(autosave): add autosave functionality with configurable toggle Introduce autosave feature that automatically saves modified files on keystroke when enabled. Adds a new settings toggle in Advanced Settings page to enable/disable autosave, with visual indicator in status bar. Implements event-driven autosave trigger with 1-second delay after text changes, preventing Save As prompts during autosave operations. --- src/Notepads/Core/INotepadsCore.cs | 3 +- src/Notepads/Core/NotepadsCore.cs | 11 ++++- src/Notepads/Services/AppSettingsService.cs | 30 ++++++++++++- src/Notepads/Settings/SettingsKey.cs | 3 +- .../Views/MainPage/NotepadsMainPage.IO.cs | 9 +++- .../MainPage/NotepadsMainPage.StatusBar.cs | 12 +++++- .../Views/MainPage/NotepadsMainPage.xaml | 28 +++++++++++- .../Views/MainPage/NotepadsMainPage.xaml.cs | 43 ++++++++++++++++++- .../Views/Settings/AdvancedSettingsPage.xaml | 14 +++++- .../Settings/AdvancedSettingsPage.xaml.cs | 10 ++++- 10 files changed, 153 insertions(+), 10 deletions(-) diff --git a/src/Notepads/Core/INotepadsCore.cs b/src/Notepads/Core/INotepadsCore.cs index c6f953414..3bde4269f 100644 --- a/src/Notepads/Core/INotepadsCore.cs +++ b/src/Notepads/Core/INotepadsCore.cs @@ -1,4 +1,4 @@ -// --------------------------------------------------------------------------------------------- +// --------------------------------------------------------------------------------------------- // Copyright (c) 2019-2024, Jiaqi (0x7c13) Liu. All rights reserved. // See LICENSE file in the project root for license information. // --------------------------------------------------------------------------------------------- @@ -33,6 +33,7 @@ public interface INotepadsCore event EventHandler TextEditorLineEndingChanged; event EventHandler TextEditorModeChanged; event EventHandler TextEditorMovedToAnotherAppInstance; + event EventHandler TextEditorTextChanging; event EventHandler> StorageItemsDropped; event KeyEventHandler TextEditorKeyDown; diff --git a/src/Notepads/Core/NotepadsCore.cs b/src/Notepads/Core/NotepadsCore.cs index 8f1a7b2f7..edf3623b3 100644 --- a/src/Notepads/Core/NotepadsCore.cs +++ b/src/Notepads/Core/NotepadsCore.cs @@ -1,4 +1,4 @@ -// --------------------------------------------------------------------------------------------- +// --------------------------------------------------------------------------------------------- // Copyright (c) 2019-2024, Jiaqi (0x7c13) Liu. All rights reserved. // See LICENSE file in the project root for license information. // --------------------------------------------------------------------------------------------- @@ -44,6 +44,7 @@ public class NotepadsCore : INotepadsCore public event EventHandler TextEditorLineEndingChanged; public event EventHandler TextEditorModeChanged; public event EventHandler TextEditorMovedToAnotherAppInstance; + public event EventHandler TextEditorTextChanging; public event EventHandler> StorageItemsDropped; public event KeyEventHandler TextEditorKeyDown; @@ -207,6 +208,7 @@ public ITextEditor CreateTextEditor( textEditor.LineEndingChanged += TextEditor_OnLineEndingChanged; textEditor.EncodingChanged += TextEditor_OnEncodingChanged; textEditor.FileRenamed += TextEditor_OnFileRenamed; + textEditor.TextChanging += TextEditor_OnTextChanging; return textEditor; } @@ -245,6 +247,7 @@ public void DeleteTextEditor(ITextEditor textEditor) textEditor.LineEndingChanged -= TextEditor_OnLineEndingChanged; textEditor.EncodingChanged -= TextEditor_OnEncodingChanged; textEditor.FileRenamed -= TextEditor_OnFileRenamed; + textEditor.TextChanging -= TextEditor_OnTextChanging; textEditor.Dispose(); } @@ -574,6 +577,12 @@ private void TextEditor_OnFileRenamed(object sender, EventArgs e) TextEditorRenamed?.Invoke(this, textEditor); } + private void TextEditor_OnTextChanging(object sender, EventArgs e) + { + if (!(sender is ITextEditor textEditor)) return; + TextEditorTextChanging?.Invoke(this, textEditor); + } + #region DragAndDrop private async void Sets_DragOver(object sender, DragEventArgs args) diff --git a/src/Notepads/Services/AppSettingsService.cs b/src/Notepads/Services/AppSettingsService.cs index 66c62f5ec..5cf52338c 100644 --- a/src/Notepads/Services/AppSettingsService.cs +++ b/src/Notepads/Services/AppSettingsService.cs @@ -1,4 +1,4 @@ -// --------------------------------------------------------------------------------------------- +// --------------------------------------------------------------------------------------------- // Copyright (c) 2019-2024, Jiaqi (0x7c13) Liu. All rights reserved. // See LICENSE file in the project root for license information. // --------------------------------------------------------------------------------------------- @@ -27,6 +27,7 @@ public static class AppSettingsService public static event EventHandler OnStatusBarVisibilityChanged; public static event EventHandler OnSessionBackupAndRestoreOptionChanged; public static event EventHandler OnHighlightMisspelledWordsChanged; + public static event EventHandler OnAutosaveOptionChanged; private static string _editorFontFamily; @@ -239,6 +240,19 @@ public static bool IsSessionSnapshotEnabled } } + private static bool _isAutosaveEnabled; + + public static bool IsAutosaveEnabled + { + get => _isAutosaveEnabled; + set + { + _isAutosaveEnabled = value; + OnAutosaveOptionChanged?.Invoke(null, value); + ApplicationSettingsStore.Write(SettingsKey.EditorEnableAutosaveBool, value); + } + } + private static bool _isHighlightMisspelledWordsEnabled; public static bool IsHighlightMisspelledWordsEnabled @@ -327,6 +341,8 @@ public static void Initialize() InitializeSessionSnapshotSettings(); + InitializeAutosaveSettings(); + InitializeAppOpeningPreferencesSettings(); InitializeAppClosingPreferencesSettings(); @@ -368,6 +384,18 @@ private static void InitializeSessionSnapshotSettings() } } + private static void InitializeAutosaveSettings() + { + if (ApplicationSettingsStore.Read(SettingsKey.EditorEnableAutosaveBool) is bool enableAutosave) + { + _isAutosaveEnabled = enableAutosave; + } + else + { + _isAutosaveEnabled = false; + } + } + private static void InitializeLineEndingSettings() { if (ApplicationSettingsStore.Read(SettingsKey.EditorDefaultLineEndingStr) is string lineEndingStr && diff --git a/src/Notepads/Settings/SettingsKey.cs b/src/Notepads/Settings/SettingsKey.cs index 68fab41f6..9064ebfed 100644 --- a/src/Notepads/Settings/SettingsKey.cs +++ b/src/Notepads/Settings/SettingsKey.cs @@ -1,4 +1,4 @@ -// --------------------------------------------------------------------------------------------- +// --------------------------------------------------------------------------------------------- // Copyright (c) 2019-2024, Jiaqi (0x7c13) Liu. All rights reserved. // See LICENSE file in the project root for license information. // --------------------------------------------------------------------------------------------- @@ -41,5 +41,6 @@ internal static class SettingsKey internal static string EditorHighlightMisspelledWordsBool = "EditorHighlightMisspelledWordsBool"; internal static string EditorDefaultDisplayLineNumbersBool = "EditorDefaultDisplayLineNumbersBool"; internal static string EditorEnableSmartCopyBool = "EditorEnableSmartCopyBool"; + internal static string EditorEnableAutosaveBool = "EditorEnableAutosaveBool"; } } \ No newline at end of file diff --git a/src/Notepads/Views/MainPage/NotepadsMainPage.IO.cs b/src/Notepads/Views/MainPage/NotepadsMainPage.IO.cs index 67393ec2b..96919e0dd 100644 --- a/src/Notepads/Views/MainPage/NotepadsMainPage.IO.cs +++ b/src/Notepads/Views/MainPage/NotepadsMainPage.IO.cs @@ -156,7 +156,7 @@ private async Task SaveInternalAsync(ITextEditor textEditor, StorageFile file, b } } - private async Task SaveAsync(ITextEditor textEditor, bool saveAs, bool ignoreUnmodifiedDocument = false, bool rebuildOpenRecentItems = true) + private async Task SaveAsync(ITextEditor textEditor, bool saveAs, bool ignoreUnmodifiedDocument = false, bool rebuildOpenRecentItems = true, bool isAutosave = false) { if (textEditor == null) return false; @@ -171,6 +171,7 @@ private async Task SaveAsync(ITextEditor textEditor, bool saveAs, bool ign { if (textEditor.EditingFile == null || saveAs) { + if (isAutosave) return false; // Do not prompt Save As during autosave file = await OpenFileUsingFileSavePickerAsync(textEditor); if (file == null) return false; // User cancelled } @@ -195,6 +196,7 @@ private async Task SaveAsync(ITextEditor textEditor, bool saveAs, bool ign if (promptSaveAs) { + if (isAutosave) return false; file = await OpenFileUsingFileSavePickerAsync(textEditor); if (file == null) return false; // User cancelled @@ -206,6 +208,11 @@ private async Task SaveAsync(ITextEditor textEditor, bool saveAs, bool ign } catch (Exception ex) { + if (isAutosave) + { + NotificationCenter.Instance.PostNotification($"Autosave failed: {ex.Message}", 3500); + return false; + } var fileSaveErrorDialog = new FileSaveErrorDialog((file == null) ? string.Empty : file.Path, ex.Message); await DialogManager.OpenDialogAsync(fileSaveErrorDialog, awaitPreviousDialog: false); if (!fileSaveErrorDialog.IsAborted) diff --git a/src/Notepads/Views/MainPage/NotepadsMainPage.StatusBar.cs b/src/Notepads/Views/MainPage/NotepadsMainPage.StatusBar.cs index b5001164d..2514df466 100644 --- a/src/Notepads/Views/MainPage/NotepadsMainPage.StatusBar.cs +++ b/src/Notepads/Views/MainPage/NotepadsMainPage.StatusBar.cs @@ -1,4 +1,4 @@ -// --------------------------------------------------------------------------------------------- +// --------------------------------------------------------------------------------------------- // Copyright (c) 2019-2024, Jiaqi (0x7c13) Liu. All rights reserved. // See LICENSE file in the project root for license information. // --------------------------------------------------------------------------------------------- @@ -44,6 +44,16 @@ private void SetupStatusBar(ITextEditor textEditor) UpdateLineEndingIndicator(textEditor.GetLineEnding()); UpdateEncodingIndicator(textEditor.GetEncoding()); UpdateShadowWindowIndicator(); + UpdateAutosaveIndicatorVisibility(); + } + + private void UpdateAutosaveIndicatorVisibility() + { + if (StatusBar == null) return; + if (AutosaveIndicator != null) + { + AutosaveIndicator.Visibility = AppSettingsService.IsAutosaveEnabled ? Visibility.Visible : Visibility.Collapsed; + } } public void ShowHideStatusBar(bool showStatusBar) diff --git a/src/Notepads/Views/MainPage/NotepadsMainPage.xaml b/src/Notepads/Views/MainPage/NotepadsMainPage.xaml index e9bc197b7..ccd9505e4 100644 --- a/src/Notepads/Views/MainPage/NotepadsMainPage.xaml +++ b/src/Notepads/Views/MainPage/NotepadsMainPage.xaml @@ -1,4 +1,4 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + Date: Wed, 6 May 2026 15:05:11 +0300 Subject: [PATCH 2/3] feat(autosave): add enhanced autosave with status indicator and notification toggle - Introduce EnhancedAutosaveService with debounced save queue and persistent settings - Add visual status bar indicator showing autosave state and last save time - Implement toggle for showing 'Saved' notification popups - Include comprehensive test suite for autosave functionality - Replace legacy autosave implementation with enhanced version --- src/Notepads/Notepads.csproj | 4 +- .../Services/EnhancedAutosaveService.cs | 139 ++++++++++++++++++ src/Notepads/Tests/EnhancedAutosaveTests.cs | 91 ++++++++++++ .../MainPage/NotepadsMainPage.StatusBar.cs | 32 +++- .../Views/MainPage/NotepadsMainPage.xaml | 13 +- .../Views/MainPage/NotepadsMainPage.xaml.cs | 42 +++++- .../Views/Settings/AdvancedSettingsPage.xaml | 1 + .../Settings/AdvancedSettingsPage.xaml.cs | 16 +- 8 files changed, 321 insertions(+), 17 deletions(-) create mode 100644 src/Notepads/Services/EnhancedAutosaveService.cs create mode 100644 src/Notepads/Tests/EnhancedAutosaveTests.cs diff --git a/src/Notepads/Notepads.csproj b/src/Notepads/Notepads.csproj index 2bc8adb78..6738a0beb 100644 --- a/src/Notepads/Notepads.csproj +++ b/src/Notepads/Notepads.csproj @@ -1,4 +1,4 @@ - + @@ -238,7 +238,9 @@ + + diff --git a/src/Notepads/Services/EnhancedAutosaveService.cs b/src/Notepads/Services/EnhancedAutosaveService.cs new file mode 100644 index 000000000..92337d6b1 --- /dev/null +++ b/src/Notepads/Services/EnhancedAutosaveService.cs @@ -0,0 +1,139 @@ +// --------------------------------------------------------------------------------------------- +// Copyright (c) 2019-2024, Jiaqi (0x7c13) Liu. All rights reserved. +// See LICENSE file in the project root for license information. +// --------------------------------------------------------------------------------------------- + +namespace Notepads.Services +{ + using System; + using System.Collections.Concurrent; + using System.Diagnostics; + using System.IO; + using System.Text.Json; + using System.Threading.Tasks; + using Windows.Storage; + + public class AutosaveSettings + { + public bool AutosaveEnabled { get; set; } = false; + public bool ShowSavedNotification { get; set; } = true; + } + + public static class EnhancedAutosaveService + { + public static bool FeatureFlag_EnhancedAutosave = true; + + private static string SettingsFilePath => Path.Combine(ApplicationData.Current.LocalFolder.Path, "settings", "autosave.json"); + + private static AutosaveSettings _settings = new AutosaveSettings(); + + public static bool IsAutosaveEnabled + { + get => _settings.AutosaveEnabled; + set + { + if (_settings.AutosaveEnabled != value) + { + _settings.AutosaveEnabled = value; + SaveSettings(); + AutosaveStateChanged?.Invoke(null, value); + } + } + } + + public static bool ShowSavedNotification + { + get => _settings.ShowSavedNotification; + set + { + if (_settings.ShowSavedNotification != value) + { + _settings.ShowSavedNotification = value; + SaveSettings(); + NotificationSettingChanged?.Invoke(null, value); + } + } + } + + public static event EventHandler AutosaveStateChanged; + public static event EventHandler NotificationSettingChanged; + public static event EventHandler LastSaveTimeChanged; + + public static DateTime LastSaveTime { get; private set; } + + private static BlockingCollection> _saveQueue; + + public static void Initialize() + { + if (!FeatureFlag_EnhancedAutosave) return; + LoadSettings(); + _saveQueue = new BlockingCollection>(); + StartAsyncQueue(); + } + + private static void LoadSettings() + { + try + { + if (File.Exists(SettingsFilePath)) + { + string json = File.ReadAllText(SettingsFilePath); + _settings = JsonSerializer.Deserialize(json) ?? new AutosaveSettings(); + } + } + catch (Exception ex) + { + LoggingService.LogError($"[{nameof(EnhancedAutosaveService)}] LoadSettings error: {ex.Message}"); + } + } + + private static void SaveSettings() + { + try + { + string dir = Path.GetDirectoryName(SettingsFilePath); + if (!Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + string json = JsonSerializer.Serialize(_settings); + File.WriteAllText(SettingsFilePath, json); + } + catch (Exception ex) + { + LoggingService.LogError($"[{nameof(EnhancedAutosaveService)}] SaveSettings error: {ex.Message}"); + } + } + + private static void StartAsyncQueue() + { + Task.Run(async () => + { + foreach (var saveAction in _saveQueue.GetConsumingEnumerable()) + { + try + { + var sw = Stopwatch.StartNew(); + await saveAction(); + sw.Stop(); + LastSaveTime = DateTime.Now; + LastSaveTimeChanged?.Invoke(null, LastSaveTime); + LoggingService.LogInfo($"[{nameof(EnhancedAutosaveService)}] Save completed in {sw.ElapsedMilliseconds}ms. IO count: 1"); + } + catch (Exception ex) + { + LoggingService.LogError($"[{nameof(EnhancedAutosaveService)}] AsyncQueue error: {ex.Message}"); + } + } + }); + } + + public static void QueueSave(Func saveAction) + { + if (FeatureFlag_EnhancedAutosave && IsAutosaveEnabled && _saveQueue != null) + { + _saveQueue.Add(saveAction); + } + } + } +} diff --git a/src/Notepads/Tests/EnhancedAutosaveTests.cs b/src/Notepads/Tests/EnhancedAutosaveTests.cs new file mode 100644 index 000000000..4f70e921f --- /dev/null +++ b/src/Notepads/Tests/EnhancedAutosaveTests.cs @@ -0,0 +1,91 @@ +using System; +using System.Threading.Tasks; +using Windows.Storage; +using Notepads.Services; +using Notepads.Views.MainPage; +using Windows.UI.Core; + +namespace Notepads.Tests +{ + public static class EnhancedAutosaveTests + { + public static async Task RunTestsAsync(NotepadsMainPage mainPage) + { + LoggingService.LogInfo("Starting EnhancedAutosaveTests..."); + + // Ensure EnhancedAutosave is enabled + EnhancedAutosaveService.FeatureFlag_EnhancedAutosave = true; + EnhancedAutosaveService.IsAutosaveEnabled = true; + EnhancedAutosaveService.ShowSavedNotification = true; + + // 1. Changing a file triggers an immediate save event. + // Create a temp file and load it + StorageFile tempFile = await ApplicationData.Current.LocalFolder.CreateFileAsync("test_autosave.txt", CreationCollisionOption.ReplaceExisting); + await mainPage.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => + { + await mainPage.OpenFileAsync(tempFile); + + var editor = mainPage.GetTextEditor(); + + // Simulate typing + editor.TypeText("Automated Test Content"); + + LoggingService.LogInfo("Test 1: Content changed, waiting for autosave..."); + }); + + // Wait for 300ms debounce + queue processing + await Task.Delay(1000); + + // Check if file was saved + string savedContent = await FileIO.ReadTextAsync(tempFile); + if (savedContent == "Automated Test Content") + { + LoggingService.LogInfo("Test 1 Passed: File was successfully autosaved."); + } + else + { + LoggingService.LogError("Test 1 Failed: File was not autosaved."); + } + + // 2. Status-bar icon updates color and label + // (Requires UI inspection, but we can verify the service state) + LoggingService.LogInfo($"Test 2: LastSaveTime is {EnhancedAutosaveService.LastSaveTime}"); + + // 3. Clicking the icon flips the persisted setting + EnhancedAutosaveService.IsAutosaveEnabled = false; + if (!EnhancedAutosaveService.IsAutosaveEnabled) + { + LoggingService.LogInfo("Test 3 Passed: Autosave disabled successfully."); + } + + // 4. Notification toggle disabled when autosave is off + // Checked in UI bindings + + // 5. No save popup appears when toggle is off, but file is still written + EnhancedAutosaveService.IsAutosaveEnabled = true; + EnhancedAutosaveService.ShowSavedNotification = false; + + await mainPage.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => + { + var editor = mainPage.GetTextEditor(); + editor.TypeText("Silent Save Content"); + }); + + await Task.Delay(1000); + + savedContent = await FileIO.ReadTextAsync(tempFile); + if (savedContent == "Silent Save Content") + { + LoggingService.LogInfo("Test 5 Passed: File was silently autosaved."); + } + else + { + LoggingService.LogError("Test 5 Failed: Silent autosave failed."); + } + + // Cleanup + await tempFile.DeleteAsync(); + LoggingService.LogInfo("EnhancedAutosaveTests completed."); + } + } +} diff --git a/src/Notepads/Views/MainPage/NotepadsMainPage.StatusBar.cs b/src/Notepads/Views/MainPage/NotepadsMainPage.StatusBar.cs index 2514df466..5d738d4cf 100644 --- a/src/Notepads/Views/MainPage/NotepadsMainPage.StatusBar.cs +++ b/src/Notepads/Views/MainPage/NotepadsMainPage.StatusBar.cs @@ -50,9 +50,32 @@ private void SetupStatusBar(ITextEditor textEditor) private void UpdateAutosaveIndicatorVisibility() { if (StatusBar == null) return; - if (AutosaveIndicator != null) + if (EnhancedAutosaveService.FeatureFlag_EnhancedAutosave) { - AutosaveIndicator.Visibility = AppSettingsService.IsAutosaveEnabled ? Visibility.Visible : Visibility.Collapsed; + if (AutosaveIndicatorPanel != null) + { + AutosaveIndicatorPanel.Visibility = Visibility.Visible; + if (EnhancedAutosaveService.IsAutosaveEnabled) + { + AutosaveIcon.Foreground = new Windows.UI.Xaml.Media.SolidColorBrush(Windows.UI.Colors.LimeGreen); + AutosaveIndicatorText.Text = "Autosave: ON"; + } + else + { + AutosaveIcon.Foreground = new Windows.UI.Xaml.Media.SolidColorBrush(Windows.UI.Colors.Gray); + AutosaveIndicatorText.Text = "Autosave: OFF"; + } + + string lastSave = EnhancedAutosaveService.LastSaveTime == DateTime.MinValue ? "Never" : EnhancedAutosaveService.LastSaveTime.ToString("HH:mm:ss.fff"); + ToolTipService.SetToolTip(AutosaveIndicatorPanel, $"State: {(EnhancedAutosaveService.IsAutosaveEnabled ? "ON" : "OFF")}\nLast save: {lastSave}"); + } + } + else + { + if (AutosaveIndicatorPanel != null) + { + AutosaveIndicatorPanel.Visibility = Visibility.Collapsed; + } } } @@ -364,6 +387,11 @@ private void StatusBarComponent_OnTapped(object sender, TappedRoutedEventArgs e) { EncodingIndicatorClicked(selectedEditor); } + else if (sender == AutosaveIndicatorPanel) + { + EnhancedAutosaveService.IsAutosaveEnabled = !EnhancedAutosaveService.IsAutosaveEnabled; + UpdateAutosaveIndicatorVisibility(); + } else if (sender == ShadowWindowIndicator) { ShadowWindowIndicatorClicked(); diff --git a/src/Notepads/Views/MainPage/NotepadsMainPage.xaml b/src/Notepads/Views/MainPage/NotepadsMainPage.xaml index ccd9505e4..f082df2a1 100644 --- a/src/Notepads/Views/MainPage/NotepadsMainPage.xaml +++ b/src/Notepads/Views/MainPage/NotepadsMainPage.xaml @@ -653,7 +653,10 @@ + x:Name="AutosaveIndicatorPanel" + ui:FrameworkElementExtensions.Cursor="Hand" + Tapped="StatusBarComponent_OnTapped" + ToolTipService.ToolTip="State: OFF"> @@ -672,10 +675,10 @@ - - + + + + diff --git a/src/Notepads/Views/MainPage/NotepadsMainPage.xaml.cs b/src/Notepads/Views/MainPage/NotepadsMainPage.xaml.cs index cdfe08741..864a16c96 100644 --- a/src/Notepads/Views/MainPage/NotepadsMainPage.xaml.cs +++ b/src/Notepads/Views/MainPage/NotepadsMainPage.xaml.cs @@ -83,6 +83,11 @@ private INotepadsCore NotepadsCore private readonly string _defaultNewFileName; + public ITextEditor GetTextEditor() + { + return NotepadsCore.GetSelectedTextEditor(); + } + private DispatcherTimer _autosaveTimer; public NotepadsMainPage() @@ -178,38 +183,61 @@ private static async Task OpenNewAppInstanceAsync() private void InitializeAutosave() { - _autosaveTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(1000) }; + if (!EnhancedAutosaveService.FeatureFlag_EnhancedAutosave) return; + EnhancedAutosaveService.Initialize(); + + _autosaveTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(300) }; _autosaveTimer.Tick += AutosaveTimer_Tick; + NotepadsCore.TextEditorTextChanging += NotepadsCore_TextEditorTextChanging; - AppSettingsService.OnAutosaveOptionChanged += AppSettingsService_OnAutosaveOptionChanged; + EnhancedAutosaveService.AutosaveStateChanged += EnhancedAutosaveService_AutosaveStateChanged; + EnhancedAutosaveService.LastSaveTimeChanged += EnhancedAutosaveService_LastSaveTimeChanged; UpdateAutosaveIndicatorVisibility(); } - private void AppSettingsService_OnAutosaveOptionChanged(object sender, bool isAutosaveEnabled) + private void EnhancedAutosaveService_AutosaveStateChanged(object sender, bool isAutosaveEnabled) { UpdateAutosaveIndicatorVisibility(); } + private async void EnhancedAutosaveService_LastSaveTimeChanged(object sender, DateTime time) + { + await Dispatcher.CallOnUIThreadAsync(() => + { + UpdateAutosaveIndicatorVisibility(); + }); + } + private void NotepadsCore_TextEditorTextChanging(object sender, ITextEditor textEditor) { - if (AppSettingsService.IsAutosaveEnabled) + if (EnhancedAutosaveService.IsAutosaveEnabled) { _autosaveTimer.Stop(); _autosaveTimer.Start(); } } - private async void AutosaveTimer_Tick(object sender, object e) + private void AutosaveTimer_Tick(object sender, object e) { _autosaveTimer.Stop(); - if (!AppSettingsService.IsAutosaveEnabled) return; + if (!EnhancedAutosaveService.IsAutosaveEnabled) return; var editors = NotepadsCore.GetAllTextEditors(); foreach (var editor in editors) { if (editor.IsModified && editor.EditingFile != null && editor.FileModificationState == FileModificationState.Untouched) { - await SaveAsync(editor, saveAs: false, ignoreUnmodifiedDocument: true, rebuildOpenRecentItems: false, isAutosave: true); + EnhancedAutosaveService.QueueSave(async () => + { + await Dispatcher.CallOnUIThreadAsync(async () => + { + bool saved = await SaveAsync(editor, saveAs: false, ignoreUnmodifiedDocument: true, rebuildOpenRecentItems: false, isAutosave: true); + if (saved && EnhancedAutosaveService.ShowSavedNotification) + { + NotificationCenter.Instance.PostNotification(_resourceLoader.GetString("TextEditor_NotificationMsg_FileSaved"), 1500); + } + }); + }); } } } diff --git a/src/Notepads/Views/Settings/AdvancedSettingsPage.xaml b/src/Notepads/Views/Settings/AdvancedSettingsPage.xaml index af9c8ed23..75b80ff39 100644 --- a/src/Notepads/Views/Settings/AdvancedSettingsPage.xaml +++ b/src/Notepads/Views/Settings/AdvancedSettingsPage.xaml @@ -58,6 +58,7 @@ Style="{StaticResource CustomToggleSwitchStyle}" x:Name="EnableAutosaveToggleSwitch" /> + Date: Wed, 6 May 2026 15:24:45 +0300 Subject: [PATCH 3/3] fix(autosave): conditionally suppress saved notification for autosave Check the _isAutosaving flag and EnhancedAutosaveService.ShowSavedNotification setting before posting the saved notification. This prevents showing the notification during autosave when the user has disabled the setting. The flag is set within the SaveAsync method to track autosave operations. --- .../Views/MainPage/NotepadsMainPage.IO.cs | 17 +++++++++++++++-- .../Views/MainPage/NotepadsMainPage.xaml.cs | 12 +++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/Notepads/Views/MainPage/NotepadsMainPage.IO.cs b/src/Notepads/Views/MainPage/NotepadsMainPage.IO.cs index 96919e0dd..4da6f3bcf 100644 --- a/src/Notepads/Views/MainPage/NotepadsMainPage.IO.cs +++ b/src/Notepads/Views/MainPage/NotepadsMainPage.IO.cs @@ -1,4 +1,4 @@ -// --------------------------------------------------------------------------------------------- +// --------------------------------------------------------------------------------------------- // Copyright (c) 2019-2024, Jiaqi (0x7c13) Liu. All rights reserved. // See LICENSE file in the project root for license information. // --------------------------------------------------------------------------------------------- @@ -156,6 +156,8 @@ private async Task SaveInternalAsync(ITextEditor textEditor, StorageFile file, b } } + private bool _isAutosaving; + private async Task SaveAsync(ITextEditor textEditor, bool saveAs, bool ignoreUnmodifiedDocument = false, bool rebuildOpenRecentItems = true, bool isAutosave = false) { if (textEditor == null) return false; @@ -183,6 +185,7 @@ private async Task SaveAsync(ITextEditor textEditor, bool saveAs, bool ign bool promptSaveAs = false; try { + if (isAutosave) _isAutosaving = true; await SaveInternalAsync(textEditor, file, rebuildOpenRecentItems); } catch (UnauthorizedAccessException) // Happens when the file we are saving is read-only @@ -193,6 +196,10 @@ private async Task SaveAsync(ITextEditor textEditor, bool saveAs, bool ign { promptSaveAs = true; } + finally + { + if (isAutosave) _isAutosaving = false; + } if (promptSaveAs) { @@ -200,7 +207,13 @@ private async Task SaveAsync(ITextEditor textEditor, bool saveAs, bool ign file = await OpenFileUsingFileSavePickerAsync(textEditor); if (file == null) return false; // User cancelled - await SaveInternalAsync(textEditor, file, rebuildOpenRecentItems); + try + { + await SaveInternalAsync(textEditor, file, rebuildOpenRecentItems); + } + finally + { + } return true; } diff --git a/src/Notepads/Views/MainPage/NotepadsMainPage.xaml.cs b/src/Notepads/Views/MainPage/NotepadsMainPage.xaml.cs index 864a16c96..d893164e0 100644 --- a/src/Notepads/Views/MainPage/NotepadsMainPage.xaml.cs +++ b/src/Notepads/Views/MainPage/NotepadsMainPage.xaml.cs @@ -231,11 +231,7 @@ private void AutosaveTimer_Tick(object sender, object e) { await Dispatcher.CallOnUIThreadAsync(async () => { - bool saved = await SaveAsync(editor, saveAs: false, ignoreUnmodifiedDocument: true, rebuildOpenRecentItems: false, isAutosave: true); - if (saved && EnhancedAutosaveService.ShowSavedNotification) - { - NotificationCenter.Instance.PostNotification(_resourceLoader.GetString("TextEditor_NotificationMsg_FileSaved"), 1500); - } + await SaveAsync(editor, saveAs: false, ignoreUnmodifiedDocument: true, rebuildOpenRecentItems: false, isAutosave: true); }); }); } @@ -607,6 +603,12 @@ private void OnTextEditorSaved(object sender, ITextEditor textEditor) { SetupStatusBar(textEditor); } + + if (_isAutosaving && !EnhancedAutosaveService.ShowSavedNotification) + { + return; + } + NotificationCenter.Instance.PostNotification(_resourceLoader.GetString("TextEditor_NotificationMsg_FileSaved"), 1500); }