Skip to content

Commit cbac62b

Browse files
committed
release(v2.3.0): merge v2.3.0 into main
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
2 parents 1756af4 + 1d8109b commit cbac62b

56 files changed

Lines changed: 1708 additions & 555 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.de.md

Lines changed: 170 additions & 0 deletions
Large diffs are not rendered by default.

CHANGELOG.md

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,176 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
88

99
---
1010

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
83+
84+
**Use Mutex-Protected Deletion Tracking** ([e777259](https://github.com/inventory69/simple-notes-sync/commit/e777259))
85+
- `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))
118+
- `LocalClipboardManager``LocalClipboard` + `ClipEntry`
119+
- `Icons.Filled.PlaylistAdd``Icons.AutoMirrored.Filled.PlaylistAdd`
120+
- `EncryptedSharedPreferences`/`MasterKey`: documented suppression (no replacement for Android 7+)
121+
- All `SharedPreferences.edit().putX().apply()` chains converted to KTX `edit { }` blocks
122+
123+
**Prevent NPE in UrlValidator for Malformed URLs** ([1a3c6a7](https://github.com/inventory69/simple-notes-sync/commit/1a3c6a7))
124+
- `parsedUrl.host` can be null for URLs like `http:///path`; safe-call with early return
125+
126+
**Replace Hardcoded German in Battery Optimization Fallback** ([98c39a6](https://github.com/inventory69/simple-notes-sync/commit/98c39a6))
127+
- `ComposeSettingsActivity` now uses the existing `battery_optimization_open_settings_failed` string resource
128+
129+
**Widget Config Load Failure Hint** ([8d7d03f](https://github.com/inventory69/simple-notes-sync/commit/8d7d03f))
130+
- The widget config activity now logs the fallback to defaults and shows a hint when previous config could not be loaded
131+
132+
### ✨ Improvements
133+
134+
**Migrate All Toast Messages to Material 3 Snackbar** ([a834e25](https://github.com/inventory69/simple-notes-sync/commit/a834e25))
135+
- All 9 `Toast.makeText()` call sites replaced with ViewModel-driven Snackbar events for consistent UX
136+
- `emitSnackbar()` helper added to `MainViewModel` and `NoteEditorViewModel`; `showToast()` extension deprecated
137+
138+
**Centralized Spacing Tokens** ([db4ce8f](https://github.com/inventory69/simple-notes-sync/commit/db4ce8f))
139+
- Added `SpacingXSmall` (2dp), `SpacingMediumLarge` (12dp), `SpacingXXLarge` (32dp) to `Dimensions`; `MarkdownRenderer` migrated to centralized tokens
140+
141+
**Replace Hardcoded Legacy Path Filter with Named Constants** ([5f4d044](https://github.com/inventory69/simple-notes-sync/commit/5f4d044))
142+
- Use `Constants.DEFAULT_SYNC_FOLDER_NAME` and `SyncUrlBuilder.MARKDOWN_SUFFIX` instead of magic strings in the root-fallback filter
143+
144+
**Extract `DEFAULT_OFFLINE_MODE` Constant** ([051054d](https://github.com/inventory69/simple-notes-sync/commit/051054d))
145+
- 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))
157+
- **Document empty product flavors** ([602c06a](https://github.com/inventory69/simple-notes-sync/commit/602c06a))
158+
- **Remove redundant `viewBinding = false`** ([0e02124](https://github.com/inventory69/simple-notes-sync/commit/0e02124))
159+
- **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
178+
179+
---
180+
11181
## [2.2.0] - 2026-03-30
12182

13183
### ✨ New Features

README.de.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ cd android
134134

135135
## 🌍 Übersetzungen
136136

137+
Übersetzungs-Hosting freundlicherweise bereitgestellt von [Weblate](https://hosted.weblate.org/projects/simple-notes-sync/) - danke für das Sponsoring von Open-Source-Projekten! 🙏
138+
137139
[![Übersetzungsstatus](https://hosted.weblate.org/widget/simple-notes-sync/android-app/svg-badge.svg)](https://hosted.weblate.org/engage/simple-notes-sync/)
138140

139141
<a href="https://hosted.weblate.org/engage/simple-notes-sync/">
@@ -157,6 +159,6 @@ GNU Affero General Public License v3.0 – siehe [LICENSE](LICENSE)
157159
<div align="center">
158160
<br /><br />
159161

160-
**v2.0.0** · Built with ❤️ using Kotlin + Jetpack Compose + Material Design 3
162+
**v2.3.0** · Built with ❤️ using Kotlin + Jetpack Compose + Material Design 3
161163

162164
</div>

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ Features with enough community support will be considered for implementation. Pl
144144

145145
## 🌍 Translations
146146

147+
Translation hosting generously provided by [Weblate](https://hosted.weblate.org/projects/simple-notes-sync/) - thank you for sponsoring open-source projects! 🙏
148+
147149
[![Translation status](https://hosted.weblate.org/widget/simple-notes-sync/android-app/svg-badge.svg)](https://hosted.weblate.org/engage/simple-notes-sync/)
148150

149151
<a href="https://hosted.weblate.org/engage/simple-notes-sync/">
@@ -167,6 +169,6 @@ GNU Affero General Public License v3.0 - see [LICENSE](LICENSE)
167169
<div align="center">
168170
<br /><br />
169171

170-
**v2.1.0** · Built with ❤️ using Kotlin + Jetpack Compose + Material Design 3
172+
**v2.3.0** · Built with ❤️ using Kotlin + Jetpack Compose + Material Design 3
171173

172174
</div>

android/app/build.gradle.kts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ android {
2020
applicationId = "dev.dettmer.simplenotes"
2121
minSdk = 24
2222
targetSdk = 36
23-
versionCode = 29 // 🆕 v2.2.0
24-
versionName = "2.2.0" // 🆕 v2.2.0
23+
versionCode = 30 // 🆕 v2.3.0
24+
versionName = "2.3.0" // 🆕 v2.3.0
2525

2626
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2727
}
@@ -38,14 +38,15 @@ android {
3838
productFlavors {
3939
create("fdroid") {
4040
dimension = "distribution"
41-
// F-Droid builds have no proprietary dependencies
42-
// All dependencies in this project are already FOSS-compatible
43-
// No APK splits - F-Droid expects single universal APK
41+
// F-Droid builds: currently identical to standard.
42+
// Flavor exists for future separation (e.g., removing
43+
// proprietary dependencies from standard builds).
4444
}
4545

4646
create("standard") {
4747
dimension = "distribution"
48-
// Standard builds can include Play Services in the future if needed
48+
// Standard builds: currently identical to fdroid.
49+
// Reserved for Google Play specific features if needed.
4950
}
5051
}
5152

@@ -91,7 +92,6 @@ android {
9192
}
9293

9394
buildFeatures {
94-
viewBinding = false
9595
buildConfig = true // Enable BuildConfig generation
9696
compose = true // v1.5.0: Jetpack Compose für Settings Redesign
9797
}

0 commit comments

Comments
 (0)