mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
refactor: restart modal on config changes for proxy and tuns
This commit is contained in:
@@ -397,6 +397,46 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun restartActiveTunnel(id: Int) =
|
||||
withContext(ioDispatcher) {
|
||||
val activeIds = activeTunnels.value.keys.toList()
|
||||
if (activeIds.isEmpty()) return@withContext
|
||||
if (!activeIds.contains(id)) return@withContext
|
||||
val tunnel = tunnelsRepository.getById(id) ?: return@withContext
|
||||
restartTunnel(tunnel)
|
||||
}
|
||||
|
||||
suspend fun restartActiveTunnels() =
|
||||
withContext(ioDispatcher) {
|
||||
val activeIds = activeTunnels.value.keys.toList()
|
||||
if (activeIds.isEmpty()) return@withContext
|
||||
|
||||
val tunnels = tunnelsRepository.getAll()
|
||||
if (tunnels.isEmpty()) return@withContext
|
||||
|
||||
supervisorScope {
|
||||
activeIds.forEach { id ->
|
||||
val tunnel =
|
||||
tunnels.find { it.id == id }
|
||||
?: run {
|
||||
Timber.w("Tunnel config $id not found; skipping restart")
|
||||
return@forEach
|
||||
}
|
||||
restartTunnel(tunnel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun restartTunnel(tunnel: TunnelConfig) {
|
||||
runCatching { stopTunnel(tunnel.id) }
|
||||
.onFailure { e -> Timber.e(e, "Failed to stop tunnel ${tunnel.id} during restart") }
|
||||
|
||||
delay(RESTART_TUNNEL_DELAY)
|
||||
|
||||
runCatching { startTunnel(tunnel) }
|
||||
.onFailure { e -> Timber.e(e, "Failed to restart tunnel ${tunnel.id}") }
|
||||
}
|
||||
|
||||
private suspend fun handleDynamicDnsMonitoring(
|
||||
activeTuns: Map<Int, TunnelState>,
|
||||
configs: List<TunnelConfig>,
|
||||
@@ -513,5 +553,6 @@ constructor(
|
||||
companion object {
|
||||
const val BASE_BACKOFF = 30_000L
|
||||
const val MAX_BACKOFF_TIME = 300_000L
|
||||
const val RESTART_TUNNEL_DELAY = 300L
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -43,6 +43,8 @@ fun LockdownSettingsScreen(viewModel: LockdownViewModel = hiltViewModel()) {
|
||||
|
||||
val uiState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
if (uiState.isLoading) return
|
||||
|
||||
var metered by remember { mutableStateOf(uiState.lockdownSettings.metered) }
|
||||
var dualStack by remember { mutableStateOf(uiState.lockdownSettings.dualStack) }
|
||||
var bypassLan by remember { mutableStateOf(uiState.lockdownSettings.bypassLan) }
|
||||
@@ -51,8 +53,6 @@ fun LockdownSettingsScreen(viewModel: LockdownViewModel = hiltViewModel()) {
|
||||
if (it is LocalSideEffect.SaveChanges) viewModel.setShowSaveModal(true)
|
||||
}
|
||||
|
||||
if (uiState.isLoading) return
|
||||
|
||||
if (uiState.showSaveModal) {
|
||||
InfoDialog(
|
||||
onDismiss = { viewModel.setShowSaveModal(false) },
|
||||
|
||||
+55
-45
@@ -10,6 +10,7 @@ import androidx.compose.material.icons.outlined.Http
|
||||
import androidx.compose.material.icons.outlined.RemoveRedEye
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -26,71 +27,82 @@ import com.zaneschepke.wireguardautotunnel.domain.model.ProxySettings
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.SurfaceRow
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.button.ThemedSwitch
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.security.SecureScreenFromRecording
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.textbox.ConfigurationTextBox
|
||||
import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.ProxySettingsViewModel
|
||||
import java.util.Locale
|
||||
import org.orbitmvi.orbit.compose.collectSideEffect
|
||||
|
||||
@Composable
|
||||
fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) {
|
||||
val sharedViewModel = LocalSharedVm.current
|
||||
val proxySettingsState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
if (proxySettingsState.isLoading) return
|
||||
val uiState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
val proxySettings by
|
||||
remember(proxySettingsState) { mutableStateOf(proxySettingsState.proxySettings) }
|
||||
if (uiState.isLoading) return
|
||||
|
||||
val locale = remember { Locale.getDefault() }
|
||||
|
||||
val proxySettings by remember(uiState) { mutableStateOf(uiState.proxySettings) }
|
||||
|
||||
var socks5Enabled by
|
||||
remember(proxySettings) {
|
||||
mutableStateOf(proxySettingsState.proxySettings.socks5ProxyEnabled)
|
||||
}
|
||||
remember(proxySettings) { mutableStateOf(uiState.proxySettings.socks5ProxyEnabled) }
|
||||
var httpEnabled by
|
||||
remember(proxySettings) {
|
||||
mutableStateOf(proxySettingsState.proxySettings.httpProxyEnabled)
|
||||
}
|
||||
remember(proxySettings) { mutableStateOf(uiState.proxySettings.httpProxyEnabled) }
|
||||
var socksBindAddress by
|
||||
remember(proxySettings) {
|
||||
mutableStateOf(proxySettingsState.proxySettings.socks5ProxyBindAddress ?: "")
|
||||
mutableStateOf(uiState.proxySettings.socks5ProxyBindAddress ?: "")
|
||||
}
|
||||
var httpBindAddress by
|
||||
remember(proxySettings) {
|
||||
mutableStateOf(proxySettingsState.proxySettings.httpProxyBindAddress ?: "")
|
||||
}
|
||||
remember(proxySettings) { mutableStateOf(uiState.proxySettings.httpProxyBindAddress ?: "") }
|
||||
var proxyUsername by
|
||||
remember(proxySettings) {
|
||||
mutableStateOf(proxySettingsState.proxySettings.proxyUsername ?: "")
|
||||
}
|
||||
remember(proxySettings) { mutableStateOf(uiState.proxySettings.proxyUsername ?: "") }
|
||||
var proxyPassword by
|
||||
remember(proxySettings) {
|
||||
mutableStateOf(proxySettingsState.proxySettings.proxyPassword ?: "")
|
||||
}
|
||||
var passwordVisible by
|
||||
remember(proxySettings) { mutableStateOf(proxySettingsState.passwordVisible) }
|
||||
remember(proxySettings) { mutableStateOf(uiState.proxySettings.proxyPassword ?: "") }
|
||||
var passwordVisible by remember(proxySettings) { mutableStateOf(uiState.passwordVisible) }
|
||||
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
|
||||
val keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() })
|
||||
val keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done)
|
||||
|
||||
fun saveChanges() {
|
||||
viewModel.save(
|
||||
ProxySettings(
|
||||
socks5ProxyEnabled = socks5Enabled,
|
||||
socks5ProxyBindAddress = socksBindAddress,
|
||||
httpProxyEnabled = httpEnabled,
|
||||
httpProxyBindAddress = httpBindAddress,
|
||||
proxyUsername = proxyUsername,
|
||||
proxyPassword = proxyPassword,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
sharedViewModel.collectSideEffect { sideEffect ->
|
||||
when (sideEffect) {
|
||||
LocalSideEffect.SaveChanges -> {
|
||||
viewModel.save(
|
||||
ProxySettings(
|
||||
socks5ProxyEnabled = socks5Enabled,
|
||||
socks5ProxyBindAddress = socksBindAddress,
|
||||
httpProxyEnabled = httpEnabled,
|
||||
httpProxyBindAddress = httpBindAddress,
|
||||
proxyUsername = proxyUsername,
|
||||
proxyPassword = proxyPassword,
|
||||
if (sideEffect is LocalSideEffect.SaveChanges) {
|
||||
if (uiState.activeTuns.isNotEmpty()) viewModel.setShowSaveModal(true) else saveChanges()
|
||||
}
|
||||
}
|
||||
|
||||
if (uiState.showSaveModal) {
|
||||
InfoDialog(
|
||||
onDismiss = { viewModel.setShowSaveModal(false) },
|
||||
onAttest = { saveChanges() },
|
||||
title = stringResource(R.string.save_changes),
|
||||
body = {
|
||||
Text(
|
||||
stringResource(
|
||||
R.string.restart_message_template,
|
||||
stringResource(R.string.tunnels).lowercase(locale),
|
||||
)
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
},
|
||||
confirmText = stringResource(R.string._continue),
|
||||
)
|
||||
}
|
||||
|
||||
SecureScreenFromRecording()
|
||||
@@ -119,10 +131,9 @@ fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) {
|
||||
),
|
||||
label = stringResource(R.string.socks_5_bind_address),
|
||||
value = socksBindAddress,
|
||||
isError = proxySettingsState.isSocks5BindAddressError,
|
||||
isError = uiState.isSocks5BindAddressError,
|
||||
onValueChange = {
|
||||
if (proxySettingsState.isSocks5BindAddressError)
|
||||
viewModel.clearSocks5BindError()
|
||||
if (uiState.isSocks5BindAddressError) viewModel.clearSocks5BindError()
|
||||
socksBindAddress = it
|
||||
},
|
||||
)
|
||||
@@ -144,10 +155,9 @@ fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) {
|
||||
),
|
||||
label = stringResource(R.string.http_bind_address),
|
||||
value = httpBindAddress,
|
||||
isError = proxySettingsState.isHttpBindAddressError,
|
||||
isError = uiState.isHttpBindAddressError,
|
||||
onValueChange = {
|
||||
if (proxySettingsState.isSocks5BindAddressError)
|
||||
viewModel.clearHttpBindError()
|
||||
if (uiState.isSocks5BindAddressError) viewModel.clearHttpBindError()
|
||||
httpBindAddress = it
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
@@ -169,11 +179,11 @@ fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) {
|
||||
ConfigurationTextBox(
|
||||
value = proxyUsername,
|
||||
onValueChange = {
|
||||
if (proxySettingsState.isUserNameError) viewModel.clearUsernameError()
|
||||
if (uiState.isUserNameError) viewModel.clearUsernameError()
|
||||
proxyUsername = it
|
||||
},
|
||||
label = stringResource(R.string.username),
|
||||
isError = proxySettingsState.isUserNameError,
|
||||
isError = uiState.isUserNameError,
|
||||
hint = "",
|
||||
keyboardActions = keyboardActions,
|
||||
keyboardOptions = keyboardOptions,
|
||||
@@ -181,11 +191,11 @@ fun ProxySettingsScreen(viewModel: ProxySettingsViewModel = hiltViewModel()) {
|
||||
ConfigurationTextBox(
|
||||
value = proxyPassword,
|
||||
onValueChange = {
|
||||
if (proxySettingsState.isUserNameError) viewModel.clearPasswordError()
|
||||
if (uiState.isUserNameError) viewModel.clearPasswordError()
|
||||
proxyPassword = it
|
||||
},
|
||||
label = stringResource(R.string.password),
|
||||
isError = proxySettingsState.isPasswordError,
|
||||
isError = uiState.isPasswordError,
|
||||
hint = "",
|
||||
keyboardActions = keyboardActions,
|
||||
keyboardOptions = keyboardOptions,
|
||||
|
||||
+32
-11
@@ -5,14 +5,18 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.entity.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.ui.LocalSharedVm
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.security.SecureScreenFromRecording
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.components.AddPeerButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.config.components.InterfaceSection
|
||||
@@ -21,33 +25,50 @@ import com.zaneschepke.wireguardautotunnel.ui.sideeffect.LocalSideEffect
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.ConfigProxy
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.PeerProxy
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.ConfigViewModel
|
||||
import java.util.Locale
|
||||
import org.orbitmvi.orbit.compose.collectSideEffect
|
||||
|
||||
@Composable
|
||||
fun ConfigScreen(viewModel: ConfigViewModel) {
|
||||
val sharedViewModel = LocalSharedVm.current
|
||||
|
||||
val configUiState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||
val uiState by viewModel.container.stateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
if (configUiState.isLoading) return
|
||||
if (uiState.isLoading) return
|
||||
|
||||
val locale = remember { Locale.getDefault() }
|
||||
|
||||
var configProxy by remember {
|
||||
mutableStateOf(
|
||||
configUiState.tunnel?.let { ConfigProxy.from(it.toAmConfig()) } ?: ConfigProxy()
|
||||
)
|
||||
mutableStateOf(uiState.tunnel?.let { ConfigProxy.from(it.toAmConfig()) } ?: ConfigProxy())
|
||||
}
|
||||
|
||||
var tunnelName by remember { mutableStateOf(configUiState.tunnel?.name ?: "") }
|
||||
var tunnelName by remember { mutableStateOf(uiState.tunnel?.name ?: "") }
|
||||
val isGlobalConfig = rememberSaveable { tunnelName == TunnelConfig.GLOBAL_CONFIG_NAME }
|
||||
|
||||
val isTunnelNameTaken by
|
||||
remember(tunnelName) {
|
||||
derivedStateOf { configUiState.unavailableNames.contains(tunnelName) }
|
||||
}
|
||||
remember(tunnelName) { derivedStateOf { uiState.unavailableNames.contains(tunnelName) } }
|
||||
|
||||
sharedViewModel.collectSideEffect { sideEffect ->
|
||||
if (sideEffect is LocalSideEffect.SaveChanges)
|
||||
viewModel.saveConfigProxy(configProxy, tunnelName)
|
||||
if (uiState.isRunning) viewModel.setShowSaveModal(true)
|
||||
else viewModel.saveConfigProxy(configProxy, tunnelName)
|
||||
}
|
||||
|
||||
if (uiState.showSaveModal) {
|
||||
InfoDialog(
|
||||
onDismiss = { viewModel.setShowSaveModal(false) },
|
||||
onAttest = { viewModel.saveConfigProxy(configProxy, tunnelName) },
|
||||
title = stringResource(R.string.save_changes),
|
||||
body = {
|
||||
Text(
|
||||
stringResource(
|
||||
R.string.restart_message_template,
|
||||
stringResource(R.string.tunnels).lowercase(locale),
|
||||
)
|
||||
)
|
||||
},
|
||||
confirmText = stringResource(R.string._continue),
|
||||
)
|
||||
}
|
||||
|
||||
SecureScreenFromRecording()
|
||||
@@ -60,7 +81,7 @@ fun ConfigScreen(viewModel: ConfigViewModel) {
|
||||
InterfaceSection(
|
||||
isGlobalConfig,
|
||||
configProxy = configProxy,
|
||||
configUiState.isRunning,
|
||||
uiState.isRunning,
|
||||
tunnelName,
|
||||
isTunnelNameTaken,
|
||||
onInterfaceChange = { configProxy = configProxy.copy(`interface` = it) },
|
||||
|
||||
@@ -7,4 +7,5 @@ data class ConfigUiState(
|
||||
val isLoading: Boolean = true,
|
||||
val tunnel: TunnelConfig? = null,
|
||||
val isRunning: Boolean = false,
|
||||
val showSaveModal: Boolean = false,
|
||||
)
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.state
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.domain.model.ProxySettings
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||
|
||||
data class ProxySettingsUiState(
|
||||
val proxySettings: ProxySettings = ProxySettings(),
|
||||
val activeTuns: Map<Int, TunnelState> = emptyMap(),
|
||||
val isSocks5BindAddressError: Boolean = false,
|
||||
val isHttpBindAddressError: Boolean = false,
|
||||
val isUserNameError: Boolean = false,
|
||||
val isPasswordError: Boolean = false,
|
||||
val passwordVisible: Boolean = false,
|
||||
val isLoading: Boolean = true,
|
||||
val showSaveModal: Boolean = false,
|
||||
)
|
||||
|
||||
@@ -51,6 +51,7 @@ constructor(
|
||||
}
|
||||
|
||||
fun saveConfigProxy(configProxy: ConfigProxy, tunnelName: String) = intent {
|
||||
reduce { state.copy(showSaveModal = false) }
|
||||
if (state.unavailableNames.contains(tunnelName))
|
||||
return@intent postSideEffect(
|
||||
GlobalSideEffect.Toast(StringValue.StringResource(R.string.tunnel_name_taken))
|
||||
@@ -72,12 +73,15 @@ constructor(
|
||||
}
|
||||
if (tunnelConfig != null) {
|
||||
tunnelRepository.save(tunnelConfig)
|
||||
|
||||
postSideEffect(
|
||||
GlobalSideEffect.Toast(
|
||||
StringValue.StringResource(R.string.config_changes_saved)
|
||||
)
|
||||
)
|
||||
postSideEffect(GlobalSideEffect.PopBackStack)
|
||||
|
||||
if (state.isRunning) tunnelManager.restartActiveTunnel(tunnelConfig.id)
|
||||
}
|
||||
}
|
||||
.onFailure {
|
||||
@@ -96,6 +100,10 @@ constructor(
|
||||
globalEffectRepository.post(globalSideEffect)
|
||||
}
|
||||
|
||||
fun setShowSaveModal(showSaveModal: Boolean) = intent {
|
||||
reduce { state.copy(showSaveModal = showSaveModal) }
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(tunnelId: Int?): ConfigViewModel
|
||||
|
||||
+6
-4
@@ -38,6 +38,12 @@ constructor(
|
||||
fun setLockdownSettings(lockdownSettings: LockdownSettings) = intent {
|
||||
reduce { state.copy(showSaveModal = false) }
|
||||
lockdownSettingsRepository.upsert(lockdownSettings)
|
||||
|
||||
postSideEffect(GlobalSideEffect.PopBackStack)
|
||||
postSideEffect(
|
||||
GlobalSideEffect.Toast(StringValue.StringResource(R.string.config_changes_saved))
|
||||
)
|
||||
|
||||
tunnelManager.setBackendMode(BackendMode.Inactive)
|
||||
val allowedIps =
|
||||
if (lockdownSettings.bypassLan) TunnelConfig.LAN_BYPASS_ALLOWED_IPS else emptySet()
|
||||
@@ -48,10 +54,6 @@ constructor(
|
||||
dualStack = lockdownSettings.dualStack,
|
||||
)
|
||||
)
|
||||
postSideEffect(GlobalSideEffect.PopBackStack)
|
||||
postSideEffect(
|
||||
GlobalSideEffect.Toast(StringValue.StringResource(R.string.config_changes_saved))
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun postSideEffect(globalSideEffect: GlobalSideEffect) {
|
||||
|
||||
+19
-5
@@ -2,6 +2,7 @@ package com.zaneschepke.wireguardautotunnel.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||
import com.zaneschepke.wireguardautotunnel.domain.model.ProxySettings
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.GlobalEffectRepository
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.ProxySettingsRepository
|
||||
@@ -11,6 +12,7 @@ import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isValidAndroidProxyBindAddress
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import org.orbitmvi.orbit.ContainerHost
|
||||
import org.orbitmvi.orbit.viewmodel.container
|
||||
|
||||
@@ -20,6 +22,7 @@ class ProxySettingsViewModel
|
||||
constructor(
|
||||
private val proxySettingsRepository: ProxySettingsRepository,
|
||||
private val globalEffectRepository: GlobalEffectRepository,
|
||||
private val tunnelManager: TunnelManager,
|
||||
) : ContainerHost<ProxySettingsUiState, Nothing>, ViewModel() {
|
||||
|
||||
override val container =
|
||||
@@ -27,12 +30,16 @@ constructor(
|
||||
ProxySettingsUiState(),
|
||||
buildSettings = { repeatOnSubscribedStopTimeout = 5000L },
|
||||
) {
|
||||
proxySettingsRepository.flow.collect {
|
||||
reduce { state.copy(proxySettings = it, isLoading = false) }
|
||||
}
|
||||
combine(tunnelManager.activeTunnels, proxySettingsRepository.flow) {
|
||||
activeTuns,
|
||||
settings ->
|
||||
state.copy(proxySettings = settings, isLoading = false, activeTuns = activeTuns)
|
||||
}
|
||||
.collect { reduce { it } }
|
||||
}
|
||||
|
||||
fun save(proxySettings: ProxySettings) = intent {
|
||||
reduce { state.copy(showSaveModal = false) }
|
||||
val updated =
|
||||
state.proxySettings.copy(
|
||||
socks5ProxyEnabled = proxySettings.socks5ProxyEnabled,
|
||||
@@ -94,19 +101,22 @@ constructor(
|
||||
)
|
||||
}
|
||||
}
|
||||
// Validate password for whitespace
|
||||
|
||||
if (updated.proxyPassword?.any { it.isWhitespace() } == true) {
|
||||
postSideEffect(
|
||||
GlobalSideEffect.Snackbar(StringValue.StringResource(R.string.password_no_spaces))
|
||||
)
|
||||
return@intent reduce { state.copy(isPasswordError = true) }
|
||||
}
|
||||
// Save if all validations pass
|
||||
|
||||
proxySettingsRepository.upsert(updated)
|
||||
|
||||
postSideEffect(
|
||||
GlobalSideEffect.Snackbar(StringValue.StringResource(R.string.config_changes_saved))
|
||||
)
|
||||
postSideEffect(GlobalSideEffect.PopBackStack)
|
||||
|
||||
if (state.activeTuns.isNotEmpty()) tunnelManager.restartActiveTunnels()
|
||||
}
|
||||
|
||||
fun clearHttpBindError() = intent { reduce { state.copy(isHttpBindAddressError = false) } }
|
||||
@@ -117,6 +127,10 @@ constructor(
|
||||
|
||||
fun clearPasswordError() = intent { reduce { state.copy(isPasswordError = false) } }
|
||||
|
||||
fun setShowSaveModal(showSaveModal: Boolean) = intent {
|
||||
reduce { state.copy(showSaveModal = showSaveModal) }
|
||||
}
|
||||
|
||||
suspend fun postSideEffect(globalSideEffect: GlobalSideEffect) {
|
||||
globalEffectRepository.post(globalSideEffect)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user