Add home button gesture trigger

This commit is contained in:
MM20 2023-06-23 18:12:05 +02:00
parent 918f340dd2
commit 62c1a4293f
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
12 changed files with 85 additions and 16 deletions

View File

@ -23,6 +23,7 @@ import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.searchactions.actions.SearchAction import de.mm20.launcher2.searchactions.actions.SearchAction
import de.mm20.launcher2.ui.component.SearchBarLevel import de.mm20.launcher2.ui.component.SearchBarLevel
import de.mm20.launcher2.ui.launcher.LauncherScaffoldVM import de.mm20.launcher2.ui.launcher.LauncherScaffoldVM
import de.mm20.launcher2.ui.launcher.gestures.LauncherGestureHandler
import de.mm20.launcher2.ui.launcher.helper.WallpaperBlur import de.mm20.launcher2.ui.launcher.helper.WallpaperBlur
import de.mm20.launcher2.ui.launcher.search.SearchColumn import de.mm20.launcher2.ui.launcher.search.SearchColumn
import de.mm20.launcher2.ui.launcher.search.SearchVM import de.mm20.launcher2.ui.launcher.search.SearchVM
@ -215,4 +216,5 @@ fun AssistantScaffold(
} else null } else null
) )
} }
LauncherGestureHandler()
} }

View File

@ -6,4 +6,5 @@ enum class Gesture {
SwipeDown, SwipeDown,
SwipeLeft, SwipeLeft,
SwipeRight, SwipeRight,
HomeButton,
} }

View File

@ -41,6 +41,9 @@ class GestureDetector {
gestureListener?.onDragEnd() gestureListener?.onDragEnd()
} }
fun dispatchHomeButtonPress() {
gestureListener?.onHomeButtonPress()
}
interface OnGestureListener { interface OnGestureListener {
fun onTap(position: Offset) {} fun onTap(position: Offset) {}
@ -54,6 +57,8 @@ class GestureDetector {
fun onDrag(offset: Offset): Boolean = false fun onDrag(offset: Offset): Boolean = false
fun onDragEnd() {} fun onDragEnd() {}
fun onHomeButtonPress() {}
} }
} }

View File

