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
This commit is contained in:
parent
f6612abe28
commit
baed8ff2e7
|
@ -29,8 +29,8 @@ This is an alternative Android Application for [WireGuard](https://www.wireguard
|
||||||
<p float="center">
|
<p float="center">
|
||||||
<img label="Main" style="padding-right:25px" src="asset/main_screen.png" width="200" />
|
<img label="Main" style="padding-right:25px" src="asset/main_screen.png" width="200" />
|
||||||
<img label="Config" style="padding-left:25px" src="./asset/config_screen.png" width="200" />
|
<img label="Config" style="padding-left:25px" src="./asset/config_screen.png" width="200" />
|
||||||
<img label="Settings" style="padding-left:25px" src="./asset/settings_screen.png" width="200" />
|
<img label="Settings" style="padding-left:25px" src="asset/settings_screen.png" width="200" />
|
||||||
<img label="Support" style="padding-left:25px" src="./asset/support_screen.png" width="200" />
|
<img label="Support" style="padding-left:25px" src="asset/support_screen.png" width="200" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<span align="left">
|
<span align="left">
|
||||||
|
@ -44,6 +44,7 @@ The inspiration for this app came from the inconvenience of constantly having to
|
||||||
* Add tunnels via .conf file
|
* Add tunnels via .conf file
|
||||||
* Auto connect to VPN based on Wi-Fi SSID
|
* Auto connect to VPN based on Wi-Fi SSID
|
||||||
* Split tunneling by application
|
* Split tunneling by application
|
||||||
|
* Always-on VPN for Android support
|
||||||
* Configurable Trusted Network list
|
* Configurable Trusted Network list
|
||||||
* Optional auto connect on mobile data
|
* Optional auto connect on mobile data
|
||||||
* Automatic service restart after reboot
|
* Automatic service restart after reboot
|
||||||
|
|
|
@ -16,8 +16,8 @@ android {
|
||||||
compileSdk = 34
|
compileSdk = 34
|
||||||
|
|
||||||
val versionMajor = 2
|
val versionMajor = 2
|
||||||
val versionMinor = 1
|
val versionMinor = 2
|
||||||
val versionPatch = 4
|
val versionPatch = 0
|
||||||
val versionBuild = 0
|
val versionBuild = 0
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2:8887605597748372702",
|
"id": "2:8887605597748372702",
|
||||||
"lastPropertyId": "8:4981008812459251156",
|
"lastPropertyId": "9:4468844863383145378",
|
||||||
"name": "Settings",
|
"name": "Settings",
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
|
@ -59,6 +59,11 @@
|
||||||
"id": "6:3370284381040192129",
|
"id": "6:3370284381040192129",
|
||||||
"name": "defaultTunnel",
|
"name": "defaultTunnel",
|
||||||
"type": 9
|
"type": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9:4468844863383145378",
|
||||||
|
"name": "isAlwaysOnVpnEnabled",
|
||||||
|
"type": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"relations": []
|
"relations": []
|
||||||
|
|
|
@ -59,11 +59,6 @@
|
||||||
"id": "6:3370284381040192129",
|
"id": "6:3370284381040192129",
|
||||||
"name": "defaultTunnel",
|
"name": "defaultTunnel",
|
||||||
"type": 9
|
"type": 9
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "8:4981008812459251156",
|
|
||||||
"name": "showProminentDisclosure",
|
|
||||||
"type": 1
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"relations": []
|
"relations": []
|
||||||
|
@ -91,7 +86,8 @@
|
||||||
7555225587864607050,
|
7555225587864607050,
|
||||||
969146862000617878,
|
969146862000617878,
|
||||||
5057486545428188436,
|
5057486545428188436,
|
||||||
2814640993034665120
|
2814640993034665120,
|
||||||
|
4981008812459251156
|
||||||
],
|
],
|
||||||
"retiredRelationUids": [],
|
"retiredRelationUids": [],
|
||||||
"version": 1
|
"version": 1
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||||
android:maxSdkVersion="32" />
|
android:maxSdkVersion="32" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"
|
||||||
android:maxSdkVersion="30" />
|
android:maxSdkVersion="30"
|
||||||
|
tools:ignore="LeanbackUsesWifi" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||||
|
@ -19,6 +20,14 @@
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<!--start service on boot permission-->
|
<!--start service on boot permission-->
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||||
|
<!--android tv support-->
|
||||||
|
<uses-feature android:name="android.software.leanback"
|
||||||
|
android:required="false" />
|
||||||
|
<uses-feature android:name="android.hardware.touchscreen"
|
||||||
|
android:required="false" />
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.location.gps"
|
||||||
|
android:required="false" />
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
@ -30,6 +39,7 @@
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:banner="@mipmap/ic_launcher_foreground"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
|
@ -42,6 +52,7 @@
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<service
|
<service
|
||||||
|
@ -52,9 +63,15 @@
|
||||||
</service>
|
</service>
|
||||||
<service
|
<service
|
||||||
android:name=".service.foreground.WireGuardTunnelService"
|
android:name=".service.foreground.WireGuardTunnelService"
|
||||||
|
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:foregroundServiceType="remoteMessaging"
|
android:foregroundServiceType="remoteMessaging"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.net.VpnService"/>
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON"
|
||||||
|
android:value="true"/>
|
||||||
</service>
|
</service>
|
||||||
<service
|
<service
|
||||||
android:name=".service.foreground.WireGuardConnectivityWatcherService"
|
android:name=".service.foreground.WireGuardConnectivityWatcherService"
|
||||||
|
|
|
@ -7,7 +7,6 @@ import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions
|
||||||
import com.google.mlkit.vision.codescanner.GmsBarcodeScanning
|
import com.google.mlkit.vision.codescanner.GmsBarcodeScanning
|
||||||
import com.zaneschepke.wireguardautotunnel.service.barcode.CodeScanner
|
import com.zaneschepke.wireguardautotunnel.service.barcode.CodeScanner
|
||||||
import com.zaneschepke.wireguardautotunnel.service.barcode.QRScanner
|
import com.zaneschepke.wireguardautotunnel.service.barcode.QRScanner
|
||||||
import dagger.Binds
|
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
|
|
@ -4,7 +4,10 @@ import android.app.Service
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import com.zaneschepke.wireguardautotunnel.repository.Repository
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
open class ForegroundService : Service() {
|
open class ForegroundService : Service() {
|
||||||
|
@ -24,6 +27,10 @@ open class ForegroundService : Service() {
|
||||||
when (action) {
|
when (action) {
|
||||||
Action.START.name -> startService(intent.extras)
|
Action.START.name -> startService(intent.extras)
|
||||||
Action.STOP.name -> stopService(intent.extras)
|
Action.STOP.name -> stopService(intent.extras)
|
||||||
|
"android.net.VpnService" -> {
|
||||||
|
Timber.d("Always-on VPN starting service")
|
||||||
|
startService(intent.extras)
|
||||||
|
}
|
||||||
else -> Timber.d("This should never happen. No action in the received intent")
|
else -> Timber.d("This should never happen. No action in the received intent")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -5,9 +5,11 @@ import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.receiver.NotificationActionReceiver
|
import com.zaneschepke.wireguardautotunnel.receiver.NotificationActionReceiver
|
||||||
|
import com.zaneschepke.wireguardautotunnel.repository.Repository
|
||||||
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
@ -26,6 +28,9 @@ class WireGuardTunnelService : ForegroundService() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var vpnService : VpnService
|
lateinit var vpnService : VpnService
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var settingsRepo: Repository<Settings>
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var notificationService : NotificationService
|
lateinit var notificationService : NotificationService
|
||||||
|
|
||||||
|
@ -48,7 +53,16 @@ class WireGuardTunnelService : ForegroundService() {
|
||||||
stopService(extras)
|
stopService(extras)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Timber.e("Tunnel config null")
|
Timber.d("Tunnel config null, starting default tunnel")
|
||||||
|
val settings = settingsRepo.getAll();
|
||||||
|
if(!settings.isNullOrEmpty()) {
|
||||||
|
val setting = settings[0]
|
||||||
|
if(setting.defaultTunnel != null && setting.isAlwaysOnVpnEnabled) {
|
||||||
|
val tunnelConfig = TunnelConfig.from(setting.defaultTunnel!!)
|
||||||
|
tunnelName = tunnelConfig.name
|
||||||
|
vpnService.startTunnel(tunnelConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CoroutineScope(job).launch {
|
CoroutineScope(job).launch {
|
||||||
|
@ -62,6 +76,7 @@ class WireGuardTunnelService : ForegroundService() {
|
||||||
if(!didShowFailedHandshakeNotification) {
|
if(!didShowFailedHandshakeNotification) {
|
||||||
launchVpnConnectionFailedNotification(getString(R.string.initial_connection_failure_message))
|
launchVpnConnectionFailedNotification(getString(R.string.initial_connection_failure_message))
|
||||||
didShowFailedHandshakeNotification = true
|
didShowFailedHandshakeNotification = true
|
||||||
|
didShowConnected = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HandshakeStatus.HEALTHY -> {
|
HandshakeStatus.HEALTHY -> {
|
||||||
|
@ -74,6 +89,7 @@ class WireGuardTunnelService : ForegroundService() {
|
||||||
if(!didShowFailedHandshakeNotification) {
|
if(!didShowFailedHandshakeNotification) {
|
||||||
launchVpnConnectionFailedNotification(getString(R.string.lost_connection_failure_message))
|
launchVpnConnectionFailedNotification(getString(R.string.lost_connection_failure_message))
|
||||||
didShowFailedHandshakeNotification = true
|
didShowFailedHandshakeNotification = true
|
||||||
|
didShowConnected = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,5 +10,6 @@ data class Settings(
|
||||||
var isAutoTunnelEnabled : Boolean = false,
|
var isAutoTunnelEnabled : Boolean = false,
|
||||||
var isTunnelOnMobileDataEnabled : Boolean = false,
|
var isTunnelOnMobileDataEnabled : Boolean = false,
|
||||||
var trustedNetworkSSIDs : MutableList<String> = mutableListOf(),
|
var trustedNetworkSSIDs : MutableList<String> = mutableListOf(),
|
||||||
var defaultTunnel : String? = null
|
var defaultTunnel : String? = null,
|
||||||
|
var isAlwaysOnVpnEnabled : Boolean = false
|
||||||
)
|
)
|
||||||
|
|
|
@ -86,6 +86,7 @@ class MainViewModel @Inject constructor(private val application : Application,
|
||||||
val setting = settings[0]
|
val setting = settings[0]
|
||||||
setting.defaultTunnel = null
|
setting.defaultTunnel = null
|
||||||
setting.isAutoTunnelEnabled = false
|
setting.isAutoTunnelEnabled = false
|
||||||
|
setting.isAlwaysOnVpnEnabled = false
|
||||||
settingsRepo.save(setting)
|
settingsRepo.save(setting)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,7 @@ fun SettingsScreen(
|
||||||
rememberPermissionState(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
|
rememberPermissionState(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
|
||||||
var currentText by remember { mutableStateOf("") }
|
var currentText by remember { mutableStateOf("") }
|
||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
|
var isLocationServicesEnabled by remember { mutableStateOf(viewModel.checkLocationServicesEnabled())}
|
||||||
|
|
||||||
LaunchedEffect(viewState) {
|
LaunchedEffect(viewState) {
|
||||||
if (viewState.showSnackbarMessage) {
|
if (viewState.showSnackbarMessage) {
|
||||||
|
@ -176,6 +177,34 @@ fun SettingsScreen(
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if(!isLocationServicesEnabled) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(padding)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
stringResource(id = R.string.location_services_not_detected),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.padding(15.dp),
|
||||||
|
fontStyle = FontStyle.Italic
|
||||||
|
)
|
||||||
|
Button(onClick = {
|
||||||
|
val locationServicesEnabled = viewModel.checkLocationServicesEnabled()
|
||||||
|
isLocationServicesEnabled = locationServicesEnabled
|
||||||
|
if(!locationServicesEnabled) {
|
||||||
|
scope.launch {
|
||||||
|
viewModel.showSnackBarMessage(context.getString(R.string.detecting_location_services_disabled))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text(stringResource(id = R.string.check_again))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
|
@ -197,6 +226,7 @@ fun SettingsScreen(
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.enable_auto_tunnel))
|
Text(stringResource(R.string.enable_auto_tunnel))
|
||||||
Switch(
|
Switch(
|
||||||
|
enabled = !settings.isAlwaysOnVpnEnabled,
|
||||||
checked = settings.isAutoTunnelEnabled,
|
checked = settings.isAutoTunnelEnabled,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
@ -213,12 +243,12 @@ fun SettingsScreen(
|
||||||
ExposedDropdownMenuBox(
|
ExposedDropdownMenuBox(
|
||||||
expanded = expanded,
|
expanded = expanded,
|
||||||
onExpandedChange = {
|
onExpandedChange = {
|
||||||
if(!settings.isAutoTunnelEnabled) {
|
if(!(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled)) {
|
||||||
expanded = !expanded }},
|
expanded = !expanded }},
|
||||||
modifier = Modifier.padding(start = 15.dp, top = 5.dp, bottom = 10.dp),
|
modifier = Modifier.padding(start = 15.dp, top = 5.dp, bottom = 10.dp),
|
||||||
) {
|
) {
|
||||||
TextField(
|
TextField(
|
||||||
enabled = !settings.isAutoTunnelEnabled,
|
enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled),
|
||||||
value = settings.defaultTunnel?.let {
|
value = settings.defaultTunnel?.let {
|
||||||
TunnelConfig.from(it).name }
|
TunnelConfig.from(it).name }
|
||||||
?: "",
|
?: "",
|
||||||
|
@ -266,11 +296,11 @@ fun SettingsScreen(
|
||||||
scope.launch {
|
scope.launch {
|
||||||
viewModel.onDeleteTrustedSSID(ssid)
|
viewModel.onDeleteTrustedSSID(ssid)
|
||||||
}
|
}
|
||||||
}, text = ssid, icon = Icons.Filled.Close, enabled = !settings.isAutoTunnelEnabled)
|
}, text = ssid, icon = Icons.Filled.Close, enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
enabled = !settings.isAutoTunnelEnabled,
|
enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled),
|
||||||
value = currentText,
|
value = currentText,
|
||||||
onValueChange = { currentText = it },
|
onValueChange = { currentText = it },
|
||||||
label = { Text(stringResource(R.string.add_trusted_ssid)) },
|
label = { Text(stringResource(R.string.add_trusted_ssid)) },
|
||||||
|
@ -306,7 +336,7 @@ fun SettingsScreen(
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.tunnel_mobile_data))
|
Text(stringResource(R.string.tunnel_mobile_data))
|
||||||
Switch(
|
Switch(
|
||||||
enabled = !settings.isAutoTunnelEnabled,
|
enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled),
|
||||||
checked = settings.isTunnelOnMobileDataEnabled,
|
checked = settings.isTunnelOnMobileDataEnabled,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
@ -315,6 +345,24 @@ fun SettingsScreen(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(14.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.always_on_vpn_support))
|
||||||
|
Switch(
|
||||||
|
enabled = !settings.isAutoTunnelEnabled,
|
||||||
|
checked = settings.isAlwaysOnVpnEnabled,
|
||||||
|
onCheckedChange = {
|
||||||
|
scope.launch {
|
||||||
|
viewModel.onToggleAlwaysOnVPN()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings
|
package com.zaneschepke.wireguardautotunnel.ui.screens.settings
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import android.location.LocationManager
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
|
@ -17,6 +19,7 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class SettingsViewModel @Inject constructor(private val application : Application,
|
class SettingsViewModel @Inject constructor(private val application : Application,
|
||||||
private val tunnelRepo : Repository<TunnelConfig>, private val settingsRepo : Repository<Settings>
|
private val tunnelRepo : Repository<TunnelConfig>, private val settingsRepo : Repository<Settings>
|
||||||
|
@ -31,6 +34,7 @@ class SettingsViewModel @Inject constructor(private val application : Applicatio
|
||||||
val viewState get() = _viewState.asStateFlow()
|
val viewState get() = _viewState.asStateFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
checkLocationServicesEnabled()
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
settingsRepo.itemFlow.collect {
|
settingsRepo.itemFlow.collect {
|
||||||
val settings = it.first()
|
val settings = it.first()
|
||||||
|
@ -69,7 +73,7 @@ class SettingsViewModel @Inject constructor(private val application : Applicatio
|
||||||
|
|
||||||
suspend fun toggleAutoTunnel() {
|
suspend fun toggleAutoTunnel() {
|
||||||
if(_settings.value.defaultTunnel.isNullOrEmpty() && !_settings.value.isAutoTunnelEnabled) {
|
if(_settings.value.defaultTunnel.isNullOrEmpty() && !_settings.value.isAutoTunnelEnabled) {
|
||||||
showSnackBarMessage("Please select a tunnel first")
|
showSnackBarMessage(application.getString(R.string.select_tunnel_message))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if(_settings.value.isAutoTunnelEnabled) {
|
if(_settings.value.isAutoTunnelEnabled) {
|
||||||
|
@ -99,8 +103,7 @@ class SettingsViewModel @Inject constructor(private val application : Applicatio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
suspend fun showSnackBarMessage(message : String) {
|
||||||
private suspend fun showSnackBarMessage(message : String) {
|
|
||||||
_viewState.emit(_viewState.value.copy(
|
_viewState.emit(_viewState.value.copy(
|
||||||
showSnackbarMessage = true,
|
showSnackbarMessage = true,
|
||||||
snackbarMessage = message,
|
snackbarMessage = message,
|
||||||
|
@ -118,4 +121,20 @@ class SettingsViewModel @Inject constructor(private val application : Applicatio
|
||||||
showSnackbarMessage = false
|
showSnackbarMessage = false
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun onToggleAlwaysOnVPN() {
|
||||||
|
if(_settings.value.defaultTunnel != null) {
|
||||||
|
_settings.emit(
|
||||||
|
_settings.value.copy(isAlwaysOnVpnEnabled = !_settings.value.isAlwaysOnVpnEnabled)
|
||||||
|
)
|
||||||
|
settingsRepo.save(_settings.value)
|
||||||
|
} else {
|
||||||
|
showSnackBarMessage(application.getString(R.string.select_tunnel_message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun checkLocationServicesEnabled() : Boolean {
|
||||||
|
val locationManager =
|
||||||
|
application.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||||
|
return locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -77,4 +77,9 @@
|
||||||
<string name="failed_connection_to">Failed connection to -</string>
|
<string name="failed_connection_to">Failed connection to -</string>
|
||||||
<string name="initial_connection_failure_message">Attempting to connect to server after 30 seconds of no response.</string>
|
<string name="initial_connection_failure_message">Attempting to connect to server after 30 seconds of no response.</string>
|
||||||
<string name="lost_connection_failure_message">Attempting to reconnect to server after more than one minute of no response.</string>
|
<string name="lost_connection_failure_message">Attempting to reconnect to server after more than one minute of no response.</string>
|
||||||
|
<string name="always_on_vpn_support">Enable Always-On VPN support</string>
|
||||||
|
<string name="select_tunnel_message">Please select a tunnel first</string>
|
||||||
|
<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>
|
||||||
</resources>
|
</resources>
|
Binary file not shown.
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 89 KiB |
Binary file not shown.
After Width: | Height: | Size: 75 KiB |
Binary file not shown.
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 101 KiB |
Binary file not shown.
After Width: | Height: | Size: 94 KiB |
Loading…
Reference in New Issue