So I am rewriting an app's UI using Jetpack Compose. I have implemented a Navigation Drawer using the regular Scaffold function. Out of the box this provides two ways of opening the drawer: either press the navigationIcon or drag towards End of screen. The screen in question is a LazyColumn of list items.
I have at a later date implemented the SwipeToDismiss pattern on these list items. The swipe to dismiss works fine but it is no longer possible to drag anywhere to open the navigation drawer.
In the old View-based system, the navigation drawer would reserve a small width inside which you could always drag to open the drawer - regardless of child items having drag support. I am unsure how to achieve the same using Compose. It seems like it should be the job of the navigation drawer to handle this - and not a screen inside it.
The screen with navigation drawer:
val coroutineScope = rememberCoroutineScope()
val scaffoldState = rememberScaffoldState(
rememberDrawerState(initialValue = DrawerValue.Closed)
)
Scaffold(
scaffoldState = scaffoldState,
topBar = {
TopAppBar(
title = { Text(screenTitle) },
navigationIcon = {
IconButton(
onClick = {
coroutineScope.launch {
scaffoldState.drawerState.open()
}
}
) {
Icon(
Icons.Default.Menu,
contentDescription = "Drawer toggle button"
)
}
},
actions = {
...
}
)
},
drawerContent = {
// List of stuff
...
},
floatingActionButton = {
...
}
) { padding ->
/// Layout with a LazyColumn with elements having SwipeToDismiss
...
}
and swipe to dismiss item (displayed inside LazyColumn)
@OptIn(
ExperimentalFoundationApi::class,
ExperimentalMaterialApi::class,
ExperimentalAnimationApi::class
)
@Composable
fun SwipeableFeedItemPreview(
onSwipe: suspend () -> Unit,
onlyUnread: Boolean,
item: FeedListItem,
showThumbnail: Boolean,
imagePainter: @Composable (String) -> Unit,
onMarkAboveAsRead: () -> Unit,
onMarkBelowAsRead: () -> Unit,
onItemClick: () -> Unit
) {
val animatedVisibilityState = remember { MutableTransitionState(true) }
val swipeableState = rememberSwipeableState(initialValue = FeedItemSwipeState.NONE)
// Needs to be set once layout is complete
var itemSize by remember { mutableStateOf(Size(1f, 1f)) }
val anchors = mapOf(
0f to FeedItemSwipeState.NONE,
-itemSize.width to FeedItemSwipeState.LEFT,
itemSize.width to FeedItemSwipeState.RIGHT
)
AnimatedVisibility(
visibleState = animatedVisibilityState,
enter = fadeIn(1f),
exit = shrinkVertically(Alignment.CenterVertically) + fadeOut()
) {
Box(
modifier = Modifier
.fillMaxWidth()
.onGloballyPositioned { layoutCoordinates ->
itemSize = layoutCoordinates.size.toSize()
}
.swipeable(
state = swipeableState,
anchors = anchors,
orientation = Orientation.Horizontal,
thresholds = { _, _ ->
FractionalThreshold(0.25f)
}
)
) {
Box(
contentAlignment = swipeIconAlignment,
modifier = Modifier
.matchParentSize()
.background(color)
.padding(horizontal = 24.dp)
) {
AnimatedVisibility(
visible = swipeableState.targetValue != FeedItemSwipeState.NONE,
enter = fadeIn(),
exit = fadeOut()
) {
Icon(
when (item.unread) {
true -> Icons.Default.VisibilityOff
false -> Icons.Default.Visibility
},
contentDescription = stringResource(id = R.string.toggle_read_status)
)
}
}
FeedItemPreview(
item = item,
showThumbnail = showThumbnail,
imagePainter = imagePainter,
onMarkAboveAsRead = onMarkAboveAsRead,
onMarkBelowAsRead = onMarkBelowAsRead,
onItemClick = onItemClick,
modifier = Modifier
.offset { IntOffset(swipeableState.offset.value.roundToInt(), 0) }
)
}
}
}