mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
82afe54b99
New features: - Full config view in quick format - Live tunnel config view in quick format - IPv4/IPv6 endpoint fallback and recovery - Improved DDNS handling - Improved realtime tunnel monitoring via wireguard-go callbacks handshake failures and success - Architecture change to always bring tunnels up with post tunnel bootstrapping for improved security and reliability with subsequent domain resolution and peer updates - Added support for DoT and custom DNS provider endpoints - Added support for Amnezia globals - Improved/shared config parser with desktop - Improved AndroidTV navigation What went away: - Kernel backend/mode - Ping monitoring (now redundant with the handshake monitoring)
156 lines
6.4 KiB
Kotlin
156 lines
6.4 KiB
Kotlin
package com.zaneschepke.wireguardautotunnel.viewmodel
|
|
|
|
import androidx.lifecycle.ViewModel
|
|
import com.zaneschepke.wireguardautotunnel.R
|
|
import com.zaneschepke.wireguardautotunnel.core.orchestration.TunnelCoordinator
|
|
import com.zaneschepke.wireguardautotunnel.domain.repository.GlobalEffectRepository
|
|
import com.zaneschepke.wireguardautotunnel.domain.repository.ProxySettingsRepository
|
|
import com.zaneschepke.wireguardautotunnel.domain.sideeffect.GlobalSideEffect
|
|
import com.zaneschepke.wireguardautotunnel.ui.state.ProxySettingsUiState
|
|
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isValidAndroidProxyBindAddress
|
|
import kotlinx.coroutines.flow.combine
|
|
import org.orbitmvi.orbit.ContainerHost
|
|
import org.orbitmvi.orbit.viewmodel.container
|
|
|
|
class ProxySettingsViewModel(
|
|
private val proxySettingsRepository: ProxySettingsRepository,
|
|
private val globalEffectRepository: GlobalEffectRepository,
|
|
private val tunnelCoordinator: TunnelCoordinator,
|
|
) : ContainerHost<ProxySettingsUiState, Nothing>, ViewModel() {
|
|
|
|
override val container =
|
|
container<ProxySettingsUiState, Nothing>(
|
|
ProxySettingsUiState(),
|
|
buildSettings = { repeatOnSubscribedStopTimeout = 5000L },
|
|
) {
|
|
combine(tunnelCoordinator.backendStatus, proxySettingsRepository.flow) {
|
|
backendStatus,
|
|
settings ->
|
|
ProxySettingsUiState(
|
|
proxySettings = settings,
|
|
backendStatus = backendStatus,
|
|
isLoading = false,
|
|
socks5Enabled = settings.socks5ProxyEnabled,
|
|
httpEnabled = settings.httpProxyEnabled,
|
|
socksBindAddress = settings.socks5ProxyBindAddress ?: "",
|
|
httpBindAddress = settings.httpProxyBindAddress ?: "",
|
|
proxyUsername = settings.proxyUsername ?: "",
|
|
proxyPassword = settings.proxyPassword ?: "",
|
|
)
|
|
}
|
|
.collect { reduce { it } }
|
|
}
|
|
|
|
fun save() = intent {
|
|
reduce { state.copy(showSaveModal = false) }
|
|
|
|
val current = state
|
|
|
|
val updated =
|
|
current.proxySettings.copy(
|
|
socks5ProxyEnabled = current.socks5Enabled,
|
|
httpProxyEnabled = current.httpEnabled,
|
|
socks5ProxyBindAddress = current.socksBindAddress.ifBlank { null },
|
|
httpProxyBindAddress = current.httpBindAddress.ifBlank { null },
|
|
proxyUsername = current.proxyUsername.ifBlank { null },
|
|
proxyPassword = current.proxyPassword.ifBlank { null },
|
|
)
|
|
|
|
val isHttpDefault = updated.httpProxyBindAddress == null
|
|
val isSocks5Default = updated.socks5ProxyBindAddress == null
|
|
|
|
// Validate bind addresses
|
|
if (!isSocks5Default && !updated.socks5ProxyBindAddress.isValidAndroidProxyBindAddress()) {
|
|
return@intent reduce { state.copy(isSocks5BindAddressError = true) }
|
|
}
|
|
if (!isHttpDefault && !updated.httpProxyBindAddress.isValidAndroidProxyBindAddress()) {
|
|
return@intent reduce { state.copy(isHttpBindAddressError = true) }
|
|
}
|
|
// Validate different ports
|
|
if (!isHttpDefault && !isSocks5Default) {
|
|
val socksPort = updated.socks5ProxyBindAddress.split(":").last().toIntOrNull()
|
|
val httpPort = updated.httpProxyBindAddress.split(":").last().toIntOrNull()
|
|
if (socksPort == null || httpPort == null || socksPort == httpPort) {
|
|
return@intent postSideEffect(
|
|
GlobalSideEffect.Snackbar(
|
|
StringValue.StringResource(R.string.ports_must_differ)
|
|
)
|
|
)
|
|
}
|
|
}
|
|
// Validate username and password (both null or both not null)
|
|
if (!areBothNullOrBothNotNull(updated.proxyUsername, updated.proxyPassword)) {
|
|
return@intent reduce {
|
|
state.copy(
|
|
isUserNameError = updated.proxyUsername == null,
|
|
isPasswordError = updated.proxyPassword == null,
|
|
)
|
|
}
|
|
}
|
|
|
|
if (updated.proxyPassword?.any { it.isWhitespace() } == true) {
|
|
postSideEffect(
|
|
GlobalSideEffect.Snackbar(StringValue.StringResource(R.string.password_no_spaces))
|
|
)
|
|
return@intent reduce { state.copy(isPasswordError = true) }
|
|
}
|
|
|
|
proxySettingsRepository.upsert(updated)
|
|
|
|
tunnelCoordinator.toggleTunnels()
|
|
tunnelCoordinator.toggleTunnels()
|
|
|
|
postSideEffect(
|
|
GlobalSideEffect.Snackbar(StringValue.StringResource(R.string.config_changes_saved))
|
|
)
|
|
postSideEffect(GlobalSideEffect.PopBackStack)
|
|
}
|
|
|
|
fun clearHttpBindError() = intent { reduce { state.copy(isHttpBindAddressError = false) } }
|
|
|
|
fun clearSocks5BindError() = intent { reduce { state.copy(isSocks5BindAddressError = false) } }
|
|
|
|
fun clearUsernameError() = intent { reduce { state.copy(isPasswordError = false) } }
|
|
|
|
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)
|
|
}
|
|
|
|
private fun areBothNullOrBothNotNull(s1: String?, s2: String?) = (s1 == null) == (s2 == null)
|
|
|
|
fun onSocks5EnabledChanged(enabled: Boolean) = intent {
|
|
reduce { state.copy(socks5Enabled = enabled) }
|
|
}
|
|
|
|
fun onHttpEnabledChanged(enabled: Boolean) = intent {
|
|
reduce { state.copy(httpEnabled = enabled) }
|
|
}
|
|
|
|
fun onSocksBindChanged(value: String) = intent {
|
|
reduce { state.copy(socksBindAddress = value, isSocks5BindAddressError = false) }
|
|
}
|
|
|
|
fun onHttpBindChanged(value: String) = intent {
|
|
reduce { state.copy(httpBindAddress = value, isHttpBindAddressError = false) }
|
|
}
|
|
|
|
fun onUsernameChanged(value: String) = intent {
|
|
reduce { state.copy(proxyUsername = value, isUserNameError = false) }
|
|
}
|
|
|
|
fun onPasswordChanged(value: String) = intent {
|
|
reduce { state.copy(proxyPassword = value, isPasswordError = false) }
|
|
}
|
|
|
|
fun onPasswordVisibilityChanged(value: Boolean) = intent {
|
|
reduce { state.copy(passwordVisible = value) }
|
|
}
|
|
}
|