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:
@@ -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}")
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -149,11 +149,10 @@ 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}"
|
} ?: ""
|
||||||
} ?: ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
|||||||
@@ -163,11 +163,10 @@ 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}"
|
} ?: ""
|
||||||
} ?: ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
|||||||
Reference in New Issue
Block a user