Compare commits

..

3 Commits

Author SHA1 Message Date
Zane Schepke b3cb7a7988 docs: update README support screenshot 2023-07-23 00:39:30 -04:00
Zane Schepke 6415f49377 chore: add feature graphic and banner to assets 2023-07-23 00:34:17 -04:00
Zane Schepke baed8ff2e7 feat: support for Always-On VPN and Android TV
Added support for Android TV

Added support for Always-On VPN in settings

Fixes bug where handshake notification is not dismissed after successful handshake

Closes #2,  Closes #5
2023-07-23 00:16:16 -04:00
17 changed files with 99 additions and 241 deletions
-7
View File
@@ -16,13 +16,6 @@ WG Tunnel
</span>
<span align="center">
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/N4N8NMJN2)
</span>
<span align="left">
This is an alternative Android Application for [WireGuard](https://www.wireguard.com/) with added features. Built using the [wireguard-android](https://github.com/WireGuard/wireguard-android) library and [Jetpack Compose](https://developer.android.com/jetpack/compose), this application was inspired by the official [WireGuard Android](https://github.com/WireGuard/wireguard-android) app.
+5 -2
View File
@@ -16,8 +16,8 @@ android {
compileSdk = 34
val versionMajor = 2
val versionMinor = 3
val versionPatch = 2
val versionMinor = 2
val versionPatch = 0
val versionBuild = 0
defaultConfig {
@@ -123,6 +123,9 @@ dependencies {
//barcode scanning
implementation("com.google.android.gms:play-services-code-scanner:16.0.0")
}
kapt {
+1 -4
View File
@@ -28,9 +28,6 @@
<uses-feature
android:name="android.hardware.location.gps"
android:required="false" />
<uses-feature
android:name="android.hardware.screen.portrait"
android:required="false" />
<queries>
<intent>
<action android:name="android.intent.action.MAIN" />
@@ -42,7 +39,7 @@
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:banner="@mipmap/ic_banner"
android:banner="@mipmap/ic_launcher_foreground"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
@@ -1,6 +0,0 @@
package com.zaneschepke.wireguardautotunnel
object Constants {
const val VPN_CONNECTIVITY_CHECK_INTERVAL = 3000L;
const val VPN_STATISTIC_CHECK_INTERVAL = 10000L;
}
@@ -8,10 +8,7 @@ import android.content.Intent
import android.os.Bundle
import android.os.PowerManager
import android.os.SystemClock
import androidx.compose.runtime.collectAsState
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.Repository
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
@@ -25,9 +22,6 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.single
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@@ -52,14 +46,15 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
@Inject
lateinit var vpnService : VpnService
private var isWifiConnected = false;
private var isMobileDataConnected = false;
private var currentNetworkSSID = "";
private lateinit var watcherJob : Job;
private lateinit var setting : Settings
private lateinit var tunnelId: String
private var connecting = false
private var disconnecting = false
private var isWifiConnected = false
private var isMobileDataConnected = false
private var wakeLock: PowerManager.WakeLock? = null
private val tag = this.javaClass.name;
@@ -141,9 +136,6 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
watchForMobileDataConnectivityChanges()
}
}
CoroutineScope(watcherJob).launch {
manageVpn()
}
}
}
@@ -157,9 +149,17 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
is NetworkStatus.CapabilitiesChanged -> {
isMobileDataConnected = true
Timber.d("Mobile data capabilities changed")
if(!disconnecting && !connecting) {
if(!isWifiConnected && setting.isTunnelOnMobileDataEnabled
&& vpnService.getState() == Tunnel.State.DOWN)
startVPN()
}
}
is NetworkStatus.Unavailable -> {
isMobileDataConnected = false
if(!disconnecting && !connecting) {
if(!isWifiConnected && vpnService.getState() == Tunnel.State.UP) stopVPN()
}
Timber.d("Lost mobile data connection")
}
}
@@ -176,52 +176,61 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
is NetworkStatus.CapabilitiesChanged -> {
Timber.d("Wifi capabilities changed")
isWifiConnected = true
currentNetworkSSID = wifiService.getNetworkName(it.networkCapabilities) ?: "";
if (!connecting && !disconnecting) {
Timber.d("Not connect and not disconnecting")
val ssid = wifiService.getNetworkName(it.networkCapabilities);
Timber.d("SSID: $ssid")
if (!setting.trustedNetworkSSIDs.contains(ssid) && vpnService.getState() == Tunnel.State.DOWN) {
Timber.d("Starting VPN Tunnel for untrusted network: $ssid")
startVPN()
} else if (!disconnecting && vpnService.getState() == Tunnel.State.UP && setting.trustedNetworkSSIDs.contains(
ssid
)
) {
Timber.d("Stopping VPN Tunnel for trusted network with ssid: $ssid")
stopVPN()
}
}
}
is NetworkStatus.Unavailable -> {
isWifiConnected = false
Timber.d("Lost Wi-Fi connection")
if(!connecting || !disconnecting) {
if(setting.isTunnelOnMobileDataEnabled && vpnService.getState() == Tunnel.State.DOWN
&& isMobileDataConnected){
Timber.d("Wifi not available so starting vpn for mobile data")
startVPN()
}
if(!setting.isTunnelOnMobileDataEnabled && vpnService.getState() == Tunnel.State.UP) {
Timber.d("Lost WiFi connection, disabling vpn")
stopVPN()
}
}
}
}
}
}
private suspend fun manageVpn() {
while(watcherJob.isActive) {
if(setting.isTunnelOnMobileDataEnabled &&
!isWifiConnected &&
isMobileDataConnected
&& vpnService.getState() == Tunnel.State.DOWN) {
startVPN()
} else if(!setting.isTunnelOnMobileDataEnabled &&
!isWifiConnected &&
vpnService.getState() == Tunnel.State.UP) {
stopVPN()
} else if(isWifiConnected &&
!setting.trustedNetworkSSIDs.contains(currentNetworkSSID) &&
(vpnService.getState() != Tunnel.State.UP)) {
startVPN()
} else if((isWifiConnected &&
setting.trustedNetworkSSIDs.contains(currentNetworkSSID)) &&
(vpnService.getState() == Tunnel.State.UP)) {
stopVPN()
}
delay(Constants.VPN_CONNECTIVITY_CHECK_INTERVAL)
private fun startVPN() {
if(!connecting) {
connecting = true
ServiceTracker.actionOnService(
Action.START,
this.applicationContext as Application,
WireGuardTunnelService::class.java,
mapOf(getString(R.string.tunnel_extras_key) to tunnelId))
connecting = false
}
}
private fun startVPN() {
ServiceTracker.actionOnService(
Action.START,
this.applicationContext as Application,
WireGuardTunnelService::class.java,
mapOf(getString(R.string.tunnel_extras_key) to tunnelId))
}
private fun stopVPN() {
ServiceTracker.actionOnService(
Action.STOP,
this.applicationContext as Application,
WireGuardTunnelService::class.java
)
if(!disconnecting) {
disconnecting = true
ServiceTracker.actionOnService(
Action.STOP,
this.applicationContext as Application,
WireGuardTunnelService::class.java
)
disconnecting = false
}
}
}
@@ -5,7 +5,6 @@ import com.wireguard.android.backend.BackendException
import com.wireguard.android.backend.Statistics
import com.wireguard.android.backend.Tunnel
import com.wireguard.crypto.Key
import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
import kotlinx.coroutines.CoroutineScope
@@ -104,7 +103,7 @@ class WireGuardTunnel @Inject constructor(private val backend : Backend,
_handshakeStatus.emit(HandshakeStatus.NOT_STARTED)
}
if(neverHadHandshakeCounter <= HandshakeStatus.NEVER_CONNECTED_TO_UNHEALTHY_TIME_LIMIT_SEC) {
neverHadHandshakeCounter += 10
neverHadHandshakeCounter++
}
return@forEach
}
@@ -115,7 +114,7 @@ class WireGuardTunnel @Inject constructor(private val backend : Backend,
}
}
_lastHandshake.emit(handshakeMap)
delay(Constants.VPN_STATISTIC_CHECK_INTERVAL)
delay(1000)
}
}
}
@@ -6,7 +6,6 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.view.KeyEvent
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
@@ -23,9 +22,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.input.key.onKeyEvent
import com.google.accompanist.navigation.animation.AnimatedNavHost
import com.google.accompanist.navigation.animation.composable
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
@@ -44,8 +40,6 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
import com.zaneschepke.wireguardautotunnel.ui.theme.TransparentSystemBars
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber
import java.lang.IllegalStateException
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@@ -57,8 +51,6 @@ class MainActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberAnimatedNavController()
val focusRequester = remember { FocusRequester() }
WireguardAutoTunnelTheme {
TransparentSystemBars()
@@ -88,25 +80,7 @@ class MainActivity : AppCompatActivity() {
} else requestNotificationPermission()
}
Scaffold(snackbarHost = { SnackbarHost(snackbarHostState)},
modifier = Modifier.onKeyEvent {
if (it.nativeKeyEvent.action == KeyEvent.ACTION_UP) {
when (it.nativeKeyEvent.keyCode) {
KeyEvent.KEYCODE_DPAD_UP -> {
try {
focusRequester.requestFocus()
} catch(e : IllegalStateException) {
Timber.e("No D-Pad focus request modifier added to element on screen")
}
false
} else -> {
false;
}
}
} else {
false
}
},
Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) },
bottomBar = if (vpnIntent == null && notificationPermissionState.status.isGranted) {
{ BottomNavBar(navController, Routes.navItems) }
} else {
@@ -152,7 +126,7 @@ class MainActivity : AppCompatActivity() {
}
}
}) {
MainScreen(padding = padding, snackbarHostState = snackbarHostState, navController = navController, focusRequester = focusRequester)
MainScreen(padding = padding, snackbarHostState = snackbarHostState, navController = navController)
}
composable(Routes.Settings.name, enterTransition = {
when (initialState.destination.route) {
@@ -173,7 +147,7 @@ class MainActivity : AppCompatActivity() {
fadeIn(animationSpec = tween(1000))
}
}
}) { SettingsScreen(padding = padding, snackbarHostState = snackbarHostState, navController = navController, focusRequester = focusRequester) }
}) { SettingsScreen(padding = padding, snackbarHostState = snackbarHostState, navController = navController) }
composable(Routes.Support.name, enterTransition = {
when (initialState.destination.route) {
Routes.Settings.name, Routes.Main.name ->
@@ -186,10 +160,10 @@ class MainActivity : AppCompatActivity() {
fadeIn(animationSpec = tween(1000))
}
}
}) { SupportScreen(padding = padding, focusRequester) }
}) { SupportScreen(padding = padding) }
composable("${Routes.Config.name}/{id}", enterTransition = {
fadeIn(animationSpec = tween(1000))
}) { ConfigScreen(padding = padding, navController = navController, id = it.arguments?.getString("id"), focusRequester = focusRequester)}
}) { ConfigScreen(padding = padding, navController = navController, id = it.arguments?.getString("id"))}
composable("${Routes.Detail.name}/{id}", enterTransition = {
fadeIn(animationSpec = tween(1000))
}) { DetailScreen(padding = padding, id = it.arguments?.getString("id")) }
@@ -1,6 +1,5 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.config
import android.content.pm.PackageManager
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
@@ -25,13 +24,10 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
@@ -52,14 +48,12 @@ import kotlinx.coroutines.launch
fun ConfigScreen(
viewModel: ConfigViewModel = hiltViewModel(),
padding: PaddingValues,
focusRequester: FocusRequester,
navController: NavController,
id : String?
) {
val context = LocalContext.current
val focusManager = LocalFocusManager.current
val keyboardController = LocalSoftwareKeyboardController.current
val scope = rememberCoroutineScope()
val tunnel by viewModel.tunnel.collectAsStateWithLifecycle(null)
@@ -92,7 +86,6 @@ fun ConfigScreen(
horizontalArrangement = Arrangement.SpaceBetween
) {
OutlinedTextField(
modifier = Modifier.focusRequester(focusRequester),
value = tunnelName.value,
onValueChange = {
viewModel.onTunnelNameChange(it)
@@ -165,6 +158,14 @@ fun ConfigScreen(
}
}
}
// LazyColumn(
// modifier = Modifier
// .fillMaxWidth()
// .fillMaxHeight(.75f)
// .padding(horizontal = 14.dp, vertical = 7.dp),
// verticalArrangement = Arrangement.Center,
// horizontalAlignment = Alignment.Start
// ) {
items(packages) { pack ->
Row(
verticalAlignment = Alignment.CenterVertically,
@@ -1,14 +1,12 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.main
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -27,7 +25,6 @@ import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.Circle
import androidx.compose.material.icons.rounded.Delete
import androidx.compose.material.icons.rounded.Edit
import androidx.compose.material.icons.rounded.Info
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FabPosition
@@ -52,10 +49,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
@@ -86,7 +80,6 @@ import kotlinx.coroutines.launch
@Composable
fun MainScreen(
viewModel: MainViewModel = hiltViewModel(), padding: PaddingValues,
focusRequester: FocusRequester,
snackbarHostState: SnackbarHostState, navController: NavController
) {
@@ -263,13 +256,7 @@ fun MainScreen(
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
selectedTunnel = tunnel;
},
onClick = {
if(!context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)){
navController.navigate("${Routes.Detail.name}/${tunnel.id}")
} else {
focusRequester.requestFocus()
}
},
onClick = { navController.navigate("${Routes.Detail.name}/${tunnel.id}") },
rowButton = {
if (tunnel.id == selectedTunnel?.id) {
Row() {
@@ -278,9 +265,7 @@ fun MainScreen(
}) {
Icon(Icons.Rounded.Edit, stringResource(id = R.string.edit))
}
IconButton(
modifier = Modifier.focusable(),
onClick = { viewModel.onDelete(tunnel) }) {
IconButton(onClick = { viewModel.onDelete(tunnel) }) {
Icon(
Icons.Rounded.Delete,
stringResource(id = R.string.delete)
@@ -288,51 +273,12 @@ fun MainScreen(
}
}
} else {
if(context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)){
Row() {
IconButton(modifier = Modifier.focusRequester(focusRequester),onClick = {
navController.navigate("${Routes.Detail.name}/${tunnel.id}")
}) {
Icon(Icons.Rounded.Info, "Info")
}
IconButton(onClick = {
if (state == Tunnel.State.UP && tunnel.name == tunnelName)
scope.launch {
viewModel.showSnackBarMessage(context.resources.getString(R.string.turn_off_tunnel))
} else {
navController.navigate("${Routes.Config.name}/${tunnel.id}")
}
}) {
Icon(Icons.Rounded.Edit, stringResource(id = R.string.edit))
}
IconButton(onClick = {
if (state == Tunnel.State.UP && tunnel.name == tunnelName)
scope.launch {
viewModel.showSnackBarMessage(context.resources.getString(R.string.turn_off_tunnel))
} else {
viewModel.onDelete(tunnel)
}
}) {
Icon(
Icons.Rounded.Delete,
stringResource(id = R.string.delete)
)
}
Switch(
checked = (state == Tunnel.State.UP && tunnel.name == tunnelName),
onCheckedChange = { checked ->
if (checked) viewModel.onTunnelStart(tunnel) else viewModel.onTunnelStop()
}
)
Switch(
checked = (state == Tunnel.State.UP && tunnel.name == tunnelName),
onCheckedChange = { checked ->
if (checked) viewModel.onTunnelStart(tunnel) else viewModel.onTunnelStop()
}
} else {
Switch(
checked = (state == Tunnel.State.UP && tunnel.name == tunnelName),
onCheckedChange = { checked ->
if (checked) viewModel.onTunnelStart(tunnel) else viewModel.onTunnelStop()
}
)
}
)
}
})
}
@@ -2,7 +2,6 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.settings
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.provider.Settings
import androidx.compose.foundation.clickable
@@ -48,8 +47,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
@@ -80,7 +77,6 @@ fun SettingsScreen(
viewModel: SettingsViewModel = hiltViewModel(),
padding: PaddingValues,
navController: NavController,
focusRequester: FocusRequester,
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }
) {
@@ -96,7 +92,6 @@ fun SettingsScreen(
val tunnels by viewModel.tunnels.collectAsStateWithLifecycle(mutableListOf())
val backgroundLocationState =
rememberPermissionState(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
var currentText by remember { mutableStateOf("") }
val scrollState = rememberScrollState()
var isLocationServicesEnabled by remember { mutableStateOf(viewModel.checkLocationServicesEnabled())}
@@ -124,16 +119,6 @@ fun SettingsScreen(
}
}
fun openSettings() {
scope.launch {
val intentSettings =
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intentSettings.data =
Uri.fromParts("package", context.packageName, null)
context.startActivity(intentSettings)
}
}
if(!backgroundLocationState.status.isGranted) {
Column(horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top,
@@ -146,6 +131,7 @@ fun SettingsScreen(
.size(128.dp))
Text(stringResource(R.string.prominent_background_location_title), textAlign = TextAlign.Center, modifier = Modifier.padding(30.dp), fontSize = 20.sp)
Text(stringResource(R.string.prominent_background_location_message), textAlign = TextAlign.Center, modifier = Modifier.padding(30.dp), fontSize = 15.sp)
//Spacer(modifier = Modifier.weight(1f))
Row(
modifier = Modifier
.fillMaxWidth()
@@ -158,8 +144,14 @@ fun SettingsScreen(
}) {
Text(stringResource(id = R.string.no_thanks))
}
Button(modifier = Modifier.focusRequester(focusRequester), onClick = {
openSettings()
Button(onClick = {
scope.launch {
val intentSettings =
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
intentSettings.data =
Uri.fromParts("package", context.packageName, null)
context.startActivity(intentSettings)
}
}) {
Text(stringResource(id = R.string.turn_on))
}
@@ -168,30 +160,6 @@ fun SettingsScreen(
return
}
if(!fineLocationState.status.isGranted) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxSize()
.padding(padding)
) {
Text(
stringResource(id = R.string.precise_location_message),
textAlign = TextAlign.Center,
modifier = Modifier.padding(15.dp),
fontStyle = FontStyle.Italic
)
Button(modifier = Modifier.focusRequester(focusRequester),onClick = {
fineLocationState.launchPermissionRequest()
}) {
Text(stringResource(id = R.string.request))
}
}
return
}
if (tunnels.isEmpty()) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
@@ -223,7 +191,7 @@ fun SettingsScreen(
modifier = Modifier.padding(15.dp),
fontStyle = FontStyle.Italic
)
Button(modifier = Modifier.focusRequester(focusRequester), onClick = {
Button(onClick = {
val locationServicesEnabled = viewModel.checkLocationServicesEnabled()
isLocationServicesEnabled = locationServicesEnabled
if(!locationServicesEnabled) {
@@ -258,7 +226,6 @@ fun SettingsScreen(
) {
Text(stringResource(R.string.enable_auto_tunnel))
Switch(
modifier = Modifier.focusRequester(focusRequester),
enabled = !settings.isAlwaysOnVpnEnabled,
checked = settings.isAutoTunnelEnabled,
onCheckedChange = {
@@ -278,9 +245,7 @@ fun SettingsScreen(
onExpandedChange = {
if(!(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled)) {
expanded = !expanded }},
modifier = Modifier.padding(start = 15.dp, top = 5.dp, bottom = 10.dp).clickable {
expanded = !expanded
},
modifier = Modifier.padding(start = 15.dp, top = 5.dp, bottom = 10.dp),
) {
TextField(
enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled),
@@ -1,10 +1,8 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.support
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@@ -19,12 +17,8 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@@ -37,7 +31,7 @@ import androidx.compose.ui.unit.sp
import com.zaneschepke.wireguardautotunnel.R
@Composable
fun SupportScreen(padding : PaddingValues, focusRequester: FocusRequester) {
fun SupportScreen(padding : PaddingValues) {
val context = LocalContext.current
@@ -52,7 +46,6 @@ fun SupportScreen(padding : PaddingValues, focusRequester: FocusRequester) {
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.focusable()
.padding(padding)) {
Text(stringResource(R.string.support_text), textAlign = TextAlign.Center, modifier = Modifier.padding(30.dp), fontSize = 15.sp)
Row(
@@ -67,16 +60,11 @@ fun SupportScreen(padding : PaddingValues, focusRequester: FocusRequester) {
}) {
Icon(imageVector = ImageVector.vectorResource(R.drawable.discord), "Discord")
}
IconButton(modifier = Modifier.focusRequester(focusRequester),onClick = {
IconButton(onClick = {
openWebPage(context.resources.getString(R.string.github_url))
}) {
Icon(imageVector = ImageVector.vectorResource(R.drawable.github), "Github")
}
// LaunchedEffect(Unit) {
// if(context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
// focusRequester.requestFocus()
// }
// }
}
Spacer(modifier = Modifier.weight(1f))
Text(stringResource(id = R.string.privacy_policy), style = TextStyle(textDecoration = TextDecoration.Underline),
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_banner_background"/>
<foreground android:drawable="@mipmap/ic_banner_foreground"/>
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_banner_background">#121212</color>
</resources>
-2
View File
@@ -82,6 +82,4 @@
<string name="location_services_not_detected">Unable to detect Location Services which are required for this feature. Please enable Location Services.</string>
<string name="check_again">Check again</string>
<string name="detecting_location_services_disabled">Detecting Location Services disabled</string>
<string name="precise_location_message">This feature requires precise location to access Wi-Fi SSID name. Please enable precise location here or in the app settings.</string>
<string name="request">Request</string>
</resources>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB