This commit is contained in:
Zane Schepke 2023-12-24 18:09:23 -05:00
parent f0ec661223
commit 408d88390b
7 changed files with 114 additions and 122 deletions

View File

@ -22,6 +22,9 @@ interface SettingsDoa {
@Query("SELECT * FROM settings") @Query("SELECT * FROM settings")
suspend fun getAll(): List<Settings> suspend fun getAll(): List<Settings>
@Query("SELECT * FROM settings LIMIT 1")
fun getSettingsFlow(): Flow<Settings>
@Query("SELECT * FROM settings") @Query("SELECT * FROM settings")
fun getAllFlow(): Flow<MutableList<Settings>> fun getAllFlow(): Flow<MutableList<Settings>>

View File

@ -27,12 +27,12 @@ class DataStoreManager(private val context: Context) {
context.dataStore.edit { context.dataStore.edit {
it[key] = value it[key] = value
} }
fun <T> getFromStoreFlow(key: Preferences.Key<T>) = context.dataStore.data.map {
fun <T> getFromStore(key: Preferences.Key<T>) =
context.dataStore.data.map {
it[key] it[key]
} }
suspend fun <T> getFromStore(key: Preferences.Key<T>) = context.dataStore.data.first { it.contains(key) }[key]
val locationDisclosureFlow: Flow<Boolean?> = context.dataStore.data.map { val locationDisclosureFlow: Flow<Boolean?> = context.dataStore.data.map {
it[LOCATION_DISCLOSURE_SHOWN] it[LOCATION_DISCLOSURE_SHOWN]
} }

View File

@ -1,8 +1,19 @@
package com.zaneschepke.wireguardautotunnel.ui package com.zaneschepke.wireguardautotunnel.ui
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.model.Settings
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject import javax.inject.Inject
class ActivityViewModel @Inject constructor() : ViewModel() { @HiltViewModel
// TODO move shared logic to shared viewmodel class ActivityViewModel @Inject constructor(
private val settingsRepo: SettingsDoa,
) : ViewModel() {
// val settings = settingsRepo.getSettingsFlow().stateIn(viewModelScope,
// SharingStarted.WhileSubscribed(5000L), Settings()
// )
} }

View File

@ -32,6 +32,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
@ -64,8 +65,7 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
// TODO move shared logic to shared viewmodel // val activityViewModel = hiltViewModel<ActivityViewModel>()
// val sharedViewModel = hiltViewModel<ActivityViewModel>()
val navController = rememberNavController() val navController = rememberNavController()
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }

View File