@ -12,6 +12,7 @@ fun GestureHandler(
onDoubleTap: (Offset) -> Unit = {}, onDoubleTap: (Offset) -> Unit = {},
onDrag: (Offset) -> Boolean = { false }, onDrag: (Offset) -> Boolean = { false },
onDragEnd: () -> Unit = {}, onDragEnd: () -> Unit = {},
onHomeButtonPress: () -> Unit = {},
) { ) {
DisposableEffect(detector) { DisposableEffect(detector) {
detector.gestureListener = object : GestureDetector.OnGestureListener { detector.gestureListener = object : GestureDetector.OnGestureListener {
@ -34,6 +35,10 @@ fun GestureHandler(
override fun onDragEnd() { override fun onDragEnd() {
onDragEnd() onDragEnd()
} }
override fun onHomeButtonPress() {
onHomeButtonPress()
}
} }
onDispose { onDispose {
detector.gestureListener = null detector.gestureListener = null

View File

@ -1,6 +1,7 @@
package de.mm20.launcher2.ui.launcher package de.mm20.launcher2.ui.launcher
import android.content.Intent import android.content.Intent
import android.util.Log
import com.android.launcher3.GestureNavContract import com.android.launcher3.GestureNavContract
@ -10,14 +11,20 @@ class LauncherActivity: SharedLauncherActivity(LauncherActivityMode.Launcher) {
val navContract = intent?.let { GestureNavContract.fromIntent(it) } val navContract = intent?.let { GestureNavContract.fromIntent(it) }
if (navContract != null) { if (navContract != null) {
enterHomeTransitionManager.resolve(navContract, window) enterHomeTransitionManager.resolve(navContract, window)
} else { } else if (System.currentTimeMillis() - pausedAt < 50) {
onBackPressed() // If the onPause was called less than 50ms ago, we assume that the app was already
// in the foreground when the user pressed the home button. In this case, we dispatch
// the home button press event to the gesture detector.
gestureDetector.dispatchHomeButtonPress()
} }
} }
private var pausedAt: Long = 0
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
enterHomeTransitionManager.clear() enterHomeTransitionManager.clear()
pausedAt = System.currentTimeMillis()
} }
override fun onBackPressed() { override fun onBackPressed() {

View File

@ -94,7 +94,7 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
} }
fun closeSearch() { fun closeSearch() {
if (isSearchOpen.value == false) return if (!isSearchOpen.value) return
isSearchOpen.value = false isSearchOpen.value = false
setSearchbarFocus(false) setSearchbarFocus(false)
} }
@ -130,6 +130,8 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
settings?.swipeDown?.takeIf { layout != Layout.PullDown } ?: GestureAction.None settings?.swipeDown?.takeIf { layout != Layout.PullDown } ?: GestureAction.None
val longPressAction = settings?.longPress ?: GestureAction.None val longPressAction = settings?.longPress ?: GestureAction.None
val doubleTapAction = settings?.doubleTap ?: GestureAction.None val doubleTapAction = settings?.doubleTap ?: GestureAction.None
val homeButtonAction = settings?.homeButton ?: GestureAction.None
val swipeLeftAppKey = val swipeLeftAppKey =
if (swipeLeftAction == GestureAction.LaunchApp) settings.swipeLeftApp else null if (swipeLeftAction == GestureAction.LaunchApp) settings.swipeLeftApp else null
val swipeRightAppKey = val swipeRightAppKey =
@ -140,12 +142,15 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
if (longPressAction == GestureAction.LaunchApp) settings.longPressApp else null if (longPressAction == GestureAction.LaunchApp) settings.longPressApp else null
val doubleTapAppKey = val doubleTapAppKey =
if (doubleTapAction == GestureAction.LaunchApp) settings.doubleTapApp else null if (doubleTapAction == GestureAction.LaunchApp) settings.doubleTapApp else null
val homeButtonAppKey =
if (homeButtonAction == GestureAction.LaunchApp) settings.homeButtonApp else null
val apps = listOfNotNull( val apps = listOfNotNull(
swipeLeftAppKey, swipeLeftAppKey,
swipeRightAppKey, swipeRightAppKey,
swipeDownAppKey, swipeDownAppKey,
longPressAppKey, longPressAppKey,
doubleTapAppKey doubleTapAppKey,
homeButtonAppKey,
).let { searchableRepository.getByKeys(it) } ).let { searchableRepository.getByKeys(it) }
GestureState( GestureState(
@ -154,11 +159,13 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
swipeDownAction = swipeDownAction, swipeDownAction = swipeDownAction,
longPressAction = longPressAction, longPressAction = longPressAction,
doubleTapAction = doubleTapAction, doubleTapAction = doubleTapAction,
homeButtonAction = homeButtonAction,
swipeLeftApp = apps.firstOrNull { it.key == swipeLeftAppKey }, swipeLeftApp = apps.firstOrNull { it.key == swipeLeftAppKey },
swipeRightApp = apps.firstOrNull { it.key == swipeRightAppKey }, swipeRightApp = apps.firstOrNull { it.key == swipeRightAppKey },
swipeDownApp = apps.firstOrNull { it.key == swipeDownAppKey }, swipeDownApp = apps.firstOrNull { it.key == swipeDownAppKey },
longPressApp = apps.firstOrNull { it.key == longPressAppKey }, longPressApp = apps.firstOrNull { it.key == longPressAppKey },
doubleTapApp = apps.firstOrNull { it.key == doubleTapAppKey } doubleTapApp = apps.firstOrNull { it.key == doubleTapAppKey },
homeButtonApp = apps.firstOrNull { it.key == homeButtonAppKey },
) )
}.stateIn(viewModelScope, SharingStarted.Eagerly, GestureState()) }.stateIn(viewModelScope, SharingStarted.Eagerly, GestureState())
@ -167,9 +174,10 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
val action = when (gesture) { val action = when (gesture) {
Gesture.DoubleTap -> gestureState.value.doubleTapAction Gesture.DoubleTap -> gestureState.value.doubleTapAction
Gesture.LongPress -> gestureState.value.longPressAction Gesture.LongPress -> gestureState.value.longPressAction
Gesture.SwipeDown -> gestureState.value.swipeDownAction.takeIf { baseLayout.value != Settings.LayoutSettings.Layout.PullDown } Gesture.SwipeDown -> gestureState.value.swipeDownAction.takeIf { baseLayout.value != Layout.PullDown }
Gesture.SwipeLeft -> gestureState.value.swipeLeftAction.takeIf { baseLayout.value != Settings.LayoutSettings.Layout.Pager } Gesture.SwipeLeft -> gestureState.value.swipeLeftAction.takeIf { baseLayout.value != Layout.Pager }
Gesture.SwipeRight -> gestureState.value.swipeRightAction.takeIf { baseLayout.value != Settings.LayoutSettings.Layout.PagerReversed } Gesture.SwipeRight -> gestureState.value.swipeRightAction.takeIf { baseLayout.value != Layout.PagerReversed }
Gesture.HomeButton -> gestureState.value.homeButtonAction
} }
val requiresAccessibilityService = val requiresAccessibilityService =
action == GestureAction.OpenRecents action == GestureAction.OpenRecents
@ -233,6 +241,7 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
Gesture.SwipeDown -> gestureState.value.swipeDownApp Gesture.SwipeDown -> gestureState.value.swipeDownApp
Gesture.LongPress -> gestureState.value.longPressApp Gesture.LongPress -> gestureState.value.longPressApp
Gesture.DoubleTap -> gestureState.value.doubleTapApp Gesture.DoubleTap -> gestureState.value.doubleTapApp
Gesture.HomeButton -> gestureState.value.homeButtonApp
}?.launch(context, options.toBundle()) }?.launch(context, options.toBundle())
true true
} }
@ -252,11 +261,13 @@ data class GestureState(
val swipeDownAction: GestureAction = GestureAction.None, val swipeDownAction: GestureAction = GestureAction.None,
val longPressAction: GestureAction = GestureAction.None, val longPressAction: GestureAction = GestureAction.None,
val doubleTapAction: GestureAction = GestureAction.None, val doubleTapAction: GestureAction = GestureAction.None,
val homeButtonAction: GestureAction = GestureAction.None,
val swipeLeftApp: SavableSearchable? = null, val swipeLeftApp: SavableSearchable? = null,
val swipeRightApp: SavableSearchable? = null, val swipeRightApp: SavableSearchable? = null,
val swipeDownApp: SavableSearchable? = null, val swipeDownApp: SavableSearchable? = null,
val longPressApp: SavableSearchable? = null, val longPressApp: SavableSearchable? = null,
val doubleTapApp: SavableSearchable? = null, val doubleTapApp: SavableSearchable? = null,
val homeButtonApp: SavableSearchable? = null,
) )
data class FailedGesture(val gesture: Gesture, val action: GestureAction) data class FailedGesture(val gesture: Gesture, val action: GestureAction)

