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 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)

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.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()
}
)
}
} }
} }
} }

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()
}
)
}
}