@ -8,6 +8,8 @@ import android.os.Build
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -84,6 +86,7 @@ import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
import com.zaneschepke.wireguardautotunnel.ui.ActivityViewModel
import com.zaneschepke.wireguardautotunnel.ui.CaptureActivityPortrait import com.zaneschepke.wireguardautotunnel.ui.CaptureActivityPortrait
import com.zaneschepke.wireguardautotunnel.ui.Routes import com.zaneschepke.wireguardautotunnel.ui.Routes
import com.zaneschepke.wireguardautotunnel.ui.common.RowListItem import com.zaneschepke.wireguardautotunnel.ui.common.RowListItem
@ -212,7 +215,7 @@ fun MainScreen(
} }
) )
if (showPrimaryChangeAlertDialog) { AnimatedVisibility(showPrimaryChangeAlertDialog) {
AlertDialog( AlertDialog(
onDismissRequest = { onDismissRequest = {
showPrimaryChangeAlertDialog = false showPrimaryChangeAlertDialog = false
@ -288,7 +291,7 @@ fun MainScreen(
} }
} }
) { ) {
if (tunnels.isEmpty()) { AnimatedVisibility(tunnels.isEmpty(), exit = fadeOut(), enter = fadeIn()) {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,

View File

@ -38,11 +38,13 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
@ -71,6 +73,7 @@ import com.wireguard.android.backend.WgQuickBackend
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.repository.datastore.DataStoreManager import com.zaneschepke.wireguardautotunnel.repository.datastore.DataStoreManager
import com.zaneschepke.wireguardautotunnel.ui.ActivityViewModel
import com.zaneschepke.wireguardautotunnel.ui.common.ClickableIconButton import com.zaneschepke.wireguardautotunnel.ui.common.ClickableIconButton
import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
@ -95,31 +98,29 @@ fun SettingsScreen(
val scope = rememberCoroutineScope { Dispatchers.IO } val scope = rememberCoroutineScope { Dispatchers.IO }
val context = LocalContext.current val context = LocalContext.current
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
val scrollState = rememberScrollState()
val keyboardController = LocalSoftwareKeyboardController.current val keyboardController = LocalSoftwareKeyboardController.current
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
val settings by viewModel.settings.collectAsStateWithLifecycle() val settings by viewModel.settings.collectAsStateWithLifecycle()
val trustedSSIDs by viewModel.trustedSSIDs.collectAsStateWithLifecycle() val tunnels by viewModel.tunnels.collectAsStateWithLifecycle()
val tunnels by viewModel.tunnels.collectAsStateWithLifecycle(mutableListOf()) val vpnState = viewModel.vpnState.collectAsStateWithLifecycle()
val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION) val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
var currentText by remember { mutableStateOf("") } var currentText by remember { mutableStateOf("") }
val scrollState = rememberScrollState()
var isBackgroundLocationGranted by remember { mutableStateOf(true) } var isBackgroundLocationGranted by remember { mutableStateOf(true) }
var showAuthPrompt by remember { mutableStateOf(false) }
var didExportFiles by remember { mutableStateOf(false) } var didExportFiles by remember { mutableStateOf(false) }
val isLocationDisclosureShown by viewModel.disclosureShown.collectAsStateWithLifecycle( var showAuthPrompt by remember { mutableStateOf(false) }
null var isLocationDisclosureShown by rememberSaveable {
) mutableStateOf(false)
val vpnState = viewModel.vpnState.collectAsStateWithLifecycle(initialValue = Tunnel.State.DOWN) }
val screenPadding = 5.dp val screenPadding = 5.dp
val fillMaxWidth = .85f val fillMaxWidth = .85f
fun setLocationDisclosureShown() = scope.launch {
viewModel.dataStoreManager.saveToDataStore( LaunchedEffect(Unit) {
DataStoreManager.LOCATION_DISCLOSURE_SHOWN, isLocationDisclosureShown = viewModel.isLocationDisclosureShown()
true
)
} }
fun exportAllConfigs() { fun exportAllConfigs() {
@ -175,25 +176,25 @@ fun SettingsScreen(
isBackgroundLocationGranted = if (!backgroundLocationState.status.isGranted) { isBackgroundLocationGranted = if (!backgroundLocationState.status.isGranted) {
false false
} else { } else {
SideEffect { if(!isLocationDisclosureShown) {
setLocationDisclosureShown() viewModel.setLocationDisclosureShown()
} }
true true
} }
} }
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
if (!fineLocationState.status.isGranted) { isBackgroundLocationGranted = if (!fineLocationState.status.isGranted) {
isBackgroundLocationGranted = false false
} else { } else {
SideEffect { SideEffect {
setLocationDisclosureShown() viewModel.setLocationDisclosureShown()
} }
isBackgroundLocationGranted = true true
} }
} }
if (isLocationDisclosureShown != true) { AnimatedVisibility(!isLocationDisclosureShown) {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top, verticalArrangement = Arrangement.Top,
@ -238,22 +239,21 @@ fun SettingsScreen(
horizontalArrangement = Arrangement.SpaceEvenly horizontalArrangement = Arrangement.SpaceEvenly
) { ) {
TextButton(onClick = { TextButton(onClick = {
setLocationDisclosureShown() viewModel.setLocationDisclosureShown()
}) { }) {
Text(stringResource(id = R.string.no_thanks)) Text(stringResource(id = R.string.no_thanks))
} }
TextButton(modifier = Modifier.focusRequester(focusRequester), onClick = { TextButton(modifier = Modifier.focusRequester(focusRequester), onClick = {
openSettings() openSettings()
setLocationDisclosureShown() viewModel.setLocationDisclosureShown()
}) { }) {
Text(stringResource(id = R.string.turn_on)) Text(stringResource(id = R.string.turn_on))
} }
} }
} }
return
} }
if (showAuthPrompt) { AnimatedVisibility(showAuthPrompt) {
AuthorizationPrompt( AuthorizationPrompt(
onSuccess = { onSuccess = {
showAuthPrompt = false showAuthPrompt = false
@ -348,7 +348,7 @@ fun SettingsScreen(
.fillMaxWidth(), .fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(5.dp) horizontalArrangement = Arrangement.spacedBy(5.dp)
) { ) {
trustedSSIDs.forEach { ssid -> settings.trustedNetworkSSIDs.forEach { ssid ->
ClickableIconButton( ClickableIconButton(
onIconClick = { onIconClick = {
scope.launch { scope.launch {
@ -360,7 +360,7 @@ fun SettingsScreen(
enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled) enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled)
) )
} }
if (trustedSSIDs.isEmpty()) { if (settings.trustedNetworkSSIDs.isEmpty()) {
Text( Text(
stringResource(R.string.none), stringResource(R.string.none),
fontStyle = FontStyle.Italic, fontStyle = FontStyle.Italic,
@ -535,7 +535,8 @@ fun SettingsScreen(
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(12.dp),
color = MaterialTheme.colorScheme.surface, color = MaterialTheme.colorScheme.surface,
modifier = Modifier modifier = Modifier
.fillMaxWidth(fillMaxWidth).padding(vertical = 10.dp) .fillMaxWidth(fillMaxWidth)
.padding(vertical = 10.dp)
.padding(bottom = 140.dp) .padding(bottom = 140.dp)
) { ) {
Column( Column(

View File

@ -6,22 +6,19 @@ import android.location.LocationManager
import android.os.Build import android.os.Build
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.wireguard.android.backend.Tunnel
import com.wireguard.android.util.RootShell import com.wireguard.android.util.RootShell
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.TunnelConfigDao import com.zaneschepke.wireguardautotunnel.repository.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.repository.datastore.DataStoreManager import com.zaneschepke.wireguardautotunnel.repository.datastore.DataStoreManager
import com.zaneschepke.wireguardautotunnel.repository.model.Settings import com.zaneschepke.wireguardautotunnel.repository.model.Settings
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import com.zaneschepke.wireguardautotunnel.util.WgTunnelException import com.zaneschepke.wireguardautotunnel.util.WgTunnelException
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.async import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
@ -32,110 +29,87 @@ constructor(
private val application: Application, private val application: Application,
private val tunnelRepo: TunnelConfigDao, private val tunnelRepo: TunnelConfigDao,
private val settingsRepo: SettingsDoa, private val settingsRepo: SettingsDoa,
val dataStoreManager: DataStoreManager, private val dataStoreManager: DataStoreManager,
private val rootShell: RootShell, private val rootShell: RootShell,
private val vpnService: VpnService private val vpnService: VpnService
) : ViewModel() { ) : ViewModel() {
private val _trustedSSIDs = MutableStateFlow(emptyList<String>()) val settings = settingsRepo.getSettingsFlow().stateIn(viewModelScope,
val trustedSSIDs = _trustedSSIDs.asStateFlow() SharingStarted.WhileSubscribed(5_000L), Settings())
private val _settings = MutableStateFlow(Settings()) val tunnels = tunnelRepo.getAllFlow().stateIn(viewModelScope,
val settings get() = _settings.asStateFlow() SharingStarted.WhileSubscribed(5_000L), emptyList())
val vpnState get() = vpnService.state val vpnState get() = vpnService.state.stateIn(viewModelScope,
val tunnels get() = tunnelRepo.getAllFlow() SharingStarted.WhileSubscribed(5_000L), Tunnel.State.DOWN)
val disclosureShown = dataStoreManager.locationDisclosureFlow
init {
isLocationServicesEnabled()
viewModelScope.launch(Dispatchers.IO) {
settingsRepo.getAllFlow().filter { it.isNotEmpty() }.collect {
val settings = it.first()
_settings.emit(settings)
_trustedSSIDs.emit(settings.trustedNetworkSSIDs.toList())
}
}
}
suspend fun onSaveTrustedSSID(ssid: String) { suspend fun onSaveTrustedSSID(ssid: String) {
val trimmed = ssid.trim() val trimmed = ssid.trim()
if (!_settings.value.trustedNetworkSSIDs.contains(trimmed)) { if (!settings.value.trustedNetworkSSIDs.contains(trimmed)) {
_settings.value.trustedNetworkSSIDs.add(trimmed) settings.value.trustedNetworkSSIDs.add(trimmed)
settingsRepo.save(_settings.value) settingsRepo.save(settings.value)
} else { } else {
throw WgTunnelException("SSID already exists.") throw WgTunnelException("SSID already exists.")
} }
} }
suspend fun isLocationDisclosureShown() : Boolean {
return dataStoreManager.getFromStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN) ?: false
}
fun setLocationDisclosureShown() {
viewModelScope.launch {
dataStoreManager.saveToDataStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN, true)
}
}
suspend fun onToggleTunnelOnMobileData() { suspend fun onToggleTunnelOnMobileData() {
settingsRepo.save( settingsRepo.save(
_settings.value.copy( settings.value.copy(
isTunnelOnMobileDataEnabled = !_settings.value.isTunnelOnMobileDataEnabled isTunnelOnMobileDataEnabled = !settings.value.isTunnelOnMobileDataEnabled
) )
) )
} }
suspend fun onDeleteTrustedSSID(ssid: String) { suspend fun onDeleteTrustedSSID(ssid: String) {
_settings.value.trustedNetworkSSIDs.remove(ssid) settings.value.trustedNetworkSSIDs.remove(ssid)
settingsRepo.save(_settings.value) settingsRepo.save(settings.value)
} }
private fun emitFirstTunnelAsDefault() = private suspend fun getDefaultTunnelOrFirst() : String {
viewModelScope.async { return settings.value.defaultTunnel ?: tunnelRepo.getAll().first().wgQuick
_settings.emit(_settings.value.copy(defaultTunnel = getFirstTunnelConfig().toString()))
} }
suspend fun toggleAutoTunnel() { suspend fun toggleAutoTunnel() {
if (_settings.value.isAutoTunnelEnabled) { val defaultTunnel = getDefaultTunnelOrFirst()
if (settings.value.isAutoTunnelEnabled) {
ServiceManager.stopWatcherService(application) ServiceManager.stopWatcherService(application)
} else { } else {
if (_settings.value.defaultTunnel == null) { ServiceManager.startWatcherService(application, defaultTunnel)
emitFirstTunnelAsDefault().await()
} }
val defaultTunnel = _settings.value.defaultTunnel saveSettings(
ServiceManager.startWatcherService(application, defaultTunnel!!) settings.value.copy(
} isAutoTunnelEnabled = settings.value.isAutoTunnelEnabled,
settingsRepo.save( defaultTunnel = defaultTunnel
_settings.value.copy(
isAutoTunnelEnabled = !_settings.value.isAutoTunnelEnabled
) )
) )
} }
private suspend fun getFirstTunnelConfig(): TunnelConfig {
return tunnelRepo.getAll().first()
}
suspend fun onToggleAlwaysOnVPN() { suspend fun onToggleAlwaysOnVPN() {
if (_settings.value.defaultTunnel == null) {
emitFirstTunnelAsDefault().await()
}
val updatedSettings = val updatedSettings =
_settings.value.copy( settings.value.copy(
isAlwaysOnVpnEnabled = !_settings.value.isAlwaysOnVpnEnabled isAlwaysOnVpnEnabled = !settings.value.isAlwaysOnVpnEnabled,
defaultTunnel = getDefaultTunnelOrFirst()
) )
emitSettings(updatedSettings)
saveSettings(updatedSettings) saveSettings(updatedSettings)
} }
private suspend fun emitSettings(settings: Settings) {
_settings.emit(
settings
)
}
private suspend fun saveSettings(settings: Settings) { private suspend fun saveSettings(settings: Settings) {
settingsRepo.save(settings) settingsRepo.save(settings)
} }
suspend fun onToggleTunnelOnEthernet() { suspend fun onToggleTunnelOnEthernet() {
if (_settings.value.defaultTunnel == null) { saveSettings(settings.value.copy(
emitFirstTunnelAsDefault().await() isTunnelOnEthernetEnabled = !settings.value.isTunnelOnEthernetEnabled
} ))
_settings.emit(
_settings.value.copy(
isTunnelOnEthernetEnabled = !_settings.value.isTunnelOnEthernetEnabled
)
)
settingsRepo.save(_settings.value)
} }
private fun isLocationServicesEnabled(): Boolean { private fun isLocationServicesEnabled(): Boolean {
@ -149,31 +123,39 @@ constructor(
} }
suspend fun onToggleShortcutsEnabled() { suspend fun onToggleShortcutsEnabled() {
settingsRepo.save( saveSettings(
_settings.value.copy( settings.value.copy(
isShortcutsEnabled = !_settings.value.isShortcutsEnabled isShortcutsEnabled = !settings.value.isShortcutsEnabled
) )
) )
} }
suspend fun onToggleBatterySaver() { suspend fun onToggleBatterySaver() {
settingsRepo.save( saveSettings(
_settings.value.copy( settings.value.copy(
isBatterySaverEnabled = !_settings.value.isBatterySaverEnabled isBatterySaverEnabled = !settings.value.isBatterySaverEnabled
) )
) )
} }
private suspend fun saveKernelMode(on: Boolean) { private suspend fun saveKernelMode(on: Boolean) {
settingsRepo.save( saveSettings(
_settings.value.copy( settings.value.copy(
isKernelEnabled = on isKernelEnabled = on
) )
) )
} }
suspend fun onToggleTunnelOnWifi() {
saveSettings(
settings.value.copy(
isTunnelOnWifiEnabled = !settings.value.isTunnelOnWifiEnabled
)
)
}
suspend fun onToggleKernelMode() { suspend fun onToggleKernelMode() {
if (!_settings.value.isKernelEnabled) { if (!settings.value.isKernelEnabled) {
try { try {
rootShell.start() rootShell.start()
Timber.d("Root shell accepted!") Timber.d("Root shell accepted!")
@ -186,12 +168,4 @@ constructor(
saveKernelMode(on = false) saveKernelMode(on = false)
} }
} }
suspend fun onToggleTunnelOnWifi() {
settingsRepo.save(
_settings.value.copy(
isTunnelOnWifiEnabled = !_settings.value.isTunnelOnWifiEnabled
)
)
}
} }