Compare commits

..

2 Commits

Author SHA1 Message Date
Zane Schepke 3f4673b2a7 fix: improve navigation animation speed
Fixes possible crashes on slow androidTVs

Closes #49
2024-08-17 00:43:57 -04:00
Zane Schepke 528a1f84e4 fix: minor ui changes 2024-08-16 22:13:31 -04:00
14 changed files with 134 additions and 122 deletions
@@ -56,8 +56,17 @@ constructor(
return runCatching {
when (val backend = backend()) {
is Backend -> backend.setState(this, tunnelState.toWgState(), TunnelConfig.configFromWgQuick(tunnelConfig.wgQuick)).let { TunnelState.from(it) }
is org.amnezia.awg.backend.Backend -> backend.setState(this, tunnelState.toAmState(), TunnelConfig.configFromAmQuick(tunnelConfig.amQuick)).let {
TunnelState.from(it)
is org.amnezia.awg.backend.Backend -> {
val config = if (tunnelConfig.amQuick.isBlank()) {
TunnelConfig.configFromAmQuick(
tunnelConfig.wgQuick,
)
} else {
TunnelConfig.configFromAmQuick(tunnelConfig.amQuick)
}
backend.setState(this, tunnelState.toAmState(), config).let {
TunnelState.from(it)
}
}
else -> throw NotImplementedError()
}
@@ -5,6 +5,9 @@ import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
@@ -15,6 +18,7 @@ import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Surface
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -48,6 +52,7 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.settings.SettingsScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
import com.zaneschepke.wireguardautotunnel.ui.screens.support.logs.LogsScreen
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.StringValue
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
@@ -127,7 +132,7 @@ class MainActivity : AppCompatActivity() {
)
}
},
// TODO refactor
containerColor = MaterialTheme.colorScheme.background,
modifier =
Modifier
.focusable()
@@ -148,88 +153,88 @@ class MainActivity : AppCompatActivity() {
)
},
) { padding ->
NavHost(
navController,
startDestination = (if (isPinLockEnabled == true) Screen.Lock.route else Screen.Main.route),
modifier =
Modifier
.padding(padding)
.fillMaxSize(),
) {
composable(
Screen.Main.route,
Surface(modifier = Modifier.fillMaxSize().padding(padding)) {
NavHost(
navController,
enterTransition = { fadeIn(tween(Constants.TRANSITION_ANIMATION_TIME)) },
exitTransition = { fadeOut(tween(Constants.TRANSITION_ANIMATION_TIME)) },
startDestination = (if (isPinLockEnabled == true) Screen.Lock.route else Screen.Main.route),
) {
MainScreen(
focusRequester = focusRequester,
appViewModel = appViewModel,
navController = navController,
)
}
composable(
Screen.Settings.route,
) {
SettingsScreen(
appViewModel = appViewModel,
navController = navController,
focusRequester = focusRequester,
)
}
composable(
Screen.Support.route,
) {
SupportScreen(
focusRequester = focusRequester,
navController = navController,
)
}
composable(Screen.Support.Logs.route) {
LogsScreen()
}
composable(
"${Screen.Config.route}/{id}?configType={configType}",
arguments =
listOf(
navArgument("id") {
type = NavType.StringType
defaultValue = "0"
},
navArgument("configType") {
type = NavType.StringType
defaultValue = ConfigType.WIREGUARD.name
},
),
) {
val id = it.arguments?.getString("id")
val configType =
ConfigType.valueOf(
it.arguments?.getString("configType") ?: ConfigType.WIREGUARD.name,
)
if (!id.isNullOrBlank()) {
ConfigScreen(
navController = navController,
tunnelId = id,
appViewModel = appViewModel,
composable(
Screen.Main.route,
) {
MainScreen(
focusRequester = focusRequester,
configType = configType,
appViewModel = appViewModel,
navController = navController,
)
}
}
composable("${Screen.Option.route}/{id}") {
val id = it.arguments?.getString("id")
if (!id.isNullOrBlank()) {
OptionsScreen(
navController = navController,
tunnelId = id,
composable(
Screen.Settings.route,
) {
SettingsScreen(
appViewModel = appViewModel,
navController = navController,
focusRequester = focusRequester,
)
}
}
composable(Screen.Lock.route) {
PinLockScreen(
navController = navController,
appViewModel = appViewModel,
)
composable(
Screen.Support.route,
) {
SupportScreen(
focusRequester = focusRequester,
navController = navController,
)
}
composable(Screen.Support.Logs.route) {
LogsScreen()
}
composable(
"${Screen.Config.route}/{id}?configType={configType}",
arguments =
listOf(
navArgument("id") {
type = NavType.StringType
defaultValue = "0"
},
navArgument("configType") {
type = NavType.StringType
defaultValue = ConfigType.WIREGUARD.name
},
),
) {
val id = it.arguments?.getString("id")
val configType =
ConfigType.valueOf(
it.arguments?.getString("configType") ?: ConfigType.WIREGUARD.name,
)
if (!id.isNullOrBlank()) {
ConfigScreen(
navController = navController,
tunnelId = id,
appViewModel = appViewModel,
focusRequester = focusRequester,
configType = configType,
)
}
}
composable("${Screen.Option.route}/{id}") {
val id = it.arguments?.getString("id")
if (!id.isNullOrBlank()) {
OptionsScreen(
navController = navController,
tunnelId = id,
appViewModel = appViewModel,
focusRequester = focusRequester,
)
}
}
composable(Screen.Lock.route) {
PinLockScreen(
navController = navController,
appViewModel = appViewModel,
)
}
}
}
}
@@ -10,37 +10,29 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.navigation.NavController
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.currentBackStackEntryAsState
import com.zaneschepke.wireguardautotunnel.ui.Screen
@Composable
fun BottomNavBar(navController: NavController, bottomNavItems: List<BottomNavItem>) {
val backStackEntry = navController.currentBackStackEntryAsState()
var showBottomBar by rememberSaveable { mutableStateOf(true) }
val navBackStackEntry by navController.currentBackStackEntryAsState()
// TODO find a better way to hide nav bar
showBottomBar =
when (navBackStackEntry?.destination?.route) {
Screen.Lock.route -> false
else -> true
}
showBottomBar = bottomNavItems.firstOrNull { navBackStackEntry?.destination?.route?.contains(it.route) == true } != null
NavigationBar(
containerColor = if (!showBottomBar) Color.Transparent else MaterialTheme.colorScheme.background,
) {
if (showBottomBar) {
if(showBottomBar) {
NavigationBar(
containerColor = MaterialTheme.colorScheme.surface,
) {
bottomNavItems.forEach { item ->
val selected = item.route == backStackEntry.value?.destination?.route
val selected = navBackStackEntry?.destination?.route?.contains(item.route) == true
NavigationBarItem(
selected = selected,
onClick = {
if(navBackStackEntry?.destination?.route == item.route) return@NavigationBarItem
navController.navigate(item.route) {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
@@ -110,7 +110,12 @@ fun ConfigScreen(
LaunchedEffect(uiState.loading) {
if (!uiState.loading && context.isRunningOnTv()) {
delay(Constants.FOCUS_REQUEST_DELAY)
focusRequester.requestFocus()
kotlin.runCatching {
focusRequester.requestFocus()
}.onFailure {
delay(Constants.FOCUS_REQUEST_DELAY)
focusRequester.requestFocus()
}
}
}
@@ -296,7 +296,7 @@ constructor(
val wgQuick = buildConfig().toWgQuickString(true)
val amQuick =
if (configType == ConfigType.AMNEZIA) {
buildAmConfig().toAwgQuickString()
buildAmConfig().toAwgQuickString(true)
} else {
TunnelConfig.AM_QUICK_DEFAULT
}
@@ -4,9 +4,6 @@ import android.annotation.SuppressLint
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.focusable
import androidx.compose.foundation.gestures.ScrollableDefaults
@@ -147,7 +144,12 @@ fun MainScreen(
LaunchedEffect(Unit) {
if (context.isRunningOnTv()) {
delay(Constants.FOCUS_REQUEST_DELAY)
focusRequester.requestFocus()
kotlin.runCatching {
focusRequester.requestFocus()
}.onFailure {
delay(Constants.FOCUS_REQUEST_DELAY)
focusRequester.requestFocus()
}
}
}
@@ -265,12 +267,8 @@ fun MainScreen(
reverseLayout = false,
flingBehavior = ScrollableDefaults.flingBehavior(),
) {
item {
AnimatedVisibility(
uiState.tunnels.isEmpty(),
exit = fadeOut(),
enter = fadeIn(),
) {
if (uiState.tunnels.isEmpty()) {
item {
GettingStartedLabel(onClick = { context.openWebUrl(it) })
}
}
@@ -179,7 +179,7 @@ constructor(
when (type) {
ConfigType.AMNEZIA -> {
val config = org.amnezia.awg.config.Config.parse(it)
amQuick = config.toAwgQuickString()
amQuick = config.toAwgQuickString(true)
config.toWgQuickString()
}
@@ -252,7 +252,7 @@ constructor(
org.amnezia.awg.config.Config.parse(
zip,
)
amQuick = config.toAwgQuickString()
amQuick = config.toAwgQuickString(true)
config.toWgQuickString()
}
@@ -88,7 +88,12 @@ fun OptionsScreen(
optionsViewModel.init(tunnelId)
if (context.isRunningOnTv()) {
delay(Constants.FOCUS_REQUEST_DELAY)
focusRequester.requestFocus()
kotlin.runCatching {
focusRequester.requestFocus()
}.onFailure {
delay(Constants.FOCUS_REQUEST_DELAY)
focusRequester.requestFocus()
}
}
}
@@ -269,7 +269,7 @@ fun SettingsScreen(
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
checkFineLocationGranted()
}
if(!uiState.isLocationDisclosureShown) {
if (!uiState.isLocationDisclosureShown) {
BackgroundLocationDisclosure(
onDismiss = { viewModel.setLocationDisclosureShown() },
onAttest = {
@@ -282,7 +282,6 @@ fun SettingsScreen(
return
}
BackgroundLocationDialog(
showLocationDialog,
onDismiss = { showLocationDialog = false },
@@ -630,7 +629,7 @@ fun SettingsScreen(
Modifier
.fillMaxWidth(fillMaxWidth)
.padding(vertical = 10.dp)
.padding(bottom = 140.dp),
.padding(bottom = 10.dp),
) {
Column(
horizontalAlignment = Alignment.Start,
@@ -28,12 +28,7 @@ import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
@Composable
fun BackgroundLocationDisclosure(
onDismiss: () -> Unit,
onAttest: () -> Unit,
scrollState: ScrollState,
focusRequester: FocusRequester,
) {
fun BackgroundLocationDisclosure(onDismiss: () -> Unit, onAttest: () -> Unit, scrollState: ScrollState, focusRequester: FocusRequester) {
val context = LocalContext.current
Column(
horizontalAlignment = Alignment.CenterHorizontally,
@@ -20,9 +20,10 @@ private val DarkColorScheme =
darkColorScheme(
// primary = Purple80,
primary = virdigris,
secondary = virdigris,
secondary = PurpleGrey40,
// secondary = PurpleGrey80,
tertiary = virdigris,
tertiary = Pink40,
surfaceTint = Pink80,
// tertiary = Pink80
)
@@ -31,6 +32,7 @@ private val LightColorScheme =
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40,
surfaceTint = Pink80,
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
@@ -24,6 +24,8 @@ object Constants {
const val SUBSCRIPTION_TIMEOUT = 5_000L
const val FOCUS_REQUEST_DELAY = 500L
const val TRANSITION_ANIMATION_TIME = 200
const val DEFAULT_PING_IP = "1.1.1.1"
const val PING_TIMEOUT = 5_000L
const val VPN_RESTART_DELAY = 1_000L
@@ -29,7 +29,7 @@ fun TunnelStatistics.PeerStats.handshakeStatus(): HandshakeStatus {
}
fun Config.toWgQuickString(): String {
val amQuick = toAwgQuickString()
val amQuick = toAwgQuickString(true)
val lines = amQuick.lines().toMutableList()
val linesIterator = lines.iterator()
while (linesIterator.hasNext()) {
+3 -3
View File
@@ -1,7 +1,7 @@
[versions]
accompanist = "0.34.0"
activityCompose = "1.9.1"
amneziawgAndroid = "1.2.1"
amneziawgAndroid = "1.2.2"
androidx-junit = "1.2.1"
appcompat = "1.7.0"
biometricKtx = "1.2.0-alpha05"
@@ -16,13 +16,13 @@ junit = "4.13.2"
kotlinx-serialization-json = "1.7.1"
lifecycle-runtime-compose = "2.8.4"
material3 = "1.2.1"
multifabVersion = "1.1.0"
multifabVersion = "1.1.1"
navigationCompose = "2.7.7"
pinLockCompose = "1.0.3"
roomVersion = "2.6.1"
timber = "5.0.1"
tunnel = "1.2.1"
androidGradlePlugin = "8.7.0-alpha06"
androidGradlePlugin = "8.6.0-rc01"
kotlin = "2.0.10"
ksp = "2.0.10-1.0.24"
composeBom = "2024.06.00"