fix: auto tunnel overrides

This commit is contained in:
Zane Schepke 2024-09-22 00:24:38 -04:00
parent a362327fa3
commit d330fa4c28
13 changed files with 172 additions and 158 deletions

View File

@ -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

View File

@ -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<GeneralState> =

View File

@ -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

View File

@ -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<org.amnezia.awg.backend.Backend>,
@Kernel kernelBackend: Provider<Backend>,
appDataRepository: AppDataRepository,
tunnelConfigRepository: TunnelConfigRepository,
@ApplicationScope applicationScope: CoroutineScope,
@IoDispatcher ioDispatcher: CoroutineDispatcher,
): TunnelService {
return WireGuardTunnel(
amneziaBackend,
tunnelConfigRepository,
kernelBackend,
appDataRepository,
applicationScope,

View File

@ -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)

View File

@ -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 (

View File

@ -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<org.amnezia.awg.backend.Backend>,
tunnelConfigRepository: TunnelConfigRepository,
@Kernel private val kernelBackend: Provider<Backend>,
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> = _vpnState.asStateFlow()
override val vpnState: StateFlow<VpnState> = _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<String> {
return when (val backend = backend()) {
@ -48,8 +64,6 @@ constructor(
}
}
private var statsJob: Job? = null
private suspend fun setState(tunnelConfig: TunnelConfig, tunnelState: TunnelState): Result<TunnelState> {
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<TunnelState> {
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<TunnelState> {
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<TunnelState> {
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
}
}

View File

@ -145,7 +145,7 @@ fun ConfigScreen(tunnelId: Int, viewModel: ConfigViewModel, focusRequester: Focu
)
}
if(showApplicationsDialog) {
if (showApplicationsDialog) {
ApplicationSelectionDialog(viewModel, uiState) {
showApplicationsDialog = false
}

View File

@ -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

View File

@ -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(

View File

@ -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 =

View File

@ -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 = "<unknown 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 ="
}

View File

@ -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()
}