Add handler for launch app gesture action
This commit is contained in:
parent
77e22e5f2b
commit
9a4a9a294f
@ -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)
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user