diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/gestures/GestureDetector.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/gestures/GestureDetector.kt index eab90a19..951ab92a 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/gestures/GestureDetector.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/gestures/GestureDetector.kt @@ -1,29 +1,49 @@ package de.mm20.launcher2.ui.gestures -import android.util.Log import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.geometry.Offset -class GestureManager { +class GestureDetector { private var dragStart: Offset? = null private var currentDrag : Offset? = null - fun reportDoubleTap(position: Offset) { + var gestureListener: OnGestureListener? = null + + fun dispatchDoubleTap(position: Offset) { + gestureListener?.onDoubleTap(position) } - fun reportLongPress(position: Offset) { + fun dispatchLongPress(position: Offset) { + gestureListener?.onLongPress(position) } - fun reportDrag(offset: Offset) { - currentDrag = (currentDrag ?: Offset.Zero) + offset + private var hasDragEnded = false + fun dispatchDrag(offset: Offset) { + if (hasDragEnded) return + val totalDrag = currentDrag?.plus(offset) ?: offset + currentDrag = totalDrag + if (gestureListener?.onDrag(totalDrag) == true) hasDragEnded = true } - fun reportDragEnd() { + fun dispatchDragEnd() { dragStart = null currentDrag = null + hasDragEnded = false + } + + + interface OnGestureListener { + fun onDoubleTap(position: Offset) {} + fun onLongPress(position: Offset) {} + + /** + * @return true if the drag gesture has been handled. + * The gesture detector will no longer track the drag gesture in this case. + */ + fun onDrag(offset: Offset): Boolean = false } } -val LocalGestureManager = staticCompositionLocalOf { - GestureManager() +val LocalGestureDetector = staticCompositionLocalOf { + GestureDetector() } \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/gestures/GestureHandler.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/gestures/GestureHandler.kt new file mode 100644 index 00000000..b6dc60c2 --- /dev/null +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/gestures/GestureHandler.kt @@ -0,0 +1,32 @@ +package de.mm20.launcher2.ui.gestures + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.geometry.Offset + +@Composable +fun GestureHandler( + detector: GestureDetector, + onLongPress: (Offset) -> Unit = {}, + onDoubleTap: (Offset) -> Unit = {}, + onDrag: (Offset) -> Boolean = { false }, +) { + DisposableEffect(detector) { + detector.gestureListener = object : GestureDetector.OnGestureListener { + override fun onLongPress(position: Offset) { + onLongPress(position) + } + + override fun onDoubleTap(position: Offset) { + onDoubleTap(position) + } + + override fun onDrag(offset: Offset): Boolean { + return onDrag(offset) + } + } + onDispose { + detector.gestureListener = null + } + } +} \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/PagerScaffold.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/PagerScaffold.kt index 130389fc..34e581ef 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/PagerScaffold.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/PagerScaffold.kt @@ -7,13 +7,11 @@ import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.slideIn import androidx.compose.animation.slideOut import androidx.compose.foundation.LocalOverscrollConfiguration -import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.calculateStartPadding @@ -33,11 +31,8 @@ import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material.FractionalThreshold import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Done -import androidx.compose.material.rememberSwipeableState -import androidx.compose.material.swipeable import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -52,10 +47,8 @@ import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer @@ -68,7 +61,6 @@ import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel @@ -77,9 +69,8 @@ import de.mm20.launcher2.preferences.Settings.SearchBarSettings.SearchBarColors import de.mm20.launcher2.preferences.Settings.SearchBarSettings.SearchBarStyle import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.SearchBarLevel -import de.mm20.launcher2.ui.gestures.LocalGestureManager +import de.mm20.launcher2.ui.gestures.LocalGestureDetector import de.mm20.launcher2.ui.ktx.animateTo -import de.mm20.launcher2.ui.ktx.toPixels import de.mm20.launcher2.ui.launcher.helper.WallpaperBlur import de.mm20.launcher2.ui.launcher.search.SearchColumn import de.mm20.launcher2.ui.launcher.search.SearchVM @@ -87,10 +78,8 @@ import de.mm20.launcher2.ui.launcher.searchbar.LauncherSearchBar import de.mm20.launcher2.ui.launcher.widgets.WidgetColumn import de.mm20.launcher2.ui.launcher.widgets.clock.ClockWidget import de.mm20.launcher2.ui.locals.LocalPreferDarkContentOverWallpaper -import de.mm20.launcher2.ui.utils.rememberNotificationShadeController import kotlinx.coroutines.launch import kotlin.math.absoluteValue -import kotlin.math.roundToInt @Composable fun PagerScaffold( @@ -252,7 +241,7 @@ fun PagerScaffold( val keyboardController = LocalSoftwareKeyboardController.current - val gestureManager = LocalGestureManager.current + val gestureManager = LocalGestureDetector.current val searchBarOffset = remember { mutableStateOf(0f) } val density = LocalDensity.current @@ -265,14 +254,14 @@ fun PagerScaffold( available: Offset, source: NestedScrollSource ): Offset { - if (source == NestedScrollSource.Drag) gestureManager.reportDrag(available) + if (source == NestedScrollSource.Drag) gestureManager.dispatchDrag(available) val deltaSearchBarOffset = consumed.y * if (isSearchOpen && reverseSearchResults) 1 else -1 searchBarOffset.value = (searchBarOffset.value + deltaSearchBarOffset).coerceIn(0f, maxSearchBarOffset) return super.onPostScroll(consumed, available, source) } override suspend fun onPreFling(available: Velocity): Velocity { - gestureManager.reportDragEnd() + gestureManager.dispatchDragEnd() return super.onPreFling(available) } } @@ -350,10 +339,10 @@ fun PagerScaffold( .pointerInput(Unit) { detectTapGestures( onDoubleTap = { - gestureManager.reportDoubleTap(it) + gestureManager.dispatchDoubleTap(it) }, onLongPress = { - gestureManager.reportLongPress(it) + gestureManager.dispatchLongPress(it) } ) } @@ -453,7 +442,6 @@ fun PagerScaffold( val searchBarLevel by remember { derivedStateOf { - Log.d("MM20", pagerState.currentPageOffsetFraction.toString()) when { pagerState.currentPageOffsetFraction != 0f -> SearchBarLevel.Raised !isSearchOpen && isWidgetsScrollZero && fillClockHeight -> SearchBarLevel.Resting diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/PullDownScaffold.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/PullDownScaffold.kt index 86ecfb30..5459f44b 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/PullDownScaffold.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/PullDownScaffold.kt @@ -7,11 +7,8 @@ import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.slideIn import androidx.compose.animation.slideOut import androidx.compose.foundation.LocalOverscrollConfiguration -import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.gestures.scrollable -import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column @@ -69,7 +66,7 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController import de.mm20.launcher2.preferences.Settings import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.component.SearchBarLevel -import de.mm20.launcher2.ui.gestures.LocalGestureManager +import de.mm20.launcher2.ui.gestures.LocalGestureDetector import de.mm20.launcher2.ui.ktx.animateTo import de.mm20.launcher2.ui.launcher.helper.WallpaperBlur import de.mm20.launcher2.ui.launcher.search.SearchColumn @@ -248,7 +245,7 @@ fun PullDownScaffold( } val keyboardController = LocalSoftwareKeyboardController.current - val gestureManager = LocalGestureManager.current + val gestureManager = LocalGestureDetector.current val nestedScrollConnection = remember { object : NestedScrollConnection { @@ -297,7 +294,7 @@ fun PullDownScaffold( if (offsetY.value > toggleSearchThreshold || offsetY.value < -toggleSearchThreshold) { viewModel.toggleSearch() } - gestureManager.reportDragEnd() + gestureManager.dispatchDragEnd() if (offsetY.value != 0f) { offsetY.animateTo(0f) return available @@ -313,10 +310,10 @@ fun PullDownScaffold( .pointerInput(Unit) { detectHorizontalDragGestures( onDragEnd = { - gestureManager.reportDragEnd() + gestureManager.dispatchDragEnd() }, onHorizontalDrag = { _, dragAmount -> - gestureManager.reportDrag(Offset(dragAmount, 0f)) + gestureManager.dispatchDrag(Offset(dragAmount, 0f)) } ) } @@ -399,10 +396,10 @@ fun PullDownScaffold( .pointerInput(Unit) { detectTapGestures( onDoubleTap = { - gestureManager.reportDoubleTap(it) + gestureManager.dispatchDoubleTap(it) }, onLongPress = { - gestureManager.reportLongPress(it) + gestureManager.dispatchLongPress(it) } ) } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt index edeb29c7..0c384a5b 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/SharedLauncherActivity.kt @@ -4,6 +4,7 @@ import android.app.WallpaperManager import android.content.res.Configuration import android.content.res.Resources import android.os.Bundle +import android.util.Log import androidx.activity.compose.setContent import androidx.activity.viewModels import androidx.compose.foundation.background @@ -29,6 +30,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.unit.dp import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsControllerCompat import androidx.lifecycle.Lifecycle @@ -41,10 +43,13 @@ import de.mm20.launcher2.ui.base.BaseActivity import de.mm20.launcher2.ui.base.ProvideCurrentTime import de.mm20.launcher2.ui.base.ProvideSettings import de.mm20.launcher2.ui.component.NavBarEffects +import de.mm20.launcher2.ui.gestures.GestureDetector +import de.mm20.launcher2.ui.gestures.GestureHandler +import de.mm20.launcher2.ui.gestures.LocalGestureDetector import de.mm20.launcher2.ui.ktx.animateTo +import de.mm20.launcher2.ui.ktx.toPixels import de.mm20.launcher2.ui.launcher.sheets.CustomizeSearchableSheet import de.mm20.launcher2.ui.launcher.sheets.EditFavoritesSheet -import de.mm20.launcher2.ui.launcher.sheets.HiddenItemsSheet import de.mm20.launcher2.ui.launcher.sheets.LauncherBottomSheetManager import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager import de.mm20.launcher2.ui.launcher.transitions.HomeTransition @@ -56,6 +61,7 @@ import de.mm20.launcher2.ui.locals.LocalWallpaperColors import de.mm20.launcher2.ui.locals.LocalWindowSize import de.mm20.launcher2.ui.theme.LauncherTheme import de.mm20.launcher2.ui.theme.wallpaperColorsAsState +import kotlin.math.absoluteValue import kotlin.math.pow @@ -81,6 +87,7 @@ abstract class SharedLauncherActivity( viewModel.setSystemInDarkMode(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES) val bottomSheetManager = LauncherBottomSheetManager() + val gestureDetector = GestureDetector() setContent { val snackbarHostState = remember { SnackbarHostState() } @@ -93,15 +100,20 @@ abstract class SharedLauncherActivity( LocalWallpaperColors provides wallpaperColors, LocalPreferDarkContentOverWallpaper provides (!dimBackground && wallpaperColors.supportsDarkText), LocalBottomSheetManager provides bottomSheetManager, + LocalGestureDetector provides gestureDetector, ) { LauncherTheme { ProvideCurrentTime { ProvideSettings { - val statusBarColor by viewModel.statusBarColor.observeAsState(SystemBarColors.Auto) + val statusBarColor by viewModel.statusBarColor.observeAsState( + SystemBarColors.Auto + ) val navBarColor by viewModel.navBarColor.observeAsState(SystemBarColors.Auto) - val lightStatus = !dimBackground && (statusBarColor == SystemBarColors.Dark || statusBarColor == SystemBarColors.Auto && wallpaperColors.supportsDarkText) - val lightNav = !dimBackground && (navBarColor == SystemBarColors.Dark || navBarColor == SystemBarColors.Auto && wallpaperColors.supportsDarkText) + val lightStatus = + !dimBackground && (statusBarColor == SystemBarColors.Dark || statusBarColor == SystemBarColors.Auto && wallpaperColors.supportsDarkText) + val lightNav = + !dimBackground && (navBarColor == SystemBarColors.Dark || navBarColor == SystemBarColors.Auto && wallpaperColors.supportsDarkText) val hideStatus by viewModel.hideStatusBar.observeAsState(false) val hideNav by viewModel.hideNavBar.observeAsState(false) @@ -207,7 +219,8 @@ abstract class SharedLauncherActivity( enterTransition?.let { val dX = it.startBounds.center.x - it.targetBounds.center.x val dY = it.startBounds.center.y - it.targetBounds.center.y - val s = (it.startBounds.minDimension / it.targetBounds.minDimension - 1f) * 0.5f + val s = + (it.startBounds.minDimension / it.targetBounds.minDimension - 1f) * 0.5f Box( modifier = Modifier .align(Alignment.TopStart) @@ -220,17 +233,60 @@ abstract class SharedLauncherActivity( scaleX = 1f + s * (1 - p) scaleY = 1f + s * (1 - p) }) { - it.icon?.invoke(Offset(dX, dY)) { enterTransitionProgress.value } + it.icon?.invoke( + Offset( + dX, + dY + ) + ) { enterTransitionProgress.value } } } bottomSheetManager.customizeSearchableSheetShown.value?.let { - CustomizeSearchableSheet(searchable = it, onDismiss = { bottomSheetManager.dismissCustomizeSearchableModal() }) + CustomizeSearchableSheet( + searchable = it, + onDismiss = { bottomSheetManager.dismissCustomizeSearchableModal() }) } if (bottomSheetManager.editFavoritesSheetShown.value) { EditFavoritesSheet(onDismiss = { bottomSheetManager.dismissEditFavoritesSheet() }) } } + + val swipeThreshold = 150.dp.toPixels() + GestureHandler( + detector = gestureDetector, + onDoubleTap = { + Log.d("MM20", "Double tap") + }, + onLongPress = { + Log.d("MM20", "Long press") + }, + onDrag = { + return@GestureHandler when { + it.x > swipeThreshold && it.x.absoluteValue > it.y.absoluteValue * 2f -> { + Log.d("MM20", "Swipe right") + true + } + + it.x < -swipeThreshold && it.x.absoluteValue > it.y.absoluteValue * 2f -> { + Log.d("MM20", "Swipe left") + true + } + + it.y > swipeThreshold && it.y.absoluteValue > it.x.absoluteValue * 2f -> { + Log.d("MM20", "Swipe down") + true + } + + it.y < -swipeThreshold && it.y.absoluteValue > it.x.absoluteValue * 2f -> { + Log.d("MM20", "Swipe up") + true + } + + else -> false + } + } + ) } } }