add amnezia import/export
This commit is contained in:
parent
e4af481402
commit
a5e60c3fbe
|
@ -139,6 +139,7 @@ dependencies {
|
||||||
|
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
|
|
||||||
// helpers for implementing LifecycleOwner in a Service
|
// helpers for implementing LifecycleOwner in a Service
|
||||||
implementation(libs.androidx.lifecycle.service)
|
implementation(libs.androidx.lifecycle.service)
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
|
@ -173,6 +174,8 @@ dependencies {
|
||||||
implementation(libs.androidx.navigation.compose)
|
implementation(libs.androidx.navigation.compose)
|
||||||
implementation(libs.androidx.hilt.navigation.compose)
|
implementation(libs.androidx.hilt.navigation.compose)
|
||||||
|
|
||||||
|
implementation(libs.zaneschepke.multifab)
|
||||||
|
|
||||||
// hilt
|
// hilt
|
||||||
implementation(libs.hilt.android)
|
implementation(libs.hilt.android)
|
||||||
ksp(libs.hilt.android.compiler)
|
ksp(libs.hilt.android.compiler)
|
||||||
|
|
|
@ -31,7 +31,7 @@ data class TunnelConfig(
|
||||||
name = "am_quick",
|
name = "am_quick",
|
||||||
defaultValue = "",
|
defaultValue = "",
|
||||||
)
|
)
|
||||||
val amQuick: String = "",
|
val amQuick: String = AM_QUICK_DEFAULT,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun configFromWgQuick(wgQuick: String): Config {
|
fun configFromWgQuick(wgQuick: String): Config {
|
||||||
|
@ -46,5 +46,6 @@ data class TunnelConfig(
|
||||||
org.amnezia.awg.config.Config.parse(it)
|
org.amnezia.awg.config.Config.parse(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const val AM_QUICK_DEFAULT = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -225,7 +226,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
|
||||||
val host = if (peer.endpoint.isPresent &&
|
val host = if (peer.endpoint.isPresent &&
|
||||||
peer.endpoint.get().resolved.isPresent)
|
peer.endpoint.get().resolved.isPresent)
|
||||||
peer.endpoint.get().resolved.get().host
|
peer.endpoint.get().resolved.get().host
|
||||||
else Constants.BACKUP_PING_HOST
|
else Constants.DEFAULT_PING_IP
|
||||||
Timber.i("Checking reachability of: $host")
|
Timber.i("Checking reachability of: $host")
|
||||||
val reachable = InetAddress.getByName(host)
|
val reachable = InetAddress.getByName(host)
|
||||||
.isReachable(Constants.PING_TIMEOUT.toInt())
|
.isReachable(Constants.PING_TIMEOUT.toInt())
|
||||||
|
|
|
@ -52,7 +52,6 @@ class TunnelControlTile : TileService() {
|
||||||
setTileDescription(it.name)
|
setTileDescription(it.name)
|
||||||
} ?: setUnavailable()
|
} ?: setUnavailable()
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> setInactive()
|
else -> setInactive()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,10 +32,12 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.navigation.NavType
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import androidx.navigation.navArgument
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.isGranted
|
import com.google.accompanist.permissions.isGranted
|
||||||
import com.google.accompanist.permissions.rememberPermissionState
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
|
@ -47,6 +49,7 @@ import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.CustomSnackBar
|
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.CustomSnackBar
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.config.ConfigScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.config.ConfigScreen
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.ConfigType
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.MainScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.MainScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.options.OptionsScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.options.OptionsScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.pinlock.PinLockScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.pinlock.PinLockScreen
|
||||||
|
@ -236,14 +239,28 @@ class MainActivity : AppCompatActivity() {
|
||||||
composable(Screen.Support.Logs.route) {
|
composable(Screen.Support.Logs.route) {
|
||||||
LogsScreen(appViewModel)
|
LogsScreen(appViewModel)
|
||||||
}
|
}
|
||||||
composable("${Screen.Config.route}/{id}") {
|
//TODO fix navigation for amnezia
|
||||||
|
composable("${Screen.Config.route}/{id}?configType={configType}", arguments =
|
||||||
|
listOf(
|
||||||
|
navArgument("id") {
|
||||||
|
type = NavType.StringType
|
||||||
|
defaultValue = "0"
|
||||||
|
},
|
||||||
|
navArgument("configType") {
|
||||||
|
type = NavType.StringType
|
||||||
|
defaultValue = ConfigType.WIREGUARD.name
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
val id = it.arguments?.getString("id")
|
val id = it.arguments?.getString("id")
|
||||||
|
val configType = ConfigType.valueOf( it.arguments?.getString("configType") ?: ConfigType.WIREGUARD.name)
|
||||||
if (!id.isNullOrBlank()) {
|
if (!id.isNullOrBlank()) {
|
||||||
ConfigScreen(
|
ConfigScreen(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
tunnelId = id,
|
tunnelId = id,
|
||||||
appViewModel = appViewModel,
|
appViewModel = appViewModel,
|
||||||
focusRequester = focusRequester,
|
focusRequester = focusRequester,
|
||||||
|
configType = configType
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,7 @@ import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationTextBox
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
|
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.screen.LoadingScreen
|
import com.zaneschepke.wireguardautotunnel.ui.common.screen.LoadingScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
|
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.ConfigType
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Event
|
import com.zaneschepke.wireguardautotunnel.util.Event
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Result
|
import com.zaneschepke.wireguardautotunnel.util.Result
|
||||||
|
@ -94,7 +95,8 @@ fun ConfigScreen(
|
||||||
focusRequester: FocusRequester,
|
focusRequester: FocusRequester,
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
appViewModel: AppViewModel,
|
appViewModel: AppViewModel,
|
||||||
tunnelId: String
|
tunnelId: String,
|
||||||
|
configType: ConfigType
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val clipboardManager: ClipboardManager = LocalClipboardManager.current
|
val clipboardManager: ClipboardManager = LocalClipboardManager.current
|
||||||
|
@ -319,7 +321,7 @@ fun ConfigScreen(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.onSaveAllChanges().let {
|
viewModel.onSaveAllChanges(configType).let {
|
||||||
when (it) {
|
when (it) {
|
||||||
is Result.Success -> {
|
is Result.Success -> {
|
||||||
appViewModel.showSnackbarMessage(it.data.message)
|
appViewModel.showSnackbarMessage(it.data.message)
|
||||||
|
@ -486,7 +488,7 @@ fun ConfigScreen(
|
||||||
modifier = Modifier.width(IntrinsicSize.Min),
|
modifier = Modifier.width(IntrinsicSize.Min),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if(uiState.isAmneziaEnabled) {
|
if(configType == ConfigType.AMNEZIA) {
|
||||||
ConfigurationTextBox(
|
ConfigurationTextBox(
|
||||||
value = uiState.interfaceProxy.junkPacketCount,
|
value = uiState.interfaceProxy.junkPacketCount,
|
||||||
onValueChange = { value -> viewModel.onJunkPacketCountChanged(value) },
|
onValueChange = { value -> viewModel.onJunkPacketCountChanged(value) },
|
||||||
|
|
|
@ -17,6 +17,7 @@ import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.config.model.PeerProxy
|
import com.zaneschepke.wireguardautotunnel.ui.screens.config.model.PeerProxy
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.ConfigType
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Event
|
import com.zaneschepke.wireguardautotunnel.util.Event
|
||||||
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
||||||
|
@ -248,19 +249,22 @@ constructor(
|
||||||
return org.amnezia.awg.config.Config.Builder().addPeers(peerList).setInterface(amInterface).build()
|
return org.amnezia.awg.config.Config.Builder().addPeers(peerList).setInterface(amInterface).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSaveAllChanges(): Result<Event> {
|
fun onSaveAllChanges(configType: ConfigType): Result<Event> {
|
||||||
return try {
|
return try {
|
||||||
val config = buildConfig()
|
val wgQuick = buildConfig().toWgQuickString()
|
||||||
|
val amQuick = if(configType == ConfigType.AMNEZIA) {
|
||||||
|
buildAmConfig().toAwgQuickString()
|
||||||
|
} else TunnelConfig.AM_QUICK_DEFAULT
|
||||||
val tunnelConfig = when (uiState.value.tunnel) {
|
val tunnelConfig = when (uiState.value.tunnel) {
|
||||||
null -> TunnelConfig(
|
null -> TunnelConfig(
|
||||||
name = _uiState.value.tunnelName,
|
name = _uiState.value.tunnelName,
|
||||||
wgQuick = config.toWgQuickString(),
|
wgQuick = wgQuick,
|
||||||
|
amQuick = amQuick
|
||||||
)
|
)
|
||||||
else -> uiState.value.tunnel!!.copy(
|
else -> uiState.value.tunnel!!.copy(
|
||||||
name = _uiState.value.tunnelName,
|
name = _uiState.value.tunnelName,
|
||||||
wgQuick = config.toWgQuickString(),
|
wgQuick = wgQuick,
|
||||||
amQuick = if(uiState.value.isAmneziaEnabled) buildAmConfig().toAwgQuickString()
|
amQuick = amQuick
|
||||||
else _uiState.value.tunnel?.amQuick ?: ""
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
updateTunnelConfig(tunnelConfig)
|
updateTunnelConfig(tunnelConfig)
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.zaneschepke.wireguardautotunnel.ui.screens.main
|
||||||
|
|
||||||
|
enum class ConfigType {
|
||||||
|
AMNEZIA,
|
||||||
|
WIREGUARD
|
||||||
|
}
|
|
@ -34,7 +34,6 @@ import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Create
|
import androidx.compose.material.icons.filled.Create
|
||||||
import androidx.compose.material.icons.filled.FileOpen
|
import androidx.compose.material.icons.filled.FileOpen
|
||||||
import androidx.compose.material.icons.filled.QrCode
|
import androidx.compose.material.icons.filled.QrCode
|
||||||
import androidx.compose.material.icons.rounded.Add
|
|
||||||
import androidx.compose.material.icons.rounded.Bolt
|
import androidx.compose.material.icons.rounded.Bolt
|
||||||
import androidx.compose.material.icons.rounded.Circle
|
import androidx.compose.material.icons.rounded.Circle
|
||||||
import androidx.compose.material.icons.rounded.CopyAll
|
import androidx.compose.material.icons.rounded.CopyAll
|
||||||
|
@ -46,7 +45,6 @@ import androidx.compose.material.icons.rounded.Star
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.FabPosition
|
import androidx.compose.material3.FabPosition
|
||||||
import androidx.compose.material3.FloatingActionButton
|
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
@ -91,6 +89,10 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
|
import com.iamageo.multifablibrary.FabIcon
|
||||||
|
import com.iamageo.multifablibrary.FabOption
|
||||||
|
import com.iamageo.multifablibrary.MultiFabItem
|
||||||
|
import com.iamageo.multifablibrary.MultiFloatingActionButton
|
||||||
import com.journeyapps.barcodescanner.ScanContract
|
import com.journeyapps.barcodescanner.ScanContract
|
||||||
import com.journeyapps.barcodescanner.ScanOptions
|
import com.journeyapps.barcodescanner.ScanOptions
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
|
@ -114,8 +116,6 @@ import com.zaneschepke.wireguardautotunnel.util.truncateWithEllipsis
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
|
||||||
import java.util.Timer
|
|
||||||
|
|
||||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||||
|
@ -133,6 +133,7 @@ fun MainScreen(
|
||||||
|
|
||||||
val sheetState = rememberModalBottomSheetState()
|
val sheetState = rememberModalBottomSheetState()
|
||||||
var showBottomSheet by remember { mutableStateOf(false) }
|
var showBottomSheet by remember { mutableStateOf(false) }
|
||||||
|
var configType by remember { mutableStateOf(ConfigType.WIREGUARD) }
|
||||||
|
|
||||||
// Nested scroll for control FAB
|
// Nested scroll for control FAB
|
||||||
val nestedScrollConnection = remember {
|
val nestedScrollConnection = remember {
|
||||||
|
@ -201,7 +202,7 @@ fun MainScreen(
|
||||||
) { data ->
|
) { data ->
|
||||||
if (data == null) return@rememberLauncherForActivityResult
|
if (data == null) return@rememberLauncherForActivityResult
|
||||||
scope.launch {
|
scope.launch {
|
||||||
viewModel.onTunnelFileSelected(data).let {
|
viewModel.onTunnelFileSelected(data, configType).let {
|
||||||
when (it) {
|
when (it) {
|
||||||
is Result.Error -> appViewModel.showSnackbarMessage(it.error.message)
|
is Result.Error -> appViewModel.showSnackbarMessage(it.error.message)
|
||||||
is Result.Success -> {}
|
is Result.Success -> {}
|
||||||
|
@ -215,7 +216,7 @@ fun MainScreen(
|
||||||
onResult = {
|
onResult = {
|
||||||
if (it.contents != null) {
|
if (it.contents != null) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
viewModel.onTunnelQrResult(it.contents).let { result ->
|
viewModel.onTunnelQrResult(it.contents, configType).let { result ->
|
||||||
when (result) {
|
when (result) {
|
||||||
is Result.Success -> {}
|
is Result.Success -> {}
|
||||||
is Result.Error -> appViewModel.showSnackbarMessage(result.error.message)
|
is Result.Error -> appViewModel.showSnackbarMessage(result.error.message)
|
||||||
|
@ -260,6 +261,19 @@ fun MainScreen(
|
||||||
return LoadingScreen()
|
return LoadingScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun launchQrScanner() {
|
||||||
|
val scanOptions = ScanOptions()
|
||||||
|
scanOptions.setDesiredBarcodeFormats(ScanOptions.QR_CODE)
|
||||||
|
scanOptions.setOrientationLocked(true)
|
||||||
|
scanOptions.setPrompt(
|
||||||
|
context.getString(R.string.scanning_qr),
|
||||||
|
)
|
||||||
|
scanOptions.setBeepEnabled(false)
|
||||||
|
scanOptions.captureActivity =
|
||||||
|
CaptureActivityPortrait::class.java
|
||||||
|
scanLauncher.launch(scanOptions)
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.pointerInput(Unit) {
|
Modifier.pointerInput(Unit) {
|
||||||
|
@ -282,7 +296,7 @@ fun MainScreen(
|
||||||
val secondaryColor = MaterialTheme.colorScheme.secondary
|
val secondaryColor = MaterialTheme.colorScheme.secondary
|
||||||
val hoverColor = MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)
|
val hoverColor = MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)
|
||||||
var fobColor by remember { mutableStateOf(secondaryColor) }
|
var fobColor by remember { mutableStateOf(secondaryColor) }
|
||||||
FloatingActionButton(
|
MultiFloatingActionButton(
|
||||||
modifier =
|
modifier =
|
||||||
(if (
|
(if (
|
||||||
WireGuardAutoTunnel.isRunningOnAndroidTv() &&
|
WireGuardAutoTunnel.isRunningOnAndroidTv() &&
|
||||||
|
@ -295,16 +309,42 @@ fun MainScreen(
|
||||||
fobColor = if (it.isFocused) hoverColor else secondaryColor
|
fobColor = if (it.isFocused) hoverColor else secondaryColor
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClick = { showBottomSheet = true },
|
fabIcon = FabIcon(
|
||||||
containerColor = fobColor,
|
iconRes = R.drawable.add,
|
||||||
shape = RoundedCornerShape(16.dp),
|
iconResAfterRotate = R.drawable.close,
|
||||||
) {
|
iconRotate = 180f
|
||||||
Icon(
|
),
|
||||||
imageVector = Icons.Rounded.Add,
|
fabOption = FabOption(
|
||||||
contentDescription = stringResource(id = R.string.add_tunnel),
|
iconTint = MaterialTheme.colorScheme.background,
|
||||||
tint = Color.DarkGray,
|
backgroundTint = MaterialTheme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
itemsMultiFab = listOf(
|
||||||
|
MultiFabItem(
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
stringResource(id = R.string.amnezia),
|
||||||
|
color = Color.White,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.padding(end = 10.dp)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
icon = R.drawable.add,
|
||||||
|
value = ConfigType.AMNEZIA.name,
|
||||||
|
),
|
||||||
|
MultiFabItem(
|
||||||
|
label = {
|
||||||
|
Text(stringResource(id = R.string.wireguard), color = Color.White, textAlign = TextAlign.Center, modifier = Modifier.padding(end = 10.dp))
|
||||||
|
},
|
||||||
|
icon = R.drawable.add,
|
||||||
|
value = ConfigType.WIREGUARD.name
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onFabItemClicked = {
|
||||||
|
showBottomSheet = true
|
||||||
|
configType = ConfigType.valueOf(it.value)
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
@ -343,16 +383,7 @@ fun MainScreen(
|
||||||
.clickable {
|
.clickable {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
showBottomSheet = false
|
showBottomSheet = false
|
||||||
val scanOptions = ScanOptions()
|
launchQrScanner()
|
||||||
scanOptions.setDesiredBarcodeFormats(ScanOptions.QR_CODE)
|
|
||||||
scanOptions.setOrientationLocked(true)
|
|
||||||
scanOptions.setPrompt(
|
|
||||||
context.getString(R.string.scanning_qr),
|
|
||||||
)
|
|
||||||
scanOptions.setBeepEnabled(false)
|
|
||||||
scanOptions.captureActivity =
|
|
||||||
CaptureActivityPortrait::class.java
|
|
||||||
scanLauncher.launch(scanOptions)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(10.dp),
|
.padding(10.dp),
|
||||||
|
@ -376,7 +407,7 @@ fun MainScreen(
|
||||||
.clickable {
|
.clickable {
|
||||||
showBottomSheet = false
|
showBottomSheet = false
|
||||||
navController.navigate(
|
navController.navigate(
|
||||||
"${Screen.Config.route}/${Constants.MANUAL_TUNNEL_CONFIG_ID}",
|
"${Screen.Config.route}/${Constants.MANUAL_TUNNEL_CONFIG_ID}?configType=${configType}",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.padding(10.dp),
|
.padding(10.dp),
|
||||||
|
|
|
@ -18,13 +18,13 @@ import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Event
|
import com.zaneschepke.wireguardautotunnel.util.Event
|
||||||
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Result
|
import com.zaneschepke.wireguardautotunnel.util.Result
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.toWgQuickString
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
|
@ -98,15 +98,23 @@ constructor(
|
||||||
serviceManager.stopVpnService(application.applicationContext, isManualStop = true)
|
serviceManager.stopVpnService(application.applicationContext, isManualStop = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun validateConfigString(config: String) {
|
private fun validateConfigString(config: String, configType: ConfigType) {
|
||||||
TunnelConfig.configFromWgQuick(config)
|
when(configType) {
|
||||||
|
ConfigType.AMNEZIA -> TunnelConfig.configFromAmQuick(config)
|
||||||
|
ConfigType.WIREGUARD -> TunnelConfig.configFromWgQuick(config)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun onTunnelQrResult(result: String): Result<Unit> {
|
suspend fun onTunnelQrResult(result: String, configType: ConfigType): Result<Unit> {
|
||||||
return try {
|
return try {
|
||||||
validateConfigString(result)
|
validateConfigString(result, configType)
|
||||||
val tunnelConfig =
|
val tunnelConfig = when(configType) {
|
||||||
TunnelConfig(name = NumberUtils.generateRandomTunnelName(), wgQuick = result)
|
ConfigType.AMNEZIA ->{
|
||||||
|
TunnelConfig(name = NumberUtils.generateRandomTunnelName(), amQuick = result,
|
||||||
|
wgQuick = TunnelConfig.configFromAmQuick(result).toWgQuickString())
|
||||||
|
}
|
||||||
|
ConfigType.WIREGUARD -> TunnelConfig(name = NumberUtils.generateRandomTunnelName(), wgQuick = result)
|
||||||
|
}
|
||||||
addTunnel(tunnelConfig)
|
addTunnel(tunnelConfig)
|
||||||
Result.Success(Unit)
|
Result.Success(Unit)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -115,32 +123,42 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun saveTunnelConfigFromStream(stream: InputStream, fileName: String) {
|
private suspend fun saveTunnelConfigFromStream(stream: InputStream, fileName: String, type: ConfigType) {
|
||||||
val bufferReader = stream.bufferedReader(charset = Charsets.UTF_8)
|
var amQuick : String? = null
|
||||||
val config = Config.parse(bufferReader)
|
val wgQuick = stream.use {
|
||||||
|
when(type) {
|
||||||
|
ConfigType.AMNEZIA -> {
|
||||||
|
val config = org.amnezia.awg.config.Config.parse(it)
|
||||||
|
amQuick = config.toAwgQuickString()
|
||||||
|
config.toWgQuickString()
|
||||||
|
}
|
||||||
|
ConfigType.WIREGUARD -> {
|
||||||
|
Config.parse(it).toWgQuickString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
val tunnelName = getNameFromFileName(fileName)
|
val tunnelName = getNameFromFileName(fileName)
|
||||||
addTunnel(TunnelConfig(name = tunnelName, wgQuick = config.toWgQuickString()))
|
addTunnel(TunnelConfig(name = tunnelName, wgQuick = wgQuick, amQuick = amQuick ?: TunnelConfig.AM_QUICK_DEFAULT))
|
||||||
withContext(Dispatchers.IO) { stream.close() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getInputStreamFromUri(uri: Uri): InputStream? {
|
private fun getInputStreamFromUri(uri: Uri): InputStream? {
|
||||||
return application.applicationContext.contentResolver.openInputStream(uri)
|
return application.applicationContext.contentResolver.openInputStream(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun onTunnelFileSelected(uri: Uri): Result<Unit> {
|
suspend fun onTunnelFileSelected(uri: Uri, configType: ConfigType): Result<Unit> {
|
||||||
try {
|
try {
|
||||||
if (isValidUriContentScheme(uri)) {
|
if (isValidUriContentScheme(uri)) {
|
||||||
val fileName = getFileName(application.applicationContext, uri)
|
val fileName = getFileName(application.applicationContext, uri)
|
||||||
when (getFileExtensionFromFileName(fileName)) {
|
when (getFileExtensionFromFileName(fileName)) {
|
||||||
Constants.CONF_FILE_EXTENSION ->
|
Constants.CONF_FILE_EXTENSION ->
|
||||||
saveTunnelFromConfUri(fileName, uri).let {
|
saveTunnelFromConfUri(fileName, uri, configType).let {
|
||||||
when (it) {
|
when (it) {
|
||||||
is Result.Error -> return Result.Error(Event.Error.FileReadFailed)
|
is Result.Error -> return Result.Error(Event.Error.FileReadFailed)
|
||||||
is Result.Success -> return it
|
is Result.Success -> return it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Constants.ZIP_FILE_EXTENSION -> saveTunnelsFromZipUri(uri)
|
Constants.ZIP_FILE_EXTENSION -> saveTunnelsFromZipUri(uri, configType)
|
||||||
else -> return Result.Error(Event.Error.InvalidFileExtension)
|
else -> return Result.Error(Event.Error.InvalidFileExtension)
|
||||||
}
|
}
|
||||||
return Result.Success(Unit)
|
return Result.Success(Unit)
|
||||||
|
@ -153,7 +171,7 @@ constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun saveTunnelsFromZipUri(uri: Uri) {
|
private suspend fun saveTunnelsFromZipUri(uri: Uri, configType: ConfigType) {
|
||||||
ZipInputStream(getInputStreamFromUri(uri)).use { zip ->
|
ZipInputStream(getInputStreamFromUri(uri)).use { zip ->
|
||||||
generateSequence { zip.nextEntry }
|
generateSequence { zip.nextEntry }
|
||||||
.filterNot {
|
.filterNot {
|
||||||
|
@ -162,18 +180,29 @@ constructor(
|
||||||
}
|
}
|
||||||
.forEach {
|
.forEach {
|
||||||
val name = getNameFromFileName(it.name)
|
val name = getNameFromFileName(it.name)
|
||||||
val config = Config.parse(zip)
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
addTunnel(TunnelConfig(name = name, wgQuick = config.toWgQuickString()))
|
var amQuick : String? = null
|
||||||
|
val wgQuick =
|
||||||
|
when(configType) {
|
||||||
|
ConfigType.AMNEZIA -> {
|
||||||
|
val config = org.amnezia.awg.config.Config.parse(zip)
|
||||||
|
amQuick = config.toAwgQuickString()
|
||||||
|
config.toWgQuickString()
|
||||||
|
}
|
||||||
|
ConfigType.WIREGUARD -> {
|
||||||
|
Config.parse(zip).toWgQuickString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addTunnel(TunnelConfig(name = name, wgQuick = wgQuick, amQuick = amQuick ?: TunnelConfig.AM_QUICK_DEFAULT))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun saveTunnelFromConfUri(name: String, uri: Uri): Result<Unit> {
|
private suspend fun saveTunnelFromConfUri(name: String, uri: Uri, configType: ConfigType): Result<Unit> {
|
||||||
val stream = getInputStreamFromUri(uri)
|
val stream = getInputStreamFromUri(uri)
|
||||||
return if (stream != null) {
|
return if (stream != null) {
|
||||||
saveTunnelConfigFromStream(stream, name)
|
saveTunnelConfigFromStream(stream, name, configType)
|
||||||
Result.Success(Unit)
|
Result.Success(Unit)
|
||||||
} else {
|
} else {
|
||||||
Result.Error(Event.Error.FileReadFailed)
|
Result.Error(Event.Error.FileReadFailed)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.options
|
package com.zaneschepke.wireguardautotunnel.ui.screens.options
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
@ -7,7 +8,6 @@ import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.IntrinsicSize
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
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.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
@ -24,9 +24,11 @@ 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
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
@ -38,16 +40,22 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
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.focus.onFocusChanged
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
|
import com.iamageo.multifablibrary.FabIcon
|
||||||
|
import com.iamageo.multifablibrary.FabOption
|
||||||
|
import com.iamageo.multifablibrary.MultiFabItem
|
||||||
|
import com.iamageo.multifablibrary.MultiFloatingActionButton
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
||||||
|
@ -55,11 +63,13 @@ import com.zaneschepke.wireguardautotunnel.ui.Screen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.ClickableIconButton
|
import com.zaneschepke.wireguardautotunnel.ui.common.ClickableIconButton
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle
|
import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
|
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.ConfigType
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Result
|
import com.zaneschepke.wireguardautotunnel.util.Result
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun OptionsScreen(
|
fun OptionsScreen(
|
||||||
|
@ -100,6 +110,63 @@ fun OptionsScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
floatingActionButton = {
|
||||||
|
val secondaryColor = MaterialTheme.colorScheme.secondary
|
||||||
|
val hoverColor = MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)
|
||||||
|
var fobColor by remember { mutableStateOf(secondaryColor) }
|
||||||
|
MultiFloatingActionButton(
|
||||||
|
modifier =
|
||||||
|
(if (
|
||||||
|
WireGuardAutoTunnel.isRunningOnAndroidTv()
|
||||||
|
)
|
||||||
|
Modifier.focusRequester(focusRequester)
|
||||||
|
else Modifier)
|
||||||
|
.onFocusChanged {
|
||||||
|
if (WireGuardAutoTunnel.isRunningOnAndroidTv()) {
|
||||||
|
fobColor = if (it.isFocused) hoverColor else secondaryColor
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fabIcon = FabIcon(
|
||||||
|
iconRes = R.drawable.edit,
|
||||||
|
iconResAfterRotate = R.drawable.close,
|
||||||
|
iconRotate = 180f
|
||||||
|
),
|
||||||
|
fabOption = FabOption(
|
||||||
|
iconTint = MaterialTheme.colorScheme.background,
|
||||||
|
backgroundTint = MaterialTheme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
itemsMultiFab = listOf(
|
||||||
|
MultiFabItem(
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
stringResource(id = R.string.amnezia),
|
||||||
|
color = Color.White,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.padding(end = 10.dp)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
icon = R.drawable.edit,
|
||||||
|
value = ConfigType.AMNEZIA.name,
|
||||||
|
),
|
||||||
|
MultiFabItem(
|
||||||
|
label = {
|
||||||
|
Text(stringResource(id = R.string.wireguard), color = Color.White, textAlign = TextAlign.Center, modifier = Modifier.padding(end = 10.dp))
|
||||||
|
},
|
||||||
|
icon = R.drawable.edit,
|
||||||
|
value = ConfigType.WIREGUARD.name
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onFabItemClicked = {
|
||||||
|
val configType = ConfigType.valueOf(it.value)
|
||||||
|
navController.navigate(
|
||||||
|
"${Screen.Config.route}/${tunnelId}?configType=${configType.name}",
|
||||||
|
)
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Top,
|
verticalArrangement = Arrangement.Top,
|
||||||
|
@ -150,23 +217,6 @@ fun OptionsScreen(
|
||||||
padding = screenPadding,
|
padding = screenPadding,
|
||||||
onCheckChanged = { optionsViewModel.onTogglePrimaryTunnel() },
|
onCheckChanged = { optionsViewModel.onTogglePrimaryTunnel() },
|
||||||
)
|
)
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(top = 5.dp),
|
|
||||||
horizontalArrangement = Arrangement.Center,
|
|
||||||
) {
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
navController.navigate(
|
|
||||||
"${Screen.Config.route}/${tunnelId}",
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.edit_tunnel))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Surface(
|
Surface(
|
||||||
|
@ -284,4 +334,5 @@ fun OptionsScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,7 @@ import com.wireguard.android.backend.Tunnel
|
||||||
import com.wireguard.android.backend.WgQuickBackend
|
import com.wireguard.android.backend.WgQuickBackend
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.Screen
|
import com.zaneschepke.wireguardautotunnel.ui.Screen
|
||||||
|
@ -131,11 +132,21 @@ fun SettingsScreen(
|
||||||
|
|
||||||
fun exportAllConfigs() {
|
fun exportAllConfigs() {
|
||||||
try {
|
try {
|
||||||
val files = uiState.tunnels.map { File(context.cacheDir, "${it.name}.conf") }
|
val wgFiles = uiState.tunnels.map { config ->
|
||||||
files.forEachIndexed { index, file ->
|
val file = File(context.cacheDir, "${config.name}-wg.conf")
|
||||||
file.outputStream().use { it.write(uiState.tunnels[index].wgQuick.toByteArray()) }
|
file.outputStream().use {
|
||||||
|
it.write(config.wgQuick.toByteArray())
|
||||||
}
|
}
|
||||||
FileUtils.saveFilesToZip(context, files)
|
file
|
||||||
|
}
|
||||||
|
val amFiles = uiState.tunnels.mapNotNull { config -> if(config.amQuick != TunnelConfig.AM_QUICK_DEFAULT) {
|
||||||
|
val file = File(context.cacheDir, "${config.name}-am.conf")
|
||||||
|
file.outputStream().use {
|
||||||
|
it.write(config.amQuick.toByteArray())
|
||||||
|
}
|
||||||
|
file
|
||||||
|
} else null }
|
||||||
|
FileUtils.saveFilesToZip(context, wgFiles + amFiles)
|
||||||
didExportFiles = true
|
didExportFiles = true
|
||||||
appViewModel.showSnackbarMessage(Event.Message.ConfigsExported.message)
|
appViewModel.showSnackbarMessage(Event.Message.ConfigsExported.message)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
|
@ -25,7 +25,7 @@ object Constants {
|
||||||
const val SUBSCRIPTION_TIMEOUT = 5_000L
|
const val SUBSCRIPTION_TIMEOUT = 5_000L
|
||||||
const val FOCUS_REQUEST_DELAY = 500L
|
const val FOCUS_REQUEST_DELAY = 500L
|
||||||
|
|
||||||
const val BACKUP_PING_HOST = "1.1.1.1"
|
const val DEFAULT_PING_IP = "1.1.1.1"
|
||||||
const val PING_TIMEOUT = 5_000L
|
const val PING_TIMEOUT = 5_000L
|
||||||
const val VPN_RESTART_DELAY = 1_000L
|
const val VPN_RESTART_DELAY = 1_000L
|
||||||
const val PING_INTERVAL = 60_000L
|
const val PING_INTERVAL = 60_000L
|
||||||
|
@ -37,4 +37,6 @@ object Constants {
|
||||||
|
|
||||||
const val UNREADABLE_SSID = "<unknown ssid>"
|
const val UNREADABLE_SSID = "<unknown ssid>"
|
||||||
|
|
||||||
|
val amneziaProperties = listOf("Jc", "Jmin", "Jmax", "S1", "S2", "H1", "H2", "H3", "H4")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ sealed class Event {
|
||||||
|
|
||||||
data object FileReadFailed : Error() {
|
data object FileReadFailed : Error() {
|
||||||
override val message: String
|
override val message: String
|
||||||
get() = WireGuardAutoTunnel.instance.getString(R.string.error_file_extension)
|
get() = WireGuardAutoTunnel.instance.getString(R.string.error_file_format)
|
||||||
}
|
}
|
||||||
|
|
||||||
data object AuthenticationFailed : Error() {
|
data object AuthenticationFailed : Error() {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.zaneschepke.wireguardautotunnel.util
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
|
import com.wireguard.config.Peer
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
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
|
||||||
|
@ -9,6 +10,8 @@ import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.amnezia.awg.config.Config
|
||||||
|
import timber.log.Timber
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
@ -69,3 +72,18 @@ fun TunnelStatistics.PeerStats.handshakeStatus(): HandshakeStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Config.toWgQuickString() : String {
|
||||||
|
val amQuick = toAwgQuickString()
|
||||||
|
val lines = amQuick.lines().toMutableList()
|
||||||
|
val linesIterator = lines.iterator()
|
||||||
|
while(linesIterator.hasNext()) {
|
||||||
|
val next = linesIterator.next()
|
||||||
|
Constants.amneziaProperties.forEach {
|
||||||
|
if(next.startsWith(it, ignoreCase = true)) {
|
||||||
|
linesIterator.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lines.joinToString(System.lineSeparator())
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<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="M440,520L200,520v-80h240v-240h80v240h240v80L520,520v240h-80v-240Z"
|
||||||
|
android:fillColor="#e8eaed"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<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="m256,760 l-56,-56 224,-224 -224,-224 56,-56 224,224 224,-224 56,56 -224,224 224,224 -56,56 -224,-224 -224,224Z"
|
||||||
|
android:fillColor="#e8eaed"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<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="M200,760h57l391,-391 -57,-57 -391,391v57ZM120,840v-170l528,-527q12,-11 26.5,-17t30.5,-6q16,0 31,6t26,18l55,56q12,11 17.5,26t5.5,30q0,16 -5.5,30.5T817,313L290,840L120,840ZM760,256 L704,200 760,256ZM619,341 L591,312 648,369 619,341Z"
|
||||||
|
android:fillColor="#e8eaed"/>
|
||||||
|
</vector>
|
|
@ -173,5 +173,8 @@
|
||||||
<string name="unsure_how">if you are unsure how to proceed</string>
|
<string name="unsure_how">if you are unsure how to proceed</string>
|
||||||
<string name="see_the">See the</string>
|
<string name="see_the">See the</string>
|
||||||
<string name="getting_started_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/getting-started.html</string>
|
<string name="getting_started_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/getting-started.html</string>
|
||||||
<string name="getting_started_guide">Getting started guide</string>
|
<string name="getting_started_guide">getting started guide</string>
|
||||||
|
<string name="amnezia" translatable="false">Amnezia</string>
|
||||||
|
<string name="wireguard" translatable="false">WireGuard</string>
|
||||||
|
<string name="error_file_format">Invalid tunnel config format</string>
|
||||||
</resources>
|
</resources>
|
|
@ -16,12 +16,13 @@ junit = "4.13.2"
|
||||||
kotlinx-serialization-json = "1.6.3"
|
kotlinx-serialization-json = "1.6.3"
|
||||||
lifecycle-runtime-compose = "2.7.0"
|
lifecycle-runtime-compose = "2.7.0"
|
||||||
material3 = "1.2.1"
|
material3 = "1.2.1"
|
||||||
|
multifabVersion = "1.0.9"
|
||||||
navigationCompose = "2.7.7"
|
navigationCompose = "2.7.7"
|
||||||
pinLockCompose = "1.0.3"
|
pinLockCompose = "1.0.3"
|
||||||
roomVersion = "2.6.1"
|
roomVersion = "2.6.1"
|
||||||
timber = "5.0.1"
|
timber = "5.0.1"
|
||||||
tunnel = "1.0.20230706"
|
tunnel = "1.0.20230706"
|
||||||
androidGradlePlugin = "8.4.0-rc02"
|
androidGradlePlugin = "8.4.0"
|
||||||
kotlin = "1.9.23"
|
kotlin = "1.9.23"
|
||||||
ksp = "1.9.23-1.0.19"
|
ksp = "1.9.23-1.0.19"
|
||||||
composeBom = "2024.05.00"
|
composeBom = "2024.05.00"
|
||||||
|
@ -87,6 +88,7 @@ pin-lock-compose = { module = "com.zaneschepke:pin_lock_compose", version.ref =
|
||||||
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
|
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
|
||||||
tunnel = { module = "com.wireguard.android:tunnel", version.ref = "tunnel" }
|
tunnel = { module = "com.wireguard.android:tunnel", version.ref = "tunnel" }
|
||||||
|
|
||||||
|
zaneschepke-multifab = { module = "com.zaneschepke:multifab", version.ref = "multifabVersion" }
|
||||||
zxing-core = { module = "com.google.zxing:core", version.ref = "zxingCore" }
|
zxing-core = { module = "com.google.zxing:core", version.ref = "zxingCore" }
|
||||||
zxing-android-embedded = { module = "com.journeyapps:zxing-android-embedded", version.ref = "zxingAndroidEmbedded" }
|
zxing-android-embedded = { module = "com.journeyapps:zxing-android-embedded", version.ref = "zxingAndroidEmbedded" }
|
||||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||||
|
|
Loading…
Reference in New Issue