diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6e168db..a159f4c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,7 +14,7 @@ val versionCodeIncrement = with(getBuildTaskName().lowercase()) { when { this.contains(Constants.NIGHTLY) || this.contains(Constants.PRERELEASE) -> { if (versionFile.exists()) { - versionFile.readText().toInt() + 1 + versionFile.readText().trim().toInt() + 1 } else { 1 } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt index c2ea8a1..911981c 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/AppViewModel.kt @@ -202,18 +202,14 @@ constructor( private suspend fun handleVpnKillSwitchChange(enabled: Boolean) { withContext(ioDispatcher) { - if (enabled) { - Timber.d("Starting kill switch") - val allowedIps = if (appDataRepository.settings.getSettings().isLanOnKillSwitchEnabled) { - TunnelConfig.IPV4_PUBLIC_NETWORKS - } else { - emptySet() - } - tunnelService.get().setBackendState(BackendState.KILL_SWITCH_ACTIVE, allowedIps) + if (!enabled) return@withContext tunnelService.get().setBackendState(BackendState.SERVICE_ACTIVE, emptySet()) + Timber.d("Starting kill switch") + val allowedIps = if (appDataRepository.settings.getSettings().isLanOnKillSwitchEnabled) { + TunnelConfig.IPV4_PUBLIC_NETWORKS } else { - Timber.d("Sending shutdown of kill switch") - tunnelService.get().setBackendState(BackendState.SERVICE_ACTIVE, emptySet()) + emptySet() } + tunnelService.get().setBackendState(BackendState.KILL_SWITCH_ACTIVE, allowedIps) } } @@ -297,25 +293,67 @@ constructor( } } - fun saveConfigChanges(config: TunnelConfig, peers: List? = null, `interface`: InterfaceProxy? = null) = viewModelScope.launch( - ioDispatcher, - ) { + fun updateExistingTunnelConfig( + tunnelConfig: TunnelConfig, + tunnelName: String? = null, + peers: List? = null, + `interface`: InterfaceProxy? = null, + ) = viewModelScope.launch { runCatching { - val amConfig = config.toAmConfig() - val wgConfig = config.toWgConfig() - rebuildConfigsAndSave(config, amConfig, wgConfig, peers, `interface`) + val amConfig = tunnelConfig.toAmConfig() + val wgConfig = tunnelConfig.toWgConfig() + updateTunnelConfig(tunnelConfig, tunnelName, amConfig, wgConfig, peers, `interface`) _popBackStack.emit(true) SnackbarController.showMessage(StringValue.StringResource(R.string.config_changes_saved)) }.onFailure { - Timber.e(it) - SnackbarController.showMessage( - it.message?.let { message -> - (StringValue.DynamicString(message)) - } ?: StringValue.StringResource(R.string.unknown_error), - ) + onConfigSaveError(it) } } + fun saveNewTunnel(tunnelName: String, peers: List, `interface`: InterfaceProxy) = viewModelScope.launch { + runCatching { + val config = buildConfigs(peers, `interface`) + appDataRepository.tunnels.save( + TunnelConfig( + name = tunnelName, + wgQuick = config.first.toWgQuickString(true), + amQuick = config.second.toAwgQuickString(true), + ), + ) + _popBackStack.emit(true) + SnackbarController.showMessage(StringValue.StringResource(R.string.config_changes_saved)) + }.onFailure { + onConfigSaveError(it) + } + } + + private fun onConfigSaveError(throwable: Throwable) { + Timber.e(throwable) + SnackbarController.showMessage( + throwable.message?.let { message -> + (StringValue.DynamicString(message)) + } ?: StringValue.StringResource(R.string.unknown_error), + ) + } + + private suspend fun updateTunnelConfig( + tunnelConfig: TunnelConfig, + tunnelName: String? = null, + amConfig: org.amnezia.awg.config.Config, + wgConfig: Config, + peers: List? = null, + `interface`: InterfaceProxy? = null, + ) { + val configs = rebuildConfigs(amConfig, wgConfig, peers, `interface`) + appDataRepository.tunnels.save( + tunnelConfig.copy( + name = tunnelName ?: tunnelConfig.name, + amQuick = configs.second.toAwgQuickString(true), + wgQuick = configs.first.toWgQuickString(true), + ), + ) + } + fun cleanUpUninstalledApps(tunnelConfig: TunnelConfig, packages: List) = viewModelScope.launch(ioDispatcher) { runCatching { val amConfig = tunnelConfig.toAmConfig() @@ -323,8 +361,8 @@ constructor( val proxy = InterfaceProxy.from(amConfig.`interface`) if (proxy.includedApplications.isEmpty() && proxy.excludedApplications.isEmpty()) return@launch if (proxy.includedApplications.retainAll(packages.toSet()) || proxy.excludedApplications.retainAll(packages.toSet())) { - Timber.i("Removing split tunnel package for app that no longer exists on the device") - rebuildConfigsAndSave(tunnelConfig, amConfig, wgConfig, `interface` = proxy) + updateTunnelConfig(tunnelConfig, amConfig = amConfig, wgConfig = wgConfig, `interface` = proxy) + Timber.i("Removed split tunnel package for app that no longer exists on the device") } }.onFailure { Timber.e(it) @@ -340,24 +378,38 @@ constructor( ) } - private suspend fun rebuildConfigsAndSave( - config: TunnelConfig, + private suspend fun rebuildConfigs( amConfig: org.amnezia.awg.config.Config, wgConfig: Config, peers: List? = null, `interface`: InterfaceProxy? = null, - ) { - appDataRepository.tunnels.save( - config.copy( - wgQuick = Config.Builder().apply { + ): Pair { + return withContext(ioDispatcher) { + Pair( + Config.Builder().apply { addPeers(peers?.map { it.toWgPeer() } ?: wgConfig.peers) setInterface(`interface`?.toWgInterface() ?: wgConfig.`interface`) - }.build().toWgQuickString(true), - amQuick = org.amnezia.awg.config.Config.Builder().apply { + }.build(), + org.amnezia.awg.config.Config.Builder().apply { addPeers(peers?.map { it.toAmPeer() } ?: amConfig.peers) setInterface(`interface`?.toAmInterface() ?: amConfig.`interface`) - }.build().toAwgQuickString(true), - ), - ) + }.build(), + ) + } + } + + private suspend fun buildConfigs(peers: List, `interface`: InterfaceProxy): Pair { + return withContext(ioDispatcher) { + Pair( + Config.Builder().apply { + addPeers(peers.map { it.toWgPeer() }) + setInterface(`interface`.toWgInterface()) + }.build(), + org.amnezia.awg.config.Config.Builder().apply { + addPeers(peers.map { it.toAmPeer() }) + setInterface(`interface`.toAmInterface()) + }.build(), + ) + } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunneloptions/TunnelOptionsScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunneloptions/TunnelOptionsScreen.kt index 0c42e6d..d2520af 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunneloptions/TunnelOptionsScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunneloptions/TunnelOptionsScreen.kt @@ -155,7 +155,7 @@ fun OptionsScreen(appViewModel: AppViewModel, appUiState: AppUiState, tunnelId: val amneziaClick = { val proxy = InterfaceProxy.from(amConfig.`interface`) val `interface` = if (!isAmneziaCompatibilityEnabled) proxy.toAmneziaCompatibilityConfig() else proxy.resetAmneziaProperties() - appViewModel.saveConfigChanges(config, `interface` = `interface`) + appViewModel.updateExistingTunnelConfig(config, `interface` = `interface`) } GroupLabel(stringResource(R.string.quick_actions)) SurfaceSelectionGroupButton( diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunneloptions/config/ConfigScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunneloptions/config/ConfigScreen.kt index 0683a8f..fb4e093 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunneloptions/config/ConfigScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunneloptions/config/ConfigScreen.kt @@ -83,13 +83,13 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I val tunnelConfig by remember { derivedStateOf { - appUiState.tunnels.first { it.id == tunnelId } + appUiState.tunnels.firstOrNull { it.id == tunnelId } } } val configPair by remember { derivedStateOf { - Pair(tunnelConfig.name, tunnelConfig.toAmConfig()) + Pair(tunnelConfig?.name ?: "", tunnelConfig?.toAmConfig()) } } @@ -98,11 +98,11 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I } var interfaceState by remember { - mutableStateOf(InterfaceProxy.from(configPair.second.`interface`)) + mutableStateOf(configPair.second?.let { InterfaceProxy.from(it.`interface`) } ?: InterfaceProxy()) } var showAmneziaValues by remember { - mutableStateOf(configPair.second.`interface`.junkPacketCount.isPresent) + mutableStateOf(configPair.second?.`interface`?.junkPacketCount?.isPresent == true) } var showScripts by remember { @@ -110,7 +110,7 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I } val peersState = remember { - configPair.second.peers.map { PeerProxy.from(it) }.toMutableStateList() + (configPair.second?.peers?.map { PeerProxy.from(it) } ?: listOf(PeerProxy())).toMutableStateList() } var showAuthPrompt by remember { mutableStateOf(false) } @@ -148,13 +148,14 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I topBar = { TopNavBar(stringResource(R.string.edit_tunnel), trailing = { IconButton(onClick = { - appViewModel.saveConfigChanges( - tunnelConfig.copy( - name = tunnelName, - ), - peers = peersState, - `interface` = interfaceState, - ) + tunnelConfig?.let { + appViewModel.updateExistingTunnelConfig( + it, + tunnelName, + peersState, + interfaceState, + ) + } ?: appViewModel.saveNewTunnel(tunnelName, peersState, interfaceState) }) { val icon = Icons.Outlined.Save Icon( @@ -226,6 +227,7 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I Modifier .fillMaxWidth(), ) + val privateKeyEnabled = (tunnelId == Constants.MANUAL_TUNNEL_CONFIG_ID) || isAuthenticated OutlinedTextField( textStyle = MaterialTheme.typography.labelLarge, modifier = @@ -234,16 +236,16 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I .clickable { showAuthPrompt = true }, value = interfaceState.privateKey, visualTransformation = - if ((tunnelId == Constants.MANUAL_TUNNEL_CONFIG_ID) || isAuthenticated) { + if (privateKeyEnabled) { VisualTransformation.None } else { PasswordVisualTransformation() }, - enabled = (tunnelId == Constants.MANUAL_TUNNEL_CONFIG_ID) || isAuthenticated, + enabled = privateKeyEnabled, onValueChange = { interfaceState = interfaceState.copy(privateKey = it) }, trailingIcon = { IconButton( - enabled = isAuthenticated, + enabled = privateKeyEnabled, modifier = Modifier.focusRequester(FocusRequester.Default), onClick = { val keypair = KeyPair() @@ -256,7 +258,7 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I Icon( Icons.Rounded.Refresh, stringResource(R.string.rotate_keys), - tint = if (isAuthenticated) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.outline, + tint = if (privateKeyEnabled) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.outline, ) } }, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunneloptions/splittunnel/SplitTunnelScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunneloptions/splittunnel/SplitTunnelScreen.kt index 150f5c3..cf5fc27 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunneloptions/splittunnel/SplitTunnelScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/tunneloptions/splittunnel/SplitTunnelScreen.kt @@ -127,7 +127,7 @@ fun SplitTunnelScreen(appUiState: AppUiState, tunnelId: Int, viewModel: AppViewM } SplitOptions.ALL -> Unit } - viewModel.saveConfigChanges(config, `interface` = proxyInterface) + viewModel.updateExistingTunnelConfig(config, `interface` = proxyInterface) }) { val icon = Icons.Outlined.Save Icon(