Add handler for launch app gesture action

This commit is contained in:
MM20 2023-02-25 17:55:48 +01:00
parent 77e22e5f2b
commit 9a4a9a294f
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
3 changed files with 163 additions and 71 deletions

View File

@ -1,6 +1,6 @@
package de.mm20.launcher2.ui.launcher
import android.util.Log
import android.content.Context
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@ -8,19 +8,23 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.favorites.FavoritesRepository
import de.mm20.launcher2.globalactions.GlobalActionsService
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.preferences.Settings.GestureSettings
import de.mm20.launcher2.preferences.Settings.GestureSettings.GestureAction
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.gestures.Gesture
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@ -30,17 +34,7 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
private val dataStore: LauncherDataStore by inject()
private val globalActionsService: GlobalActionsService by inject()
private val permissionsManager: PermissionsManager by inject()
private var gestureSettings : GestureSettings? = null
init {
viewModelScope.launch {
dataStore.data.map { it.gestures }.collectLatest {
gestureSettings = it
}
}
}
private val favoritesRepository: FavoritesRepository by inject()
private var isSystemInDarkMode = MutableStateFlow(false)
@ -112,15 +106,46 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
val searchBarStyle = dataStore.data.map { it.searchBar.searchBarStyle }.asLiveData()
val shouldDetectDoubleTapGesture = dataStore.data.map { it.gestures.doubleTap != GestureAction.None }.asLiveData()
val gestureState: StateFlow<GestureState> = dataStore
.data.map { it.gestures }
.distinctUntilChanged()
.map { settings ->
val swipeLeftAction = settings?.swipeLeft ?: GestureAction.None
val swipeRightAction = settings?.swipeRight ?: GestureAction.None
val swipeDownAction = settings?.swipeDown ?: GestureAction.None
val longPressAction = settings?.longPress ?: GestureAction.None
val doubleTapAction = settings?.doubleTap ?: GestureAction.None
val apps = listOfNotNull(
if (swipeLeftAction == GestureAction.LaunchApp) settings.swipeLeftApp else null,
if (swipeRightAction == GestureAction.LaunchApp) settings.swipeRightApp else null,
if (swipeDownAction == GestureAction.LaunchApp) settings.swipeDownApp else null,
if (longPressAction == GestureAction.LaunchApp) settings.longPressApp else null,
if (doubleTapAction == GestureAction.LaunchApp) settings.doubleTapApp else null
).let { favoritesRepository.getFromKeys(it) }
GestureState(
swipeLeftAction = swipeLeftAction,
swipeRightAction = swipeRightAction,
swipeDownAction = swipeDownAction,
longPressAction = longPressAction,
doubleTapAction = doubleTapAction,
swipeLeftApp = apps.firstOrNull { it.key == settings?.swipeLeftApp },
swipeRightApp = apps.firstOrNull { it.key == settings?.swipeRightApp },
swipeDownApp = apps.firstOrNull { it.key == settings?.swipeDownApp },
longPressApp = apps.firstOrNull { it.key == settings?.longPressApp },
doubleTapApp = apps.firstOrNull { it.key == settings?.doubleTapApp }
)
}.stateIn(viewModelScope, SharingStarted.Eagerly, GestureState())
var failedGestureState by mutableStateOf<FailedGesture?>(null)
fun handleGesture(gesture: Gesture): Boolean {
fun handleGesture(context: Context, gesture: Gesture): Boolean {
val action = when (gesture) {
Gesture.DoubleTap -> gestureSettings?.doubleTap
Gesture.LongPress -> gestureSettings?.longPress
Gesture.SwipeDown -> gestureSettings?.swipeDown?.takeIf { baseLayout.value != Settings.LayoutSettings.Layout.PullDown }
Gesture.SwipeLeft -> gestureSettings?.swipeLeft?.takeIf { baseLayout.value != Settings.LayoutSettings.Layout.Pager }
Gesture.SwipeRight -> gestureSettings?.swipeRight?.takeIf { baseLayout.value != Settings.LayoutSettings.Layout.PagerReversed }
Gesture.DoubleTap -> gestureState.value.doubleTapAction
Gesture.LongPress -> gestureState.value.longPressAction
Gesture.SwipeDown -> gestureState.value.swipeDownAction?.takeIf { baseLayout.value != Settings.LayoutSettings.Layout.PullDown }
Gesture.SwipeLeft -> gestureState.value.swipeLeftAction?.takeIf { baseLayout.value != Settings.LayoutSettings.Layout.Pager }
Gesture.SwipeRight -> gestureState.value.swipeRightAction?.takeIf { baseLayout.value != Settings.LayoutSettings.Layout.PagerReversed }
}
val requiresAccessibilityService =
action == GestureAction.OpenRecents
@ -129,7 +154,10 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
|| action == GestureAction.OpenNotificationDrawer
|| action == GestureAction.LockScreen
if (action != null && requiresAccessibilityService && !permissionsManager.checkPermissionOnce(PermissionGroup.Accessibility)) {
if (action != null && requiresAccessibilityService && !permissionsManager.checkPermissionOnce(
PermissionGroup.Accessibility
)
) {
failedGestureState = FailedGesture(gesture, action)
return true
}
@ -166,6 +194,17 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
true
}
GestureAction.LaunchApp -> {
when (gesture) {
Gesture.SwipeLeft -> gestureState.value.swipeLeftApp
Gesture.SwipeRight -> gestureState.value.swipeRightApp
Gesture.SwipeDown -> gestureState.value.swipeDownApp
Gesture.LongPress -> gestureState.value.longPressApp
Gesture.DoubleTap -> gestureState.value.doubleTapApp
}?.launch(context, null)
true
}
else -> false
}
}
@ -175,4 +214,17 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
}
}
data class GestureState(
val swipeLeftAction: GestureAction = GestureAction.None,
val swipeRightAction: GestureAction = GestureAction.None,
val swipeDownAction: GestureAction = GestureAction.None,
val longPressAction: GestureAction = GestureAction.None,
val doubleTapAction: GestureAction = GestureAction.None,
val swipeLeftApp: SavableSearchable? = null,
val swipeRightApp: SavableSearchable? = null,
val swipeDownApp: SavableSearchable? = null,
val longPressApp: SavableSearchable? = null,
val doubleTapApp: SavableSearchable? = null,
)
data class FailedGesture(val gesture: Gesture, val action: GestureAction)

