diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4dc121b..736070b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -66,12 +66,12 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
- android:theme="@style/Theme.AppSplashScreen"
+ android:theme="@style/Theme.App.Start"
tools:targetApi="tiramisu">
+ android:theme="@style/Theme.WireguardAutoTunnel">
@@ -83,11 +83,6 @@
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
-
-
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/DataStoreAppStateRepository.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/DataStoreAppStateRepository.kt
index 66d3516..918a3ca 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/DataStoreAppStateRepository.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/repository/DataStoreAppStateRepository.kt
@@ -45,6 +45,15 @@ class DataStoreAppStateRepository(
dataStoreManager.saveToDataStore(DataStoreManager.CURRENT_SSID, ssid)
}
+ override suspend fun isTunnelStatsExpanded(): Boolean {
+ return dataStoreManager.getFromStore(DataStoreManager.IS_TUNNEL_STATS_EXPANDED)
+ ?: GeneralState.IS_TUNNEL_STATS_EXPANDED
+ }
+
+ override suspend fun setTunnelStatsExpanded(expanded: Boolean) {
+ dataStoreManager.saveToDataStore(DataStoreManager.IS_TUNNEL_STATS_EXPANDED, expanded)
+ }
+
override val generalStateFlow: Flow =
dataStoreManager.preferencesFlow.map { prefs ->
prefs?.let { pref ->
@@ -59,6 +68,7 @@ class DataStoreAppStateRepository(
isPinLockEnabled =
pref[DataStoreManager.IS_PIN_LOCK_ENABLED]
?: GeneralState.PIN_LOCK_ENABLED_DEFAULT,
+ isTunnelStatsExpanded = pref[DataStoreManager.IS_TUNNEL_STATS_EXPANDED] ?: GeneralState.IS_TUNNEL_STATS_EXPANDED,
)
} catch (e: IllegalArgumentException) {
Timber.e(e)
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/NavigationModule.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/NavigationModule.kt
deleted file mode 100644
index 862781e..0000000
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/NavigationModule.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.zaneschepke.wireguardautotunnel.module
-
-import android.content.Context
-import androidx.navigation.NavHostController
-import com.zaneschepke.wireguardautotunnel.ui.common.navigation.NavigationService
-import dagger.Module
-import dagger.Provides
-import dagger.hilt.InstallIn
-import dagger.hilt.android.components.ActivityRetainedComponent
-import dagger.hilt.android.qualifiers.ApplicationContext
-import dagger.hilt.android.scopes.ActivityRetainedScoped
-
-@Module
-@InstallIn(ActivityRetainedComponent::class)
-object NavigationModule {
-
- @Provides
- @ActivityRetainedScoped
- fun provideNestedNavController(@ApplicationContext context: Context): NavHostController {
- return NavigationService(context).navController
- }
-}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/WireGuardNotification.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/WireGuardNotification.kt
index c8c0d88..1409697 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/WireGuardNotification.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/WireGuardNotification.kt
@@ -9,7 +9,7 @@ import android.content.Intent
import android.graphics.Color
import androidx.core.app.NotificationCompat
import com.zaneschepke.wireguardautotunnel.R
-import com.zaneschepke.wireguardautotunnel.ui.SplashActivity
+import com.zaneschepke.wireguardautotunnel.ui.MainActivity
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
@@ -63,7 +63,7 @@ constructor(
}
notificationManager.createNotificationChannel(channel)
val pendingIntent: PendingIntent =
- Intent(context, SplashActivity::class.java).let { notificationIntent ->
+ Intent(context, MainActivity::class.java).let { notificationIntent ->
PendingIntent.getActivity(
context,
0,
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt
index 22e33e6..350a322 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt
@@ -2,30 +2,36 @@ package com.zaneschepke.wireguardautotunnel.ui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import androidx.navigation.NavHostController
+import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
+import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
+import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import xyz.teamgravity.pin_lock_compose.PinManager
import javax.inject.Inject
+import javax.inject.Provider
@HiltViewModel
class AppViewModel
@Inject
constructor(
private val appDataRepository: AppDataRepository,
- tunnelService: TunnelService,
- val navHostController: NavHostController,
+ private val tunnelService: Provider,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
) : ViewModel() {
@@ -35,7 +41,7 @@ constructor(
combine(
appDataRepository.settings.getSettingsFlow(),
appDataRepository.tunnels.getTunnelConfigsFlow(),
- tunnelService.vpnState,
+ tunnelService.get().vpnState,
appDataRepository.appState.generalStateFlow,
) { settings, tunnels, tunnelState, generalState ->
AppUiState(
@@ -44,12 +50,50 @@ constructor(
tunnelState,
generalState,
)
+ }.stateIn(
+ viewModelScope + ioDispatcher,
+ SharingStarted.WhileSubscribed(Constants.SUBSCRIPTION_TIMEOUT),
+ _appUiState.value,
+ )
+
+ private val _isAppReady = MutableStateFlow(false)
+ val isAppReady = _isAppReady.asStateFlow()
+
+ init {
+ viewModelScope.launch {
+ initPin()
+ initAutoTunnel()
+ initTunnel()
+ appReadyCheck()
}
- .stateIn(
- viewModelScope + ioDispatcher,
- SharingStarted.WhileSubscribed(Constants.SUBSCRIPTION_TIMEOUT),
- _appUiState.value,
- )
+ }
+
+ private suspend fun appReadyCheck() {
+ val tunnelCount = appDataRepository.tunnels.count()
+ uiState.takeWhile { it.tunnels.size != tunnelCount }.onCompletion {
+ _isAppReady.emit(true)
+ }.collect()
+ }
+
+ private suspend fun initTunnel() {
+ if (tunnelService.get().getState() == TunnelState.UP) tunnelService.get().startStatsJob()
+ val activeTunnels = appDataRepository.tunnels.getActive()
+ if (activeTunnels.isNotEmpty() &&
+ tunnelService.get().getState() == TunnelState.DOWN
+ ) {
+ tunnelService.get().startTunnel(activeTunnels.first())
+ }
+ }
+
+ private suspend fun initPin() {
+ val isPinEnabled = appDataRepository.appState.isPinLockEnabled()
+ if(isPinEnabled) PinManager.initialize(WireGuardAutoTunnel.instance)
+ }
+
+ private suspend fun initAutoTunnel() {
+ val settings = appDataRepository.settings.getSettings()
+ if (settings.isAutoTunnelEnabled) ServiceManager.startWatcherService(WireGuardAutoTunnel.instance)
+ }
fun setTunnels(tunnels: TunnelConfigs) = viewModelScope.launch(ioDispatcher) {
_appUiState.emit(
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt
index ee92acc..78a5ad6 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt
@@ -4,11 +4,13 @@ import android.os.Bundle
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
+import androidx.activity.viewModels
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.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
@@ -19,8 +21,8 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarData
import androidx.compose.material3.SnackbarHost
-import androidx.compose.material3.Surface
import androidx.compose.material3.surfaceColorAtElevation
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
@@ -31,11 +33,12 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
+import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
@@ -44,6 +47,7 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavItem
+import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.isCurrentRoute
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.CustomSnackBar
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarControllerProvider
@@ -58,6 +62,7 @@ import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
import dagger.hilt.android.AndroidEntryPoint
+import xyz.teamgravity.pin_lock_compose.PinManager
import javax.inject.Inject
@AndroidEntryPoint
@@ -68,11 +73,10 @@ class MainActivity : AppCompatActivity() {
@Inject
lateinit var tunnelService: TunnelService
+ private val viewModel by viewModels()
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
-
- val isPinLockEnabled = intent.extras?.getBoolean(SplashActivity.IS_PIN_LOCK_ENABLED_KEY)
-
enableEdgeToEdge(
navigationBarStyle = SystemBarStyle.auto(
lightScrim = Color.Transparent.toArgb(),
@@ -80,10 +84,15 @@ class MainActivity : AppCompatActivity() {
),
)
+ installSplashScreen().apply {
+ setKeepOnScreenCondition {
+ !viewModel.isAppReady.value
+ }
+ }
+
setContent {
- val appViewModel = hiltViewModel()
- val appUiState by appViewModel.uiState.collectAsStateWithLifecycle(lifecycle = this.lifecycle)
- val navController = appViewModel.navHostController
+ val appUiState by viewModel.uiState.collectAsStateWithLifecycle(lifecycle = this.lifecycle)
+ val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
LaunchedEffect(appUiState.vpnState.status) {
@@ -95,109 +104,105 @@ class MainActivity : AppCompatActivity() {
context.requestTunnelTileServiceStateUpdate()
}
- SnackbarControllerProvider { host ->
- WireguardAutoTunnelTheme {
- val focusRequester = remember { FocusRequester() }
- Scaffold(
- snackbarHost = {
- SnackbarHost(host) { snackbarData: SnackbarData ->
- CustomSnackBar(
- snackbarData.visuals.message,
- isRtl = false,
- containerColor =
- MaterialTheme.colorScheme.surfaceColorAtElevation(
- 2.dp,
- ),
- )
- }
- },
- containerColor = MaterialTheme.colorScheme.background,
- modifier =
- Modifier
- .focusable()
- .focusProperties {
- if (navBackStackEntry?.isCurrentRoute(Route.Lock) == true) {
- Unit
- } else {
- up = focusRequester
+ CompositionLocalProvider(LocalNavController provides navController) {
+ SnackbarControllerProvider { host ->
+ WireguardAutoTunnelTheme {
+ val focusRequester = remember { FocusRequester() }
+ Scaffold(
+ snackbarHost = {
+ SnackbarHost(host) { snackbarData: SnackbarData ->
+ CustomSnackBar(
+ snackbarData.visuals.message,
+ isRtl = false,
+ containerColor =
+ MaterialTheme.colorScheme.surfaceColorAtElevation(
+ 2.dp,
+ ),
+ )
}
},
- bottomBar = {
- BottomNavBar(
- navController,
- listOf(
- BottomNavItem(
- name = stringResource(R.string.tunnels),
- route = Route.Main,
- icon = Icons.Rounded.Home,
+ modifier =
+ Modifier
+ .focusable()
+ .focusProperties {
+ if (navBackStackEntry?.isCurrentRoute(Route.Lock) == true) {
+ Unit
+ } else {
+ up = focusRequester
+ }
+ },
+ bottomBar = {
+ BottomNavBar(
+ navController,
+ listOf(
+ BottomNavItem(
+ name = stringResource(R.string.tunnels),
+ route = Route.Main,
+ icon = Icons.Rounded.Home,
+ ),
+ BottomNavItem(
+ name = stringResource(R.string.settings),
+ route = Route.Settings,
+ icon = Icons.Rounded.Settings,
+ ),
+ BottomNavItem(
+ name = stringResource(R.string.support),
+ route = Route.Support,
+ icon = Icons.Rounded.QuestionMark,
+ ),
),
- BottomNavItem(
- name = stringResource(R.string.settings),
- route = Route.Settings,
- icon = Icons.Rounded.Settings,
- ),
- BottomNavItem(
- name = stringResource(R.string.support),
- route = Route.Support,
- icon = Icons.Rounded.QuestionMark,
- ),
- ),
- )
- },
- ) { padding ->
- 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) Route.Lock else Route.Main),
- ) {
- composable {
- MainScreen(
- focusRequester = focusRequester,
- uiState = appUiState,
- navController = navController,
- )
- }
- composable {
- SettingsScreen(
- appViewModel = appViewModel,
- uiState = appUiState,
- navController = navController,
- focusRequester = focusRequester,
- )
- }
- composable {
- SupportScreen(
- focusRequester = focusRequester,
- navController = navController,
- appUiState = appUiState,
- )
- }
- composable {
- LogsScreen()
- }
- composable {
- val args = it.toRoute()
- ConfigScreen(
- focusRequester = focusRequester,
- tunnelId = args.id,
- )
- }
- composable {
- val args = it.toRoute()
- OptionsScreen(
- navController = navController,
- tunnelId = args.id,
- focusRequester = focusRequester,
- appUiState = appUiState,
- )
- }
- composable {
- PinLockScreen(
- navController = navController,
- appViewModel = appViewModel,
- )
+ )
+ },
+ ) { padding ->
+ Box(modifier = Modifier.fillMaxSize().padding(padding)) {
+ NavHost(
+ navController,
+ enterTransition = { fadeIn(tween(Constants.TRANSITION_ANIMATION_TIME)) },
+ exitTransition = { fadeOut(tween(Constants.TRANSITION_ANIMATION_TIME)) },
+ startDestination = (if (appUiState.generalState.isPinLockEnabled == true) Route.Lock else Route.Main),
+ ) {
+ composable {
+ MainScreen(
+ focusRequester = focusRequester,
+ uiState = appUiState,
+ )
+ }
+ composable {
+ SettingsScreen(
+ appViewModel = viewModel,
+ uiState = appUiState,
+ focusRequester = focusRequester,
+ )
+ }
+ composable {
+ SupportScreen(
+ focusRequester = focusRequester,
+ appUiState = appUiState,
+ )
+ }
+ composable {
+ LogsScreen()
+ }
+ composable {
+ val args = it.toRoute()
+ ConfigScreen(
+ focusRequester = focusRequester,
+ tunnelId = args.id,
+ )
+ }
+ composable {
+ val args = it.toRoute()
+ OptionsScreen(
+ tunnelId = args.id,
+ focusRequester = focusRequester,
+ appUiState = appUiState,
+ )
+ }
+ composable {
+ PinLockScreen(
+ appViewModel = viewModel,
+ )
+ }
}
}
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/SplashActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/SplashActivity.kt
deleted file mode 100644
index f21f106..0000000
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/SplashActivity.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-package com.zaneschepke.wireguardautotunnel.ui
-
-import android.annotation.SuppressLint
-import android.content.Intent
-import android.os.Build
-import android.os.Bundle
-import androidx.activity.ComponentActivity
-import androidx.activity.viewModels
-import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
-import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
-import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
-import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
-import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
-import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
-import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate
-import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.async
-import kotlinx.coroutines.launch
-import xyz.teamgravity.pin_lock_compose.PinManager
-import javax.inject.Inject
-import javax.inject.Provider
-
-@SuppressLint("CustomSplashScreen")
-@AndroidEntryPoint
-class SplashActivity : ComponentActivity() {
- @Inject
- lateinit var appStateRepository: AppStateRepository
-
- @Inject
- lateinit var appDataRepository: AppDataRepository
-
- @Inject
- lateinit var tunnelService: Provider
-
- private val appViewModel: AppViewModel by viewModels()
-
- override fun onCreate(savedInstanceState: Bundle?) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- val splashScreen = installSplashScreen()
- splashScreen.setKeepOnScreenCondition { true }
- }
- super.onCreate(savedInstanceState)
-
- lifecycleScope.launch {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- val pinLockEnabled = async {
- appStateRepository.isPinLockEnabled().also {
- if (it) PinManager.initialize(WireGuardAutoTunnel.instance)
- }
- }.await()
- async {
- val settings = appDataRepository.settings.getSettings()
- if (settings.isAutoTunnelEnabled) ServiceManager.startWatcherService(application.applicationContext)
- if (tunnelService.get().getState() == TunnelState.UP) tunnelService.get().startStatsJob()
- val activeTunnels = appDataRepository.tunnels.getActive()
- if (activeTunnels.isNotEmpty() &&
- tunnelService.get().getState() == TunnelState.DOWN
- ) {
- tunnelService.get().startTunnel(activeTunnels.first())
- }
- }.await()
-
- async {
- val tunnels = appDataRepository.tunnels.getAll()
- appViewModel.setTunnels(tunnels)
- }.await()
-
- requestAutoTunnelTileServiceUpdate()
-
- val intent =
- Intent(this@SplashActivity, MainActivity::class.java).apply {
- putExtra(IS_PIN_LOCK_ENABLED_KEY, pinLockEnabled)
- }
- startActivity(intent)
- finish()
- }
- }
- }
-
- companion object {
- const val IS_PIN_LOCK_ENABLED_KEY = "is_pin_lock_enabled"
- }
-}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/NestedScrollListener.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/NestedScrollListener.kt
new file mode 100644
index 0000000..2050963
--- /dev/null
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/NestedScrollListener.kt
@@ -0,0 +1,13 @@
+package com.zaneschepke.wireguardautotunnel.ui.common
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+
+class NestedScrollListener( val onUp: () -> Unit, val onDown: () -> Unit) : NestedScrollConnection {
+ override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ if (available.y < -1) onDown()
+ if (available.y > 1) onUp()
+ return Offset.Zero
+ }
+}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/RowListItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/RowListItem.kt
index d14c007..3f7832c 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/RowListItem.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/RowListItem.kt
@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -17,9 +18,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
+import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
import com.zaneschepke.wireguardautotunnel.util.extensions.toThreeDecimalPlaceString
@@ -52,16 +54,17 @@ fun RowListItem(
modifier =
Modifier
.fillMaxWidth()
- .padding(horizontal = 15.dp, vertical = 5.dp),
+ .padding(horizontal = 15.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(15.dp),
modifier = Modifier.fillMaxWidth(13 / 20f),
) {
icon()
- Text(text, maxLines = 1, overflow = TextOverflow.Ellipsis)
+ Text(text, maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.labelLarge)
}
rowButton()
}
@@ -71,26 +74,32 @@ fun RowListItem(
modifier =
Modifier
.fillMaxWidth()
- .padding(end = 10.dp, bottom = 10.dp, start = 10.dp),
+ .padding(end = 10.dp, bottom = 10.dp, start = 45.dp),
verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.SpaceEvenly,
+ horizontalArrangement = Arrangement.spacedBy(30.dp, Alignment.Start),
) {
- // TODO change these to string resources
val handshakeEpoch = statistics.peerStats(it)!!.latestHandshakeEpochMillis
- val peerTx = statistics.peerStats(it)!!.txBytes
- val peerRx = statistics.peerStats(it)!!.rxBytes
val peerId = it.toBase64().subSequence(0, 3).toString() + "***"
val handshakeSec =
NumberUtils.getSecondsBetweenTimestampAndNow(handshakeEpoch)
val handshake =
- if (handshakeSec == null) "never" else "$handshakeSec secs ago"
+ if (handshakeSec == null) stringResource(R.string.never) else "$handshakeSec " + stringResource(R.string.sec)
+ val peerTx = statistics.peerStats(it)!!.txBytes
+ val peerRx = statistics.peerStats(it)!!.rxBytes
val peerTxMB = NumberUtils.bytesToMB(peerTx).toThreeDecimalPlaceString()
val peerRxMB = NumberUtils.bytesToMB(peerRx).toThreeDecimalPlaceString()
- val fontSize = 9.sp
- Text("peer: $peerId", fontSize = fontSize)
- Text("handshake: $handshake", fontSize = fontSize)
- Text("tx: $peerTxMB MB", fontSize = fontSize)
- Text("rx: $peerRxMB MB", fontSize = fontSize)
+ Column(
+ verticalArrangement = Arrangement.spacedBy(10.dp),
+ ) {
+ Text(stringResource(R.string.peer).lowercase() + ": $peerId", style = MaterialTheme.typography.bodySmall)
+ Text("tx: $peerTxMB MB", style = MaterialTheme.typography.bodySmall)
+ }
+ Column(
+ verticalArrangement = Arrangement.spacedBy(10.dp),
+ ) {
+ Text(stringResource(R.string.handshake) + ": $handshake", style = MaterialTheme.typography.bodySmall)
+ Text("rx: $peerRxMB MB", style = MaterialTheme.typography.bodySmall)
+ }
}
}
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/config/ConfigurationToggle.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/config/ConfigurationToggle.kt
index 1621061..d5be376 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/config/ConfigurationToggle.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/config/ConfigurationToggle.kt
@@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -32,6 +33,7 @@ fun ConfigurationToggle(
Text(
label,
textAlign = TextAlign.Start,
+ style = MaterialTheme.typography.labelLarge,
modifier =
Modifier
.weight(
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/BottomNavBar.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/BottomNavBar.kt
index 6f4798f..d8bbfaa 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/BottomNavBar.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/BottomNavBar.kt
@@ -10,10 +10,7 @@ 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.text.font.FontWeight
import androidx.navigation.NavController
-import androidx.navigation.NavDestination.Companion.hasRoute
-import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.currentBackStackEntryAsState
@@ -22,11 +19,9 @@ fun BottomNavBar(navController: NavController, bottomNavItems: List
- bottomNavItems.map { dest.hasRoute(route = it.route::class) }.contains(true)
- } == true
- } != null
+ showBottomBar = bottomNavItems.any {
+ navBackStackEntry?.isCurrentRoute(it.route) == true
+ }
if (showBottomBar) {
NavigationBar(
@@ -53,7 +48,7 @@ fun BottomNavBar(navController: NavController, bottomNavItems: List {
+ error("NavController was not provided")
+}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/NavigationService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/NavigationService.kt
deleted file mode 100644
index 685e037..0000000
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/navigation/NavigationService.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.zaneschepke.wireguardautotunnel.ui.common.navigation
-
-import android.content.Context
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.ComposeNavigator
-import androidx.navigation.compose.DialogNavigator
-
-class NavigationService constructor(
- context: Context,
-) {
- val navController = NavHostController(context).apply {
- navigatorProvider.addNavigator(ComposeNavigator())
- navigatorProvider.addNavigator(DialogNavigator())
- }
-}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/text/SectionTitle.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/text/SectionTitle.kt
index 52b641c..548eff8 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/text/SectionTitle.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/text/SectionTitle.kt
@@ -1,22 +1,20 @@
package com.zaneschepke.wireguardautotunnel.ui.common.text
import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
@Composable
fun SectionTitle(title: String, padding: Dp) {
Text(
title,
textAlign = TextAlign.Start,
- style = TextStyle(fontSize = 18.sp, fontWeight = FontWeight.ExtraBold),
+ style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(padding, bottom = 5.dp, top = 5.dp),
)
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt
index e2399b5..6f3755e 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt
@@ -56,8 +56,10 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.zaneschepke.wireguardautotunnel.R
+import com.zaneschepke.wireguardautotunnel.ui.Route
import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationTextBox
import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle
+import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
@@ -78,12 +80,21 @@ fun ConfigScreen(tunnelId: Int, focusRequester: FocusRequester) {
val snackbar = SnackbarController.current
val clipboardManager: ClipboardManager = LocalClipboardManager.current
val keyboardController = LocalSoftwareKeyboardController.current
+ val navController = LocalNavController.current
+
var showApplicationsDialog by remember { mutableStateOf(false) }
var showAuthPrompt by remember { mutableStateOf(false) }
var isAuthenticated by remember { mutableStateOf(false) }
var configType by remember { mutableStateOf(ConfigType.WIREGUARD) }
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
+ val saved by viewModel.saved.collectAsStateWithLifecycle(null)
+
+ LaunchedEffect(saved) {
+ if (saved == true) {
+ navController.navigate(Route.Main)
+ }
+ }
LaunchedEffect(Unit) {
if (!uiState.loading && context.isRunningOnTv()) {
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt
index 40a113f..6f91a6b 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt
@@ -6,7 +6,6 @@ import android.content.pm.PackageManager
import android.os.Build
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import androidx.navigation.NavHostController
import com.wireguard.config.Config
import com.wireguard.config.Interface
import com.wireguard.config.Peer
@@ -17,7 +16,6 @@ import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
-import com.zaneschepke.wireguardautotunnel.ui.Route
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
import com.zaneschepke.wireguardautotunnel.ui.screens.config.model.PeerProxy
import com.zaneschepke.wireguardautotunnel.util.Constants
@@ -30,8 +28,10 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
@@ -44,12 +44,14 @@ class ConfigViewModel
@AssistedInject
constructor(
private val appDataRepository: AppDataRepository,
- private val navController: NavHostController,
@Assisted val id: Int,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
) : ViewModel() {
private val packageManager = WireGuardAutoTunnel.instance.packageManager
+ private val _saved = MutableSharedFlow()
+ val saved = _saved.asSharedFlow()
+
private val _uiState = MutableStateFlow(ConfigUiState())
val uiState = _uiState.onStart {
appDataRepository.tunnels.getById(id)?.let {
@@ -335,7 +337,7 @@ constructor(
SnackbarController.showMessage(
StringValue.StringResource(R.string.config_changes_saved),
)
- navController.navigate(Route.Main)
+ _saved.emit(true)
}.onFailure {
Timber.e(it)
val message = it.message?.substringAfter(":", missingDelimiterValue = "")
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt
index 29998e7..5084478 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt
@@ -11,7 +11,6 @@ import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@@ -46,20 +45,15 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
-import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
-import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.buildAnnotatedString
-import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.navigation.NavController
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions
import com.wireguard.android.backend.GoBackend
@@ -69,16 +63,19 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
import com.zaneschepke.wireguardautotunnel.ui.Route
+import com.zaneschepke.wireguardautotunnel.ui.common.NestedScrollListener
import com.zaneschepke.wireguardautotunnel.ui.common.RowListItem
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberFileImportLauncherForResult
+import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.GettingStartedLabel
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.ScrollDismissFab
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.TunnelImportSheet
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.VpnDeniedDialog
-import com.zaneschepke.wireguardautotunnel.ui.theme.corn
-import com.zaneschepke.wireguardautotunnel.ui.theme.mint
+import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
+import com.zaneschepke.wireguardautotunnel.ui.theme.Corn
+import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.extensions.handshakeStatus
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
@@ -86,49 +83,31 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.mapPeerStats
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
import com.zaneschepke.wireguardautotunnel.util.extensions.startTunnelBackground
import kotlinx.coroutines.delay
-import timber.log.Timber
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@OptIn(ExperimentalFoundationApi::class)
@Composable
-fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState, focusRequester: FocusRequester, navController: NavController) {
+fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState, focusRequester: FocusRequester) {
val haptic = LocalHapticFeedback.current
val context = LocalContext.current
+ val navController = LocalNavController.current
val snackbar = SnackbarController.current
var showBottomSheet by remember { mutableStateOf(false) }
var showVpnPermissionDialog by remember { mutableStateOf(false) }
- val isVisible = rememberSaveable { mutableStateOf(true) }
+ var isFabVisible by rememberSaveable { mutableStateOf(true) }
var showDeleteTunnelAlertDialog by remember { mutableStateOf(false) }
var selectedTunnel by remember { mutableStateOf(null) }
- val nestedScrollConnection =
- remember {
- object : NestedScrollConnection {
- override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
- // Hide FAB
- if (available.y < -1) {
- isVisible.value = false
- }
- // Show FAB
- if (available.y > 1) {
- isVisible.value = true
- }
- return Offset.Zero
- }
- }
- }
+ val nestedScrollConnection = remember {
+ NestedScrollListener({ isFabVisible = false },{ isFabVisible = true })
+ }
val vpnActivityResultState =
rememberLauncherForActivityResult(
ActivityResultContracts.StartActivityForResult(),
onResult = {
- val accepted = (it.resultCode == RESULT_OK)
- if (accepted) {
- Timber.d("VPN permission granted")
- } else {
- showVpnPermissionDialog = true
- }
+ if(it.resultCode != RESULT_OK) showVpnPermissionDialog = true
},
)
@@ -179,16 +158,11 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
}
fun onTunnelToggle(checked: Boolean, tunnel: TunnelConfig) {
- if (checked) {
- if (uiState.settings.isKernelEnabled) {
- context.startTunnelBackground(tunnel.id)
- } else {
- viewModel.onTunnelStart(tunnel)
- }
+ if (!checked) viewModel.onTunnelStop(tunnel).also { return }
+ if (uiState.settings.isKernelEnabled) {
+ context.startTunnelBackground(tunnel.id)
} else {
- viewModel.onTunnelStop(
- tunnel,
- )
+ viewModel.onTunnelStart(tunnel)
}
}
@@ -223,7 +197,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
contentDescription = icon.name,
tint = MaterialTheme.colorScheme.onPrimary,
)
- }, focusRequester, isVisible = isVisible.value, onClick = {
+ }, focusRequester, isVisible = isFabVisible, onClick = {
showBottomSheet = true
})
},
@@ -282,13 +256,12 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
icon.name,
modifier =
Modifier
- .padding(end = 8.5.dp)
- .size(25.dp),
+ .size(iconSize),
tint =
if (uiState.settings.isAutoTunnelPaused) {
Color.Gray
} else {
- mint
+ SilverTree
},
)
},
@@ -330,6 +303,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
it.id == tunnel.id &&
it.isActive
}
+ val expanded = uiState.generalState.isTunnelStatsExpanded
val leadingIconColor =
(
if (
@@ -339,8 +313,8 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
.map { it.value?.handshakeStatus() }
.let { statuses ->
when {
- statuses.all { it == HandshakeStatus.HEALTHY } -> mint
- statuses.any { it == HandshakeStatus.STALE } -> corn
+ statuses.all { it == HandshakeStatus.HEALTHY } -> SilverTree
+ statuses.any { it == HandshakeStatus.STALE } -> Corn
statuses.all { it == HandshakeStatus.NOT_STARTED } ->
Color.Gray
@@ -354,7 +328,6 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
}
)
val itemFocusRequester = remember { FocusRequester() }
- val expanded = remember { mutableStateOf(false) }
RowListItem(
icon = {
val circleIcon = Icons.Rounded.Circle
@@ -370,13 +343,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
icon,
icon.name,
tint = leadingIconColor,
- modifier =
- Modifier
- .padding(
- end = if (icon == circleIcon) 12.5.dp else 10.dp,
- start = if (icon == circleIcon) 2.5.dp else 0.dp,
- )
- .size(if (icon == circleIcon) 15.dp else 20.dp),
+ modifier = Modifier.size(iconSize),
)
},
text = tunnel.name,
@@ -389,7 +356,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
if (
isActive
) {
- expanded.value = !expanded.value
+ viewModel.onExpandedChanged(!expanded)
}
} else {
selectedTunnel = tunnel
@@ -397,7 +364,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
}
},
statistics = uiState.vpnState.statistics,
- expanded = expanded.value,
+ expanded = expanded && isActive,
focusRequester = focusRequester,
rowButton = {
if (
@@ -437,13 +404,11 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
}
}
} else {
- if (!isActive) expanded.value = false
@Composable
fun TunnelSwitch() = Switch(
modifier = Modifier.focusRequester(itemFocusRequester),
checked = isActive,
onCheckedChange = { checked ->
- if (!checked) expanded.value = false
val intent = if (uiState.settings.isKernelEnabled) null else GoBackend.VpnService.prepare(context)
if (intent != null) return@Switch vpnActivityResultState.launch(intent)
onTunnelToggle(checked, tunnel)
@@ -474,7 +439,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
uiState.vpnState.status == TunnelState.UP &&
(uiState.vpnState.tunnelConfig?.name == tunnel.name)
) {
- expanded.value = !expanded.value
+ viewModel.onExpandedChanged(!expanded)
} else {
snackbar.showMessage(
context.getString(R.string.turn_on_tunnel),
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt
index fcab195..e2b4e7b 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt
@@ -63,6 +63,10 @@ constructor(
)
}
+ fun onExpandedChanged(expanded: Boolean) = viewModelScope.launch {
+ appDataRepository.appState.setTunnelStatsExpanded(expanded)
+ }
+
fun onTunnelStart(tunnelConfig: TunnelConfig) = viewModelScope.launch {
Timber.i("Starting tunnel ${tunnelConfig.name}")
tunnelService.startTunnel(tunnelConfig)
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsScreen.kt
index 3e05be4..fc8e595 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsScreen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/options/OptionsScreen.kt
@@ -54,6 +54,7 @@ import com.zaneschepke.wireguardautotunnel.ui.Route
import com.zaneschepke.wireguardautotunnel.ui.common.ClickableIconButton
import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle
import com.zaneschepke.wireguardautotunnel.ui.common.config.SubmitConfigurationTextBox
+import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.ScrollDismissFab
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.WildcardSupportingLabel
@@ -68,13 +69,13 @@ import kotlinx.coroutines.delay
@Composable
fun OptionsScreen(
optionsViewModel: OptionsViewModel = hiltViewModel(),
- navController: NavController,
focusRequester: FocusRequester,
appUiState: AppUiState,
tunnelId: Int,
) {
val scrollState = rememberScrollState()
val context = LocalContext.current
+ val navController = LocalNavController.current
val config = appUiState.tunnels.first { it.id == tunnelId }
val interactionSource = remember { MutableInteractionSource() }
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/pinlock/PinLockScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/pinlock/PinLockScreen.kt
index d4e1d99..d2a1fc2 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/pinlock/PinLockScreen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/pinlock/PinLockScreen.kt
@@ -9,14 +9,16 @@ import androidx.navigation.NavController
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
import com.zaneschepke.wireguardautotunnel.ui.Route
+import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
import com.zaneschepke.wireguardautotunnel.util.StringValue
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
import xyz.teamgravity.pin_lock_compose.PinLock
@Composable
-fun PinLockScreen(navController: NavController, appViewModel: AppViewModel) {
+fun PinLockScreen(appViewModel: AppViewModel) {
val context = LocalContext.current
+ val navController = LocalNavController.current
val snackbar = SnackbarController.current
PinLock(
title = { pinExists ->
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt
index 556621e..fee7f99 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsScreen.kt
@@ -72,6 +72,7 @@ import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
import com.zaneschepke.wireguardautotunnel.ui.Route
import com.zaneschepke.wireguardautotunnel.ui.common.ClickableIconButton
import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle
+import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
@@ -95,12 +96,13 @@ fun SettingsScreen(
viewModel: SettingsViewModel = hiltViewModel(),
appViewModel: AppViewModel,
uiState: AppUiState,
- navController: NavController,
focusRequester: FocusRequester,
) {
val context = LocalContext.current
+ val navController = LocalNavController.current
val focusManager = LocalFocusManager.current
val snackbar = SnackbarController.current
+
val scrollState = rememberScrollState()
val interactionSource = remember { MutableInteractionSource() }
val isRunningOnTv = context.isRunningOnTv()
@@ -545,10 +547,12 @@ fun SettingsScreen(
ConfigurationToggle(
stringResource(R.string.always_on_vpn_support),
enabled = !(
- (uiState.settings.isTunnelOnWifiEnabled ||
- uiState.settings.isTunnelOnEthernetEnabled ||
- uiState.settings.isTunnelOnMobileDataEnabled) &&
- uiState.settings.isAutoTunnelEnabled
+ (
+ uiState.settings.isTunnelOnWifiEnabled ||
+ uiState.settings.isTunnelOnEthernetEnabled ||
+ uiState.settings.isTunnelOnMobileDataEnabled
+ ) &&
+ uiState.settings.isAutoTunnelEnabled
),
checked = uiState.settings.isAlwaysOnVpnEnabled,
padding = screenPadding,
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportScreen.kt
index 3b3377c..dd9bff1 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportScreen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/support/SupportScreen.kt
@@ -47,13 +47,15 @@ import com.zaneschepke.wireguardautotunnel.BuildConfig
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
import com.zaneschepke.wireguardautotunnel.ui.Route
+import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
import com.zaneschepke.wireguardautotunnel.util.extensions.launchSupportEmail
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
@Composable
-fun SupportScreen(navController: NavController, focusRequester: FocusRequester, appUiState: AppUiState) {
+fun SupportScreen(focusRequester: FocusRequester, appUiState: AppUiState) {
val context = LocalContext.current
+ val navController = LocalNavController.current
val fillMaxWidth = .85f
Column(
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Color.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Color.kt
index d5a2e23..708c8b1 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Color.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Color.kt
@@ -2,17 +2,37 @@ package com.zaneschepke.wireguardautotunnel.ui.theme
import androidx.compose.ui.graphics.Color
-val Purple80 = Color(0xFFD0BCFF)
-val PurpleGrey80 = Color(0xFFCCC2DC)
-val Pink80 = Color(0xFF492532)
-val virdigris = Color(0xFF5BC0BE)
+val OffWhite = Color(0xFFE5E1E5)
+val LightGrey = Color(0xFF8D9D9F)
+val Aqua = Color(0xFF76BEBD)
+val SilverTree = Color(0xFF6DB58B)
+val Plantation = Color(0xFF264A49)
+val Shark = Color(0xFF21272A)
+val BalticSea = Color(0xFF1C1B1F)
+val Brick = Color(0xFFCE4257)
+val Corn = Color(0xFFFBEC5D)
-val Purple40 = Color(0xFF6650a4)
-val PurpleGrey40 = Color(0xFF625b71)
-val Pink40 = Color(0xFFFFFFFF)
+sealed class ThemeColors(
+ val background: Color,
+ val surface: Color,
+ val primary: Color,
+ val secondary: Color,
+ val onSurface: Color,
+) {
+ //TODO fix light theme colors
+ data object Light : ThemeColors(
+ background = LightGrey,
+ surface = OffWhite,
+ primary = Plantation,
+ secondary = OffWhite,
+ onSurface = BalticSea,
+ )
-// status colors
-val brickRed = Color(0xFFCE4257)
-val corn = Color(0xFFFBEC5D)
-val pinkRed = Color(0xFFEF476F)
-val mint = Color(0xFF52B788)
+ data object Dark : ThemeColors(
+ background = BalticSea,
+ surface = Shark,
+ primary = Aqua,
+ secondary = Plantation,
+ onSurface = OffWhite,
+ )
+}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Theme.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Theme.kt
index a71aa12..c3446d0 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Theme.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Theme.kt
@@ -18,30 +18,22 @@ import androidx.core.view.WindowCompat
private val DarkColorScheme =
darkColorScheme(
- // primary = Purple80,
- primary = virdigris,
- secondary = PurpleGrey40,
- // secondary = PurpleGrey80,
- tertiary = Pink40,
- surfaceTint = Pink80,
- // tertiary = Pink80
+ primary = ThemeColors.Dark.primary,
+ surface = ThemeColors.Dark.surface,
+ background = ThemeColors.Dark.background,
+ secondaryContainer = ThemeColors.Dark.secondary,
+ onSurface = ThemeColors.Dark.onSurface,
+ onSecondaryContainer = ThemeColors.Dark.primary,
)
private val LightColorScheme =
lightColorScheme(
- primary = Purple40,
- secondary = PurpleGrey40,
- tertiary = Pink40,
- surfaceTint = Pink80,
- /* Other default colors to override
- background = Color(0xFFFFFBFE),
- surface = Color(0xFFFFFBFE),
- onPrimary = Color.White,
- onSecondary = Color.White,
- onTertiary = Color.White,
- onBackground = Color(0xFF1C1B1F),
- onSurface = Color(0xFF1C1B1F),
- */
+ primary = ThemeColors.Light.primary,
+ surface = ThemeColors.Light.surface,
+ background = ThemeColors.Light.background,
+ secondaryContainer = ThemeColors.Light.secondary,
+ onSurface = ThemeColors.Light.onSurface,
+ onSecondaryContainer = ThemeColors.Light.primary,
)
@Composable
@@ -52,15 +44,16 @@ fun WireguardAutoTunnelTheme(
) {
val context = LocalContext.current
val colorScheme = when {
- (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) -> {
- if (useDarkTheme) {
- dynamicDarkColorScheme(context)
- } else {
- dynamicLightColorScheme(context)
- }
- }
+ (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) -> {
+ if (useDarkTheme) {
+ dynamicDarkColorScheme(context)
+ } else {
+ dynamicLightColorScheme(context)
+ }
+ }
useDarkTheme -> DarkColorScheme
- else -> LightColorScheme
+ //TODO force dark theme for now until light theme designed
+ else -> DarkColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Type.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Type.kt
index 0a1a9c9..3a40321 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Type.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Type.kt
@@ -2,35 +2,58 @@ package com.zaneschepke.wireguardautotunnel.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import com.zaneschepke.wireguardautotunnel.R
// Set of Material typography styles to start with
+
+val inter = FontFamily(
+ Font(R.font.inter),
+)
+
val Typography =
Typography(
bodyLarge =
TextStyle(
- fontFamily = FontFamily.Default,
+ fontFamily = inter,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp,
),
- /* Other default text styles to override
- titleLarge = TextStyle(
- fontFamily = FontFamily.Default,
- fontWeight = FontWeight.Normal,
- fontSize = 22.sp,
- lineHeight = 28.sp,
- letterSpacing = 0.sp
- ),
- labelSmall = TextStyle(
- fontFamily = FontFamily.Default,
- fontWeight = FontWeight.Medium,
- fontSize = 11.sp,
- lineHeight = 16.sp,
- letterSpacing = 0.5.sp
- )
- */
+ bodySmall = TextStyle(
+ fontFamily = inter,
+ fontWeight = FontWeight.Normal,
+ fontSize = 13.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 1.sp,
+ color = LightGrey,
+ ),
+ labelLarge = TextStyle(
+ fontFamily = inter,
+ fontWeight = FontWeight.Normal,
+ fontSize = 15.sp,
+ lineHeight = 18.sp,
+ letterSpacing = 0.sp,
+ ),
+ labelMedium = TextStyle(
+ fontFamily = inter,
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 12.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.5.sp,
+ ),
+ titleMedium = TextStyle(
+ fontFamily = inter,
+ fontWeight = FontWeight.Bold,
+ fontSize = 17.sp,
+ lineHeight = 21.sp,
+ letterSpacing = 0.sp,
+ ),
)
+
+val iconSize = 15.dp
diff --git a/app/src/main/res/font/inter.ttf b/app/src/main/res/font/inter.ttf
new file mode 100644
index 0000000..e31b51e
Binary files /dev/null and b/app/src/main/res/font/inter.ttf differ
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0dbd484..7ae0931 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -196,4 +196,7 @@
Learn about supported wildcards.
details
Show Amnezia properties
+ never
+ sec
+ handshake
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 58e567b..90af9b3 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -5,11 +5,9 @@
- @color/black_background
-
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 15ae102..b62e81a 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -16,19 +16,19 @@ junit = "4.13.2"
kotlinx-serialization-json = "1.7.3"
lifecycle-runtime-compose = "2.8.6"
material3 = "1.3.0"
-navigationCompose = "2.8.1"
+navigationCompose = "2.8.2"
pinLockCompose = "1.0.3"
roomVersion = "2.6.1"
timber = "5.0.1"
tunnel = "1.2.1"
-androidGradlePlugin = "8.6.1"
-kotlin = "2.0.20"
+androidGradlePlugin = "8.7.0"
+kotlin = "2.0.21"
ksp = "2.0.20-1.0.25"
-composeBom = "2024.09.02"
-compose = "1.7.2"
+composeBom = "2024.09.03"
+compose = "1.7.3"
zxingAndroidEmbedded = "4.3.0"
coreSplashscreen = "1.0.1"
-gradlePlugins-grgit = "5.2.2"
+gradlePlugins-grgit = "5.3.0"
#plugins
material = "1.12.0"