mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 205493092b | |||
| 47472f088f | |||
| f5a62cba1b | |||
| 89f6dec357 | |||
| ab7499a616 | |||
| 105c753c66 | |||
| d9f0de2dd4 | |||
| 82280091ad | |||
| b97b7cf989 | |||
| f83e40f6cc | |||
| 1fab9dfdf2 | |||
| a670931b06 |
+2
-1
@@ -1,2 +1,3 @@
|
||||
ko_fi: zaneschepke
|
||||
liberapay: zaneschepke
|
||||
liberapay: zaneschepke
|
||||
github: zaneschepke
|
||||
|
||||
@@ -35,28 +35,27 @@ on:
|
||||
jobs:
|
||||
check_commits:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
new_commits: ${{ steps.check_last_commit.outputs.new_commits }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check for new commits in the last 23 hours
|
||||
id: check_commits
|
||||
id: check_last_commit
|
||||
run: |
|
||||
# Get the current time and the time 23 hours ago in ISO 8601 format
|
||||
now=$(date --utc +%Y-%m-%dT%H:%M:%SZ)
|
||||
past=$(date --utc --date='23 hours ago' +%Y-%m-%dT%H:%M:%SZ)
|
||||
|
||||
# Fetch commit history and check for commits in the last 23 hours
|
||||
if git rev-list --since="$past" --count HEAD > /dev/null; then
|
||||
if git log --since="23 hours ago" --oneline | grep -q .; then
|
||||
echo "New commits found in the last 23 hours."
|
||||
echo "::set-output name=new_commits::true"
|
||||
echo "new_commits=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "No new commits found in the last 23 hours."
|
||||
echo "::set-output name=new_commits::false"
|
||||
echo "No new commits in the last 23 hours."
|
||||
echo "new_commits=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
build:
|
||||
needs: check_commits
|
||||
if: ${{ needs.check_commits.outputs.new_commits == 'true' || github.event_name != 'schedule'}}
|
||||
if: needs.check_commits.outputs.new_commits == 'true'
|
||||
name: Build Signed APK
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
|
||||
@@ -10,10 +10,7 @@
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_WIFI_STATE"
|
||||
android:maxSdkVersion="30"
|
||||
tools:ignore="LeanbackUsesWifi" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
|
||||
@@ -55,6 +55,7 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.config.ConfigScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.MainScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.options.OptionsScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.pinlock.PinLockScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.scanner.ScannerScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.SettingsScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
|
||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.logs.LogsScreen
|
||||
@@ -209,6 +210,9 @@ class MainActivity : AppCompatActivity() {
|
||||
appViewModel = viewModel,
|
||||
)
|
||||
}
|
||||
composable<Route.Scanner> {
|
||||
ScannerScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@ sealed class Route {
|
||||
@Serializable
|
||||
data object Lock : Route()
|
||||
|
||||
@Serializable
|
||||
data object Scanner : Route()
|
||||
|
||||
@Serializable
|
||||
data class Config(
|
||||
val id: Int,
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.common.navigation
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun TopNavBar(title: String, trailing: @Composable () -> Unit = {}) {
|
||||
val navController = LocalNavController.current
|
||||
CenterAlignedTopAppBar(
|
||||
title = {
|
||||
Text(title)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { navController.popBackStack() }) {
|
||||
val icon = Icons.AutoMirrored.Outlined.ArrowBack
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = icon.name,
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
trailing()
|
||||
},
|
||||
)
|
||||
}
|
||||
+26
-9
@@ -34,6 +34,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -85,9 +86,14 @@ fun ConfigScreen(tunnelId: Int, focusRequester: FocusRequester) {
|
||||
var showApplicationsDialog by remember { mutableStateOf(false) }
|
||||
var showAuthPrompt by remember { mutableStateOf(false) }
|
||||
var isAuthenticated by remember { mutableStateOf(false) }
|
||||
var configType by remember { mutableStateOf(ConfigType.WIREGUARD) }
|
||||
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
var configType by remember { mutableStateOf<ConfigType?>(null) }
|
||||
val derivedConfigType = remember {
|
||||
derivedStateOf<ConfigType> {
|
||||
configType ?: if (!uiState.hasAmneziaProperties()) ConfigType.WIREGUARD else ConfigType.AMNEZIA
|
||||
}
|
||||
}
|
||||
val saved by viewModel.saved.collectAsStateWithLifecycle(null)
|
||||
|
||||
LaunchedEffect(saved) {
|
||||
@@ -226,7 +232,7 @@ fun ConfigScreen(tunnelId: Int, focusRequester: FocusRequester) {
|
||||
)
|
||||
ConfigurationToggle(
|
||||
stringResource(id = R.string.show_amnezia_properties),
|
||||
checked = configType == ConfigType.AMNEZIA,
|
||||
checked = derivedConfigType.value == ConfigType.AMNEZIA,
|
||||
padding = screenPadding,
|
||||
onCheckChanged = { configType = if (it) ConfigType.AMNEZIA else ConfigType.WIREGUARD },
|
||||
modifier = Modifier.focusRequester(focusRequester),
|
||||
@@ -239,8 +245,7 @@ fun ConfigScreen(tunnelId: Int, focusRequester: FocusRequester) {
|
||||
hint = stringResource(R.string.tunnel_name).lowercase(),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester),
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
OutlinedTextField(
|
||||
modifier =
|
||||
@@ -344,7 +349,7 @@ fun ConfigScreen(tunnelId: Int, focusRequester: FocusRequester) {
|
||||
modifier = Modifier.width(IntrinsicSize.Min),
|
||||
)
|
||||
}
|
||||
if (configType == ConfigType.AMNEZIA) {
|
||||
if (derivedConfigType.value == ConfigType.AMNEZIA) {
|
||||
ConfigurationTextBox(
|
||||
value = uiState.interfaceProxy.junkPacketCount,
|
||||
onValueChange = viewModel::onJunkPacketCountChanged,
|
||||
@@ -534,15 +539,27 @@ fun ConfigScreen(tunnelId: Int, focusRequester: FocusRequester) {
|
||||
hint = stringResource(R.string.base64_key),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
ConfigurationTextBox(
|
||||
OutlinedTextField(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { showAuthPrompt = true },
|
||||
value = peer.preSharedKey,
|
||||
visualTransformation =
|
||||
if ((tunnelId == Constants.MANUAL_TUNNEL_CONFIG_ID.toInt()) || isAuthenticated) {
|
||||
VisualTransformation.None
|
||||
} else {
|
||||
PasswordVisualTransformation()
|
||||
},
|
||||
enabled = (tunnelId == Constants.MANUAL_TUNNEL_CONFIG_ID.toInt()) || isAuthenticated || peer.preSharedKey.isEmpty(),
|
||||
onValueChange = { value ->
|
||||
viewModel.onPreSharedKeyChange(index, value)
|
||||
},
|
||||
label = { Text(stringResource(R.string.preshared_key)) },
|
||||
singleLine = true,
|
||||
placeholder = { Text(stringResource(R.string.optional)) },
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
label = stringResource(R.string.preshared_key),
|
||||
hint = stringResource(R.string.optional),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
|
||||
+3
@@ -18,6 +18,9 @@ data class ConfigUiState(
|
||||
var tunnelName: String = "",
|
||||
val isAmneziaEnabled: Boolean = false,
|
||||
) {
|
||||
fun hasAmneziaProperties(): Boolean {
|
||||
return this.interfaceProxy.junkPacketCount != ""
|
||||
}
|
||||
companion object {
|
||||
fun from(config: Config): ConfigUiState {
|
||||
val proxyPeers = config.peers.map { PeerProxy.from(it) }
|
||||
|
||||
+7
-23
@@ -36,8 +36,6 @@ import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.journeyapps.barcodescanner.ScanContract
|
||||
import com.journeyapps.barcodescanner.ScanOptions
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
|
||||
@@ -105,15 +103,12 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
||||
viewModel.onTunnelFileSelected(data, context)
|
||||
})
|
||||
|
||||
val scanLauncher =
|
||||
rememberLauncherForActivityResult(
|
||||
contract = ScanContract(),
|
||||
onResult = {
|
||||
if (it.contents != null) {
|
||||
viewModel.onTunnelQrResult(it.contents)
|
||||
}
|
||||
},
|
||||
)
|
||||
val requestPermissionLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.RequestPermission(),
|
||||
) { isGranted ->
|
||||
if (!isGranted) return@rememberLauncherForActivityResult snackbar.showMessage("Camera permission required")
|
||||
navController.navigate(Route.Scanner)
|
||||
}
|
||||
|
||||
VpnDeniedDialog(showVpnPermissionDialog, onDismiss = { showVpnPermissionDialog = false })
|
||||
|
||||
@@ -142,17 +137,6 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
||||
}
|
||||
}
|
||||
|
||||
fun launchQrScanner() {
|
||||
val scanOptions = ScanOptions()
|
||||
scanOptions.setDesiredBarcodeFormats(ScanOptions.QR_CODE)
|
||||
scanOptions.setOrientationLocked(true)
|
||||
scanOptions.setPrompt(
|
||||
context.getString(R.string.scanning_qr),
|
||||
)
|
||||
scanOptions.setBeepEnabled(false)
|
||||
scanLauncher.launch(scanOptions)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier =
|
||||
Modifier.pointerInput(Unit) {
|
||||
@@ -181,7 +165,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
||||
showBottomSheet,
|
||||
onDismiss = { showBottomSheet = false },
|
||||
onFileClick = { tunnelFileImportResultLauncher.launch(Constants.ALLOWED_TV_FILE_TYPES) },
|
||||
onQrClick = { launchQrScanner() },
|
||||
onQrClick = { requestPermissionLauncher.launch(android.Manifest.permission.CAMERA) },
|
||||
onManualImportClick = {
|
||||
navController.navigate(
|
||||
Route.Config(Constants.MANUAL_TUNNEL_CONFIG_ID),
|
||||
|
||||
+18
-25
@@ -19,6 +19,8 @@ import com.zaneschepke.wireguardautotunnel.util.FileReadException
|
||||
import com.zaneschepke.wireguardautotunnel.util.InvalidFileExtensionException
|
||||
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.extractNameAndNumber
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.hasNumberInParentheses
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toWgQuickString
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
@@ -100,28 +102,18 @@ constructor(
|
||||
return defaultName
|
||||
}
|
||||
|
||||
fun onTunnelQrResult(result: String) = viewModelScope.launch(ioDispatcher) {
|
||||
kotlin.runCatching {
|
||||
val amConfig = TunnelConfig.configFromAmQuick(result)
|
||||
val amQuick = amConfig.toAwgQuickString(true)
|
||||
val wgQuick = amConfig.toWgQuickString()
|
||||
|
||||
val tunnelName = makeTunnelNameUnique(generateQrCodeTunnelName(result))
|
||||
val tunnelConfig = TunnelConfig(name = tunnelName, wgQuick = wgQuick, amQuick = amQuick)
|
||||
saveTunnel(tunnelConfig)
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
SnackbarController.showMessage(StringValue.StringResource(R.string.error_invalid_code))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun makeTunnelNameUnique(name: String): String {
|
||||
return withContext(ioDispatcher) {
|
||||
val tunnels = appDataRepository.tunnels.getAll()
|
||||
var tunnelName = name
|
||||
var num = 1
|
||||
while (tunnels.any { it.name == tunnelName }) {
|
||||
tunnelName = "$name($num)"
|
||||
tunnelName = if (!tunnelName.hasNumberInParentheses()) {
|
||||
"$name($num)"
|
||||
} else {
|
||||
val pair = tunnelName.extractNameAndNumber()
|
||||
"${pair?.first}($num)"
|
||||
}
|
||||
num++
|
||||
}
|
||||
tunnelName
|
||||
@@ -248,14 +240,15 @@ constructor(
|
||||
|
||||
private fun saveSettings(settings: Settings) = viewModelScope.launch { appDataRepository.settings.save(settings) }
|
||||
|
||||
fun onCopyTunnel(tunnel: TunnelConfig?) = viewModelScope.launch {
|
||||
tunnel?.let {
|
||||
saveTunnel(
|
||||
TunnelConfig(
|
||||
name = it.name.plus(NumberUtils.randomThree()),
|
||||
wgQuick = it.wgQuick,
|
||||
),
|
||||
)
|
||||
}
|
||||
fun onCopyTunnel(tunnel: TunnelConfig) = viewModelScope.launch {
|
||||
saveTunnel(
|
||||
tunnel.copy(
|
||||
id = 0,
|
||||
isPrimaryTunnel = false,
|
||||
isMobileDataTunnel = false,
|
||||
isActive = false,
|
||||
name = makeTunnelNameUnique(tunnel.name),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -21,6 +21,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.outlined.Add
|
||||
import androidx.compose.material.icons.outlined.Edit
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.scanner
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.journeyapps.barcodescanner.CompoundBarcodeView
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
fun ScannerScreen(viewModel: ScannerViewModel = hiltViewModel()) {
|
||||
val context = LocalContext.current
|
||||
val navController = LocalNavController.current
|
||||
|
||||
val success = viewModel.success.collectAsStateWithLifecycle(null)
|
||||
|
||||
LaunchedEffect(success.value) {
|
||||
if (success.value != null) navController.popBackStack()
|
||||
}
|
||||
|
||||
val barcodeView = remember {
|
||||
CompoundBarcodeView(context).apply {
|
||||
this.initializeFromIntent((context as Activity).intent)
|
||||
this.setStatusText("")
|
||||
this.decodeSingle { result ->
|
||||
result.text?.let { barCodeOrQr ->
|
||||
viewModel.onTunnelQrResult(barCodeOrQr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
AndroidView(factory = { barcodeView })
|
||||
DisposableEffect(Unit) {
|
||||
barcodeView.resume()
|
||||
onDispose {
|
||||
barcodeView.pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package com.zaneschepke.wireguardautotunnel.ui.screens.scanner
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.zaneschepke.wireguardautotunnel.R
|
||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toWgQuickString
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class ScannerViewModel @Inject
|
||||
constructor(
|
||||
private val appDataRepository: AppDataRepository,
|
||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _success = MutableSharedFlow<Boolean>()
|
||||
val success = _success.asSharedFlow()
|
||||
|
||||
private suspend fun makeTunnelNameUnique(name: String): String {
|
||||
return withContext(ioDispatcher) {
|
||||
val tunnels = appDataRepository.tunnels.getAll()
|
||||
var tunnelName = name
|
||||
var num = 1
|
||||
while (tunnels.any { it.name == tunnelName }) {
|
||||
tunnelName = "$name($num)"
|
||||
num++
|
||||
}
|
||||
tunnelName
|
||||
}
|
||||
}
|
||||
|
||||
fun onTunnelQrResult(result: String) = viewModelScope.launch(ioDispatcher) {
|
||||
kotlin.runCatching {
|
||||
val amConfig = TunnelConfig.configFromAmQuick(result)
|
||||
val amQuick = amConfig.toAwgQuickString(true)
|
||||
val wgQuick = amConfig.toWgQuickString()
|
||||
val tunnelName = makeTunnelNameUnique(generateQrCodeDefaultName(result))
|
||||
val tunnelConfig = TunnelConfig(name = tunnelName, wgQuick = wgQuick, amQuick = amQuick)
|
||||
appDataRepository.tunnels.save(tunnelConfig)
|
||||
_success.emit(true)
|
||||
}.onFailure {
|
||||
_success.emit(false)
|
||||
Timber.e(it)
|
||||
SnackbarController.showMessage(StringValue.StringResource(R.string.error_invalid_code))
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateQrCodeDefaultName(config: String): String {
|
||||
return try {
|
||||
TunnelConfig.configFromAmQuick(config).peers[0].endpoint.get().host
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
NumberUtils.generateRandomTunnelName()
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -3,6 +3,8 @@ package com.zaneschepke.wireguardautotunnel.util.extensions
|
||||
import timber.log.Timber
|
||||
import java.util.regex.Pattern
|
||||
|
||||
val hasNumberInParentheses = """^(.+?)\((\d+)\)$""".toRegex()
|
||||
|
||||
fun String.isValidIpv4orIpv6Address(): Boolean {
|
||||
val ipv4Pattern = Pattern.compile(
|
||||
"^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\$",
|
||||
@@ -13,6 +15,18 @@ fun String.isValidIpv4orIpv6Address(): Boolean {
|
||||
return ipv4Pattern.matcher(this).matches() || ipv6Pattern.matcher(this).matches()
|
||||
}
|
||||
|
||||
fun String.hasNumberInParentheses(): Boolean {
|
||||
return hasNumberInParentheses.matches(this)
|
||||
}
|
||||
|
||||
// Function to extract name and number
|
||||
fun String.extractNameAndNumber(): Pair<String, Int>? {
|
||||
val matchResult = hasNumberInParentheses.matchEntire(this)
|
||||
return matchResult?.let {
|
||||
Pair(it.groupValues[1], it.groupValues[2].toInt())
|
||||
}
|
||||
}
|
||||
|
||||
fun List<String>.isMatchingToWildcardList(value: String): Boolean {
|
||||
val excludeValues = this.filter { it.startsWith("!") }.map { it.removePrefix("!").toRegexWithWildcards() }
|
||||
Timber.d("Excluded values: $excludeValues")
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.zaneschepke.wireguardautotunnel.util.extensions
|
||||
|
||||
import androidx.navigation.NavController
|
||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||
|
||||
fun NavController.navigateAndForget(route: Route) {
|
||||
navigate(route) {
|
||||
popUpTo(0)
|
||||
}
|
||||
}
|
||||
@@ -199,4 +199,5 @@
|
||||
<string name="never">never</string>
|
||||
<string name="sec">sec</string>
|
||||
<string name="handshake">handshake</string>
|
||||
<string name="logs">Logs</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
object Constants {
|
||||
const val VERSION_NAME = "3.5.3"
|
||||
const val VERSION_NAME = "3.5.4"
|
||||
const val JVM_TARGET = "17"
|
||||
const val VERSION_CODE = 35300
|
||||
const val VERSION_CODE = 35400
|
||||
const val TARGET_SDK = 34
|
||||
const val MIN_SDK = 26
|
||||
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
What's new:
|
||||
- Fix Android 12 crashing issue
|
||||
- Fix copy tunnel bug
|
||||
- Auto toggle Amnezia props
|
||||
- Hide preshared key without auth
|
||||
@@ -0,0 +1,14 @@
|
||||
Features
|
||||
|
||||
- Add tunnels via .conf file, zip, manual entry, or QR code
|
||||
- Auto connect to VPN based on Wi-Fi SSID, ethernet, or mobile data
|
||||
- Split tunneling by application with search
|
||||
- WireGuard support for kernel and userspace modes
|
||||
- Amnezia support for userspace mode for DPI/censorship protection
|
||||
- Always-On VPN support
|
||||
- Export Amnezia and WireGuard tunnels to zip
|
||||
- Quick tile support for VPN toggling
|
||||
- Static shortcuts support for primary tunnel for automation integration
|
||||
- Intent automation support for all tunnels
|
||||
- Automatic service restart after reboot
|
||||
- Battery preservation measures
|
||||
Reference in New Issue
Block a user