View File

@ -69,12 +69,12 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.accompanist.systemuicontroller.rememberSystemUiController
import de.mm20.launcher2.preferences.Settings.SearchBarSettings.SearchBarColors import de.mm20.launcher2.preferences.Settings.SearchBarSettings.SearchBarColors
import de.mm20.launcher2.preferences.Settings.SearchBarSettings.SearchBarStyle
import de.mm20.launcher2.searchactions.actions.SearchAction import de.mm20.launcher2.searchactions.actions.SearchAction
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.SearchBarLevel import de.mm20.launcher2.ui.component.SearchBarLevel
import de.mm20.launcher2.ui.gestures.LocalGestureDetector import de.mm20.launcher2.ui.gestures.LocalGestureDetector
import de.mm20.launcher2.ui.ktx.animateTo import de.mm20.launcher2.ui.ktx.animateTo
import de.mm20.launcher2.ui.launcher.gestures.LauncherGestureHandler
import de.mm20.launcher2.ui.launcher.helper.WallpaperBlur import de.mm20.launcher2.ui.launcher.helper.WallpaperBlur
import de.mm20.launcher2.ui.launcher.search.SearchColumn import de.mm20.launcher2.ui.launcher.search.SearchColumn
import de.mm20.launcher2.ui.launcher.search.SearchVM import de.mm20.launcher2.ui.launcher.search.SearchVM
@ -227,15 +227,18 @@ fun PagerScaffold(
val searchBarOffset = remember { mutableStateOf(0f) } val searchBarOffset = remember { mutableStateOf(0f) }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
BackHandler {
val `handleBackOrHomeEvent` = {
when { when {
isSearchOpen -> { isSearchOpen -> {
viewModel.closeSearch() viewModel.closeSearch()
searchVM.search("") searchVM.search("")
true
} }
isWidgetEditMode -> { isWidgetEditMode -> {
viewModel.setWidgetEditMode(false) viewModel.setWidgetEditMode(false)
true
} }
widgetsScrollState.value != 0 -> { widgetsScrollState.value != 0 -> {
@ -245,10 +248,16 @@ fun PagerScaffold(
scope.launch { scope.launch {
searchBarOffset.animateTo(0f) searchBarOffset.animateTo(0f)
} }
true
} }
else -> false
} }
} }
BackHandler {
`handleBackOrHomeEvent`()
}
val keyboardController = LocalSoftwareKeyboardController.current val keyboardController = LocalSoftwareKeyboardController.current
val gestureManager = LocalGestureDetector.current val gestureManager = LocalGestureDetector.current
@ -542,6 +551,9 @@ fun PagerScaffold(
} else null } else null
) )
} }
LauncherGestureHandler(
onHomeButtonPress = handleBackOrHomeEvent,
)
} }
private enum class Page { private enum class Page {

View File

@ -72,6 +72,7 @@ import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.SearchBarLevel import de.mm20.launcher2.ui.component.SearchBarLevel
import de.mm20.launcher2.ui.gestures.LocalGestureDetector import de.mm20.launcher2.ui.gestures.LocalGestureDetector
import de.mm20.launcher2.ui.ktx.animateTo import de.mm20.launcher2.ui.ktx.animateTo
import de.mm20.launcher2.ui.launcher.gestures.LauncherGestureHandler
import de.mm20.launcher2.ui.launcher.helper.WallpaperBlur import de.mm20.launcher2.ui.launcher.helper.WallpaperBlur
import de.mm20.launcher2.ui.launcher.search.SearchColumn import de.mm20.launcher2.ui.launcher.search.SearchColumn
import de.mm20.launcher2.ui.launcher.search.SearchVM import de.mm20.launcher2.ui.launcher.search.SearchVM
@ -247,15 +248,17 @@ fun PullDownScaffold(
if (!isWidgetEditMode) searchBarOffset.value = 0f if (!isWidgetEditMode) searchBarOffset.value = 0f
} }
BackHandler { val handleBackOrHomeEvent = {
when { when {
isSearchOpen -> { isSearchOpen -> {
viewModel.closeSearch() viewModel.closeSearch()
searchVM.search("") searchVM.search("")
true
} }
isWidgetEditMode -> { isWidgetEditMode -> {
viewModel.setWidgetEditMode(false) viewModel.setWidgetEditMode(false)
true
} }
widgetsScrollState.value != 0 -> { widgetsScrollState.value != 0 -> {
@ -265,10 +268,16 @@ fun PullDownScaffold(
scope.launch { scope.launch {
searchBarOffset.animateTo(0f) searchBarOffset.animateTo(0f)
} }
true
} }
else -> false
} }
} }
BackHandler {
handleBackOrHomeEvent()
}
val keyboardController = LocalSoftwareKeyboardController.current val keyboardController = LocalSoftwareKeyboardController.current
val gestureManager = LocalGestureDetector.current val gestureManager = LocalGestureDetector.current
@ -571,4 +580,7 @@ fun PullDownScaffold(
) )
} }
LauncherGestureHandler(
onHomeButtonPress = handleBackOrHomeEvent,
)
} }

View File

@ -44,10 +44,9 @@ import de.mm20.launcher2.ui.component.NavBarEffects
import de.mm20.launcher2.ui.gestures.GestureDetector import de.mm20.launcher2.ui.gestures.GestureDetector
import de.mm20.launcher2.ui.gestures.LocalGestureDetector import de.mm20.launcher2.ui.gestures.LocalGestureDetector
import de.mm20.launcher2.ui.ktx.animateTo import de.mm20.launcher2.ui.ktx.animateTo
import de.mm20.launcher2.ui.launcher.gestures.LauncherGestureHandler
import de.mm20.launcher2.ui.launcher.search.SearchVM import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.sheets.LauncherBottomSheets
import de.mm20.launcher2.ui.launcher.sheets.LauncherBottomSheetManager import de.mm20.launcher2.ui.launcher.sheets.LauncherBottomSheetManager
import de.mm20.launcher2.ui.launcher.sheets.LauncherBottomSheets
import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager import de.mm20.launcher2.ui.launcher.sheets.LocalBottomSheetManager
import de.mm20.launcher2.ui.launcher.transitions.EnterHomeTransition import de.mm20.launcher2.ui.launcher.transitions.EnterHomeTransition
import de.mm20.launcher2.ui.launcher.transitions.EnterHomeTransitionManager import de.mm20.launcher2.ui.launcher.transitions.EnterHomeTransitionManager
@ -70,6 +69,8 @@ abstract class SharedLauncherActivity(
internal val enterHomeTransitionManager = EnterHomeTransitionManager() internal val enterHomeTransitionManager = EnterHomeTransitionManager()
internal val gestureDetector = GestureDetector()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -84,7 +85,6 @@ abstract class SharedLauncherActivity(
viewModel.setSystemInDarkMode(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES) viewModel.setSystemInDarkMode(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES)
val bottomSheetManager = LauncherBottomSheetManager(this) val bottomSheetManager = LauncherBottomSheetManager(this)
val gestureDetector = GestureDetector()
setContent { setContent {
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
@ -258,7 +258,6 @@ abstract class SharedLauncherActivity(
} }
LauncherBottomSheets() LauncherBottomSheets()
} }
LauncherGestureHandler()
} }
} }
} }

