feat(editor): IMPL_14 - Separator drag cross-boundary with auto-toggle
Separator is now its own LazyColumn item instead of being rendered inline inside the first checked item's composable. This fixes: Bug A: Separator disappearing during drag (was hidden as workaround for height inflation). Now always visible with primary color highlight. Bug B: Cross-boundary move blocked (isChecked != toItem.isChecked returned early). Now auto-toggles isChecked when crossing boundary — like Google Tasks. Bug C: Drag flicker at separator boundary (draggingItemIndex updated even when onMove was a no-op → oscillation). Index remapping via visualToDataIndex()/dataToVisualIndex() ensures correct data indices. Architecture changes: - DragDropListState: separatorVisualIndex, index remapping functions, isAdjacentSkippingSeparator() skips separator in swap detection - NoteEditorScreen: Extracted DraggableChecklistItem composable, 3 LazyColumn blocks (unchecked items, separator, checked items), removed hardcoded AnimatedVisibility(visible=true) wrapper - NoteEditorViewModel: moveChecklistItem() allows cross-boundary moves with automatic isChecked toggle - CheckedItemsSeparator: isDragActive parameter for visual feedback Files changed: - DragDropListState.kt (+56 lines) - NoteEditorScreen.kt (refactored, net +84 lines) - NoteEditorViewModel.kt (simplified cross-boundary logic) - CheckedItemsSeparator.kt (drag-awareness parameter)
This commit is contained in:
@@ -25,6 +25,7 @@ import kotlinx.coroutines.launch
|
|||||||
* v1.5.0: NoteEditor Redesign
|
* v1.5.0: NoteEditor Redesign
|
||||||
* v1.8.0: IMPL_023 - Drag & Drop Fix (pointerInput key + Handle-only drag)
|
* v1.8.0: IMPL_023 - Drag & Drop Fix (pointerInput key + Handle-only drag)
|
||||||
* v1.8.0: IMPL_023b - Flicker-Fix (Straddle-Target-Center-Erkennung statt Mittelpunkt)
|
* v1.8.0: IMPL_023b - Flicker-Fix (Straddle-Target-Center-Erkennung statt Mittelpunkt)
|
||||||
|
* v1.8.1: IMPL_14 - Separator als eigenes Item, Cross-Boundary-Drag mit Auto-Toggle
|
||||||
*/
|
*/
|
||||||
class DragDropListState(
|
class DragDropListState(
|
||||||
private val state: LazyListState,
|
private val state: LazyListState,
|
||||||
@@ -41,6 +42,9 @@ class DragDropListState(
|
|||||||
private var draggingItemSize by mutableStateOf(0)
|
private var draggingItemSize by mutableStateOf(0)
|
||||||
private var overscrollJob by mutableStateOf<Job?>(null)
|
private var overscrollJob by mutableStateOf<Job?>(null)
|
||||||
|
|
||||||
|
// 🆕 v1.8.1 IMPL_14: Visual-Index des Separators (-1 = kein Separator)
|
||||||
|
var separatorVisualIndex by mutableStateOf(-1)
|
||||||
|
|
||||||
val draggingItemOffset: Float
|
val draggingItemOffset: Float
|
||||||
get() = draggingItemLayoutInfo?.let { item ->
|
get() = draggingItemLayoutInfo?.let { item ->
|
||||||
draggingItemInitialOffset + draggingItemDraggedDelta - item.offset
|
draggingItemInitialOffset + draggingItemDraggedDelta - item.offset
|
||||||
@@ -50,6 +54,23 @@ class DragDropListState(
|
|||||||
get() = state.layoutInfo.visibleItemsInfo
|
get() = state.layoutInfo.visibleItemsInfo
|
||||||
.firstOrNull { it.index == draggingItemIndex }
|
.firstOrNull { it.index == draggingItemIndex }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🆕 v1.8.1 IMPL_14: Visual-Index → Data-Index Konvertierung.
|
||||||
|
* Wenn ein Separator existiert, sind alle Items nach dem Separator um 1 verschoben.
|
||||||
|
*/
|
||||||
|
fun visualToDataIndex(visualIndex: Int): Int {
|
||||||
|
if (separatorVisualIndex < 0) return visualIndex
|
||||||
|
return if (visualIndex > separatorVisualIndex) visualIndex - 1 else visualIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🆕 v1.8.1 IMPL_14: Data-Index → Visual-Index Konvertierung.
|
||||||
|
*/
|
||||||
|
fun dataToVisualIndex(dataIndex: Int): Int {
|
||||||
|
if (separatorVisualIndex < 0) return dataIndex
|
||||||
|
return if (dataIndex >= separatorVisualIndex) dataIndex + 1 else dataIndex
|
||||||
|
}
|
||||||
|
|
||||||
fun onDragStart(offset: Offset, itemIndex: Int) {
|
fun onDragStart(offset: Offset, itemIndex: Int) {
|
||||||
draggingItemIndex = itemIndex
|
draggingItemIndex = itemIndex
|
||||||
val info = draggingItemLayoutInfo
|
val info = draggingItemLayoutInfo
|
||||||
@@ -78,9 +99,12 @@ class DragDropListState(
|
|||||||
// Statt den Mittelpunkt des gezogenen Items zu prüfen ("liegt mein Zentrum im Target?"),
|
// Statt den Mittelpunkt des gezogenen Items zu prüfen ("liegt mein Zentrum im Target?"),
|
||||||
// wird geprüft ob das gezogene Item den MITTELPUNKT des Targets überspannt.
|
// wird geprüft ob das gezogene Item den MITTELPUNKT des Targets überspannt.
|
||||||
// Dies verhindert Oszillation bei Items unterschiedlicher Größe.
|
// Dies verhindert Oszillation bei Items unterschiedlicher Größe.
|
||||||
// Zusätzlich: Nur adjazente Items (Index ± 1) als Swap-Kandidaten.
|
// 🆕 v1.8.1 IMPL_14: Separator überspringen, Adjazenz berücksichtigt Separator-Lücke
|
||||||
val targetItem = state.layoutInfo.visibleItemsInfo.firstOrNull { item ->
|
val targetItem = state.layoutInfo.visibleItemsInfo.firstOrNull { item ->
|
||||||
(item.index == draggingItem.index - 1 || item.index == draggingItem.index + 1) &&
|
// Separator überspringen
|
||||||
|
item.index != separatorVisualIndex &&
|
||||||
|
// Nur adjazente Items (Separator-Lücke wird übersprungen)
|
||||||
|
isAdjacentSkippingSeparator(draggingItem.index, item.index) &&
|
||||||
run {
|
run {
|
||||||
val targetCenter = item.offset + item.size / 2
|
val targetCenter = item.offset + item.size / 2
|
||||||
startOffset < targetCenter && endOffset > targetCenter
|
startOffset < targetCenter && endOffset > targetCenter
|
||||||
@@ -96,15 +120,19 @@ class DragDropListState(
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🆕 v1.8.1 IMPL_14: Visual-Indizes zu Data-Indizes konvertieren für onMove
|
||||||
|
val fromDataIndex = visualToDataIndex(draggingItem.index)
|
||||||
|
val toDataIndex = visualToDataIndex(targetItem.index)
|
||||||
|
|
||||||
if (scrollToIndex != null) {
|
if (scrollToIndex != null) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
state.scrollToItem(scrollToIndex, state.firstVisibleItemScrollOffset)
|
state.scrollToItem(scrollToIndex, state.firstVisibleItemScrollOffset)
|
||||||
onMove(draggingItem.index, targetItem.index)
|
onMove(fromDataIndex, toDataIndex)
|
||||||
// 🆕 v1.8.0: IMPL_023b — Index-Update NACH dem Move (verhindert Race-Condition)
|
// 🆕 v1.8.0: IMPL_023b — Index-Update NACH dem Move (verhindert Race-Condition)
|
||||||
draggingItemIndex = targetItem.index
|
draggingItemIndex = targetItem.index
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onMove(draggingItem.index, targetItem.index)
|
onMove(fromDataIndex, toDataIndex)
|
||||||
draggingItemIndex = targetItem.index
|
draggingItemIndex = targetItem.index
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -128,6 +156,26 @@ class DragDropListState(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🆕 v1.8.1 IMPL_14: Prüft ob zwei Visual-Indizes adjazent sind,
|
||||||
|
* wobei der Separator übersprungen wird.
|
||||||
|
* Beispiel: Items bei Visual 1 und Visual 3 sind adjazent wenn Separator bei Visual 2 liegt.
|
||||||
|
*/
|
||||||
|
private fun isAdjacentSkippingSeparator(indexA: Int, indexB: Int): Boolean {
|
||||||
|
val diff = kotlin.math.abs(indexA - indexB)
|
||||||
|
if (diff == 1) {
|
||||||
|
// Direkt benachbart — aber NICHT wenn der Separator dazwischen liegt
|
||||||
|
val between = minOf(indexA, indexB) + 1
|
||||||
|
return between != separatorVisualIndex || separatorVisualIndex < 0
|
||||||
|
}
|
||||||
|
if (diff == 2 && separatorVisualIndex >= 0) {
|
||||||
|
// 2 Positionen entfernt — adjazent wenn Separator dazwischen
|
||||||
|
val between = minOf(indexA, indexB) + 1
|
||||||
|
return between == separatorVisualIndex
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("UnusedPrivateProperty")
|
@Suppress("UnusedPrivateProperty")
|
||||||
private val LazyListItemInfo.offsetEnd: Int
|
private val LazyListItemInfo.offsetEnd: Int
|
||||||
get() = this.offset + this.size
|
get() = this.offset + this.size
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
package dev.dettmer.simplenotes.ui.editor
|
package dev.dettmer.simplenotes.ui.editor
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.core.animateDpAsState
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
import androidx.compose.animation.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.animation.slideInVertically
|
|
||||||
import androidx.compose.animation.slideOutVertically
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -19,6 +14,7 @@ import androidx.compose.foundation.layout.imePadding
|
|||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
@@ -327,6 +323,63 @@ private fun TextNoteContent(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🆕 v1.8.1 IMPL_14: Extrahiertes Composable für ein einzelnes draggbares Checklist-Item.
|
||||||
|
* Entkoppelt von der Separator-Logik — wiederverwendbar für unchecked und checked Items.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun LazyItemScope.DraggableChecklistItem(
|
||||||
|
item: ChecklistItemState,
|
||||||
|
visualIndex: Int,
|
||||||
|
dragDropState: DragDropListState,
|
||||||
|
focusNewItemId: String?,
|
||||||
|
onTextChange: (String, String) -> Unit,
|
||||||
|
onCheckedChange: (String, Boolean) -> Unit,
|
||||||
|
onDelete: (String) -> Unit,
|
||||||
|
onAddNewItemAfter: (String) -> Unit,
|
||||||
|
onFocusHandled: () -> Unit,
|
||||||
|
) {
|
||||||
|
val isDragging = dragDropState.draggingItemIndex == visualIndex
|
||||||
|
val elevation by animateDpAsState(
|
||||||
|
targetValue = if (isDragging) 8.dp else 0.dp,
|
||||||
|
label = "elevation"
|
||||||
|
)
|
||||||
|
|
||||||
|
val shouldFocus = item.id == focusNewItemId
|
||||||
|
|
||||||
|
LaunchedEffect(shouldFocus) {
|
||||||
|
if (shouldFocus) {
|
||||||
|
onFocusHandled()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ChecklistItemRow(
|
||||||
|
item = item,
|
||||||
|
onTextChange = { onTextChange(item.id, it) },
|
||||||
|
onCheckedChange = { onCheckedChange(item.id, it) },
|
||||||
|
onDelete = { onDelete(item.id) },
|
||||||
|
onAddNewItem = { onAddNewItemAfter(item.id) },
|
||||||
|
requestFocus = shouldFocus,
|
||||||
|
isDragging = isDragging,
|
||||||
|
isAnyItemDragging = dragDropState.draggingItemIndex != null,
|
||||||
|
dragModifier = Modifier.dragContainer(dragDropState, visualIndex),
|
||||||
|
modifier = Modifier
|
||||||
|
.then(if (!isDragging) Modifier.animateItem() else Modifier)
|
||||||
|
.offset {
|
||||||
|
IntOffset(
|
||||||
|
0,
|
||||||
|
if (isDragging) dragDropState.draggingItemOffset.roundToInt() else 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.zIndex(if (isDragging) DRAGGING_ITEM_Z_INDEX else 0f)
|
||||||
|
.shadow(elevation, shape = RoundedCornerShape(ITEM_CORNER_RADIUS_DP.dp))
|
||||||
|
.background(
|
||||||
|
color = MaterialTheme.colorScheme.surface,
|
||||||
|
shape = RoundedCornerShape(ITEM_CORNER_RADIUS_DP.dp)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("LongParameterList") // Compose functions commonly have many callback parameters
|
@Suppress("LongParameterList") // Compose functions commonly have many callback parameters
|
||||||
@Composable
|
@Composable
|
||||||
private fun ChecklistEditor(
|
private fun ChecklistEditor(
|
||||||
@@ -359,75 +412,61 @@ private fun ChecklistEditor(
|
|||||||
val showSeparator = shouldShowSeparator && uncheckedCount > 0 && checkedCount > 0
|
val showSeparator = shouldShowSeparator && uncheckedCount > 0 && checkedCount > 0
|
||||||
|
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
|
// 🆕 v1.8.1 IMPL_14: Separator-Position für DragDropState aktualisieren
|
||||||
|
val separatorVisualIndex = if (showSeparator) uncheckedCount else -1
|
||||||
|
LaunchedEffect(separatorVisualIndex) {
|
||||||
|
dragDropState.separatorVisualIndex = separatorVisualIndex
|
||||||
|
}
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
state = listState,
|
state = listState,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
contentPadding = PaddingValues(vertical = 8.dp),
|
contentPadding = PaddingValues(vertical = 8.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||||
) {
|
) {
|
||||||
|
// 🆕 v1.8.1 IMPL_14: Unchecked Items (Visual Index 0..uncheckedCount-1)
|
||||||
itemsIndexed(
|
itemsIndexed(
|
||||||
items = items,
|
items = if (showSeparator) items.subList(0, uncheckedCount) else items,
|
||||||
key = { _, item -> item.id }
|
key = { _, item -> item.id }
|
||||||
) { index, item ->
|
) { index, item ->
|
||||||
// 🆕 v1.8.0 (IMPL_017): Separator vor dem ersten Checked-Item
|
DraggableChecklistItem(
|
||||||
// 🆕 v1.8.1: Separator während Drag ausblenden — verhindert Flächenveränderung
|
item = item,
|
||||||
// am Separator-Item die Swap-Erkennung destabilisiert
|
visualIndex = index,
|
||||||
if (showSeparator && index == uncheckedCount && dragDropState.draggingItemIndex == null) {
|
dragDropState = dragDropState,
|
||||||
CheckedItemsSeparator(checkedCount = checkedCount)
|
focusNewItemId = focusNewItemId,
|
||||||
}
|
onTextChange = onTextChange,
|
||||||
|
onCheckedChange = onCheckedChange,
|
||||||
val isDragging = dragDropState.draggingItemIndex == index
|
onDelete = onDelete,
|
||||||
val elevation by animateDpAsState(
|
onAddNewItemAfter = onAddNewItemAfter,
|
||||||
targetValue = if (isDragging) 8.dp else 0.dp,
|
onFocusHandled = onFocusHandled
|
||||||
label = "elevation"
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val shouldFocus = item.id == focusNewItemId
|
// 🆕 v1.8.1 IMPL_14: Separator als eigenes LazyColumn-Item
|
||||||
|
if (showSeparator) {
|
||||||
// v1.5.0: Clear focus request after handling
|
item(key = "separator") {
|
||||||
LaunchedEffect(shouldFocus) {
|
CheckedItemsSeparator(
|
||||||
if (shouldFocus) {
|
checkedCount = checkedCount,
|
||||||
onFocusHandled()
|
isDragActive = dragDropState.draggingItemIndex != null
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🆕 v1.8.0 (IMPL_017): AnimatedVisibility für sanfte Übergänge
|
// 🆕 v1.8.1 IMPL_14: Checked Items (Visual Index uncheckedCount+1..)
|
||||||
AnimatedVisibility(
|
itemsIndexed(
|
||||||
visible = true,
|
items = items.subList(uncheckedCount, items.size),
|
||||||
enter = fadeIn() + slideInVertically(),
|
key = { _, item -> item.id }
|
||||||
exit = fadeOut() + slideOutVertically()
|
) { index, item ->
|
||||||
) {
|
val visualIndex = uncheckedCount + 1 + index // +1 für Separator
|
||||||
ChecklistItemRow(
|
DraggableChecklistItem(
|
||||||
item = item,
|
item = item,
|
||||||
onTextChange = { onTextChange(item.id, it) },
|
visualIndex = visualIndex,
|
||||||
onCheckedChange = { onCheckedChange(item.id, it) },
|
dragDropState = dragDropState,
|
||||||
onDelete = { onDelete(item.id) },
|
focusNewItemId = focusNewItemId,
|
||||||
onAddNewItem = { onAddNewItemAfter(item.id) },
|
onTextChange = onTextChange,
|
||||||
requestFocus = shouldFocus,
|
onCheckedChange = onCheckedChange,
|
||||||
// 🆕 v1.8.0: IMPL_023 - Drag state übergeben
|
onDelete = onDelete,
|
||||||
isDragging = isDragging,
|
onAddNewItemAfter = onAddNewItemAfter,
|
||||||
// 🆕 v1.8.0: IMPL_023 - Gradient während Drag ausblenden
|
onFocusHandled = onFocusHandled
|
||||||
isAnyItemDragging = dragDropState.draggingItemIndex != null,
|
|
||||||
// 🆕 v1.8.0: IMPL_023 - Drag nur auf Handle
|
|
||||||
dragModifier = Modifier.dragContainer(dragDropState, index),
|
|
||||||
modifier = Modifier
|
|
||||||
// 🆕 v1.8.1: animateItem NUR für nicht-gedraggte Items
|
|
||||||
// Bei gedraggten Items kämpft animateItem (Layout-Animation)
|
|
||||||
// gegen den manuellen offset (Finger-Position) → Flackern
|
|
||||||
.then(if (!isDragging) Modifier.animateItem() else Modifier)
|
|
||||||
.offset {
|
|
||||||
IntOffset(
|
|
||||||
0,
|
|
||||||
if (isDragging) dragDropState.draggingItemOffset.roundToInt() else 0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// 🆕 v1.8.0: IMPL_023 - Gedraggtes Item liegt über anderen
|
|
||||||
.zIndex(if (isDragging) DRAGGING_ITEM_Z_INDEX else 0f)
|
|
||||||
.shadow(elevation, shape = RoundedCornerShape(ITEM_CORNER_RADIUS_DP.dp))
|
|
||||||
.background(
|
|
||||||
color = MaterialTheme.colorScheme.surface,
|
|
||||||
shape = RoundedCornerShape(ITEM_CORNER_RADIUS_DP.dp)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,15 +238,18 @@ class NoteEditorViewModel(
|
|||||||
val fromItem = items.getOrNull(fromIndex) ?: return@update items
|
val fromItem = items.getOrNull(fromIndex) ?: return@update items
|
||||||
val toItem = items.getOrNull(toIndex) ?: return@update items
|
val toItem = items.getOrNull(toIndex) ?: return@update items
|
||||||
|
|
||||||
// 🆕 v1.8.0 (IMPL_017): Drag nur innerhalb der gleichen Gruppe erlauben
|
|
||||||
// (checked ↔ checked, unchecked ↔ unchecked)
|
|
||||||
if (fromItem.isChecked != toItem.isChecked) {
|
|
||||||
return@update items // Kein Move über Gruppen-Grenze
|
|
||||||
}
|
|
||||||
|
|
||||||
val mutableList = items.toMutableList()
|
val mutableList = items.toMutableList()
|
||||||
val item = mutableList.removeAt(fromIndex)
|
val item = mutableList.removeAt(fromIndex)
|
||||||
mutableList.add(toIndex, item)
|
|
||||||
|
// 🆕 v1.8.1 IMPL_14: Cross-Boundary Move mit Auto-Toggle
|
||||||
|
// Wenn ein Item die Grenze überschreitet, wird es automatisch checked/unchecked.
|
||||||
|
val movedItem = if (fromItem.isChecked != toItem.isChecked) {
|
||||||
|
item.copy(isChecked = toItem.isChecked)
|
||||||
|
} else {
|
||||||
|
item
|
||||||
|
}
|
||||||
|
|
||||||
|
mutableList.add(toIndex, movedItem)
|
||||||
// Update order values
|
// Update order values
|
||||||
mutableList.mapIndexed { index, i -> i.copy(order = index) }
|
mutableList.mapIndexed { index, i -> i.copy(order = index) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import dev.dettmer.simplenotes.R
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 🆕 v1.8.0 (IMPL_017): Visueller Separator zwischen unchecked und checked Items
|
* 🆕 v1.8.0 (IMPL_017): Visueller Separator zwischen unchecked und checked Items
|
||||||
|
* 🆕 v1.8.1 (IMPL_14): Drag-Awareness — Primary-Farbe während Drag als visueller Hinweis
|
||||||
*
|
*
|
||||||
* Zeigt eine dezente Linie mit Anzahl der erledigten Items:
|
* Zeigt eine dezente Linie mit Anzahl der erledigten Items:
|
||||||
* ── 3 completed ──
|
* ── 3 completed ──
|
||||||
@@ -22,7 +23,8 @@ import dev.dettmer.simplenotes.R
|
|||||||
@Composable
|
@Composable
|
||||||
fun CheckedItemsSeparator(
|
fun CheckedItemsSeparator(
|
||||||
checkedCount: Int,
|
checkedCount: Int,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier,
|
||||||
|
isDragActive: Boolean = false // 🆕 v1.8.1 IMPL_14
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@@ -32,7 +34,10 @@ fun CheckedItemsSeparator(
|
|||||||
) {
|
) {
|
||||||
HorizontalDivider(
|
HorizontalDivider(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
color = MaterialTheme.colorScheme.outlineVariant
|
color = if (isDragActive)
|
||||||
|
MaterialTheme.colorScheme.primary.copy(alpha = 0.5f)
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.outlineVariant
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
@@ -42,13 +47,19 @@ fun CheckedItemsSeparator(
|
|||||||
checkedCount
|
checkedCount
|
||||||
),
|
),
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
color = MaterialTheme.colorScheme.outline,
|
color = if (isDragActive)
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.outline,
|
||||||
modifier = Modifier.padding(horizontal = 12.dp)
|
modifier = Modifier.padding(horizontal = 12.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
HorizontalDivider(
|
HorizontalDivider(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
color = MaterialTheme.colorScheme.outlineVariant
|
color = if (isDragActive)
|
||||||
|
MaterialTheme.colorScheme.primary.copy(alpha = 0.5f)
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.outlineVariant
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user