Add fake splash screen for launching apps by gestures

This commit is contained in:
MM20 2023-02-26 00:12:56 +01:00
parent c664f2e777
commit 608d73706a
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
8 changed files with 357 additions and 27 deletions

View File

@ -0,0 +1,161 @@
package de.mm20.launcher2.ui.component
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.TypedValue
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import coil.compose.AsyncImage
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.LauncherApp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun FakeSplashScreen(
modifier: Modifier = Modifier,
searchable: SavableSearchable? = null
) {
val splashScreenData = rememberSplashScreenData(searchable)
val animatedBackgroundColor by animateColorAsState(splashScreenData.backgroundColor)
Surface(
modifier = modifier
.fillMaxSize(),
shadowElevation = 4.dp,
color = animatedBackgroundColor,
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
AsyncImage(
modifier = Modifier.size(288.dp),
model = splashScreenData.icon,
contentDescription = null
)
AsyncImage(
modifier = Modifier
.padding(bottom = 60.dp)
.width(200.dp)
.height(80.dp)
.align(Alignment.BottomCenter),
model = splashScreenData.brandingIcon,
contentDescription = null
)
}
}
}
data class SplashScreenData(
val backgroundColor: Color,
val icon: Drawable? = null,
val brandingIcon: Drawable? = null,
val iconBackground: Color? = null,
)
@Composable
fun rememberSplashScreenData(searchable: SavableSearchable?): SplashScreenData {
val context = LocalContext.current
val defaultBackgroundColor = MaterialTheme.colorScheme.background
val state = remember {
mutableStateOf(SplashScreenData(backgroundColor = defaultBackgroundColor))
}
LaunchedEffect(searchable) {
withContext(Dispatchers.IO) {
if (searchable is LauncherApp && isAtLeastApiLevel(31)) {
val activityInfo = searchable.launcherActivityInfo.activityInfo
val themeRes = activityInfo.themeResource
val ctx = context.createPackageContext(
searchable.`package`,
Context.CONTEXT_IGNORE_SECURITY
)
ctx.setTheme(themeRes)
val theme = ctx.theme
val typedValue = TypedValue()
theme.resolveAttribute(
android.R.attr.windowSplashScreenBackground,
typedValue,
true
)
if (!typedValue.isColorType || typedValue.data == 0) {
theme.resolveAttribute(
android.R.attr.windowBackground,
typedValue,
true
)
}
if (!typedValue.isColorType || typedValue.data == 0) {
theme.resolveAttribute(
android.R.attr.colorBackground,
typedValue,
true
)
}
if (!typedValue.isColorType || typedValue.data == 0) {
theme.resolveAttribute(
android.R.attr.background,
typedValue,
true
)
}
val backgroundColor = typedValue.takeIf { it.isColorType && it.data != 0 }?.data
theme.resolveAttribute(
android.R.attr.windowSplashScreenAnimatedIcon,
typedValue,
true
)
val icon = if (typedValue.resourceId != 0) {
ContextCompat.getDrawable(ctx, typedValue.resourceId)
} else {
null
}
theme.resolveAttribute(
android.R.attr.windowSplashScreenBrandingImage,
typedValue,
true
)
val brandingIcon = if (typedValue.resourceId != 0) {
ContextCompat.getDrawable(ctx, typedValue.resourceId)
} else {
null
}
state.value = SplashScreenData(
backgroundColor = backgroundColor?.let { Color(it) } ?: defaultBackgroundColor,
icon = icon,
brandingIcon = brandingIcon
)
}
}
}
return state.value
}

View File

@ -38,6 +38,7 @@ class GestureDetector {
dragStart = null dragStart = null
currentDrag = null currentDrag = null
hasDragEnded = false hasDragEnded = false
gestureListener?.onDragEnd()
} }
@ -51,6 +52,8 @@ class GestureDetector {
* The gesture detector will no longer track the drag gesture in this case. * The gesture detector will no longer track the drag gesture in this case.
*/ */
fun onDrag(offset: Offset): Boolean = false fun onDrag(offset: Offset): Boolean = false
fun onDragEnd() {}
} }
} }

View File

