You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Security
- Migrate WebDAV credentials to EncryptedSharedPreferences (Tink/AES-256)
Sync & Network
- Fix phantom 'Untitled' notes from WebDAV root scan
- Map HTTP 401 during directory ensure to auth error
- Add reachability check (HTTP HEAD) before sync
- Improve MKCOL 404 handling and WebDAV validation
- Detect and recover stale sync state via timestamps
- Trigger onSave sync after note deletion from editor
- Move file I/O off the main thread; mutex-protect deletions
Battery & Offline
- Restore battery optimization prompt (shown when disabling offline mode)
Backup
- Allow title-less notes in backup validation (v2.2.0 compat)
Widget
- Truncate content to prevent TransactionTooLargeException
- Align checklist sort logic with editor for all sort options
- Localize empty state and add tap-to-reconfigure action
- Extract shared WidgetUpdateHelper utility
Editor & UI
- Prevent flash of wrong editor state on async note load
- Preserve list scroll position when returning from editor
- Migrate all Toast messages to Material 3 Snackbar system
- Trigger auto-save on sort option change and update widgets
Code Quality
- Audit and restructure R8/ProGuard keep rules
- Migrate android.util.Log → project Logger (+ 18 silent catch blocks)
- Migrate editor VM var properties to StateFlow
- Add in-memory cache to loadAllNotes (2 s TTL)
- Resolve deprecated API warnings and lint issues
- Extract dimension tokens, constants, and helper utilities
Full changelog: CHANGELOG.md
Copy file name to clipboardExpand all lines: CHANGELOG.md
+170Lines changed: 170 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,6 +8,176 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
8
8
9
9
---
10
10
11
+
## [2.3.0] - 2026-04-18
12
+
13
+
### 🛡️ Security
14
+
15
+
**WebDAV Credentials Now Stored Encrypted** ([bf117f8](https://github.com/inventory69/simple-notes-sync/commit/bf117f8))
16
+
- WebDAV username and password were previously stored as plaintext in regular SharedPreferences
17
+
- Migrated to `EncryptedSharedPreferences` with AES256-GCM encryption
18
+
- One-time auto-migration on first start: existing credentials are moved to the encrypted store and removed from the plaintext store
19
+
20
+
### ✨ New Features
21
+
22
+
**Battery Optimization Prompt on Sync Enable & Migration** ([da2ab36](https://github.com/inventory69/simple-notes-sync/commit/da2ab36))
23
+
- When the user disables offline mode, the app immediately checks battery optimization exemption and shows the system dialog if needed
24
+
- One-time migration for existing users: users who already have sync enabled but were never prompted see the dialog once on next app start
25
+
- Uses new SharedPreferences key `battery_opt_migration_shown`
26
+
27
+
### 🐛 Bug Fixes
28
+
29
+
**Fix Markdown Auto-Sync Not Firing on Save** ([1756af4](https://github.com/inventory69/simple-notes-sync/commit/1756af4))
30
+
- SharedPreferences for markdown export/auto-import were only persisted after a successful initial export — if it failed (HTTP 405, timeout, network error), the prefs were never set and on-save export never fired
31
+
- Prefs are now persisted immediately after server config validation; initial export is best-effort
32
+
- HTTP 405 fallback added to `ensureMarkdownDirExists()` (list-after-failed-exists pattern)
33
+
- Thanks to [@minosimo](https://github.com/minosimo) for the detailed bug report and logs! ([#50](https://github.com/inventory69/simple-notes-sync/issues/50))
34
+
35
+
**Improve MKCOL 404 Handling and WebDAV Validation** ([8c5907a](https://github.com/inventory69/simple-notes-sync/commit/8c5907a))
36
+
-`SafeSardineWrapper.createDirectory()`: handle 404 with `list()` fallback (analogous to existing 405 handling)
37
+
-`WebDavSyncService.testConnection()`: verify WebDAV capability via PROPFIND after HEAD check to prevent false "Reachable" status
38
+
-`SyncExceptionMapper`: detect MKCOL failures and show user-friendly message with WebDAV URL hint
39
+
- Thanks to [@Ichigo-Meow](https://github.com/Ichigo-Meow) for reporting! ([#55](https://github.com/inventory69/simple-notes-sync/issues/55))
40
+
41
+
**Stop Phantom "Untitled" Notes from WebDAV Root Scan** ([fab23eb](https://github.com/inventory69/simple-notes-sync/commit/fab23eb))
42
+
- Foreign JSON files in the WebDAV root (e.g. `info.json`) were parsed on every sync, producing a fresh "Untitled" ghost note with a random UUID — saved as SYNCED, then immediately flagged DELETED_ON_SERVER, accumulating endlessly
43
+
- Disabled the legacy v1.2.0 root fallback in the normal sync path; the migration scan still runs in `restoreFromServer()`
44
+
- Added UUID-format and id-vs-filename guards in `NoteDownloader` Phase 2 as defense-in-depth
45
+
- Thanks to [@angeld-jr2](https://github.com/angeld-jr2) for the detailed debug log that made this diagnosable! ([#62](https://github.com/inventory69/simple-notes-sync/issues/62))
46
+
47
+
**Allow Title-less Notes in Backup Validation** ([d41d02b](https://github.com/inventory69/simple-notes-sync/commit/d41d02b))
48
+
- Backup restore rejected v2.2.0 backups containing notes with an empty title (e.g. checklists)
49
+
-`validateBackup()` now matches the editor: a note is only invalid when both title AND content/checklist-items are blank
50
+
- Thanks to [@angeld-jr2](https://github.com/angeld-jr2) for reporting!
51
+
52
+
**Map HTTP 401 During Directory Ensure to Auth Error** ([02c3f77](https://github.com/inventory69/simple-notes-sync/commit/02c3f77))
53
+
-`ensureNotesDirectoryExists()` and `ensureMarkdownDirectoryExists()` swallowed 401s and fell through to MKCOL, surfacing "Cannot create sync folder" instead of "Authentication failed"
54
+
- Auth errors are now detected and re-thrown before MKCOL; defense-in-depth in `SyncExceptionMapper`
55
+
56
+
**Align Widget Checklist Sort with Editor for All Sort Options** ([dcc740b](https://github.com/inventory69/simple-notes-sync/commit/dcc740b))
57
+
- Widget `ToggleChecklistItemAction` only handled MANUAL and UNCHECKED_FIRST
58
+
- Extracted shared `ChecklistSorter` utility used by both widget and editor for consistent sorting across all seven sort options
59
+
- Thanks to MrsMinchen for the contribution!
60
+
61
+
**Trigger Auto-Save on Sort Option Change** ([06c5228](https://github.com/inventory69/simple-notes-sync/commit/06c5228))
62
+
-`sortChecklistItems(option)` was missing `isDirty` and `scheduleAutosave`, so sort changes were lost without explicit save
63
+
- Widget updates also added after auto-save and saveOnBack to keep widgets in sync
64
+
- Thanks to freemen for reporting!
65
+
66
+
**Truncate Widget Content to Prevent TransactionTooLargeException** ([7aba796](https://github.com/inventory69/simple-notes-sync/commit/7aba796))
67
+
- Limit text notes to 100 lines and checklists to 100 items in widget rendering — very long notes exceeded the 1MB Binder IPC limit for RemoteViews
68
+
69
+
**Localize Widget Empty State and Add Tap-to-Reconfigure** ([0b7dbf8](https://github.com/inventory69/simple-notes-sync/commit/0b7dbf8))
70
+
- Replaced hardcoded "Note not found" with a string resource
71
+
- Tapping the widget now opens the config activity so users can recover after note data is cleared
72
+
73
+
**Move NotesStorage File I/O off the Main Thread** ([645ce9e](https://github.com/inventory69/simple-notes-sync/commit/645ce9e))
74
+
-`saveNote`, `loadNote`, `loadAllNotes`, `deleteNote` are now suspend functions on `Dispatchers.IO`
75
+
- Fixes `loadAllNotes()` race-condition crash (FileNotFoundException between listFiles/readText)
76
+
- Fixes empty TextFieldState on first note open after app start
77
+
- Fixes "Note marked PENDING on back-navigation from empty editor"
78
+
- Fixes "Widget showing 'Note not found'": `loadNoteSync()` now runs inside `provideContent` so every widget update reads fresh data
79
+
80
+
**Prevent Flash of Wrong Editor State on Async Note Load** ([b7b3a1c](https://github.com/inventory69/simple-notes-sync/commit/b7b3a1c))
81
+
- Async load surfaced TEXT-mode defaults for 1+ frames before the IO load completed, briefly showing wrong TopBar title and content type for checklists
82
+
-`isNewNote=false` is now set synchronously before launching the coroutine; isLoading gates the entire screen
-`deleteNote()` now uses `trackDeletionSafe()` to prevent race conditions during batch deletes; the legacy unprotected variant is deprecated
86
+
87
+
**Preserve List Scroll Position When Returning from Editor** ([c5b4955](https://github.com/inventory69/simple-notes-sync/commit/c5b4955))
88
+
- New-note detection moved from the unsorted load to the sorted-flow; editing an existing note no longer resets scroll
89
+
90
+
**Persist Navigation Flags Across Process Death** ([78b331b](https://github.com/inventory69/simple-notes-sync/commit/78b331b))
91
+
-`cameFromEditor`/`cameFromSettings` are saved/restored via `onSaveInstanceState`, preventing incorrect scroll-to-top and sync suppression after system process termination
92
+
93
+
**Trigger onSave Sync After Editor Deletion** ([5c7f008](https://github.com/inventory69/simple-notes-sync/commit/5c7f008))
94
+
-`deleteNoteFromEditor` now triggers a sync to propagate the deletion to the server immediately, consistent with `saveNote` behavior
95
+
96
+
**Add Reachability Check to Settings syncNow()** ([d1928be](https://github.com/inventory69/simple-notes-sync/commit/d1928be))
97
+
- The Settings "Sync now" path was the only `syncNotes()` caller bypassing `SyncGateChecker.isServerReachable()`; unreachable servers caused FATAL exceptions instead of a clean abort
98
+
99
+
**Add HTTP HEAD Check to Server Reachability Gate** ([13ad82e](https://github.com/inventory69/simple-notes-sync/commit/13ad82e))
100
+
-`SyncGateChecker` now performs a HEAD request after the TCP socket check to verify the server actually speaks HTTP — prevents false positives for servers with TLS issues
101
+
102
+
**Timestamp-Based Stale Sync State Detection** ([bd394fa](https://github.com/inventory69/simple-notes-sync/commit/bd394fa))
103
+
-`SyncStateManager` auto-resets `SYNCING` state older than 5 minutes; called from `Application.onCreate` and `MainViewModel.init` to cover both process death and configuration changes
104
+
105
+
**Add Logging to 18 Silent Catch Blocks** ([728f33a](https://github.com/inventory69/simple-notes-sync/commit/728f33a))
106
+
- Replace `catch (_: Exception)` with logged exceptions across `ImportWizard`, `WebDavSyncService`, `ConnectionManager`, `SyncGateChecker`, `ThemePreferences`, `MainViewModel`, `NoteDownloader`, and widget code
107
+
108
+
**Logging and User Hints for Silent Error Paths** ([e6dac28](https://github.com/inventory69/simple-notes-sync/commit/e6dac28))
109
+
-`WidgetConfig` now logs failures; import shows a user-visible hint when zero notes are imported despite candidates being present
110
+
111
+
**Add Logging to 403 Workaround in `SafeSardineWrapper.exists`** ([4917fc4](https://github.com/inventory69/simple-notes-sync/commit/4917fc4))
112
+
- Warns when the Jianguoyun 403-as-exists workaround triggers, so false positives are visible in debug logs
113
+
114
+
**Null Check for `sardine.getInputStream` in `readContent`** ([98a778f](https://github.com/inventory69/simple-notes-sync/commit/98a778f))
115
+
- Sardine can return null for non-existent resources; safe-call prevents NPE during import
116
+
117
+
**Resolve Deprecated APIs and Lint Warnings** ([6b87dd2](https://github.com/inventory69/simple-notes-sync/commit/6b87dd2))
- Replace magic boolean in 3 `getBoolean()` calls; comment documents why default is `true` (safe for first install)
146
+
147
+
### 🔧 Internal Changes
148
+
149
+
-**StateFlow migration in `NoteEditorViewModel`** ([9071905](https://github.com/inventory69/simple-notes-sync/commit/9071905)) — `existingNote`, `isDirty`, `hasUnsavedChecklistEdits`, `isRestoringSnapshot` are now backed by `MutableStateFlow`
150
+
-**In-memory cache for `loadAllNotes` with 2s TTL** ([8b74b43](https://github.com/inventory69/simple-notes-sync/commit/8b74b43)) — avoids re-reading and parsing all JSON files on every onResume; race-safe via `AtomicLong` version counter
151
+
-**Replace `android.util.Log` with project Logger in 4 files** ([57c3246](https://github.com/inventory69/simple-notes-sync/commit/57c3246)) — `DragDropListState`, `NoteEditorScreen`, `ChecklistItemRow`, `SettingsViewModel`
152
+
-**Deduplicate `getTimeoutMs` into `ConnectionManager`** ([ea05e03](https://github.com/inventory69/simple-notes-sync/commit/ea05e03)) — `SyncGateChecker` now delegates
153
+
-**Extract `BatteryOptimizationHelper`** ([c3a1e3b](https://github.com/inventory69/simple-notes-sync/commit/c3a1e3b)) — `ComposeSettingsActivity` and `ComposeMainActivity` deduplicated; `setAutoSync()` only shows the dialog when not already exempt
154
+
-**Unify SharedPreferences/StateFlow write order** ([908493b](https://github.com/inventory69/simple-notes-sync/commit/908493b)) — write prefs first, then state; in-memory state never diverges from persisted state on partial failures
155
+
-**Extract `WidgetUpdateHelper`** ([00f66db](https://github.com/inventory69/simple-notes-sync/commit/00f66db)) — single helper for the `GlanceAppWidgetManager → getGlanceIds → forEach update` pattern
156
+
-**Document `@Immutable` on `Note`** ([15c6a12](https://github.com/inventory69/simple-notes-sync/commit/15c6a12))
-**Extract widget magic numbers to named constants** ([2a84b32](https://github.com/inventory69/simple-notes-sync/commit/2a84b32))
160
+
-**Safe Long-to-Int coercion for socket timeout** ([b77ac4d](https://github.com/inventory69/simple-notes-sync/commit/b77ac4d))
161
+
-**Increase `MAX_LOG_ENTRIES` from 500 to 5000** ([b18fa8f](https://github.com/inventory69/simple-notes-sync/commit/b18fa8f)) — full sync cycles with 30+ notes can exceed 500 lines
162
+
-**Remove dead legacy `toReadableTime` without context param** ([d3ea343](https://github.com/inventory69/simple-notes-sync/commit/d3ea343))
163
+
-**Bump version 2.2.0 → 2.3.0** ([71cf215](https://github.com/inventory69/simple-notes-sync/commit/71cf215))
164
+
165
+
### 🌍 Translations
166
+
167
+
- Chinese (Simplified) updated via Weblate ([1be36bb](https://github.com/inventory69/simple-notes-sync/commit/1be36bb)) — thanks to [@heretic43](https://github.com/heretic43)!
168
+
169
+
Translation hosting generously provided by [Weblate](https://hosted.weblate.org/projects/simple-notes-sync/) — thank you for sponsoring open-source projects! 🙏
170
+
171
+
### 🙏 Acknowledgements
172
+
173
+
-[@angeld-jr2](https://github.com/angeld-jr2) — reported the backup-validation crash and provided the debug log that made the phantom-notes bug diagnosable
174
+
-[@Ichigo-Meow](https://github.com/Ichigo-Meow) — reported the MKCOL/WebDAV validation issue ([#55](https://github.com/inventory69/simple-notes-sync/issues/55))
175
+
-[@minosimo](https://github.com/minosimo) — reported the markdown-auto-sync regression ([#50](https://github.com/inventory69/simple-notes-sync/issues/50))
176
+
- MrsMinchen — contributed the widget checklist sort fix
177
+
- freemen — reported the missing auto-save on checklist sort changes
Copy file name to clipboardExpand all lines: README.de.md
+3-1Lines changed: 3 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -134,6 +134,8 @@ cd android
134
134
135
135
## 🌍 Übersetzungen
136
136
137
+
Übersetzungs-Hosting freundlicherweise bereitgestellt von [Weblate](https://hosted.weblate.org/projects/simple-notes-sync/) - danke für das Sponsoring von Open-Source-Projekten! 🙏
Copy file name to clipboardExpand all lines: README.md
+3-1Lines changed: 3 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -144,6 +144,8 @@ Features with enough community support will be considered for implementation. Pl
144
144
145
145
## 🌍 Translations
146
146
147
+
Translation hosting generously provided by [Weblate](https://hosted.weblate.org/projects/simple-notes-sync/) - thank you for sponsoring open-source projects! 🙏
0 commit comments