feat(preview): IMPL_03 - Add checklist sorting in main preview

Changes:
- Note.kt: Add checklistSortOption field (String?, nullable for backward compatibility)
- Note.fromJson(): Parse checklistSortOption from JSON
- Note.fromMarkdown(): Parse sort field from YAML frontmatter
- Note.toMarkdown(): Include sort field in YAML frontmatter for checklists
- ChecklistPreviewHelper.kt: New file with sortChecklistItemsForPreview() and generateChecklistPreview()
- NoteEditorViewModel.loadNote(): Load and restore checklistSortOption from note
- NoteEditorViewModel.saveNote(): Persist checklistSortOption when saving (both new and existing notes)
- NoteCardCompact.kt: Use generateChecklistPreview() for sorted preview (includes IMPL_06 emoji change)
- NoteCardGrid.kt: Use generateChecklistPreview() for sorted preview (includes IMPL_06 emoji change)
This commit is contained in:
inventory69
2026-02-11 09:30:18 +01:00
parent ffe0e46e3d
commit 2c43b47e96
5 changed files with 109 additions and 14 deletions

View File

@@ -24,7 +24,9 @@ data class Note(
val syncStatus: SyncStatus = SyncStatus.LOCAL_ONLY, val syncStatus: SyncStatus = SyncStatus.LOCAL_ONLY,
// v1.4.0: Checklisten-Felder // v1.4.0: Checklisten-Felder
val noteType: NoteType = NoteType.TEXT, val noteType: NoteType = NoteType.TEXT,
val checklistItems: List<ChecklistItem>? = null val checklistItems: List<ChecklistItem>? = null,
// 🆕 v1.8.1 (IMPL_03): Persistierte Sortierung
val checklistSortOption: String? = null
) { ) {
/** /**
* Serialisiert Note zu JSON * Serialisiert Note zu JSON
@@ -71,13 +73,20 @@ data class Note(
* v1.4.0: Unterstützt jetzt auch Checklisten-Format * v1.4.0: Unterstützt jetzt auch Checklisten-Format
*/ */
fun toMarkdown(): String { fun toMarkdown(): String {
// 🆕 v1.8.1 (IMPL_03): Sortierung im Frontmatter
val sortLine = if (noteType == NoteType.CHECKLIST && checklistSortOption != null) {
"\nsort: $checklistSortOption"
} else {
""
}
val header = """ val header = """
--- ---
id: $id id: $id
created: ${formatISO8601(createdAt)} created: ${formatISO8601(createdAt)}
updated: ${formatISO8601(updatedAt)} updated: ${formatISO8601(updatedAt)}
device: $deviceId device: $deviceId
type: ${noteType.name.lowercase()} type: ${noteType.name.lowercase()}$sortLine
--- ---
# $title # $title
@@ -119,6 +128,14 @@ type: ${noteType.name.lowercase()}
NoteType.TEXT NoteType.TEXT
} }
// 🆕 v1.8.1 (IMPL_03): Gespeicherte Sortierung laden
val checklistSortOption = if (jsonObject.has("checklistSortOption") &&
!jsonObject.get("checklistSortOption").isJsonNull) {
jsonObject.get("checklistSortOption").asString
} else {
null
}
// Parsen der Basis-Note // Parsen der Basis-Note
val rawNote = gson.fromJson(json, NoteRaw::class.java) val rawNote = gson.fromJson(json, NoteRaw::class.java)
@@ -158,7 +175,8 @@ type: ${noteType.name.lowercase()}
deviceId = rawNote.deviceId, deviceId = rawNote.deviceId,
syncStatus = rawNote.syncStatus ?: SyncStatus.LOCAL_ONLY, syncStatus = rawNote.syncStatus ?: SyncStatus.LOCAL_ONLY,
noteType = noteType, noteType = noteType,
checklistItems = checklistItems checklistItems = checklistItems,
checklistSortOption = checklistSortOption // 🆕 v1.8.1 (IMPL_03)
) )
} catch (e: Exception) { } catch (e: Exception) {
Logger.w(TAG, "Failed to parse JSON: ${e.message}") Logger.w(TAG, "Failed to parse JSON: ${e.message}")
@@ -246,6 +264,9 @@ type: ${noteType.name.lowercase()}
else -> NoteType.TEXT else -> NoteType.TEXT
} }
// 🆕 v1.8.1 (IMPL_03): Gespeicherte Sortierung aus YAML laden
val checklistSortOption = metadata["sort"]
// v1.4.0: Parse Content basierend auf Typ // v1.4.0: Parse Content basierend auf Typ
// FIX: Robusteres Parsing - suche nach dem Titel-Header und extrahiere den Rest // FIX: Robusteres Parsing - suche nach dem Titel-Header und extrahiere den Rest
val titleLineIndex = contentBlock.lines().indexOfFirst { it.startsWith("# ") } val titleLineIndex = contentBlock.lines().indexOfFirst { it.startsWith("# ") }
@@ -300,7 +321,8 @@ type: ${noteType.name.lowercase()}
deviceId = metadata["device"] ?: "desktop", deviceId = metadata["device"] ?: "desktop",
syncStatus = SyncStatus.SYNCED, // Annahme: Vom Server importiert syncStatus = SyncStatus.SYNCED, // Annahme: Vom Server importiert
noteType = noteType, noteType = noteType,
checklistItems = checklistItems checklistItems = checklistItems,
checklistSortOption = checklistSortOption // 🆕 v1.8.1 (IMPL_03)
) )
} catch (e: Exception) { } catch (e: Exception) {
Logger.w(TAG, "Failed to parse Markdown: ${e.message}") Logger.w(TAG, "Failed to parse Markdown: ${e.message}")

View File

@@ -109,6 +109,16 @@ class NoteEditorViewModel(
} }
if (note.noteType == NoteType.CHECKLIST) { if (note.noteType == NoteType.CHECKLIST) {
// 🆕 v1.8.1 (IMPL_03): Gespeicherte Sortierung laden
note.checklistSortOption?.let { sortName ->
try {
_lastChecklistSortOption.value = ChecklistSortOption.valueOf(sortName)
} catch (e: IllegalArgumentException) {
Logger.w(TAG, "Unknown sort option '$sortName', using MANUAL")
_lastChecklistSortOption.value = ChecklistSortOption.MANUAL
}
}
val items = note.checklistItems?.sortedBy { it.order }?.map { val items = note.checklistItems?.sortedBy { it.order }?.map {
ChecklistItemState( ChecklistItemState(
id = it.id, id = it.id,
@@ -351,6 +361,7 @@ class NoteEditorViewModel(
content = "", // Empty for checklists content = "", // Empty for checklists
noteType = NoteType.CHECKLIST, noteType = NoteType.CHECKLIST,
checklistItems = validItems, checklistItems = validItems,
checklistSortOption = _lastChecklistSortOption.value.name, // 🆕 v1.8.1 (IMPL_03)
updatedAt = System.currentTimeMillis(), updatedAt = System.currentTimeMillis(),
syncStatus = SyncStatus.PENDING syncStatus = SyncStatus.PENDING
) )
@@ -360,6 +371,7 @@ class NoteEditorViewModel(
content = "", content = "",
noteType = NoteType.CHECKLIST, noteType = NoteType.CHECKLIST,
checklistItems = validItems, checklistItems = validItems,
checklistSortOption = _lastChecklistSortOption.value.name, // 🆕 v1.8.1 (IMPL_03)
deviceId = DeviceIdGenerator.getDeviceId(getApplication()), deviceId = DeviceIdGenerator.getDeviceId(getApplication()),
syncStatus = SyncStatus.LOCAL_ONLY syncStatus = SyncStatus.LOCAL_ONLY
) )

View File

@@ -0,0 +1,63 @@
package dev.dettmer.simplenotes.ui.main.components
import dev.dettmer.simplenotes.models.ChecklistItem
import dev.dettmer.simplenotes.models.ChecklistSortOption
/**
* 🆕 v1.8.1 (IMPL_03): Helper-Funktionen für die Checklisten-Vorschau in Main Activity.
*
* Stellt sicher, dass die Sortierung aus dem Editor konsistent
* in allen Preview-Components (NoteCard, NoteCardCompact, NoteCardGrid)
* angezeigt wird.
*/
/**
* Sortiert Checklist-Items für die Vorschau basierend auf der
* gespeicherten Sortier-Option.
*/
fun sortChecklistItemsForPreview(
items: List<ChecklistItem>,
sortOptionName: String?
): List<ChecklistItem> {
val sortOption = try {
sortOptionName?.let { ChecklistSortOption.valueOf(it) }
} catch (e: IllegalArgumentException) {
null
} ?: ChecklistSortOption.MANUAL
return when (sortOption) {
ChecklistSortOption.MANUAL,
ChecklistSortOption.UNCHECKED_FIRST ->
items.sortedBy { it.isChecked }
ChecklistSortOption.CHECKED_FIRST ->
items.sortedByDescending { it.isChecked }
ChecklistSortOption.ALPHABETICAL_ASC ->
items.sortedBy { it.text.lowercase() }
ChecklistSortOption.ALPHABETICAL_DESC ->
items.sortedByDescending { it.text.lowercase() }
}
}
/**
* Generiert den Vorschau-Text für eine Checkliste mit korrekter
* Sortierung und passenden Emojis.
*
* @param items Die Checklisten-Items
* @param sortOptionName Der Name der ChecklistSortOption (oder null für MANUAL)
* @return Formatierter Preview-String mit Emojis und Zeilenumbrüchen
*
* 🆕 v1.8.1 (IMPL_06): Emoji-Änderung (☑️ statt ✅ für checked items)
*/
fun generateChecklistPreview(
items: List<ChecklistItem>,
sortOptionName: String?
): String {
val sorted = sortChecklistItemsForPreview(items, sortOptionName)
return sorted.joinToString("\n") { item ->
val prefix = if (item.isChecked) "☑️" else ""
"$prefix ${item.text}"
}
}

View File

@@ -149,10 +149,9 @@ fun NoteCardCompact(
text = when (note.noteType) { text = when (note.noteType) {
NoteType.TEXT -> note.content NoteType.TEXT -> note.content
NoteType.CHECKLIST -> { NoteType.CHECKLIST -> {
note.checklistItems // 🆕 v1.8.1 (IMPL_03 + IMPL_06): Sortierte Preview mit neuen Emojis
?.joinToString("\n") { item -> note.checklistItems?.let { items ->
val prefix = if (item.isChecked) "" else "" generateChecklistPreview(items, note.checklistSortOption)
"$prefix ${item.text}"
} ?: "" } ?: ""
} }
}, },

View File

@@ -163,10 +163,9 @@ fun NoteCardGrid(
text = when (note.noteType) { text = when (note.noteType) {
NoteType.TEXT -> note.content NoteType.TEXT -> note.content
NoteType.CHECKLIST -> { NoteType.CHECKLIST -> {
note.checklistItems // 🆕 v1.8.1 (IMPL_03 + IMPL_06): Sortierte Preview mit neuen Emojis
?.joinToString("\n") { item -> note.checklistItems?.let { items ->
val prefix = if (item.isChecked) "" else "" generateChecklistPreview(items, note.checklistSortOption)
"$prefix ${item.text}"
} ?: "" } ?: ""
} }
}, },