Spring the confirmation chip, and build it a frame earlier (#68)
The chip springs open from its corner on the expressive fast specs instead of sliding in clipped, and its text is resolved in composition rather than from an effect — a frame late meant it opened carrying the previous message and grew into the new one mid-animation. Undo now leaves the outcome standing until its write reports back, so the one chip changes what it says instead of closing and reopening.
This commit is contained in:
@@ -32,6 +32,9 @@ import java.util.Locale
|
||||
/** How long the confirmation chip stays up, matching a short snackbar. */
|
||||
private const val CHIP_MILLIS = 4_000L
|
||||
|
||||
/** What the chip currently reads, kept past the outcome it was built from. */
|
||||
private data class ChipContent(val message: String, val undo: MoveUndo?)
|
||||
|
||||
/** The FAB's own band at the bottom end, which the chip must not run into. */
|
||||
private val FAB_BAND = 88.dp
|
||||
|
||||
@@ -75,13 +78,16 @@ fun EventMoveHost(viewModel: RescheduleViewModel, modifier: Modifier = Modifier)
|
||||
}
|
||||
|
||||
// Held past the outcome being consumed so the chip has something to draw
|
||||
// while it slides back out.
|
||||
var shownMessage by remember { mutableStateOf("") }
|
||||
var shownUndo by remember { mutableStateOf<MoveUndo?>(null) }
|
||||
// while it springs back out. Updated *in composition* rather than from an
|
||||
// effect: an effect lands a frame late, so the chip would open carrying the
|
||||
// previous message and grow into this one while it was still animating in.
|
||||
val shown = remember { mutableStateOf(ChipContent("", null)) }
|
||||
if (message != null && (shown.value.message != message || shown.value.undo != moved?.undo)) {
|
||||
shown.value = ChipContent(message, moved?.undo)
|
||||
}
|
||||
val content = shown.value
|
||||
LaunchedEffect(outcome) {
|
||||
val text = message ?: return@LaunchedEffect
|
||||
shownMessage = text
|
||||
shownUndo = moved?.undo
|
||||
if (outcome == null) return@LaunchedEffect
|
||||
delay(CHIP_MILLIS)
|
||||
viewModel.consumeOutcome()
|
||||
}
|
||||
@@ -101,11 +107,11 @@ fun EventMoveHost(viewModel: RescheduleViewModel, modifier: Modifier = Modifier)
|
||||
) {
|
||||
SnackChip(
|
||||
visible = outcome != null,
|
||||
message = shownMessage,
|
||||
message = content.message,
|
||||
maxWidth = chipMaxWidth,
|
||||
actionLabel = stringResource(R.string.event_move_undo)
|
||||
.takeIf { shownUndo != null },
|
||||
onAction = shownUndo?.let { undo -> { viewModel.undo(undo) } },
|
||||
.takeIf { content.undo != null },
|
||||
onAction = content.undo?.let { undo -> { viewModel.undo(undo) } },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,11 +226,15 @@ class RescheduleViewModel @Inject constructor(
|
||||
_scopePrompt.value = null
|
||||
}
|
||||
|
||||
/** Put a completed move back where it came from. */
|
||||
/**
|
||||
* Put a completed move back where it came from. The outcome deliberately
|
||||
* stands until the inverse write reports back: clearing it first would drop
|
||||
* the confirmation chip and open a second one a moment later, rather than
|
||||
* letting the one chip change what it says.
|
||||
*/
|
||||
fun undo(undo: MoveUndo) {
|
||||
if (busy) return
|
||||
busy = true
|
||||
_outcome.value = null
|
||||
viewModelScope.launch {
|
||||
_outcome.value = try {
|
||||
repository.updateEvent(undo.eventId, undo.moved, undo.restored)
|
||||
|
||||
Reference in New Issue
Block a user