android: predictive back scale for settings pages and remediation detail
This commit is contained in:
1 parent
2681233449
commit
dc6e1e32fb
3 files changed
+77
-3
No files matched your search
+4
-1
@@ -61,6 +61,7 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import org.strongswan.android.R
|
import org.strongswan.android.R
|
||||||
|
import org.strongswan.android.ui.compose.components.PredictiveBackScale
|
||||||
import org.strongswan.android.logic.imc.RemediationInstruction
|
import org.strongswan.android.logic.imc.RemediationInstruction
|
||||||
import org.strongswan.android.ui.compose.components.ShxCard
|
import org.strongswan.android.ui.compose.components.ShxCard
|
||||||
import org.strongswan.android.ui.compose.components.ShxEmptyState
|
import org.strongswan.android.ui.compose.components.ShxEmptyState
|
||||||
@@ -127,7 +128,8 @@ private fun RemediationUi(
|
|||||||
onFinish: () -> Unit,
|
onFinish: () -> Unit,
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
BackHandler(enabled = selected != null) { onSelect(null) }
|
PredictiveBackScale(enabled = selected != null, onBack = { onSelect(null) }, modifier = Modifier.fillMaxSize())
|
||||||
|
{
|
||||||
Column(Modifier.fillMaxSize())
|
Column(Modifier.fillMaxSize())
|
||||||
{
|
{
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
@@ -246,4 +248,5 @@ private fun RemediationUi(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+8
-2
@@ -96,6 +96,7 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.widthIn
|
import androidx.compose.foundation.layout.widthIn
|
||||||
|
import org.strongswan.android.ui.compose.components.PredictiveBackScale
|
||||||
import org.strongswan.android.R
|
import org.strongswan.android.R
|
||||||
import org.strongswan.android.data.VpnProfile
|
import org.strongswan.android.data.VpnProfile
|
||||||
import org.strongswan.android.logic.StrongSwanApplication
|
import org.strongswan.android.logic.StrongSwanApplication
|
||||||
@@ -178,14 +179,18 @@ fun SettingsScreen(profiles: List<VpnProfile>, showTitle: Boolean = true)
|
|||||||
|
|
||||||
var pageState by rememberSaveable { mutableStateOf(SettingsPage.ROOT) }
|
var pageState by rememberSaveable { mutableStateOf(SettingsPage.ROOT) }
|
||||||
/* from a sub page "back" returns to the settings list, not out of the tab */
|
/* from a sub page "back" returns to the settings list, not out of the tab */
|
||||||
BackHandler(enabled = pageState != SettingsPage.ROOT) { pageState = SettingsPage.ROOT }
|
|
||||||
val effects = MaterialTheme.motionScheme.defaultEffectsSpec<Float>()
|
val effects = MaterialTheme.motionScheme.defaultEffectsSpec<Float>()
|
||||||
val spatial = MaterialTheme.motionScheme.defaultSpatialSpec<androidx.compose.ui.unit.IntOffset>()
|
val spatial = MaterialTheme.motionScheme.defaultSpatialSpec<androidx.compose.ui.unit.IntOffset>()
|
||||||
|
|
||||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.TopCenter)
|
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.TopCenter)
|
||||||
{
|
{
|
||||||
|
PredictiveBackScale(
|
||||||
|
enabled = pageState != SettingsPage.ROOT,
|
||||||
|
onBack = { pageState = SettingsPage.ROOT },
|
||||||
|
modifier = Modifier.widthIn(max = 640.dp).fillMaxSize(),
|
||||||
|
)
|
||||||
|
{
|
||||||
AnimatedContent(
|
AnimatedContent(
|
||||||
modifier = Modifier.widthIn(max = 640.dp),
|
|
||||||
targetState = pageState,
|
targetState = pageState,
|
||||||
transitionSpec = {
|
transitionSpec = {
|
||||||
val forward = targetState != SettingsPage.ROOT
|
val forward = targetState != SettingsPage.ROOT
|
||||||
@@ -533,6 +538,7 @@ fun SettingsScreen(profiles: List<VpnProfile>, showTitle: Boolean = true)
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
ShxSnackbarHost(
|
ShxSnackbarHost(
|
||||||
hostState = snackbarHostState,
|
hostState = snackbarHostState,
|
||||||
modifier = Modifier.align(Alignment.BottomCenter),
|
modifier = Modifier.align(Alignment.BottomCenter),
|
||||||
|
|||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2026 SHX VPN
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.strongswan.android.ui.compose.components
|
||||||
|
|
||||||
|
import androidx.activity.compose.PredictiveBackHandler
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the system back gesture for in-screen navigation (a sub page that returns
|
||||||
|
* to its parent): while the user drags, the content shrinks slightly and rounds its
|
||||||
|
* corners, then [onBack] runs when the gesture is committed.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun PredictiveBackScale(
|
||||||
|
enabled: Boolean,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var progress by remember { mutableFloatStateOf(0f) }
|
||||||
|
PredictiveBackHandler(enabled = enabled) { events ->
|
||||||
|
try
|
||||||
|
{
|
||||||
|
events.collect { progress = it.progress }
|
||||||
|
onBack()
|
||||||
|
progress = 0f
|
||||||
|
}
|
||||||
|
catch (e: CancellationException)
|
||||||
|
{
|
||||||
|
progress = 0f
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier
|
||||||
|
.graphicsLayer {
|
||||||
|
val scale = 1f - 0.08f * progress
|
||||||
|
scaleX = scale
|
||||||
|
scaleY = scale
|
||||||
|
}
|
||||||
|
.clip(RoundedCornerShape((progress * 28f).dp)),
|
||||||
|
)
|
||||||
|
{
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user