parent
4196a543b2
commit
ab858ab59e
|
@ -115,7 +115,9 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
|
||||||
val onAutoTunnelClick = {
|
val onAutoTunnelClick = {
|
||||||
if (!uiState.generalState.isLocationDisclosureShown) {
|
if (!uiState.generalState.isLocationDisclosureShown) {
|
||||||
navController.navigate(Route.LocationDisclosure)
|
navController.navigate(Route.LocationDisclosure)
|
||||||
} else navController.navigate(Route.AutoTunnel)
|
} else {
|
||||||
|
navController.navigate(Route.AutoTunnel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SurfaceSelectionGroupButton(
|
SurfaceSelectionGroupButton(
|
||||||
listOf(
|
listOf(
|
||||||
|
|
|
@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
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
|
||||||
|
@ -21,7 +22,6 @@ 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
|
||||||
import androidx.compose.material.icons.outlined.SignalCellular4Bar
|
import androidx.compose.material.icons.outlined.SignalCellular4Bar
|
||||||
import androidx.compose.material.icons.outlined.VpnKeyOff
|
|
||||||
import androidx.compose.material.icons.outlined.Wifi
|
import androidx.compose.material.icons.outlined.Wifi
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
@ -135,6 +135,7 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
|
.imePadding()
|
||||||
.padding(top = 24.dp.scaledHeight())
|
.padding(top = 24.dp.scaledHeight())
|
||||||
.padding(horizontal = 24.dp.scaledWidth()),
|
.padding(horizontal = 24.dp.scaledWidth()),
|
||||||
) {
|
) {
|
||||||
|
@ -376,7 +377,7 @@ fun AutoTunnelScreen(uiState: AppUiState, viewModel: AutoTunnelViewModel = hiltV
|
||||||
ForwardButton { navController.navigate(Route.AutoTunnelAdvanced) }
|
ForwardButton { navController.navigate(Route.AutoTunnelAdvanced) }
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,6 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.scaledWidth
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AdvancedScreen(appUiState: AppUiState, appViewModel: AppViewModel) {
|
fun AdvancedScreen(appUiState: AppUiState, appViewModel: AppViewModel) {
|
||||||
|
|
||||||
var isDropDownExpanded by remember {
|
var isDropDownExpanded by remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
|
@ -86,32 +85,36 @@ fun AdvancedScreen(appUiState: AppUiState, appViewModel: AppViewModel) {
|
||||||
trailing = {
|
trailing = {
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(5.dp, Alignment.CenterHorizontally),
|
horizontalArrangement = Arrangement.spacedBy(5.dp, Alignment.CenterHorizontally),
|
||||||
verticalAlignment = Alignment.CenterVertically) {
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
Text(text = selected.toString(), style = MaterialTheme.typography.bodyMedium)
|
Text(text = selected.toString(), style = MaterialTheme.typography.bodyMedium)
|
||||||
val icon = Icons.Default.ArrowDropDown
|
val icon = Icons.Default.ArrowDropDown
|
||||||
Icon(icon, icon.name)
|
Icon(icon, icon.name)
|
||||||
}
|
}
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
modifier = Modifier.height(140.dp.scaledHeight()),
|
modifier = Modifier.height(250.dp.scaledHeight()),
|
||||||
scrollState = rememberScrollState(),
|
scrollState = rememberScrollState(),
|
||||||
containerColor = MaterialTheme.colorScheme.surface,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
expanded = isDropDownExpanded,
|
expanded = isDropDownExpanded,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
isDropDownExpanded = false
|
isDropDownExpanded = false
|
||||||
}) {
|
},
|
||||||
|
) {
|
||||||
(0..10).forEachIndexed { index, num ->
|
(0..10).forEachIndexed { index, num ->
|
||||||
DropdownMenuItem(text = {
|
DropdownMenuItem(
|
||||||
|
text = {
|
||||||
Text(text = num.toString())
|
Text(text = num.toString())
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
isDropDownExpanded = false
|
isDropDownExpanded = false
|
||||||
selected = num
|
selected = num
|
||||||
})
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.advanced
|
||||||
|
|
||||||
|
enum class InterfaceActions {
|
||||||
|
TOGGLE_AMNEZIA_VALUES,
|
||||||
|
SET_AMNEZIA_COMPATIBILITY,
|
||||||
|
TOGGLE_SHOW_SCRIPTS,
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.advanced
|
||||||
|
|
||||||
|
enum class PeerActions {
|
||||||
|
EXCLUDE_LAN,
|
||||||
|
}
|
|
@ -3,12 +3,12 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.tunneloptions
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
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.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.Adjust
|
|
||||||
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.Star
|
import androidx.compose.material.icons.outlined.Star
|
||||||
|
@ -32,28 +32,17 @@ 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.label.GroupLabel
|
|
||||||
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.ui.screens.tunneloptions.config.model.InterfaceProxy
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isWgCompatibilityMode
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.resetAmneziaProperties
|
|
||||||
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 com.zaneschepke.wireguardautotunnel.util.extensions.toAmneziaCompatibilityConfig
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun OptionsScreen(appViewModel: AppViewModel, appUiState: AppUiState, tunnelId: Int) {
|
fun OptionsScreen(appViewModel: AppViewModel, appUiState: AppUiState, tunnelId: Int) {
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val config = appUiState.tunnels.first { it.id == tunnelId }
|
val config = appUiState.tunnels.first { it.id == tunnelId }
|
||||||
|
|
||||||
// TODO optimize
|
|
||||||
|
|
||||||
val amConfig = config.toAmConfig()
|
|
||||||
|
|
||||||
val isAmneziaCompatibilityEnabled = amConfig.`interface`.isWgCompatibilityMode()
|
|
||||||
|
|
||||||
var currentText by remember { mutableStateOf("") }
|
var currentText by remember { mutableStateOf("") }
|
||||||
|
|
||||||
LaunchedEffect(config.tunnelNetworks) {
|
LaunchedEffect(config.tunnelNetworks) {
|
||||||
|
@ -71,6 +60,7 @@ fun OptionsScreen(appViewModel: AppViewModel, appUiState: AppUiState, tunnelId:
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(it)
|
.padding(it)
|
||||||
|
.imePadding()
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.padding(top = 24.dp.scaledHeight())
|
.padding(top = 24.dp.scaledHeight())
|
||||||
.padding(horizontal = 24.dp.scaledWidth()),
|
.padding(horizontal = 24.dp.scaledWidth()),
|
||||||
|
@ -152,38 +142,6 @@ 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.updateExistingTunnelConfig(config, `interface` = `interface`)
|
|
||||||
}
|
|
||||||
GroupLabel(stringResource(R.string.quick_actions))
|
|
||||||
SurfaceSelectionGroupButton(
|
|
||||||
listOf(
|
|
||||||
SelectionItem(
|
|
||||||
Icons.Outlined.Adjust,
|
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.enable_amnezia),
|
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(MaterialTheme.colorScheme.onSurface),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
description = {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.wg_compat_mode),
|
|
||||||
style = MaterialTheme.typography.bodySmall.copy(MaterialTheme.colorScheme.outline),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
trailing = {
|
|
||||||
ScaledSwitch(
|
|
||||||
isAmneziaCompatibilityEnabled,
|
|
||||||
onClick = { amneziaClick() },
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onClick = { amneziaClick() },
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.tunneloptions.config
|
package com.zaneschepke.wireguardautotunnel.ui.screens.tunneloptions.config
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.focusGroup
|
import androidx.compose.foundation.focusGroup
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
@ -8,7 +9,9 @@ import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
@ -19,7 +22,10 @@ import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Save
|
import androidx.compose.material.icons.outlined.Save
|
||||||
import androidx.compose.material.icons.rounded.ContentCopy
|
import androidx.compose.material.icons.rounded.ContentCopy
|
||||||
import androidx.compose.material.icons.rounded.Delete
|
import androidx.compose.material.icons.rounded.Delete
|
||||||
|
import androidx.compose.material.icons.rounded.MoreVert
|
||||||
import androidx.compose.material.icons.rounded.Refresh
|
import androidx.compose.material.icons.rounded.Refresh
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
@ -30,15 +36,14 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.toMutableStateList
|
import androidx.compose.runtime.toMutableStateList
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.platform.ClipboardManager
|
import androidx.compose.ui.platform.ClipboardManager
|
||||||
|
@ -56,18 +61,19 @@ import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
|
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationTextBox
|
import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationTextBox
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
import com.zaneschepke.wireguardautotunnel.ui.common.label.GroupLabel
|
||||||
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.common.prompt.AuthorizationPrompt
|
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.advanced.InterfaceActions
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.autotunnel.advanced.PeerActions
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunneloptions.config.model.InterfaceProxy
|
import com.zaneschepke.wireguardautotunnel.ui.screens.tunneloptions.config.model.InterfaceProxy
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunneloptions.config.model.PeerProxy
|
import com.zaneschepke.wireguardautotunnel.ui.screens.tunneloptions.config.model.PeerProxy
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
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 kotlinx.coroutines.launch
|
|
||||||
import org.amnezia.awg.crypto.KeyPair
|
import org.amnezia.awg.crypto.KeyPair
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -77,21 +83,16 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I
|
||||||
val clipboardManager: ClipboardManager = LocalClipboardManager.current
|
val clipboardManager: ClipboardManager = LocalClipboardManager.current
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
val keyboardController = LocalSoftwareKeyboardController.current
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
var isInterfaceDropDownExpanded by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
val popBackStack by appViewModel.popBackStack.collectAsStateWithLifecycle(false)
|
val popBackStack by appViewModel.popBackStack.collectAsStateWithLifecycle(false)
|
||||||
|
|
||||||
val tunnelConfig by remember {
|
val tunnelConfig = appUiState.tunnels.firstOrNull { it.id == tunnelId }
|
||||||
derivedStateOf {
|
|
||||||
appUiState.tunnels.firstOrNull { it.id == tunnelId }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val configPair by remember {
|
val configPair = Pair(tunnelConfig?.name ?: "", tunnelConfig?.toAmConfig())
|
||||||
derivedStateOf {
|
|
||||||
Pair(tunnelConfig?.name ?: "", tunnelConfig?.toAmConfig())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var tunnelName by remember {
|
var tunnelName by remember {
|
||||||
mutableStateOf(configPair.first)
|
mutableStateOf(configPair.first)
|
||||||
|
@ -173,6 +174,7 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
|
.imePadding()
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.padding(top = 24.dp.scaledHeight())
|
.padding(top = 24.dp.scaledHeight())
|
||||||
.padding(horizontal = 24.dp.scaledWidth()),
|
.padding(horizontal = 24.dp.scaledWidth()),
|
||||||
|
@ -187,36 +189,104 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(16.dp.scaledWidth())
|
.padding(16.dp.scaledWidth())
|
||||||
.focusGroup(),
|
.focusGroup(),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier =
|
||||||
|
Modifier.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
GroupLabel(
|
GroupLabel(
|
||||||
stringResource(R.string.interface_),
|
stringResource(R.string.interface_),
|
||||||
)
|
)
|
||||||
ConfigurationToggle(
|
Column {
|
||||||
stringResource(id = R.string.show_amnezia_properties),
|
IconButton(
|
||||||
checked = showAmneziaValues,
|
modifier = Modifier.size(iconSize),
|
||||||
onCheckChanged = {
|
onClick = {
|
||||||
if (appUiState.settings.isKernelEnabled) {
|
isInterfaceDropDownExpanded = true
|
||||||
snackbar.showMessage(context.getString(R.string.amnezia_kernel_message))
|
},
|
||||||
|
) {
|
||||||
|
val icon = Icons.Rounded.MoreVert
|
||||||
|
Icon(icon, icon.name)
|
||||||
|
}
|
||||||
|
DropdownMenu(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
expanded = isInterfaceDropDownExpanded,
|
||||||
|
modifier = Modifier.shadow(12.dp).background(MaterialTheme.colorScheme.surface),
|
||||||
|
onDismissRequest = {
|
||||||
|
isInterfaceDropDownExpanded = false
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
val isAmneziaCompatibilitySet = interfaceState.isAmneziaCompatibilityModeSet()
|
||||||
|
InterfaceActions.entries.forEach { action ->
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
text = when (action) {
|
||||||
|
InterfaceActions.TOGGLE_SHOW_SCRIPTS -> if (showScripts) {
|
||||||
|
stringResource(R.string.hide_scripts)
|
||||||
} else {
|
} else {
|
||||||
showAmneziaValues = it
|
stringResource(R.string.show_scripts)
|
||||||
|
}
|
||||||
|
InterfaceActions.TOGGLE_AMNEZIA_VALUES -> if (showAmneziaValues) {
|
||||||
|
stringResource(R.string.hide_amnezia_properties)
|
||||||
|
} else {
|
||||||
|
stringResource(R.string.show_amnezia_properties)
|
||||||
|
}
|
||||||
|
|
||||||
|
InterfaceActions.SET_AMNEZIA_COMPATIBILITY -> if (isAmneziaCompatibilitySet) {
|
||||||
|
stringResource(R.string.remove_amnezia_compatibility)
|
||||||
|
} else {
|
||||||
|
stringResource(R.string.enable_amnezia_compatibility)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
ConfigurationToggle(
|
},
|
||||||
stringResource(id = R.string.show_scripts),
|
onClick = {
|
||||||
checked = showScripts,
|
isInterfaceDropDownExpanded = false
|
||||||
onCheckChanged = { checked ->
|
when (action) {
|
||||||
if (appUiState.settings.isKernelEnabled) {
|
InterfaceActions.TOGGLE_AMNEZIA_VALUES -> showAmneziaValues = !showAmneziaValues
|
||||||
showScripts = checked
|
InterfaceActions.TOGGLE_SHOW_SCRIPTS -> showScripts = !showScripts
|
||||||
|
InterfaceActions.SET_AMNEZIA_COMPATIBILITY -> if (isAmneziaCompatibilitySet) {
|
||||||
|
showAmneziaValues = false
|
||||||
|
interfaceState = interfaceState.resetAmneziaProperties()
|
||||||
} else {
|
} else {
|
||||||
scope.launch {
|
showAmneziaValues = true
|
||||||
appViewModel.requestRoot().onSuccess {
|
interfaceState = interfaceState.toAmneziaCompatibilityConfig()
|
||||||
showScripts = checked
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ConfigurationToggle(
|
||||||
|
// stringResource(id = R.string.show_amnezia_properties),
|
||||||
|
// checked = showAmneziaValues,
|
||||||
|
// onCheckChanged = {
|
||||||
|
// if (appUiState.settings.isKernelEnabled) {
|
||||||
|
// snackbar.showMessage(context.getString(R.string.amnezia_kernel_message))
|
||||||
|
// } else {
|
||||||
|
// showAmneziaValues = it
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// )
|
||||||
|
// ConfigurationToggle(
|
||||||
|
// stringResource(id = R.string.show_scripts),
|
||||||
|
// checked = showScripts,
|
||||||
|
// onCheckChanged = { checked ->
|
||||||
|
// if (appUiState.settings.isKernelEnabled) {
|
||||||
|
// showScripts = checked
|
||||||
|
// } else {
|
||||||
|
// scope.launch {
|
||||||
|
// appViewModel.requestRoot().onSuccess {
|
||||||
|
// showScripts = checked
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// )
|
||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
value = tunnelName,
|
value = tunnelName,
|
||||||
onValueChange = { tunnelName = it },
|
onValueChange = { tunnelName = it },
|
||||||
|
@ -246,7 +316,7 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
IconButton(
|
IconButton(
|
||||||
enabled = privateKeyEnabled,
|
enabled = privateKeyEnabled,
|
||||||
modifier = Modifier.focusRequester(FocusRequester.Default),
|
modifier = Modifier.focusRequester(FocusRequester.Default).size(iconSize),
|
||||||
onClick = {
|
onClick = {
|
||||||
val keypair = KeyPair()
|
val keypair = KeyPair()
|
||||||
interfaceState = interfaceState.copy(
|
interfaceState = interfaceState.copy(
|
||||||
|
@ -287,7 +357,7 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I
|
||||||
},
|
},
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.focusRequester(FocusRequester.Default),
|
modifier = Modifier.focusRequester(FocusRequester.Default).size(iconSize),
|
||||||
onClick = {
|
onClick = {
|
||||||
clipboardManager.setText(
|
clipboardManager.setText(
|
||||||
AnnotatedString(interfaceState.publicKey),
|
AnnotatedString(interfaceState.publicKey),
|
||||||
|
@ -540,13 +610,17 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
peersState.forEachIndexed { index, peer ->
|
peersState.forEachIndexed { index, peer ->
|
||||||
|
var isPeerDropDownExpanded by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
val isLanExcluded = peer.isLanExcluded()
|
||||||
Surface(
|
Surface(
|
||||||
shape = RoundedCornerShape(12.dp),
|
shape = RoundedCornerShape(12.dp),
|
||||||
color = MaterialTheme.colorScheme.surface,
|
color = MaterialTheme.colorScheme.surface,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
verticalArrangement = Arrangement.spacedBy(5.dp, Alignment.Top),
|
verticalArrangement = Arrangement.spacedBy(10.dp, Alignment.Top),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(16.dp.scaledWidth())
|
.padding(16.dp.scaledWidth())
|
||||||
.focusGroup(),
|
.focusGroup(),
|
||||||
|
@ -560,12 +634,66 @@ fun ConfigScreen(appUiState: AppUiState, appViewModel: AppViewModel, tunnelId: I
|
||||||
GroupLabel(
|
GroupLabel(
|
||||||
stringResource(R.string.peer),
|
stringResource(R.string.peer),
|
||||||
)
|
)
|
||||||
IconButton(onClick = {
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(10.dp, Alignment.End),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
modifier = Modifier.size(iconSize),
|
||||||
|
onClick = {
|
||||||
|
// TODO make a dialog to confirm this
|
||||||
peersState.removeAt(index)
|
peersState.removeAt(index)
|
||||||
}) {
|
},
|
||||||
|
) {
|
||||||
val icon = Icons.Rounded.Delete
|
val icon = Icons.Rounded.Delete
|
||||||
Icon(icon, icon.name)
|
Icon(icon, icon.name)
|
||||||
}
|
}
|
||||||
|
Column {
|
||||||
|
IconButton(
|
||||||
|
modifier = Modifier.size(iconSize),
|
||||||
|
onClick = {
|
||||||
|
isPeerDropDownExpanded = true
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
val icon = Icons.Rounded.MoreVert
|
||||||
|
Icon(icon, icon.name)
|
||||||
|
}
|
||||||
|
DropdownMenu(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
expanded = isPeerDropDownExpanded,
|
||||||
|
modifier = Modifier.shadow(12.dp).background(MaterialTheme.colorScheme.surface),
|
||||||
|
onDismissRequest = {
|
||||||
|
isPeerDropDownExpanded = false
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
PeerActions.entries.forEach { action ->
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
text = when (action) {
|
||||||
|
PeerActions.EXCLUDE_LAN -> if (isLanExcluded) {
|
||||||
|
stringResource(R.string.include_lan)
|
||||||
|
} else {
|
||||||
|
stringResource(R.string.exclude_lan)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
isPeerDropDownExpanded = false
|
||||||
|
when (action) {
|
||||||
|
PeerActions.EXCLUDE_LAN -> if (isLanExcluded) {
|
||||||
|
peersState[index] = peer.includeLan()
|
||||||
|
} else {
|
||||||
|
peersState[index] = peer.excludeLan()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.tunneloptions.config.mode
|
||||||
import com.wireguard.config.Interface
|
import com.wireguard.config.Interface
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.joinAndTrim
|
import com.zaneschepke.wireguardautotunnel.util.extensions.joinAndTrim
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toTrimmedList
|
import com.zaneschepke.wireguardautotunnel.util.extensions.toTrimmedList
|
||||||
|
import kotlin.ranges.contains
|
||||||
|
|
||||||
data class InterfaceProxy(
|
data class InterfaceProxy(
|
||||||
val privateKey: String = "",
|
val privateKey: String = "",
|
||||||
|
@ -44,6 +45,46 @@ data class InterfaceProxy(
|
||||||
}.build()
|
}.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun toAmneziaCompatibilityConfig(): InterfaceProxy {
|
||||||
|
return copy(
|
||||||
|
junkPacketCount = "4",
|
||||||
|
junkPacketMinSize = "40",
|
||||||
|
junkPacketMaxSize = "70",
|
||||||
|
initPacketJunkSize = "0",
|
||||||
|
responsePacketJunkSize = "0",
|
||||||
|
initPacketMagicHeader = "1",
|
||||||
|
responsePacketMagicHeader = "2",
|
||||||
|
underloadPacketMagicHeader = "3",
|
||||||
|
transportPacketMagicHeader = "4",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetAmneziaProperties(): InterfaceProxy {
|
||||||
|
return copy(
|
||||||
|
junkPacketCount = "",
|
||||||
|
junkPacketMinSize = "",
|
||||||
|
junkPacketMaxSize = "",
|
||||||
|
initPacketJunkSize = "",
|
||||||
|
responsePacketJunkSize = "",
|
||||||
|
initPacketMagicHeader = "",
|
||||||
|
responsePacketMagicHeader = "",
|
||||||
|
underloadPacketMagicHeader = "",
|
||||||
|
transportPacketMagicHeader = "",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isAmneziaCompatibilityModeSet(): Boolean {
|
||||||
|
return junkPacketCount.toIntOrNull() in 3..<5 &&
|
||||||
|
junkPacketMinSize.toIntOrNull() == 40 &&
|
||||||
|
junkPacketMaxSize.toIntOrNull() == 70 &&
|
||||||
|
with(initPacketJunkSize.toIntOrNull()) { this == 0 || this == null } &&
|
||||||
|
with(responsePacketJunkSize.toIntOrNull()) { this == 0 || this == null } &&
|
||||||
|
initPacketMagicHeader.toLongOrNull() == 1L &&
|
||||||
|
responsePacketMagicHeader.toLongOrNull() == 2L &&
|
||||||
|
underloadPacketMagicHeader.toLongOrNull() == 3L &&
|
||||||
|
transportPacketMagicHeader.toLongOrNull() == 4L
|
||||||
|
}
|
||||||
|
|
||||||
fun toAmInterface(): org.amnezia.awg.config.Interface {
|
fun toAmInterface(): org.amnezia.awg.config.Interface {
|
||||||
return org.amnezia.awg.config.Interface.Builder().apply {
|
return org.amnezia.awg.config.Interface.Builder().apply {
|
||||||
parseAddresses(addresses)
|
parseAddresses(addresses)
|
||||||
|
|
|
@ -8,7 +8,7 @@ data class PeerProxy(
|
||||||
val preSharedKey: String = "",
|
val preSharedKey: String = "",
|
||||||
val persistentKeepalive: String = "",
|
val persistentKeepalive: String = "",
|
||||||
val endpoint: String = "",
|
val endpoint: String = "",
|
||||||
val allowedIps: String = IPV4_WILDCARD.joinAndTrim(),
|
val allowedIps: String = ALL_IPS.joinAndTrim(),
|
||||||
) {
|
) {
|
||||||
fun toWgPeer(): Peer {
|
fun toWgPeer(): Peer {
|
||||||
return Peer.Builder().apply {
|
return Peer.Builder().apply {
|
||||||
|
@ -29,6 +29,22 @@ data class PeerProxy(
|
||||||
}.build()
|
}.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isLanExcluded(): Boolean {
|
||||||
|
return this.allowedIps.contains(IPV4_PUBLIC_NETWORKS.joinAndTrim())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun includeLan(): PeerProxy {
|
||||||
|
return this.copy(
|
||||||
|
allowedIps = ALL_IPS.joinAndTrim(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun excludeLan(): PeerProxy {
|
||||||
|
return this.copy(
|
||||||
|
allowedIps = IPV4_PUBLIC_NETWORKS.joinAndTrim(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun from(peer: Peer): PeerProxy {
|
fun from(peer: Peer): PeerProxy {
|
||||||
return PeerProxy(
|
return PeerProxy(
|
||||||
|
@ -113,6 +129,6 @@ data class PeerProxy(
|
||||||
"200.0.0.0/5",
|
"200.0.0.0/5",
|
||||||
"208.0.0.0/4",
|
"208.0.0.0/4",
|
||||||
)
|
)
|
||||||
val IPV4_WILDCARD = setOf("0.0.0.0/0")
|
val ALL_IPS = setOf("0.0.0.0/0", "::/0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,17 +6,14 @@ import com.wireguard.config.Peer
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.BackendState
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.BackendState
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.tunneloptions.config.model.InterfaceProxy
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
|
import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.Straw
|
import com.zaneschepke.wireguardautotunnel.ui.theme.Straw
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
||||||
import org.amnezia.awg.backend.Backend
|
import org.amnezia.awg.backend.Backend
|
||||||
import org.amnezia.awg.config.Config
|
import org.amnezia.awg.config.Config
|
||||||
import org.amnezia.awg.config.Interface
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import kotlin.jvm.optionals.getOrNull
|
|
||||||
|
|
||||||
fun TunnelStatistics.mapPeerStats(): Map<org.amnezia.awg.crypto.Key, TunnelStatistics.PeerStats?> {
|
fun TunnelStatistics.mapPeerStats(): Map<org.amnezia.awg.crypto.Key, TunnelStatistics.PeerStats?> {
|
||||||
return this.getPeers().associateWith { key -> (this.peerStats(key)) }
|
return this.getPeers().associateWith { key -> (this.peerStats(key)) }
|
||||||
|
@ -98,45 +95,3 @@ fun Backend.BackendState.asBackendState(): BackendState {
|
||||||
fun BackendState.asAmBackendState(): Backend.BackendState {
|
fun BackendState.asAmBackendState(): Backend.BackendState {
|
||||||
return Backend.BackendState.valueOf(this.name)
|
return Backend.BackendState.valueOf(this.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Interface.isWgCompatibilityMode(): Boolean {
|
|
||||||
return (
|
|
||||||
junkPacketCount.getOrNull() in 3..<5 &&
|
|
||||||
junkPacketMinSize.getOrNull() == 40 &&
|
|
||||||
junkPacketMaxSize.getOrNull() == 70 &&
|
|
||||||
with(initPacketJunkSize.getOrNull()) { this == 0 || this == null } &&
|
|
||||||
with(responsePacketJunkSize.getOrNull()) { this == 0 || this == null } &&
|
|
||||||
initPacketMagicHeader.getOrNull() == 1L &&
|
|
||||||
responsePacketMagicHeader.getOrNull() == 2L &&
|
|
||||||
underloadPacketMagicHeader.getOrNull() == 3L &&
|
|
||||||
transportPacketMagicHeader.getOrNull() == 4L
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun InterfaceProxy.toAmneziaCompatibilityConfig(): InterfaceProxy {
|
|
||||||
return copy(
|
|
||||||
junkPacketCount = "4",
|
|
||||||
junkPacketMinSize = "40",
|
|
||||||
junkPacketMaxSize = "70",
|
|
||||||
initPacketJunkSize = "0",
|
|
||||||
responsePacketJunkSize = "0",
|
|
||||||
initPacketMagicHeader = "1",
|
|
||||||
responsePacketMagicHeader = "2",
|
|
||||||
underloadPacketMagicHeader = "3",
|
|
||||||
transportPacketMagicHeader = "4",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun InterfaceProxy.resetAmneziaProperties(): InterfaceProxy {
|
|
||||||
return copy(
|
|
||||||
junkPacketCount = "",
|
|
||||||
junkPacketMinSize = "",
|
|
||||||
junkPacketMaxSize = "",
|
|
||||||
initPacketJunkSize = "",
|
|
||||||
responsePacketJunkSize = "",
|
|
||||||
initPacketMagicHeader = "",
|
|
||||||
responsePacketMagicHeader = "",
|
|
||||||
underloadPacketMagicHeader = "",
|
|
||||||
transportPacketMagicHeader = "",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -196,4 +196,10 @@
|
||||||
<string name="quick_actions">Quick actions</string>
|
<string name="quick_actions">Quick actions</string>
|
||||||
<string name="advanced_settings">Advanced settings</string>
|
<string name="advanced_settings">Advanced settings</string>
|
||||||
<string name="debounce_delay">Debounce delay</string>
|
<string name="debounce_delay">Debounce delay</string>
|
||||||
|
<string name="hide_amnezia_properties">Hide Amnezia properties</string>
|
||||||
|
<string name="hide_scripts">Hide scripts</string>
|
||||||
|
<string name="enable_amnezia_compatibility">Enable Amnezia compatibility</string>
|
||||||
|
<string name="remove_amnezia_compatibility">Remove Amnezia compatibility</string>
|
||||||
|
<string name="exclude_lan">Exclude LAN</string>
|
||||||
|
<string name="include_lan">Include LAN</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue