feat!: move ping from auto tunnel to tunnel
Ping feature will not be tunnel specific and work without auto tunneling being active
This commit is contained in:
parent
ba064b267f
commit
f772dc0f8a
|
@ -66,7 +66,7 @@ data class TunnelConfig(
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun toAmConfig(): org.amnezia.awg.config.Config {
|
fun toAmConfig(): org.amnezia.awg.config.Config {
|
||||||
return configFromAmQuick(if (amQuick != "") amQuick else wgQuick)
|
return configFromAmQuick(if (amQuick.isNotBlank()) amQuick else wgQuick)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toWgConfig(): Config {
|
fun toWgConfig(): Config {
|
||||||
|
|
|
@ -9,7 +9,6 @@ import androidx.lifecycle.lifecycleScope
|
||||||
import com.wireguard.android.util.RootShell
|
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.domain.Settings
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
|
||||||
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.AppShell
|
||||||
import com.zaneschepke.wireguardautotunnel.module.Ethernet
|
import com.zaneschepke.wireguardautotunnel.module.Ethernet
|
||||||
|
@ -29,29 +28,20 @@ import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotific
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.getCurrentWifiName
|
import com.zaneschepke.wireguardautotunnel.util.extensions.getCurrentWifiName
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.onNotRunning
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.debounce
|
import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.filterNot
|
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.net.InetAddress
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
|
|
||||||
|
@ -100,10 +90,6 @@ class AutoTunnelService : LifecycleService() {
|
||||||
|
|
||||||
private var wakeLock: PowerManager.WakeLock? = null
|
private var wakeLock: PowerManager.WakeLock? = null
|
||||||
|
|
||||||
private val pingTunnelRestartActive = AtomicBoolean(false)
|
|
||||||
|
|
||||||
private var pingJob: Job? = null
|
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
serviceManager.autoTunnelService.complete(this)
|
serviceManager.autoTunnelService.complete(this)
|
||||||
|
@ -135,7 +121,6 @@ class AutoTunnelService : LifecycleService() {
|
||||||
}
|
}
|
||||||
startAutoTunnelJob()
|
startAutoTunnelJob()
|
||||||
startAutoTunnelStateJob()
|
startAutoTunnelStateJob()
|
||||||
startPingStateJob()
|
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Timber.e(it)
|
Timber.e(it)
|
||||||
}
|
}
|
||||||
|
@ -147,7 +132,6 @@ class AutoTunnelService : LifecycleService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
cancelAndResetPingJob()
|
|
||||||
serviceManager.autoTunnelService = CompletableDeferred()
|
serviceManager.autoTunnelService = CompletableDeferred()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
@ -184,59 +168,6 @@ class AutoTunnelService : LifecycleService() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startPingJob() = lifecycleScope.launch {
|
|
||||||
watchForPingFailure()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startPingStateJob() = lifecycleScope.launch {
|
|
||||||
autoTunnelStateFlow.collect {
|
|
||||||
if (it == defaultState) return@collect
|
|
||||||
if (it.isPingEnabled()) {
|
|
||||||
pingJob.onNotRunning { pingJob = startPingJob() }
|
|
||||||
} else {
|
|
||||||
if (!pingTunnelRestartActive.get()) cancelAndResetPingJob()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun watchForPingFailure() {
|
|
||||||
withContext(ioDispatcher) {
|
|
||||||
Timber.i("Starting ping watcher")
|
|
||||||
runCatching {
|
|
||||||
do {
|
|
||||||
val vpnState = autoTunnelStateFlow.value.vpnState
|
|
||||||
if (vpnState.status.isUp() && !autoTunnelStateFlow.value.isNoConnectivity()) {
|
|
||||||
if (vpnState.tunnelConfig != null) {
|
|
||||||
val config = TunnelConfig.configFromWgQuick(vpnState.tunnelConfig.wgQuick)
|
|
||||||
val results = if (vpnState.tunnelConfig.pingIp != null) {
|
|
||||||
Timber.d("Pinging custom ip : ${vpnState.tunnelConfig.pingIp}")
|
|
||||||
listOf(InetAddress.getByName(vpnState.tunnelConfig.pingIp).isReachable(Constants.PING_TIMEOUT.toInt()))
|
|
||||||
} else {
|
|
||||||
Timber.d("Pinging all peers")
|
|
||||||
config.peers.map { peer ->
|
|
||||||
peer.isReachable()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Timber.i("Ping results reachable: $results")
|
|
||||||
if (results.contains(false)) {
|
|
||||||
Timber.i("Restarting VPN for ping failure")
|
|
||||||
val cooldown = vpnState.tunnelConfig.pingCooldown
|
|
||||||
pingTunnelRestartActive.set(true)
|
|
||||||
tunnelService.get().bounceTunnel()
|
|
||||||
pingTunnelRestartActive.set(false)
|
|
||||||
delay(cooldown ?: Constants.PING_COOLDOWN)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delay(vpnState.tunnelConfig?.pingInterval ?: Constants.PING_INTERVAL)
|
|
||||||
} while (true)
|
|
||||||
}.onFailure {
|
|
||||||
Timber.e(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startAutoTunnelStateJob() = lifecycleScope.launch(ioDispatcher) {
|
private fun startAutoTunnelStateJob() = lifecycleScope.launch(ioDispatcher) {
|
||||||
combine(
|
combine(
|
||||||
combineSettings(),
|
combineSettings(),
|
||||||
|
@ -263,11 +194,6 @@ class AutoTunnelService : LifecycleService() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelAndResetPingJob() {
|
|
||||||
pingJob?.cancelWithMessage("Ping job canceled")
|
|
||||||
pingJob = null
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
@OptIn(FlowPreview::class)
|
||||||
private fun combineNetworkEventsJob(): Flow<NetworkState> {
|
private fun combineNetworkEventsJob(): Flow<NetworkState> {
|
||||||
return combine(
|
return combine(
|
||||||
|
@ -281,7 +207,7 @@ class AutoTunnelService : LifecycleService() {
|
||||||
wifi.name,
|
wifi.name,
|
||||||
wifi.capabilities,
|
wifi.capabilities,
|
||||||
)
|
)
|
||||||
}.distinctUntilChanged().filterNot { it.isWifiConnected && it.wifiName == null }
|
}.distinctUntilChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun combineSettings(): Flow<Pair<Settings, TunnelConfigs>> {
|
private fun combineSettings(): Flow<Pair<Settings, TunnelConfigs>> {
|
||||||
|
|
|
@ -17,8 +17,11 @@ import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotific
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendState
|
import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendState
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asBackendState
|
import com.zaneschepke.wireguardautotunnel.util.extensions.asBackendState
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
@ -33,6 +36,7 @@ import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.amnezia.awg.backend.Tunnel
|
import org.amnezia.awg.backend.Tunnel
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.net.InetAddress
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
import javax.inject.Provider
|
||||||
|
|
||||||
|
@ -54,6 +58,7 @@ constructor(
|
||||||
|
|
||||||
private var statsJob: Job? = null
|
private var statsJob: Job? = null
|
||||||
private var tunnelChangesJob: Job? = null
|
private var tunnelChangesJob: Job? = null
|
||||||
|
private var pingJob: Job? = null
|
||||||
|
|
||||||
@get:Synchronized @set:Synchronized
|
@get:Synchronized @set:Synchronized
|
||||||
private var isKernelBackend: Boolean? = null
|
private var isKernelBackend: Boolean? = null
|
||||||
|
@ -86,16 +91,9 @@ constructor(
|
||||||
private suspend fun setState(tunnelConfig: TunnelConfig, tunnelState: TunnelState): Result<TunnelState> {
|
private suspend fun setState(tunnelConfig: TunnelConfig, tunnelState: TunnelState): Result<TunnelState> {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
when (val backend = backend()) {
|
when (val backend = backend()) {
|
||||||
is Backend -> backend.setState(this, tunnelState.toWgState(), TunnelConfig.configFromWgQuick(tunnelConfig.wgQuick)).let { TunnelState.from(it) }
|
is Backend -> backend.setState(this, tunnelState.toWgState(), tunnelConfig.toWgConfig()).let { TunnelState.from(it) }
|
||||||
is org.amnezia.awg.backend.Backend -> {
|
is org.amnezia.awg.backend.Backend -> {
|
||||||
val config = if (tunnelConfig.amQuick.isBlank()) {
|
backend.setState(this, tunnelState.toAmState(), tunnelConfig.toAmConfig()).let {
|
||||||
TunnelConfig.configFromAmQuick(
|
|
||||||
tunnelConfig.wgQuick,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
TunnelConfig.configFromAmQuick(tunnelConfig.amQuick)
|
|
||||||
}
|
|
||||||
backend.setState(this, tunnelState.toAmState(), config).let {
|
|
||||||
TunnelState.from(it)
|
TunnelState.from(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,9 +106,11 @@ constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isTunnelAlreadyRunning(tunnelConfig: TunnelConfig): Boolean {
|
private fun isTunnelAlreadyRunning(tunnelConfig: TunnelConfig): Boolean {
|
||||||
val isRunning = tunnelConfig.id == _vpnState.value.tunnelConfig?.id && _vpnState.value.status.isUp()
|
return with(_vpnState.value) {
|
||||||
if (isRunning) Timber.w("Tunnel already running")
|
this.tunnelConfig?.id == tunnelConfig.id && status.isUp().also {
|
||||||
return isRunning
|
if (it) Timber.w("Tunnel already running")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun startTunnel(tunnelConfig: TunnelConfig?, background: Boolean) {
|
override suspend fun startTunnel(tunnelConfig: TunnelConfig?, background: Boolean) {
|
||||||
|
@ -165,10 +165,12 @@ constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun bounceTunnel() {
|
override suspend fun bounceTunnel() {
|
||||||
_vpnState.value.tunnelConfig?.let {
|
with(_vpnState.value) {
|
||||||
withServiceActive {
|
if (tunnelConfig != null && status.isUp()) {
|
||||||
toggleTunnel(it)
|
withServiceActive {
|
||||||
toggleTunnel(it)
|
toggleTunnel(tunnelConfig)
|
||||||
|
toggleTunnel(tunnelConfig)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -273,13 +275,15 @@ constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cancelActiveTunnelJobs() {
|
override fun cancelActiveTunnelJobs() {
|
||||||
statsJob?.cancel()
|
statsJob?.cancelWithMessage("Tunnel stats job cancelled")
|
||||||
tunnelChangesJob?.cancel()
|
tunnelChangesJob?.cancelWithMessage("Tunnel changes job cancelled")
|
||||||
|
pingJob?.cancelWithMessage("Ping job cancelled")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startActiveTunnelJobs() {
|
override fun startActiveTunnelJobs() {
|
||||||
statsJob = startTunnelStatisticsJob()
|
statsJob = startTunnelStatisticsJob()
|
||||||
tunnelChangesJob = startTunnelConfigChangesJob()
|
tunnelChangesJob = startTunnelConfigChangesJob()
|
||||||
|
if (_vpnState.value.tunnelConfig?.isPingEnabled == true) pingJob = startPingJob()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getName(): String {
|
override fun getName(): String {
|
||||||
|
@ -304,22 +308,92 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isQuickConfigChanged(config: TunnelConfig): Boolean {
|
||||||
|
return with(_vpnState.value) {
|
||||||
|
if (tunnelConfig == null) return false
|
||||||
|
config.wgQuick != tunnelConfig.wgQuick ||
|
||||||
|
config.amQuick != tunnelConfig.amQuick
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isPingConfigMatching(config: TunnelConfig): Boolean {
|
||||||
|
return with(_vpnState.value.tunnelConfig) {
|
||||||
|
if (this == null) return true
|
||||||
|
config.isPingEnabled == isPingEnabled &&
|
||||||
|
pingIp == config.pingIp &&
|
||||||
|
config.pingCooldown == pingCooldown &&
|
||||||
|
config.pingInterval == pingInterval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handlePingConfigChanges() {
|
||||||
|
with(_vpnState.value.tunnelConfig) {
|
||||||
|
if (this == null) return
|
||||||
|
if (!isPingEnabled && pingJob?.isActive == true) {
|
||||||
|
pingJob?.cancelWithMessage("Ping job cancelled")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
restartPingJob()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun restartPingJob() {
|
||||||
|
pingJob?.cancelWithMessage("Ping job cancelled")
|
||||||
|
pingJob = startPingJob()
|
||||||
|
}
|
||||||
|
|
||||||
private fun startTunnelConfigChangesJob() = applicationScope.launch(ioDispatcher) {
|
private fun startTunnelConfigChangesJob() = applicationScope.launch(ioDispatcher) {
|
||||||
tunnelConfigRepository.getTunnelConfigsFlow().collect {
|
tunnelConfigRepository.getTunnelConfigsFlow().collect { tunnels ->
|
||||||
with(_vpnState.value) {
|
with(_vpnState.value) {
|
||||||
if (status.isDown() || tunnelConfig == null) return@collect
|
if (tunnelConfig == null) return@collect
|
||||||
val vpnConfigFromStorage = it.first { it.id == tunnelConfig.id }
|
val storageConfig = tunnels.firstOrNull { it.id == tunnelConfig.id }
|
||||||
val isRestartNeeded = vpnConfigFromStorage.wgQuick != tunnelConfig.wgQuick ||
|
if (storageConfig == null) return@collect
|
||||||
vpnConfigFromStorage.amQuick != tunnelConfig.amQuick
|
val quickChanged = isQuickConfigChanged(storageConfig)
|
||||||
updateTunnelConfig(vpnConfigFromStorage)
|
val pingMatching = isPingConfigMatching(storageConfig)
|
||||||
if (isRestartNeeded) {
|
updateTunnelConfig(storageConfig)
|
||||||
Timber.d("Bouncing tunnel on config change")
|
if (quickChanged) bounceTunnel()
|
||||||
bounceTunnel()
|
if (!pingMatching) handlePingConfigChanges()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun pingTunnel(tunnelConfig: TunnelConfig): List<Boolean> {
|
||||||
|
return withContext(ioDispatcher) {
|
||||||
|
val config = tunnelConfig.toWgConfig()
|
||||||
|
if (tunnelConfig.pingIp != null) {
|
||||||
|
Timber.i("Pinging custom ip")
|
||||||
|
listOf(InetAddress.getByName(tunnelConfig.pingIp).isReachable(Constants.PING_TIMEOUT.toInt()))
|
||||||
|
} else {
|
||||||
|
Timber.i("Pinging all peers")
|
||||||
|
config.peers.map { peer ->
|
||||||
|
peer.isReachable()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun startPingJob() = applicationScope.launch(ioDispatcher) {
|
||||||
|
do {
|
||||||
|
run {
|
||||||
|
with(_vpnState.value) {
|
||||||
|
// TODO ignore when no connectivity
|
||||||
|
if (status.isUp() && tunnelConfig != null) {
|
||||||
|
val reachable = pingTunnel(tunnelConfig)
|
||||||
|
if (reachable.contains(false)) {
|
||||||
|
Timber.i("Ping result: target was not reachable, bouncing the tunnel")
|
||||||
|
bounceTunnel()
|
||||||
|
delay(tunnelConfig.pingCooldown ?: Constants.PING_COOLDOWN)
|
||||||
|
return@run
|
||||||
|
} else {
|
||||||
|
Timber.i("Ping result: all ping targets were reached successfully")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delay(tunnelConfig?.pingInterval ?: Constants.PING_INTERVAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (true)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onStateChange(newState: Tunnel.State) {
|
override fun onStateChange(newState: Tunnel.State) {
|
||||||
_vpnState.update {
|
_vpnState.update {
|
||||||
it.copy(status = TunnelState.from(newState))
|
it.copy(status = TunnelState.from(newState))
|
||||||
|
|
|
@ -381,15 +381,6 @@ constructor(
|
||||||
serviceManager.startAutoTunnel(true)
|
serviceManager.startAutoTunnel(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onTogglePrimaryTunnel(tunnelConfig: TunnelConfig) = viewModelScope.launch {
|
|
||||||
appDataRepository.tunnels.updatePrimaryTunnel(
|
|
||||||
when (tunnelConfig.isPrimaryTunnel) {
|
|
||||||
true -> null
|
|
||||||
false -> tunnelConfig
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun rebuildConfigs(
|
private suspend fun rebuildConfigs(
|
||||||
amConfig: org.amnezia.awg.config.Config,
|
amConfig: org.amnezia.awg.config.Config,
|
||||||
wgConfig: Config,
|
wgConfig: Config,
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.graphics.Color.TRANSPARENT
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.SystemBarStyle
|
import androidx.activity.SystemBarStyle
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
|
@ -35,6 +36,7 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
@ -69,6 +71,7 @@ import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate
|
import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
@ -228,7 +231,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
composable<Route.TunnelOptions> {
|
composable<Route.TunnelOptions> {
|
||||||
val args = it.toRoute<Route.TunnelOptions>()
|
val args = it.toRoute<Route.TunnelOptions>()
|
||||||
val config = appUiState.tunnels.first { it.id == args.id }
|
val config = appUiState.tunnels.first { it.id == args.id }
|
||||||
OptionsScreen(config, viewModel)
|
OptionsScreen(config)
|
||||||
}
|
}
|
||||||
composable<Route.Lock> {
|
composable<Route.Lock> {
|
||||||
PinLockScreen(viewModel)
|
PinLockScreen(viewModel)
|
||||||
|
@ -250,6 +253,13 @@ class MainActivity : AppCompatActivity() {
|
||||||
TunnelAutoTunnelScreen(config, appUiState.settings)
|
TunnelAutoTunnelScreen(config, appUiState.settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BackHandler(enabled = true) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
if (!navController.popBackStack()) {
|
||||||
|
this@MainActivity.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -257,9 +267,4 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
|
||||||
// save battery by not polling stats while app is closed
|
|
||||||
tunnelService.cancelActiveTunnelJobs()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,9 @@ fun TopNavBar(title: String, trailing: @Composable () -> Unit = {}, showBack: Bo
|
||||||
},
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
if (showBack) {
|
if (showBack) {
|
||||||
IconButton(onClick = { navController.popBackStack() }) {
|
IconButton(onClick = {
|
||||||
|
navController.popBackStack()
|
||||||
|
}) {
|
||||||
val icon = Icons.AutoMirrored.Outlined.ArrowBack
|
val icon = Icons.AutoMirrored.Outlined.ArrowBack
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
|
|
|
@ -17,7 +17,6 @@ import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.AirplanemodeActive
|
import androidx.compose.material.icons.outlined.AirplanemodeActive
|
||||||
import androidx.compose.material.icons.outlined.Code
|
import androidx.compose.material.icons.outlined.Code
|
||||||
import androidx.compose.material.icons.outlined.Filter1
|
import androidx.compose.material.icons.outlined.Filter1
|
||||||
import androidx.compose.material.icons.outlined.NetworkPing
|
|
||||||
import androidx.compose.material.icons.outlined.Security
|
import androidx.compose.material.icons.outlined.Security
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material.icons.outlined.SettingsEthernet
|
import androidx.compose.material.icons.outlined.SettingsEthernet
|
||||||
|
@ -316,24 +315,6 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||||
viewModel.onToggleTunnelOnEthernet()
|
viewModel.onToggleTunnelOnEthernet()
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SelectionItem(
|
|
||||||
Icons.Outlined.NetworkPing,
|
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.restart_on_ping),
|
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
trailing = {
|
|
||||||
ScaledSwitch(
|
|
||||||
checked = uiState.settings.isPingEnabled,
|
|
||||||
onClick = { viewModel.onToggleRestartOnPing() },
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onClick = {
|
|
||||||
viewModel.onToggleRestartOnPing()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Outlined.AirplanemodeActive,
|
Icons.Outlined.AirplanemodeActive,
|
||||||
title = {
|
title = {
|
||||||
|
|
|
@ -119,16 +119,6 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onToggleRestartOnPing() = viewModelScope.launch {
|
|
||||||
with(settings.value) {
|
|
||||||
appDataRepository.settings.save(
|
|
||||||
copy(
|
|
||||||
isPingEnabled = !isPingEnabled,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onToggleStopOnNoInternet() = viewModelScope.launch {
|
fun onToggleStopOnNoInternet() = viewModelScope.launch {
|
||||||
with(settings.value) {
|
with(settings.value) {
|
||||||
appDataRepository.settings.save(
|
appDataRepository.settings.save(
|
||||||
|
|
|
@ -6,11 +6,13 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
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.automirrored.outlined.CallSplit
|
import androidx.compose.material.icons.automirrored.outlined.CallSplit
|
||||||
import androidx.compose.material.icons.outlined.Bolt
|
import androidx.compose.material.icons.outlined.Bolt
|
||||||
import androidx.compose.material.icons.outlined.Edit
|
import androidx.compose.material.icons.outlined.Edit
|
||||||
|
import androidx.compose.material.icons.outlined.NetworkPing
|
||||||
import androidx.compose.material.icons.outlined.Star
|
import androidx.compose.material.icons.outlined.Star
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
@ -24,22 +26,30 @@ 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 com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
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.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.components.ForwardButton
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.ForwardButton
|
||||||
|
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
|
||||||
|
import kotlin.text.isBlank
|
||||||
|
import kotlin.text.isNullOrBlank
|
||||||
|
import kotlin.text.toLong
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun OptionsScreen(tunnelConfig: TunnelConfig, appViewModel: AppViewModel) {
|
fun OptionsScreen(tunnelConfig: TunnelConfig, viewModel: TunnelOptionsViewModel = hiltViewModel()) {
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
|
|
||||||
var currentText by remember { mutableStateOf("") }
|
var currentText by remember { mutableStateOf("") }
|
||||||
|
@ -83,10 +93,10 @@ fun OptionsScreen(tunnelConfig: TunnelConfig, appViewModel: AppViewModel) {
|
||||||
trailing = {
|
trailing = {
|
||||||
ScaledSwitch(
|
ScaledSwitch(
|
||||||
tunnelConfig.isPrimaryTunnel,
|
tunnelConfig.isPrimaryTunnel,
|
||||||
onClick = { appViewModel.onTogglePrimaryTunnel(tunnelConfig) },
|
onClick = { viewModel.onTogglePrimaryTunnel(tunnelConfig) },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = { appViewModel.onTogglePrimaryTunnel(tunnelConfig) },
|
onClick = { viewModel.onTogglePrimaryTunnel(tunnelConfig) },
|
||||||
),
|
),
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
Icons.Outlined.Bolt,
|
Icons.Outlined.Bolt,
|
||||||
|
@ -141,6 +151,81 @@ fun OptionsScreen(tunnelConfig: TunnelConfig, appViewModel: AppViewModel) {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
SurfaceSelectionGroupButton(
|
||||||
|
buildList {
|
||||||
|
add(
|
||||||
|
SelectionItem(
|
||||||
|
Icons.Outlined.NetworkPing,
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.restart_on_ping),
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
trailing = {
|
||||||
|
ScaledSwitch(
|
||||||
|
checked = tunnelConfig.isPingEnabled,
|
||||||
|
onClick = { viewModel.onToggleRestartOnPing(tunnelConfig) },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = { viewModel.onToggleRestartOnPing(tunnelConfig) },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if (tunnelConfig.isPingEnabled) {
|
||||||
|
add(
|
||||||
|
SelectionItem(
|
||||||
|
title = {},
|
||||||
|
description = {
|
||||||
|
SubmitConfigurationTextBox(
|
||||||
|
tunnelConfig.pingIp,
|
||||||
|
stringResource(R.string.set_custom_ping_ip),
|
||||||
|
stringResource(R.string.default_ping_ip),
|
||||||
|
isErrorValue = { !it.isNullOrBlank() && !it.isValidIpv4orIpv6Address() },
|
||||||
|
onSubmit = {
|
||||||
|
viewModel.saveTunnelChanges(
|
||||||
|
tunnelConfig.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(
|
||||||
|
tunnelConfig.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 = {
|
||||||
|
viewModel.saveTunnelChanges(
|
||||||
|
tunnelConfig.copy(pingInterval = if (it.isBlank()) null else it.toLong() * 1000),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
SubmitConfigurationTextBox(
|
||||||
|
tunnelConfig.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 = {
|
||||||
|
viewModel.saveTunnelChanges(
|
||||||
|
tunnelConfig.copy(pingCooldown = if (it.isBlank()) null else it.toLong() * 1000),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.zaneschepke.wireguardautotunnel.ui.screens.tunneloptions
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class TunnelOptionsViewModel
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
private val appDataRepository: AppDataRepository,
|
||||||
|
) : ViewModel() {
|
||||||
|
fun onToggleRestartOnPing(tunnelConfig: TunnelConfig) = viewModelScope.launch {
|
||||||
|
appDataRepository.tunnels.save(
|
||||||
|
tunnelConfig.copy(
|
||||||
|
isPingEnabled = !tunnelConfig.isPingEnabled,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onTogglePrimaryTunnel(tunnelConfig: TunnelConfig) = viewModelScope.launch {
|
||||||
|
appDataRepository.tunnels.updatePrimaryTunnel(
|
||||||
|
when (tunnelConfig.isPrimaryTunnel) {
|
||||||
|
true -> null
|
||||||
|
false -> tunnelConfig
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveTunnelChanges(tunnelConfig: TunnelConfig) = viewModelScope.launch {
|
||||||
|
appDataRepository.tunnels.save(tunnelConfig)
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,10 +8,8 @@ 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.NetworkPing
|
|
||||||
import androidx.compose.material.icons.outlined.PhoneAndroid
|
import androidx.compose.material.icons.outlined.PhoneAndroid
|
||||||
import androidx.compose.material.icons.outlined.Security
|
import androidx.compose.material.icons.outlined.Security
|
||||||
import androidx.compose.material.icons.outlined.SettingsEthernet
|
import androidx.compose.material.icons.outlined.SettingsEthernet
|
||||||
|
@ -28,8 +26,6 @@ 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,13 +34,10 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
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.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
|
||||||
|
|
||||||
|
@ -119,76 +112,8 @@ fun TunnelAutoTunnelScreen(tunnelConfig: TunnelConfig, settings: Settings, tunne
|
||||||
},
|
},
|
||||||
onClick = { tunnelAutoTunnelViewModel.onToggleIsEthernetTunnel(tunnelConfig) },
|
onClick = { tunnelAutoTunnelViewModel.onToggleIsEthernetTunnel(tunnelConfig) },
|
||||||
),
|
),
|
||||||
SelectionItem(
|
|
||||||
Icons.Outlined.NetworkPing,
|
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.restart_on_ping),
|
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
trailing = {
|
|
||||||
ScaledSwitch(
|
|
||||||
checked = tunnelConfig.isPingEnabled,
|
|
||||||
onClick = { tunnelAutoTunnelViewModel.onToggleRestartOnPing(tunnelConfig) },
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onClick = { tunnelAutoTunnelViewModel.onToggleRestartOnPing(tunnelConfig) },
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if (tunnelConfig.isPingEnabled || settings.isPingEnabled) {
|
|
||||||
add(
|
|
||||||
SelectionItem(
|
|
||||||
title = {},
|
|
||||||
description = {
|
|
||||||
SubmitConfigurationTextBox(
|
|
||||||
tunnelConfig.pingIp,
|
|
||||||
stringResource(R.string.set_custom_ping_ip),
|
|
||||||
stringResource(R.string.default_ping_ip),
|
|
||||||
isErrorValue = { !it.isNullOrBlank() && !it.isValidIpv4orIpv6Address() },
|
|
||||||
onSubmit = {
|
|
||||||
tunnelAutoTunnelViewModel.saveTunnelChanges(
|
|
||||||
tunnelConfig.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(
|
|
||||||
tunnelConfig.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 = {
|
|
||||||
tunnelAutoTunnelViewModel.saveTunnelChanges(
|
|
||||||
tunnelConfig.copy(pingInterval = if (it.isBlank()) null else it.toLong() * 1000),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
SubmitConfigurationTextBox(
|
|
||||||
tunnelConfig.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 = {
|
|
||||||
tunnelAutoTunnelViewModel.saveTunnelChanges(
|
|
||||||
tunnelConfig.copy(pingCooldown = if (it.isBlank()) null else it.toLong() * 1000),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
add(
|
add(
|
||||||
SelectionItem(
|
SelectionItem(
|
||||||
title = {
|
title = {
|
||||||
|
|
|
@ -61,14 +61,6 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onToggleRestartOnPing(tunnelConfig: TunnelConfig) = viewModelScope.launch {
|
|
||||||
appDataRepository.tunnels.save(
|
|
||||||
tunnelConfig.copy(
|
|
||||||
isPingEnabled = !tunnelConfig.isPingEnabled,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onToggleIsEthernetTunnel(tunnelConfig: TunnelConfig) = viewModelScope.launch {
|
fun onToggleIsEthernetTunnel(tunnelConfig: TunnelConfig) = viewModelScope.launch {
|
||||||
if (tunnelConfig.isEthernetTunnel) {
|
if (tunnelConfig.isEthernetTunnel) {
|
||||||
appDataRepository.tunnels.updateEthernetTunnel(null)
|
appDataRepository.tunnels.updateEthernetTunnel(null)
|
||||||
|
|
|
@ -78,15 +78,9 @@ fun <T> CoroutineScope.asChannel(flow: Flow<T>): ReceiveChannel<T> = produce {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Job?.onNotRunning(callback: () -> Unit) {
|
|
||||||
if (this == null || this.isCompleted || this.isCompleted) {
|
|
||||||
callback.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Job.cancelWithMessage(message: String) {
|
fun Job.cancelWithMessage(message: String) {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
this.cancel()
|
cancel()
|
||||||
Timber.i(message)
|
Timber.i(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,11 +46,10 @@ fun Peer.isReachable(): Boolean {
|
||||||
} else {
|
} else {
|
||||||
Constants.DEFAULT_PING_IP
|
Constants.DEFAULT_PING_IP
|
||||||
}
|
}
|
||||||
Timber.i("Checking reachability of peer: $host")
|
Timber.d("Checking reachability of peer: $host")
|
||||||
val reachable =
|
val reachable =
|
||||||
InetAddress.getByName(host)
|
InetAddress.getByName(host)
|
||||||
.isReachable(Constants.PING_TIMEOUT.toInt())
|
.isReachable(Constants.PING_TIMEOUT.toInt())
|
||||||
Timber.i("Result: reachable - $reachable")
|
|
||||||
return reachable
|
return reachable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue