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
|
package de.mm20.launcher2.ui.launcher
|
||||||
|
|
||||||
import android.util.Log
|
import android.content.Context
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@ -8,19 +8,23 @@ import androidx.lifecycle.MutableLiveData
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||||
import de.mm20.launcher2.globalactions.GlobalActionsService
|
import de.mm20.launcher2.globalactions.GlobalActionsService
|
||||||
import de.mm20.launcher2.permissions.PermissionGroup
|
import de.mm20.launcher2.permissions.PermissionGroup
|
||||||
import de.mm20.launcher2.permissions.PermissionsManager
|
import de.mm20.launcher2.permissions.PermissionsManager
|
||||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
import de.mm20.launcher2.preferences.Settings
|
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.preferences.Settings.GestureSettings.GestureAction
|
||||||
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
import de.mm20.launcher2.ui.gestures.Gesture
|
import de.mm20.launcher2.ui.gestures.Gesture
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
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.combine
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
@ -30,17 +34,7 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
|
|||||||
private val dataStore: LauncherDataStore by inject()
|
private val dataStore: LauncherDataStore by inject()
|
||||||
private val globalActionsService: GlobalActionsService by inject()
|
private val globalActionsService: GlobalActionsService by inject()
|
||||||
private val permissionsManager: PermissionsManager by inject()
|
private val permissionsManager: PermissionsManager by inject()
|
||||||
|
private val favoritesRepository: FavoritesRepository by inject()
|
||||||
private var gestureSettings : GestureSettings? = null
|
|
||||||
|
|
||||||
|
|
||||||
init {
|
|
||||||
viewModelScope.launch {
|
|
||||||
dataStore.data.map { it.gestures }.collectLatest {
|
|
||||||
gestureSettings = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var isSystemInDarkMode = MutableStateFlow(false)
|
private var isSystemInDarkMode = MutableStateFlow(false)
|
||||||
|
|
||||||
@ -112,15 +106,46 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
|
|||||||
val searchBarStyle = dataStore.data.map { it.searchBar.searchBarStyle }.asLiveData()
|
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)
|
var failedGestureState by mutableStateOf<FailedGesture?>(null)
|
||||||
fun handleGesture(gesture: Gesture): Boolean {
|
fun handleGesture(context: Context, gesture: Gesture): Boolean {
|
||||||
val action = when (gesture) {
|
val action = when (gesture) {
|
||||||
Gesture.DoubleTap -> gestureSettings?.doubleTap
|
Gesture.DoubleTap -> gestureState.value.doubleTapAction
|
||||||
Gesture.LongPress -> gestureSettings?.longPress
|
Gesture.LongPress -> gestureState.value.longPressAction
|
||||||
Gesture.SwipeDown -> gestureSettings?.swipeDown?.takeIf { baseLayout.value != Settings.LayoutSettings.Layout.PullDown }
|
Gesture.SwipeDown -> gestureState.value.swipeDownAction?.takeIf { baseLayout.value != Settings.LayoutSettings.Layout.PullDown }
|
||||||
Gesture.SwipeLeft -> gestureSettings?.swipeLeft?.takeIf { baseLayout.value != Settings.LayoutSettings.Layout.Pager }
|
Gesture.SwipeLeft -> gestureState.value.swipeLeftAction?.takeIf { baseLayout.value != Settings.LayoutSettings.Layout.Pager }
|
||||||
Gesture.SwipeRight -> gestureSettings?.swipeRight?.takeIf { baseLayout.value != Settings.LayoutSettings.Layout.PagerReversed }
|
Gesture.SwipeRight -> gestureState.value.swipeRightAction?.takeIf { baseLayout.value != Settings.LayoutSettings.Layout.PagerReversed }
|
||||||
}
|
}
|
||||||
val requiresAccessibilityService =
|
val requiresAccessibilityService =
|
||||||
action == GestureAction.OpenRecents
|
action == GestureAction.OpenRecents
|
||||||
@ -129,7 +154,10 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
|
|||||||
|| action == GestureAction.OpenNotificationDrawer
|
|| action == GestureAction.OpenNotificationDrawer
|
||||||
|| action == GestureAction.LockScreen
|
|| action == GestureAction.LockScreen
|
||||||
|
|
||||||
if (action != null && requiresAccessibilityService && !permissionsManager.checkPermissionOnce(PermissionGroup.Accessibility)) {
|
if (action != null && requiresAccessibilityService && !permissionsManager.checkPermissionOnce(
|
||||||
|
PermissionGroup.Accessibility
|
||||||
|
)
|
||||||
|
) {
|
||||||
failedGestureState = FailedGesture(gesture, action)
|
failedGestureState = FailedGesture(gesture, action)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -166,6 +194,17 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
|
|||||||
true
|
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
|
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)
|
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.gestures.LocalGestureDetector
|
||||||
import de.mm20.launcher2.ui.ktx.animateTo
|
import de.mm20.launcher2.ui.ktx.animateTo
|
||||||
import de.mm20.launcher2.ui.ktx.toPixels
|
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.search.SearchVM
|
||||||
import de.mm20.launcher2.ui.launcher.sheets.FailedGestureSheet
|
import de.mm20.launcher2.ui.launcher.sheets.FailedGestureSheet
|
||||||
import de.mm20.launcher2.ui.launcher.sheets.LauncherBottomSheets
|
import de.mm20.launcher2.ui.launcher.sheets.LauncherBottomSheets
|
||||||
@ -97,10 +98,6 @@ abstract class SharedLauncherActivity(
|
|||||||
val bottomSheetManager = LauncherBottomSheetManager()
|
val bottomSheetManager = LauncherBottomSheetManager()
|
||||||
val gestureDetector = GestureDetector()
|
val gestureDetector = GestureDetector()
|
||||||
|
|
||||||
viewModel.shouldDetectDoubleTapGesture.observe(this) {
|
|
||||||
gestureDetector.shouldDetectDoubleTaps = it
|
|
||||||
}
|
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
val wallpaperColors by wallpaperColorsAsState()
|
val wallpaperColors by wallpaperColorsAsState()
|
||||||
@ -270,51 +267,7 @@ abstract class SharedLauncherActivity(
|
|||||||
}
|
}
|
||||||
LauncherBottomSheets()
|
LauncherBottomSheets()
|
||||||
}
|
}
|
||||||
|
LauncherGestureHandler()
|
||||||
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()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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