Add gesture handler
This commit is contained in:
parent
3778e8405b
commit
7cd1269c33
@ -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> {
|
||||
GestureManager()
|
||||
val LocalGestureDetector = staticCompositionLocalOf<GestureDetector> {
|
||||
GestureDetector()
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user