View File

@ -34,8 +34,15 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
/**
* Handles gestures on the launcher according to the user's settings.
* @param onHomeButtonPress Called when the home button is pressed. Allows the caller to intercept the event.
* If the function returns true, the event is considered handled and the default action is not performed.
*/
@Composable @Composable
fun LauncherGestureHandler() { fun LauncherGestureHandler(
onHomeButtonPress: () -> Boolean = { false }
) {
val context = LocalContext.current val context = LocalContext.current
val wallpaperManager = remember { WallpaperManager.getInstance(context) } val wallpaperManager = remember { WallpaperManager.getInstance(context) }
val gestureDetector = LocalGestureDetector.current val gestureDetector = LocalGestureDetector.current
@ -69,6 +76,12 @@ fun LauncherGestureHandler() {
onLongPress = { onLongPress = {
viewModel.handleGesture(context, Gesture.LongPress) viewModel.handleGesture(context, Gesture.LongPress)
}, },
onHomeButtonPress = {
if (onHomeButtonPress()) {
return@GestureHandler
}
viewModel.handleGesture(context, Gesture.HomeButton)
},
onDrag = { onDrag = {
when { when {
gestureState.swipeRightApp != null && it.x > swipeStartThreshold && ( gestureState.swipeRightApp != null && it.x > swipeStartThreshold && (

View File

@ -43,6 +43,7 @@ fun FailedGestureSheet(
Gesture.SwipeDown -> R.string.preference_gesture_swipe_down Gesture.SwipeDown -> R.string.preference_gesture_swipe_down
Gesture.SwipeLeft -> R.string.preference_gesture_swipe_left Gesture.SwipeLeft -> R.string.preference_gesture_swipe_left
Gesture.SwipeRight -> R.string.preference_gesture_swipe_right Gesture.SwipeRight -> R.string.preference_gesture_swipe_right
Gesture.HomeButton -> R.string.preference_gesture_home_button
}) })
BottomSheetDialog( BottomSheetDialog(

View File

@ -31,6 +31,7 @@ class FailedGestureSheetVM : ViewModel(), KoinComponent {
Gesture.SwipeRight -> swipeRight = GestureAction.None Gesture.SwipeRight -> swipeRight = GestureAction.None
Gesture.DoubleTap -> doubleTap = GestureAction.None Gesture.DoubleTap -> doubleTap = GestureAction.None
Gesture.LongPress -> longPress = GestureAction.None Gesture.LongPress -> longPress = GestureAction.None
Gesture.HomeButton -> homeButton = GestureAction.None
} }
}.build() }.build()
).build() ).build()