View File

@ -53,6 +53,7 @@ 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.gestures.LauncherGestureHandler
import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.sheets.FailedGestureSheet
import de.mm20.launcher2.ui.launcher.sheets.LauncherBottomSheets
@ -97,10 +98,6 @@ abstract class SharedLauncherActivity(
val bottomSheetManager = LauncherBottomSheetManager()
val gestureDetector = GestureDetector()
viewModel.shouldDetectDoubleTapGesture.observe(this) {
gestureDetector.shouldDetectDoubleTaps = it
}
setContent {
val snackbarHostState = remember { SnackbarHostState() }
val wallpaperColors by wallpaperColorsAsState()
@ -270,51 +267,7 @@ abstract class SharedLauncherActivity(
}
LauncherBottomSheets()
}
val swipeThreshold = 150.dp.toPixels()
GestureHandler(
detector = gestureDetector,
onDoubleTap = {
viewModel.handleGesture(Gesture.DoubleTap)
},
onLongPress = {
viewModel.handleGesture(Gesture.LongPress)
},
onDrag = {
return@GestureHandler when {
it.x > swipeThreshold && it.x.absoluteValue > it.y.absoluteValue * 2f -> {
viewModel.handleGesture(Gesture.SwipeRight)
}
it.x < -swipeThreshold && it.x.absoluteValue > it.y.absoluteValue * 2f -> {
viewModel.handleGesture(Gesture.SwipeLeft)
}
it.y > swipeThreshold && it.y.absoluteValue > it.x.absoluteValue * 2f -> {
viewModel.handleGesture(Gesture.SwipeDown)
}
else -> false
}
},
onTap = {
wallpaperManager.sendWallpaperCommand(
window.decorView.windowToken,
WallpaperManager.COMMAND_TAP,
it.x.toInt(),
it.y.toInt(),
0,
null
)
}
)
if (viewModel.failedGestureState != null) {
FailedGestureSheet(
failedGesture = viewModel.failedGestureState!!,
onDismiss = {
viewModel.dismissGestureFailedSheet()
}
)
}
LauncherGestureHandler()
}
}
}

View File

@ -0,0 +1,87 @@
package de.mm20.launcher2.ui.launcher.gestures
import android.app.WallpaperManager
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.preferences.Settings.GestureSettings.GestureAction
import de.mm20.launcher2.ui.gestures.Gesture
import de.mm20.launcher2.ui.gestures.GestureHandler
import de.mm20.launcher2.ui.gestures.LocalGestureDetector
import de.mm20.launcher2.ui.ktx.toPixels
import de.mm20.launcher2.ui.launcher.GestureState
import de.mm20.launcher2.ui.launcher.LauncherScaffoldVM
import de.mm20.launcher2.ui.launcher.sheets.FailedGestureSheet
import kotlin.math.absoluteValue
@Composable
fun LauncherGestureHandler() {
val context = LocalContext.current
val wallpaperManager = remember { WallpaperManager.getInstance(context) }
val gestureDetector = LocalGestureDetector.current
val viewModel: LauncherScaffoldVM = viewModel()
val gestureState by viewModel.gestureState.collectAsState(GestureState())
val shouldDetectDoubleTapGesture = gestureState.gdoubleTapAction != GestureAction.None
LaunchedEffect(shouldDetectDoubleTapGesture) {
gestureDetector.shouldDetectDoubleTaps = shouldDetectDoubleTapGesture
}
val windowToken = LocalView.current.windowToken
val swipeThreshold = 150.dp.toPixels()
GestureHandler(
detector = gestureDetector,
onDoubleTap = {
viewModel.handleGesture(context, Gesture.DoubleTap)
},
onLongPress = {
viewModel.handleGesture(context, Gesture.LongPress)
},
onDrag = {
return@GestureHandler when {
it.x > swipeThreshold && it.x.absoluteValue > it.y.absoluteValue * 2f -> {
viewModel.handleGesture(context, Gesture.SwipeRight)
}
it.x < -swipeThreshold && it.x.absoluteValue > it.y.absoluteValue * 2f -> {
viewModel.handleGesture(context, Gesture.SwipeLeft)
}
it.y > swipeThreshold && it.y.absoluteValue > it.x.absoluteValue * 2f -> {
viewModel.handleGesture(context, Gesture.SwipeDown)
}
else -> false
}
},
onTap = {
wallpaperManager.sendWallpaperCommand(
windowToken,
WallpaperManager.COMMAND_TAP,
it.x.toInt(),
it.y.toInt(),
0,
null
)
}
)
if (viewModel.failedGestureState != null) {
FailedGestureSheet(
failedGesture = viewModel.failedGestureState!!,
onDismiss = {
viewModel.dismissGestureFailedSheet()
}
)
}
}