fix: add back per tunnel ping settings
This commit is contained in:
parent
72bf0a1979
commit
777a948244
|
@ -2,13 +2,19 @@ package com.zaneschepke.wireguardautotunnel.ui
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.wireguard.android.backend.WgQuickBackend
|
||||||
|
import com.wireguard.android.util.RootShell
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.module.AppShell
|
||||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
@ -21,6 +27,7 @@ import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.takeWhile
|
import kotlinx.coroutines.flow.takeWhile
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import xyz.teamgravity.pin_lock_compose.PinManager
|
import xyz.teamgravity.pin_lock_compose.PinManager
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
|
@ -32,6 +39,7 @@ constructor(
|
||||||
private val appDataRepository: AppDataRepository,
|
private val appDataRepository: AppDataRepository,
|
||||||
private val tunnelService: Provider<TunnelService>,
|
private val tunnelService: Provider<TunnelService>,
|
||||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
|
@AppShell private val rootShell: Provider<RootShell>,
|
||||||
private val serviceManager: ServiceManager,
|
private val serviceManager: ServiceManager,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
@ -107,4 +115,79 @@ constructor(
|
||||||
fun setLocationDisclosureShown() = viewModelScope.launch {
|
fun setLocationDisclosureShown() = viewModelScope.launch {
|
||||||
appDataRepository.appState.setLocationDisclosureShown(true)
|
appDataRepository.appState.setLocationDisclosureShown(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onToggleAlwaysOnVPN() = viewModelScope.launch {
|
||||||
|
with(uiState.value.settings) {
|
||||||
|
appDataRepository.settings.save(
|
||||||
|
copy(
|
||||||
|
isAlwaysOnVpnEnabled = !isAlwaysOnVpnEnabled,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onToggleRestartAtBoot() = viewModelScope.launch {
|
||||||
|
with(uiState.value.settings) {
|
||||||
|
appDataRepository.settings.save(
|
||||||
|
copy(
|
||||||
|
isRestoreOnBootEnabled = !isRestoreOnBootEnabled,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onToggleShortcutsEnabled() = viewModelScope.launch {
|
||||||
|
with(uiState.value.settings) {
|
||||||
|
appDataRepository.settings.save(
|
||||||
|
this.copy(
|
||||||
|
isShortcutsEnabled = !isShortcutsEnabled,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveKernelMode(enabled: Boolean) = viewModelScope.launch {
|
||||||
|
with(uiState.value.settings) {
|
||||||
|
appDataRepository.settings.save(
|
||||||
|
this.copy(
|
||||||
|
isKernelEnabled = enabled,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onToggleKernelMode() = viewModelScope.launch {
|
||||||
|
with(uiState.value.settings) {
|
||||||
|
if (!isKernelEnabled) {
|
||||||
|
requestRoot().onSuccess {
|
||||||
|
if (!isKernelSupported()) return@onSuccess SnackbarController.showMessage(StringValue.StringResource(R.string.kernel_not_supported))
|
||||||
|
appDataRepository.settings.save(
|
||||||
|
copy(
|
||||||
|
isKernelEnabled = true,
|
||||||
|
isAmneziaEnabled = false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
saveKernelMode(enabled = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun isKernelSupported(): Boolean {
|
||||||
|
return withContext(ioDispatcher) {
|
||||||
|
WgQuickBackend.hasKernelSupport()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun requestRoot(): Result<Unit> {
|
||||||
|
return withContext(ioDispatcher) {
|
||||||
|
kotlin.runCatching {
|
||||||
|
rootShell.get().start()
|
||||||
|
SnackbarController.showMessage(StringValue.StringResource(R.string.root_accepted))
|
||||||
|
}.onFailure {
|
||||||
|
SnackbarController.showMessage(StringValue.StringResource(R.string.error_root_denied))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.zaneschepke.wireguardautotunnel.ui.common.config
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
@ -10,7 +11,6 @@ import androidx.compose.material.icons.outlined.Save
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
@ -20,10 +20,11 @@ import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.textbox.CustomTextField
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SubmitConfigurationTextBox(
|
fun SubmitConfigurationTextBox(
|
||||||
|
@ -44,17 +45,22 @@ fun SubmitConfigurationTextBox(
|
||||||
|
|
||||||
var stateValue by remember { mutableStateOf(value ?: "") }
|
var stateValue by remember { mutableStateOf(value ?: "") }
|
||||||
|
|
||||||
OutlinedTextField(
|
CustomTextField(
|
||||||
isError = isErrorValue(stateValue),
|
isError = isErrorValue(stateValue),
|
||||||
modifier = Modifier
|
textStyle = MaterialTheme.typography.bodySmall,
|
||||||
.fillMaxWidth(),
|
|
||||||
value = stateValue,
|
value = stateValue,
|
||||||
singleLine = true,
|
|
||||||
interactionSource = interactionSource,
|
|
||||||
onValueChange = { stateValue = it },
|
onValueChange = { stateValue = it },
|
||||||
|
interactionSource = interactionSource,
|
||||||
label = { Text(label) },
|
label = { Text(label) },
|
||||||
maxLines = 1,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
placeholder = { Text(hint) },
|
placeholder = { Text(hint, style = MaterialTheme.typography.bodySmall) },
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(
|
||||||
|
top = 5.dp,
|
||||||
|
bottom = 10.dp,
|
||||||
|
).fillMaxWidth().padding(end = 16.dp.scaledWidth()),
|
||||||
|
singleLine = true,
|
||||||
keyboardOptions = keyboardOptions,
|
keyboardOptions = keyboardOptions,
|
||||||
keyboardActions = KeyboardActions(
|
keyboardActions = KeyboardActions(
|
||||||
onDone = {
|
onDone = {
|
||||||
|
@ -62,16 +68,17 @@ fun SubmitConfigurationTextBox(
|
||||||
keyboardController?.hide()
|
keyboardController?.hide()
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
trailingIcon = {
|
trailing = {
|
||||||
if (!isErrorValue(stateValue) && isFocused) {
|
if (!isErrorValue(stateValue) && isFocused) {
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
onSubmit(stateValue)
|
onSubmit(stateValue)
|
||||||
keyboardController?.hide()
|
keyboardController?.hide()
|
||||||
focusManager.clearFocus()
|
focusManager.clearFocus()
|
||||||
}) {
|
}) {
|
||||||
|
val icon = Icons.Outlined.Save
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Save,
|
imageVector = icon,
|
||||||
contentDescription = stringResource(R.string.save_changes),
|
contentDescription = icon.name,
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||||
import androidx.compose.material3.TextFieldDefaults
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
@ -37,8 +36,8 @@ fun CustomTextField(
|
||||||
isError: Boolean = false,
|
isError: Boolean = false,
|
||||||
readOnly: Boolean = false,
|
readOnly: Boolean = false,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
|
interactionSource: MutableInteractionSource,
|
||||||
) {
|
) {
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
|
||||||
val space = " "
|
val space = " "
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
value = value,
|
value = value,
|
||||||
|
|
|
@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Edit
|
import androidx.compose.material.icons.outlined.Edit
|
||||||
|
@ -30,6 +31,8 @@ import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
|
@ -38,12 +41,15 @@ import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.ScaledSwitch
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SelectionItem
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
import com.zaneschepke.wireguardautotunnel.ui.common.button.surface.SurfaceSelectionGroupButton
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.config.SubmitConfigurationTextBox
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.TopNavBar
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.TopNavBar
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.components.TrustedNetworkTextBox
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.components.TrustedNetworkTextBox
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.components.WildcardsLabel
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.components.WildcardsLabel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isValidIpv4orIpv6Address
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledHeight
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||||
|
|
||||||
|
@ -88,112 +94,175 @@ fun OptionsScreen(optionsViewModel: OptionsViewModel = hiltViewModel(), appUiSta
|
||||||
) {
|
) {
|
||||||
GroupLabel(stringResource(R.string.auto_tunneling))
|
GroupLabel(stringResource(R.string.auto_tunneling))
|
||||||
SurfaceSelectionGroupButton(
|
SurfaceSelectionGroupButton(
|
||||||
listOf(
|
buildList {
|
||||||
SelectionItem(
|
addAll(
|
||||||
Icons.Outlined.Star,
|
listOf(
|
||||||
title = {
|
SelectionItem(
|
||||||
Text(
|
Icons.Outlined.Star,
|
||||||
stringResource(R.string.primary_tunnel),
|
title = {
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
Text(
|
||||||
)
|
stringResource(R.string.primary_tunnel),
|
||||||
},
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
description = {
|
)
|
||||||
Text(
|
},
|
||||||
stringResource(R.string.set_primary_tunnel),
|
description = {
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.outline),
|
Text(
|
||||||
)
|
stringResource(R.string.set_primary_tunnel),
|
||||||
},
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.outline),
|
||||||
trailing = {
|
)
|
||||||
ScaledSwitch(
|
},
|
||||||
config.isPrimaryTunnel,
|
trailing = {
|
||||||
|
ScaledSwitch(
|
||||||
|
config.isPrimaryTunnel,
|
||||||
|
onClick = { optionsViewModel.onTogglePrimaryTunnel(config) },
|
||||||
|
)
|
||||||
|
},
|
||||||
onClick = { optionsViewModel.onTogglePrimaryTunnel(config) },
|
onClick = { optionsViewModel.onTogglePrimaryTunnel(config) },
|
||||||
)
|
),
|
||||||
},
|
SelectionItem(
|
||||||
onClick = { optionsViewModel.onTogglePrimaryTunnel(config) },
|
Icons.Outlined.PhoneAndroid,
|
||||||
),
|
title = {
|
||||||
SelectionItem(
|
Text(
|
||||||
Icons.Outlined.PhoneAndroid,
|
stringResource(R.string.mobile_tunnel),
|
||||||
title = { Text(stringResource(R.string.mobile_tunnel), style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface)) },
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
description = {
|
)
|
||||||
Text(
|
},
|
||||||
stringResource(R.string.mobile_data_tunnel),
|
description = {
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.outline),
|
Text(
|
||||||
)
|
stringResource(R.string.mobile_data_tunnel),
|
||||||
},
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.outline),
|
||||||
trailing = {
|
)
|
||||||
ScaledSwitch(
|
},
|
||||||
config.isMobileDataTunnel,
|
trailing = {
|
||||||
|
ScaledSwitch(
|
||||||
|
config.isMobileDataTunnel,
|
||||||
|
onClick = { optionsViewModel.onToggleIsMobileDataTunnel(config) },
|
||||||
|
)
|
||||||
|
},
|
||||||
onClick = { optionsViewModel.onToggleIsMobileDataTunnel(config) },
|
onClick = { optionsViewModel.onToggleIsMobileDataTunnel(config) },
|
||||||
)
|
),
|
||||||
},
|
SelectionItem(
|
||||||
onClick = { optionsViewModel.onToggleIsMobileDataTunnel(config) },
|
Icons.Outlined.NetworkPing,
|
||||||
),
|
title = {
|
||||||
SelectionItem(
|
Text(
|
||||||
Icons.Outlined.NetworkPing,
|
stringResource(R.string.restart_on_ping),
|
||||||
title = {
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
Text(
|
)
|
||||||
stringResource(R.string.restart_on_ping),
|
},
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
trailing = {
|
||||||
)
|
ScaledSwitch(
|
||||||
},
|
checked = config.isPingEnabled,
|
||||||
trailing = {
|
onClick = { optionsViewModel.onToggleRestartOnPing(config) },
|
||||||
ScaledSwitch(
|
)
|
||||||
checked = config.isPingEnabled,
|
},
|
||||||
onClick = { optionsViewModel.onToggleRestartOnPing(config) },
|
onClick = { optionsViewModel.onToggleRestartOnPing(config) },
|
||||||
)
|
),
|
||||||
},
|
),
|
||||||
onClick = { optionsViewModel.onToggleRestartOnPing(config) },
|
)
|
||||||
),
|
if (config.isPingEnabled || appUiState.settings.isPingEnabled) {
|
||||||
SelectionItem(
|
add(
|
||||||
title = {
|
SelectionItem(
|
||||||
Row(
|
title = {},
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
description = {
|
||||||
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp.scaledHeight()),
|
SubmitConfigurationTextBox(
|
||||||
) {
|
config.pingIp,
|
||||||
|
stringResource(R.string.set_custom_ping_ip),
|
||||||
|
stringResource(R.string.default_ping_ip),
|
||||||
|
isErrorValue = { !it.isNullOrBlank() && !it.isValidIpv4orIpv6Address() },
|
||||||
|
onSubmit = {
|
||||||
|
optionsViewModel.saveTunnelChanges(
|
||||||
|
config.copy(pingIp = it.ifBlank { null }),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
fun isSecondsError(seconds: String?): Boolean {
|
||||||
|
return seconds?.let { value -> if (value.isBlank()) false else value.toLong() >= Long.MAX_VALUE / 1000 } ?: false
|
||||||
|
}
|
||||||
|
SubmitConfigurationTextBox(
|
||||||
|
config.pingInterval?.let { (it / 1000).toString() },
|
||||||
|
stringResource(R.string.set_custom_ping_internal),
|
||||||
|
"(${stringResource(R.string.optional_default)} ${Constants.PING_INTERVAL / 1000})",
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Number,
|
||||||
|
imeAction = ImeAction.Done,
|
||||||
|
),
|
||||||
|
isErrorValue = ::isSecondsError,
|
||||||
|
onSubmit = {
|
||||||
|
optionsViewModel.saveTunnelChanges(
|
||||||
|
config.copy(pingInterval = if (it.isBlank()) null else it.toLong() * 1000),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
SubmitConfigurationTextBox(
|
||||||
|
config.pingCooldown?.let { (it / 1000).toString() },
|
||||||
|
stringResource(R.string.set_custom_ping_cooldown),
|
||||||
|
"(${stringResource(R.string.optional_default)} ${Constants.PING_COOLDOWN / 1000})",
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Number,
|
||||||
|
),
|
||||||
|
isErrorValue = ::isSecondsError,
|
||||||
|
onSubmit = {
|
||||||
|
optionsViewModel.saveTunnelChanges(
|
||||||
|
config.copy(pingCooldown = if (it.isBlank()) null else it.toLong() * 1000),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
add(
|
||||||
|
SelectionItem(
|
||||||
|
title = {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp.scaledHeight()),
|
||||||
.weight(4f, false)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
) {
|
) {
|
||||||
val icon = Icons.Outlined.Security
|
Row(
|
||||||
Icon(
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
icon,
|
|
||||||
icon.name,
|
|
||||||
modifier = Modifier.size(iconSize),
|
|
||||||
)
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.Start,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(2.dp, Alignment.CenterVertically),
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.weight(4f, false)
|
||||||
.padding(start = 16.dp.scaledWidth())
|
.fillMaxWidth(),
|
||||||
.padding(vertical = 6.dp.scaledHeight()),
|
|
||||||
) {
|
) {
|
||||||
Text(
|
val icon = Icons.Outlined.Security
|
||||||
stringResource(R.string.use_tunnel_on_wifi_name),
|
Icon(
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
icon,
|
||||||
|
icon.name,
|
||||||
|
modifier = Modifier.size(iconSize),
|
||||||
)
|
)
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.Start,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(2.dp, Alignment.CenterVertically),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 16.dp.scaledWidth())
|
||||||
|
.padding(vertical = 6.dp.scaledHeight()),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.use_tunnel_on_wifi_name),
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
description = {
|
||||||
description = {
|
TrustedNetworkTextBox(
|
||||||
TrustedNetworkTextBox(
|
config.tunnelNetworks,
|
||||||
config.tunnelNetworks,
|
onDelete = { optionsViewModel.onDeleteRunSSID(it, config) },
|
||||||
onDelete = { optionsViewModel.onDeleteRunSSID(it, config) },
|
currentText = currentText,
|
||||||
currentText = currentText,
|
onSave = { optionsViewModel.onSaveRunSSID(it, config) },
|
||||||
onSave = { optionsViewModel.onSaveRunSSID(it, config) },
|
onValueChange = { currentText = it },
|
||||||
onValueChange = { currentText = it },
|
supporting = {
|
||||||
supporting = {
|
if (appUiState.settings.isWildcardsEnabled) {
|
||||||
if (appUiState.settings.isWildcardsEnabled) {
|
WildcardsLabel()
|
||||||
WildcardsLabel()
|
}
|
||||||
}
|
},
|
||||||
},
|
)
|
||||||
)
|
},
|
||||||
},
|
),
|
||||||
),
|
)
|
||||||
),
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,7 +150,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
{
|
{
|
||||||
ScaledSwitch(
|
ScaledSwitch(
|
||||||
uiState.settings.isShortcutsEnabled,
|
uiState.settings.isShortcutsEnabled,
|
||||||
onClick = { viewModel.onToggleShortcutsEnabled() },
|
onClick = { appViewModel.onToggleShortcutsEnabled() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
|
@ -159,7 +159,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = { viewModel.onToggleShortcutsEnabled() },
|
onClick = { appViewModel.onToggleShortcutsEnabled() },
|
||||||
),
|
),
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Outlined.VpnLock,
|
Icons.Outlined.VpnLock,
|
||||||
|
@ -173,7 +173,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
) &&
|
) &&
|
||||||
uiState.settings.isAutoTunnelEnabled
|
uiState.settings.isAutoTunnelEnabled
|
||||||
),
|
),
|
||||||
onClick = { viewModel.onToggleAlwaysOnVPN() },
|
onClick = { appViewModel.onToggleAlwaysOnVPN() },
|
||||||
checked = uiState.settings.isAlwaysOnVpnEnabled,
|
checked = uiState.settings.isAlwaysOnVpnEnabled,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -183,7 +183,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = { viewModel.onToggleAlwaysOnVPN() },
|
onClick = { appViewModel.onToggleAlwaysOnVPN() },
|
||||||
),
|
),
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Outlined.AdminPanelSettings,
|
Icons.Outlined.AdminPanelSettings,
|
||||||
|
@ -209,7 +209,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
{
|
{
|
||||||
ScaledSwitch(
|
ScaledSwitch(
|
||||||
uiState.settings.isRestoreOnBootEnabled,
|
uiState.settings.isRestoreOnBootEnabled,
|
||||||
onClick = { viewModel.onToggleRestartAtBoot() },
|
onClick = { appViewModel.onToggleRestartAtBoot() },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
|
@ -218,7 +218,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = { viewModel.onToggleRestartAtBoot() },
|
onClick = { appViewModel.onToggleRestartAtBoot() },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -294,7 +294,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
trailing = {
|
trailing = {
|
||||||
ScaledSwitch(
|
ScaledSwitch(
|
||||||
uiState.settings.isKernelEnabled,
|
uiState.settings.isKernelEnabled,
|
||||||
onClick = { viewModel.onToggleKernelMode() },
|
onClick = { appViewModel.onToggleKernelMode() },
|
||||||
enabled = !(
|
enabled = !(
|
||||||
uiState.settings.isAutoTunnelEnabled ||
|
uiState.settings.isAutoTunnelEnabled ||
|
||||||
uiState.settings.isAlwaysOnVpnEnabled ||
|
uiState.settings.isAlwaysOnVpnEnabled ||
|
||||||
|
@ -303,7 +303,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.onToggleKernelMode()
|
appViewModel.onToggleKernelMode()
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -4,119 +4,23 @@ import android.content.Context
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.wireguard.android.backend.WgQuickBackend
|
|
||||||
import com.wireguard.android.util.RootShell
|
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.module.AppShell
|
|
||||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
import com.zaneschepke.wireguardautotunnel.util.FileUtils
|
||||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchShareFile
|
import com.zaneschepke.wireguardautotunnel.util.extensions.launchShareFile
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class SettingsViewModel
|
class SettingsViewModel
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val appDataRepository: AppDataRepository,
|
private val appDataRepository: AppDataRepository,
|
||||||
@AppShell private val rootShell: Provider<RootShell>,
|
|
||||||
private val fileUtils: FileUtils,
|
private val fileUtils: FileUtils,
|
||||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val settings = appDataRepository.settings.getSettingsFlow()
|
|
||||||
.stateIn(viewModelScope, SharingStarted.Eagerly, Settings())
|
|
||||||
|
|
||||||
fun setLocationDisclosureShown() = viewModelScope.launch {
|
|
||||||
appDataRepository.appState.setLocationDisclosureShown(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onToggleAlwaysOnVPN() = viewModelScope.launch {
|
|
||||||
with(settings.value) {
|
|
||||||
appDataRepository.settings.save(
|
|
||||||
copy(
|
|
||||||
isAlwaysOnVpnEnabled = !isAlwaysOnVpnEnabled,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onToggleShortcutsEnabled() = viewModelScope.launch {
|
|
||||||
with(settings.value) {
|
|
||||||
appDataRepository.settings.save(
|
|
||||||
this.copy(
|
|
||||||
isShortcutsEnabled = !isShortcutsEnabled,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun saveKernelMode(enabled: Boolean) = viewModelScope.launch {
|
|
||||||
with(settings.value) {
|
|
||||||
appDataRepository.settings.save(
|
|
||||||
this.copy(
|
|
||||||
isKernelEnabled = enabled,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onToggleKernelMode() = viewModelScope.launch {
|
|
||||||
with(settings.value) {
|
|
||||||
if (!isKernelEnabled) {
|
|
||||||
requestRoot().onSuccess {
|
|
||||||
if (!isKernelSupported()) return@onSuccess SnackbarController.showMessage(StringValue.StringResource(R.string.kernel_not_supported))
|
|
||||||
appDataRepository.settings.save(
|
|
||||||
copy(
|
|
||||||
isKernelEnabled = true,
|
|
||||||
isAmneziaEnabled = false,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
saveKernelMode(enabled = false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun isKernelSupported(): Boolean {
|
|
||||||
return withContext(ioDispatcher) {
|
|
||||||
WgQuickBackend.hasKernelSupport()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onToggleRestartAtBoot() = viewModelScope.launch {
|
|
||||||
with(settings.value) {
|
|
||||||
appDataRepository.settings.save(
|
|
||||||
copy(
|
|
||||||
isRestoreOnBootEnabled = !isRestoreOnBootEnabled,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun requestRoot(): Result<Unit> {
|
|
||||||
return withContext(ioDispatcher) {
|
|
||||||
kotlin.runCatching {
|
|
||||||
rootShell.get().start()
|
|
||||||
SnackbarController.showMessage(StringValue.StringResource(R.string.root_accepted))
|
|
||||||
}.onFailure {
|
|
||||||
SnackbarController.showMessage(StringValue.StringResource(R.string.error_root_denied))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun exportAllConfigs(context: Context) = viewModelScope.launch {
|
fun exportAllConfigs(context: Context) = viewModelScope.launch {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
val shareFile = fileUtils.createNewShareFile("wg-export_${Instant.now().epochSecond}.zip")
|
val shareFile = fileUtils.createNewShareFile("wg-export_${Instant.now().epochSecond}.zip")
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.components
|
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
@ -16,6 +17,7 @@ import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
@ -66,6 +68,7 @@ fun TrustedNetworkTextBox(
|
||||||
textStyle = MaterialTheme.typography.bodySmall,
|
textStyle = MaterialTheme.typography.bodySmall,
|
||||||
value = currentText,
|
value = currentText,
|
||||||
onValueChange = onValueChange,
|
onValueChange = onValueChange,
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
label = { Text(stringResource(R.string.add_wifi_name)) },
|
label = { Text(stringResource(R.string.add_wifi_name)) },
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
supportingText = supporting,
|
supportingText = supporting,
|
||||||
|
|
Loading…
Reference in New Issue