Skip to content

Commit d24d2f2

Browse files
committed
release(v2.2.0): merge v2.2.0 into main
Merges all v2.2.0 changes: - feat: receive text/plain share intents as new notes or checklists - feat: new-note shortcut widget for homescreen with auto-layout - feat: checklist button in markdown toolbar - feat: checklist item context menu (copy, duplicate, copy to checklist) - feat: auto-collapse expanded checklist items on drag start - fix: checklist title corruption from missing blank line in toMarkdown() (critical) β†’ defensive parsing in fromMarkdown()/fromJson(), one-time repair migration β†’ CRLF normalization, corruption warning logging - fix: connectivity-change WorkManager fallback for WiFi sync trigger - i18n: Chinese (Simplified) via Weblate - chore: APK packaging optimized (βˆ’28 KB) - chore: version bump 2.1.0 β†’ 2.2.0 (versionCode 28 β†’ 29) Thanks to @madelgijs, @Stowaway2979, and freemen.
2 parents b5c8693 + eeffac5 commit d24d2f2

26 files changed

Lines changed: 1479 additions & 46 deletions

β€ŽCHANGELOG.de.mdβ€Ž

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,51 @@ Das Format basiert auf [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
88

99
---
1010

11+
## [2.2.0] - 2026-03-30
12+
13+
### ✨ Neue Features
14+
15+
**Share-Intent: Text als neue Notiz oder Checkliste empfangen** ([766f67e](https://github.com/inventory69/simple-notes-sync/commit/766f67e))
16+
- Text/plain-Share-Intents aus anderen Apps als neue Notizen oder Checklisten empfangen
17+
- Danke an [@madelgijs](https://github.com/madelgijs) fΓΌr den Feature-Wunsch! ([Discussion #46](https://github.com/inventory69/simple-notes-sync/discussions/46))
18+
19+
**Neue-Notiz-Shortcut-Widget** ([5c79ab6](https://github.com/inventory69/simple-notes-sync/commit/5c79ab6))
20+
- Homescreen-Widget mit Auto-Layout zum schnellen Erstellen neuer Notizen
21+
- Danke an [@Stowaway2979](https://github.com/Stowaway2979) fΓΌr den Feature-Wunsch! ([Discussion #49](https://github.com/inventory69/simple-notes-sync/discussions/49))
22+
23+
**Checklisten-Button in Markdown-Toolbar** ([2157a09](https://github.com/inventory69/simple-notes-sync/commit/2157a09))
24+
- Checklisten-Items direkt aus der Markdown-Editor-Toolbar einfΓΌgen
25+
26+
**Checklisten-Item-KontextmenΓΌ: Kopieren, Duplizieren, In-Checkliste-Kopieren** ([d98edd7](https://github.com/inventory69/simple-notes-sync/commit/d98edd7))
27+
- Fokussiertes MoreVert-MenΓΌ an Checklisten-Items mit Text kopieren, Item duplizieren und Item in andere Checkliste kopieren
28+
- Danke an freemen fΓΌr den Feature-Wunsch!
29+
30+
**Auto-Einklappen erweiterter Items beim Ziehen** ([c030794](https://github.com/inventory69/simple-notes-sync/commit/c030794))
31+
- Erweiterte Checklisten-Items klappen automatisch ein wenn Drag-and-Drop startet
32+
33+
### πŸ› Fehlerbehebungen
34+
35+
**Checklisten-Titel-Korruption durch fehlende Leerzeile behoben** ([c6cd50e](https://github.com/inventory69/simple-notes-sync/commit/c6cd50e))
36+
- **Kritischer Fix:** `toMarkdown()` hat keine Leerzeile zwischen `# Titel` und erstem Checklisten-Item geschrieben β†’ progressive Titel-Korruption bei jedem Sync-Zyklus (erstes Item wurde in den Titel verschluckt)
37+
- Defensives Parsing in `fromMarkdown()` und `fromJson()` erkennt und repariert korrupte Titel
38+
- Einmalige Migration repariert alle lokal gespeicherten korrupten Checklisten-Notizen beim ersten Start nach dem Update
39+
- CRLF-Zeilenumbruch-Normalisierung im Markdown-Parser verhindert Parse-Fehler bei Windows-bearbeiteten Dateien
40+
- Korruptions-Warn-Logging im Markdown-Sync-Import zur Überwachung
41+
- Danke an freemen fΓΌr die Hilfe beim AufspΓΌren der Korruptions-Kaskade!
42+
43+
**WiFi-Sync-WorkManager-Fallback** ([ee0b54c](https://github.com/inventory69/simple-notes-sync/commit/ee0b54c))
44+
- Connectivity-Change-WorkManager-Fallback fΓΌr zuverlΓ€ssigen WiFi-getriggerten Sync hinzugefΓΌgt
45+
46+
### 🌍 Übersetzungen
47+
48+
- Chinesisch (vereinfacht) ΓΌber Weblate aktualisiert ([9bcd4db](https://github.com/inventory69/simple-notes-sync/commit/9bcd4db), [864b23e](https://github.com/inventory69/simple-notes-sync/commit/864b23e))
49+
50+
### πŸ“¦ Code-QualitΓ€t
51+
52+
- APK-Packaging optimiert: gebΓΌndelte kotlin_builtins und LICENSE-Dateien entfernt (βˆ’28 KB)
53+
54+
---
55+
1156
## [2.1.0] - 2026-03-26
1257

1358
### πŸ› Fehlerbehebungen & UX-Verbesserungen

β€ŽCHANGELOG.mdβ€Ž

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

99
---
1010

11+
## [2.2.0] - 2026-03-30
12+
13+
### ✨ New Features
14+
15+
**Share Intent: Receive Text as New Note or Checklist** ([766f67e](https://github.com/inventory69/simple-notes-sync/commit/766f67e))
16+
- Receive text/plain share intents from other apps as new notes or checklists
17+
- Thanks to [@madelgijs](https://github.com/madelgijs) for the feature request! ([Discussion #46](https://github.com/inventory69/simple-notes-sync/discussions/46))
18+
19+
**New-Note Shortcut Widget** ([5c79ab6](https://github.com/inventory69/simple-notes-sync/commit/5c79ab6))
20+
- Homescreen widget with auto-layout for quickly creating new notes
21+
- Thanks to [@Stowaway2979](https://github.com/Stowaway2979) for the feature request! ([Discussion #49](https://github.com/inventory69/simple-notes-sync/discussions/49))
22+
23+
**Checklist Button in Markdown Toolbar** ([2157a09](https://github.com/inventory69/simple-notes-sync/commit/2157a09))
24+
- Insert checklist items directly from the markdown editor toolbar
25+
26+
**Checklist Item Context Menu: Copy, Duplicate, Copy-to-Checklist** ([d98edd7](https://github.com/inventory69/simple-notes-sync/commit/d98edd7))
27+
- Focused MoreVert menu on checklist items with copy text, duplicate item, and copy item to another checklist
28+
- Thanks to freemen for the feature request!
29+
30+
**Auto-Collapse Expanded Items on Drag** ([c030794](https://github.com/inventory69/simple-notes-sync/commit/c030794))
31+
- Expanded checklist items automatically collapse when drag-and-drop starts
32+
33+
### πŸ› Bug Fixes
34+
35+
**Fix Checklist Title Corruption from Missing Blank Line** ([c6cd50e](https://github.com/inventory69/simple-notes-sync/commit/c6cd50e))
36+
- **Critical fix:** `toMarkdown()` was missing a blank line between `# Title` and the first checklist item, causing progressive title corruption on every sync cycle (first item merged into title)
37+
- Defensive parsing in `fromMarkdown()` and `fromJson()` now detects and repairs corrupted titles
38+
- One-time migration repairs all locally stored corrupted checklist notes on first start after update
39+
- CRLF line ending normalization in Markdown parser prevents parse failures on Windows-edited files
40+
- Corruption warning logging in Markdown sync import for monitoring
41+
- Thanks to freemen for helping track down the corruption cascade!
42+
43+
**WiFi Sync WorkManager Fallback** ([ee0b54c](https://github.com/inventory69/simple-notes-sync/commit/ee0b54c))
44+
- Added connectivity-change WorkManager fallback for reliable WiFi-triggered sync
45+
46+
### 🌍 Translations
47+
48+
- Chinese (Simplified) updated via Weblate ([9bcd4db](https://github.com/inventory69/simple-notes-sync/commit/9bcd4db), [864b23e](https://github.com/inventory69/simple-notes-sync/commit/864b23e))
49+
50+
### πŸ“¦ Code Quality
51+
52+
- APK packaging optimized: removed bundled kotlin_builtins and LICENSE files (βˆ’28 KB)
53+
54+
---
55+
1156
## [2.1.0] - 2026-03-26
1257

1358
### πŸ› Bug Fixes & UX Improvements

β€Žandroid/app/build.gradle.ktsβ€Ž

Lines changed: 8 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 = 28 // πŸ†• v2.1.0
24-
versionName = "2.1.0" // πŸ†• v2.1.0
23+
versionCode = 29 // πŸ†• v2.2.0
24+
versionName = "2.2.0" // πŸ†• v2.2.0
2525

2626
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2727
}
@@ -97,14 +97,20 @@ android {
9797
}
9898

9999
// v2.1.0: Remove debug artifacts from release APK
100+
// v2.2.0: kotlin_builtins direct path fix + LICENSE exclusion
100101
packaging {
101102
resources {
102103
excludes += setOf(
103104
"DebugProbesKt.bin",
104105
"kotlin-tooling-metadata.json",
105106
"kotlin/**/*.kotlin_builtins",
107+
"kotlin/*.kotlin_builtins", // direct path (** may not match zero dirs)
106108
"META-INF/*.kotlin_module",
107109
"META-INF/versions/**",
110+
"META-INF/**/LICENSE.txt", // androidx license copies
111+
"META-INF/**/LICENSE",
112+
"META-INF/**/NOTICE.txt",
113+
"META-INF/**/NOTICE",
108114
)
109115
}
110116
}

β€Žandroid/app/src/main/AndroidManifest.xmlβ€Ž

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,18 @@
4545
</activity>
4646

4747
<!-- Editor Activity v1.5.0 (Jetpack Compose) -->
48+
<!-- πŸ†• v2.2.0: Share Intent Target fΓΌr text/plain -->
4849
<activity
4950
android:name=".ui.editor.ComposeNoteEditorActivity"
51+
android:exported="true"
5052
android:windowSoftInputMode="adjustResize"
51-
android:parentActivityName=".ui.main.ComposeMainActivity" />
53+
android:parentActivityName=".ui.main.ComposeMainActivity">
54+
<intent-filter>
55+
<action android:name="android.intent.action.SEND" />
56+
<category android:name="android.intent.category.DEFAULT" />
57+
<data android:mimeType="text/plain" />
58+
</intent-filter>
59+
</activity>
5260

5361
<!-- Settings Activity v1.5.0 (Jetpack Compose) -->
5462
<!-- v1.8.0: Handle locale changes without recreate for smooth language switching -->
@@ -98,12 +106,25 @@
98106
android:resource="@xml/note_widget_info" />
99107
</receiver>
100108

101-
<!-- πŸ†• v1.8.0: Widget Config Activity -->
109+
<!-- v1.8.0: Widget Config Activity -->
102110
<activity
103111
android:name=".widget.NoteWidgetConfigActivity"
104112
android:exported="false"
105113
android:theme="@style/Theme.SimpleNotes" />
106114

115+
<!-- πŸ†• v2.2.0: New-Note Shortcut Widget Receiver -->
116+
<receiver
117+
android:name=".widget.NewNoteWidgetReceiver"
118+
android:exported="true"
119+
android:label="@string/new_note_widget_name">
120+
<intent-filter>
121+
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
122+
</intent-filter>
123+
<meta-data
124+
android:name="android.appwidget.provider"
125+
android:resource="@xml/new_note_widget_info" />
126+
</receiver>
127+
107128
</application>
108129

109130
</manifest>

β€Žandroid/app/src/main/java/dev/dettmer/simplenotes/SimpleNotesApplication.ktβ€Ž

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import android.content.Context
55
import androidx.core.content.edit
66
import dev.dettmer.simplenotes.sync.NetworkMonitor
77
import dev.dettmer.simplenotes.sync.SyncStateManager
8+
import dev.dettmer.simplenotes.storage.NotesStorage
89
import dev.dettmer.simplenotes.utils.Constants
910
import dev.dettmer.simplenotes.utils.Logger
11+
import dev.dettmer.simplenotes.utils.NoteCorruptionRepair
1012
import dev.dettmer.simplenotes.utils.NotificationHelper
1113

1214
class SimpleNotesApplication : Application() {
@@ -67,6 +69,14 @@ class SimpleNotesApplication : Application() {
6769
SyncStateManager.reset()
6870
}
6971
Logger.d(TAG, "βœ… WorkManager-based auto-sync initialized")
72+
73+
// πŸ”§ v2.2.0: Einmalige Reparatur korrupter Checklist-Titel (Bug #07)
74+
try {
75+
val storage = NotesStorage(this)
76+
NoteCorruptionRepair.repairIfNeeded(storage, prefs)
77+
} catch (e: Exception) {
78+
Logger.e(TAG, "⚠️ Corruption repair failed (non-fatal)", e)
79+
}
7080
}
7181

7282
override fun onTerminate() {

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

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,25 @@ type: ${noteType.name.lowercase()}$sortLine
111111
""".trimIndent()
112112

113113
return when (noteType) {
114-
NoteType.TEXT -> header + "\n" + content
114+
NoteType.TEXT -> {
115+
// FIX-01 (v2.2.0): Leerzeile zwischen # Titel und Content (CommonMark-konform)
116+
if (content.isNotBlank()) {
117+
header + "\n\n" + content
118+
} else {
119+
header + "\n"
120+
}
121+
}
115122
NoteType.CHECKLIST -> {
116123
val checklistMarkdown = checklistItems?.sortedBy { it.order }?.joinToString("\n") { item ->
117124
val checkbox = if (item.isChecked) "[x]" else "[ ]"
118125
"- $checkbox ${item.text}"
119126
}.orEmpty()
120-
header + "\n" + checklistMarkdown
127+
// FIX-01 (v2.2.0): Leerzeile zwischen # Titel und Items verhindert Titel-Korruption beim Re-Import
128+
if (checklistMarkdown.isNotEmpty()) {
129+
header + "\n\n" + checklistMarkdown
130+
} else {
131+
header + "\n"
132+
}
121133
}
122134
}
123135
}
@@ -183,10 +195,39 @@ type: ${noteType.name.lowercase()}$sortLine
183195
}
184196
}
185197

198+
// FIX-03 (v2.2.0): Titel-Korrektur fΓΌr CHECKLIST-Notizen aus korruptem JSON
199+
var cleanTitle = rawNote.title
200+
if (noteType == NoteType.CHECKLIST) {
201+
val checklistPatternInTitle = Regex("""[-*]\s*\[([ xX])\]\s+""")
202+
if (checklistPatternInTitle.containsMatchIn(cleanTitle)) {
203+
val splitMatch = checklistPatternInTitle.find(cleanTitle)!!
204+
val rescuedText = cleanTitle.substring(splitMatch.range.first)
205+
cleanTitle = cleanTitle.substring(0, splitMatch.range.first).trim()
206+
207+
// Alle verschluckten Items aus dem Titel extrahieren
208+
val rescuedItems = Regex("""[-*]\s*\[([ xX])\]\s+(.+?)(?=\s*[-*]\s*\[|${'$'})""")
209+
.findAll(rescuedText).mapIndexed { idx, m ->
210+
ChecklistItem(
211+
id = UUID.randomUUID().toString(),
212+
text = m.groupValues[2].trim(),
213+
isChecked = m.groupValues[1].lowercase() == "x",
214+
order = idx
215+
)
216+
}.toList()
217+
218+
if (rescuedItems.isNotEmpty()) {
219+
checklistItems = (rescuedItems + (checklistItems ?: emptyList()))
220+
.mapIndexed { i, item -> item.copy(order = i) }
221+
Logger.w(TAG, "⚠️ CORRUPTION FIX (JSON): '${rawNote.title}' β†’ '$cleanTitle', " +
222+
"rescued ${rescuedItems.size} item(s)")
223+
}
224+
}
225+
}
226+
186227
// Note mit korrekten Werten erstellen
187228
Note(
188229
id = rawNote.id,
189-
title = rawNote.title,
230+
title = cleanTitle,
190231
content = rawNote.content,
191232
createdAt = rawNote.createdAt,
192233
updatedAt = rawNote.updatedAt,
@@ -254,9 +295,12 @@ type: ${noteType.name.lowercase()}$sortLine
254295
*/
255296
fun fromMarkdown(md: String, serverModifiedTime: Long? = null): Note? {
256297
return try {
298+
// FIX-06 (v2.2.0): Normalisiere ZeilenumbrΓΌche vor Regex-Matching
299+
val normalizedMd = md.replace("\r\n", "\n").replace("\r", "\n")
300+
257301
// Parse YAML Frontmatter + Markdown Content
258302
val frontmatterRegex = Regex("^---\\n(.+?)\\n---\\n(.*)$", RegexOption.DOT_MATCHES_ALL)
259-
val match = frontmatterRegex.find(md) ?: return null
303+
val match = frontmatterRegex.find(normalizedMd) ?: return null
260304

261305
val yamlBlock = match.groupValues[1]
262306
val contentBlock = match.groupValues[2]
@@ -273,10 +317,21 @@ type: ${noteType.name.lowercase()}$sortLine
273317
}.toMap()
274318

275319
// Extract title from first # heading
276-
val title = contentBlock.lines()
320+
var title = contentBlock.lines()
277321
.firstOrNull { it.startsWith("# ") }
278322
?.removePrefix("# ")?.trim() ?: "Untitled"
279323

324+
// FIX-02 (v2.2.0): Titel-Korrektur wenn Checklist-Pattern im Titel
325+
// Bug: fehlende Leerzeile in toMarkdown() fΓΌhrte zu "Titel- [ ] Item" als Titelzeile
326+
val checklistPatternInTitle = Regex("""[-*]\s*\[([ xX])\]\s+""")
327+
var rescuedItemLine: String? = null
328+
if (checklistPatternInTitle.containsMatchIn(title)) {
329+
val splitMatch = checklistPatternInTitle.find(title)!!
330+
rescuedItemLine = title.substring(splitMatch.range.first).trim()
331+
title = title.substring(0, splitMatch.range.first).trim()
332+
Logger.w(TAG, "⚠️ CORRUPTION FIX: Checklist pattern in title β†’ cleaned to '$title', rescued: '$rescuedItemLine'")
333+
}
334+
280335
// v1.4.0: PrΓΌfe ob type: checklist im Frontmatter
281336
val noteTypeStr = metadata["type"]?.lowercase() ?: "text"
282337
val noteType = when (noteTypeStr) {
@@ -302,14 +357,25 @@ type: ${noteType.name.lowercase()}$sortLine
302357
contentBlock.trim()
303358
}
304359

360+
// FIX-02 (v2.2.0): Gerettetes Item vor den restlichen Content einfΓΌgen
361+
val effectiveContent = if (rescuedItemLine != null) {
362+
if (contentAfterTitle.isNotEmpty()) {
363+
rescuedItemLine + "\n" + contentAfterTitle
364+
} else {
365+
rescuedItemLine
366+
}
367+
} else {
368+
contentAfterTitle
369+
}
370+
305371
val content: String
306372
val checklistItems: List<ChecklistItem>?
307373

308374
if (noteType == NoteType.CHECKLIST) {
309375
// Parse Checklist Items
310376
// πŸ†• v1.10.0-P2: More lenient regex β€” supports `*` prefix and extra spaces
311377
val checklistRegex = Regex("""^[-*]\s+\[([ xX])\]\s+(.*)$""", RegexOption.MULTILINE)
312-
checklistItems = checklistRegex.findAll(contentAfterTitle).mapIndexed { index, matchResult ->
378+
checklistItems = checklistRegex.findAll(effectiveContent).mapIndexed { index, matchResult ->
313379
ChecklistItem(
314380
id = UUID.randomUUID().toString(),
315381
text = matchResult.groupValues[2].trim(),
@@ -319,7 +385,7 @@ type: ${noteType.name.lowercase()}$sortLine
319385
}.toList().ifEmpty { null }
320386
content = "" // Checklisten haben keinen "content"
321387
} else {
322-
content = contentAfterTitle
388+
content = effectiveContent
323389
checklistItems = null
324390
}
325391

β€Žandroid/app/src/main/java/dev/dettmer/simplenotes/sync/MarkdownSyncManager.ktβ€Ž

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,17 @@ internal class MarkdownSyncManager(
378378
continue
379379
}
380380

381+
// FIX-05 (v2.2.0): Checklist-Import-Logging + Korruptions-Warnung
382+
if (mdNote.noteType == NoteType.CHECKLIST) {
383+
Logger.d(TAG, " πŸ“‹ Checklist import: title='${mdNote.title}', items=${mdNote.checklistItems?.size ?: 0}")
384+
if (mdNote.title.contains("[ ]") || mdNote.title.contains("[x]") || mdNote.title.contains("[X]")) {
385+
@Suppress("MagicNumber")
386+
val previewLength = 200
387+
Logger.e(TAG, "🚨 CORRUPTION WARNING: Checklist pattern in title after parse: '${mdNote.title}'")
388+
Logger.e(TAG, "🚨 Source: ${resource.name}, first $previewLength chars: ${mdContent.take(previewLength)}")
389+
}
390+
}
391+
381392
// πŸ†• v1.11.0: Skip Markdown files whose note ID was just exported in this sync cycle.
382393
if (mdNote.id in excludeNoteIds) {
383394
skippedCount++

0 commit comments

Comments
Β (0)