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"