Skip to content

Commit 7128c25

Browse files
committed
Merge feature/v1.4.1-bugfixes: Bugfixes + Checklist improvements
2 parents 9b37078 + 356ccde commit 7128c25

11 files changed

Lines changed: 211 additions & 31 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,4 @@ Thumbs.db
4242
*.tmp
4343
*.swp
4444
*~
45+
test-apks/

CHANGELOG.md

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

77
---
88

9+
## [1.4.1] - 2026-01-11
10+
11+
### Fixed
12+
13+
- **🗑️ Löschen älterer Notizen (v1.2.0 Kompatibilität)**
14+
- Notizen aus App-Version v1.2.0 oder früher werden jetzt korrekt vom Server gelöscht
15+
- Behebt Problem bei Multi-Device-Nutzung mit älteren Notizen
16+
17+
- **🔄 Checklisten-Sync Abwärtskompatibilität**
18+
- Checklisten werden jetzt auch als Text-Fallback im `content`-Feld gespeichert
19+
- Ältere App-Versionen (v1.3.x) zeigen Checklisten als lesbaren Text
20+
- Format: GitHub-Style Task-Listen (`[ ] Item` / `[x] Item`)
21+
- Recovery-Mode: Falls Checklisten-Items verloren gehen, werden sie aus dem Content wiederhergestellt
22+
23+
### Improved
24+
25+
- **📝 Checklisten Auto-Zeilenumbruch**
26+
- Lange Checklisten-Texte werden jetzt automatisch umgebrochen
27+
- Keine Begrenzung auf 3 Zeilen mehr
28+
- Enter-Taste erstellt weiterhin ein neues Item
29+
30+
### Looking Ahead
31+
32+
> 🚀 **v1.5.0** wird das nächste größere Release. Wir sammeln Ideen und Feedback!
33+
> Feature-Requests gerne als [GitHub Issue](https://github.com/inventory69/simple-notes-sync/issues) einreichen.
34+
35+
---
36+
937
## [1.4.0] - 2026-01-10
1038

1139
### 🎉 New Feature: Checklists

android/app/build.gradle.kts

Lines changed: 2 additions & 2 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 = 11 // 🚀 v1.4.0: Checklists Feature
24-
versionName = "1.4.0" // 🚀 v1.4.0: Checklists, Multi-Device Sync Fixes, UX Improvements
23+
versionCode = 12 // 🔧 v1.4.1: Bugfixes (Root-Delete, Checklist Compat)
24+
versionName = "1.4.1" // 🔧 v1.4.1: Root-Folder Delete Fix, Checklisten-Sync Abwärtskompatibilität
2525

2626
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2727
}

android/app/src/main/java/dev/dettmer/simplenotes/MainActivity.kt

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ class MainActivity : AppCompatActivity() {
131131
setupRecyclerView()
132132
setupFab()
133133

134+
// v1.4.1: Migrate checklists for backwards compatibility
135+
migrateChecklistsForBackwardsCompat()
136+
134137
loadNotes()
135138

136139
// 🔄 v1.3.1: Observe sync state for UI updates
@@ -730,6 +733,54 @@ class MainActivity : AppCompatActivity() {
730733
}
731734
}
732735

