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