mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b61da18c41 | |||
| df7b3cf0f5 | |||
| b50b37e6c9 | |||
| 1d80da6383 | |||
| fa5a36515f | |||
| 58f53a4267 |
@@ -118,11 +118,6 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: android_artifacts_${{ inputs.flavor }}
|
||||
path: >-
|
||||
app/build/outputs/apk/${{ inputs.flavor }}/${{ inputs.build_type }}/${{
|
||||
inputs.flavor == 'fdroid' && inputs.build_type == 'release'
|
||||
&& 'wgtunnel-fdroid-release-*.apk'
|
||||
|| format('wgtunnel-{0}-v*.apk', inputs.flavor)
|
||||
}}
|
||||
path: app/build/outputs/apk/${{ inputs.flavor }}/${{ inputs.build_type }}/wgtunnel-${{ inputs.flavor }}${{ inputs.flavor == 'fdroid' && '-release' || '' }}-*.apk
|
||||
retention-days: 1
|
||||
if-no-files-found: warn
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
run: mkdir ${{ github.workspace }}/temp
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: android_artifacts_*
|
||||
path: ${{ github.workspace }}/temp
|
||||
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
run: mkdir ${{ github.workspace }}/temp
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: android_artifacts_*
|
||||
path: ${{ github.workspace }}/temp
|
||||
|
||||
@@ -121,8 +121,8 @@ android {
|
||||
packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } }
|
||||
|
||||
licensee {
|
||||
allowedLicenses().forEach { allow(it) }
|
||||
allowedLicenseUrls().forEach { allowUrl(it) }
|
||||
Constants.allowedLicenses.forEach { allow(it) }
|
||||
Constants.allowedLicenseUrls.forEach { allowUrl(it) }
|
||||
}
|
||||
|
||||
applicationVariants.all {
|
||||
@@ -225,9 +225,6 @@ dependencies {
|
||||
implementation(libs.shizuku.provider)
|
||||
|
||||
implementation(libs.reorderable)
|
||||
implementation(libs.roomdatabasebackup) {
|
||||
exclude(group = "org.reactivestreams", module = "reactive-streams")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register<Copy>("copyLicenseeJsonToAssets") {
|
||||
|
||||
@@ -47,10 +47,6 @@
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent>
|
||||
</queries>
|
||||
<application
|
||||
android:name=".WireGuardAutoTunnel"
|
||||
|
||||
@@ -28,7 +28,6 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
@@ -36,9 +35,6 @@ import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.toRoute
|
||||
import com.zaneschepke.networkmonitor.NetworkMonitor
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||
import com.zaneschepke.wireguardautotunnel.data.AppDatabase
|
||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.di.MainDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppStateRepository
|
||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.VpnDeniedDialog
|
||||
@@ -71,16 +67,11 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.license.LicenseScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.restartApp
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.showToast
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import de.raphaelebner.roomdatabasebackup.core.RoomBackup
|
||||
import javax.inject.Inject
|
||||
import kotlin.system.exitProcess
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.launch
|
||||
import org.amnezia.awg.backend.GoBackend.VpnService
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -92,16 +83,8 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
@Inject lateinit var networkMonitor: NetworkMonitor
|
||||
|
||||
@Inject @IoDispatcher lateinit var ioDispatcher: CoroutineDispatcher
|
||||
|
||||
@Inject @MainDispatcher lateinit var mainDispatcher: CoroutineDispatcher
|
||||
|
||||
@Inject lateinit var appDatabase: AppDatabase
|
||||
|
||||
private var lastLocationPermissionState: Boolean? = null
|
||||
|
||||
private lateinit var roomBackup: RoomBackup
|
||||
|
||||
val REQUEST_CODE = 123
|
||||
|
||||
@SuppressLint("BatteryLife")
|
||||
@@ -114,7 +97,6 @@ class MainActivity : AppCompatActivity() {
|
||||
window.isNavigationBarContrastEnforced = false
|
||||
}
|
||||
super.onCreate(savedInstanceState)
|
||||
roomBackup = RoomBackup(this)
|
||||
|
||||
val viewModel by viewModels<AppViewModel>()
|
||||
|
||||
@@ -268,7 +250,7 @@ class MainActivity : AppCompatActivity() {
|
||||
MainScreen(appUiState, appViewState, viewModel)
|
||||
}
|
||||
composable<Route.Settings> {
|
||||
SettingsScreen(appUiState, appViewState, viewModel)
|
||||
SettingsScreen(appUiState, viewModel)
|
||||
}
|
||||
composable<Route.SettingsAdvanced> {
|
||||
SettingsAdvancedScreen(appUiState, viewModel)
|
||||
@@ -301,7 +283,7 @@ class MainActivity : AppCompatActivity() {
|
||||
val args = backStack.toRoute<Route.Config>()
|
||||
val config =
|
||||
appUiState.tunnels.firstOrNull { it.id == args.id }
|
||||
ConfigScreen(config, appUiState, viewModel)
|
||||
ConfigScreen(config, viewModel)
|
||||
}
|
||||
composable<Route.TunnelOptions> { backStack ->
|
||||
val args = backStack.toRoute<Route.TunnelOptions>()
|
||||
@@ -356,53 +338,4 @@ class MainActivity : AppCompatActivity() {
|
||||
super.onPause()
|
||||
WireGuardAutoTunnel.setUiActive(false)
|
||||
}
|
||||
|
||||
fun performBackup() =
|
||||
lifecycleScope.launch(ioDispatcher) {
|
||||
roomBackup
|
||||
.database(appDatabase)
|
||||
.backupLocation(RoomBackup.BACKUP_FILE_LOCATION_CUSTOM_DIALOG)
|
||||
.enableLogDebug(true)
|
||||
.maxFileCount(5)
|
||||
.apply {
|
||||
onCompleteListener { success, message, exitCode ->
|
||||
lifecycleScope.launch(mainDispatcher) {
|
||||
if (success) {
|
||||
showToast(
|
||||
getString(
|
||||
R.string.backup_success,
|
||||
getString(R.string.restarting_app),
|
||||
)
|
||||
)
|
||||
restartApp()
|
||||
} else showToast(R.string.backup_failed)
|
||||
}
|
||||
}
|
||||
}
|
||||
.backup()
|
||||
}
|
||||
|
||||
fun performRestore() =
|
||||
lifecycleScope.launch {
|
||||
roomBackup
|
||||
.database(appDatabase)
|
||||
.enableLogDebug(true)
|
||||
.backupLocation(RoomBackup.BACKUP_FILE_LOCATION_CUSTOM_DIALOG)
|
||||
.apply {
|
||||
onCompleteListener { success, message, exitCode ->
|
||||
lifecycleScope.launch(mainDispatcher) {
|
||||
if (success) {
|
||||
showToast(
|
||||
getString(
|
||||
R.string.restore_success,
|
||||
getString(R.string.restarting_app),
|
||||
)
|
||||
)
|
||||
restartApp()
|
||||
} else showToast(R.string.restore_failed)
|
||||
}
|
||||
}
|
||||
}
|
||||
.restore()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import com.zaneschepke.wireguardautotunnel.core.worker.ServiceWorker
|
||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.di.MainDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.util.LocaleUtil
|
||||
import com.zaneschepke.wireguardautotunnel.util.ReleaseTree
|
||||
@@ -91,7 +91,7 @@ class WireGuardAutoTunnel : Application(), Configuration.Provider {
|
||||
|
||||
override fun onTerminate() {
|
||||
applicationScope.cancel()
|
||||
tunnelManager.setBackendStatus(BackendStatus.Inactive)
|
||||
tunnelManager.setBackendState(BackendState.INACTIVE, emptyList())
|
||||
super.onTerminate()
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -16,7 +16,7 @@ import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelMonitor
|
||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.NotificationAction
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus.StopReason.Ping
|
||||
import com.zaneschepke.wireguardautotunnel.domain.events.AutoTunnelEvent
|
||||
@@ -109,13 +109,13 @@ class AutoTunnelService : LifecycleService() {
|
||||
with(autoTunnelStateFlow.value) {
|
||||
if (
|
||||
settings.isVpnKillSwitchEnabled &&
|
||||
tunnelManager.getBackendStatus() !is BackendStatus.KillSwitch
|
||||
tunnelManager.getBackendState() != BackendState.KILL_SWITCH_ACTIVE
|
||||
) {
|
||||
eventHandlerJob?.cancel()
|
||||
val allowedIps =
|
||||
if (settings.isLanOnKillSwitchEnabled) TunnelConf.LAN_BYPASS_ALLOWED_IPS
|
||||
else emptyList()
|
||||
tunnelManager.setBackendStatus(BackendStatus.KillSwitch(allowedIps))
|
||||
tunnelManager.setBackendState(BackendState.KILL_SWITCH_ACTIVE, allowedIps)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -402,11 +402,11 @@ class AutoTunnelService : LifecycleService() {
|
||||
handleBounceWithBackoff(event.configsPeerKeyResolvedMap)
|
||||
is AutoTunnelEvent.StartKillSwitch -> {
|
||||
Timber.d("Starting kill switch")
|
||||
tunnelManager.setBackendStatus(BackendStatus.KillSwitch(event.allowedIps))
|
||||
tunnelManager.setBackendState(BackendState.KILL_SWITCH_ACTIVE, event.allowedIps)
|
||||
}
|
||||
AutoTunnelEvent.StopKillSwitch -> {
|
||||
Timber.d("Stopping kill switch")
|
||||
tunnelManager.setBackendStatus(BackendStatus.Active)
|
||||
tunnelManager.setBackendState(BackendState.SERVICE_ACTIVE, emptySet())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.wireguard.android.backend.BackendException
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.events.BackendError
|
||||
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||
@@ -59,12 +59,12 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun setBackendStatus(backendStatus: BackendStatus) {
|
||||
override fun setBackendState(backendState: BackendState, allowedIps: Collection<String>) {
|
||||
Timber.w("Not yet implemented for kernel")
|
||||
}
|
||||
|
||||
override fun getBackendStatus(): BackendStatus {
|
||||
return BackendStatus.Inactive
|
||||
override fun getBackendState(): BackendState {
|
||||
return BackendState.INACTIVE
|
||||
}
|
||||
|
||||
override suspend fun runningTunnelNames(): Set<String> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.events.BackendError
|
||||
import com.zaneschepke.wireguardautotunnel.domain.events.BackendMessage
|
||||
@@ -34,8 +34,7 @@ constructor(
|
||||
appDataRepository.settings.flow
|
||||
.filterNotNull()
|
||||
.flatMapLatest { settings ->
|
||||
val backend = if (settings.isKernelEnabled) kernelTunnel else userspaceTunnel
|
||||
MutableStateFlow(backend)
|
||||
MutableStateFlow(if (settings.isKernelEnabled) kernelTunnel else userspaceTunnel)
|
||||
}
|
||||
.stateIn(
|
||||
scope = applicationScope.plus(ioDispatcher),
|
||||
@@ -90,12 +89,12 @@ constructor(
|
||||
tunnelProviderFlow.value.bounceTunnel(tunnelConf, reason)
|
||||
}
|
||||
|
||||
override fun setBackendStatus(backendStatus: BackendStatus) {
|
||||
tunnelProviderFlow.value.setBackendStatus(backendStatus)
|
||||
override fun setBackendState(backendState: BackendState, allowedIps: Collection<String>) {
|
||||
tunnelProviderFlow.value.setBackendState(backendState, allowedIps)
|
||||
}
|
||||
|
||||
override fun getBackendStatus(): BackendStatus {
|
||||
return tunnelProviderFlow.value.getBackendStatus()
|
||||
override fun getBackendState(): BackendState {
|
||||
return tunnelProviderFlow.value.getBackendState()
|
||||
}
|
||||
|
||||
override suspend fun runningTunnelNames(): Set<String> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.events.BackendError
|
||||
import com.zaneschepke.wireguardautotunnel.domain.events.BackendMessage
|
||||
@@ -41,9 +41,9 @@ interface TunnelProvider {
|
||||
reason: TunnelStatus.StopReason = TunnelStatus.StopReason.User,
|
||||
)
|
||||
|
||||
fun setBackendStatus(backendStatus: BackendStatus)
|
||||
fun setBackendState(backendState: BackendState, allowedIps: Collection<String>)
|
||||
|
||||
fun getBackendStatus(): BackendStatus
|
||||
fun getBackendState(): BackendState
|
||||
|
||||
suspend fun runningTunnelNames(): Set<String>
|
||||
|
||||
|
||||
+39
-18
@@ -1,15 +1,15 @@
|
||||
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.events.BackendError
|
||||
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.AmneziaStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendStatus
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asBackendStatus
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendState
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asBackendState
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toBackendError
|
||||
import javax.inject.Inject
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
@@ -17,6 +17,7 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import org.amnezia.awg.backend.Backend
|
||||
import org.amnezia.awg.backend.BackendException
|
||||
import org.amnezia.awg.backend.Tunnel
|
||||
import org.amnezia.awg.config.Config
|
||||
import timber.log.Timber
|
||||
|
||||
class UserspaceTunnel
|
||||
@@ -28,21 +29,14 @@ constructor(
|
||||
private val backend: Backend,
|
||||
) : BaseTunnel(applicationScope, appDataRepository, serviceManager) {
|
||||
|
||||
private var previousBackendState: Pair<BackendState, Boolean>? = null
|
||||
|
||||
override suspend fun startBackend(tunnel: TunnelConf) {
|
||||
try {
|
||||
updateTunnelStatus(tunnel, TunnelStatus.Starting)
|
||||
val amConfig = tunnel.toAmConfig()
|
||||
var previousKillSwitch: Backend.BackendStatus? = null
|
||||
// prevent dns failures from bringing tuns up when vpn kill switch active
|
||||
if (
|
||||
amConfig.peers.any { it.endpoint.getOrNull()?.toString()?.isUrl() == true } &&
|
||||
backend.backendStatus is Backend.BackendStatus.KillSwitchActive
|
||||
) {
|
||||
previousKillSwitch = backend.backendStatus
|
||||
setBackendStatus(BackendStatus.Active)
|
||||
}
|
||||
handleVpnKillSwitchWithDomainEndpoints(amConfig)
|
||||
backend.setState(tunnel, Tunnel.State.UP, amConfig)
|
||||
previousKillSwitch?.let { backend.backendStatus = it }
|
||||
} catch (e: BackendException) {
|
||||
Timber.e(e, "Failed to start up backend for tunnel ${tunnel.name}")
|
||||
throw e.toBackendError()
|
||||
@@ -59,20 +53,47 @@ constructor(
|
||||
} catch (e: BackendException) {
|
||||
Timber.e(e, "Failed to stop tunnel ${tunnel.id}")
|
||||
throw e.toBackendError()
|
||||
} finally {
|
||||
handlePreviouslyEnabledVpnKillSwitch()
|
||||
}
|
||||
}
|
||||
|
||||
override fun setBackendStatus(backendStatus: BackendStatus) {
|
||||
Timber.d("Setting backend state: $backendStatus")
|
||||
// stop vpn kill switch if we need to resolve DNS for peer endpoints
|
||||
private suspend fun handleVpnKillSwitchWithDomainEndpoints(config: Config) {
|
||||
if (
|
||||
config.peers.any { it.endpoint.getOrNull()?.toString()?.isUrl() == true } &&
|
||||
backend.backendState.asBackendState() == BackendState.KILL_SWITCH_ACTIVE
|
||||
) {
|
||||
val bypassLan = appDataRepository.settings.get().isLanOnKillSwitchEnabled
|
||||
previousBackendState = Pair(BackendState.KILL_SWITCH_ACTIVE, bypassLan)
|
||||
setBackendState(BackendState.SERVICE_ACTIVE, emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
// restore vpn kill switch if needed
|
||||
private fun handlePreviouslyEnabledVpnKillSwitch() {
|
||||
// let auto tunnel handle this if it is active
|
||||
if (serviceManager.autoTunnelService.value == null) {
|
||||
previousBackendState?.let { (state, lanEnabled) ->
|
||||
Timber.d("Restoring kill switch configuration")
|
||||
val lan = if (lanEnabled) TunnelConf.LAN_BYPASS_ALLOWED_IPS else emptyList()
|
||||
backend.setBackendState(state.asAmBackendState(), lan)
|
||||
}
|
||||
}
|
||||
previousBackendState = null
|
||||
}
|
||||
|
||||
override fun setBackendState(backendState: BackendState, allowedIps: Collection<String>) {
|
||||
Timber.d("Setting backend state: $backendState with allowedIps: $allowedIps")
|
||||
try {
|
||||
backend.backendStatus = backendStatus.asAmBackendStatus()
|
||||
backend.setBackendState(backendState.asAmBackendState(), allowedIps)
|
||||
} catch (e: BackendException) {
|
||||
throw e.toBackendError()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBackendStatus(): BackendStatus {
|
||||
return backend.backendStatus.asBackendStatus()
|
||||
override fun getBackendState(): BackendState {
|
||||
return backend.backendState.asBackendState()
|
||||
}
|
||||
|
||||
override suspend fun runningTunnelNames(): Set<String> {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package com.zaneschepke.wireguardautotunnel.domain.enums
|
||||
|
||||
sealed class BackendStatus {
|
||||
data object Inactive : BackendStatus()
|
||||
|
||||
data object Active : BackendStatus()
|
||||
|
||||
data class KillSwitch(val allowedIps: List<String>) : BackendStatus()
|
||||
enum class BackendState {
|
||||
KILL_SWITCH_ACTIVE,
|
||||
SERVICE_ACTIVE,
|
||||
INACTIVE,
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package com.zaneschepke.wireguardautotunnel.domain.state
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||
import org.amnezia.awg.crypto.Key
|
||||
|
||||
data class TunnelState(
|
||||
val status: TunnelStatus = TunnelStatus.Down,
|
||||
val backendState: BackendStatus = BackendStatus.Inactive,
|
||||
val backendState: BackendState = BackendState.INACTIVE,
|
||||
val statistics: TunnelStatistics? = null,
|
||||
val pingStates: Map<Key, PingState>? = null,
|
||||
val handshakeSuccessLogs: Boolean? = null,
|
||||
|
||||
-9
@@ -143,15 +143,6 @@ fun currentNavBackStackEntryAsNavBarState(
|
||||
NavBarState(
|
||||
topTitle = { Text(stringResource(R.string.settings)) },
|
||||
route = Route.Settings,
|
||||
topTrailing = {
|
||||
ActionIconButton(Icons.Rounded.Menu, R.string.quick_actions) {
|
||||
viewModel.handleEvent(
|
||||
AppEvent.SetBottomSheet(
|
||||
AppViewState.BottomSheet.BACKUP_AND_RESTORE
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
backStackEntry.isCurrentRoute(Route.Appearance::class) ->
|
||||
|
||||
+19
-37
@@ -7,7 +7,10 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -22,7 +25,6 @@ import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.components.AddPeerButton
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.components.InterfaceSection
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.components.PeersSection
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.AppUiState
|
||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
|
||||
@@ -30,7 +32,6 @@ import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
|
||||
@Composable
|
||||
fun ConfigScreen(
|
||||
tunnelConf: TunnelConf?,
|
||||
appUiState: AppUiState,
|
||||
appViewModel: AppViewModel,
|
||||
viewModel: ConfigViewModel = hiltViewModel(),
|
||||
) {
|
||||
@@ -41,17 +42,6 @@ fun ConfigScreen(
|
||||
|
||||
val activity = context as? MainActivity
|
||||
|
||||
var save by remember { mutableStateOf(false) }
|
||||
|
||||
val isTunnelNameTaken by
|
||||
remember(uiState.tunnelName, appUiState.tunnels) {
|
||||
derivedStateOf {
|
||||
appUiState.tunnels
|
||||
.filter { it.id != tunnelConf?.id }
|
||||
.any { it.name == uiState.tunnelName }
|
||||
}
|
||||
}
|
||||
|
||||
// Secure screen due to sensitive information
|
||||
DisposableEffect(Unit) {
|
||||
activity
|
||||
@@ -68,34 +58,26 @@ fun ConfigScreen(
|
||||
appViewModel.handleEvent(
|
||||
AppEvent.SetScreenAction {
|
||||
keyboardController?.hide()
|
||||
if (!isTunnelNameTaken) {
|
||||
save = true
|
||||
}
|
||||
viewModel.save(tunnelConf)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(tunnelConf) { viewModel.initFromTunnel(tunnelConf) }
|
||||
|
||||
// TODO improve error messages
|
||||
LaunchedEffect(save) {
|
||||
if (save) {
|
||||
try {
|
||||
appViewModel.handleEvent(
|
||||
AppEvent.SaveTunnelUniquely(
|
||||
uiState.configProxy.buildTunnelConfFromState(uiState.tunnelName, tunnelConf)
|
||||
)
|
||||
)
|
||||
appViewModel.handleEvent(
|
||||
AppEvent.ShowMessage(StringValue.StringResource(R.string.config_changes_saved))
|
||||
)
|
||||
appViewModel.handleEvent(AppEvent.PopBackStack(true))
|
||||
} catch (e: Exception) {
|
||||
val message = e.message ?: context.resources.getString(R.string.unknown_error)
|
||||
appViewModel.handleEvent(AppEvent.ShowMessage(StringValue.DynamicString(message)))
|
||||
} finally {
|
||||
save = false
|
||||
}
|
||||
LaunchedEffect(uiState.success) {
|
||||
if (uiState.success == true) {
|
||||
appViewModel.handleEvent(
|
||||
AppEvent.ShowMessage(StringValue.StringResource(R.string.config_changes_saved))
|
||||
)
|
||||
appViewModel.handleEvent(AppEvent.PopBackStack(true))
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(uiState.message) {
|
||||
uiState.message?.let { message ->
|
||||
appViewModel.handleEvent(AppEvent.ShowMessage(message))
|
||||
viewModel.setMessage(null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +111,7 @@ fun ConfigScreen(
|
||||
modifier =
|
||||
Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 24.dp),
|
||||
) {
|
||||
InterfaceSection(isTunnelNameTaken, uiState, viewModel)
|
||||
InterfaceSection(uiState, viewModel)
|
||||
PeersSection(uiState, viewModel)
|
||||
AddPeerButton(viewModel)
|
||||
}
|
||||
|
||||
+48
-1
@@ -1,20 +1,32 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.main.config
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.TunnelRepository
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.state.ConfigUiState
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.ConfigProxy
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.InterfaceProxy
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.PeerProxy
|
||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@HiltViewModel
|
||||
class ConfigViewModel @Inject constructor() : ViewModel() {
|
||||
class ConfigViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
private val tunnelRepository: TunnelRepository,
|
||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _uiState = MutableStateFlow(ConfigUiState())
|
||||
val uiState: StateFlow<ConfigUiState> = _uiState.asStateFlow()
|
||||
@@ -109,6 +121,41 @@ class ConfigViewModel @Inject constructor() : ViewModel() {
|
||||
updatePeer(index, updated)
|
||||
}
|
||||
|
||||
fun setMessage(message: StringValue?) {
|
||||
_uiState.update { it.copy(message = message) }
|
||||
}
|
||||
|
||||
// TODO improve error messaging
|
||||
fun save(tunnelConf: TunnelConf?) =
|
||||
viewModelScope.launch(ioDispatcher) {
|
||||
val message =
|
||||
try {
|
||||
val saveConfig = buildTunnelConfFromState(tunnelConf)
|
||||
tunnelRepository.save(saveConfig)
|
||||
_uiState.update { it.copy(success = true) }
|
||||
} catch (e: Exception) {
|
||||
setMessage(
|
||||
e.message?.let { message -> (StringValue.DynamicString(message)) }
|
||||
?: StringValue.StringResource(R.string.unknown_error)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildTunnelConfFromState(tunnelConf: TunnelConf?): TunnelConf {
|
||||
val (wg, am) = _uiState.value.configProxy.buildConfigs()
|
||||
val name = _uiState.value.tunnelName
|
||||
return tunnelConf?.copyWithCallback(
|
||||
tunName = name,
|
||||
amQuick = am.toAwgQuickString(true),
|
||||
wgQuick = wg.toWgQuickString(true),
|
||||
)
|
||||
?: TunnelConf(
|
||||
tunName = name,
|
||||
amQuick = am.toAwgQuickString(true),
|
||||
wgQuick = wg.toWgQuickString(true),
|
||||
)
|
||||
}
|
||||
|
||||
fun onAuthenticated() {
|
||||
_uiState.update { it.copy(isAuthenticated = true) }
|
||||
}
|
||||
|
||||
+1
-6
@@ -17,11 +17,7 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.ConfigViewMode
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.config.state.ConfigUiState
|
||||
|
||||
@Composable
|
||||
fun InterfaceSection(
|
||||
isTunnelNameTaken: Boolean,
|
||||
uiState: ConfigUiState,
|
||||
viewModel: ConfigViewModel,
|
||||
) {
|
||||
fun InterfaceSection(uiState: ConfigUiState, viewModel: ConfigViewModel) {
|
||||
var isDropDownExpanded by remember { mutableStateOf(false) }
|
||||
val isAmneziaCompatibilitySet =
|
||||
remember(uiState.configProxy.`interface`) {
|
||||
@@ -54,7 +50,6 @@ fun InterfaceSection(
|
||||
value = uiState.tunnelName,
|
||||
onValueChange = viewModel::updateTunnelName,
|
||||
label = stringResource(R.string.name),
|
||||
isError = isTunnelNameTaken,
|
||||
hint = stringResource(R.string.tunnel_name).lowercase(),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
+3
-1
@@ -3,6 +3,7 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.main.config.state
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.ConfigProxy
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.InterfaceProxy
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.PeerProxy
|
||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||
|
||||
data class ConfigUiState(
|
||||
val tunnelName: String = "",
|
||||
@@ -12,5 +13,6 @@ data class ConfigUiState(
|
||||
val showScripts: Boolean = false,
|
||||
val isAuthenticated: Boolean = true,
|
||||
val showAuthPrompt: Boolean = false,
|
||||
val saveChanges: Boolean = false,
|
||||
val message: StringValue? = null,
|
||||
val success: Boolean? = null,
|
||||
)
|
||||
|
||||
+1
-9
@@ -22,24 +22,16 @@ import com.zaneschepke.wireguardautotunnel.ui.navigation.LocalNavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.autotunnel.components.AdvancedSettingsItem
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.*
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.AppUiState
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.AppViewState
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||
|
||||
@Composable
|
||||
fun SettingsScreen(uiState: AppUiState, appViewState: AppViewState, viewModel: AppViewModel) {
|
||||
fun SettingsScreen(uiState: AppUiState, viewModel: AppViewModel) {
|
||||
val isTv = LocalIsAndroidTV.current
|
||||
val focusManager = LocalFocusManager.current
|
||||
val navController = LocalNavController.current
|
||||
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
when (appViewState.bottomSheet) {
|
||||
AppViewState.BottomSheet.BACKUP_AND_RESTORE -> {
|
||||
SettingsBottomSheet(viewModel)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
||||
|
||||
-78
@@ -1,78 +0,0 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Restore
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.zaneschepke.wireguardautotunnel.MainActivity
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.ui.state.AppViewState
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.AppViewModel
|
||||
import com.zaneschepke.wireguardautotunnel.viewmodel.event.AppEvent
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingsBottomSheet(viewModel: AppViewModel) {
|
||||
val context = LocalContext.current
|
||||
|
||||
ModalBottomSheet(
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
onDismissRequest = {
|
||||
viewModel.handleEvent(AppEvent.SetBottomSheet(AppViewState.BottomSheet.NONE))
|
||||
},
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.clickable {
|
||||
viewModel.handleEvent(
|
||||
AppEvent.SetBottomSheet(AppViewState.BottomSheet.NONE)
|
||||
)
|
||||
(context as? MainActivity)?.performBackup()
|
||||
}
|
||||
.padding(10.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.database),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(10.dp),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.backup_application),
|
||||
modifier = Modifier.padding(10.dp),
|
||||
)
|
||||
}
|
||||
HorizontalDivider()
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.clickable {
|
||||
viewModel.handleEvent(
|
||||
AppEvent.SetBottomSheet(AppViewState.BottomSheet.NONE)
|
||||
)
|
||||
(context as? MainActivity)?.performRestore()
|
||||
}
|
||||
.padding(10.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Restore,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(10.dp),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.restore_application),
|
||||
modifier = Modifier.padding(10.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ data class AppViewState(
|
||||
}
|
||||
|
||||
enum class BottomSheet {
|
||||
BACKUP_AND_RESTORE,
|
||||
EXPORT_TUNNELS,
|
||||
IMPORT_TUNNELS,
|
||||
LOGS,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.state
|
||||
|
||||
import com.zaneschepke.wireguardautotunnel.domain.model.TunnelConf
|
||||
import org.amnezia.awg.config.Config
|
||||
|
||||
data class ConfigProxy(val peers: List<PeerProxy>, val `interface`: InterfaceProxy) {
|
||||
@@ -29,20 +28,6 @@ data class ConfigProxy(val peers: List<PeerProxy>, val `interface`: InterfacePro
|
||||
)
|
||||
}
|
||||
|
||||
fun buildTunnelConfFromState(name: String, tunnelConf: TunnelConf?): TunnelConf {
|
||||
val (wg, am) = buildConfigs()
|
||||
return tunnelConf?.copyWithCallback(
|
||||
tunName = name,
|
||||
amQuick = am.toAwgQuickString(true),
|
||||
wgQuick = wg.toWgQuickString(true),
|
||||
)
|
||||
?: TunnelConf(
|
||||
tunName = name,
|
||||
amQuick = am.toAwgQuickString(true),
|
||||
wgQuick = wg.toWgQuickString(true),
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun from(amConfig: Config): ConfigProxy {
|
||||
return ConfigProxy(
|
||||
|
||||
+23
-11
@@ -8,6 +8,7 @@ import android.content.Context.POWER_SERVICE
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.location.LocationManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
@@ -15,15 +16,17 @@ import android.provider.Settings
|
||||
import android.service.quicksettings.TileService
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.location.LocationManagerCompat
|
||||
import androidx.core.net.toUri
|
||||
import com.zaneschepke.wireguardautotunnel.MainActivity
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.tile.AutoTunnelControlTile
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.tile.TunnelControlTile
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import kotlin.system.exitProcess
|
||||
import timber.log.Timber
|
||||
|
||||
fun Context.openWebUrl(url: String): Result<Unit> {
|
||||
@@ -110,12 +113,13 @@ fun Context.launchShareFile(file: Uri) {
|
||||
this.startActivity(chooserIntent)
|
||||
}
|
||||
|
||||
fun Context.showToast(resId: Int) {
|
||||
Toast.makeText(this, this.getString(resId), Toast.LENGTH_LONG).show()
|
||||
fun Context.isLocationServicesEnabled(): Boolean {
|
||||
val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
return LocationManagerCompat.isLocationEnabled(locationManager)
|
||||
}
|
||||
|
||||
fun Context.showToast(message: String) {
|
||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
|
||||
fun Context.showToast(resId: Int) {
|
||||
Toast.makeText(this, this.getString(resId), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
fun Context.launchSupportEmail() {
|
||||
@@ -235,9 +239,17 @@ fun Activity.setScreenBrightness(brightness: Float) {
|
||||
window.attributes = window.attributes.apply { screenBrightness = brightness }
|
||||
}
|
||||
|
||||
fun MainActivity.restartApp() {
|
||||
Intent(this, MainActivity::class.java).also {
|
||||
startActivity(it)
|
||||
exitProcess(0)
|
||||
}
|
||||
fun Activity.enableImmersiveMode() {
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
val controller = WindowCompat.getInsetsController(window, window.decorView)
|
||||
controller.systemBarsBehavior =
|
||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
controller.hide(WindowInsetsCompat.Type.systemBars())
|
||||
}
|
||||
|
||||
fun Activity.disableImmersiveMode() {
|
||||
WindowCompat.setDecorFitsSystemWindows(window, true)
|
||||
val controller = WindowCompat.getInsetsController(window, window.decorView)
|
||||
controller.show(WindowInsetsCompat.Type.systemBars())
|
||||
window.statusBarColor = android.graphics.Color.TRANSPARENT
|
||||
}
|
||||
|
||||
+5
-14
@@ -2,7 +2,7 @@ package com.zaneschepke.wireguardautotunnel.util.extensions
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.wireguard.android.backend.BackendException
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.HandshakeStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.events.BackendError
|
||||
@@ -75,21 +75,12 @@ fun Config.defaultName(): String {
|
||||
}
|
||||
}
|
||||
|
||||
fun Backend.BackendStatus.asBackendStatus(): BackendStatus {
|
||||
return when (val status = this) {
|
||||
is Backend.BackendStatus.KillSwitchActive ->
|
||||
BackendStatus.KillSwitch(status.allowedIps.toList())
|
||||
is Backend.BackendStatus.ServiceActive -> BackendStatus.Active
|
||||
else -> BackendStatus.Inactive
|
||||
}
|
||||
fun Backend.BackendState.asBackendState(): BackendState {
|
||||
return BackendState.valueOf(this.name)
|
||||
}
|
||||
|
||||
fun BackendStatus.asAmBackendStatus(): Backend.BackendStatus {
|
||||
return when (val status = this) {
|
||||
is BackendStatus.Active -> Backend.BackendStatus.ServiceActive.INSTANCE
|
||||
is BackendStatus.Inactive -> Backend.BackendStatus.Inactive.INSTANCE
|
||||
is BackendStatus.KillSwitch -> Backend.BackendStatus.KillSwitchActive(status.allowedIps)
|
||||
}
|
||||
fun BackendState.asAmBackendState(): Backend.BackendState {
|
||||
return Backend.BackendState.valueOf(this.name)
|
||||
}
|
||||
|
||||
fun Tunnel.State.asTunnelState(): TunnelStatus {
|
||||
|
||||
@@ -19,7 +19,7 @@ import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelManager
|
||||
import com.zaneschepke.wireguardautotunnel.di.AppShell
|
||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.di.MainDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.ConfigType
|
||||
import com.zaneschepke.wireguardautotunnel.domain.events.BackendError
|
||||
import com.zaneschepke.wireguardautotunnel.domain.model.AppSettings
|
||||
@@ -42,7 +42,7 @@ import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
@@ -70,7 +70,12 @@ constructor(
|
||||
|
||||
private var logsJob: Job? = null
|
||||
|
||||
private val _eventChannel = Channel<AppEvent>(Channel.BUFFERED)
|
||||
private val _eventFlow =
|
||||
MutableSharedFlow<AppEvent>(
|
||||
replay = 0,
|
||||
extraBufferCapacity = 10,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST,
|
||||
)
|
||||
|
||||
private val tunnelMutex = Mutex()
|
||||
private val settingsMutex = Mutex()
|
||||
@@ -134,7 +139,7 @@ constructor(
|
||||
if (state.appState.isLocalLogsEnabled) logsJob = startCollectingLogs()
|
||||
handleTunnelMessages()
|
||||
}
|
||||
for (event in _eventChannel) {
|
||||
_eventFlow.collect { event ->
|
||||
val state = uiState.value
|
||||
when (event) {
|
||||
AppEvent.ToggleLocalLogging ->
|
||||
@@ -148,7 +153,8 @@ constructor(
|
||||
is AppEvent.ImportTunnelFromClipboard ->
|
||||
handleClipboardImport(event.text, state.tunnels)
|
||||
|
||||
is AppEvent.ImportTunnelFromFile -> handleImportTunnelFromFile(event.data)
|
||||
is AppEvent.ImportTunnelFromFile ->
|
||||
handleImportTunnelFromFile(event.data, state.tunnels)
|
||||
|
||||
is AppEvent.ImportTunnelFromUrl ->
|
||||
handleImportTunnelFromUrl(event.url, state.tunnels)
|
||||
@@ -241,7 +247,6 @@ constructor(
|
||||
is AppEvent.SaveAllConfigs -> saveAllTunnels(event.tunnels)
|
||||
AppEvent.ToggleShowDetailedPingStats ->
|
||||
handleToggleShowDetailedPingStats(state.appState)
|
||||
|
||||
is AppEvent.SaveMonitoringSettings ->
|
||||
handleMonitoringSaveChanges(
|
||||
state.appSettings,
|
||||
@@ -253,60 +258,24 @@ constructor(
|
||||
AppEvent.TogglePingMonitoring -> handleTogglePingMonitoring(state.appSettings)
|
||||
is AppEvent.SetPingAttempts ->
|
||||
saveSettings(state.appSettings.copy(tunnelPingAttempts = event.count))
|
||||
|
||||
is AppEvent.SetPingInterval ->
|
||||
saveSettings(
|
||||
state.appSettings.copy(tunnelPingIntervalSeconds = event.interval)
|
||||
)
|
||||
|
||||
is AppEvent.SetPingTimeout ->
|
||||
saveSettings(
|
||||
state.appSettings.copy(tunnelPingTimeoutSeconds = event.timeout)
|
||||
)
|
||||
is AppEvent.SaveTunnelUniquely -> saveTunnelsUniquely(listOf(event.tunnel))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun saveTunnelsUniquely(tunnels: List<TunnelConf>) {
|
||||
withContext(ioDispatcher) {
|
||||
tunnelMutex.withLock {
|
||||
val existingTunnels = appDataRepository.tunnels.getAll()
|
||||
val uniqueTuns = generateUniquelyNamedConfigs(tunnels, existingTunnels)
|
||||
appDataRepository.tunnels.saveAll(uniqueTuns)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateUniquelyNamedConfigs(
|
||||
incoming: List<TunnelConf>,
|
||||
existing: List<TunnelConf>,
|
||||
): List<TunnelConf> {
|
||||
val usedNames = existing.map { it.tunName }.toMutableSet()
|
||||
val result = mutableListOf<TunnelConf>()
|
||||
|
||||
for (tun in incoming) {
|
||||
var uniqueName = tun.tunName
|
||||
var counter = 1
|
||||
|
||||
while (uniqueName in usedNames) {
|
||||
uniqueName = "${tun.tunName} ($counter)"
|
||||
counter++
|
||||
}
|
||||
|
||||
usedNames.add(uniqueName)
|
||||
result.add(tun.copy(tunName = uniqueName))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun handleUiEvent(event: UiEvent): Job =
|
||||
viewModelScope.launch(mainDispatcher) { _uiEvent.emit(event) }
|
||||
|
||||
fun handleEvent(event: AppEvent) {
|
||||
_eventChannel.trySend(event)
|
||||
_eventFlow.tryEmit(event)
|
||||
}
|
||||
|
||||
private suspend fun handleTogglePingMonitoring(appSettings: AppSettings) {
|
||||
@@ -452,7 +421,7 @@ constructor(
|
||||
_appViewState.update { it.copy(bottomSheet = bottomSheet) }
|
||||
|
||||
private suspend fun handleTunnelPingTargetChange(tunnelConf: TunnelConf, target: String) =
|
||||
saveTunnel(tunnelConf.copy(pingTarget = target.ifBlank { null }))
|
||||
saveTunnel(tunnelConf.copy(pingTarget = target))
|
||||
|
||||
private suspend fun handleTogglePingTunnel(tunnel: TunnelConf) =
|
||||
saveTunnel(tunnel.copy(restartOnPingFailure = !tunnel.restartOnPingFailure))
|
||||
@@ -548,10 +517,17 @@ constructor(
|
||||
_appViewState.update { it.copy(popBackStack = true) }
|
||||
}
|
||||
|
||||
private suspend fun handleImportTunnelFromFile(uri: Uri) {
|
||||
private suspend fun handleImportTunnelFromFile(uri: Uri, tunnels: List<TunnelConf>) {
|
||||
runCatching {
|
||||
val tunnelConfigs = fileUtils.buildTunnelsFromUri(uri)
|
||||
saveTunnelsUniquely(tunnelConfigs)
|
||||
val existingNames = tunnels.map { it.tunName }.toMutableList()
|
||||
val uniqueTunnelConfigs =
|
||||
tunnelConfigs.map { config ->
|
||||
val uniqueName = config.generateUniqueName(existingNames)
|
||||
existingNames.add(uniqueName)
|
||||
config.copy(tunName = uniqueName)
|
||||
}
|
||||
appDataRepository.tunnels.saveAll(uniqueTunnelConfigs)
|
||||
}
|
||||
.onFailure {
|
||||
when (it) {
|
||||
@@ -570,7 +546,11 @@ constructor(
|
||||
runCatching {
|
||||
val amConfig = TunnelConf.configFromAmQuick(config)
|
||||
val tunnelConf = TunnelConf.tunnelConfigFromAmConfig(amConfig)
|
||||
saveTunnelsUniquely(listOf(tunnelConf))
|
||||
saveTunnel(
|
||||
tunnelConf.copy(
|
||||
tunName = tunnelConf.generateUniqueName(tunnels.map { it.tunName })
|
||||
)
|
||||
)
|
||||
}
|
||||
.onFailure {
|
||||
Timber.e(it)
|
||||
@@ -588,7 +568,11 @@ constructor(
|
||||
url.openStream().use { stream ->
|
||||
val amConfig = Config.parse(stream)
|
||||
val tunnelConf = TunnelConf.tunnelConfigFromAmConfig(amConfig)
|
||||
saveTunnelsUniquely(listOf(tunnelConf))
|
||||
saveTunnel(
|
||||
tunnelConf.copy(
|
||||
tunName = tunnelConf.generateUniqueName(tunnels.map { it.tunName })
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
.onFailure {
|
||||
@@ -666,19 +650,19 @@ constructor(
|
||||
val updatedSettings =
|
||||
appSettings.copy(isLanOnKillSwitchEnabled = !appSettings.isLanOnKillSwitchEnabled)
|
||||
saveSettings(updatedSettings)
|
||||
handleKillSwitchChange(updatedSettings)
|
||||
handleKillSwitchChange(appSettings)
|
||||
}
|
||||
|
||||
private fun handleKillSwitchChange(appSettings: AppSettings) {
|
||||
// let auto tunnel handle kill switch changes if running
|
||||
if (uiState.value.isAutoTunnelActive) return
|
||||
if (!appSettings.isVpnKillSwitchEnabled)
|
||||
return tunnelManager.setBackendStatus(BackendStatus.Active)
|
||||
return tunnelManager.setBackendState(BackendState.SERVICE_ACTIVE, emptyList())
|
||||
Timber.d("Starting kill switch")
|
||||
val allowedIps =
|
||||
if (appSettings.isLanOnKillSwitchEnabled) TunnelConf.LAN_BYPASS_ALLOWED_IPS
|
||||
else emptyList()
|
||||
tunnelManager.setBackendStatus(BackendStatus.KillSwitch(allowedIps))
|
||||
tunnelManager.setBackendState(BackendState.KILL_SWITCH_ACTIVE, allowedIps)
|
||||
}
|
||||
|
||||
private suspend fun handleToggleAppShortcuts(appSettings: AppSettings) {
|
||||
@@ -713,7 +697,7 @@ constructor(
|
||||
}
|
||||
if (enabled && !requestRoot()) return
|
||||
// disable kill switch feature in kernel mode
|
||||
tunnelManager.setBackendStatus(BackendStatus.Inactive)
|
||||
tunnelManager.setBackendState(BackendState.INACTIVE, emptyList())
|
||||
saveSettings(
|
||||
appSettings.copy(
|
||||
isKernelEnabled = enabled,
|
||||
|
||||
@@ -77,8 +77,6 @@ sealed class AppEvent {
|
||||
|
||||
data class SetTheme(val theme: Theme) : AppEvent()
|
||||
|
||||
data class SaveTunnelUniquely(val tunnel: TunnelConf) : AppEvent()
|
||||
|
||||
data class SaveMonitoringSettings(
|
||||
val pingInterval: Int,
|
||||
val tunnelPingAttempts: Int,
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:pathData="M480,840q-151,0 -255.5,-46.5T120,680v-400q0,-66 105.5,-113T480,120q149,0 254.5,47T840,280v400q0,67 -104.5,113.5T480,840ZM480,361q89,0 179,-25.5T760,281q-11,-29 -100.5,-55T480,200q-91,0 -178.5,25.5T200,281q14,30 101.5,55T480,361ZM480,560q42,0 81,-4t74.5,-11.5q35.5,-7.5 67,-18.5t57.5,-25v-120q-26,14 -57.5,25t-67,18.5Q600,432 561,436t-81,4q-42,0 -82,-4t-75.5,-11.5Q287,417 256,406t-56,-25v120q25,14 56,25t66.5,18.5Q358,552 398,556t82,4ZM480,760q46,0 93.5,-7t87.5,-18.5q40,-11.5 67,-26t32,-29.5v-98q-26,14 -57.5,25t-67,18.5Q600,632 561,636t-81,4q-42,0 -82,-4t-75.5,-11.5Q287,617 256,606t-56,-25v99q5,15 31.5,29t66.5,25.5q40,11.5 88,18.5t94,7Z"
|
||||
android:fillColor="#e3e3e3"/>
|
||||
</vector>
|
||||
@@ -327,11 +327,4 @@
|
||||
<string name="ping_target_template">Ping target: %1$s</string>
|
||||
<string name="_true">True</string>
|
||||
<string name="_false">False</string>
|
||||
<string name="backup_success">Backup success. %1$s</string>
|
||||
<string name="restore_success">Restore success. %1$s</string>
|
||||
<string name="restarting_app">Restarting app to apply changes…</string>
|
||||
<string name="restore_failed">Failed to restore from backup.</string>
|
||||
<string name="backup_failed">Failed to create backup.</string>
|
||||
<string name="backup_application">Backup application data</string>
|
||||
<string name="restore_application">Restore from backup</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
object Constants {
|
||||
const val VERSION_NAME = "3.9.4"
|
||||
const val JVM_TARGET = "17"
|
||||
const val VERSION_CODE = 39400
|
||||
const val TARGET_SDK = 35
|
||||
const val MIN_SDK = 26
|
||||
@@ -9,4 +10,8 @@ object Constants {
|
||||
// build types
|
||||
const val RELEASE = "release"
|
||||
const val NIGHTLY = "nightly"
|
||||
|
||||
val allowedLicenses = listOf("MIT", "Apache-2.0", "BSD-3-Clause")
|
||||
val allowedLicenseUrls = listOf("https://github.com/journeyapps/zxing-android-embedded/blob/master/COPYING",
|
||||
"https://github.com/RikkaApps/Shizuku-API/blob/master/LICENSE")
|
||||
}
|
||||
|
||||
@@ -15,15 +15,6 @@ fun Project.languageList(): List<String> {
|
||||
.toList() + "en"
|
||||
}
|
||||
|
||||
fun allowedLicenses(): List<String> {
|
||||
return listOf("MIT", "Apache-2.0", "BSD-3-Clause")
|
||||
}
|
||||
|
||||
fun allowedLicenseUrls(): List<String> {
|
||||
return listOf("https://github.com/journeyapps/zxing-android-embedded/blob/master/COPYING",
|
||||
"https://github.com/RikkaApps/Shizuku-API/blob/master/LICENSE", "https://github.com/rafi0101/Android-Room-Database-Backup/blob/master/LICENSE",
|
||||
"https://opensource.org/license/mit")
|
||||
}
|
||||
|
||||
fun buildLanguagesArray(languages: List<String>): String {
|
||||
return languages.joinToString(separator = ", ") { "\"$it\"" }
|
||||
|
||||
+14
-16
@@ -1,39 +1,38 @@
|
||||
[versions]
|
||||
accompanist = "0.37.3"
|
||||
activityCompose = "1.10.1"
|
||||
amneziawgAndroid = "1.6.1"
|
||||
androidx-junit = "1.3.0"
|
||||
amneziawgAndroid = "1.5.0"
|
||||
androidx-junit = "1.2.1"
|
||||
icmp4a = "1.0.0"
|
||||
roomdatabasebackup = "1.1.0"
|
||||
shizuku = "13.1.5"
|
||||
appcompat = "1.7.1"
|
||||
biometricKtx = "1.2.0-alpha05"
|
||||
coreKtx = "1.16.0"
|
||||
datastorePreferences = "1.2.0-alpha02"
|
||||
desugar_jdk_libs = "2.1.5"
|
||||
espressoCore = "3.7.0"
|
||||
hiltAndroid = "2.57"
|
||||
espressoCore = "3.6.1"
|
||||
hiltAndroid = "2.56.2"
|
||||
hiltCompiler = "1.2.0"
|
||||
junit = "4.13.2"
|
||||
kotlinx-serialization-json = "1.9.0"
|
||||
ktorClientCore = "3.2.3"
|
||||
kotlinx-serialization-json = "1.8.1"
|
||||
ktorClientCore = "3.1.3"
|
||||
lifecycle-runtime-compose = "2.9.2"
|
||||
material3 = "1.3.2"
|
||||
navigationCompose = "2.9.3"
|
||||
navigationCompose = "2.9.0"
|
||||
pinLockCompose = "1.0.4"
|
||||
qrose = "1.0.1"
|
||||
roomVersion = "2.7.2"
|
||||
roomVersion = "2.7.1"
|
||||
semver4j = "3.1.0"
|
||||
slf4jAndroid = "1.7.36"
|
||||
timber = "5.0.1"
|
||||
tunnel = "1.4.0"
|
||||
androidGradlePlugin = "8.12.0"
|
||||
androidGradlePlugin = "8.10.1"
|
||||
kotlin = "2.2.0"
|
||||
ksp = "2.2.0-2.0.2"
|
||||
composeBom = "2025.07.00"
|
||||
compose = "1.8.3"
|
||||
composeBom = "2025.06.00"
|
||||
compose = "1.8.2"
|
||||
icons = "1.7.8"
|
||||
workRuntimeKtxVersion = "2.10.3"
|
||||
workRuntimeKtxVersion = "2.10.1"
|
||||
zxingAndroidEmbedded = "4.3.0"
|
||||
coreSplashscreen = "1.0.1"
|
||||
gradlePlugins-grgit = "5.3.2"
|
||||
@@ -41,8 +40,8 @@ reorderable = "2.5.1"
|
||||
|
||||
#plugins
|
||||
material = "1.12.0"
|
||||
storage = "1.6.0"
|
||||
ktfmt = "0.23.0"
|
||||
storage = "1.5.0"
|
||||
ktfmt = "0.22.0"
|
||||
licensee = "1.13.0"
|
||||
|
||||
|
||||
@@ -105,7 +104,6 @@ material-icons-extended = { module = "androidx.compose.material:material-icons-e
|
||||
|
||||
# util
|
||||
pin-lock-compose = { module = "com.zaneschepke:pin_lock_compose", version.ref = "pinLockCompose" }
|
||||
roomdatabasebackup = { module = "de.raphaelebner:roomdatabasebackup", version.ref = "roomdatabasebackup" }
|
||||
shizuku-api = { module = "dev.rikka.shizuku:api", version.ref = "shizuku" }
|
||||
shizuku-provider = { module = "dev.rikka.shizuku:provider", version.ref = "shizuku" }
|
||||
qrose = { module = "io.github.alexzhirkevich:qrose", version.ref = "qrose" }
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
#Wed Oct 11 22:39:21 EDT 2023
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||
distributionSha256Sum=f397b287023acdba1e9f6fc5ea72d22dd63669d59ed4a289a29b1a76eee151c6
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
Reference in New Issue
Block a user