@ -11,6 +11,7 @@ fun GestureHandler(
onLongPress: (Offset) -> Unit = {}, onLongPress: (Offset) -> Unit = {},
onDoubleTap: (Offset) -> Unit = {}, onDoubleTap: (Offset) -> Unit = {},
onDrag: (Offset) -> Boolean = { false }, onDrag: (Offset) -> Boolean = { false },
onDragEnd: () -> Unit = {},
) { ) {
DisposableEffect(detector) { DisposableEffect(detector) {
detector.gestureListener = object : GestureDetector.OnGestureListener { detector.gestureListener = object : GestureDetector.OnGestureListener {
@ -29,6 +30,10 @@ fun GestureHandler(
override fun onDrag(offset: Offset): Boolean { override fun onDrag(offset: Offset): Boolean {
return onDrag(offset) return onDrag(offset)
} }
override fun onDragEnd() {
onDragEnd()
}
} }
onDispose { onDispose {
detector.gestureListener = null detector.gestureListener = null

View File

@ -1,5 +1,6 @@
package de.mm20.launcher2.ui.launcher package de.mm20.launcher2.ui.launcher
import android.app.ActivityOptions
import android.content.Context import android.content.Context
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -15,6 +16,7 @@ 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.GestureAction import de.mm20.launcher2.preferences.Settings.GestureSettings.GestureAction
import de.mm20.launcher2.preferences.Settings.LayoutSettings.Layout
import de.mm20.launcher2.search.SavableSearchable 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
@ -57,7 +59,8 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
isSystemInDarkMode.value = darkMode isSystemInDarkMode.value = darkMode
} }
val baseLayout = dataStore.data.map { it.layout.baseLayout }.asLiveData() val baseLayout = dataStore.data.map { it.layout.baseLayout }
.stateIn(viewModelScope, SharingStarted.Eagerly, null)
val bottomSearchBar = dataStore.data.map { it.layout.bottomSearchBar }.asLiveData() val bottomSearchBar = dataStore.data.map { it.layout.bottomSearchBar }.asLiveData()
val reverseSearchResults = dataStore.data.map { it.layout.reverseSearchResults }.asLiveData() val reverseSearchResults = dataStore.data.map { it.layout.reverseSearchResults }.asLiveData()
val fixedSearchBar = dataStore.data.map { it.layout.fixedSearchBar }.asLiveData() val fixedSearchBar = dataStore.data.map { it.layout.fixedSearchBar }.asLiveData()
@ -109,18 +112,31 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
val gestureState: StateFlow<GestureState> = dataStore val gestureState: StateFlow<GestureState> = dataStore
.data.map { it.gestures } .data.map { it.gestures }
.distinctUntilChanged() .distinctUntilChanged()
.map { settings -> .combine(baseLayout) { settings, layout ->
val swipeLeftAction = settings?.swipeLeft ?: GestureAction.None val swipeLeftAction =
val swipeRightAction = settings?.swipeRight ?: GestureAction.None settings?.swipeLeft?.takeIf { layout != Layout.Pager } ?: GestureAction.None
val swipeDownAction = settings?.swipeDown ?: GestureAction.None val swipeRightAction = settings?.swipeRight?.takeIf { layout != Layout.PagerReversed }
?: GestureAction.None
val swipeDownAction =
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 apps = listOfNotNull( val swipeLeftAppKey =
if (swipeLeftAction == GestureAction.LaunchApp) settings.swipeLeftApp else null, if (swipeLeftAction == GestureAction.LaunchApp) settings.swipeLeftApp else null
if (swipeRightAction == GestureAction.LaunchApp) settings.swipeRightApp else null, val swipeRightAppKey =
if (swipeDownAction == GestureAction.LaunchApp) settings.swipeDownApp else null, if (swipeRightAction == GestureAction.LaunchApp) settings.swipeRightApp else null
if (longPressAction == GestureAction.LaunchApp) settings.longPressApp else null, val swipeDownAppKey =
if (swipeDownAction == GestureAction.LaunchApp) settings.swipeDownApp else null
val longPressAppKey =
if (longPressAction == GestureAction.LaunchApp) settings.longPressApp else null
val doubleTapAppKey =
if (doubleTapAction == GestureAction.LaunchApp) settings.doubleTapApp else null if (doubleTapAction == GestureAction.LaunchApp) settings.doubleTapApp else null
val apps = listOfNotNull(
swipeLeftAppKey,
swipeRightAppKey,
swipeDownAppKey,
longPressAppKey,
doubleTapAppKey
).let { favoritesRepository.getFromKeys(it) } ).let { favoritesRepository.getFromKeys(it) }
GestureState( GestureState(
@ -129,11 +145,11 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
swipeDownAction = swipeDownAction, swipeDownAction = swipeDownAction,
longPressAction = longPressAction, longPressAction = longPressAction,
doubleTapAction = doubleTapAction, doubleTapAction = doubleTapAction,
swipeLeftApp = apps.firstOrNull { it.key == settings?.swipeLeftApp }, swipeLeftApp = apps.firstOrNull { it.key == swipeLeftAppKey },
swipeRightApp = apps.firstOrNull { it.key == settings?.swipeRightApp }, swipeRightApp = apps.firstOrNull { it.key == swipeRightAppKey },
swipeDownApp = apps.firstOrNull { it.key == settings?.swipeDownApp }, swipeDownApp = apps.firstOrNull { it.key == swipeDownAppKey },
longPressApp = apps.firstOrNull { it.key == settings?.longPressApp }, longPressApp = apps.firstOrNull { it.key == longPressAppKey },
doubleTapApp = apps.firstOrNull { it.key == settings?.doubleTapApp } doubleTapApp = apps.firstOrNull { it.key == doubleTapAppKey }
) )
}.stateIn(viewModelScope, SharingStarted.Eagerly, GestureState()) }.stateIn(viewModelScope, SharingStarted.Eagerly, GestureState())
@ -143,9 +159,9 @@ 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 != Settings.LayoutSettings.Layout.PullDown }
Gesture.SwipeLeft -> gestureState.value.swipeLeftAction?.takeIf { baseLayout.value != Settings.LayoutSettings.Layout.Pager } Gesture.SwipeLeft -> gestureState.value.swipeLeftAction.takeIf { baseLayout.value != Settings.LayoutSettings.Layout.Pager }
Gesture.SwipeRight -> gestureState.value.swipeRightAction?.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
@ -195,13 +211,18 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
} }
GestureAction.LaunchApp -> { GestureAction.LaunchApp -> {
val options = ActivityOptions.makeCustomAnimation(
context,
android.R.anim.fade_in,
android.R.anim.fade_out,
)
when (gesture) { when (gesture) {
Gesture.SwipeLeft -> gestureState.value.swipeLeftApp Gesture.SwipeLeft -> gestureState.value.swipeLeftApp
Gesture.SwipeRight -> gestureState.value.swipeRightApp Gesture.SwipeRight -> gestureState.value.swipeRightApp
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
}?.launch(context, null) }?.launch(context, options.toBundle())
true true
} }

View File

@ -18,6 +18,7 @@ import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.key import androidx.compose.runtime.key
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
@ -126,7 +127,7 @@ abstract class SharedLauncherActivity(
val hideStatus by viewModel.hideStatusBar.observeAsState(false) val hideStatus by viewModel.hideStatusBar.observeAsState(false)
val hideNav by viewModel.hideNavBar.observeAsState(false) val hideNav by viewModel.hideNavBar.observeAsState(false)
val layout by viewModel.baseLayout.observeAsState(null) val layout by viewModel.baseLayout.collectAsState(null)
val bottomSearchBar by viewModel.bottomSearchBar.observeAsState(false) val bottomSearchBar by viewModel.bottomSearchBar.observeAsState(false)
val reverseSearchResults by viewModel.reverseSearchResults.observeAsState(false) val reverseSearchResults by viewModel.reverseSearchResults.observeAsState(false)
val fixedSearchBar by viewModel.fixedSearchBar.observeAsState(false) val fixedSearchBar by viewModel.fixedSearchBar.observeAsState(false)

View File

@ -1,23 +1,37 @@
package de.mm20.launcher2.ui.launcher.gestures package de.mm20.launcher2.ui.launcher.gestures
import android.app.WallpaperManager import android.app.WallpaperManager
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
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.component.FakeSplashScreen
import de.mm20.launcher2.ui.gestures.Gesture import de.mm20.launcher2.ui.gestures.Gesture
import de.mm20.launcher2.ui.gestures.GestureHandler 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.toPixels import de.mm20.launcher2.ui.ktx.toPixels
import de.mm20.launcher2.ui.launcher.GestureState import de.mm20.launcher2.ui.launcher.GestureState
import de.mm20.launcher2.ui.launcher.LauncherScaffoldVM import de.mm20.launcher2.ui.launcher.LauncherScaffoldVM
import de.mm20.launcher2.ui.launcher.sheets.FailedGestureSheet import de.mm20.launcher2.ui.launcher.sheets.FailedGestureSheet
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
@Composable @Composable
@ -27,6 +41,7 @@ fun LauncherGestureHandler() {
val gestureDetector = LocalGestureDetector.current val gestureDetector = LocalGestureDetector.current
val viewModel: LauncherScaffoldVM = viewModel() val viewModel: LauncherScaffoldVM = viewModel()
val scope = rememberCoroutineScope()
val gestureState by viewModel.gestureState.collectAsState(GestureState()) val gestureState by viewModel.gestureState.collectAsState(GestureState())
@ -38,8 +53,13 @@ fun LauncherGestureHandler() {
val windowToken = LocalView.current.windowToken val windowToken = LocalView.current.windowToken
var launchingApp by remember { mutableStateOf<SavableSearchable?>(null) }
var swipeGestureProgress = remember { mutableStateOf(0f) }
var swipeGestureDirection by remember { mutableStateOf<SwipeDirection?>(null) }
val swipeThreshold = 150.dp.toPixels() val swipeStartThreshold = 18.dp.toPixels()
val swipeActionThreshold = 150.dp.toPixels()
val swipeLaunchAppThreshold = 220.dp.toPixels()
GestureHandler( GestureHandler(
detector = gestureDetector, detector = gestureDetector,
onDoubleTap = { onDoubleTap = {
@ -49,22 +69,98 @@ fun LauncherGestureHandler() {
viewModel.handleGesture(context, Gesture.LongPress) viewModel.handleGesture(context, Gesture.LongPress)
}, },
onDrag = { onDrag = {
when {
gestureState.swipeRightApp != null && it.x > swipeStartThreshold && (
swipeGestureDirection == SwipeDirection.Right || (swipeGestureDirection == null && it.x.absoluteValue > it.y.absoluteValue * 2f)
) -> {
swipeGestureDirection = SwipeDirection.Right
swipeGestureProgress.value =
((it.x - swipeStartThreshold) / (swipeLaunchAppThreshold - swipeStartThreshold)).coerceIn(
0f,
1f
)
launchingApp = gestureState.swipeRightApp
return@GestureHandler false
}
gestureState.swipeLeftApp != null && it.x < -swipeStartThreshold && (
swipeGestureDirection == SwipeDirection.Left || (swipeGestureDirection == null && it.x.absoluteValue > it.y.absoluteValue * 2f)
) -> {
swipeGestureDirection = SwipeDirection.Left
swipeGestureProgress.value =
((-it.x - swipeStartThreshold) / (swipeLaunchAppThreshold - swipeStartThreshold)).coerceIn(
0f,
1f
)
launchingApp = gestureState.swipeLeftApp
return@GestureHandler false
}
gestureState.swipeDownApp != null && it.y > swipeStartThreshold && (
swipeGestureDirection == SwipeDirection.Down || (swipeGestureDirection == null && it.y.absoluteValue > it.x.absoluteValue * 2f)
) -> {
swipeGestureDirection = SwipeDirection.Down
swipeGestureProgress.value =
((it.y - swipeStartThreshold) / (swipeLaunchAppThreshold - swipeStartThreshold)).coerceIn(
0f,
1f
)
launchingApp = gestureState.swipeDownApp
return@GestureHandler false
}
else -> {
swipeGestureDirection = null
swipeGestureProgress.value = 0f
launchingApp = null
}
}
return@GestureHandler when { return@GestureHandler when {
it.x > swipeThreshold && it.x.absoluteValue > it.y.absoluteValue * 2f -> { it.x > swipeActionThreshold && it.x.absoluteValue > it.y.absoluteValue * 2f -> {
viewModel.handleGesture(context, Gesture.SwipeRight) viewModel.handleGesture(context, Gesture.SwipeRight)
} }
it.x < -swipeThreshold && it.x.absoluteValue > it.y.absoluteValue * 2f -> { it.x < -swipeActionThreshold && it.x.absoluteValue > it.y.absoluteValue * 2f -> {
viewModel.handleGesture(context, Gesture.SwipeLeft) viewModel.handleGesture(context, Gesture.SwipeLeft)
} }
it.y > swipeThreshold && it.y.absoluteValue > it.x.absoluteValue * 2f -> { it.y > swipeActionThreshold && it.y.absoluteValue > it.x.absoluteValue * 2f -> {
viewModel.handleGesture(context, Gesture.SwipeDown) viewModel.handleGesture(context, Gesture.SwipeDown)
} }
else -> false else -> false
} }
}, },
onDragEnd = {
if (swipeGestureProgress.value > 0f) {
scope.launch {
val direction = swipeGestureDirection
if ((swipeGestureProgress.value * swipeLaunchAppThreshold) + swipeStartThreshold >= swipeActionThreshold
&& direction != null
) {
swipeGestureProgress.animateTo(1f)
viewModel.handleGesture(
context,
when (direction) {
SwipeDirection.Right -> Gesture.SwipeRight
SwipeDirection.Left -> Gesture.SwipeLeft
SwipeDirection.Down -> Gesture.SwipeDown
},
)
delay(500)
} else {
swipeGestureProgress.animateTo(0f)
}
swipeGestureDirection = null
swipeGestureProgress.value = 0f
launchingApp = null
}
}
},
onTap = { onTap = {
wallpaperManager.sendWallpaperCommand( wallpaperManager.sendWallpaperCommand(
windowToken, windowToken,
@ -76,6 +172,43 @@ fun LauncherGestureHandler() {
) )
} }
) )
if (launchingApp != null) {
BoxWithConstraints(
modifier = Modifier.fillMaxSize()
) {
FakeSplashScreen(
modifier = Modifier
.offset {
val p = swipeGestureProgress.value
when (swipeGestureDirection) {
SwipeDirection.Right -> IntOffset(
(-swipeActionThreshold * (1f - p)).toInt(),
0
)
SwipeDirection.Left -> IntOffset(
(swipeActionThreshold * (1f - p)).toInt(),
0
)
SwipeDirection.Down -> IntOffset(
0,
(-swipeActionThreshold * (1f - p)).toInt()
)
else -> IntOffset.Zero
}
}
.graphicsLayer {
alpha = (2f * swipeGestureProgress.value).coerceAtMost(1f)
},
searchable = launchingApp,
)
}
}
if (viewModel.failedGestureState != null) { if (viewModel.failedGestureState != null) {
FailedGestureSheet( FailedGestureSheet(
failedGesture = viewModel.failedGestureState!!, failedGesture = viewModel.failedGestureState!!,
@ -85,3 +218,9 @@ fun LauncherGestureHandler() {
) )
} }
} }
internal enum class SwipeDirection {
Left,
Right,
Down
}

View File

@ -68,9 +68,6 @@ abstract class SearchableItemVM(
ActivityOptionsCompat.makeBasic() ActivityOptionsCompat.makeBasic()
} }
val bundle = options.toBundle() val bundle = options.toBundle()
if (isAtLeastApiLevel(31)) {
bundle?.putInt("android.activity.splashScreenStyle", 1)
}
if (searchable.launch(context, bundle)) { if (searchable.launch(context, bundle)) {
favoritesRepository.incrementLaunchCounter(searchable) favoritesRepository.incrementLaunchCounter(searchable)
return true return true

View File

@ -120,6 +120,9 @@ data class LauncherApp(
override fun launch(context: Context, options: Bundle?): Boolean { override fun launch(context: Context, options: Bundle?): Boolean {
val launcherApps = context.getSystemService<LauncherApps>()!! val launcherApps = context.getSystemService<LauncherApps>()!!
if (isAtLeastApiLevel(31)) {
options?.putInt("android.activity.splashScreenStyle", 1)
}
try { try {
launcherApps.startMainActivity( launcherApps.startMainActivity(
ComponentName(`package`, activity), ComponentName(`package`, activity),