Last active
December 6, 2023 17:45
-
-
Save bmc08gt/8668f228de4b8d4055993b0c73f72374 to your computer and use it in GitHub Desktop.
FKScaffold with accessory view triggers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
FKScaffold( | |
modifier = Modifier.fillMaxSize(), | |
topBar = { | |
FKLargeTopAppBar(title = name) | |
}, | |
) { padding, triggerAccessoryView -> | |
ItemForm( | |
modifier = Modifier.padding(top = padding.calculateTopPadding()), | |
state = state, | |
dispatch = viewModel::dispatchEvent | |
) | |
LaunchedEffect(viewModel) { | |
viewModel.eventFlow | |
.filterIsInstance<ItemViewModel.Event.ChooseExpiration>() | |
.map { state.builder.expiration } | |
.onEach { selectedDate -> | |
triggerAccessoryView(AccessoryViewTrigger.DatePicker( | |
selectedDate = selectedDate ?: Clock.System.now().plus(3.days), | |
minDate = Clock.System.now(), | |
onResult = { | |
viewModel.dispatchEvent(ItemViewModel.Event.OnExpirationSet(it)) | |
} | |
)) | |
}.launchIn(this) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
val LocalAccessoryViewHeight: ProvidableCompositionLocal<Dp> = | |
staticCompositionLocalOf { error("AccessoryViewHeight not initialized") } | |
val LocalAccessoryVisible: ProvidableCompositionLocal<Boolean> = | |
staticCompositionLocalOf { error("AccessoryVisible not initialized") } | |
enum class AccessoryViewType { | |
DatePicker | |
} | |
sealed interface AccessoryViewTrigger<T> { | |
val type: AccessoryViewType | |
val onResult: (T) -> Unit | |
data class DatePicker( | |
val selectedDate: Instant, | |
val minDate: Instant?, | |
override val onResult: (Instant) -> Unit | |
) : AccessoryViewTrigger<Instant> { | |
override val type: AccessoryViewType = AccessoryViewType.DatePicker | |
} | |
} | |
@Composable | |
fun FKScaffold( | |
modifier: Modifier = Modifier, | |
topBar: @Composable () -> Unit = {}, | |
bottomBar: @Composable () -> Unit = {}, | |
snackbarHost: @Composable () -> Unit = {}, | |
floatingActionButton: @Composable () -> Unit = {}, | |
floatingActionButtonPosition: FabPosition = FabPosition.End, | |
containerColor: Color = MaterialTheme.colorScheme.background, | |
contentColor: Color = contentColorFor(containerColor), | |
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets, | |
content: @Composable BoxScope.(PaddingValues, (AccessoryViewTrigger<*>) -> Unit) -> Unit | |
) { | |
var accessoryView by remember { | |
mutableStateOf<AccessoryViewTrigger<*>?>(null) | |
} | |
val accessoryHeights = remember { | |
mutableStateMapOf(AccessoryViewType.DatePicker to 0.dp) | |
} | |
val animatedHeight by animateDpAsState( | |
if (accessoryView != null) accessoryHeights[accessoryView!!.type]!! else 0.dp | |
) | |
val scrim by animateColorAsState( | |
if (accessoryView != null) MaterialTheme.colorScheme.scrim.copy(alpha = 0.32f) else Color.Transparent | |
) | |
@Composable | |
fun bottomBar(bottomBar: @Composable () -> Unit, trigger: AccessoryViewTrigger<*>?) { | |
val dateTrigger = trigger as? AccessoryViewTrigger.DatePicker | |
DatePicker( | |
modifier = Modifier.fillMaxWidth() | |
.measured { | |
val size = accessoryHeights[AccessoryViewType.DatePicker] ?: 0.dp | |
if (size == 0.dp) { | |
accessoryHeights[AccessoryViewType.DatePicker] = it.height | |
} | |
}, | |
value = dateTrigger?.selectedDate ?: Clock.System.now(), | |
minValue = dateTrigger?.minDate, | |
mode = DatePickerMode.Date, | |
isVisible = trigger is AccessoryViewTrigger.DatePicker, | |
onValueChanged = { | |
dateTrigger?.onResult?.invoke(it) | |
accessoryView = null | |
} | |
) | |
if (trigger == null) { | |
bottomBar() | |
} | |
} | |
Scaffold( | |
modifier = modifier, | |
topBar = { | |
Box { | |
topBar() | |
if (accessoryView != null) { | |
Scrim(modifier = Modifier.matchParentSize(), color = scrim) | |
} | |
} | |
}, | |
bottomBar = { bottomBar(bottomBar, accessoryView) }, | |
snackbarHost = snackbarHost, | |
floatingActionButton = floatingActionButton, | |
floatingActionButtonPosition = floatingActionButtonPosition, | |
containerColor = containerColor, | |
contentColor = contentColor, | |
contentWindowInsets = contentWindowInsets, | |
content = { padding -> | |
CompositionLocalProvider( | |
LocalAccessoryViewHeight provides animatedHeight, | |
LocalAccessoryVisible provides (accessoryView != null), | |
) { | |
Box(Modifier.fillMaxSize()) { | |
content(padding) { accView -> | |
accessoryView = accView | |
} | |
if (accessoryView != null) { | |
Scrim(modifier = Modifier.matchParentSize(), color = scrim) | |
} | |
} | |
} | |
} | |
) | |
} | |
@Composable | |
private fun Scrim(modifier: Modifier, color: Color) { | |
Box( | |
modifier = modifier | |
.addIf(Platform.showScrimWithDatePicker) { | |
Modifier.background(color) | |
}.clickable( | |
indication = null, | |
interactionSource = remember { MutableInteractionSource() } | |
) { /* swallow clicks */ } | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment