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:
@@ -5,6 +5,7 @@ import androidx.glance.GlanceId
|
|||||||
import androidx.glance.action.ActionParameters
|
import androidx.glance.action.ActionParameters
|
||||||
import androidx.glance.appwidget.action.ActionCallback
|
import androidx.glance.appwidget.action.ActionCallback
|
||||||
import androidx.glance.appwidget.state.updateAppWidgetState
|
import androidx.glance.appwidget.state.updateAppWidgetState
|
||||||
|
import dev.dettmer.simplenotes.models.ChecklistSortOption
|
||||||
import dev.dettmer.simplenotes.models.SyncStatus
|
import dev.dettmer.simplenotes.models.SyncStatus
|
||||||
import dev.dettmer.simplenotes.storage.NotesStorage
|
import dev.dettmer.simplenotes.storage.NotesStorage
|
||||||
import dev.dettmer.simplenotes.utils.Logger
|
import dev.dettmer.simplenotes.utils.Logger
|
||||||
@@ -51,14 +52,32 @@ class ToggleChecklistItemAction : ActionCallback {
|
|||||||
} else item
|
} else item
|
||||||
} ?: return
|
} ?: 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(
|
val updatedNote = note.copy(
|
||||||
checklistItems = updatedItems,
|
checklistItems = sortedItems,
|
||||||
updatedAt = System.currentTimeMillis(),
|
updatedAt = System.currentTimeMillis(),
|
||||||
syncStatus = SyncStatus.PENDING
|
syncStatus = SyncStatus.PENDING
|
||||||
)
|
)
|
||||||
|
|
||||||
storage.saveNote(updatedNote)
|
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
|
// 🐛 FIX: Glance-State ändern um Re-Render zu erzwingen
|
||||||
updateAppWidgetState(context, glanceId) { prefs ->
|
updateAppWidgetState(context, glanceId) { prefs ->
|
||||||
|
|||||||
@@ -38,9 +38,11 @@ import androidx.glance.layout.width
|
|||||||
import androidx.glance.text.Text
|
import androidx.glance.text.Text
|
||||||
import androidx.glance.text.TextStyle
|
import androidx.glance.text.TextStyle
|
||||||
import dev.dettmer.simplenotes.R
|
import dev.dettmer.simplenotes.R
|
||||||
|
import dev.dettmer.simplenotes.models.ChecklistSortOption
|
||||||
import dev.dettmer.simplenotes.models.Note
|
import dev.dettmer.simplenotes.models.Note
|
||||||
import dev.dettmer.simplenotes.models.NoteType
|
import dev.dettmer.simplenotes.models.NoteType
|
||||||
import dev.dettmer.simplenotes.ui.editor.ComposeNoteEditorActivity
|
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
|
* 🆕 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
|
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
|
@Composable
|
||||||
fun NoteWidgetContent(
|
fun NoteWidgetContent(
|
||||||
note: Note?,
|
note: Note?,
|
||||||
@@ -404,13 +429,35 @@ private fun ChecklistCompactView(
|
|||||||
isLocked: Boolean,
|
isLocked: Boolean,
|
||||||
glanceId: GlanceId
|
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 visibleItems = items.take(maxItems)
|
||||||
val remainingCount = items.size - visibleItems.size
|
val remainingCount = items.size - visibleItems.size
|
||||||
val checkedCount = items.count { it.isChecked }
|
|
||||||
|
|
||||||
Column(modifier = GlanceModifier.padding(horizontal = 8.dp, vertical = 2.dp)) {
|
Column(modifier = GlanceModifier.padding(horizontal = 8.dp, vertical = 2.dp)) {
|
||||||
|
var separatorShown = false
|
||||||
visibleItems.forEach { item ->
|
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) {
|
if (isLocked) {
|
||||||
Row(
|
Row(
|
||||||
modifier = GlanceModifier
|
modifier = GlanceModifier
|
||||||
@@ -419,7 +466,7 @@ private fun ChecklistCompactView(
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = if (item.isChecked) "✅" else "☐",
|
text = if (item.isChecked) "☑️" else "☐", // 🆕 v1.8.1 (IMPL_06)
|
||||||
style = TextStyle(fontSize = 14.sp)
|
style = TextStyle(fontSize = 14.sp)
|
||||||
)
|
)
|
||||||
Spacer(modifier = GlanceModifier.width(6.dp))
|
Spacer(modifier = GlanceModifier.width(6.dp))
|
||||||
@@ -477,15 +524,41 @@ private fun ChecklistFullView(
|
|||||||
isLocked: Boolean,
|
isLocked: Boolean,
|
||||||
glanceId: GlanceId
|
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(
|
LazyColumn(
|
||||||
modifier = GlanceModifier
|
modifier = GlanceModifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(horizontal = 8.dp)
|
.padding(horizontal = 8.dp)
|
||||||
) {
|
) {
|
||||||
items(items.size) { index ->
|
items(totalItems) { index ->
|
||||||
val item = items[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) {
|
if (isLocked) {
|
||||||
Row(
|
Row(
|
||||||
@@ -495,7 +568,7 @@ private fun ChecklistFullView(
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = if (item.isChecked) "✅" else "☐",
|
text = if (item.isChecked) "☑️" else "☐", // 🆕 v1.8.1 (IMPL_06)
|
||||||
style = TextStyle(fontSize = 16.sp)
|
style = TextStyle(fontSize = 16.sp)
|
||||||
)
|
)
|
||||||
Spacer(modifier = GlanceModifier.width(8.dp))
|
Spacer(modifier = GlanceModifier.width(8.dp))
|
||||||
|
|||||||
Reference in New Issue
Block a user