diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/GeneralState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/GeneralState.kt index 6ab2566..1564fb9 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/GeneralState.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/data/domain/GeneralState.kt @@ -4,7 +4,6 @@ data class GeneralState( val isLocationDisclosureShown: Boolean = LOCATION_DISCLOSURE_SHOWN_DEFAULT, val isBatteryOptimizationDisableShown: Boolean = BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT, val isPinLockEnabled: Boolean = PIN_LOCK_ENABLED_DEFAULT, - val lastActiveTunnelId: Int? = null, ) { companion object { const val LOCATION_DISCLOSURE_SHOWN_DEFAULT = false 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 3374969..66d3516 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 @@ -2,57 +2,47 @@ package com.zaneschepke.wireguardautotunnel.data.repository import com.zaneschepke.wireguardautotunnel.data.datastore.DataStoreManager import com.zaneschepke.wireguardautotunnel.data.domain.GeneralState -import com.zaneschepke.wireguardautotunnel.module.IoDispatcher -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.withContext import timber.log.Timber class DataStoreAppStateRepository( private val dataStoreManager: DataStoreManager, - @IoDispatcher private val ioDispatcher: CoroutineDispatcher, ) : AppStateRepository { override suspend fun isLocationDisclosureShown(): Boolean { - return withContext(ioDispatcher) { - dataStoreManager.getFromStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN) - ?: GeneralState.LOCATION_DISCLOSURE_SHOWN_DEFAULT - } + return dataStoreManager.getFromStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN) + ?: GeneralState.LOCATION_DISCLOSURE_SHOWN_DEFAULT } override suspend fun setLocationDisclosureShown(shown: Boolean) { - withContext(ioDispatcher) { dataStoreManager.saveToDataStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN, shown) } + dataStoreManager.saveToDataStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN, shown) } override suspend fun isPinLockEnabled(): Boolean { - return withContext(ioDispatcher) { - dataStoreManager.getFromStore(DataStoreManager.IS_PIN_LOCK_ENABLED) - ?: GeneralState.PIN_LOCK_ENABLED_DEFAULT - } + return dataStoreManager.getFromStore(DataStoreManager.IS_PIN_LOCK_ENABLED) + ?: GeneralState.PIN_LOCK_ENABLED_DEFAULT } override suspend fun setPinLockEnabled(enabled: Boolean) { - withContext(ioDispatcher) { dataStoreManager.saveToDataStore(DataStoreManager.IS_PIN_LOCK_ENABLED, enabled) } + dataStoreManager.saveToDataStore(DataStoreManager.IS_PIN_LOCK_ENABLED, enabled) } override suspend fun isBatteryOptimizationDisableShown(): Boolean { - return withContext(ioDispatcher) { - dataStoreManager.getFromStore(DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN) - ?: GeneralState.BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT - } + return dataStoreManager.getFromStore(DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN) + ?: GeneralState.BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT } override suspend fun setBatteryOptimizationDisableShown(shown: Boolean) { - withContext(ioDispatcher) { dataStoreManager.saveToDataStore(DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN, shown) } + dataStoreManager.saveToDataStore(DataStoreManager.BATTERY_OPTIMIZE_DISABLE_SHOWN, shown) } override suspend fun getCurrentSsid(): String? { - return withContext(ioDispatcher) { dataStoreManager.getFromStore(DataStoreManager.CURRENT_SSID) } + return dataStoreManager.getFromStore(DataStoreManager.CURRENT_SSID) } override suspend fun setCurrentSsid(ssid: String) { - withContext(ioDispatcher) { dataStoreManager.saveToDataStore(DataStoreManager.CURRENT_SSID, ssid) } + dataStoreManager.saveToDataStore(DataStoreManager.CURRENT_SSID, ssid) } override val generalStateFlow: Flow = diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/RepositoryModule.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/RepositoryModule.kt index 23450d7..7e78d9e 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/RepositoryModule.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/RepositoryModule.kt @@ -72,8 +72,8 @@ class RepositoryModule { @Provides @Singleton - fun provideGeneralStateRepository(dataStoreManager: DataStoreManager, @IoDispatcher ioDispatcher: CoroutineDispatcher): AppStateRepository { - return DataStoreAppStateRepository(dataStoreManager, ioDispatcher) + fun provideGeneralStateRepository(dataStoreManager: DataStoreManager): AppStateRepository { + return DataStoreAppStateRepository(dataStoreManager) } @Provides diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/TunnelModule.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/TunnelModule.kt index 7ed1465..b14c118 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/TunnelModule.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/TunnelModule.kt @@ -8,6 +8,7 @@ import com.wireguard.android.backend.WgQuickBackend import com.wireguard.android.util.RootShell import com.wireguard.android.util.ToolsInstaller import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository +import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService import com.zaneschepke.wireguardautotunnel.service.tunnel.WireGuardTunnel import dagger.Module @@ -61,11 +62,13 @@ class TunnelModule { amneziaBackend: Provider, @Kernel kernelBackend: Provider, appDataRepository: AppDataRepository, + tunnelConfigRepository: TunnelConfigRepository, @ApplicationScope applicationScope: CoroutineScope, @IoDispatcher ioDispatcher: CoroutineDispatcher, ): TunnelService { return WireGuardTunnel( amneziaBackend, + tunnelConfigRepository, kernelBackend, appDataRepository, applicationScope, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/AutoTunnelService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/AutoTunnelService.kt index a863ef4..2d5926a 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/AutoTunnelService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/AutoTunnelService.kt @@ -33,6 +33,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -71,7 +72,7 @@ class AutoTunnelService : LifecycleService() { @MainImmediateDispatcher lateinit var mainImmediateDispatcher: CoroutineDispatcher - private val networkEventsFlow = MutableStateFlow(AutoTunnelState()) + private val autoTunnelStateFlow = MutableStateFlow(AutoTunnelState()) private var wakeLock: PowerManager.WakeLock? = null @@ -132,6 +133,7 @@ class AutoTunnelService : LifecycleService() { initWakeLock() } startSettingsJob() + startVpnStateJob() }.onFailure { Timber.e(it) } @@ -191,6 +193,10 @@ class AutoTunnelService : LifecycleService() { watchForSettingsChanges() } + private fun startVpnStateJob() = lifecycleScope.launch { + watchForVpnStateChanges() + } + private fun startWifiJob() = lifecycleScope.launch { watchForWifiConnectivityChanges() } @@ -218,7 +224,7 @@ class AutoTunnelService : LifecycleService() { when (status) { is NetworkStatus.Available -> { Timber.i("Gained Mobile data connection") - networkEventsFlow.update { + autoTunnelStateFlow.update { it.copy( isMobileDataConnected = true, ) @@ -226,7 +232,7 @@ class AutoTunnelService : LifecycleService() { } is NetworkStatus.CapabilitiesChanged -> { - networkEventsFlow.update { + autoTunnelStateFlow.update { it.copy( isMobileDataConnected = true, ) @@ -235,7 +241,7 @@ class AutoTunnelService : LifecycleService() { } is NetworkStatus.Unavailable -> { - networkEventsFlow.update { + autoTunnelStateFlow.update { it.copy( isMobileDataConnected = false, ) @@ -284,16 +290,8 @@ class AutoTunnelService : LifecycleService() { } } - private fun updateSettings(settings: Settings) { - networkEventsFlow.update { - it.copy( - settings = settings, - ) - } - } - private fun onAutoTunnelPause(paused: Boolean) { - if (networkEventsFlow.value.settings.isAutoTunnelPaused + if (autoTunnelStateFlow.value.settings.isAutoTunnelPaused != paused ) { when (paused) { @@ -307,19 +305,36 @@ class AutoTunnelService : LifecycleService() { Timber.i("Starting settings watcher") withContext(ioDispatcher) { appDataRepository.settings.getSettingsFlow().combine( - appDataRepository.tunnels.getTunnelConfigsFlow(), + // ignore isActive changes to allow manual tunnel overrides + appDataRepository.tunnels.getTunnelConfigsFlow().distinctUntilChanged { old, new -> + old.map { it.isActive } != new.map { it.isActive } + }, ) { settings, tunnels -> - val activeTunnel = tunnels.firstOrNull { it.isActive } - if (!settings.isPingEnabled) { - settings.copy(isPingEnabled = activeTunnel?.isPingEnabled ?: false) - } else { - settings - } + autoTunnelStateFlow.value.copy( + settings = settings, + tunnels = tunnels, + ) }.collect { - Timber.d("Settings change: $it") - onAutoTunnelPause(it.isAutoTunnelPaused) - updateSettings(it) - manageJobsBySettings(it) + onAutoTunnelPause(it.settings.isAutoTunnelPaused) + manageJobsBySettings(it.settings) + autoTunnelStateFlow.emit(it) + } + } + } + + private suspend fun watchForVpnStateChanges() { + Timber.i("Starting vpn state watcher") + withContext(ioDispatcher) { + tunnelService.get().vpnState.collect { state -> + state.tunnelConfig?.let { + val settings = appDataRepository.settings.getSettings() + if (it.isPingEnabled && !settings.isPingEnabled) { + pingJob.onNotRunning { pingJob = startPingJob() } + } + if (!it.isPingEnabled && !settings.isPingEnabled) { + cancelAndResetPingJob() + } + } } } } @@ -375,7 +390,7 @@ class AutoTunnelService : LifecycleService() { } private fun updateEthernet(connected: Boolean) { - networkEventsFlow.update { + autoTunnelStateFlow.update { it.copy( isEthernetConnected = connected, ) @@ -413,7 +428,7 @@ class AutoTunnelService : LifecycleService() { when (status) { is NetworkStatus.Available -> { Timber.i("Gained Wi-Fi connection") - networkEventsFlow.update { + autoTunnelStateFlow.update { it.copy( isWifiConnected = true, ) @@ -422,7 +437,7 @@ class AutoTunnelService : LifecycleService() { is NetworkStatus.CapabilitiesChanged -> { Timber.i("Wifi capabilities changed") - networkEventsFlow.update { + autoTunnelStateFlow.update { it.copy( isWifiConnected = true, ) @@ -435,7 +450,7 @@ class AutoTunnelService : LifecycleService() { Timber.i("Detected valid SSID") } appDataRepository.appState.setCurrentSsid(name) - networkEventsFlow.update { + autoTunnelStateFlow.update { it.copy( currentNetworkSSID = name, ) @@ -444,7 +459,7 @@ class AutoTunnelService : LifecycleService() { } is NetworkStatus.Unavailable -> { - networkEventsFlow.update { + autoTunnelStateFlow.update { it.copy( isWifiConnected = false, ) @@ -460,10 +475,6 @@ class AutoTunnelService : LifecycleService() { return appDataRepository.tunnels.findByMobileDataTunnel().firstOrNull() } - private suspend fun getSsidTunnel(ssid: String): TunnelConfig? { - return appDataRepository.tunnels.findByTunnelNetworksName(ssid).firstOrNull() - } - private fun isTunnelDown(): Boolean { return tunnelService.get().vpnState.value.status == TunnelState.DOWN } @@ -471,7 +482,7 @@ class AutoTunnelService : LifecycleService() { private suspend fun handleNetworkEventChanges() { withContext(ioDispatcher) { Timber.i("Starting network event watcher") - networkEventsFlow.collectLatest { watcherState -> + autoTunnelStateFlow.collectLatest { watcherState -> val autoTunnel = "Auto-tunnel watcher" if (!watcherState.settings.isAutoTunnelPaused) { // delay for rapid network state changes and then collect latest @@ -517,7 +528,7 @@ class AutoTunnelService : LifecycleService() { Timber.i( "$autoTunnel - tunnel on ssid not associated with current tunnel condition met", ) - getSsidTunnel(watcherState.currentNetworkSSID)?.let { + watcherState.tunnels.firstOrNull { it.tunnelNetworks.isMatchingToWildcardList(watcherState.currentNetworkSSID) }?.let { Timber.i("Found tunnel associated with this SSID, bringing tunnel up: ${it.name}") if (isTunnelDown() || activeTunnel?.id != it.id) { tunnelService.get().startTunnel(it) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/AutoTunnelState.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/AutoTunnelState.kt index c030879..adb8391 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/AutoTunnelState.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/AutoTunnelState.kt @@ -1,6 +1,7 @@ package com.zaneschepke.wireguardautotunnel.service.foreground import com.zaneschepke.wireguardautotunnel.data.domain.Settings +import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList data class AutoTunnelState( @@ -9,6 +10,7 @@ data class AutoTunnelState( val isMobileDataConnected: Boolean = false, val currentNetworkSSID: String = "", val settings: Settings = Settings(), + val tunnels: TunnelConfigs = emptyList(), ) { fun isEthernetConditionMet(): Boolean { return ( diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt index 3aa191c..a511d5d 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt @@ -2,24 +2,24 @@ package com.zaneschepke.wireguardautotunnel.service.tunnel import com.wireguard.android.backend.Backend import com.wireguard.android.backend.Tunnel.State -import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository +import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository import com.zaneschepke.wireguardautotunnel.module.ApplicationScope import com.zaneschepke.wireguardautotunnel.module.IoDispatcher import com.zaneschepke.wireguardautotunnel.module.Kernel import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics -import com.zaneschepke.wireguardautotunnel.util.Constants -import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.amnezia.awg.backend.Tunnel @@ -31,6 +31,7 @@ class WireGuardTunnel @Inject constructor( private val amneziaBackend: Provider, + tunnelConfigRepository: TunnelConfigRepository, @Kernel private val kernelBackend: Provider, private val appDataRepository: AppDataRepository, @ApplicationScope private val applicationScope: CoroutineScope, @@ -38,7 +39,22 @@ constructor( ) : TunnelService { private val _vpnState = MutableStateFlow(VpnState()) - override val vpnState: StateFlow = _vpnState.asStateFlow() + override val vpnState: StateFlow = _vpnState.combine( + tunnelConfigRepository.getTunnelConfigsFlow(), + ) { + vpnState, tunnels -> + vpnState.copy( + tunnelConfig = tunnels.firstOrNull { it.id == vpnState.tunnelConfig?.id }, + ) + }.stateIn(applicationScope, SharingStarted.Lazily, VpnState()) + + private var statsJob: Job? = null + + private suspend fun backend(): Any { + val settings = appDataRepository.settings.getSettings() + if (settings.isKernelEnabled) return kernelBackend.get() + return amneziaBackend.get() + } override suspend fun runningTunnelNames(): Set { return when (val backend = backend()) { @@ -48,8 +64,6 @@ constructor( } } - private var statsJob: Job? = null - private suspend fun setState(tunnelConfig: TunnelConfig, tunnelState: TunnelState): Result { return runCatching { when (val backend = backend()) { @@ -73,33 +87,26 @@ constructor( } } - private suspend fun backend(): Any { - val settings = appDataRepository.settings.getSettings() - if (settings.isKernelEnabled) return kernelBackend.get() - return amneziaBackend.get() - } - override suspend fun startTunnel(tunnelConfig: TunnelConfig): Result { return withContext(ioDispatcher) { - if (_vpnState.value.status == TunnelState.UP) vpnState.value.tunnelConfig?.let { stopTunnel(it) } - emitTunnelConfig(tunnelConfig) + onBeforeStart(tunnelConfig) setState(tunnelConfig, TunnelState.UP).onSuccess { emitTunnelState(it) - appDataRepository.tunnels.save(tunnelConfig.copy(isActive = true)) }.onFailure { Timber.e(it) + onStartFailed() } } } override suspend fun stopTunnel(tunnelConfig: TunnelConfig): Result { return withContext(ioDispatcher) { + onBeforeStop(tunnelConfig) setState(tunnelConfig, TunnelState.DOWN).onSuccess { emitTunnelState(it) - appDataRepository.tunnels.save(tunnelConfig.copy(isActive = false)) - resetBackendStatistics() }.onFailure { Timber.e(it) + onStopFailed() } } } @@ -107,7 +114,7 @@ constructor( // use this when we just want to bounce tunnel and not change tunnelConfig active state override suspend fun bounceTunnel(tunnelConfig: TunnelConfig): Result { toggleTunnel(tunnelConfig) - delay(Constants.VPN_RESTART_DELAY) + delay(VPN_RESTART_DELAY) return toggleTunnel(tunnelConfig) } @@ -122,6 +129,34 @@ constructor( } } + private suspend fun onStopFailed() { + _vpnState.value.tunnelConfig?.let { + appDataRepository.tunnels.save(it.copy(isActive = true)) + } + } + + private suspend fun onStartFailed() { + _vpnState.value.tunnelConfig?.let { + appDataRepository.tunnels.save(it.copy(isActive = false)) + } + cancelStatsJob() + resetBackendStatistics() + } + + private suspend fun onBeforeStart(tunnelConfig: TunnelConfig) { + if (_vpnState.value.status == TunnelState.UP) vpnState.value.tunnelConfig?.let { stopTunnel(it) } + resetBackendStatistics() + appDataRepository.tunnels.save(tunnelConfig.copy(isActive = true)) + emitVpnStateConfig(tunnelConfig) + startStatsJob() + } + + private suspend fun onBeforeStop(tunnelConfig: TunnelConfig) { + cancelStatsJob() + resetBackendStatistics() + appDataRepository.tunnels.save(tunnelConfig.copy(isActive = false)) + } + private fun emitTunnelState(state: TunnelState) { _vpnState.tryEmit( _vpnState.value.copy( @@ -138,7 +173,7 @@ constructor( ) } - private fun emitTunnelConfig(tunnelConfig: TunnelConfig?) { + private fun emitVpnStateConfig(tunnelConfig: TunnelConfig) { _vpnState.tryEmit( _vpnState.value.copy( tunnelConfig = tunnelConfig, @@ -174,21 +209,9 @@ constructor( return _vpnState.value.tunnelConfig?.name ?: "" } - override fun onStateChange(newState: Tunnel.State) { - handleStateChange(TunnelState.from(newState)) - } - - private fun handleStateChange(state: TunnelState) { - emitTunnelState(state) - WireGuardAutoTunnel.instance.requestTunnelTileServiceStateUpdate() - when (state) { - TunnelState.UP -> startStatsJob() - else -> cancelStatsJob() - } - } - private fun startTunnelStatisticsJob() = applicationScope.launch(ioDispatcher) { val backend = backend() + delay(STATS_START_DELAY) while (true) { when (backend) { is Backend -> emitBackendStatistics( @@ -202,11 +225,21 @@ constructor( ) } } - delay(Constants.VPN_STATISTIC_CHECK_INTERVAL) + delay(VPN_STATISTIC_CHECK_INTERVAL) } } + override fun onStateChange(newState: Tunnel.State) { + emitTunnelState(TunnelState.from(newState)) + } + override fun onStateChange(state: State) { - handleStateChange(TunnelState.from(state)) + emitTunnelState(TunnelState.from(state)) + } + + companion object { + const val STATS_START_DELAY = 5_000L + const val VPN_STATISTIC_CHECK_INTERVAL = 1_000L + const val VPN_RESTART_DELAY = 1_000L } } 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 01a908e..66c04f2 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 @@ -145,7 +145,7 @@ fun ConfigScreen(tunnelId: Int, viewModel: ConfigViewModel, focusRequester: Focu ) } - if(showApplicationsDialog) { + if (showApplicationsDialog) { ApplicationSelectionDialog(viewModel, uiState) { showApplicationsDialog = false } 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 047bf5d..8c68bb2 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 @@ -40,7 +40,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -96,7 +95,6 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState, val haptic = LocalHapticFeedback.current val context = LocalContext.current val snackbar = SnackbarController.current - val scope = rememberCoroutineScope() var showBottomSheet by remember { mutableStateOf(false) } var showVpnPermissionDialog by remember { mutableStateOf(false) } @@ -328,20 +326,22 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState, uiState.tunnels, key = { tunnel -> tunnel.id }, ) { tunnel -> - val isActive = uiState.tunnels.any { it.id == tunnel.id && it.isActive } + val isActive = uiState.tunnels.any { + it.id == tunnel.id && + it.isActive + } val leadingIconColor = ( if ( - isActive + isActive && uiState.vpnState.statistics != null ) { - uiState.vpnState.statistics - ?.mapPeerStats() - ?.map { it.value?.handshakeStatus() } + uiState.vpnState.statistics.mapPeerStats() + .map { it.value?.handshakeStatus() } .let { statuses -> when { - statuses?.all { it == HandshakeStatus.HEALTHY } == true -> mint - statuses?.any { it == HandshakeStatus.STALE } == true -> corn - statuses?.all { it == HandshakeStatus.NOT_STARTED } == true -> + statuses.all { it == HandshakeStatus.HEALTHY } -> mint + statuses.any { it == HandshakeStatus.STALE } -> corn + statuses.all { it == HandshakeStatus.NOT_STARTED } -> Color.Gray else -> { @@ -381,15 +381,6 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState, }, text = tunnel.name, onHold = { - if ( - (uiState.vpnState.status == TunnelState.UP) && - (tunnel.name == uiState.vpnState.tunnelConfig?.name) - ) { - snackbar.showMessage( - context.getString(R.string.turn_off_tunnel), - ) - return@RowListItem - } haptic.performHapticFeedback(HapticFeedbackType.LongPress) selectedTunnel = tunnel }, @@ -416,18 +407,9 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState, Row { IconButton( onClick = { - if ( - uiState.settings.isAutoTunnelEnabled && - !uiState.settings.isAutoTunnelPaused - ) { - snackbar.showMessage( - context.getString(R.string.turn_off_tunnel), - ) - } else { - navController.navigate( - "${Screen.Option.route}/${selectedTunnel?.id}", - ) - } + navController.navigate( + "${Screen.Option.route}/${selectedTunnel?.id}", + ) }, ) { val icon = Icons.Rounded.Settings @@ -444,6 +426,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState, Icon(icon, icon.name) } IconButton( + enabled = !isActive, modifier = Modifier.focusable(), onClick = { showDeleteTunnelAlertDialog = true }, ) { @@ -468,16 +451,10 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState, Row { IconButton( onClick = { - if (uiState.settings.isAutoTunnelEnabled && !uiState.settings.isAutoTunnelPaused) { - snackbar.showMessage( - context.getString(R.string.turn_off_auto), - ) - } else { - selectedTunnel = tunnel - navController.navigate( - "${Screen.Option.route}/${selectedTunnel?.id}", - ) - } + selectedTunnel = tunnel + navController.navigate( + "${Screen.Option.route}/${selectedTunnel?.id}", + ) }, ) { val icon = Icons.Rounded.Settings 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 b9b8d80..3b7e678 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 @@ -56,9 +56,11 @@ import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle import com.zaneschepke.wireguardautotunnel.ui.common.config.SubmitConfigurationTextBox 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 import com.zaneschepke.wireguardautotunnel.util.Constants import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv import com.zaneschepke.wireguardautotunnel.util.extensions.isValidIpv4orIpv6Address +import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl import kotlinx.coroutines.delay @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @@ -246,6 +248,7 @@ fun OptionsScreen( value = currentText, onValueChange = { currentText = it }, label = { Text(stringResource(id = R.string.use_tunnel_on_wifi_name)) }, + supportingText = { WildcardSupportingLabel { context.openWebUrl(it) } }, modifier = Modifier .padding( 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 b49a58e..2335d2f 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 @@ -314,7 +314,20 @@ fun SettingsScreen( enabled = !uiState.settings.isAlwaysOnVpnEnabled, checked = uiState.settings.isTunnelOnWifiEnabled, padding = screenPadding, - onCheckChanged = { viewModel.onToggleTunnelOnWifi() }, + onCheckChanged = { checked -> + if (!checked) viewModel.onToggleTunnelOnWifi() + if (checked) { + when (false) { + isBackgroundLocationGranted -> showLocationDialog = true + fineLocationState.status.isGranted -> showLocationDialog = true + viewModel.isLocationEnabled(context) -> + showLocationServicesAlertDialog = true + else -> { + viewModel.onToggleTunnelOnWifi() + } + } + } + }, modifier = if (uiState.settings.isAutoTunnelEnabled) { Modifier @@ -442,23 +455,7 @@ fun SettingsScreen( TextButton( onClick = { if (uiState.tunnels.isEmpty()) return@TextButton context.showToast(R.string.tunnel_required) - if ( - uiState.settings.isTunnelOnWifiEnabled && - !uiState.settings.isAutoTunnelEnabled - ) { - when (false) { - isBackgroundLocationGranted -> showLocationDialog = true - fineLocationState.status.isGranted -> showLocationDialog = true - viewModel.isLocationEnabled(context) -> - showLocationServicesAlertDialog = true - - else -> { - handleAutoTunnelToggle() - } - } - } else { - handleAutoTunnelToggle() - } + handleAutoTunnelToggle() }, ) { val autoTunnelButtonText = diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Constants.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Constants.kt index 688f3e4..ae1c2a1 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Constants.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/Constants.kt @@ -6,8 +6,8 @@ object Constants { const val MANUAL_TUNNEL_CONFIG_ID = "0" const val BATTERY_SAVER_WATCHER_WAKE_LOCK_TIMEOUT = 10 * 60 * 1_000L // 10 minutes - const val VPN_STATISTIC_CHECK_INTERVAL = 1_000L const val WATCHER_COLLECTION_DELAY = 3_000L + const val CONF_FILE_EXTENSION = ".conf" const val ZIP_FILE_EXTENSION = ".zip" const val URI_CONTENT_SCHEME = "content" @@ -27,12 +27,11 @@ object Constants { const val DEFAULT_PING_IP = "1.1.1.1" const val PING_TIMEOUT = 5_000L - const val VPN_RESTART_DELAY = 1_000L const val PING_INTERVAL = 60_000L const val PING_COOLDOWN = PING_INTERVAL * 60 // one hour const val UNREADABLE_SSID = "" - val amneziaProperties = listOf("Jc", "Jmin", "Jmax", "S1", "S2", "H1", "H2", "H3", "H4") + val amProperties = listOf("Jc", "Jmin", "Jmax", "S1", "S2", "H1", "H2", "H3", "H4") const val QR_CODE_NAME_PROPERTY = "# Name =" } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/TunnelExtensions.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/TunnelExtensions.kt index 1e1bd44..6f5b6c5 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/TunnelExtensions.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/extensions/TunnelExtensions.kt @@ -54,7 +54,7 @@ fun Config.toWgQuickString(): String { val linesIterator = lines.iterator() while (linesIterator.hasNext()) { val next = linesIterator.next() - Constants.amneziaProperties.forEach { + Constants.amProperties.forEach { if (next.startsWith(it, ignoreCase = true)) { linesIterator.remove() }