736+
/**
737+
* v1.4.1: Migriert bestehende Checklisten für Abwärtskompatibilität.
738+
*
739+
* Problem: v1.4.0 Checklisten haben leeren "content", was auf älteren
740+
* App-Versionen (v1.3.x) als leere Notiz angezeigt wird.
741+
*
742+
* Lösung: Alle Checklisten ohne Fallback-Content als PENDING markieren,
743+
* damit sie beim nächsten Sync mit Fallback-Content hochgeladen werden.
744+
*
745+
* TODO: Diese Migration kann entfernt werden, sobald v1.4.0 nicht mehr
746+
* im Umlauf ist (ca. 6 Monate nach v1.4.1 Release, also ~Juli 2026).
747+
* Tracking: https://github.com/inventory69/simple-notes-sync/issues/XXX
748+
*/
749+
private fun migrateChecklistsForBackwardsCompat() {
750+
val migrationKey = "v1.4.1_checklist_migration_done"
751+
752+
// Nur einmal ausführen
753+
if (prefs.getBoolean(migrationKey, false)) {
754+
return
755+
}
756+
757+
val allNotes = storage.loadAllNotes()
758+
val checklistsToMigrate = allNotes.filter { note ->
759+
note.noteType == NoteType.CHECKLIST &&
760+
note.content.isBlank() &&
761+
note.checklistItems?.isNotEmpty() == true
762+
}
763+
764+
if (checklistsToMigrate.isNotEmpty()) {
765+
Logger.d(TAG, "🔄 v1.4.1 Migration: Found ${checklistsToMigrate.size} checklists without fallback content")
766+
767+
for (note in checklistsToMigrate) {
768+
// Als PENDING markieren, damit beim nächsten Sync der Fallback-Content
769+
// generiert und hochgeladen wird
770+
val updatedNote = note.copy(
771+
syncStatus = dev.dettmer.simplenotes.models.SyncStatus.PENDING
772+
)
773+
storage.saveNote(updatedNote)
774+
Logger.d(TAG, " 📝 Marked for re-sync: ${note.title}")
775+
}
776+
777+
Logger.d(TAG, "✅ v1.4.1 Migration: ${checklistsToMigrate.size} checklists marked for re-sync")
778+
}
779+
780+
// Migration als erledigt markieren
781+
prefs.edit().putBoolean(migrationKey, true).apply()
782+
}
783+
733784
override fun onRequestPermissionsResult(
734785
requestCode: Int,
735786
permissions: Array<out String>,

android/app/src/main/java/dev/dettmer/simplenotes/adapters/ChecklistEditorAdapter.kt

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@ package dev.dettmer.simplenotes.adapters
33
import android.graphics.Paint
44
import android.text.Editable
55
import android.text.TextWatcher
6-
import android.view.KeyEvent
76
import android.view.LayoutInflater
87
import android.view.MotionEvent
98
import android.view.View
109
import android.view.ViewGroup
11-
import android.view.inputmethod.EditorInfo
1210
import android.widget.EditText
1311
import android.widget.ImageButton
1412
import android.widget.ImageView
@@ -55,32 +53,30 @@ class ChecklistEditorAdapter(
5553
editText.setText(item.text)
5654
updateStrikethrough(item.isChecked)
5755

58-
// TextWatcher für Änderungen
56+
// v1.4.1: TextWatcher für Änderungen + Enter-Erkennung für neues Item
5957
textWatcher = object : TextWatcher {
6058
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
6159
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
6260
override fun afterTextChanged(s: Editable?) {
6361
val pos = bindingAdapterPosition
64-
if (pos != RecyclerView.NO_POSITION) {
65-
onItemTextChanged(pos, s?.toString() ?: "")
66-
}
67-
}
68-
}
69-
editText.addTextChangedListener(textWatcher)
70-
71-
// Enter-Taste = neues Item
72-
editText.setOnEditorActionListener { _, actionId, event ->
73-
if (actionId == EditorInfo.IME_ACTION_NEXT ||
74-
(event?.keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN)) {
75-
val pos = bindingAdapterPosition
76-
if (pos != RecyclerView.NO_POSITION) {
62+
if (pos == RecyclerView.NO_POSITION) return
63+
64+
val text = s?.toString() ?: ""
65+
66+
// Prüfe ob ein Newline eingegeben wurde
67+
if (text.contains("\n")) {
68+
// Newline entfernen und neues Item erstellen
69+
val cleanText = text.replace("\n", "")
70+
editText.setText(cleanText)
71+
editText.setSelection(cleanText.length)
72+
onItemTextChanged(pos, cleanText)
7773
onAddNewItem(pos + 1)
74+
} else {
75+
onItemTextChanged(pos, text)
7876
}
79-
true
80-
} else {
81-
false
8277
}
8378
}
79+
editText.addTextChangedListener(textWatcher)
8480

8581
// Delete Button
8682
deleteButton.setOnClickListener {

android/app/src/main/java/dev/dettmer/simplenotes/models/Note.kt

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,42 @@ data class Note(
2020
val checklistItems: List<ChecklistItem>? = null
2121
) {
2222
/**
23-
* Serialisiert Note zu JSON (v1.4.0: Nutzt Gson für komplexe Strukturen)
23+
* Serialisiert Note zu JSON
24+
* v1.4.0: Nutzt Gson für komplexe Strukturen
25+
* v1.4.1: Für Checklisten wird ein Fallback-Content generiert, damit ältere
26+
* App-Versionen (v1.3.x) die Notiz als Text anzeigen können.
2427
*/
2528
fun toJson(): String {
2629
val gson = com.google.gson.GsonBuilder()
2730
.setPrettyPrinting()
2831
.create()
29-
return gson.toJson(this)
32+
33+
// v1.4.1: Für Checklisten den Fallback-Content generieren
34+
val noteToSerialize = if (noteType == NoteType.CHECKLIST && checklistItems != null) {
35+
this.copy(content = generateChecklistFallbackContent())
36+
} else {
37+
this
38+
}
39+
40+
return gson.toJson(noteToSerialize)
41+
}
42+
43+
/**
44+
* v1.4.1: Generiert einen lesbaren Text-Fallback aus Checklist-Items.
45+
* Format: GitHub-Style Task-Listen (kompatibel mit Markdown)
46+
*
47+
* Beispiel:
48+
* [ ] Milch kaufen
49+
* [x] Brot gekauft
50+
* [ ] Eier
51+
*
52+
* Wird von älteren App-Versionen (v1.3.x) als normaler Text angezeigt.
53+
*/
54+
private fun generateChecklistFallbackContent(): String {
55+
return checklistItems?.sortedBy { it.order }?.joinToString("\n") { item ->
56+
val checkbox = if (item.isChecked) "[x]" else "[ ]"
57+
"$checkbox ${item.text}"
58+
} ?: ""
3059
}
3160

3261
/**
@@ -88,7 +117,7 @@ type: ${noteType.name.lowercase()}
88117

89118
// Checklist-Items parsen (kann null sein)
90119
val checklistItemsType = object : com.google.gson.reflect.TypeToken<List<ChecklistItem>>() {}.type
91-
val checklistItems = if (jsonObject.has("checklistItems") &&
120+
var checklistItems: List<ChecklistItem>? = if (jsonObject.has("checklistItems") &&
92121
!jsonObject.get("checklistItems").isJsonNull
93122
) {
94123
gson.fromJson<List<ChecklistItem>>(
@@ -99,6 +128,19 @@ type: ${noteType.name.lowercase()}
99128
null
100129
}
101130

131+
// v1.4.1: Recovery-Mode - Falls Checkliste aber keine Items,
132+
// versuche Content als Fallback zu parsen
133+
if (noteType == NoteType.CHECKLIST &&
134+
(checklistItems == null || checklistItems.isEmpty()) &&
135+
rawNote.content.isNotBlank()) {
136+
137+
val recoveredItems = parseChecklistFromContent(rawNote.content)
138+
if (recoveredItems.isNotEmpty()) {
139+
Logger.d(TAG, "🔄 Recovered ${recoveredItems.size} checklist items from content fallback")
140+
checklistItems = recoveredItems
141+
}
142+
}
143+
102144
// Note mit korrekten Werten erstellen
103145
Note(
104146
id = rawNote.id,
@@ -130,6 +172,34 @@ type: ${noteType.name.lowercase()}
130172
val syncStatus: SyncStatus? = null
131173
)
132174

175+
/**
176+
* v1.4.1: Parst GitHub-Style Checklisten aus Text (Recovery-Mode).
177+
*
178+
* Unterstützte Formate:
179+
* - [ ] Unchecked item
180+
* - [x] Checked item
181+
* - [X] Checked item (case insensitive)
182+
*
183+
* Wird verwendet, wenn eine v1.4.0 Checkliste von einer älteren
184+
* App-Version (v1.3.x) bearbeitet wurde und die checklistItems verloren gingen.
185+
*
186+
* @param content Der Text-Content der Notiz
187+
* @return Liste von ChecklistItems oder leere Liste
188+
*/
189+
private fun parseChecklistFromContent(content: String): List<ChecklistItem> {
190+
val pattern = Regex("""^\s*\[([ xX])\]\s*(.+)$""", RegexOption.MULTILINE)
191+
return pattern.findAll(content).mapIndexed { index, match ->
192+
val checked = match.groupValues[1].lowercase() == "x"
193+
val text = match.groupValues[2].trim()
194+
ChecklistItem(
195+
id = UUID.randomUUID().toString(),
196+
text = text,
197+
isChecked = checked,
198+
order = index
199+
)
200+
}.toList()
201+
}
202+
133203
/**
134204
* Parst Markdown zurück zu Note-Objekt (Task #1.2.0-09)
135205
* v1.4.0: Unterstützt jetzt auch Checklisten-Format

android/app/src/main/java/dev/dettmer/simplenotes/sync/WebDavSyncService.kt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1764,6 +1764,9 @@ class WebDavSyncService(private val context: Context) {
17641764
* Deletes a note from the server (JSON + Markdown)
17651765
* Does NOT delete from local storage!
17661766
*
1767+
* v1.4.1: Now supports v1.2.0 compatibility mode - also checks ROOT folder
1768+
* for notes that were created before the /notes/ directory structure.
1769+
*
17671770
* @param noteId The ID of the note to delete
17681771
* @return true if at least one file was deleted, false otherwise
17691772
*/
@@ -1775,12 +1778,21 @@ class WebDavSyncService(private val context: Context) {
17751778
var deletedJson = false
17761779
var deletedMd = false
17771780

1778-
// Delete JSON
1781+
// v1.4.1: Try to delete JSON from /notes/ first (standard path)
17791782
val jsonUrl = getNotesUrl(serverUrl) + "$noteId.json"
17801783
if (sardine.exists(jsonUrl)) {
17811784
sardine.delete(jsonUrl)
17821785
deletedJson = true
1783-
Logger.d(TAG, "🗑️ Deleted from server: $noteId.json")
1786+
Logger.d(TAG, "🗑️ Deleted from server: $noteId.json (from /notes/)")
1787+
} else {
1788+
// v1.4.1: Fallback - check ROOT folder for v1.2.0 compatibility
1789+
val rootJsonUrl = serverUrl.trimEnd('/') + "/$noteId.json"
1790+
Logger.d(TAG, "🔍 JSON not in /notes/, checking ROOT: $rootJsonUrl")
1791+
if (sardine.exists(rootJsonUrl)) {
1792+
sardine.delete(rootJsonUrl)
1793+
deletedJson = true
1794+
Logger.d(TAG, "🗑️ Deleted from server: $noteId.json (from ROOT - v1.2.0 compat)")
1795+
}
17841796
}
17851797

17861798
// Delete Markdown (v1.3.0: YAML-scan based approach)

android/app/src/main/res/layout/item_checklist_editor.xml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,15 @@
3131
android:minHeight="0dp" />
3232

3333
<!-- Text Input (ohne Box, nur transparent) -->
34+
<!-- v1.4.1: Auto-Zeilenumbruch für lange Texte -->
3435
<EditText
3536
android:id="@+id/etItemText"
3637
android:layout_width="0dp"
3738
android:layout_height="wrap_content"
3839
android:layout_weight="1"
3940
android:background="@null"
4041
android:hint="@string/item_placeholder"
41-
android:inputType="text"
42-
android:imeOptions="actionNext"
43-
android:maxLines="3"
42+
android:inputType="textMultiLine|textCapSentences"
4443
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
4544
tools:text="Milch kaufen" />
4645

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
• Bugfix: Löschen von Notizen aus älteren App-Versionen (v1.2.0)
2+
• Bugfix: Checklisten-Sync mit älteren App-Versionen (v1.3.x)
3+
• Checklisten werden jetzt auch als Text-Fallback gespeichert
4+
• Neu: Checklisten-Texte werden automatisch umgebrochen
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
• Bugfix: Deleting notes from older app versions (v1.2.0)
2+
• Bugfix: Checklist sync with older app versions (v1.3.x)
3+
• Checklists are now also saved as text fallback
4+
• New: Checklist texts now wrap automatically
5+
p automatically

0 commit comments

Comments
 (0)