fix: allow swipe-to-close when drawer is open on pager screens

Keep drawer gestures enabled when the drawer is open so users can
swipe left to close it. Only disable gestures when the drawer is
closed (to let the pager and ZonedSwipeModifier handle gestures).
This commit is contained in:
davotoula
2026-03-15 23:25:04 +01:00
parent 7f6219c846
commit 2a9c9e4510
5 changed files with 116 additions and 3 deletions
@@ -0,0 +1,90 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.components
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.pager.PagerState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
private val EDGE_ZONE_WIDTH = 48.dp
fun Modifier.zonedDrawerSwipe(
pagerState: PagerState,
openDrawer: () -> Unit,
): Modifier =
composed {
val edgeZonePx = with(LocalDensity.current) { EDGE_ZONE_WIDTH.toPx() }
var gestureStartX by remember { mutableFloatStateOf(0f) }
var gestureStartPage by remember { mutableIntStateOf(0) }
var drawerOpened by remember { mutableStateOf(false) }
val connection =
remember {
object : NestedScrollConnection {
override fun onPreScroll(
available: Offset,
source: NestedScrollSource,
): Offset {
if (source != NestedScrollSource.UserInput) return Offset.Zero
if (drawerOpened) return Offset(available.x, 0f)
// available.x > 0 means user is swiping right
if (available.x > 0f) {
val wasOnFirstPage = gestureStartPage == 0
val isInEdgeZone = gestureStartX < edgeZonePx
if (wasOnFirstPage || !isInEdgeZone) {
drawerOpened = true
openDrawer()
return Offset(available.x, 0f)
}
}
return Offset.Zero
}
}
}
this
.pointerInput(Unit) {
awaitEachGesture {
val down = awaitFirstDown(requireUnconsumed = false)
gestureStartX = down.position.x
gestureStartPage = pagerState.currentPage
drawerOpened = false
}
}.nestedScroll(connection)
}
@@ -37,8 +37,10 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.core.net.toUri
import androidx.core.util.Consumer
import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.crashreports.DisplayCrashMessages
@@ -142,7 +144,14 @@ fun AppNavigation(
) {
val nav = rememberNav()
AccountSwitcherAndLeftDrawerLayout(accountViewModel, accountSessionManager, nav) {
val navBackStackEntry by nav.controller.currentBackStackEntryAsState()
val isTabPagerRoute =
navBackStackEntry?.destination?.let { dest ->
dest.hasRoute<Route.Home>() || dest.hasRoute<Route.Message>()
} ?: false
val drawerGesturesEnabled = !isTabPagerRoute || nav.drawerState.isOpen
AccountSwitcherAndLeftDrawerLayout(accountViewModel, accountSessionManager, nav, drawerGesturesEnabled) {
NavHost(
navController = nav.controller,
startDestination = Route.Home,
@@ -49,6 +49,7 @@ fun AccountSwitcherAndLeftDrawerLayout(
accountViewModel: AccountViewModel,
accountSessionManager: AccountSessionManager,
nav: INav,
gesturesEnabled: Boolean = true,
content: @Composable () -> Unit,
) {
val scope = rememberCoroutineScope()
@@ -83,6 +84,7 @@ fun AccountSwitcherAndLeftDrawerLayout(
ModalNavigationDrawer(
drawerState = nav.drawerState,
gesturesEnabled = gesturesEnabled,
drawerContent = {
DrawerContent(nav, openSheetFunction, accountViewModel)
BackHandler(enabled = nav.drawerState.isOpen, nav::closeDrawer)
@@ -48,6 +48,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState
import com.vitorpamplona.amethyst.ui.components.zonedDrawerSwipe
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
@@ -124,7 +125,12 @@ fun MessagesPager(
HorizontalPager(
contentPadding = paddingValues,
state = pagerState,
userScrollEnabled = false,
userScrollEnabled = true,
modifier =
Modifier.zonedDrawerSwipe(
pagerState = pagerState,
openDrawer = nav::openDrawer,
),
) { page ->
ChatroomListFeedView(
feedContentState = tabs[page].feedContentState,
@@ -63,6 +63,7 @@ import com.vitorpamplona.amethyst.model.TopFilter
import com.vitorpamplona.amethyst.service.OnlineChecker
import com.vitorpamplona.amethyst.service.location.LocationState
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.components.zonedDrawerSwipe
import com.vitorpamplona.amethyst.ui.feeds.ChannelFeedContentState
import com.vitorpamplona.amethyst.ui.feeds.ChannelFeedState
import com.vitorpamplona.amethyst.ui.feeds.PagerStateKeys
@@ -211,7 +212,12 @@ private fun HomePages(
HorizontalPager(
contentPadding = it,
state = pagerState,
userScrollEnabled = false,
userScrollEnabled = true,
modifier =
Modifier.zonedDrawerSwipe(
pagerState = pagerState,
openDrawer = nav::openDrawer,
),
) { page ->
HomeFeeds(
feedState = tabs[page].feedState,