From 356ccde6276c6365b1a9ac7e52441cb7c7d0a6b7 Mon Sep 17 00:00:00 2001 From: inventory69 Date: Sun, 11 Jan 2026 21:53:49 +0100 Subject: [PATCH] feat(v1.4.1): Bugfixes + Checklist auto line wrap Fixed: - Delete notes from older app versions (v1.2.0 compatibility) - Checklist sync backwards compatibility (v1.3.x) - Fallback content in GitHub task list format - Recovery mode for lost checklistItems Improved: - Checklist auto line wrap (no maxLines limit) - Enter key creates new item (via TextWatcher) Metadata: - Changelogs for versionCode 12 - IzzyOnDroid metadata updated --- .gitignore | 1 + CHANGELOG.md | 28 +++++++ android/app/build.gradle.kts | 4 +- .../dev/dettmer/simplenotes/MainActivity.kt | 51 +++++++++++++ .../adapters/ChecklistEditorAdapter.kt | 34 ++++----- .../dev/dettmer/simplenotes/models/Note.kt | 76 ++++++++++++++++++- .../simplenotes/sync/WebDavSyncService.kt | 16 +++- .../main/res/layout/item_checklist_editor.xml | 5 +- .../metadata/android/de-DE/changelogs/12.txt | 4 + .../metadata/android/en-US/changelogs/12.txt | 5 ++ metadata/dev.dettmer.simplenotes.yml | 18 ++++- 11 files changed, 211 insertions(+), 31 deletions(-) create mode 100644 fastlane/metadata/android/de-DE/changelogs/12.txt create mode 100644 fastlane/metadata/android/en-US/changelogs/12.txt diff --git a/.gitignore b/.gitignore index a0e4217..c25c326 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ Thumbs.db *.tmp *.swp *~ +test-apks/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 26d69e7..accbc72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,34 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). --- +## [1.4.1] - 2026-01-11 + +### Fixed + +- **🗑️ Löschen älterer Notizen (v1.2.0 Kompatibilität)** + - Notizen aus App-Version v1.2.0 oder früher werden jetzt korrekt vom Server gelöscht + - Behebt Problem bei Multi-Device-Nutzung mit älteren Notizen + +- **🔄 Checklisten-Sync Abwärtskompatibilität** + - Checklisten werden jetzt auch als Text-Fallback im `content`-Feld gespeichert + - Ältere App-Versionen (v1.3.x) zeigen Checklisten als lesbaren Text + - Format: GitHub-Style Task-Listen (`[ ] Item` / `[x] Item`) + - Recovery-Mode: Falls Checklisten-Items verloren gehen, werden sie aus dem Content wiederhergestellt + +### Improved + +- **📝 Checklisten Auto-Zeilenumbruch** + - Lange Checklisten-Texte werden jetzt automatisch umgebrochen + - Keine Begrenzung auf 3 Zeilen mehr + - Enter-Taste erstellt weiterhin ein neues Item + +### Looking Ahead + +> 🚀 **v1.5.0** wird das nächste größere Release. Wir sammeln Ideen und Feedback! +> Feature-Requests gerne als [GitHub Issue](https://github.com/inventory69/simple-notes-sync/issues) einreichen. + +--- + ## [1.4.0] - 2026-01-10 ### 🎉 New Feature: Checklists diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 2dfa20f..495c3ea 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -20,8 +20,8 @@ android { applicationId = "dev.dettmer.simplenotes" minSdk = 24 targetSdk = 36 - versionCode = 11 // 🚀 v1.4.0: Checklists Feature - versionName = "1.4.0" // 🚀 v1.4.0: Checklists, Multi-Device Sync Fixes, UX Improvements + versionCode = 12 // 🔧 v1.4.1: Bugfixes (Root-Delete, Checklist Compat) + versionName = "1.4.1" // 🔧 v1.4.1: Root-Folder Delete Fix, Checklisten-Sync Abwärtskompatibilität testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/android/app/src/main/java/dev/dettmer/simplenotes/MainActivity.kt b/android/app/src/main/java/dev/dettmer/simplenotes/MainActivity.kt index f7e2272..6683772 100644 --- a/android/app/src/main/java/dev/dettmer/simplenotes/MainActivity.kt +++ b/android/app/src/main/java/dev/dettmer/simplenotes/MainActivity.kt @@ -131,6 +131,9 @@ class MainActivity : AppCompatActivity() { setupRecyclerView() setupFab() + // v1.4.1: Migrate checklists for backwards compatibility + migrateChecklistsForBackwardsCompat() + loadNotes() // 🔄 v1.3.1: Observe sync state for UI updates @@ -730,6 +733,54 @@ class MainActivity : AppCompatActivity() { } } + /** + * v1.4.1: Migriert bestehende Checklisten für Abwärtskompatibilität. + * + * Problem: v1.4.0 Checklisten haben leeren "content", was auf älteren + * App-Versionen (v1.3.x) als leere Notiz angezeigt wird. + * + * Lösung: Alle Checklisten ohne Fallback-Content als PENDING markieren, + * damit sie beim nächsten Sync mit Fallback-Content hochgeladen werden. + * + * TODO: Diese Migration kann entfernt werden, sobald v1.4.0 nicht mehr + * im Umlauf ist (ca. 6 Monate nach v1.4.1 Release, also ~Juli 2026). + * Tracking: https://github.com/inventory69/simple-notes-sync/issues/XXX + */ + private fun migrateChecklistsForBackwardsCompat() { + val migrationKey = "v1.4.1_checklist_migration_done" + + // Nur einmal ausführen + if (prefs.getBoolean(migrationKey, false)) { + return + } + + val allNotes = storage.loadAllNotes() + val checklistsToMigrate = allNotes.filter { note -> + note.noteType == NoteType.CHECKLIST && + note.content.isBlank() && + note.checklistItems?.isNotEmpty() == true + } + + if (checklistsToMigrate.isNotEmpty()) { + Logger.d(TAG, "🔄 v1.4.1 Migration: Found ${checklistsToMigrate.size} checklists without fallback content") + + for (note in checklistsToMigrate) { + // Als PENDING markieren, damit beim nächsten Sync der Fallback-Content + // generiert und hochgeladen wird + val updatedNote = note.copy( + syncStatus = dev.dettmer.simplenotes.models.SyncStatus.PENDING + ) + storage.saveNote(updatedNote) + Logger.d(TAG, " 📝 Marked for re-sync: ${note.title}") + } + + Logger.d(TAG, "✅ v1.4.1 Migration: ${checklistsToMigrate.size} checklists marked for re-sync") + } + + // Migration als erledigt markieren + prefs.edit().putBoolean(migrationKey, true).apply() + } + override fun onRequestPermissionsResult( requestCode: Int, permissions: Array, diff --git a/android/app/src/main/java/dev/dettmer/simplenotes/adapters/ChecklistEditorAdapter.kt b/android/app/src/main/java/dev/dettmer/simplenotes/adapters/ChecklistEditorAdapter.kt index 4fdd014..59baa07 100644 --- a/android/app/src/main/java/dev/dettmer/simplenotes/adapters/ChecklistEditorAdapter.kt +++ b/android/app/src/main/java/dev/dettmer/simplenotes/adapters/ChecklistEditorAdapter.kt @@ -3,12 +3,10 @@ package dev.dettmer.simplenotes.adapters import android.graphics.Paint import android.text.Editable import android.text.TextWatcher -import android.view.KeyEvent import android.view.LayoutInflater import android.view.MotionEvent import android.view.View import android.view.ViewGroup -import android.view.inputmethod.EditorInfo import android.widget.EditText import android.widget.ImageButton import android.widget.ImageView @@ -55,33 +53,31 @@ class ChecklistEditorAdapter( editText.setText(item.text) updateStrikethrough(item.isChecked) - // TextWatcher für Änderungen + // v1.4.1: TextWatcher für Änderungen + Enter-Erkennung für neues Item textWatcher = object : TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} override fun afterTextChanged(s: Editable?) { val pos = bindingAdapterPosition - if (pos != RecyclerView.NO_POSITION) { - onItemTextChanged(pos, s?.toString() ?: "") + if (pos == RecyclerView.NO_POSITION) return + + val text = s?.toString() ?: "" + + // Prüfe ob ein Newline eingegeben wurde + if (text.contains("\n")) { + // Newline entfernen und neues Item erstellen + val cleanText = text.replace("\n", "") + editText.setText(cleanText) + editText.setSelection(cleanText.length) + onItemTextChanged(pos, cleanText) + onAddNewItem(pos + 1) + } else { + onItemTextChanged(pos, text) } } } editText.addTextChangedListener(textWatcher) - // Enter-Taste = neues Item - editText.setOnEditorActionListener { _, actionId, event -> - if (actionId == EditorInfo.IME_ACTION_NEXT || - (event?.keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN)) { - val pos = bindingAdapterPosition - if (pos != RecyclerView.NO_POSITION) { - onAddNewItem(pos + 1) - } - true - } else { - false - } - } - // Delete Button deleteButton.setOnClickListener { val pos = bindingAdapterPosition diff --git a/android/app/src/main/java/dev/dettmer/simplenotes/models/Note.kt b/android/app/src/main/java/dev/dettmer/simplenotes/models/Note.kt index ab2b16b..6dce920 100644 --- a/android/app/src/main/java/dev/dettmer/simplenotes/models/Note.kt +++ b/android/app/src/main/java/dev/dettmer/simplenotes/models/Note.kt @@ -20,13 +20,42 @@ data class Note( val checklistItems: List? = null ) { /** - * Serialisiert Note zu JSON (v1.4.0: Nutzt Gson für komplexe Strukturen) + * Serialisiert Note zu JSON + * v1.4.0: Nutzt Gson für komplexe Strukturen + * v1.4.1: Für Checklisten wird ein Fallback-Content generiert, damit ältere + * App-Versionen (v1.3.x) die Notiz als Text anzeigen können. */ fun toJson(): String { val gson = com.google.gson.GsonBuilder() .setPrettyPrinting() .create() - return gson.toJson(this) + + // v1.4.1: Für Checklisten den Fallback-Content generieren + val noteToSerialize = if (noteType == NoteType.CHECKLIST && checklistItems != null) { + this.copy(content = generateChecklistFallbackContent()) + } else { + this + } + + return gson.toJson(noteToSerialize) + } + + /** + * v1.4.1: Generiert einen lesbaren Text-Fallback aus Checklist-Items. + * Format: GitHub-Style Task-Listen (kompatibel mit Markdown) + * + * Beispiel: + * [ ] Milch kaufen + * [x] Brot gekauft + * [ ] Eier + * + * Wird von älteren App-Versionen (v1.3.x) als normaler Text angezeigt. + */ + private fun generateChecklistFallbackContent(): String { + return checklistItems?.sortedBy { it.order }?.joinToString("\n") { item -> + val checkbox = if (item.isChecked) "[x]" else "[ ]" + "$checkbox ${item.text}" + } ?: "" } /** @@ -88,7 +117,7 @@ type: ${noteType.name.lowercase()} // Checklist-Items parsen (kann null sein) val checklistItemsType = object : com.google.gson.reflect.TypeToken>() {}.type - val checklistItems = if (jsonObject.has("checklistItems") && + var checklistItems: List? = if (jsonObject.has("checklistItems") && !jsonObject.get("checklistItems").isJsonNull ) { gson.fromJson>( @@ -99,6 +128,19 @@ type: ${noteType.name.lowercase()} null } + // v1.4.1: Recovery-Mode - Falls Checkliste aber keine Items, + // versuche Content als Fallback zu parsen + if (noteType == NoteType.CHECKLIST && + (checklistItems == null || checklistItems.isEmpty()) && + rawNote.content.isNotBlank()) { + + val recoveredItems = parseChecklistFromContent(rawNote.content) + if (recoveredItems.isNotEmpty()) { + Logger.d(TAG, "🔄 Recovered ${recoveredItems.size} checklist items from content fallback") + checklistItems = recoveredItems + } + } + // Note mit korrekten Werten erstellen Note( id = rawNote.id, @@ -130,6 +172,34 @@ type: ${noteType.name.lowercase()} val syncStatus: SyncStatus? = null ) + /** + * v1.4.1: Parst GitHub-Style Checklisten aus Text (Recovery-Mode). + * + * Unterstützte Formate: + * - [ ] Unchecked item + * - [x] Checked item + * - [X] Checked item (case insensitive) + * + * Wird verwendet, wenn eine v1.4.0 Checkliste von einer älteren + * App-Version (v1.3.x) bearbeitet wurde und die checklistItems verloren gingen. + * + * @param content Der Text-Content der Notiz + * @return Liste von ChecklistItems oder leere Liste + */ + private fun parseChecklistFromContent(content: String): List { + val pattern = Regex("""^\s*\[([ xX])\]\s*(.+)$""", RegexOption.MULTILINE) + return pattern.findAll(content).mapIndexed { index, match -> + val checked = match.groupValues[1].lowercase() == "x" + val text = match.groupValues[2].trim() + ChecklistItem( + id = UUID.randomUUID().toString(), + text = text, + isChecked = checked, + order = index + ) + }.toList() + } + /** * Parst Markdown zurück zu Note-Objekt (Task #1.2.0-09) * v1.4.0: Unterstützt jetzt auch Checklisten-Format diff --git a/android/app/src/main/java/dev/dettmer/simplenotes/sync/WebDavSyncService.kt b/android/app/src/main/java/dev/dettmer/simplenotes/sync/WebDavSyncService.kt index f70cbea..0c3caa7 100644 --- a/android/app/src/main/java/dev/dettmer/simplenotes/sync/WebDavSyncService.kt +++ b/android/app/src/main/java/dev/dettmer/simplenotes/sync/WebDavSyncService.kt @@ -1764,6 +1764,9 @@ class WebDavSyncService(private val context: Context) { * Deletes a note from the server (JSON + Markdown) * Does NOT delete from local storage! * + * v1.4.1: Now supports v1.2.0 compatibility mode - also checks ROOT folder + * for notes that were created before the /notes/ directory structure. + * * @param noteId The ID of the note to delete * @return true if at least one file was deleted, false otherwise */ @@ -1775,12 +1778,21 @@ class WebDavSyncService(private val context: Context) { var deletedJson = false var deletedMd = false - // Delete JSON + // v1.4.1: Try to delete JSON from /notes/ first (standard path) val jsonUrl = getNotesUrl(serverUrl) + "$noteId.json" if (sardine.exists(jsonUrl)) { sardine.delete(jsonUrl) deletedJson = true - Logger.d(TAG, "🗑️ Deleted from server: $noteId.json") + Logger.d(TAG, "🗑️ Deleted from server: $noteId.json (from /notes/)") + } else { + // v1.4.1: Fallback - check ROOT folder for v1.2.0 compatibility + val rootJsonUrl = serverUrl.trimEnd('/') + "/$noteId.json" + Logger.d(TAG, "🔍 JSON not in /notes/, checking ROOT: $rootJsonUrl") + if (sardine.exists(rootJsonUrl)) { + sardine.delete(rootJsonUrl) + deletedJson = true + Logger.d(TAG, "🗑️ Deleted from server: $noteId.json (from ROOT - v1.2.0 compat)") + } } // Delete Markdown (v1.3.0: YAML-scan based approach) diff --git a/android/app/src/main/res/layout/item_checklist_editor.xml b/android/app/src/main/res/layout/item_checklist_editor.xml index 3afec45..8fe341d 100644 --- a/android/app/src/main/res/layout/item_checklist_editor.xml +++ b/android/app/src/main/res/layout/item_checklist_editor.xml @@ -31,6 +31,7 @@ android:minHeight="0dp" /> + diff --git a/fastlane/metadata/android/de-DE/changelogs/12.txt b/fastlane/metadata/android/de-DE/changelogs/12.txt new file mode 100644 index 0000000..4c7bf0c --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/12.txt @@ -0,0 +1,4 @@ +• Bugfix: Löschen von Notizen aus älteren App-Versionen (v1.2.0) +• Bugfix: Checklisten-Sync mit älteren App-Versionen (v1.3.x) +• Checklisten werden jetzt auch als Text-Fallback gespeichert +• Neu: Checklisten-Texte werden automatisch umgebrochen diff --git a/fastlane/metadata/android/en-US/changelogs/12.txt b/fastlane/metadata/android/en-US/changelogs/12.txt new file mode 100644 index 0000000..473cc8d --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/12.txt @@ -0,0 +1,5 @@ +• Bugfix: Deleting notes from older app versions (v1.2.0) +• Bugfix: Checklist sync with older app versions (v1.3.x) +• Checklists are now also saved as text fallback +• New: Checklist texts now wrap automatically +p automatically \ No newline at end of file diff --git a/metadata/dev.dettmer.simplenotes.yml b/metadata/dev.dettmer.simplenotes.yml index 56332b5..9e8da6d 100644 --- a/metadata/dev.dettmer.simplenotes.yml +++ b/metadata/dev.dettmer.simplenotes.yml @@ -147,7 +147,21 @@ Builds: scandelete: - android/gradle/wrapper + - versionName: 1.4.1 + versionCode: 12 + commit: v1.4.1 + subdir: android/app + sudo: + - apt-get update + - apt-get install -y openjdk-17-jdk-headless + - update-java-alternatives -a + gradle: + - fdroid + prebuild: sed -i -e '/signingConfig/d' build.gradle.kts + scandelete: + - android/gradle/wrapper + AutoUpdateMode: Version UpdateCheckMode: Tags -CurrentVersion: 1.4.0 -CurrentVersionCode: 11 +CurrentVersion: 1.4.1 +CurrentVersionCode: 12