Fold the selection bar while a chip shows and match it to the FAB (#141)
This commit is contained in:
@@ -391,6 +391,8 @@ fun CalendarHost(
|
||||
val reschedule: RescheduleViewModel = hiltViewModel()
|
||||
val movableCalendarIds by reschedule.movableCalendarIds.collectAsStateWithLifecycle()
|
||||
val eventActions: EventActionsViewModel = hiltViewModel()
|
||||
val moveOutcome by reschedule.outcome.collectAsStateWithLifecycle()
|
||||
val selectionFailure by eventActions.failure.collectAsStateWithLifecycle()
|
||||
val onEditEvent: (EventInstance) -> Unit = { event ->
|
||||
val key = longArrayOf(
|
||||
event.eventId,
|
||||
@@ -543,6 +545,11 @@ fun CalendarHost(
|
||||
importFormSource = ImportSource.Insert
|
||||
importForm = form
|
||||
},
|
||||
chipShown = moveOutcome != null || selectionFailure != null,
|
||||
onDismissChip = {
|
||||
reschedule.consumeOutcome()
|
||||
eventActions.consumeFailure()
|
||||
},
|
||||
)
|
||||
CalendarFab(chromeHost, todayInToolbar, fabSelection)
|
||||
},
|
||||
@@ -600,12 +607,10 @@ fun CalendarHost(
|
||||
|
||||
// Scope prompt + confirmation/undo snackbar for a dropped event, declared
|
||||
// right after the calendar views so later overlays cover it.
|
||||
val actionFailure by eventActions.failure.collectAsStateWithLifecycle()
|
||||
EventMoveHost(
|
||||
reschedule,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
raised = chromeHost.selectionBarShown && chromeHost.selectionBarExpanded,
|
||||
actionFailure = eventActionFailureMessage(actionFailure),
|
||||
actionFailure = eventActionFailureMessage(selectionFailure),
|
||||
onActionFailureShown = eventActions::consumeFailure,
|
||||
)
|
||||
|
||||
|
||||
@@ -90,10 +90,6 @@ class ChromeHost {
|
||||
var selectionCompact by mutableStateOf(false)
|
||||
internal set
|
||||
|
||||
/** Whether the selection's bar is unfolded, rather than folded into its button. */
|
||||
var selectionBarExpanded by mutableStateOf(false)
|
||||
internal set
|
||||
|
||||
/** Whether the selection's action bar is showing — it steps aside mid-gesture. */
|
||||
val selectionBarShown: Boolean get() = selection != null && !selectionBusy
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
@@ -90,8 +89,6 @@ fun CalendarFabColumn(
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = selection?.expanded == true,
|
||||
// Overflows the row evenly above and below, centred on the FAB.
|
||||
modifier = Modifier.wrapContentHeight(unbounded = true),
|
||||
enter = expandHorizontally(
|
||||
animationSpec = MaterialTheme.motionScheme.fastSpatialSpec(),
|
||||
expandFrom = Alignment.End,
|
||||
|
||||
@@ -51,7 +51,6 @@ private val FAB_BAND = 88.dp
|
||||
fun EventMoveHost(
|
||||
viewModel: RescheduleViewModel,
|
||||
modifier: Modifier = Modifier,
|
||||
raised: Boolean = false,
|
||||
actionFailure: String? = null,
|
||||
onActionFailureShown: () -> Unit = {},
|
||||
) {
|
||||
@@ -126,11 +125,7 @@ fun EventMoveHost(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomStart)
|
||||
.navigationBarsPadding()
|
||||
// Above the selection bar while it is up (#141).
|
||||
.padding(
|
||||
start = SnackChipMargin,
|
||||
bottom = SnackChipMargin + if (raised) SelectionBarClearance else 0.dp,
|
||||
)
|
||||
.padding(start = SnackChipMargin, bottom = SnackChipMargin)
|
||||
.height(SnackChipHeight),
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
) {
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
package de.jeanlucmakiola.calendula.ui.common
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ContentCopy
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.FloatingToolbarDefaults
|
||||
import androidx.compose.material3.HorizontalFloatingToolbar
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.jeanlucmakiola.calendula.R
|
||||
|
||||
/** The selected event's action bar (#141); Edit and Delete only where its calendar allows. */
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun EventSelectionBar(
|
||||
canEdit: Boolean,
|
||||
@@ -28,15 +32,23 @@ fun EventSelectionBar(
|
||||
onDelete: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
HorizontalFloatingToolbar(
|
||||
expanded = true,
|
||||
colors = FloatingToolbarDefaults.vibrantFloatingToolbarColors(),
|
||||
modifier = modifier,
|
||||
// The FAB's height and colours, so the two read as one control.
|
||||
Surface(
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.primaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||
shadowElevation = 6.dp,
|
||||
modifier = modifier.height(56.dp),
|
||||
) {
|
||||
if (canEdit) BarAction(Icons.Default.Edit, R.string.event_detail_edit, onEdit)
|
||||
BarAction(Icons.Default.ContentCopy, R.string.event_detail_duplicate, onDuplicate)
|
||||
BarAction(Icons.Default.Share, R.string.event_detail_share, onShare)
|
||||
if (canDelete) BarAction(Icons.Default.Delete, R.string.event_detail_delete, onDelete)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(horizontal = 4.dp),
|
||||
) {
|
||||
if (canEdit) BarAction(Icons.Default.Edit, R.string.event_detail_edit, onEdit)
|
||||
BarAction(Icons.Default.ContentCopy, R.string.event_detail_duplicate, onDuplicate)
|
||||
BarAction(Icons.Default.Share, R.string.event_detail_share, onShare)
|
||||
if (canDelete) BarAction(Icons.Default.Delete, R.string.event_detail_delete, onDelete)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package de.jeanlucmakiola.calendula.ui.common
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -17,8 +16,8 @@ import de.jeanlucmakiola.calendula.domain.EventInstance
|
||||
import de.jeanlucmakiola.floret.components.SnackChipMargin
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/** How far the bottom chips rise while the selection bar is open, to clear it. */
|
||||
val SelectionBarClearance = 64.dp + SnackChipMargin
|
||||
/** The bottom band the selection bar and FAB cover, margin included. */
|
||||
val SelectionBarClearance = 56.dp + SnackChipMargin
|
||||
|
||||
/** The FAB's role while an event is selected (#141): its action bar's toggle. Null otherwise. */
|
||||
@Composable
|
||||
@@ -27,6 +26,8 @@ fun rememberFabSelection(
|
||||
viewModel: EventActionsViewModel,
|
||||
onEdit: (EventInstance) -> Unit,
|
||||
onDuplicate: (EventForm) -> Unit,
|
||||
chipShown: Boolean,
|
||||
onDismissChip: () -> Unit,
|
||||
): FabSelection? {
|
||||
val calendars by viewModel.calendars.collectAsStateWithLifecycle()
|
||||
val context = LocalContext.current
|
||||
@@ -36,8 +37,8 @@ fun rememberFabSelection(
|
||||
|
||||
// What the FAB has been told for this selection, over what the coverage says.
|
||||
var chosen by remember(host.selection?.event) { mutableStateOf<Boolean?>(null) }
|
||||
val expanded = chosen ?: !host.selectionCompact
|
||||
SideEffect { host.selectionBarExpanded = expanded && host.selection != null }
|
||||
// Folded while a chip is up, which then sits beside the FAB as usual.
|
||||
val expanded = !chipShown && (chosen ?: !host.selectionCompact)
|
||||
|
||||
val selection = host.selection ?: return null
|
||||
val event = selection.event
|
||||
@@ -45,7 +46,10 @@ fun rememberFabSelection(
|
||||
val canEdit = calendar?.canModifyContents == true
|
||||
return FabSelection(
|
||||
expanded = expanded,
|
||||
onToggle = { chosen = !expanded },
|
||||
onToggle = {
|
||||
if (chipShown) onDismissChip()
|
||||
chosen = !expanded
|
||||
},
|
||||
bar = {
|
||||
EventSelectionBar(
|
||||
canEdit = canEdit,
|
||||
|
||||
Reference in New Issue
Block a user