Add fake splash screen for launching apps by gestures
This commit is contained in:
parent
c664f2e777
commit
608d73706a
@ -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
|
||||
}
|
||||
@ -38,6 +38,7 @@ class GestureDetector {
|
||||
dragStart = null
|
||||
currentDrag = null
|
||||
hasDragEnded = false
|
||||
gestureListener?.onDragEnd()
|
||||
}
|
||||
|
||||
|
||||
@ -51,6 +52,8 @@ class GestureDetector {
|
||||
* The gesture detector will no longer track the drag gesture in this case.
|
||||
*/
|
||||
fun onDrag(offset: Offset): Boolean = false
|
||||
|
||||
fun onDragEnd() {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ fun GestureHandler(
|
||||
onLongPress: (Offset) -> Unit = {},
|
||||
onDoubleTap: (Offset) -> Unit = {},
|
||||
onDrag: (Offset) -> Boolean = { false },
|
||||
onDragEnd: () -> Unit = {},
|
||||
) {
|
||||
DisposableEffect(detector) {
|
||||
detector.gestureListener = object : GestureDetector.OnGestureListener {
|
||||
@ -29,6 +30,10 @@ fun GestureHandler(
|
||||
override fun onDrag(offset: Offset): Boolean {
|
||||
return onDrag(offset)
|
||||
}
|
||||
|
||||
override fun onDragEnd() {
|
||||
onDragEnd()
|
||||
}
|
||||
}
|
||||
onDispose {
|
||||
detector.gestureListener = null
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package de.mm20.launcher2.ui.launcher
|
||||
|
||||
import android.app.ActivityOptions
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.getValue
|
||||
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.Settings
|
||||
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.ui.gestures.Gesture
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@ -57,7 +59,8 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
|
||||
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 reverseSearchResults = dataStore.data.map { it.layout.reverseSearchResults }.asLiveData()
|
||||
val fixedSearchBar = dataStore.data.map { it.layout.fixedSearchBar }.asLiveData()
|
||||
@ -109,18 +112,31 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
|
||||
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
|
||||
.combine(baseLayout) { settings, layout ->
|
||||
val swipeLeftAction =
|
||||
settings?.swipeLeft?.takeIf { layout != Layout.Pager } ?: 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 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,
|
||||
val swipeLeftAppKey =
|
||||
if (swipeLeftAction == GestureAction.LaunchApp) settings.swipeLeftApp else null
|
||||
val swipeRightAppKey =
|
||||
if (swipeRightAction == GestureAction.LaunchApp) settings.swipeRightApp 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
|
||||
val apps = listOfNotNull(
|
||||
swipeLeftAppKey,
|
||||
swipeRightAppKey,
|
||||
swipeDownAppKey,
|
||||
longPressAppKey,
|
||||
doubleTapAppKey
|
||||
).let { favoritesRepository.getFromKeys(it) }
|
||||
|
||||
GestureState(
|
||||
@ -129,11 +145,11 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
|
||||
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 }
|
||||
swipeLeftApp = apps.firstOrNull { it.key == swipeLeftAppKey },
|
||||
swipeRightApp = apps.firstOrNull { it.key == swipeRightAppKey },
|
||||
swipeDownApp = apps.firstOrNull { it.key == swipeDownAppKey },
|
||||
longPressApp = apps.firstOrNull { it.key == longPressAppKey },
|
||||
doubleTapApp = apps.firstOrNull { it.key == doubleTapAppKey }
|
||||
)
|
||||
}.stateIn(viewModelScope, SharingStarted.Eagerly, GestureState())
|
||||
|
||||
@ -143,9 +159,9 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
|
||||
val action = when (gesture) {
|
||||
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 }
|
||||
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
|
||||
@ -195,13 +211,18 @@ class LauncherScaffoldVM : ViewModel(), KoinComponent {
|
||||
}
|
||||
|
||||
GestureAction.LaunchApp -> {
|
||||
val options = ActivityOptions.makeCustomAnimation(
|
||||
context,
|
||||
android.R.anim.fade_in,
|
||||
android.R.anim.fade_out,
|
||||
)
|
||||
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)
|
||||
}?.launch(context, options.toBundle())
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
@ -126,7 +127,7 @@ abstract class SharedLauncherActivity(
|
||||
|
||||
val hideStatus by viewModel.hideStatusBar.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 reverseSearchResults by viewModel.reverseSearchResults.observeAsState(false)
|
||||
val fixedSearchBar by viewModel.fixedSearchBar.observeAsState(false)
|
||||
|
||||
@ -1,23 +1,37 @@
|
||||
package de.mm20.launcher2.ui.launcher.gestures
|
||||
|
||||
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.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
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.LocalView
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
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.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.GestureState
|
||||
import de.mm20.launcher2.ui.launcher.LauncherScaffoldVM
|
||||
import de.mm20.launcher2.ui.launcher.sheets.FailedGestureSheet
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
@Composable
|
||||
@ -27,6 +41,7 @@ fun LauncherGestureHandler() {
|
||||
val gestureDetector = LocalGestureDetector.current
|
||||
|
||||
val viewModel: LauncherScaffoldVM = viewModel()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val gestureState by viewModel.gestureState.collectAsState(GestureState())
|
||||
|
||||
@ -38,8 +53,13 @@ fun LauncherGestureHandler() {
|
||||
|
||||
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(
|
||||
detector = gestureDetector,
|
||||
onDoubleTap = {
|
||||
@ -49,22 +69,98 @@ fun LauncherGestureHandler() {
|
||||
viewModel.handleGesture(context, Gesture.LongPress)
|
||||
},
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 = {
|
||||
wallpaperManager.sendWallpaperCommand(
|
||||
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) {
|
||||
FailedGestureSheet(
|
||||
failedGesture = viewModel.failedGestureState!!,
|
||||
@ -84,4 +217,10 @@ fun LauncherGestureHandler() {
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal enum class SwipeDirection {
|
||||
Left,
|
||||
Right,
|
||||
Down
|
||||
}
|
||||
@ -68,9 +68,6 @@ abstract class SearchableItemVM(
|
||||
ActivityOptionsCompat.makeBasic()
|
||||
}
|
||||
val bundle = options.toBundle()
|
||||
if (isAtLeastApiLevel(31)) {
|
||||
bundle?.putInt("android.activity.splashScreenStyle", 1)
|
||||
}
|
||||
if (searchable.launch(context, bundle)) {
|
||||
favoritesRepository.incrementLaunchCounter(searchable)
|
||||
return true
|
||||
|
||||
@ -120,6 +120,9 @@ data class LauncherApp(
|
||||
|
||||
override fun launch(context: Context, options: Bundle?): Boolean {
|
||||
val launcherApps = context.getSystemService<LauncherApps>()!!
|
||||
if (isAtLeastApiLevel(31)) {
|
||||
options?.putInt("android.activity.splashScreenStyle", 1)
|
||||
}
|
||||
try {
|
||||
launcherApps.startMainActivity(
|
||||
ComponentName(`package`, activity),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user