feat(widget): IMPL_04 - Add sorting & separator to widget checklists

Changes:
- NoteWidgetContent.kt: Import sortChecklistItemsForPreview and ChecklistSortOption
- NoteWidgetContent.kt: Add WidgetCheckedItemsSeparator composable
- NoteWidgetContent.kt: Apply note.checklistSortOption in ChecklistCompactView
- NoteWidgetContent.kt: Apply note.checklistSortOption in ChecklistFullView
- NoteWidgetContent.kt: Integrate separator for MANUAL/UNCHECKED_FIRST modes
- NoteWidgetContent.kt: Change  to ☑️ emoji (IMPL_06)
- NoteWidgetActions.kt: Add auto-sort after toggle in ToggleChecklistItemAction

Widgets now match editor behavior: apply saved sort option, show separator
between unchecked/checked items, and auto-sort when toggling checkboxes.
This commit is contained in:
inventory69
2026-02-11 10:32:38 +01:00
parent c72b3fe1c0
commit 66d98c0cad
2 changed files with 101 additions and 9 deletions

View File

@@ -5,6 +5,7 @@ import androidx.glance.GlanceId
import androidx.glance.action.ActionParameters
import androidx.glance.appwidget.action.ActionCallback
import androidx.glance.appwidget.state.updateAppWidgetState
import dev.dettmer.simplenotes.models.ChecklistSortOption
import dev.dettmer.simplenotes.models.SyncStatus
import dev.dettmer.simplenotes.storage.NotesStorage
import dev.dettmer.simplenotes.utils.Logger
@@ -51,14 +52,32 @@ class ToggleChecklistItemAction : ActionCallback {
} else item
} ?: return
// 🆕 v1.8.1 (IMPL_04): Auto-Sort nach Toggle
// Konsistent mit NoteEditorViewModel.updateChecklistItemChecked
val sortOption = try {
note.checklistSortOption?.let { ChecklistSortOption.valueOf(it) }
} catch (e: IllegalArgumentException) { null }
?: ChecklistSortOption.MANUAL
val sortedItems = if (sortOption == ChecklistSortOption.MANUAL ||
sortOption == ChecklistSortOption.UNCHECKED_FIRST) {
val unchecked = updatedItems.filter { !it.isChecked }
val checked = updatedItems.filter { it.isChecked }
(unchecked + checked).mapIndexed { index, item ->
item.copy(order = index)
}
} else {
updatedItems.mapIndexed { index, item -> item.copy(order = index) }
}
val updatedNote = note.copy(
checklistItems = updatedItems,
checklistItems = sortedItems,
updatedAt = System.currentTimeMillis(),
syncStatus = SyncStatus.PENDING
)
storage.saveNote(updatedNote)
Logger.d(TAG, "Toggled checklist item '$itemId' in widget")
Logger.d(TAG, "Toggled + auto-sorted checklist item '$itemId' in widget")
// 🐛 FIX: Glance-State ändern um Re-Render zu erzwingen
updateAppWidgetState(context, glanceId) { prefs ->

View File

@@ -38,9 +38,11 @@ import androidx.glance.layout.width
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import dev.dettmer.simplenotes.R
import dev.dettmer.simplenotes.models.ChecklistSortOption
import dev.dettmer.simplenotes.models.Note
import dev.dettmer.simplenotes.models.NoteType
import dev.dettmer.simplenotes.ui.editor.ComposeNoteEditorActivity
import dev.dettmer.simplenotes.ui.main.components.sortChecklistItemsForPreview
/**
* 🆕 v1.8.0: Glance Composable Content für das Notiz-Widget
@@ -72,6 +74,29 @@ private fun DpSize.toSizeClass(): WidgetSizeClass = when {
else -> WidgetSizeClass.WIDE_TALL
}
/**
* 🆕 v1.8.1 (IMPL_04): Separator zwischen erledigten und unerledigten Items im Widget.
* Glance-kompatible Version von CheckedItemsSeparator.
*/
@Composable
private fun WidgetCheckedItemsSeparator(checkedCount: Int) {
Row(
modifier = GlanceModifier
.fillMaxWidth()
.padding(vertical = 4.dp, horizontal = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "── $checkedCount ✔ ──",
style = TextStyle(
color = GlanceTheme.colors.outline,
fontSize = 11.sp
)
)
}
}
@Composable
fun NoteWidgetContent(
note: Note?,
@@ -404,13 +429,35 @@ private fun ChecklistCompactView(
isLocked: Boolean,
glanceId: GlanceId
) {
val items = note.checklistItems?.sortedBy { it.order } ?: return
// 🆕 v1.8.1 (IMPL_04): Sortierung aus Editor übernehmen
val items = note.checklistItems?.let { rawItems ->
sortChecklistItemsForPreview(rawItems, note.checklistSortOption)
} ?: return
// 🆕 v1.8.1 (IMPL_04): Separator-Logik
val uncheckedCount = items.count { !it.isChecked }
val checkedCount = items.count { it.isChecked }
val sortOption = try {
note.checklistSortOption?.let { ChecklistSortOption.valueOf(it) }
} catch (e: IllegalArgumentException) { null }
?: ChecklistSortOption.MANUAL
val showSeparator = (sortOption == ChecklistSortOption.MANUAL ||
sortOption == ChecklistSortOption.UNCHECKED_FIRST) &&
uncheckedCount > 0 && checkedCount > 0
val visibleItems = items.take(maxItems)
val remainingCount = items.size - visibleItems.size
val checkedCount = items.count { it.isChecked }
Column(modifier = GlanceModifier.padding(horizontal = 8.dp, vertical = 2.dp)) {
var separatorShown = false
visibleItems.forEach { item ->
// 🆕 v1.8.1: Separator vor dem ersten checked Item anzeigen
if (showSeparator && !separatorShown && item.isChecked) {
WidgetCheckedItemsSeparator(checkedCount = checkedCount)
separatorShown = true
}
if (isLocked) {
Row(
modifier = GlanceModifier
@@ -419,7 +466,7 @@ private fun ChecklistCompactView(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = if (item.isChecked) "" else "",
text = if (item.isChecked) "☑️" else "", // 🆕 v1.8.1 (IMPL_06)
style = TextStyle(fontSize = 14.sp)
)
Spacer(modifier = GlanceModifier.width(6.dp))
@@ -477,15 +524,41 @@ private fun ChecklistFullView(
isLocked: Boolean,
glanceId: GlanceId
) {
val items = note.checklistItems?.sortedBy { it.order } ?: return
// 🆕 v1.8.1 (IMPL_04): Sortierung aus Editor übernehmen
val items = note.checklistItems?.let { rawItems ->
sortChecklistItemsForPreview(rawItems, note.checklistSortOption)
} ?: return
// 🆕 v1.8.1 (IMPL_04): Separator-Logik
val uncheckedCount = items.count { !it.isChecked }
val checkedCount = items.count { it.isChecked }
val sortOption = try {
note.checklistSortOption?.let { ChecklistSortOption.valueOf(it) }
} catch (e: IllegalArgumentException) { null }
?: ChecklistSortOption.MANUAL
val showSeparator = (sortOption == ChecklistSortOption.MANUAL ||
sortOption == ChecklistSortOption.UNCHECKED_FIRST) &&
uncheckedCount > 0 && checkedCount > 0
// 🆕 v1.8.1: Berechne die Gesamtanzahl der Elemente inklusive Separator
val totalItems = items.size + if (showSeparator) 1 else 0
LazyColumn(
modifier = GlanceModifier
.fillMaxSize()
.padding(horizontal = 8.dp)
) {
items(items.size) { index ->
val item = items[index]
items(totalItems) { index ->
// 🆕 v1.8.1: Separator an Position uncheckedCount einfügen
if (showSeparator && index == uncheckedCount) {
WidgetCheckedItemsSeparator(checkedCount = checkedCount)
return@items
}
// Tatsächlichen Item-Index berechnen (nach Separator um 1 verschoben)
val itemIndex = if (showSeparator && index > uncheckedCount) index - 1 else index
val item = items.getOrNull(itemIndex) ?: return@items
if (isLocked) {
Row(
@@ -495,7 +568,7 @@ private fun ChecklistFullView(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = if (item.isChecked) "" else "",
text = if (item.isChecked) "☑️" else "", // 🆕 v1.8.1 (IMPL_06)
style = TextStyle(fontSize = 16.sp)
)
Spacer(modifier = GlanceModifier.width(8.dp))